Skip to content

Commit 0d20426

Browse files
committed
feat!: use .name property instead of .code for errors (#2655)
JavaScript errors have a `.name` property that can be used to disambiguate the type of error. libp2p has used the `.code` property for this until now, but we will soon use that field to indicate remote errors, so switch to using the `.name` property. BREAKING CHANGE: The `.code` property has been removed from most errors, use `.name` instead
1 parent b435a21 commit 0d20426

File tree

194 files changed

+1862
-1394
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

194 files changed

+1862
-1394
lines changed

packages/connection-encrypter-plaintext/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"build": "aegir build",
4242
"test": "aegir test",
4343
"clean": "aegir clean",
44-
"generate": "protons ./src/pb/index.proto",
44+
"generate": "protons ./src/pb/proto.proto",
4545
"lint": "aegir lint",
4646
"test:chrome": "aegir test -t browser --cov",
4747
"test:chrome-webworker": "aegir test -t webworker",
@@ -59,7 +59,8 @@
5959
"it-protobuf-stream": "^1.1.3",
6060
"it-stream-types": "^2.0.1",
6161
"protons-runtime": "^5.4.0",
62-
"uint8arraylist": "^2.4.8"
62+
"uint8arraylist": "^2.4.8",
63+
"uint8arrays": "^5.1.0"
6364
},
6465
"devDependencies": {
6566
"@libp2p/interface-compliance-tests": "^5.4.12",

packages/connection-encrypter-plaintext/src/pb/proto.ts

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
55
/* eslint-disable @typescript-eslint/no-empty-interface */
66

7-
import { encodeMessage, decodeMessage, message, enumeration } from 'protons-runtime'
8-
import type { Codec } from 'protons-runtime'
7+
import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, enumeration, message } from 'protons-runtime'
8+
import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc'
99
import type { Uint8ArrayList } from 'uint8arraylist'
1010

1111
export interface Exchange {
@@ -36,7 +36,7 @@ export namespace Exchange {
3636
if (opts.lengthDelimited !== false) {
3737
w.ldelim()
3838
}
39-
}, (reader, length) => {
39+
}, (reader, length, opts = {}) => {
4040
const obj: any = {}
4141

4242
const end = length == null ? reader.len : reader.pos + length
@@ -45,15 +45,20 @@ export namespace Exchange {
4545
const tag = reader.uint32()
4646

4747
switch (tag >>> 3) {
48-
case 1:
48+
case 1: {
4949
obj.id = reader.bytes()
5050
break
51-
case 2:
52-
obj.pubkey = PublicKey.codec().decode(reader, reader.uint32())
51+
}
52+
case 2: {
53+
obj.pubkey = PublicKey.codec().decode(reader, reader.uint32(), {
54+
limits: opts.limits?.pubkey
55+
})
5356
break
54-
default:
57+
}
58+
default: {
5559
reader.skipType(tag & 7)
5660
break
61+
}
5762
}
5863
}
5964

@@ -68,8 +73,8 @@ export namespace Exchange {
6873
return encodeMessage(obj, Exchange.codec())
6974
}
7075

71-
export const decode = (buf: Uint8Array | Uint8ArrayList): Exchange => {
72-
return decodeMessage(buf, Exchange.codec())
76+
export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<Exchange>): Exchange => {
77+
return decodeMessage(buf, Exchange.codec(), opts)
7378
}
7479
}
7580

@@ -120,10 +125,10 @@ export namespace PublicKey {
120125
if (opts.lengthDelimited !== false) {
121126
w.ldelim()
122127
}
123-
}, (reader, length) => {
128+
}, (reader, length, opts = {}) => {
124129
const obj: any = {
125130
Type: KeyType.RSA,
126-
Data: new Uint8Array(0)
131+
Data: uint8ArrayAlloc(0)
127132
}
128133

129134
const end = length == null ? reader.len : reader.pos + length
@@ -132,15 +137,18 @@ export namespace PublicKey {
132137
const tag = reader.uint32()
133138

134139
switch (tag >>> 3) {
135-
case 1:
140+
case 1: {
136141
obj.Type = KeyType.codec().decode(reader)
137142
break
138-
case 2:
143+
}
144+
case 2: {
139145
obj.Data = reader.bytes()
140146
break
141-
default:
147+
}
148+
default: {
142149
reader.skipType(tag & 7)
143150
break
151+
}
144152
}
145153
}
146154

@@ -155,7 +163,7 @@ export namespace PublicKey {
155163
return encodeMessage(obj, PublicKey.codec())
156164
}
157165

158-
export const decode = (buf: Uint8Array | Uint8ArrayList): PublicKey => {
159-
return decodeMessage(buf, PublicKey.codec())
166+
export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<PublicKey>): PublicKey => {
167+
return decodeMessage(buf, PublicKey.codec(), opts)
160168
}
161169
}

packages/connection-encrypter-plaintext/test/index.spec.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
/* eslint-env mocha */
22

3-
import {
4-
InvalidCryptoExchangeError,
5-
UnexpectedPeerError
6-
} from '@libp2p/interface'
73
import { mockMultiaddrConnPair } from '@libp2p/interface-compliance-tests/mocks'
84
import { defaultLogger } from '@libp2p/logger'
95
import { peerIdFromBytes } from '@libp2p/peer-id'
@@ -56,7 +52,7 @@ describe('plaintext', () => {
5652
encrypter.secureOutbound(outbound, wrongPeer)
5753
]).then(() => expect.fail('should have failed'), (err) => {
5854
expect(err).to.exist()
59-
expect(err).to.have.property('code', UnexpectedPeerError.code)
55+
expect(err).to.have.property('name', 'UnexpectedPeerError')
6056
})
6157
})
6258

@@ -81,6 +77,6 @@ describe('plaintext', () => {
8177
encrypter.secureInbound(inbound),
8278
encrypterRemote.secureOutbound(outbound, localPeer)
8379
]))
84-
.to.eventually.be.rejected.with.property('code', InvalidCryptoExchangeError.code)
80+
.to.eventually.be.rejected.with.property('name', 'InvalidCryptoExchangeError')
8581
})
8682
})
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* The handshake timed out
3+
*/
4+
export class HandshakeTimeoutError extends Error {
5+
constructor (message = 'Handshake timeout') {
6+
super(message)
7+
this.name = 'HandshakeTimeoutError'
8+
}
9+
}
10+
11+
/**
12+
* The certificate was invalid
13+
*/
14+
export class InvalidCertificateError extends Error {
15+
constructor (message = 'Invalid certificate') {
16+
super(message)
17+
this.name = 'InvalidCertificateError'
18+
}
19+
}

packages/connection-encrypter-tls/src/pb/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
55
/* eslint-disable @typescript-eslint/no-empty-interface */
66

7-
import { type Codec, decodeMessage, encodeMessage, enumeration, message } from 'protons-runtime'
7+
import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, enumeration, message } from 'protons-runtime'
88
import type { Uint8ArrayList } from 'uint8arraylist'
99

1010
export enum KeyType {
@@ -54,7 +54,7 @@ export namespace PublicKey {
5454
if (opts.lengthDelimited !== false) {
5555
w.ldelim()
5656
}
57-
}, (reader, length) => {
57+
}, (reader, length, opts = {}) => {
5858
const obj: any = {}
5959

6060
const end = length == null ? reader.len : reader.pos + length
@@ -89,7 +89,7 @@ export namespace PublicKey {
8989
return encodeMessage(obj, PublicKey.codec())
9090
}
9191

92-
export const decode = (buf: Uint8Array | Uint8ArrayList): PublicKey => {
93-
return decodeMessage(buf, PublicKey.codec())
92+
export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<PublicKey>): PublicKey => {
93+
return decodeMessage(buf, PublicKey.codec(), opts)
9494
}
9595
}

packages/connection-encrypter-tls/src/tls.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
*/
2020

2121
import { TLSSocket, type TLSSocketOptions, connect } from 'node:tls'
22-
import { CodeError, serviceCapabilities } from '@libp2p/interface'
22+
import { serviceCapabilities } from '@libp2p/interface'
23+
import { HandshakeTimeoutError } from './errors.js'
2324
import { generateCertificate, verifyPeerCertificate, itToStream, streamToIt } from './utils.js'
2425
import { PROTOCOL } from './index.js'
2526
import type { TLSComponents, TLSInit } from './index.js'
@@ -84,7 +85,7 @@ export class TLS implements ConnectionEncrypter {
8485

8586
return new Promise((resolve, reject) => {
8687
const abortTimeout = setTimeout(() => {
87-
socket.destroy(new CodeError('Handshake timeout', 'ERR_HANDSHAKE_TIMEOUT'))
88+
socket.destroy(new HandshakeTimeoutError())
8889
}, this.timeout)
8990

9091
const verifyRemote = (): void => {

packages/connection-encrypter-tls/src/utils.ts

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Duplex as DuplexStream } from 'node:stream'
22
import { Ed25519PublicKey, Secp256k1PublicKey, marshalPublicKey, supportedKeys, unmarshalPrivateKey, unmarshalPublicKey } from '@libp2p/crypto/keys'
3-
import { CodeError, InvalidCryptoExchangeError, UnexpectedPeerError } from '@libp2p/interface'
3+
import { InvalidCryptoExchangeError, InvalidParametersError, UnexpectedPeerError } from '@libp2p/interface'
44
import { peerIdFromKeys } from '@libp2p/peer-id'
55
import { AsnConvert } from '@peculiar/asn1-schema'
66
import * as asn1X509 from '@peculiar/asn1-x509'
@@ -11,7 +11,8 @@ import { pushable } from 'it-pushable'
1111
import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
1212
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
1313
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
14-
import { KeyType, PublicKey } from '../src/pb/index.js'
14+
import { InvalidCertificateError } from './errors.js'
15+
import { KeyType, PublicKey } from './pb/index.js'
1516
import type { PeerId, PublicKey as Libp2pPublicKey, Logger } from '@libp2p/interface'
1617
import type { Duplex } from 'it-stream-types'
1718
import type { Uint8ArrayList } from 'uint8arraylist'
@@ -33,12 +34,12 @@ export async function verifyPeerCertificate (rawCertificate: Uint8Array, expecte
3334

3435
if (x509Cert.notBefore.getTime() > now) {
3536
log?.error('the certificate was not valid yet')
36-
throw new CodeError('The certificate is not valid yet', 'ERR_INVALID_CERTIFICATE')
37+
throw new InvalidCertificateError('The certificate is not valid yet')
3738
}
3839

3940
if (x509Cert.notAfter.getTime() < now) {
4041
log?.error('the certificate has expired')
41-
throw new CodeError('The certificate has expired', 'ERR_INVALID_CERTIFICATE')
42+
throw new InvalidCertificateError('The certificate has expired')
4243
}
4344

4445
const certSignatureValid = await x509Cert.verify()
@@ -59,7 +60,7 @@ export async function verifyPeerCertificate (rawCertificate: Uint8Array, expecte
5960

6061
if (libp2pPublicKeyExtension == null || libp2pPublicKeyExtension.type !== LIBP2P_PUBLIC_KEY_EXTENSION) {
6162
log?.error('the certificate did not include the libp2p public key extension')
62-
throw new CodeError('The certificate did not include the libp2p public key extension', 'ERR_INVALID_CERTIFICATE')
63+
throw new InvalidCertificateError('The certificate did not include the libp2p public key extension')
6364
}
6465

6566
const { result: libp2pKeySequence } = asn1js.fromBER(libp2pPublicKeyExtension.value)
@@ -104,34 +105,17 @@ export async function verifyPeerCertificate (rawCertificate: Uint8Array, expecte
104105
}
105106

106107
export async function generateCertificate (peerId: PeerId): Promise<{ cert: string, key: string }> {
107-
const now = Date.now()
108-
109-
const alg = {
110-
name: 'ECDSA',
111-
namedCurve: 'P-256',
112-
hash: 'SHA-256'
113-
}
114-
115-
const keys = await crypto.subtle.generateKey(alg, true, ['sign'])
116-
117-
const certPublicKeySpki = await crypto.subtle.exportKey('spki', keys.publicKey)
118-
const dataToSign = encodeSignatureData(certPublicKeySpki)
119-
120108
if (peerId.privateKey == null) {
121-
throw new InvalidCryptoExchangeError('Private key was missing from PeerId')
109+
throw new InvalidParametersError('Private key was missing from PeerId')
122110
}
123111

124-
const privateKey = await unmarshalPrivateKey(peerId.privateKey)
125-
const sig = await privateKey.sign(dataToSign)
126-
127-
let keyType: KeyType
128-
let keyData: Uint8Array
129-
130112
if (peerId.publicKey == null) {
131-
throw new CodeError('Public key missing from PeerId', 'ERR_INVALID_PEER_ID')
113+
throw new InvalidParametersError('Public key missing from PeerId')
132114
}
133115

134116
const publicKey = unmarshalPublicKey(peerId.publicKey)
117+
let keyType: KeyType
118+
let keyData: Uint8Array
135119

136120
if (peerId.type === 'Ed25519') {
137121
// Ed25519: Only the 32 bytes of the public key
@@ -146,9 +130,22 @@ export async function generateCertificate (peerId: PeerId): Promise<{ cert: stri
146130
keyType = KeyType.RSA
147131
keyData = publicKey.marshal()
148132
} else {
149-
throw new CodeError('Unknown PeerId type', 'ERR_UNKNOWN_PEER_ID_TYPE')
133+
throw new InvalidParametersError('PeerId had unknown or unsupported type')
150134
}
151135

136+
const now = Date.now()
137+
138+
const alg = {
139+
name: 'ECDSA',
140+
namedCurve: 'P-256',
141+
hash: 'SHA-256'
142+
}
143+
144+
const keys = await crypto.subtle.generateKey(alg, true, ['sign'])
145+
const certPublicKeySpki = await crypto.subtle.exportKey('spki', keys.publicKey)
146+
const dataToSign = encodeSignatureData(certPublicKeySpki)
147+
const privateKey = await unmarshalPrivateKey(peerId.privateKey)
148+
const sig = await privateKey.sign(dataToSign)
152149
const notAfter = new Date(now + CERT_VALIDITY_PERIOD_TO)
153150
// workaround for https://github.com/PeculiarVentures/x509/issues/73
154151
notAfter.setMilliseconds(0)

packages/connection-encrypter-tls/test/index.spec.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
/* eslint-env mocha */
22

3-
import {
4-
InvalidCryptoExchangeError,
5-
UnexpectedPeerError
6-
} from '@libp2p/interface'
73
import { mockMultiaddrConnPair } from '@libp2p/interface-compliance-tests/mocks'
84
import { defaultLogger } from '@libp2p/logger'
95
import { peerIdFromBytes } from '@libp2p/peer-id'
@@ -51,7 +47,7 @@ describe('tls', () => {
5147
encrypter.secureOutbound(outbound, wrongPeer)
5248
]).then(() => expect.fail('should have failed'), (err) => {
5349
expect(err).to.exist()
54-
expect(err).to.have.property('code', UnexpectedPeerError.code)
50+
expect(err).to.have.property('name', 'UnexpectedPeerError')
5551
})
5652
})
5753

@@ -76,6 +72,6 @@ describe('tls', () => {
7672
encrypter.secureInbound(inbound),
7773
encrypter.secureOutbound(outbound, localPeer)
7874
]))
79-
.to.eventually.be.rejected.with.property('code', InvalidCryptoExchangeError.code)
75+
.to.eventually.be.rejected.with.property('name', 'InvalidParametersError')
8076
})
8177
})

packages/connection-encrypter-tls/test/utils.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ describe('utils', () => {
3030

3131
it('should reject certificate with a the wrong peer id in the extension', async () => {
3232
await expect(verifyPeerCertificate(testVectors.wrongPeerIdInExtension.cert, undefined, logger('libp2p'))).to.eventually.be.rejected
33-
.with.property('code', 'ERR_INVALID_CRYPTO_EXCHANGE')
33+
.with.property('name', 'InvalidCryptoExchangeError')
3434
})
3535

3636
it('should reject certificate with invalid self signature', async () => {
3737
await expect(verifyPeerCertificate(testVectors.invalidCertificateSignature.cert, undefined, logger('libp2p'))).to.eventually.be.rejected
38-
.with.property('code', 'ERR_INVALID_CRYPTO_EXCHANGE')
38+
.with.property('name', 'InvalidCryptoExchangeError')
3939
})
4040

4141
it('should reject certificate with a chain', async () => {
@@ -72,6 +72,6 @@ describe('utils', () => {
7272
})
7373

7474
await expect(verifyPeerCertificate(new Uint8Array(cert.rawData), undefined, logger('libp2p'))).to.eventually.be.rejected
75-
.with.property('code', 'ERR_INVALID_CRYPTO_EXCHANGE')
75+
.with.property('name', 'InvalidCryptoExchangeError')
7676
})
7777
})

packages/crypto/src/errors.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Signing a message failed
3+
*/
4+
export class SigningError extends Error {
5+
constructor (message = 'An error occurred while signing a message') {
6+
super(message)
7+
this.name = 'SigningError'
8+
}
9+
}
10+
11+
/**
12+
* Verifying a message signature failed
13+
*/
14+
export class VerificationError extends Error {
15+
constructor (message = 'An error occurred while verifying a message') {
16+
super(message)
17+
this.name = 'VerificationError'
18+
}
19+
}
20+
21+
/**
22+
* WebCrypto was not available in the current context
23+
*/
24+
export class WebCryptoMissingError extends Error {
25+
constructor (message = 'Missing Web Crypto API') {
26+
super(message)
27+
this.name = 'WebCryptoMissingError'
28+
}
29+
}

0 commit comments

Comments
 (0)