From 0d20426fd5ea19b03345c70289bbd692e4348e1f Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 14 Aug 2024 16:54:10 +0100 Subject: [PATCH] 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 --- .../package.json | 5 +- .../src/pb/proto.ts | 40 ++-- .../test/index.spec.ts | 8 +- .../connection-encrypter-tls/src/errors.ts | 19 ++ .../connection-encrypter-tls/src/pb/index.ts | 8 +- packages/connection-encrypter-tls/src/tls.ts | 5 +- .../connection-encrypter-tls/src/utils.ts | 51 ++-- .../test/index.spec.ts | 8 +- .../test/utils.spec.ts | 6 +- packages/crypto/src/errors.ts | 29 +++ packages/crypto/src/keys/ecdh-browser.ts | 23 +- packages/crypto/src/keys/ecdh.ts | 8 +- packages/crypto/src/keys/ed25519-class.ts | 6 +- packages/crypto/src/keys/index.ts | 10 +- packages/crypto/src/keys/key-stretcher.ts | 23 +- packages/crypto/src/keys/keys.ts | 33 +-- packages/crypto/src/keys/rsa-browser.ts | 8 +- packages/crypto/src/keys/rsa-class.ts | 14 +- packages/crypto/src/keys/rsa-utils.ts | 16 +- packages/crypto/src/keys/rsa.ts | 8 +- packages/crypto/src/keys/secp256k1-browser.ts | 17 +- packages/crypto/src/keys/secp256k1-class.ts | 4 +- packages/crypto/src/keys/secp256k1.ts | 13 +- packages/crypto/src/pbkdf2.ts | 4 +- packages/crypto/src/random-bytes.ts | 4 +- packages/crypto/src/webcrypto-browser.ts | 17 +- packages/crypto/test/crypto.spec.ts | 8 +- .../crypto/test/fixtures/go-elliptic-key.ts | 12 +- .../crypto/test/keys/ephemeral-keys.spec.ts | 7 +- .../crypto/test/keys/key-stretcher.spec.ts | 6 +- packages/crypto/test/keys/rsa.spec.ts | 2 +- packages/crypto/test/keys/secp256k1.spec.ts | 3 +- packages/crypto/test/random-bytes.spec.ts | 6 +- .../test/circuit-relay.node.ts | 6 +- packages/integration-tests/test/fetch.spec.ts | 5 +- .../src/connection-encryption/index.ts | 3 +- .../src/connection/index.ts | 12 +- .../src/mocks/connection-manager.ts | 14 +- .../src/mocks/connection.ts | 4 +- .../src/stream-muxer/close-test.ts | 10 +- .../src/stream-muxer/fixtures/pb/message.ts | 21 +- packages/interface/src/errors.ts | 190 ++++++++++++--- packages/kad-dht/package.json | 6 +- .../kad-dht/src/content-fetching/index.ts | 11 +- packages/kad-dht/src/content-routing/index.ts | 2 +- packages/kad-dht/src/errors.ts | 39 +++ packages/kad-dht/src/kad-dht.ts | 6 +- packages/kad-dht/src/message/dht.ts | 40 +++- packages/kad-dht/src/network.ts | 7 +- packages/kad-dht/src/peer-routing/index.ts | 21 +- packages/kad-dht/src/query/manager.ts | 2 +- packages/kad-dht/src/query/query-path.ts | 5 +- packages/kad-dht/src/record/selectors.ts | 15 +- packages/kad-dht/src/record/validators.ts | 14 +- packages/kad-dht/src/routing-table/index.ts | 4 +- .../kad-dht/src/rpc/handlers/add-provider.ts | 6 +- .../kad-dht/src/rpc/handlers/find-node.ts | 4 +- .../kad-dht/src/rpc/handlers/get-providers.ts | 8 +- .../kad-dht/src/rpc/handlers/get-value.ts | 14 +- .../kad-dht/src/rpc/handlers/put-value.ts | 4 +- packages/kad-dht/test/kad-dht.spec.ts | 12 +- packages/kad-dht/test/query.spec.ts | 2 +- .../kad-dht/test/record/selection.spec.ts | 10 +- .../kad-dht/test/record/validator.spec.ts | 32 +-- .../test/rpc/handlers/add-provider.spec.ts | 6 +- .../test/rpc/handlers/get-providers.spec.ts | 3 +- .../test/rpc/handlers/get-value.spec.ts | 2 +- .../test/rpc/handlers/put-value.spec.ts | 2 +- packages/keychain/package.json | 4 +- packages/keychain/src/errors.ts | 17 -- packages/keychain/src/keychain.ts | 65 +++-- packages/keychain/test/keychain.spec.ts | 74 ++++-- packages/libp2p/package.json | 4 +- packages/libp2p/src/components.ts | 7 +- packages/libp2p/src/config.ts | 7 +- .../connection-manager/connection-pruner.ts | 2 +- .../src/connection-manager/dial-queue.ts | 24 +- .../libp2p/src/connection-manager/index.ts | 7 +- packages/libp2p/src/connection/index.ts | 8 +- packages/libp2p/src/content-routing.ts | 12 +- packages/libp2p/src/errors.ts | 157 +++++++----- packages/libp2p/src/get-peer.ts | 9 +- packages/libp2p/src/libp2p.ts | 13 +- packages/libp2p/src/peer-routing.ts | 12 +- packages/libp2p/src/registrar.ts | 12 +- packages/libp2p/src/transport-manager.ts | 38 ++- packages/libp2p/src/upgrader.ts | 44 ++-- .../connection-manager/dial-queue.spec.ts | 8 +- .../test/connection-manager/direct.node.ts | 15 +- .../test/connection-manager/direct.spec.ts | 23 +- .../test/connection-manager/index.node.ts | 3 +- .../content-routing/content-routing.spec.ts | 6 +- packages/libp2p/test/core/random-walk.spec.ts | 4 +- .../test/core/service-dependencies.spec.ts | 2 +- .../test/peer-routing/peer-routing.spec.ts | 22 +- .../test/transports/transport-manager.spec.ts | 9 +- .../libp2p/test/upgrading/upgrader.spec.ts | 19 +- packages/logger/package.json | 2 +- .../multistream-select/src/multistream.ts | 4 +- packages/multistream-select/src/select.ts | 6 +- .../multistream-select/test/dialer.spec.ts | 2 +- .../test/integration.spec.ts | 6 +- .../test/multistream.spec.ts | 6 +- packages/peer-id-factory/src/proto.ts | 21 +- packages/peer-id/src/index.ts | 8 +- packages/peer-record/src/envelope/envelope.ts | 33 +-- packages/peer-record/src/envelope/errors.ts | 9 + packages/peer-record/src/envelope/index.ts | 5 +- packages/peer-record/src/errors.ts | 3 - .../src/peer-record/peer-record.ts | 46 ++-- packages/peer-record/test/envelope.spec.ts | 3 +- packages/peer-store/package.json | 4 +- packages/peer-store/src/errors.ts | 3 - packages/peer-store/src/index.ts | 2 +- packages/peer-store/src/pb/peer.ts | 119 +++++++--- packages/peer-store/src/store.ts | 7 +- .../peer-store/src/utils/dedupe-addresses.ts | 5 +- .../src/utils/peer-data-to-datastore-peer.ts | 23 +- .../src/utils/peer-id-to-datastore-key.ts | 5 +- packages/peer-store/src/utils/to-peer-pb.ts | 23 +- packages/peer-store/test/index.spec.ts | 8 +- packages/peer-store/test/save.spec.ts | 7 +- packages/pnet/src/errors.ts | 1 - packages/pnet/src/index.ts | 5 +- packages/protocol-autonat/src/autonat.ts | 6 +- packages/protocol-autonat/src/pb/index.ts | 82 ++++--- packages/protocol-dcutr/package.json | 2 +- packages/protocol-dcutr/src/dcutr.ts | 14 +- packages/protocol-dcutr/src/pb/message.ts | 22 +- packages/protocol-echo/package.json | 1 - packages/protocol-fetch/package.json | 2 +- packages/protocol-fetch/src/fetch.ts | 10 +- packages/protocol-fetch/src/pb/proto.ts | 33 +-- packages/protocol-fetch/test/index.spec.ts | 8 +- packages/protocol-identify/package.json | 2 +- packages/protocol-identify/src/identify.ts | 8 +- packages/protocol-identify/src/pb/message.ts | 41 +++- packages/protocol-identify/src/utils.ts | 12 +- packages/protocol-identify/test/index.spec.ts | 6 +- packages/protocol-ping/src/constants.ts | 2 - packages/protocol-ping/src/ping.ts | 14 +- packages/protocol-ping/test/index.spec.ts | 6 +- packages/pubsub-floodsub/src/message/rpc.ts | 224 ++++++++++++------ packages/pubsub/src/errors.ts | 57 ----- packages/pubsub/src/index.ts | 37 ++- packages/pubsub/src/utils.ts | 7 +- packages/pubsub/test/message/rpc.ts | 224 ++++++++++++------ packages/pubsub/test/pubsub.spec.ts | 4 +- .../stream-multiplexer-mplex/src/decode.ts | 7 +- .../stream-multiplexer-mplex/src/errors.ts | 9 + .../stream-multiplexer-mplex/src/mplex.ts | 7 +- .../test/mplex.spec.ts | 4 +- .../test/restrict-size.spec.ts | 6 +- .../test/stream.spec.ts | 6 +- .../src/constants.ts | 5 - .../transport-circuit-relay-v2/src/errors.ts | 19 ++ .../src/pb/index.ts | 6 +- .../src/transport/listener.ts | 4 +- .../src/transport/transport.ts | 10 +- .../transport-circuit-relay-v2/src/utils.ts | 9 +- packages/transport-tcp/src/listener.ts | 14 +- packages/transport-tcp/src/socket-to-conn.ts | 8 +- .../transport-tcp/test/socket-to-conn.spec.ts | 8 +- packages/transport-webrtc/src/error.ts | 82 ++----- packages/transport-webrtc/src/pb/message.ts | 18 +- .../private-to-private/initiate-connection.ts | 13 +- .../src/private-to-private/pb/message.ts | 18 +- .../signaling-stream-handler.ts | 10 +- .../src/private-to-private/transport.ts | 11 +- .../src/private-to-private/util.ts | 6 +- .../src/private-to-public/sdp.ts | 13 +- .../src/private-to-public/transport.ts | 16 +- packages/transport-webrtc/src/stream.ts | 12 +- packages/transport-webrtc/test/stream.spec.ts | 2 +- .../transport-webrtc/test/transport.spec.ts | 2 +- packages/transport-websockets/src/index.ts | 4 +- .../src/socket-to-conn.ts | 4 +- packages/transport-webtransport/src/index.ts | 10 +- .../src/utils/parse-multiaddr.ts | 4 +- .../transport-webtransport/test/browser.ts | 2 +- .../test/fixtures/random-bytes.ts | 5 +- .../test/utils/parse-multiaddr.spec.ts | 4 +- packages/upnp-nat/src/errors.ts | 6 + packages/upnp-nat/src/upnp-nat.ts | 9 +- packages/upnp-nat/test/index.spec.ts | 6 +- packages/utils/src/abstract-stream.ts | 8 +- packages/utils/src/errors.ts | 20 ++ packages/utils/src/ip-port-to-multiaddr.ts | 14 +- packages/utils/src/queue/index.ts | 4 +- packages/utils/src/rate-limiter.ts | 4 +- packages/utils/test/abstract-stream.spec.ts | 8 +- .../utils/test/ip-port-to-multiaddr.spec.ts | 9 +- packages/utils/test/queue.spec.ts | 12 +- packages/utils/test/rate-limiter.spec.ts | 8 +- 194 files changed, 1862 insertions(+), 1394 deletions(-) create mode 100644 packages/connection-encrypter-tls/src/errors.ts create mode 100644 packages/crypto/src/errors.ts create mode 100644 packages/kad-dht/src/errors.ts delete mode 100644 packages/keychain/src/errors.ts create mode 100644 packages/peer-record/src/envelope/errors.ts delete mode 100644 packages/peer-record/src/errors.ts delete mode 100644 packages/peer-store/src/errors.ts delete mode 100644 packages/pubsub/src/errors.ts create mode 100644 packages/stream-multiplexer-mplex/src/errors.ts create mode 100644 packages/transport-circuit-relay-v2/src/errors.ts create mode 100644 packages/upnp-nat/src/errors.ts create mode 100644 packages/utils/src/errors.ts diff --git a/packages/connection-encrypter-plaintext/package.json b/packages/connection-encrypter-plaintext/package.json index e575d3e44b..c094cb7961 100644 --- a/packages/connection-encrypter-plaintext/package.json +++ b/packages/connection-encrypter-plaintext/package.json @@ -41,7 +41,7 @@ "build": "aegir build", "test": "aegir test", "clean": "aegir clean", - "generate": "protons ./src/pb/index.proto", + "generate": "protons ./src/pb/proto.proto", "lint": "aegir lint", "test:chrome": "aegir test -t browser --cov", "test:chrome-webworker": "aegir test -t webworker", @@ -59,7 +59,8 @@ "it-protobuf-stream": "^1.1.3", "it-stream-types": "^2.0.1", "protons-runtime": "^5.4.0", - "uint8arraylist": "^2.4.8" + "uint8arraylist": "^2.4.8", + "uint8arrays": "^5.1.0" }, "devDependencies": { "@libp2p/interface-compliance-tests": "^5.4.12", diff --git a/packages/connection-encrypter-plaintext/src/pb/proto.ts b/packages/connection-encrypter-plaintext/src/pb/proto.ts index d1ebbd510c..3a85c10b42 100644 --- a/packages/connection-encrypter-plaintext/src/pb/proto.ts +++ b/packages/connection-encrypter-plaintext/src/pb/proto.ts @@ -4,8 +4,8 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { encodeMessage, decodeMessage, message, enumeration } from 'protons-runtime' -import type { Codec } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, enumeration, message } from 'protons-runtime' +import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' import type { Uint8ArrayList } from 'uint8arraylist' export interface Exchange { @@ -36,7 +36,7 @@ export namespace Exchange { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -45,15 +45,20 @@ export namespace Exchange { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.id = reader.bytes() break - case 2: - obj.pubkey = PublicKey.codec().decode(reader, reader.uint32()) + } + case 2: { + obj.pubkey = PublicKey.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.pubkey + }) break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -68,8 +73,8 @@ export namespace Exchange { return encodeMessage(obj, Exchange.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Exchange => { - return decodeMessage(buf, Exchange.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Exchange => { + return decodeMessage(buf, Exchange.codec(), opts) } } @@ -120,10 +125,10 @@ export namespace PublicKey { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { Type: KeyType.RSA, - Data: new Uint8Array(0) + Data: uint8ArrayAlloc(0) } const end = length == null ? reader.len : reader.pos + length @@ -132,15 +137,18 @@ export namespace PublicKey { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.Type = KeyType.codec().decode(reader) break - case 2: + } + case 2: { obj.Data = reader.bytes() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -155,7 +163,7 @@ export namespace PublicKey { return encodeMessage(obj, PublicKey.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): PublicKey => { - return decodeMessage(buf, PublicKey.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): PublicKey => { + return decodeMessage(buf, PublicKey.codec(), opts) } } diff --git a/packages/connection-encrypter-plaintext/test/index.spec.ts b/packages/connection-encrypter-plaintext/test/index.spec.ts index 016a060aa8..c2a72f2dbb 100644 --- a/packages/connection-encrypter-plaintext/test/index.spec.ts +++ b/packages/connection-encrypter-plaintext/test/index.spec.ts @@ -1,9 +1,5 @@ /* eslint-env mocha */ -import { - InvalidCryptoExchangeError, - UnexpectedPeerError -} from '@libp2p/interface' import { mockMultiaddrConnPair } from '@libp2p/interface-compliance-tests/mocks' import { defaultLogger } from '@libp2p/logger' import { peerIdFromBytes } from '@libp2p/peer-id' @@ -56,7 +52,7 @@ describe('plaintext', () => { encrypter.secureOutbound(outbound, wrongPeer) ]).then(() => expect.fail('should have failed'), (err) => { expect(err).to.exist() - expect(err).to.have.property('code', UnexpectedPeerError.code) + expect(err).to.have.property('name', 'UnexpectedPeerError') }) }) @@ -81,6 +77,6 @@ describe('plaintext', () => { encrypter.secureInbound(inbound), encrypterRemote.secureOutbound(outbound, localPeer) ])) - .to.eventually.be.rejected.with.property('code', InvalidCryptoExchangeError.code) + .to.eventually.be.rejected.with.property('name', 'InvalidCryptoExchangeError') }) }) diff --git a/packages/connection-encrypter-tls/src/errors.ts b/packages/connection-encrypter-tls/src/errors.ts new file mode 100644 index 0000000000..6336c438fe --- /dev/null +++ b/packages/connection-encrypter-tls/src/errors.ts @@ -0,0 +1,19 @@ +/** + * The handshake timed out + */ +export class HandshakeTimeoutError extends Error { + constructor (message = 'Handshake timeout') { + super(message) + this.name = 'HandshakeTimeoutError' + } +} + +/** + * The certificate was invalid + */ +export class InvalidCertificateError extends Error { + constructor (message = 'Invalid certificate') { + super(message) + this.name = 'InvalidCertificateError' + } +} diff --git a/packages/connection-encrypter-tls/src/pb/index.ts b/packages/connection-encrypter-tls/src/pb/index.ts index 964f210db2..f9da51abfb 100644 --- a/packages/connection-encrypter-tls/src/pb/index.ts +++ b/packages/connection-encrypter-tls/src/pb/index.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { type Codec, decodeMessage, encodeMessage, enumeration, message } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, enumeration, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export enum KeyType { @@ -54,7 +54,7 @@ export namespace PublicKey { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -89,7 +89,7 @@ export namespace PublicKey { return encodeMessage(obj, PublicKey.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): PublicKey => { - return decodeMessage(buf, PublicKey.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): PublicKey => { + return decodeMessage(buf, PublicKey.codec(), opts) } } diff --git a/packages/connection-encrypter-tls/src/tls.ts b/packages/connection-encrypter-tls/src/tls.ts index 9d5b34d913..d64b19033f 100644 --- a/packages/connection-encrypter-tls/src/tls.ts +++ b/packages/connection-encrypter-tls/src/tls.ts @@ -19,7 +19,8 @@ */ import { TLSSocket, type TLSSocketOptions, connect } from 'node:tls' -import { CodeError, serviceCapabilities } from '@libp2p/interface' +import { serviceCapabilities } from '@libp2p/interface' +import { HandshakeTimeoutError } from './errors.js' import { generateCertificate, verifyPeerCertificate, itToStream, streamToIt } from './utils.js' import { PROTOCOL } from './index.js' import type { TLSComponents, TLSInit } from './index.js' @@ -84,7 +85,7 @@ export class TLS implements ConnectionEncrypter { return new Promise((resolve, reject) => { const abortTimeout = setTimeout(() => { - socket.destroy(new CodeError('Handshake timeout', 'ERR_HANDSHAKE_TIMEOUT')) + socket.destroy(new HandshakeTimeoutError()) }, this.timeout) const verifyRemote = (): void => { diff --git a/packages/connection-encrypter-tls/src/utils.ts b/packages/connection-encrypter-tls/src/utils.ts index e95c3694c4..ffeb058a40 100644 --- a/packages/connection-encrypter-tls/src/utils.ts +++ b/packages/connection-encrypter-tls/src/utils.ts @@ -1,6 +1,6 @@ import { Duplex as DuplexStream } from 'node:stream' import { Ed25519PublicKey, Secp256k1PublicKey, marshalPublicKey, supportedKeys, unmarshalPrivateKey, unmarshalPublicKey } from '@libp2p/crypto/keys' -import { CodeError, InvalidCryptoExchangeError, UnexpectedPeerError } from '@libp2p/interface' +import { InvalidCryptoExchangeError, InvalidParametersError, UnexpectedPeerError } from '@libp2p/interface' import { peerIdFromKeys } from '@libp2p/peer-id' import { AsnConvert } from '@peculiar/asn1-schema' import * as asn1X509 from '@peculiar/asn1-x509' @@ -11,7 +11,8 @@ import { pushable } from 'it-pushable' import { concat as uint8ArrayConcat } from 'uint8arrays/concat' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' -import { KeyType, PublicKey } from '../src/pb/index.js' +import { InvalidCertificateError } from './errors.js' +import { KeyType, PublicKey } from './pb/index.js' import type { PeerId, PublicKey as Libp2pPublicKey, Logger } from '@libp2p/interface' import type { Duplex } from 'it-stream-types' import type { Uint8ArrayList } from 'uint8arraylist' @@ -33,12 +34,12 @@ export async function verifyPeerCertificate (rawCertificate: Uint8Array, expecte if (x509Cert.notBefore.getTime() > now) { log?.error('the certificate was not valid yet') - throw new CodeError('The certificate is not valid yet', 'ERR_INVALID_CERTIFICATE') + throw new InvalidCertificateError('The certificate is not valid yet') } if (x509Cert.notAfter.getTime() < now) { log?.error('the certificate has expired') - throw new CodeError('The certificate has expired', 'ERR_INVALID_CERTIFICATE') + throw new InvalidCertificateError('The certificate has expired') } const certSignatureValid = await x509Cert.verify() @@ -59,7 +60,7 @@ export async function verifyPeerCertificate (rawCertificate: Uint8Array, expecte if (libp2pPublicKeyExtension == null || libp2pPublicKeyExtension.type !== LIBP2P_PUBLIC_KEY_EXTENSION) { log?.error('the certificate did not include the libp2p public key extension') - throw new CodeError('The certificate did not include the libp2p public key extension', 'ERR_INVALID_CERTIFICATE') + throw new InvalidCertificateError('The certificate did not include the libp2p public key extension') } const { result: libp2pKeySequence } = asn1js.fromBER(libp2pPublicKeyExtension.value) @@ -104,34 +105,17 @@ export async function verifyPeerCertificate (rawCertificate: Uint8Array, expecte } export async function generateCertificate (peerId: PeerId): Promise<{ cert: string, key: string }> { - const now = Date.now() - - const alg = { - name: 'ECDSA', - namedCurve: 'P-256', - hash: 'SHA-256' - } - - const keys = await crypto.subtle.generateKey(alg, true, ['sign']) - - const certPublicKeySpki = await crypto.subtle.exportKey('spki', keys.publicKey) - const dataToSign = encodeSignatureData(certPublicKeySpki) - if (peerId.privateKey == null) { - throw new InvalidCryptoExchangeError('Private key was missing from PeerId') + throw new InvalidParametersError('Private key was missing from PeerId') } - const privateKey = await unmarshalPrivateKey(peerId.privateKey) - const sig = await privateKey.sign(dataToSign) - - let keyType: KeyType - let keyData: Uint8Array - if (peerId.publicKey == null) { - throw new CodeError('Public key missing from PeerId', 'ERR_INVALID_PEER_ID') + throw new InvalidParametersError('Public key missing from PeerId') } const publicKey = unmarshalPublicKey(peerId.publicKey) + let keyType: KeyType + let keyData: Uint8Array if (peerId.type === 'Ed25519') { // Ed25519: Only the 32 bytes of the public key @@ -146,9 +130,22 @@ export async function generateCertificate (peerId: PeerId): Promise<{ cert: stri keyType = KeyType.RSA keyData = publicKey.marshal() } else { - throw new CodeError('Unknown PeerId type', 'ERR_UNKNOWN_PEER_ID_TYPE') + throw new InvalidParametersError('PeerId had unknown or unsupported type') } + const now = Date.now() + + const alg = { + name: 'ECDSA', + namedCurve: 'P-256', + hash: 'SHA-256' + } + + const keys = await crypto.subtle.generateKey(alg, true, ['sign']) + const certPublicKeySpki = await crypto.subtle.exportKey('spki', keys.publicKey) + const dataToSign = encodeSignatureData(certPublicKeySpki) + const privateKey = await unmarshalPrivateKey(peerId.privateKey) + const sig = await privateKey.sign(dataToSign) const notAfter = new Date(now + CERT_VALIDITY_PERIOD_TO) // workaround for https://github.com/PeculiarVentures/x509/issues/73 notAfter.setMilliseconds(0) diff --git a/packages/connection-encrypter-tls/test/index.spec.ts b/packages/connection-encrypter-tls/test/index.spec.ts index 7d7a6e543b..135d3a3f03 100644 --- a/packages/connection-encrypter-tls/test/index.spec.ts +++ b/packages/connection-encrypter-tls/test/index.spec.ts @@ -1,9 +1,5 @@ /* eslint-env mocha */ -import { - InvalidCryptoExchangeError, - UnexpectedPeerError -} from '@libp2p/interface' import { mockMultiaddrConnPair } from '@libp2p/interface-compliance-tests/mocks' import { defaultLogger } from '@libp2p/logger' import { peerIdFromBytes } from '@libp2p/peer-id' @@ -51,7 +47,7 @@ describe('tls', () => { encrypter.secureOutbound(outbound, wrongPeer) ]).then(() => expect.fail('should have failed'), (err) => { expect(err).to.exist() - expect(err).to.have.property('code', UnexpectedPeerError.code) + expect(err).to.have.property('name', 'UnexpectedPeerError') }) }) @@ -76,6 +72,6 @@ describe('tls', () => { encrypter.secureInbound(inbound), encrypter.secureOutbound(outbound, localPeer) ])) - .to.eventually.be.rejected.with.property('code', InvalidCryptoExchangeError.code) + .to.eventually.be.rejected.with.property('name', 'InvalidParametersError') }) }) diff --git a/packages/connection-encrypter-tls/test/utils.spec.ts b/packages/connection-encrypter-tls/test/utils.spec.ts index 9b1d4c1e7f..14768c584b 100644 --- a/packages/connection-encrypter-tls/test/utils.spec.ts +++ b/packages/connection-encrypter-tls/test/utils.spec.ts @@ -30,12 +30,12 @@ describe('utils', () => { it('should reject certificate with a the wrong peer id in the extension', async () => { await expect(verifyPeerCertificate(testVectors.wrongPeerIdInExtension.cert, undefined, logger('libp2p'))).to.eventually.be.rejected - .with.property('code', 'ERR_INVALID_CRYPTO_EXCHANGE') + .with.property('name', 'InvalidCryptoExchangeError') }) it('should reject certificate with invalid self signature', async () => { await expect(verifyPeerCertificate(testVectors.invalidCertificateSignature.cert, undefined, logger('libp2p'))).to.eventually.be.rejected - .with.property('code', 'ERR_INVALID_CRYPTO_EXCHANGE') + .with.property('name', 'InvalidCryptoExchangeError') }) it('should reject certificate with a chain', async () => { @@ -72,6 +72,6 @@ describe('utils', () => { }) await expect(verifyPeerCertificate(new Uint8Array(cert.rawData), undefined, logger('libp2p'))).to.eventually.be.rejected - .with.property('code', 'ERR_INVALID_CRYPTO_EXCHANGE') + .with.property('name', 'InvalidCryptoExchangeError') }) }) diff --git a/packages/crypto/src/errors.ts b/packages/crypto/src/errors.ts new file mode 100644 index 0000000000..2a8fc8a805 --- /dev/null +++ b/packages/crypto/src/errors.ts @@ -0,0 +1,29 @@ +/** + * Signing a message failed + */ +export class SigningError extends Error { + constructor (message = 'An error occurred while signing a message') { + super(message) + this.name = 'SigningError' + } +} + +/** + * Verifying a message signature failed + */ +export class VerificationError extends Error { + constructor (message = 'An error occurred while verifying a message') { + super(message) + this.name = 'VerificationError' + } +} + +/** + * WebCrypto was not available in the current context + */ +export class WebCryptoMissingError extends Error { + constructor (message = 'Missing Web Crypto API') { + super(message) + this.name = 'WebCryptoMissingError' + } +} diff --git a/packages/crypto/src/keys/ecdh-browser.ts b/packages/crypto/src/keys/ecdh-browser.ts index c26a14375d..3626597f60 100644 --- a/packages/crypto/src/keys/ecdh-browser.ts +++ b/packages/crypto/src/keys/ecdh-browser.ts @@ -1,9 +1,10 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidParametersError } from '@libp2p/interface' import { concat as uint8ArrayConcat } from 'uint8arrays/concat' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { base64urlToBuffer } from '../util.js' import webcrypto from '../webcrypto.js' +import type { Curve } from './ecdh.js' import type { ECDHKey, ECDHKeyPair, JWKEncodedPrivateKey, JWKEncodedPublicKey } from './interface.js' const bits = { @@ -15,9 +16,9 @@ const bits = { const curveTypes = Object.keys(bits) const names = curveTypes.join(' / ') -export async function generateEphmeralKeyPair (curve: string): Promise { +export async function generateEphmeralKeyPair (curve: Curve): Promise { if (curve !== 'P-256' && curve !== 'P-384' && curve !== 'P-521') { - throw new CodeError(`Unknown curve: ${curve}. Must be ${names}`, 'ERR_INVALID_CURVE') + throw new InvalidParametersError(`Unknown curve: ${curve}. Must be ${names}`) } const pair = await webcrypto.get().subtle.generateKey( @@ -94,11 +95,11 @@ const curveLengths = { // go-ipfs uses) function marshalPublicKey (jwk: JsonWebKey): Uint8Array { if (jwk.crv == null || jwk.x == null || jwk.y == null) { - throw new CodeError('JWK was missing components', 'ERR_INVALID_PARAMETERS') + throw new InvalidParametersError('JWK was missing components') } if (jwk.crv !== 'P-256' && jwk.crv !== 'P-384' && jwk.crv !== 'P-521') { - throw new CodeError(`Unknown curve: ${jwk.crv}. Must be ${names}`, 'ERR_INVALID_CURVE') + throw new InvalidParametersError(`Unknown curve: ${jwk.crv}. Must be ${names}`) } const byteLen = curveLengths[jwk.crv] @@ -110,16 +111,18 @@ function marshalPublicKey (jwk: JsonWebKey): Uint8Array { ], 1 + byteLen * 2) } -// Unmarshal converts a point, serialized by Marshal, into an jwk encoded key -function unmarshalPublicKey (curve: string, key: Uint8Array): JWKEncodedPublicKey { +/** + * Unmarshal converts a point, serialized by Marshal, into an jwk encoded key + */ +function unmarshalPublicKey (curve: Curve, key: Uint8Array): JWKEncodedPublicKey { if (curve !== 'P-256' && curve !== 'P-384' && curve !== 'P-521') { - throw new CodeError(`Unknown curve: ${curve}. Must be ${names}`, 'ERR_INVALID_CURVE') + throw new InvalidParametersError(`Unknown curve: ${curve}. Must be ${names}`) } const byteLen = curveLengths[curve] if (!uint8ArrayEquals(key.subarray(0, 1), Uint8Array.from([4]))) { - throw new CodeError('Cannot unmarshal public key - invalid key format', 'ERR_INVALID_KEY_FORMAT') + throw new InvalidParametersError('Cannot unmarshal public key - invalid key format') } return { @@ -131,7 +134,7 @@ function unmarshalPublicKey (curve: string, key: Uint8Array): JWKEncodedPublicKe } } -const unmarshalPrivateKey = (curve: string, key: ECDHKeyPair): JWKEncodedPrivateKey => ({ +const unmarshalPrivateKey = (curve: Curve, key: ECDHKeyPair): JWKEncodedPrivateKey => ({ ...unmarshalPublicKey(curve, key.public), d: uint8ArrayToString(key.private, 'base64url') }) diff --git a/packages/crypto/src/keys/ecdh.ts b/packages/crypto/src/keys/ecdh.ts index cde5ff62b5..1e44303a31 100644 --- a/packages/crypto/src/keys/ecdh.ts +++ b/packages/crypto/src/keys/ecdh.ts @@ -1,7 +1,9 @@ import crypto from 'crypto' -import { CodeError } from '@libp2p/interface' +import { InvalidParametersError } from '@libp2p/interface' import type { ECDHKey, ECDHKeyPair } from './interface.js' +export type Curve = 'P-256' | 'P-384' | 'P-521' + const curves = { 'P-256': 'prime256v1', 'P-384': 'secp384r1', @@ -16,9 +18,9 @@ const names = curveTypes.join(' / ') * * Focuses only on ECDH now, but can be made more general in the future. */ -export async function generateEphmeralKeyPair (curve: string): Promise { +export async function generateEphmeralKeyPair (curve: Curve): Promise { if (curve !== 'P-256' && curve !== 'P-384' && curve !== 'P-521') { - throw new CodeError(`Unknown curve: ${curve}. Must be ${names}`, 'ERR_INVALID_CURVE') + throw new InvalidParametersError(`Unknown curve: ${curve}. Must be ${names}`) } const ecdh = crypto.createECDH(curves[curve]) diff --git a/packages/crypto/src/keys/ed25519-class.ts b/packages/crypto/src/keys/ed25519-class.ts index c4449b7af2..9f12dfe5d9 100644 --- a/packages/crypto/src/keys/ed25519-class.ts +++ b/packages/crypto/src/keys/ed25519-class.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidParametersError } from '@libp2p/interface' import { base58btc } from 'multiformats/bases/base58' import { identity } from 'multiformats/hashes/identity' import { sha256 } from 'multiformats/hashes/sha2' @@ -116,7 +116,7 @@ export class Ed25519PrivateKey implements PrivateKey<'Ed25519'> { if (format === 'libp2p-key') { return exporter(this.bytes, password) } else { - throw new CodeError(`export format '${format}' is not supported`, 'ERR_INVALID_EXPORT_FORMAT') + throw new InvalidParametersError(`export format '${format}' is not supported`) } } } @@ -154,7 +154,7 @@ export async function generateKeyPairFromSeed (seed: Uint8Array): Promise> { +function unsupportedKey (type: string): Error { const supported = Object.keys(supportedKeys).join(' / ') - return new CodeError(`invalid or unsupported key type ${type}. Must be ${supported}`, 'ERR_UNSUPPORTED_KEY_TYPE') + return new InvalidParametersError(`invalid or unsupported key type ${type}. Must be ${supported}`) } function typeToKey (type: string): typeof RSA | typeof Ed25519 | typeof Secp256k1 { @@ -67,7 +67,7 @@ export async function generateKeyPair (type: T, bits?: numb */ export async function generateKeyPairFromSeed (type: T, seed: Uint8Array, bits?: number): Promise> { if (type.toLowerCase() !== 'ed25519') { - throw new CodeError('Seed key derivation is unimplemented for RSA or secp256k1', 'ERR_UNSUPPORTED_KEY_DERIVATION_TYPE') + throw new InvalidParametersError('Seed key derivation is unimplemented for RSA or secp256k1') } return Ed25519.generateKeyPairFromSeed(seed) @@ -143,7 +143,7 @@ export async function importKey (encryptedKey: string, pass } if (!encryptedKey.includes('BEGIN')) { - throw new CodeError('Encrypted key was not a libp2p-key or a PEM file', 'ERR_INVALID_IMPORT_FORMAT') + throw new InvalidParametersError('Encrypted key was not a libp2p-key or a PEM file') } return importFromPem(encryptedKey, password) diff --git a/packages/crypto/src/keys/key-stretcher.ts b/packages/crypto/src/keys/key-stretcher.ts index d6159e1b5a..29258fecae 100644 --- a/packages/crypto/src/keys/key-stretcher.ts +++ b/packages/crypto/src/keys/key-stretcher.ts @@ -1,10 +1,15 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidParametersError } from '@libp2p/interface' import { concat as uint8ArrayConcat } from 'uint8arrays/concat' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import * as hmac from '../hmac/index.js' import type { EnhancedKey, EnhancedKeyPair } from './interface.js' -const cipherMap = { +interface Cipher { + ivSize: number + keySize: number +} + +const cipherMap: Record = { 'AES-128': { ivSize: 16, keySize: 16 @@ -24,17 +29,19 @@ const cipherMap = { * (myIV, theirIV, myCipherKey, theirCipherKey, myMACKey, theirMACKey) */ export async function keyStretcher (cipherType: 'AES-128' | 'AES-256' | 'Blowfish', hash: 'SHA1' | 'SHA256' | 'SHA512', secret: Uint8Array): Promise { - const cipher = cipherMap[cipherType] + if (cipherType !== 'AES-128' && cipherType !== 'AES-256' && cipherType !== 'Blowfish') { + throw new InvalidParametersError('Cipher type was missing or unsupported') + } - if (cipher == null) { - const allowed = Object.keys(cipherMap).join(' / ') - throw new CodeError(`unknown cipher type '${cipherType}'. Must be ${allowed}`, 'ERR_INVALID_CIPHER_TYPE') + if (hash !== 'SHA1' && hash !== 'SHA256' && hash !== 'SHA512') { + throw new InvalidParametersError('Hash type was missing or unsupported') } - if (hash == null) { - throw new CodeError('missing hash type', 'ERR_MISSING_HASH_TYPE') + if (secret == null || !(secret instanceof Uint8Array)) { + throw new InvalidParametersError('Secret was missing or an incorrect type') } + const cipher = cipherMap[cipherType] const cipherKeySize = cipher.keySize const ivSize = cipher.ivSize const hmacKeySize = 20 diff --git a/packages/crypto/src/keys/keys.ts b/packages/crypto/src/keys/keys.ts index 0d9fcd41ed..5890865111 100644 --- a/packages/crypto/src/keys/keys.ts +++ b/packages/crypto/src/keys/keys.ts @@ -4,8 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { enumeration, encodeMessage, decodeMessage, message } from 'protons-runtime' -import type { Codec } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, enumeration, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export enum KeyType { @@ -53,7 +52,7 @@ export namespace PublicKey { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -62,15 +61,18 @@ export namespace PublicKey { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.Type = KeyType.codec().decode(reader) break - case 2: + } + case 2: { obj.Data = reader.bytes() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -85,8 +87,8 @@ export namespace PublicKey { return encodeMessage(obj, PublicKey.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): PublicKey => { - return decodeMessage(buf, PublicKey.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): PublicKey => { + return decodeMessage(buf, PublicKey.codec(), opts) } } @@ -118,7 +120,7 @@ export namespace PrivateKey { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -127,15 +129,18 @@ export namespace PrivateKey { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.Type = KeyType.codec().decode(reader) break - case 2: + } + case 2: { obj.Data = reader.bytes() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -150,7 +155,7 @@ export namespace PrivateKey { return encodeMessage(obj, PrivateKey.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): PrivateKey => { - return decodeMessage(buf, PrivateKey.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): PrivateKey => { + return decodeMessage(buf, PrivateKey.codec(), opts) } } diff --git a/packages/crypto/src/keys/rsa-browser.ts b/packages/crypto/src/keys/rsa-browser.ts index 7fba2781cf..5c4e76a9ca 100644 --- a/packages/crypto/src/keys/rsa-browser.ts +++ b/packages/crypto/src/keys/rsa-browser.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidParametersError } from '@libp2p/interface' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import randomBytes from '../random-bytes.js' import webcrypto from '../webcrypto.js' @@ -102,7 +102,7 @@ export async function hashAndVerify (key: JsonWebKey, sig: Uint8Array, msg: Uint async function exportKey (pair: CryptoKeyPair): Promise<[JsonWebKey, JsonWebKey]> { if (pair.privateKey == null || pair.publicKey == null) { - throw new CodeError('Private and public key are required', 'ERR_INVALID_PARAMETERS') + throw new InvalidParametersError('Private and public key are required') } return Promise.all([ @@ -130,9 +130,9 @@ async function derivePublicFromPrivate (jwKey: JsonWebKey): Promise { export function keySize (jwk: JsonWebKey): number { if (jwk.kty !== 'RSA') { - throw new CodeError('invalid key type', 'ERR_INVALID_KEY_TYPE') + throw new InvalidParametersError('invalid key type') } else if (jwk.n == null) { - throw new CodeError('invalid key modulus', 'ERR_INVALID_KEY_MODULUS') + throw new InvalidParametersError('invalid key modulus') } const bytes = uint8ArrayFromString(jwk.n, 'base64url') return bytes.length * 8 diff --git a/packages/crypto/src/keys/rsa-class.ts b/packages/crypto/src/keys/rsa-class.ts index eae38a95b4..18ce91a896 100644 --- a/packages/crypto/src/keys/rsa-class.ts +++ b/packages/crypto/src/keys/rsa-class.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidParametersError, InvalidPrivateKeyError, InvalidPublicKeyError } from '@libp2p/interface' import { sha256 } from 'multiformats/hashes/sha2' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' @@ -68,7 +68,7 @@ export class RsaPrivateKey implements PrivateKey<'RSA'> { get public (): RsaPublicKey { if (this._publicKey == null) { - throw new CodeError('public key not provided', 'ERR_PUBKEY_NOT_PROVIDED') + throw new InvalidPublicKeyError('public key not provided') } return new RsaPublicKey(this._publicKey) @@ -124,7 +124,7 @@ export class RsaPrivateKey implements PrivateKey<'RSA'> { } else if (format === 'libp2p-key') { return exporter(this.bytes, password) } else { - throw new CodeError(`export format '${format}' is not supported`, 'ERR_INVALID_EXPORT_FORMAT') + throw new InvalidParametersError('Export format is not supported') } } } @@ -133,7 +133,7 @@ export async function unmarshalRsaPrivateKey (bytes: Uint8Array): Promise MAX_RSA_KEY_SIZE) { - throw new CodeError('key size is too large', 'ERR_KEY_SIZE_TOO_LARGE') + throw new InvalidPrivateKeyError('Key size is too large') } const keys = await crypto.unmarshalPrivateKey(jwk) @@ -145,7 +145,7 @@ export function unmarshalRsaPublicKey (bytes: Uint8Array): RsaPublicKey { const jwk = crypto.utils.pkixToJwk(bytes) if (crypto.keySize(jwk) > MAX_RSA_KEY_SIZE) { - throw new CodeError('key size is too large', 'ERR_KEY_SIZE_TOO_LARGE') + throw new InvalidPublicKeyError('Key size is too large') } return new RsaPublicKey(jwk) @@ -153,7 +153,7 @@ export function unmarshalRsaPublicKey (bytes: Uint8Array): RsaPublicKey { export async function fromJwk (jwk: JsonWebKey): Promise { if (crypto.keySize(jwk) > MAX_RSA_KEY_SIZE) { - throw new CodeError('key size is too large', 'ERR_KEY_SIZE_TOO_LARGE') + throw new InvalidParametersError('Key size is too large') } const keys = await crypto.unmarshalPrivateKey(jwk) @@ -163,7 +163,7 @@ export async function fromJwk (jwk: JsonWebKey): Promise { export async function generateKeyPair (bits: number): Promise { if (bits > MAX_RSA_KEY_SIZE) { - throw new CodeError('key size is too large', 'ERR_KEY_SIZE_TOO_LARGE') + throw new InvalidParametersError('Key size is too large') } const keys = await crypto.generateKey(bits) diff --git a/packages/crypto/src/keys/rsa-utils.ts b/packages/crypto/src/keys/rsa-utils.ts index 3520e46b34..e15a599aec 100644 --- a/packages/crypto/src/keys/rsa-utils.ts +++ b/packages/crypto/src/keys/rsa-utils.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidParametersError } from '@libp2p/interface' import { pbkdf2Async } from '@noble/hashes/pbkdf2' import { sha512 } from '@noble/hashes/sha512' import * as asn1js from 'asn1js' @@ -39,7 +39,7 @@ export function pkcs1ToJwk (bytes: Uint8Array): JsonWebKey { */ export function jwkToPkcs1 (jwk: JsonWebKey): Uint8Array { if (jwk.n == null || jwk.e == null || jwk.d == null || jwk.p == null || jwk.q == null || jwk.dp == null || jwk.dq == null || jwk.qi == null) { - throw new CodeError('JWK was missing components', 'ERR_INVALID_PARAMETERS') + throw new InvalidParametersError('JWK was missing components') } const root = new asn1js.Sequence({ @@ -83,7 +83,7 @@ export function pkixToJwk (bytes: Uint8Array): JsonWebKey { */ export function jwkToPkix (jwk: JsonWebKey): Uint8Array { if (jwk.n == null || jwk.e == null) { - throw new CodeError('JWK was missing components', 'ERR_INVALID_PARAMETERS') + throw new InvalidParametersError('JWK was missing components') } const root = new asn1js.Sequence({ @@ -336,7 +336,7 @@ export async function importFromPem (pem: string, password: string): Promise { // Takes a jwk key export async function unmarshalPrivateKey (key: JsonWebKey): Promise { if (key == null) { - throw new CodeError('Missing key parameter', 'ERR_MISSING_KEY') + throw new InvalidParametersError('Missing key parameter') } return { privateKey: key, @@ -76,9 +76,9 @@ export async function hashAndVerify (key: JsonWebKey, sig: Uint8Array, msg: Uint export function keySize (jwk: JsonWebKey): number { if (jwk.kty !== 'RSA') { - throw new CodeError('invalid key type', 'ERR_INVALID_KEY_TYPE') + throw new InvalidParametersError('Invalid key type') } else if (jwk.n == null) { - throw new CodeError('invalid key modulus', 'ERR_INVALID_KEY_MODULUS') + throw new InvalidParametersError('Invalid key modulus') } const modulus = uint8ArrayFromString(jwk.n, 'base64url') return modulus.length * 8 diff --git a/packages/crypto/src/keys/secp256k1-browser.ts b/packages/crypto/src/keys/secp256k1-browser.ts index c12f3f2261..c12dcbd8fd 100644 --- a/packages/crypto/src/keys/secp256k1-browser.ts +++ b/packages/crypto/src/keys/secp256k1-browser.ts @@ -1,6 +1,7 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidPrivateKeyError, InvalidPublicKeyError } from '@libp2p/interface' import { secp256k1 as secp } from '@noble/curves/secp256k1' import { sha256 } from 'multiformats/hashes/sha2' +import { SigningError, VerificationError } from '../errors.js' import { isPromise } from '../util.js' import type { Uint8ArrayList } from 'uint8arraylist' @@ -21,14 +22,14 @@ export function hashAndSign (key: Uint8Array, msg: Uint8Array | Uint8ArrayList): if (isPromise(p)) { return p.then(({ digest }) => secp.sign(digest, key).toDERRawBytes()) .catch(err => { - throw new CodeError(String(err), 'ERR_INVALID_INPUT') + throw new SigningError(String(err)) }) } try { return secp.sign(p.digest, key).toDERRawBytes() } catch (err) { - throw new CodeError(String(err), 'ERR_INVALID_INPUT') + throw new SigningError(String(err)) } } @@ -41,14 +42,14 @@ export function hashAndVerify (key: Uint8Array, sig: Uint8Array, msg: Uint8Array if (isPromise(p)) { return p.then(({ digest }) => secp.verify(sig, digest, key)) .catch(err => { - throw new CodeError(String(err), 'ERR_INVALID_INPUT') + throw new VerificationError(String(err)) }) } try { return secp.verify(sig, p.digest, key) } catch (err) { - throw new CodeError(String(err), 'ERR_INVALID_INPUT') + throw new VerificationError(String(err)) } } @@ -66,7 +67,7 @@ export function validatePrivateKey (key: Uint8Array): void { try { secp.getPublicKey(key, true) } catch (err) { - throw new CodeError(String(err), 'ERR_INVALID_PRIVATE_KEY') + throw new InvalidPrivateKeyError(String(err)) } } @@ -74,7 +75,7 @@ export function validatePublicKey (key: Uint8Array): void { try { secp.ProjectivePoint.fromHex(key) } catch (err) { - throw new CodeError(String(err), 'ERR_INVALID_PUBLIC_KEY') + throw new InvalidPublicKeyError(String(err)) } } @@ -82,6 +83,6 @@ export function computePublicKey (privateKey: Uint8Array): Uint8Array { try { return secp.getPublicKey(privateKey, true) } catch (err) { - throw new CodeError(String(err), 'ERR_INVALID_PRIVATE_KEY') + throw new InvalidPrivateKeyError(String(err)) } } diff --git a/packages/crypto/src/keys/secp256k1-class.ts b/packages/crypto/src/keys/secp256k1-class.ts index 98dcd0b935..2a6d8b994b 100644 --- a/packages/crypto/src/keys/secp256k1-class.ts +++ b/packages/crypto/src/keys/secp256k1-class.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidParametersError } from '@libp2p/interface' import { sha256 } from 'multiformats/hashes/sha2' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' @@ -114,7 +114,7 @@ export class Secp256k1PrivateKey implements PrivateKey<'secp256k1'> { if (format === 'libp2p-key') { return exporter(this.bytes, password) } else { - throw new CodeError(`export format '${format}' is not supported`, 'ERR_INVALID_EXPORT_FORMAT') + throw new InvalidParametersError('Export format is not supported') } } } diff --git a/packages/crypto/src/keys/secp256k1.ts b/packages/crypto/src/keys/secp256k1.ts index a0f297db45..8c9886ecd6 100644 --- a/packages/crypto/src/keys/secp256k1.ts +++ b/packages/crypto/src/keys/secp256k1.ts @@ -1,6 +1,7 @@ import crypto from 'node:crypto' -import { CodeError } from '@libp2p/interface' +import { InvalidPrivateKeyError, InvalidPublicKeyError } from '@libp2p/interface' import { secp256k1 as secp } from '@noble/curves/secp256k1' +import { SigningError, VerificationError } from '../errors.js' import type { Uint8ArrayList } from 'uint8arraylist' const PRIVATE_KEY_BYTE_LENGTH = 32 @@ -31,7 +32,7 @@ export function hashAndSign (key: Uint8Array, msg: Uint8Array | Uint8ArrayList): const signature = secp.sign(digest, key) return signature.toDERRawBytes() } catch (err) { - throw new CodeError(String(err), 'ERR_INVALID_INPUT') + throw new SigningError(String(err)) } } @@ -54,7 +55,7 @@ export function hashAndVerify (key: Uint8Array, sig: Uint8Array, msg: Uint8Array try { return secp.verify(sig, digest, key) } catch (err) { - throw new CodeError(String(err), 'ERR_INVALID_INPUT') + throw new VerificationError(String(err)) } } @@ -72,7 +73,7 @@ export function validatePrivateKey (key: Uint8Array): void { try { secp.getPublicKey(key, true) } catch (err) { - throw new CodeError(String(err), 'ERR_INVALID_PRIVATE_KEY') + throw new InvalidPrivateKeyError(String(err)) } } @@ -80,7 +81,7 @@ export function validatePublicKey (key: Uint8Array): void { try { secp.ProjectivePoint.fromHex(key) } catch (err) { - throw new CodeError(String(err), 'ERR_INVALID_PUBLIC_KEY') + throw new InvalidPublicKeyError(String(err)) } } @@ -88,6 +89,6 @@ export function computePublicKey (privateKey: Uint8Array): Uint8Array { try { return secp.getPublicKey(privateKey, true) } catch (err) { - throw new CodeError(String(err), 'ERR_INVALID_PRIVATE_KEY') + throw new InvalidPrivateKeyError(String(err)) } } diff --git a/packages/crypto/src/pbkdf2.ts b/packages/crypto/src/pbkdf2.ts index f67750c05f..a90dafc7bf 100644 --- a/packages/crypto/src/pbkdf2.ts +++ b/packages/crypto/src/pbkdf2.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidParametersError } from '@libp2p/interface' import { pbkdf2 as pbkdf2Sync } from '@noble/hashes/pbkdf2' import { sha1 } from '@noble/hashes/sha1' import { sha256 } from '@noble/hashes/sha256' @@ -24,7 +24,7 @@ const hashName = { export default function pbkdf2 (password: string, salt: string | Uint8Array, iterations: number, keySize: number, hash: string): string { if (hash !== 'sha1' && hash !== 'sha2-256' && hash !== 'sha2-512') { const types = Object.keys(hashName).join(' / ') - throw new CodeError(`Hash '${hash}' is unknown or not supported. Must be ${types}`, 'ERR_UNSUPPORTED_HASH_TYPE') + throw new InvalidParametersError(`Hash '${hash}' is unknown or not supported. Must be ${types}`) } const hasher = hashName[hash] diff --git a/packages/crypto/src/random-bytes.ts b/packages/crypto/src/random-bytes.ts index 9260fe7212..e1a3d208fc 100644 --- a/packages/crypto/src/random-bytes.ts +++ b/packages/crypto/src/random-bytes.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidParametersError } from '@libp2p/interface' import { randomBytes as randB } from '@noble/hashes/utils' /** @@ -6,7 +6,7 @@ import { randomBytes as randB } from '@noble/hashes/utils' */ export default function randomBytes (length: number): Uint8Array { if (isNaN(length) || length <= 0) { - throw new CodeError('random bytes length must be a Number bigger than 0', 'ERR_INVALID_LENGTH') + throw new InvalidParametersError('random bytes length must be a Number bigger than 0') } return randB(length) } diff --git a/packages/crypto/src/webcrypto-browser.ts b/packages/crypto/src/webcrypto-browser.ts index a1e3288093..3f1f11a3aa 100644 --- a/packages/crypto/src/webcrypto-browser.ts +++ b/packages/crypto/src/webcrypto-browser.ts @@ -1,5 +1,7 @@ /* eslint-env browser */ +import { WebCryptoMissingError } from './errors.js' + // Check native crypto exists and is enabled (In insecure context `self.crypto` // exists but `self.crypto.subtle` does not). export default { @@ -7,15 +9,12 @@ export default { const nativeCrypto = win.crypto if (nativeCrypto?.subtle == null) { - throw Object.assign( - new Error( - 'Missing Web Crypto API. ' + - 'The most likely cause of this error is that this page is being accessed ' + - 'from an insecure context (i.e. not HTTPS). For more information and ' + - 'possible resolutions see ' + - 'https://github.com/libp2p/js-libp2p/blob/main/packages/crypto/README.md#web-crypto-api' - ), - { code: 'ERR_MISSING_WEB_CRYPTO' } + throw new WebCryptoMissingError( + 'Missing Web Crypto API. ' + + 'The most likely cause of this error is that this page is being accessed ' + + 'from an insecure context (i.e. not HTTPS). For more information and ' + + 'possible resolutions see ' + + 'https://github.com/libp2p/js-libp2p/blob/main/packages/crypto/README.md#web-crypto-api' ) } diff --git a/packages/crypto/test/crypto.spec.ts b/packages/crypto/test/crypto.spec.ts index 3972b8ed98..29fa0b2cfa 100644 --- a/packages/crypto/test/crypto.spec.ts +++ b/packages/crypto/test/crypto.spec.ts @@ -50,14 +50,16 @@ describe('libp2p-crypto', function () { it('generateKeyPair', () => { // @ts-expect-error key type is invalid - return expect(crypto.keys.generateKeyPair('invalid-key-type', 512)).to.eventually.be.rejected.with.property('code', 'ERR_UNSUPPORTED_KEY_TYPE') + return expect(crypto.keys.generateKeyPair('invalid-key-type', 512)).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) it('generateKeyPairFromSeed', () => { const seed = crypto.randomBytes(32) // @ts-expect-error key type is invalid - return expect(crypto.keys.generateKeyPairFromSeed('invalid-key-type', seed, 512)).to.eventually.be.rejected.with.property('code', 'ERR_UNSUPPORTED_KEY_DERIVATION_TYPE') + return expect(crypto.keys.generateKeyPairFromSeed('invalid-key-type', seed, 512)).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) // https://github.com/libp2p/js-libp2p-crypto/issues/314 @@ -134,7 +136,7 @@ describe('libp2p-crypto', function () { it('throws on invalid hash name', () => { const fn = (): string => crypto.pbkdf2('password', 'at least 16 character salt', 500, 512 / 8, 'shaX-xxx') - expect(fn).to.throw().with.property('code', 'ERR_UNSUPPORTED_HASH_TYPE') + expect(fn).to.throw().with.property('name', 'InvalidParametersError') }) }) diff --git a/packages/crypto/test/fixtures/go-elliptic-key.ts b/packages/crypto/test/fixtures/go-elliptic-key.ts index fe5b9f0c48..489b06c4eb 100644 --- a/packages/crypto/test/fixtures/go-elliptic-key.ts +++ b/packages/crypto/test/fixtures/go-elliptic-key.ts @@ -1,3 +1,13 @@ +import type { Curve } from '../../src/keys/ecdh.js' + +export interface GoEllipticKey { + curve: Curve + bob: { + private: Uint8Array + public: Uint8Array + } +} + export default { curve: 'P-256', bob: { @@ -8,4 +18,4 @@ export default { 4, 53, 59, 128, 56, 162, 250, 72, 141, 206, 117, 232, 57, 96, 39, 39, 247, 7, 27, 57, 251, 232, 120, 186, 21, 239, 176, 139, 195, 129, 125, 85, 11, 188, 191, 32, 227, 0, 6, 163, 101, 68, 208, 1, 43, 131, 124, 112, 102, 91, 104, 79, 16, 119, 152, 208, 4, 147, 155, 83, 20, 146, 104, 55, 90 ]) } -} +} satisfies GoEllipticKey diff --git a/packages/crypto/test/keys/ephemeral-keys.spec.ts b/packages/crypto/test/keys/ephemeral-keys.spec.ts index 1caadbc2db..8aa90e341d 100644 --- a/packages/crypto/test/keys/ephemeral-keys.spec.ts +++ b/packages/crypto/test/keys/ephemeral-keys.spec.ts @@ -3,8 +3,9 @@ import { expect } from 'aegir/chai' import * as crypto from '../../src/index.js' import fixtures from '../fixtures/go-elliptic-key.js' +import type { Curve } from '../../src/keys/ecdh.js' -const curves = ['P-256', 'P-384'] // 'P-521' fails in tests :( no clue why +const curves: Curve[] = ['P-256', 'P-384'] // 'P-521' fails in tests :( no clue why const lengths: Record = { 'P-256': 65, 'P-384': 97, @@ -57,6 +58,8 @@ describe('generateEphemeralKeyPair', () => { }) it('handles bad curve name', async () => { - await expect(crypto.keys.generateEphemeralKeyPair('bad name')).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_CURVE') + // @ts-expect-error argument is not a Curve + await expect(crypto.keys.generateEphemeralKeyPair('bad name')).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) }) diff --git a/packages/crypto/test/keys/key-stretcher.spec.ts b/packages/crypto/test/keys/key-stretcher.spec.ts index c0b902d36a..7f8608a75c 100644 --- a/packages/crypto/test/keys/key-stretcher.spec.ts +++ b/packages/crypto/test/keys/key-stretcher.spec.ts @@ -29,12 +29,14 @@ describe('keyStretcher', () => { it('handles invalid cipher type', () => { // @ts-expect-error cipher name is invalid - return expect(crypto.keys.keyStretcher('invalid-cipher', 'SHA256', 'secret')).to.eventually.be.rejected().with.property('code', 'ERR_INVALID_CIPHER_TYPE') + return expect(crypto.keys.keyStretcher('invalid-cipher', 'SHA256', 'secret')).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) it('handles missing hash type', () => { // @ts-expect-error secret name is invalid - return expect(crypto.keys.keyStretcher('AES-128', undefined, 'secret')).to.eventually.be.rejected().with.property('code', 'ERR_MISSING_HASH_TYPE') + return expect(crypto.keys.keyStretcher('AES-128', undefined, 'secret')).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) }) diff --git a/packages/crypto/test/keys/rsa.spec.ts b/packages/crypto/test/keys/rsa.spec.ts index 400907e3f4..619de10287 100644 --- a/packages/crypto/test/keys/rsa.spec.ts +++ b/packages/crypto/test/keys/rsa.spec.ts @@ -193,7 +193,7 @@ vQ2NBF1B1/I4w5/LCbEDxrliX5fTe9osfkFZolLMsD6B9c2J1DvAJKaiMhc= it('handles invalid export type', () => { return expect(key.export('secret', 'invalid-type')).to.eventually.be.rejected - .with.property('code', 'ERR_INVALID_EXPORT_FORMAT') + .with.property('name', 'InvalidParametersError') }) }) diff --git a/packages/crypto/test/keys/secp256k1.spec.ts b/packages/crypto/test/keys/secp256k1.spec.ts index bb55f0b3d5..27cac0ee54 100644 --- a/packages/crypto/test/keys/secp256k1.spec.ts +++ b/packages/crypto/test/keys/secp256k1.spec.ts @@ -179,7 +179,8 @@ describe('crypto functions', () => { it('errors when signing with an invalid key', async () => { await expect((async () => { await secp256k1Crypto.hashAndSign(uint8ArrayFromString('42'), uint8ArrayFromString('Hello')) - })()).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_INPUT') + })()).to.eventually.be.rejected + .with.property('name', 'SigningError') }) it('errors if given a null Uint8Array to validate', async () => { diff --git a/packages/crypto/test/random-bytes.spec.ts b/packages/crypto/test/random-bytes.spec.ts index 06fa923b1e..4227a5fa90 100644 --- a/packages/crypto/test/random-bytes.spec.ts +++ b/packages/crypto/test/random-bytes.spec.ts @@ -8,15 +8,15 @@ describe('randomBytes', () => { }) it('throws if length is 0', () => { - expect(() => randomBytes(0)).to.throw(Error).with.property('code', 'ERR_INVALID_LENGTH') + expect(() => randomBytes(0)).to.throw(Error).with.property('name', 'InvalidParametersError') }) it('throws if length is < 0', () => { - expect(() => randomBytes(-1)).to.throw(Error).with.property('code', 'ERR_INVALID_LENGTH') + expect(() => randomBytes(-1)).to.throw(Error).with.property('name', 'InvalidParametersError') }) it('throws if length is not a number', () => { // @ts-expect-error invalid params - expect(() => randomBytes('hi')).to.throw(Error).with.property('code', 'ERR_INVALID_LENGTH') + expect(() => randomBytes('hi')).to.throw(Error).with.property('name', 'InvalidParametersError') }) }) diff --git a/packages/integration-tests/test/circuit-relay.node.ts b/packages/integration-tests/test/circuit-relay.node.ts index 72a185ae8c..162fec3e41 100644 --- a/packages/integration-tests/test/circuit-relay.node.ts +++ b/packages/integration-tests/test/circuit-relay.node.ts @@ -543,7 +543,7 @@ describe('circuit-relay', () => { const ma = getRelayAddress(relay1).encapsulate(`/p2p-circuit/p2p/${remote.peerId.toString()}`) await expect(local.dial(ma)).to.eventually.be.rejected - .with.property('code', 'ERR_RELAYED_DIAL') + .with.property('name', 'DialError') }) /* it('should fail to open connection over relayed connection', async () => { @@ -687,7 +687,7 @@ describe('circuit-relay', () => { expect(connection).to.have.property('limits').that.is.ok() await expect(connection.newStream('/my-protocol/1.0.0')) - .to.eventually.be.rejected.with.property('code', 'ERR_LIMITED_CONNECTION') + .to.eventually.be.rejected.with.property('name', 'LimitedConnectionError') }) it('should not allow incoming streams on a limited connection', async () => { @@ -714,7 +714,7 @@ describe('circuit-relay', () => { await expect(connection.newStream('/my-protocol/1.0.0', { runOnLimitedConnection: false })) - .to.eventually.be.rejected.with.property('code', 'ERR_LIMITED_CONNECTION') + .to.eventually.be.rejected.with.property('name', 'LimitedConnectionError') }) it('should open streams on a limited connection when told to do so', async () => { diff --git a/packages/integration-tests/test/fetch.spec.ts b/packages/integration-tests/test/fetch.spec.ts index c715b97cd2..c8ca47ade2 100644 --- a/packages/integration-tests/test/fetch.spec.ts +++ b/packages/integration-tests/test/fetch.spec.ts @@ -1,7 +1,6 @@ /* eslint-env mocha */ import { type Fetch, fetch } from '@libp2p/fetch' -import { ERR_INVALID_PARAMETERS } from '@libp2p/interface' import { expect } from 'aegir/chai' import { createLibp2p } from 'libp2p' import { isWebWorker } from 'wherearewe' @@ -113,14 +112,14 @@ describe('fetch', () => { receiver.services.fetch.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) await expect(sender.services.fetch.fetch(receiver.peerId, '/moduleUNKNOWN/foobar')) - .to.eventually.be.rejected.with.property('code', ERR_INVALID_PARAMETERS) + .to.eventually.be.rejected.with.property('name', 'ProtocolError') }) it('registering multiple handlers for same prefix errors', async () => { receiver.services.fetch.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A)) expect(() => { receiver.services.fetch.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_B)) }) - .to.throw().with.property('code', 'ERR_KEY_ALREADY_EXISTS') + .to.throw().with.property('name', 'InvalidParametersError') }) it('can unregister handler', async () => { diff --git a/packages/interface-compliance-tests/src/connection-encryption/index.ts b/packages/interface-compliance-tests/src/connection-encryption/index.ts index 0f029e6f18..7918e69b7a 100644 --- a/packages/interface-compliance-tests/src/connection-encryption/index.ts +++ b/packages/interface-compliance-tests/src/connection-encryption/index.ts @@ -1,4 +1,3 @@ -import { UnexpectedPeerError } from '@libp2p/interface' import * as PeerIdFactory from '@libp2p/peer-id-factory' import { expect } from 'aegir/chai' import all from 'it-all' @@ -102,7 +101,7 @@ export default (common: TestSetup expect.fail(), (err) => { expect(err).to.exist() - expect(err).to.have.property('code', UnexpectedPeerError.code) + expect(err).to.have.property('name', 'UnexpectedPeerError') }) }) }) diff --git a/packages/interface-compliance-tests/src/connection/index.ts b/packages/interface-compliance-tests/src/connection/index.ts index 559733d67e..657b53ff1f 100644 --- a/packages/interface-compliance-tests/src/connection/index.ts +++ b/packages/interface-compliance-tests/src/connection/index.ts @@ -158,16 +158,8 @@ export default (test: TestSetup): void => { expect(connection.timeline.close).to.not.exist() await connection.close() - try { - const protocol = '/echo/0.0.1' - await connection.newStream([protocol]) - } catch (err: any) { - expect(err).to.exist() - expect(err.code).to.equal('ERR_CONNECTION_CLOSED') - return - } - - throw new Error('should fail to create a new stream if the connection is closing') + await expect(connection.newStream(['/echo/0.0.1'])).to.eventually.be.rejected + .with.property('name', 'ConnectionClosedError') }) }) }) diff --git a/packages/interface-compliance-tests/src/mocks/connection-manager.ts b/packages/interface-compliance-tests/src/mocks/connection-manager.ts index 0d941fb501..a70c9d0b83 100644 --- a/packages/interface-compliance-tests/src/mocks/connection-manager.ts +++ b/packages/interface-compliance-tests/src/mocks/connection-manager.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { UnsupportedOperationError } from '@libp2p/interface' import { isPeerId, type PeerId, type ComponentLogger, type Libp2pEvents, type PendingDial, type Connection, type TypedEventTarget, type PubSub, type Startable } from '@libp2p/interface' import { PeerMap } from '@libp2p/peer-collections' import { peerIdFromString } from '@libp2p/peer-id' @@ -33,7 +33,7 @@ class MockNetwork { } } - throw new CodeError('Peer not found', 'ERR_PEER_NOT_FOUND') + throw new Error('Peer not found') } reset (): void { @@ -97,12 +97,8 @@ class MockConnectionManager implements ConnectionManager, Startable { } async openConnection (peerId: PeerId | Multiaddr | Multiaddr[]): Promise { - if (this.components == null) { - throw new CodeError('Not initialized', 'ERR_NOT_INITIALIZED') - } - if (isMultiaddr(peerId)) { - throw new CodeError('Dialing multiaddrs not supported', 'ERR_NOT_SUPPORTED') + throw new UnsupportedOperationError('Dialing multiaddrs not supported') } let existingConnections: Connection[] = [] @@ -153,10 +149,6 @@ class MockConnectionManager implements ConnectionManager, Startable { } async closeConnections (peerId: PeerId): Promise { - if (this.components == null) { - throw new CodeError('Not initialized', 'ERR_NOT_INITIALIZED') - } - const connections = this.getConnections(peerId) if (connections.length === 0) { diff --git a/packages/interface-compliance-tests/src/mocks/connection.ts b/packages/interface-compliance-tests/src/mocks/connection.ts index fdb605bc4a..41758718c8 100644 --- a/packages/interface-compliance-tests/src/mocks/connection.ts +++ b/packages/interface-compliance-tests/src/mocks/connection.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { ConnectionClosedError } from '@libp2p/interface' import { defaultLogger, logger } from '@libp2p/logger' import * as mss from '@libp2p/multistream-select' import { peerIdFromString } from '@libp2p/peer-id' @@ -78,7 +78,7 @@ class MockConnection implements Connection { } if (this.status !== 'open') { - throw new CodeError('connection must be open to create streams', 'ERR_CONNECTION_CLOSED') + throw new ConnectionClosedError('connection must be open to create streams') } const id = `${Math.random()}` diff --git a/packages/interface-compliance-tests/src/stream-muxer/close-test.ts b/packages/interface-compliance-tests/src/stream-muxer/close-test.ts index 9ba2f246ba..bf380e831d 100644 --- a/packages/interface-compliance-tests/src/stream-muxer/close-test.ts +++ b/packages/interface-compliance-tests/src/stream-muxer/close-test.ts @@ -251,10 +251,12 @@ export default (common: TestSetup): void => { controllers.push(controller) try { - const abortableRand = abortableSource(infiniteRandom(), controller.signal, { abortCode: 'ERR_TEST_ABORT' }) + const abortableRand = abortableSource(infiniteRandom(), controller.signal, { + abortName: 'TestAbortError' + }) await pipe(abortableRand, stream, drain) } catch (err: any) { - if (err.code !== 'ERR_TEST_ABORT') throw err + if (err.name !== 'TestAbortError') throw err } if (!closed) throw new Error('stream should not have ended yet!') @@ -316,7 +318,7 @@ export default (common: TestSetup): void => { await stream.sink(data) const err = await deferred.promise - expect(err).to.have.property('code', 'ERR_SINK_INVALID_STATE') + expect(err).to.have.property('name', 'StreamStateError') }) it('can close a stream for reading', async () => { @@ -473,7 +475,7 @@ export default (common: TestSetup): void => { // close should time out as message is never read await expect(pb.unwrap().close()).to.eventually.be.rejected - .with.property('code', 'ERR_CLOSE_READ_ABORTED') + .with.property('name', 'TimeoutError') }) */ }) diff --git a/packages/interface-compliance-tests/src/stream-muxer/fixtures/pb/message.ts b/packages/interface-compliance-tests/src/stream-muxer/fixtures/pb/message.ts index 74bdd8bb68..12a64c437a 100644 --- a/packages/interface-compliance-tests/src/stream-muxer/fixtures/pb/message.ts +++ b/packages/interface-compliance-tests/src/stream-muxer/fixtures/pb/message.ts @@ -4,8 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { encodeMessage, decodeMessage, message } from 'protons-runtime' -import type { Codec } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export interface Message { @@ -42,7 +41,7 @@ export namespace Message { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { message: '', value: 0, @@ -55,18 +54,22 @@ export namespace Message { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.message = reader.string() break - case 2: + } + case 2: { obj.value = reader.uint32() break - case 3: + } + case 3: { obj.flag = reader.bool() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -81,7 +84,7 @@ export namespace Message { return encodeMessage(obj, Message.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Message => { - return decodeMessage(buf, Message.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Message => { + return decodeMessage(buf, Message.codec(), opts) } } diff --git a/packages/interface/src/errors.ts b/packages/interface/src/errors.ts index 541970491c..3ebb174223 100644 --- a/packages/interface/src/errors.ts +++ b/packages/interface/src/errors.ts @@ -4,21 +4,15 @@ * AbortSignal. */ export class AbortError extends Error { - public readonly code: string - public readonly type: string - constructor (message: string = 'The operation was aborted') { super(message) this.name = 'AbortError' - this.code = AbortError.code - this.type = AbortError.type } - - static readonly code = 'ABORT_ERR' - - static readonly type = 'aborted' } +/** + * @deprecated + */ export class CodeError = Record> extends Error { public readonly props: T @@ -34,6 +28,9 @@ export class CodeError = Record> ex } } +/** + * @deprecated + */ export class AggregateCodeError = Record> extends AggregateError { public readonly props: T @@ -51,44 +48,179 @@ export class AggregateCodeError = Record multiaddr) }) } catch (err: any) { - if (err.code !== 'ERR_NOT_FOUND') { + if (err.name !== 'NotFoundError') { throw err } diff --git a/packages/kad-dht/src/errors.ts b/packages/kad-dht/src/errors.ts new file mode 100644 index 0000000000..16a9930468 --- /dev/null +++ b/packages/kad-dht/src/errors.ts @@ -0,0 +1,39 @@ +/** + * An error occurred during a query + */ +export class QueryError extends Error { + constructor (message = 'Query error') { + super(message) + this.name = 'QueryError' + } +} + +/** + * A query was aborted + */ +export class QueryAbortedError extends Error { + constructor (message = 'Query aborted') { + super(message) + this.name = 'QueryAbortedError' + } +} + +/** + * An invalid record was received + */ +export class InvalidRecordError extends Error { + constructor (message = 'Invalid record') { + super(message) + this.name = 'InvalidRecordError' + } +} + +/** + * A selector function was missing + */ +export class MissingSelectorError extends Error { + constructor (message = 'No selector function configured for prefix') { + super(message) + this.name = 'MissingSelectorError' + } +} diff --git a/packages/kad-dht/src/kad-dht.ts b/packages/kad-dht/src/kad-dht.ts index b5063d32e2..0cccaaf1df 100644 --- a/packages/kad-dht/src/kad-dht.ts +++ b/packages/kad-dht/src/kad-dht.ts @@ -1,4 +1,4 @@ -import { CodeError, TypedEventEmitter, contentRoutingSymbol, peerDiscoverySymbol, peerRoutingSymbol, serviceCapabilities, serviceDependencies, start, stop } from '@libp2p/interface' +import { NotFoundError, TypedEventEmitter, contentRoutingSymbol, peerDiscoverySymbol, peerRoutingSymbol, serviceCapabilities, serviceDependencies, start, stop } from '@libp2p/interface' import drain from 'it-drain' import pDefer from 'p-defer' import { PROTOCOL } from './constants.js' @@ -56,7 +56,7 @@ class DHTContentRouting implements ContentRouting { } } - throw new CodeError('Not found', 'ERR_NOT_FOUND') + throw new NotFoundError('Could not find value for key') } } @@ -77,7 +77,7 @@ class DHTPeerRouting implements PeerRouting { } } - throw new CodeError('Not found', 'ERR_NOT_FOUND') + throw new NotFoundError('Peer not found') } async * getClosestPeers (key: Uint8Array, options: RoutingOptions = {}): AsyncIterable { diff --git a/packages/kad-dht/src/message/dht.ts b/packages/kad-dht/src/message/dht.ts index 3dfcefbf0f..e9a9ea2cca 100644 --- a/packages/kad-dht/src/message/dht.ts +++ b/packages/kad-dht/src/message/dht.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { type Codec, decodeMessage, encodeMessage, enumeration, message } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, enumeration, MaxLengthError, message } from 'protons-runtime' import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' import type { Uint8ArrayList } from 'uint8arraylist' @@ -54,7 +54,7 @@ export namespace Record { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -101,8 +101,8 @@ export namespace Record { return encodeMessage(obj, Record.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Record => { - return decodeMessage(buf, Record.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Record => { + return decodeMessage(buf, Record.codec(), opts) } } @@ -184,7 +184,7 @@ export namespace PeerInfo { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { id: uint8ArrayAlloc(0), multiaddrs: [] @@ -201,6 +201,10 @@ export namespace PeerInfo { break } case 2: { + if (opts.limits?.multiaddrs != null && obj.multiaddrs.length === opts.limits.multiaddrs) { + throw new MaxLengthError('Decode error - map field "multiaddrs" had too many elements') + } + obj.multiaddrs.push(reader.bytes()) break } @@ -226,8 +230,8 @@ export namespace PeerInfo { return encodeMessage(obj, PeerInfo.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): PeerInfo => { - return decodeMessage(buf, PeerInfo.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): PeerInfo => { + return decodeMessage(buf, PeerInfo.codec(), opts) } } @@ -287,7 +291,7 @@ export namespace Message { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { type: MessageType.PUT_VALUE, closer: [], @@ -317,11 +321,23 @@ export namespace Message { break } case 8: { - obj.closer.push(PeerInfo.codec().decode(reader, reader.uint32())) + if (opts.limits?.closer != null && obj.closer.length === opts.limits.closer) { + throw new MaxLengthError('Decode error - map field "closer" had too many elements') + } + + obj.closer.push(PeerInfo.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.closer$ + })) break } case 9: { - obj.providers.push(PeerInfo.codec().decode(reader, reader.uint32())) + if (opts.limits?.providers != null && obj.providers.length === opts.limits.providers) { + throw new MaxLengthError('Decode error - map field "providers" had too many elements') + } + + obj.providers.push(PeerInfo.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.providers$ + })) break } default: { @@ -342,7 +358,7 @@ export namespace Message { return encodeMessage(obj, Message.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Message => { - return decodeMessage(buf, Message.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Message => { + return decodeMessage(buf, Message.codec(), opts) } } diff --git a/packages/kad-dht/src/network.ts b/packages/kad-dht/src/network.ts index 3346acfee0..025e77cd6b 100644 --- a/packages/kad-dht/src/network.ts +++ b/packages/kad-dht/src/network.ts @@ -1,8 +1,7 @@ -import { TypedEventEmitter } from '@libp2p/interface' +import { InvalidParametersError, TypedEventEmitter } from '@libp2p/interface' import { Libp2pRecord } from '@libp2p/record' import { AdaptiveTimeout, type AdaptiveTimeoutInit } from '@libp2p/utils/adaptive-timeout' import { pbStream } from 'it-protobuf-stream' -import { CodeError } from 'protons-runtime' import { Message } from './message/dht.js' import { fromPbPeerInfo } from './message/utils.js' import { @@ -88,7 +87,7 @@ export class Network extends TypedEventEmitter implements Startab const type = msg.type if (type == null) { - throw new CodeError('Message type was missing', 'ERR_INVALID_PARAMETERS') + throw new InvalidParametersError('Message type was missing') } this.log('sending %s to %p', msg.type, to) @@ -141,7 +140,7 @@ export class Network extends TypedEventEmitter implements Startab const type = msg.type if (type == null) { - throw new CodeError('Message type was missing', 'ERR_INVALID_PARAMETERS') + throw new InvalidParametersError('Message type was missing') } this.log('sending %s to %p', msg.type, to) diff --git a/packages/kad-dht/src/peer-routing/index.ts b/packages/kad-dht/src/peer-routing/index.ts index 667f1cc568..f8c0c37965 100644 --- a/packages/kad-dht/src/peer-routing/index.ts +++ b/packages/kad-dht/src/peer-routing/index.ts @@ -1,8 +1,9 @@ import { keys } from '@libp2p/crypto' -import { CodeError } from '@libp2p/interface' +import { InvalidPublicKeyError, NotFoundError } from '@libp2p/interface' import { peerIdFromKeys } from '@libp2p/peer-id' import { Libp2pRecord } from '@libp2p/record' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import { QueryError, InvalidRecordError } from '../errors.js' import { MessageType } from '../message/dht.js' import { PeerDistanceList } from '../peer-list/peer-distance-list.js' import { @@ -63,7 +64,7 @@ export class PeerRouting { try { peerData = await this.peerStore.get(p) } catch (err: any) { - if (err.code !== 'ERR_NOT_FOUND') { + if (err.name !== 'NotFoundError') { throw err } } @@ -73,7 +74,7 @@ export class PeerRouting { try { peerData = await this.peerStore.get(peer) } catch (err: any) { - if (err.code !== 'ERR_NOT_FOUND') { + if (err.name !== 'NotFoundError') { throw err } } @@ -117,18 +118,18 @@ export class PeerRouting { // compare hashes of the pub key if (!recPeer.equals(peer)) { - throw new CodeError('public key does not match id', 'ERR_PUBLIC_KEY_DOES_NOT_MATCH_ID') + throw new InvalidPublicKeyError('public key does not match id') } if (recPeer.publicKey == null) { - throw new CodeError('public key missing', 'ERR_PUBLIC_KEY_MISSING') + throw new InvalidPublicKeyError('public key missing') } yield valueEvent({ from: peer, value: recPeer.publicKey }, options) } } - throw new CodeError(`Node not responding with its public key: ${peer.toString()}`, 'ERR_INVALID_RECORD') + throw new QueryError(`Node not responding with its public key: ${peer.toString()}`) } /** @@ -190,7 +191,7 @@ export class PeerRouting { } if (!foundPeer) { - yield queryErrorEvent({ from: this.peerId, error: new CodeError('Not found', 'ERR_NOT_FOUND') }, options) + yield queryErrorEvent({ from: this.peerId, error: new NotFoundError('Not found') }, options) } } @@ -257,7 +258,7 @@ export class PeerRouting { const errMsg = 'invalid record received, discarded' this.log(errMsg) - yield queryErrorEvent({ from: event.from, error: new CodeError(errMsg, 'ERR_INVALID_RECORD') }, options) + yield queryErrorEvent({ from: event.from, error: new QueryError(errMsg) }, options) continue } } @@ -273,7 +274,7 @@ export class PeerRouting { */ async _verifyRecordOnline (record: DHTRecord): Promise { if (record.timeReceived == null) { - throw new CodeError('invalid record received', 'ERR_INVALID_RECORD') + throw new InvalidRecordError('invalid record received') } await verifyRecord(this.validators, new Libp2pRecord(record.key, record.value, record.timeReceived)) @@ -301,7 +302,7 @@ export class PeerRouting { multiaddrs: peer.addresses.map(({ multiaddr }) => multiaddr) }) } catch (err: any) { - if (err.code !== 'ERR_NOT_FOUND') { + if (err.name !== 'NotFoundError') { throw err } } diff --git a/packages/kad-dht/src/query/manager.ts b/packages/kad-dht/src/query/manager.ts index 61603cc0f5..1e7c32bb1c 100644 --- a/packages/kad-dht/src/query/manager.ts +++ b/packages/kad-dht/src/query/manager.ts @@ -223,7 +223,7 @@ export class QueryManager implements Startable { queryFinished = true } catch (err: any) { - if (!this.running && err.code === 'ERR_QUERY_ABORTED') { + if (!this.running && err.name === 'QueryAbortedError') { // ignore query aborted errors that were thrown during query manager shutdown } else { throw err diff --git a/packages/kad-dht/src/query/query-path.ts b/packages/kad-dht/src/query/query-path.ts index d5a0343253..7b36e4d6d9 100644 --- a/packages/kad-dht/src/query/query-path.ts +++ b/packages/kad-dht/src/query/query-path.ts @@ -1,8 +1,9 @@ -import { CodeError, setMaxListeners } from '@libp2p/interface' +import { setMaxListeners } from '@libp2p/interface' import { Queue } from '@libp2p/utils/queue' import { anySignal } from 'any-signal' import { xor as uint8ArrayXor } from 'uint8arrays/xor' import { xorCompare as uint8ArrayXorCompare } from 'uint8arrays/xor-compare' +import { QueryAbortedError } from '../errors.js' import { convertPeerId, convertBuffer } from '../utils.js' import { queryErrorEvent } from './events.js' import type { QueryEvent } from '../index.js' @@ -195,7 +196,7 @@ export async function * queryPath (options: QueryPathOptions): AsyncGenerator => { if (!(key instanceof Uint8Array)) { - throw new CodeError('"key" must be a Uint8Array', 'ERR_INVALID_RECORD_KEY_NOT_BUFFER') + throw new InvalidParametersError('"key" must be a Uint8Array') } if (key.byteLength < 5) { - throw new CodeError('invalid public key record', 'ERR_INVALID_RECORD_KEY_TOO_SHORT') + throw new InvalidParametersError('Invalid public key record') } const prefix = uint8ArrayToString(key.subarray(0, 4)) if (prefix !== '/pk/') { - throw new CodeError('key was not prefixed with /pk/', 'ERR_INVALID_RECORD_KEY_BAD_PREFIX') + throw new InvalidParametersError('key was not prefixed with /pk/') } const keyhash = key.slice(4) @@ -60,7 +58,7 @@ const validatePublicKeyRecord = async (key: Uint8Array, publicKey: Uint8Array): const publicKeyHash = await sha256.digest(publicKey) if (!uint8ArrayEquals(keyhash, publicKeyHash.bytes)) { - throw new CodeError('public key does not match passed in key', 'ERR_INVALID_RECORD_HASH_MISMATCH') + throw new InvalidParametersError('public key does not match passed in key') } } diff --git a/packages/kad-dht/src/routing-table/index.ts b/packages/kad-dht/src/routing-table/index.ts index 5561c54dea..defa270e72 100644 --- a/packages/kad-dht/src/routing-table/index.ts +++ b/packages/kad-dht/src/routing-table/index.ts @@ -1,4 +1,4 @@ -import { CodeError, TypedEventEmitter } from '@libp2p/interface' +import { InvalidMessageError, TypedEventEmitter } from '@libp2p/interface' import { PeerSet } from '@libp2p/peer-collections' import { PeerQueue } from '@libp2p/utils/peer-queue' import { pbStream } from 'it-protobuf-stream' @@ -254,7 +254,7 @@ export class RoutingTable extends TypedEventEmitter implemen await pb.unwrap().close() if (response.type !== MessageType.PING) { - throw new CodeError(`Incorrect message type received, expected PING got ${response.type}`, 'ERR_BAD_PING_RESPONSE') + throw new InvalidMessageError(`Incorrect message type received, expected PING got ${response.type}`) } return true diff --git a/packages/kad-dht/src/rpc/handlers/add-provider.ts b/packages/kad-dht/src/rpc/handlers/add-provider.ts index 0d15f3cf2b..486f18f77f 100644 --- a/packages/kad-dht/src/rpc/handlers/add-provider.ts +++ b/packages/kad-dht/src/rpc/handlers/add-provider.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidMessageError } from '@libp2p/interface' import { peerIdFromBytes } from '@libp2p/peer-id' import { multiaddr } from '@multiformats/multiaddr' import { CID } from 'multiformats/cid' @@ -29,7 +29,7 @@ export class AddProviderHandler implements DHTMessageHandler { this.log('start') if (msg.key == null || msg.key.length === 0) { - throw new CodeError('Missing key', 'ERR_MISSING_KEY') + throw new InvalidMessageError('Missing key') } let cid: CID @@ -37,7 +37,7 @@ export class AddProviderHandler implements DHTMessageHandler { // this is actually just the multihash, not the whole CID cid = CID.decode(msg.key) } catch (err: any) { - throw new CodeError('Invalid CID', 'ERR_INVALID_CID') + throw new InvalidMessageError('Invalid CID') } if (msg.providers == null || msg.providers.length === 0) { diff --git a/packages/kad-dht/src/rpc/handlers/find-node.ts b/packages/kad-dht/src/rpc/handlers/find-node.ts index 6f4150d482..2141297793 100644 --- a/packages/kad-dht/src/rpc/handlers/find-node.ts +++ b/packages/kad-dht/src/rpc/handlers/find-node.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidMessageError } from '@libp2p/interface' import { protocols } from '@multiformats/multiaddr' import { equals as uint8ArrayEquals } from 'uint8arrays' import { MessageType } from '../../message/dht.js' @@ -45,7 +45,7 @@ export class FindNodeHandler implements DHTMessageHandler { this.log('incoming request from %p for peers closer to %b', peerId, msg.key) if (msg.key == null) { - throw new CodeError('Invalid FIND_NODE message received - key was missing', 'ERR_INVALID_MESSAGE') + throw new InvalidMessageError('Invalid FIND_NODE message received - key was missing') } const closer: PeerInfo[] = await this.peerRouting.getCloserPeersOffline(msg.key, peerId) diff --git a/packages/kad-dht/src/rpc/handlers/get-providers.ts b/packages/kad-dht/src/rpc/handlers/get-providers.ts index f8bb968626..d9b7f74ecf 100644 --- a/packages/kad-dht/src/rpc/handlers/get-providers.ts +++ b/packages/kad-dht/src/rpc/handlers/get-providers.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidMessageError } from '@libp2p/interface' import { CID } from 'multiformats/cid' import { MessageType } from '../../message/dht.js' import type { PeerInfoMapper } from '../../index.js' @@ -40,14 +40,14 @@ export class GetProvidersHandler implements DHTMessageHandler { async handle (peerId: PeerId, msg: Message): Promise { if (msg.key == null) { - throw new CodeError('Invalid GET_PROVIDERS message received - key was missing', 'ERR_INVALID_MESSAGE') + throw new InvalidMessageError('Invalid GET_PROVIDERS message received - key was missing') } let cid try { cid = CID.decode(msg.key) } catch (err: any) { - throw new CodeError('Invalid CID', 'ERR_INVALID_CID') + throw new InvalidMessageError('Invalid CID') } this.log('%p asking for providers for %s', peerId, cid) @@ -104,7 +104,7 @@ export class GetProvidersHandler implements DHTMessageHandler { output.push(peerAfterFilter) } } catch (err: any) { - if (err.code !== 'ERR_NOT_FOUND') { + if (err.name !== 'NotFoundError') { throw err } } diff --git a/packages/kad-dht/src/rpc/handlers/get-value.ts b/packages/kad-dht/src/rpc/handlers/get-value.ts index d072569f7d..bdf33d74d4 100644 --- a/packages/kad-dht/src/rpc/handlers/get-value.ts +++ b/packages/kad-dht/src/rpc/handlers/get-value.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidMessageError, NotFoundError } from '@libp2p/interface' import { Libp2pRecord } from '@libp2p/record' import { MAX_RECORD_AGE @@ -41,7 +41,7 @@ export class GetValueHandler implements DHTMessageHandler { this.log('%p asked for key %b', peerId, key) if (key == null || key.length === 0) { - throw new CodeError('Invalid key', 'ERR_INVALID_KEY') + throw new InvalidMessageError('Invalid key') } const response: Message = { @@ -61,12 +61,12 @@ export class GetValueHandler implements DHTMessageHandler { const peer = await this.peerStore.get(idFromKey) if (peer.id.publicKey == null) { - throw new CodeError('No public key found in key book', 'ERR_NOT_FOUND') + throw new NotFoundError('No public key found in key book') } pubKey = peer.id.publicKey } catch (err: any) { - if (err.code !== 'ERR_NOT_FOUND') { + if (err.name !== 'NotFoundError') { throw err } } @@ -114,7 +114,7 @@ export class GetValueHandler implements DHTMessageHandler { try { rawRecord = await this.datastore.get(dsKey) } catch (err: any) { - if (err.code === 'ERR_NOT_FOUND') { + if (err.name === 'NotFoundError') { return undefined } throw err @@ -123,10 +123,6 @@ export class GetValueHandler implements DHTMessageHandler { // Create record from the returned bytes const record = Libp2pRecord.deserialize(rawRecord) - if (record == null) { - throw new CodeError('Invalid record', 'ERR_INVALID_RECORD') - } - // Check validity: compare time received with max record age if (record.timeReceived == null || Date.now() - record.timeReceived.getTime() > MAX_RECORD_AGE) { diff --git a/packages/kad-dht/src/rpc/handlers/put-value.ts b/packages/kad-dht/src/rpc/handlers/put-value.ts index b56b3a81e8..c73122f2ad 100644 --- a/packages/kad-dht/src/rpc/handlers/put-value.ts +++ b/packages/kad-dht/src/rpc/handlers/put-value.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidMessageError } from '@libp2p/interface' import { Libp2pRecord } from '@libp2p/record' import { verifyRecord } from '../../record/validators.js' import { bufferToRecordKey } from '../../utils.js' @@ -39,7 +39,7 @@ export class PutValueHandler implements DHTMessageHandler { const errMsg = `Empty record from: ${peerId.toString()}` this.log.error(errMsg) - throw new CodeError(errMsg, 'ERR_EMPTY_RECORD') + throw new InvalidMessageError(errMsg) } try { diff --git a/packages/kad-dht/test/kad-dht.spec.ts b/packages/kad-dht/test/kad-dht.spec.ts index 6bfc67c5c6..3fc7ab4b89 100644 --- a/packages/kad-dht/test/kad-dht.spec.ts +++ b/packages/kad-dht/test/kad-dht.spec.ts @@ -1,7 +1,6 @@ /* eslint-env mocha */ /* eslint max-nested-callbacks: ["error", 8] */ -import { CodeError } from '@libp2p/interface' import { peerIdFromBytes } from '@libp2p/peer-id' import { Libp2pRecord } from '@libp2p/record' import { expect } from 'aegir/chai' @@ -243,8 +242,7 @@ describe('KadDHT', () => { it('put - should require a minimum number of peers to have successful puts', async function () { this.timeout(10 * 1000) - const errCode = 'ERR_NOT_AVAILABLE' - const error = new CodeError('fake error', errCode) + const error = new Error('fake error') const key = uint8ArrayFromString('/v/hello') const value = uint8ArrayFromString('world') @@ -342,7 +340,8 @@ describe('KadDHT', () => { await drain(dhtA.put(key, value)) - await expect(last(dhtA.get(key))).to.eventually.be.rejected().property('code', 'ERR_UNRECOGNIZED_KEY_PREFIX') + await expect(last(dhtA.get(key))).to.eventually.be.rejected + .with.property('name', 'MissingSelectorError') }) it('put - get with update', async function () { @@ -836,8 +835,7 @@ describe('KadDHT', () => { it('get should handle correctly an unexpected error', async function () { this.timeout(240 * 1000) - const errCode = 'ERR_INVALID_RECORD_FAKE' - const error = new CodeError('fake error', errCode) + const error = new Error('fake error') const [dhtA, dhtB] = await Promise.all([ tdht.spawn(), @@ -851,7 +849,7 @@ describe('KadDHT', () => { const errors = await all(filter(dhtA.get(uint8ArrayFromString('/v/hello')), event => event.name === 'QUERY_ERROR')) expect(errors).to.have.lengthOf(1) - expect(errors).to.have.nested.property('[0].error.code', errCode) + expect(errors).to.have.nested.property('[0].error', error) stub.restore() }) diff --git a/packages/kad-dht/test/query.spec.ts b/packages/kad-dht/test/query.spec.ts index 0aaf562bd3..174ae9ec10 100644 --- a/packages/kad-dht/test/query.spec.ts +++ b/packages/kad-dht/test/query.spec.ts @@ -341,7 +341,7 @@ describe('QueryManager', () => { }, 10) await expect(all(manager.run(key, queryFunc, { signal: controller.signal }))).to.eventually.be.rejected() - .with.property('code', 'ERR_QUERY_ABORTED') + .with.property('name', 'QueryAbortedError') expect(aborted).to.be.true() diff --git a/packages/kad-dht/test/record/selection.spec.ts b/packages/kad-dht/test/record/selection.spec.ts index 49556941dd..88ef5cfbff 100644 --- a/packages/kad-dht/test/record/selection.spec.ts +++ b/packages/kad-dht/test/record/selection.spec.ts @@ -13,20 +13,20 @@ describe('selection', () => { it('throws no records given when no records received', () => { expect( () => selection.bestRecord({}, uint8ArrayFromString('/'), []) - ).to.throw().with.property('code', 'ERR_NO_RECORDS_RECEIVED') + ).to.throw().with.property('name', 'InvalidParametersError') }) it('throws on missing selector in the record key', () => { expect( - () => selection.bestRecord({}, uint8ArrayFromString('/'), records) - ).to.throw().with.property('code', 'ERR_NO_SELECTOR_FUNCTION_FOR_RECORD_KEY') + () => selection.bestRecord({}, uint8ArrayFromString('/no-selector/key-value'), records) + ).to.throw().with.property('name', 'MissingSelectorError') }) it('throws on unknown key prefix', () => { expect( // @ts-expect-error invalid input - () => selection.bestRecord({ world () {} }, uint8ArrayFromString('/hello/'), records) - ).to.throw().with.property('code', 'ERR_UNRECOGNIZED_KEY_PREFIX') + () => selection.bestRecord({ world () {} }, uint8ArrayFromString('/world'), records) + ).to.throw().with.property('name', 'InvalidParametersError') }) it('returns the index from the matching selector', () => { diff --git a/packages/kad-dht/test/record/validator.spec.ts b/packages/kad-dht/test/record/validator.spec.ts index 531a9e797a..949439436e 100644 --- a/packages/kad-dht/test/record/validator.spec.ts +++ b/packages/kad-dht/test/record/validator.spec.ts @@ -14,10 +14,7 @@ interface Cases { publicKey: Uint8Array[] } invalid: { - publicKey: Array<{ - data: Uint8Array - code: string - }> + publicKey: Uint8Array[] } } @@ -32,20 +29,13 @@ const generateCases = (hash: Uint8Array): Cases => { ] }, invalid: { - publicKey: [{ - data: uint8ArrayFromString('/pk/'), - code: 'ERR_INVALID_RECORD_KEY_TOO_SHORT' - }, { - data: Uint8Array.of(...uint8ArrayFromString('/pk/'), ...uint8ArrayFromString('random')), - code: 'ERR_INVALID_RECORD_HASH_MISMATCH' - }, { - data: hash, - code: 'ERR_INVALID_RECORD_KEY_BAD_PREFIX' - }, { + publicKey: [ + uint8ArrayFromString('/pk/'), + Uint8Array.of(...uint8ArrayFromString('/pk/'), ...uint8ArrayFromString('random')), + hash, // @ts-expect-error invalid input - data: 'not a buffer', - code: 'ERR_INVALID_RECORD_KEY_NOT_BUFFER' - }] + 'not a buffer' + ] } } } @@ -86,7 +76,7 @@ describe('validator', () => { } } await expect(validator.verifyRecord(validators, rec)) - .to.eventually.rejected.with.property('code', 'ERR_INVALID_RECORD_KEY_TYPE') + .to.eventually.rejected.with.property('name', 'InvalidParametersError') }) }) @@ -109,15 +99,15 @@ describe('validator', () => { }) it('throws on invalid records', async () => { - return Promise.all(cases.invalid.publicKey.map(async ({ data, code }) => { + return Promise.all(cases.invalid.publicKey.map(async data => { try { // await validator.validators.pk(data, key.public.bytes) } catch (err: any) { - expect(err.code).to.eql(code) + expect(err).to.have.property('name', 'InvalidParametersError') return } - expect.fail('did not throw an error with code ' + code) + expect.fail('did not throw an InvalidParametersError') })) }) }) diff --git a/packages/kad-dht/test/rpc/handlers/add-provider.spec.ts b/packages/kad-dht/test/rpc/handlers/add-provider.spec.ts index 72adb3aa72..6c225bbf11 100644 --- a/packages/kad-dht/test/rpc/handlers/add-provider.spec.ts +++ b/packages/kad-dht/test/rpc/handlers/add-provider.spec.ts @@ -51,7 +51,7 @@ describe('rpc - handlers - AddProvider', () => { closer: [], providers: [] }, - error: 'ERR_MISSING_KEY' + error: 'InvalidMessageError' }, { message: { type: MessageType.ADD_PROVIDER, @@ -59,7 +59,7 @@ describe('rpc - handlers - AddProvider', () => { closer: [], providers: [] }, - error: 'ERR_INVALID_CID' + error: 'InvalidMessageError' }] tests.forEach((t) => { @@ -68,7 +68,7 @@ describe('rpc - handlers - AddProvider', () => { await handler.handle(peerIds[0], t.message) } catch (err: any) { expect(err).to.exist() - expect(err.code).to.equal(t.error) + expect(err).to.have.property('name', t.error) return } throw new Error() diff --git a/packages/kad-dht/test/rpc/handlers/get-providers.spec.ts b/packages/kad-dht/test/rpc/handlers/get-providers.spec.ts index a9d486cf43..c9b187033e 100644 --- a/packages/kad-dht/test/rpc/handlers/get-providers.spec.ts +++ b/packages/kad-dht/test/rpc/handlers/get-providers.spec.ts @@ -68,7 +68,8 @@ describe('rpc - handlers - GetProviders', () => { providers: [] } - await expect(handler.handle(sourcePeer, msg)).to.eventually.be.rejected().with.property('code', 'ERR_INVALID_CID') + await expect(handler.handle(sourcePeer, msg)).to.eventually.be.rejected + .with.property('name', 'InvalidMessageError') }) it('responds with providers and closer peers', async () => { diff --git a/packages/kad-dht/test/rpc/handlers/get-value.spec.ts b/packages/kad-dht/test/rpc/handlers/get-value.spec.ts index fed8d55335..c18522ae1c 100644 --- a/packages/kad-dht/test/rpc/handlers/get-value.spec.ts +++ b/packages/kad-dht/test/rpc/handlers/get-value.spec.ts @@ -66,7 +66,7 @@ describe('rpc - handlers - GetValue', () => { try { await handler.handle(sourcePeer, msg) } catch (err: any) { - expect(err.code).to.eql('ERR_INVALID_KEY') + expect(err.name).to.equal('InvalidMessageError') return } diff --git a/packages/kad-dht/test/rpc/handlers/put-value.spec.ts b/packages/kad-dht/test/rpc/handlers/put-value.spec.ts index f2125924e0..0c9493ddec 100644 --- a/packages/kad-dht/test/rpc/handlers/put-value.spec.ts +++ b/packages/kad-dht/test/rpc/handlers/put-value.spec.ts @@ -50,7 +50,7 @@ describe('rpc - handlers - PutValue', () => { try { await handler.handle(sourcePeer, msg) } catch (err: any) { - expect(err.code).to.eql('ERR_EMPTY_RECORD') + expect(err.name).to.equal('InvalidMessageError') return } diff --git a/packages/keychain/package.json b/packages/keychain/package.json index ec670b1a77..93a95560b9 100644 --- a/packages/keychain/package.json +++ b/packages/keychain/package.json @@ -62,7 +62,7 @@ "@libp2p/crypto": "^4.1.9", "@libp2p/interface": "^1.7.0", "@libp2p/peer-id": "^4.2.4", - "interface-datastore": "^8.2.11", + "interface-datastore": "^8.3.0", "merge-options": "^3.0.4", "multiformats": "^13.1.0", "sanitize-filename": "^1.6.3", @@ -72,7 +72,7 @@ "@libp2p/logger": "^4.0.20", "@libp2p/peer-id-factory": "^4.2.4", "aegir": "^44.0.1", - "datastore-core": "^9.2.9" + "datastore-core": "^10.0.0" }, "sideEffects": false } diff --git a/packages/keychain/src/errors.ts b/packages/keychain/src/errors.ts deleted file mode 100644 index c3b11133a3..0000000000 --- a/packages/keychain/src/errors.ts +++ /dev/null @@ -1,17 +0,0 @@ -export enum codes { - ERR_INVALID_PARAMETERS = 'ERR_INVALID_PARAMETERS', - ERR_INVALID_KEY_NAME = 'ERR_INVALID_KEY_NAME', - ERR_INVALID_KEY_TYPE = 'ERR_INVALID_KEY_TYPE', - ERR_KEY_ALREADY_EXISTS = 'ERR_KEY_ALREADY_EXISTS', - ERR_INVALID_KEY_SIZE = 'ERR_INVALID_KEY_SIZE', - ERR_KEY_NOT_FOUND = 'ERR_KEY_NOT_FOUND', - ERR_OLD_KEY_NAME_INVALID = 'ERR_OLD_KEY_NAME_INVALID', - ERR_NEW_KEY_NAME_INVALID = 'ERR_NEW_KEY_NAME_INVALID', - ERR_PASSWORD_REQUIRED = 'ERR_PASSWORD_REQUIRED', - ERR_PEM_REQUIRED = 'ERR_PEM_REQUIRED', - ERR_CANNOT_READ_KEY = 'ERR_CANNOT_READ_KEY', - ERR_MISSING_PRIVATE_KEY = 'ERR_MISSING_PRIVATE_KEY', - ERR_INVALID_OLD_PASS_TYPE = 'ERR_INVALID_OLD_PASS_TYPE', - ERR_INVALID_NEW_PASS_TYPE = 'ERR_INVALID_NEW_PASS_TYPE', - ERR_INVALID_PASS_LENGTH = 'ERR_INVALID_PASS_LENGTH' -} diff --git a/packages/keychain/src/keychain.ts b/packages/keychain/src/keychain.ts index 898bf200ee..e3bef0b44d 100644 --- a/packages/keychain/src/keychain.ts +++ b/packages/keychain/src/keychain.ts @@ -2,14 +2,13 @@ import { pbkdf2, randomBytes } from '@libp2p/crypto' import { generateKeyPair, importKey, unmarshalPrivateKey } from '@libp2p/crypto/keys' -import { CodeError, serviceCapabilities } from '@libp2p/interface' +import { InvalidParametersError, NotFoundError, serviceCapabilities } from '@libp2p/interface' import { peerIdFromKeys } from '@libp2p/peer-id' import { Key } from 'interface-datastore/key' import mergeOptions from 'merge-options' import sanitize from 'sanitize-filename' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' -import { codes } from './errors.js' import type { KeychainComponents, KeychainInit, Keychain, KeyInfo } from './index.js' import type { Logger, KeyType, PeerId } from '@libp2p/interface' @@ -157,26 +156,26 @@ export class DefaultKeychain implements Keychain { async createKey (name: string, type: KeyType, size = 2048): Promise { if (!validateKeyName(name) || name === 'self') { await randomDelay() - throw new CodeError('Invalid key name', codes.ERR_INVALID_KEY_NAME) + throw new InvalidParametersError('Invalid key name') } if (typeof type !== 'string') { await randomDelay() - throw new CodeError('Invalid key type', codes.ERR_INVALID_KEY_TYPE) + throw new InvalidParametersError('Invalid key type') } const dsname = DsName(name) const exists = await this.components.datastore.has(dsname) if (exists) { await randomDelay() - throw new CodeError('Key name already exists', codes.ERR_KEY_ALREADY_EXISTS) + throw new InvalidParametersError('Key name already exists') } switch (type.toLowerCase()) { case 'rsa': if (!Number.isSafeInteger(size) || size < 2048) { await randomDelay() - throw new CodeError('Invalid RSA key size', codes.ERR_INVALID_KEY_SIZE) + throw new InvalidParametersError('Invalid RSA key size') } break default: @@ -190,7 +189,7 @@ export class DefaultKeychain implements Keychain { const cached = privates.get(this) if (cached == null) { - throw new CodeError('dek missing', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('dek missing') } const dek = cached.dek @@ -239,7 +238,7 @@ export class DefaultKeychain implements Keychain { const key = keys.find((k) => k.id === id) if (key == null) { - throw new CodeError(`Key with id '${id}' does not exist.`, codes.ERR_KEY_NOT_FOUND) + throw new InvalidParametersError(`Key with id '${id}' does not exist.`) } return key @@ -258,7 +257,7 @@ export class DefaultKeychain implements Keychain { async findKeyByName (name: string): Promise { if (!validateKeyName(name)) { await randomDelay() - throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME) + throw new InvalidParametersError(`Invalid key name '${name}'`) } const dsname = DsInfoName(name) @@ -268,7 +267,7 @@ export class DefaultKeychain implements Keychain { } catch (err: any) { await randomDelay() this.log.error(err) - throw new CodeError(`Key '${name}' does not exist.`, codes.ERR_KEY_NOT_FOUND) + throw new NotFoundError(`Key '${name}' does not exist.`) } } @@ -281,7 +280,7 @@ export class DefaultKeychain implements Keychain { async removeKey (name: string): Promise { if (!validateKeyName(name) || name === 'self') { await randomDelay() - throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME) + throw new InvalidParametersError(`Invalid key name '${name}'`) } const dsname = DsName(name) const keyInfo = await this.findKeyByName(name) @@ -302,11 +301,11 @@ export class DefaultKeychain implements Keychain { async renameKey (oldName: string, newName: string): Promise { if (!validateKeyName(oldName) || oldName === 'self') { await randomDelay() - throw new CodeError(`Invalid old key name '${oldName}'`, codes.ERR_OLD_KEY_NAME_INVALID) + throw new InvalidParametersError(`Invalid old key name '${oldName}'`) } if (!validateKeyName(newName) || newName === 'self') { await randomDelay() - throw new CodeError(`Invalid new key name '${newName}'`, codes.ERR_NEW_KEY_NAME_INVALID) + throw new InvalidParametersError(`Invalid new key name '${newName}'`) } const oldDsname = DsName(oldName) const newDsname = DsName(newName) @@ -316,7 +315,7 @@ export class DefaultKeychain implements Keychain { const exists = await this.components.datastore.has(newDsname) if (exists) { await randomDelay() - throw new CodeError(`Key '${newName}' already exists`, codes.ERR_KEY_ALREADY_EXISTS) + throw new InvalidParametersError(`Key '${newName}' already exists`) } try { @@ -344,11 +343,11 @@ export class DefaultKeychain implements Keychain { async exportKey (name: string, password: string): Promise { if (!validateKeyName(name)) { await randomDelay() - throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME) + throw new InvalidParametersError(`Invalid key name '${name}'`) } if (password == null) { await randomDelay() - throw new CodeError('Password is required', codes.ERR_PASSWORD_REQUIRED) + throw new InvalidParametersError('Password is required') } const dsname = DsName(name) @@ -358,7 +357,7 @@ export class DefaultKeychain implements Keychain { const cached = privates.get(this) if (cached == null) { - throw new CodeError('dek missing', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('dek missing') } const dek = cached.dek @@ -394,17 +393,17 @@ export class DefaultKeychain implements Keychain { async importKey (name: string, pem: string, password: string): Promise { if (!validateKeyName(name) || name === 'self') { await randomDelay() - throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME) + throw new InvalidParametersError(`Invalid key name '${name}'`) } if (pem == null) { await randomDelay() - throw new CodeError('PEM encoded key is required', codes.ERR_PEM_REQUIRED) + throw new InvalidParametersError('PEM encoded key is required') } const dsname = DsName(name) const exists = await this.components.datastore.has(dsname) if (exists) { await randomDelay() - throw new CodeError(`Key '${name}' already exists`, codes.ERR_KEY_ALREADY_EXISTS) + throw new InvalidParametersError(`Key '${name}' already exists`) } let privateKey @@ -412,7 +411,7 @@ export class DefaultKeychain implements Keychain { privateKey = await importKey(pem, password) } catch (err: any) { await randomDelay() - throw new CodeError('Cannot read the key, most likely the password is wrong', codes.ERR_CANNOT_READ_KEY) + throw new InvalidParametersError('Cannot read the key, most likely the password is wrong') } let kid @@ -421,7 +420,7 @@ export class DefaultKeychain implements Keychain { const cached = privates.get(this) if (cached == null) { - throw new CodeError('dek missing', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('dek missing') } const dek = cached.dek @@ -449,13 +448,13 @@ export class DefaultKeychain implements Keychain { async importPeer (name: string, peer: PeerId): Promise { try { if (!validateKeyName(name)) { - throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME) + throw new InvalidParametersError(`Invalid key name '${name}'`) } if (peer == null) { - throw new CodeError('PeerId is required', codes.ERR_MISSING_PRIVATE_KEY) + throw new InvalidParametersError('PeerId is required') } if (peer.privateKey == null) { - throw new CodeError('PeerId.privKey is required', codes.ERR_MISSING_PRIVATE_KEY) + throw new InvalidParametersError('PeerId.privKey is required') } const privateKey = await unmarshalPrivateKey(peer.privateKey) @@ -464,13 +463,13 @@ export class DefaultKeychain implements Keychain { const exists = await this.components.datastore.has(dsname) if (exists) { await randomDelay() - throw new CodeError(`Key '${name}' already exists`, codes.ERR_KEY_ALREADY_EXISTS) + throw new InvalidParametersError(`Key '${name}' already exists`) } const cached = privates.get(this) if (cached == null) { - throw new CodeError('dek missing', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('dek missing') } const dek = cached.dek @@ -496,7 +495,7 @@ export class DefaultKeychain implements Keychain { async getPrivateKey (name: string): Promise { if (!validateKeyName(name)) { await randomDelay() - throw new CodeError(`Invalid key name '${name}'`, codes.ERR_INVALID_KEY_NAME) + throw new InvalidParametersError(`Invalid key name '${name}'`) } try { @@ -506,7 +505,7 @@ export class DefaultKeychain implements Keychain { } catch (err: any) { await randomDelay() this.log.error(err) - throw new CodeError(`Key '${name}' does not exist.`, codes.ERR_KEY_NOT_FOUND) + throw new InvalidParametersError(`Key '${name}' does not exist.`) } } @@ -516,21 +515,21 @@ export class DefaultKeychain implements Keychain { async rotateKeychainPass (oldPass: string, newPass: string): Promise { if (typeof oldPass !== 'string') { await randomDelay() - throw new CodeError(`Invalid old pass type '${typeof oldPass}'`, codes.ERR_INVALID_OLD_PASS_TYPE) + throw new InvalidParametersError(`Invalid old pass type '${typeof oldPass}'`) } if (typeof newPass !== 'string') { await randomDelay() - throw new CodeError(`Invalid new pass type '${typeof newPass}'`, codes.ERR_INVALID_NEW_PASS_TYPE) + throw new InvalidParametersError(`Invalid new pass type '${typeof newPass}'`) } if (newPass.length < 20) { await randomDelay() - throw new CodeError(`Invalid pass length ${newPass.length}`, codes.ERR_INVALID_PASS_LENGTH) + throw new InvalidParametersError(`Invalid pass length ${newPass.length}`) } this.log('recreating keychain') const cached = privates.get(this) if (cached == null) { - throw new CodeError('dek missing', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('dek missing') } const oldDek = cached.dek diff --git a/packages/keychain/test/keychain.spec.ts b/packages/keychain/test/keychain.spec.ts index 2c3e6b2b1f..9bddb63705 100644 --- a/packages/keychain/test/keychain.spec.ts +++ b/packages/keychain/test/keychain.spec.ts @@ -148,7 +148,7 @@ describe('keychain', () => { expect(errors).to.have.length(5) errors.forEach(error => { - expect(error).to.have.property('code', 'ERR_INVALID_KEY_NAME') + expect(error).to.have.property('name', 'InvalidParametersError') }) }) }) @@ -168,39 +168,47 @@ describe('keychain', () => { it('throws if an invalid private key name is given', async () => { // @ts-expect-error invalid parameters - await expect(ks.getPrivateKey(undefined)).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_NAME') + await expect(ks.getPrivateKey(undefined)).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) it('throws if a private key cant be found', async () => { - await expect(ks.getPrivateKey('not real')).to.eventually.be.rejected.with.property('code', 'ERR_KEY_NOT_FOUND') + await expect(ks.getPrivateKey('not real')).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) it('does not overwrite existing key', async () => { - await expect(ks.createKey(rsaKeyName, 'RSA', 2048)).to.eventually.be.rejected.with.property('code', 'ERR_KEY_ALREADY_EXISTS') + await expect(ks.createKey(rsaKeyName, 'RSA', 2048)).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) it('cannot create the "self" key', async () => { - await expect(ks.createKey('self', 'RSA', 2048)).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_NAME') + await expect(ks.createKey('self', 'RSA', 2048)).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) it('should validate name is string', async () => { // @ts-expect-error invalid parameters - await expect(ks.createKey(5, 'rsa', 2048)).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_NAME') + await expect(ks.createKey(5, 'rsa', 2048)).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) it('should validate type is string', async () => { // @ts-expect-error invalid parameters - await expect(ks.createKey(`TEST-${Date.now()}`, null, 2048)).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_TYPE') + await expect(ks.createKey(`TEST-${Date.now()}`, null, 2048)).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) it('should validate size is integer', async () => { // @ts-expect-error invalid parameters - await expect(ks.createKey(`TEST-${Date.now()}`, 'RSA', 'string')).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_SIZE') + await expect(ks.createKey(`TEST-${Date.now()}`, 'RSA', 'string')).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) describe('implements NIST SP 800-131A', () => { it('disallows RSA length < 2048', async () => { - await expect(ks.createKey('bad-nist-rsa', 'RSA', 1024)).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_SIZE') + await expect(ks.createKey('bad-nist-rsa', 'RSA', 1024)).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) }) }) @@ -215,7 +223,8 @@ describe('keychain', () => { }) it('does not overwrite existing key', async () => { - await expect(ks.createKey(keyName, 'Ed25519')).to.eventually.be.rejected.with.property('code', 'ERR_KEY_ALREADY_EXISTS') + await expect(ks.createKey(keyName, 'Ed25519')).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) it('can export/import a key', async () => { @@ -230,7 +239,8 @@ describe('keychain', () => { }) it('cannot create the "self" key', async () => { - await expect(ks.createKey('self', 'Ed25519')).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_NAME') + await expect(ks.createKey('self', 'Ed25519')).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) }) @@ -269,12 +279,14 @@ describe('keychain', () => { it('requires the password', async () => { // @ts-expect-error invalid parameters - await expect(ks.exportKey(rsaKeyName)).to.eventually.be.rejected.with.property('code', 'ERR_PASSWORD_REQUIRED') + await expect(ks.exportKey(rsaKeyName)).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) it('requires the key name', async () => { // @ts-expect-error invalid parameters - await expect(ks.exportKey(undefined, 'password')).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_NAME') + await expect(ks.exportKey(undefined, 'password')).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) it('is a PKCS #8 encrypted pem', async () => { @@ -290,15 +302,18 @@ describe('keychain', () => { it('requires the pem', async () => { // @ts-expect-error invalid parameters - await expect(ks.importKey('imported-key', undefined, 'password')).to.eventually.be.rejected.with.property('code', 'ERR_PEM_REQUIRED') + await expect(ks.importKey('imported-key', undefined, 'password')).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) it('cannot be imported as an existing key name', async () => { - await expect(ks.importKey(rsaKeyName, pemKey, 'password')).to.eventually.be.rejected.with.property('code', 'ERR_KEY_ALREADY_EXISTS') + await expect(ks.importKey(rsaKeyName, pemKey, 'password')).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) it('cannot be imported with the wrong password', async () => { - await expect(ks.importKey('a-new-name-for-import', pemKey, 'not the password')).to.eventually.be.rejected.with.property('code', 'ERR_CANNOT_READ_KEY') + await expect(ks.importKey('a-new-name-for-import', pemKey, 'not the password')).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) }) @@ -328,12 +343,14 @@ describe('keychain', () => { it('private key import requires a valid name', async () => { // @ts-expect-error invalid parameters - await expect(ks.importPeer(undefined, alice)).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_NAME') + await expect(ks.importPeer(undefined, alice)).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) it('private key import requires the peer', async () => { // @ts-expect-error invalid parameters - await expect(ks.importPeer('alice')).to.eventually.be.rejected.with.property('code', 'ERR_MISSING_PRIVATE_KEY') + await expect(ks.importPeer('alice')).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) it('key id exists', async () => { @@ -383,19 +400,23 @@ describe('keychain', () => { describe('rename', () => { it('requires an existing key name', async () => { - await expect(ks.renameKey('not-there', renamedRsaKeyName)).to.eventually.be.rejected.with.property('code', 'ERR_NOT_FOUND') + await expect(ks.renameKey('not-there', renamedRsaKeyName)).to.eventually.be.rejected + .with.property('name', 'NotFoundError') }) it('requires a valid new key name', async () => { - await expect(ks.renameKey(rsaKeyName, '..\not-valid')).to.eventually.be.rejected.with.property('code', 'ERR_NEW_KEY_NAME_INVALID') + await expect(ks.renameKey(rsaKeyName, '..\not-valid')).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) it('does not overwrite existing key', async () => { - await expect(ks.renameKey(rsaKeyName, rsaKeyName)).to.eventually.be.rejected.with.property('code', 'ERR_KEY_ALREADY_EXISTS') + await expect(ks.renameKey(rsaKeyName, rsaKeyName)).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) it('cannot create the "self" key', async () => { - await expect(ks.renameKey(rsaKeyName, 'self')).to.eventually.be.rejected.with.property('code', 'ERR_NEW_KEY_NAME_INVALID') + await expect(ks.renameKey(rsaKeyName, 'self')).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) it('removes the existing key name', async () => { @@ -422,17 +443,20 @@ describe('keychain', () => { it('throws with invalid key names', async () => { // @ts-expect-error invalid parameters - await expect(ks.findKeyByName(undefined)).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_NAME') + await expect(ks.findKeyByName(undefined)).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) }) describe('key removal', () => { it('cannot remove the "self" key', async () => { - await expect(ks.removeKey('self')).to.eventually.be.rejected.with.property('code', 'ERR_INVALID_KEY_NAME') + await expect(ks.removeKey('self')).to.eventually.be.rejected + .with.property('name', 'InvalidParametersError') }) it('cannot remove an unknown key', async () => { - await expect(ks.removeKey('not-there')).to.eventually.be.rejected.with.property('code', 'ERR_KEY_NOT_FOUND') + await expect(ks.removeKey('not-there')).to.eventually.be.rejected + .with.property('name', 'NotFoundError') }) it('can remove a known key', async () => { diff --git a/packages/libp2p/package.json b/packages/libp2p/package.json index 2ed3091174..b20d3ef6d1 100644 --- a/packages/libp2p/package.json +++ b/packages/libp2p/package.json @@ -99,8 +99,8 @@ "@multiformats/multiaddr": "^12.2.3", "@multiformats/multiaddr-matcher": "^1.2.1", "any-signal": "^4.1.1", - "datastore-core": "^9.2.9", - "interface-datastore": "^8.2.11", + "datastore-core": "^10.0.0", + "interface-datastore": "^8.3.0", "it-byte-stream": "^1.0.12", "it-merge": "^3.0.5", "it-parallel": "^3.0.7", diff --git a/packages/libp2p/src/components.ts b/packages/libp2p/src/components.ts index 508ab2fc33..461a9fd12b 100644 --- a/packages/libp2p/src/components.ts +++ b/packages/libp2p/src/components.ts @@ -1,6 +1,7 @@ -import { CodeError, serviceCapabilities, serviceDependencies } from '@libp2p/interface' +import { serviceCapabilities, serviceDependencies } from '@libp2p/interface' import { isStartable, type Startable, type Libp2pEvents, type ComponentLogger, type NodeInfo, type ConnectionProtector, type ConnectionGater, type ContentRouting, type TypedEventTarget, type Metrics, type PeerId, type PeerRouting, type PeerStore, type PrivateKey, type Upgrader } from '@libp2p/interface' import { defaultLogger } from '@libp2p/logger' +import { MissingServiceError, UnmetServiceDependenciesError } from './errors.js' import type { AddressManager, ConnectionManager, RandomWalk, Registrar, TransportManager } from '@libp2p/interface-internal' import type { DNS } from '@multiformats/dns' import type { Datastore } from 'interface-datastore' @@ -134,7 +135,7 @@ export function defaultComponents (init: ComponentsInit = {}): Components { const service = components.components[prop] if (service == null && !OPTIONAL_SERVICES.includes(prop)) { - throw new CodeError(`${prop} not set`, 'ERR_SERVICE_MISSING') + throw new MissingServiceError(`${prop} not set`) } return service @@ -170,7 +171,7 @@ export function checkServiceDependencies (components: Components): void { for (const service of Object.values(components.components)) { for (const capability of getServiceDependencies(service)) { if (serviceCapabilities[capability] !== true) { - throw new CodeError(`Service "${getServiceName(service)}" required capability "${capability}" but it was not provided by any component, you may need to add additional configuration when creating your node.`, 'ERR_UNMET_SERVICE_DEPENDENCIES') + throw new UnmetServiceDependenciesError(`Service "${getServiceName(service)}" required capability "${capability}" but it was not provided by any component, you may need to add additional configuration when creating your node.`) } } } diff --git a/packages/libp2p/src/config.ts b/packages/libp2p/src/config.ts index 606e4efa53..a8d7619aa0 100644 --- a/packages/libp2p/src/config.ts +++ b/packages/libp2p/src/config.ts @@ -1,9 +1,8 @@ -import { CodeError, FaultTolerance } from '@libp2p/interface' +import { FaultTolerance, InvalidParametersError } from '@libp2p/interface' import { peerIdFromKeys } from '@libp2p/peer-id' import { defaultAddressSort } from '@libp2p/utils/address-sort' import { dnsaddrResolver } from '@multiformats/multiaddr/resolvers' import mergeOptions from 'merge-options' -import { codes, messages } from './errors.js' import type { Libp2pInit } from './index.js' import type { ServiceMap } from '@libp2p/interface' import type { Multiaddr } from '@multiformats/multiaddr' @@ -30,11 +29,11 @@ export async function validateConfig & Required, 'peerId'>> = mergeOptions(DefaultConfig, opts) if (resultingOptions.connectionProtector === null && globalThis.process?.env?.LIBP2P_FORCE_PNET != null) { // eslint-disable-line no-undef - throw new CodeError(messages.ERR_PROTECTOR_REQUIRED, codes.ERR_PROTECTOR_REQUIRED) + throw new InvalidParametersError('Private network is enforced, but no protector was provided') } if (resultingOptions.privateKey != null && !(await peerIdFromKeys(resultingOptions.privateKey.public.bytes, resultingOptions.privateKey.bytes)).equals(resultingOptions.peerId)) { - throw new CodeError('Private key doesn\'t match peer id', codes.ERR_INVALID_KEY) + throw new InvalidParametersError('Private key doesn\'t match peer id') } return resultingOptions diff --git a/packages/libp2p/src/connection-manager/connection-pruner.ts b/packages/libp2p/src/connection-manager/connection-pruner.ts index e04e49f18a..784e3916e2 100644 --- a/packages/libp2p/src/connection-manager/connection-pruner.ts +++ b/packages/libp2p/src/connection-manager/connection-pruner.ts @@ -84,7 +84,7 @@ export class ConnectionPruner { return acc + curr.value }, 0)) } catch (err: any) { - if (err.code !== 'ERR_NOT_FOUND') { + if (err.name !== 'NotFoundError') { this.log.error('error loading peer tags', err) } } diff --git a/packages/libp2p/src/connection-manager/dial-queue.ts b/packages/libp2p/src/connection-manager/dial-queue.ts index 6dae392cb9..9d927e4c51 100644 --- a/packages/libp2p/src/connection-manager/dial-queue.ts +++ b/packages/libp2p/src/connection-manager/dial-queue.ts @@ -1,5 +1,5 @@ /* eslint-disable max-depth */ -import { CodeError, AggregateCodeError, ERR_TIMEOUT, setMaxListeners } from '@libp2p/interface' +import { TimeoutError, DialError, setMaxListeners } from '@libp2p/interface' import { PeerMap } from '@libp2p/peer-collections' import { defaultAddressSort } from '@libp2p/utils/address-sort' import { PriorityQueue, type PriorityQueueJobOptions } from '@libp2p/utils/priority-queue' @@ -9,7 +9,7 @@ import { Circuit } from '@multiformats/multiaddr-matcher' import { type ClearableSignal, anySignal } from 'any-signal' import { CustomProgressEvent } from 'progress-events' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { codes } from '../errors.js' +import { DialDeniedError, NoValidAddressesError } from '../errors.js' import { getPeerAddress } from '../get-peer.js' import { DIAL_TIMEOUT, @@ -192,7 +192,7 @@ export class DialQueue { } if (this.queue.size >= this.maxDialQueueLength) { - throw new CodeError('Dial queue is full', 'ERR_DIAL_QUEUE_FULL') + throw new DialError('Dial queue is full') } this.log('creating dial target for %p', peerId, multiaddrs.map(ma => ma.toString())) @@ -231,7 +231,7 @@ export class DialQueue { if (dialed === this.maxPeerAddrsToDial) { this.log('dialed maxPeerAddrsToDial (%d) addresses for %p, not trying any others', dialed, peerId) - throw new CodeError('Peer had more than maxPeerAddrsToDial', codes.ERR_TOO_MANY_ADDRESSES) + throw new DialError('Peer had more than maxPeerAddrsToDial') } dialed++ @@ -263,7 +263,7 @@ export class DialQueue { // the user/dial timeout/shutdown controller signal aborted if (signal.aborted) { - throw new CodeError(err.message, ERR_TIMEOUT) + throw new TimeoutError(err.message) } errors.push(err) @@ -274,7 +274,7 @@ export class DialQueue { throw errors[0] } - throw new AggregateCodeError(errors, 'All multiaddr dials failed', codes.ERR_TRANSPORT_DIAL_FAILED) + throw new AggregateError(errors, 'All multiaddr dials failed') } finally { // clean up abort signals/controllers signal.clear() @@ -312,11 +312,11 @@ export class DialQueue { // if a peer id or multiaddr(s) with a peer id, make sure it isn't our peer id and that we are allowed to dial it if (peerId != null) { if (this.components.peerId.equals(peerId)) { - throw new CodeError('Tried to dial self', codes.ERR_DIALED_SELF) + throw new DialError('Tried to dial self') } if ((await this.components.connectionGater.denyDialPeer?.(peerId)) === true) { - throw new CodeError('The dial request is blocked by gater.allowDialPeer', codes.ERR_PEER_DIAL_INTERCEPTED) + throw new DialDeniedError('The dial request is blocked by gater.allowDialPeer') } // if just a peer id was passed, load available multiaddrs for this peer @@ -328,7 +328,7 @@ export class DialQueue { addrs.push(...peer.addresses) this.log('loaded multiaddrs for %p', peerId, addrs.map(({ multiaddr }) => multiaddr.toString())) } catch (err: any) { - if (err.code !== codes.ERR_NOT_FOUND) { + if (err.name !== 'NotFoundError') { throw err } } @@ -349,7 +349,7 @@ export class DialQueue { isCertified: false }))) } catch (err: any) { - if (err.code !== codes.ERR_NO_ROUTERS_AVAILABLE) { + if (err.name !== 'NoPeerRoutersError') { this.log.error('looking up multiaddrs for %p in the peer routing failed', peerId, err) } } @@ -437,7 +437,7 @@ export class DialQueue { // make sure we actually have some addresses to dial if (dedupedMultiaddrs.length === 0) { - throw new CodeError('The dial request has no valid addresses', codes.ERR_NO_VALID_ADDRESSES) + throw new NoValidAddressesError('The dial request has no valid addresses') } const gatedAdrs: Address[] = [] @@ -454,7 +454,7 @@ export class DialQueue { // make sure we actually have some addresses to dial if (sortedGatedAddrs.length === 0) { - throw new CodeError('The connection gater denied all addresses in the dial request', codes.ERR_NO_VALID_ADDRESSES) + throw new DialDeniedError('The connection gater denied all addresses in the dial request') } this.log.trace('addresses for %p before filtering', peerId ?? 'unknown peer', resolvedAddresses.map(({ multiaddr }) => multiaddr.toString())) diff --git a/packages/libp2p/src/connection-manager/index.ts b/packages/libp2p/src/connection-manager/index.ts index 85a34fd748..72972f060e 100644 --- a/packages/libp2p/src/connection-manager/index.ts +++ b/packages/libp2p/src/connection-manager/index.ts @@ -1,11 +1,10 @@ -import { CodeError, KEEP_ALIVE } from '@libp2p/interface' +import { InvalidParametersError, KEEP_ALIVE, NotStartedError } from '@libp2p/interface' import { PeerMap } from '@libp2p/peer-collections' import { defaultAddressSort } from '@libp2p/utils/address-sort' import { RateLimiter } from '@libp2p/utils/rate-limiter' import { type Multiaddr, type Resolver, multiaddr } from '@multiformats/multiaddr' import { dnsaddrResolver } from '@multiformats/multiaddr/resolvers' import { CustomProgressEvent } from 'progress-events' -import { codes } from '../errors.js' import { getPeerAddress } from '../get-peer.js' import { AutoDial } from './auto-dial.js' import { ConnectionPruner } from './connection-pruner.js' @@ -190,7 +189,7 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { const minConnections = init.minConnections ?? defaultOptions.minConnections if (this.maxConnections < minConnections) { - throw new CodeError('Connection Manager maxConnections must be greater than minConnections', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Connection Manager maxConnections must be greater than minConnections') } /** @@ -495,7 +494,7 @@ export class DefaultConnectionManager implements ConnectionManager, Startable { async openConnection (peerIdOrMultiaddr: PeerId | Multiaddr | Multiaddr[], options: OpenConnectionOptions = {}): Promise { if (!this.isStarted()) { - throw new CodeError('Not started', codes.ERR_NODE_NOT_STARTED) + throw new NotStartedError('Not started') } options.signal?.throwIfAborted() diff --git a/packages/libp2p/src/connection/index.ts b/packages/libp2p/src/connection/index.ts index c3955ca47c..735515bc9b 100644 --- a/packages/libp2p/src/connection/index.ts +++ b/packages/libp2p/src/connection/index.ts @@ -1,4 +1,4 @@ -import { connectionSymbol, CodeError, setMaxListeners } from '@libp2p/interface' +import { connectionSymbol, setMaxListeners, LimitedConnectionError, ConnectionClosedError, ConnectionClosingError } from '@libp2p/interface' import type { AbortOptions, Logger, ComponentLogger, Direction, Connection, Stream, ConnectionTimeline, ConnectionStatus, NewStreamOptions, PeerId, ConnectionLimits } from '@libp2p/interface' import type { Multiaddr } from '@multiformats/multiaddr' @@ -116,11 +116,11 @@ export class ConnectionImpl implements Connection { */ async newStream (protocols: string | string[], options?: NewStreamOptions): Promise { if (this.status === 'closing') { - throw new CodeError('the connection is being closed', 'ERR_CONNECTION_BEING_CLOSED') + throw new ConnectionClosingError('the connection is being closed') } if (this.status === 'closed') { - throw new CodeError('the connection is closed', 'ERR_CONNECTION_CLOSED') + throw new ConnectionClosedError('the connection is closed') } if (!Array.isArray(protocols)) { @@ -128,7 +128,7 @@ export class ConnectionImpl implements Connection { } if (this.limits != null && options?.runOnLimitedConnection !== true) { - throw new CodeError('Cannot open protocol stream on limited connection', 'ERR_LIMITED_CONNECTION') + throw new LimitedConnectionError('Cannot open protocol stream on limited connection') } const stream = await this._newStream(protocols, options) diff --git a/packages/libp2p/src/content-routing.ts b/packages/libp2p/src/content-routing.ts index ee878cc5b7..a2a1a0d9e7 100644 --- a/packages/libp2p/src/content-routing.ts +++ b/packages/libp2p/src/content-routing.ts @@ -1,7 +1,7 @@ -import { CodeError } from '@libp2p/interface' +import { NotStartedError } from '@libp2p/interface' import { PeerSet } from '@libp2p/peer-collections' import merge from 'it-merge' -import { codes, messages } from './errors.js' +import { NoContentRoutersError } from './errors.js' import type { AbortOptions, ComponentLogger, ContentRouting, PeerInfo, PeerRouting, PeerStore, RoutingOptions, Startable } from '@libp2p/interface' import type { CID } from 'multiformats/cid' @@ -45,7 +45,7 @@ export class CompoundContentRouting implements ContentRouting, Startable { */ async * findProviders (key: CID, options: RoutingOptions = {}): AsyncIterable { if (this.routers.length === 0) { - throw new CodeError('No content routers available', codes.ERR_NO_ROUTERS_AVAILABLE) + throw new NoContentRoutersError('No content routers available') } const self = this @@ -84,7 +84,7 @@ export class CompoundContentRouting implements ContentRouting, Startable { */ async provide (key: CID, options: AbortOptions = {}): Promise { if (this.routers.length === 0) { - throw new CodeError('No content routers available', codes.ERR_NO_ROUTERS_AVAILABLE) + throw new NoContentRoutersError('No content routers available') } await Promise.all(this.routers.map(async (router) => { @@ -97,7 +97,7 @@ export class CompoundContentRouting implements ContentRouting, Startable { */ async put (key: Uint8Array, value: Uint8Array, options?: AbortOptions): Promise { if (!this.isStarted()) { - throw new CodeError(messages.NOT_STARTED_YET, codes.ERR_NODE_NOT_STARTED) + throw new NotStartedError() } await Promise.all(this.routers.map(async (router) => { @@ -111,7 +111,7 @@ export class CompoundContentRouting implements ContentRouting, Startable { */ async get (key: Uint8Array, options?: AbortOptions): Promise { if (!this.isStarted()) { - throw new CodeError(messages.NOT_STARTED_YET, codes.ERR_NODE_NOT_STARTED) + throw new NotStartedError() } return Promise.any(this.routers.map(async (router) => { diff --git a/packages/libp2p/src/errors.ts b/packages/libp2p/src/errors.ts index d89459fb3d..98c847aa8c 100644 --- a/packages/libp2p/src/errors.ts +++ b/packages/libp2p/src/errors.ts @@ -1,67 +1,102 @@ export enum messages { NOT_STARTED_YET = 'The libp2p node is not started yet', - ERR_PROTECTOR_REQUIRED = 'Private network is enforced, but no protector was provided', NOT_FOUND = 'Not found' } -export enum codes { - ERR_PROTECTOR_REQUIRED = 'ERR_PROTECTOR_REQUIRED', - ERR_PEER_DIAL_INTERCEPTED = 'ERR_PEER_DIAL_INTERCEPTED', - ERR_CONNECTION_INTERCEPTED = 'ERR_CONNECTION_INTERCEPTED', - ERR_INVALID_PROTOCOLS_FOR_STREAM = 'ERR_INVALID_PROTOCOLS_FOR_STREAM', - ERR_CONNECTION_ENDED = 'ERR_CONNECTION_ENDED', - ERR_CONNECTION_FAILED = 'ERR_CONNECTION_FAILED', - ERR_NODE_NOT_STARTED = 'ERR_NODE_NOT_STARTED', - ERR_ALREADY_ABORTED = 'ERR_ALREADY_ABORTED', - ERR_TOO_MANY_ADDRESSES = 'ERR_TOO_MANY_ADDRESSES', - ERR_NO_VALID_ADDRESSES = 'ERR_NO_VALID_ADDRESSES', - ERR_RELAYED_DIAL = 'ERR_RELAYED_DIAL', - ERR_DIALED_SELF = 'ERR_DIALED_SELF', - ERR_DISCOVERED_SELF = 'ERR_DISCOVERED_SELF', - ERR_DUPLICATE_TRANSPORT = 'ERR_DUPLICATE_TRANSPORT', - ERR_ENCRYPTION_FAILED = 'ERR_ENCRYPTION_FAILED', - ERR_HOP_REQUEST_FAILED = 'ERR_HOP_REQUEST_FAILED', - ERR_INVALID_KEY = 'ERR_INVALID_KEY', - ERR_INVALID_MESSAGE = 'ERR_INVALID_MESSAGE', - ERR_INVALID_PARAMETERS = 'ERR_INVALID_PARAMETERS', - ERR_INVALID_PEER = 'ERR_INVALID_PEER', - ERR_MUXER_UNAVAILABLE = 'ERR_MUXER_UNAVAILABLE', - ERR_NOT_FOUND = 'ERR_NOT_FOUND', - ERR_TRANSPORT_UNAVAILABLE = 'ERR_TRANSPORT_UNAVAILABLE', - ERR_TRANSPORT_DIAL_FAILED = 'ERR_TRANSPORT_DIAL_FAILED', - ERR_UNSUPPORTED_PROTOCOL = 'ERR_UNSUPPORTED_PROTOCOL', - ERR_PROTOCOL_HANDLER_ALREADY_REGISTERED = 'ERR_PROTOCOL_HANDLER_ALREADY_REGISTERED', - ERR_INVALID_MULTIADDR = 'ERR_INVALID_MULTIADDR', - ERR_SIGNATURE_NOT_VALID = 'ERR_SIGNATURE_NOT_VALID', - ERR_FIND_SELF = 'ERR_FIND_SELF', - ERR_NO_ROUTERS_AVAILABLE = 'ERR_NO_ROUTERS_AVAILABLE', - ERR_CONNECTION_NOT_MULTIPLEXED = 'ERR_CONNECTION_NOT_MULTIPLEXED', - ERR_NO_DIAL_TOKENS = 'ERR_NO_DIAL_TOKENS', - ERR_INVALID_CMS = 'ERR_INVALID_CMS', - ERR_MISSING_KEYS = 'ERR_MISSING_KEYS', - ERR_NO_KEY = 'ERR_NO_KEY', - ERR_INVALID_KEY_NAME = 'ERR_INVALID_KEY_NAME', - ERR_INVALID_KEY_TYPE = 'ERR_INVALID_KEY_TYPE', - ERR_KEY_ALREADY_EXISTS = 'ERR_KEY_ALREADY_EXISTS', - ERR_INVALID_KEY_SIZE = 'ERR_INVALID_KEY_SIZE', - ERR_KEY_NOT_FOUND = 'ERR_KEY_NOT_FOUND', - ERR_OLD_KEY_NAME_INVALID = 'ERR_OLD_KEY_NAME_INVALID', - ERR_NEW_KEY_NAME_INVALID = 'ERR_NEW_KEY_NAME_INVALID', - ERR_PASSWORD_REQUIRED = 'ERR_PASSWORD_REQUIRED', - ERR_PEM_REQUIRED = 'ERR_PEM_REQUIRED', - ERR_CANNOT_READ_KEY = 'ERR_CANNOT_READ_KEY', - ERR_MISSING_PRIVATE_KEY = 'ERR_MISSING_PRIVATE_KEY', - ERR_MISSING_PUBLIC_KEY = 'ERR_MISSING_PUBLIC_KEY', - ERR_INVALID_OLD_PASS_TYPE = 'ERR_INVALID_OLD_PASS_TYPE', - ERR_INVALID_NEW_PASS_TYPE = 'ERR_INVALID_NEW_PASS_TYPE', - ERR_INVALID_PASS_LENGTH = 'ERR_INVALID_PASS_LENGTH', - ERR_NOT_IMPLEMENTED = 'ERR_NOT_IMPLEMENTED', - ERR_WRONG_PING_ACK = 'ERR_WRONG_PING_ACK', - ERR_INVALID_RECORD = 'ERR_INVALID_RECORD', - ERR_ALREADY_SUCCEEDED = 'ERR_ALREADY_SUCCEEDED', - ERR_NO_HANDLER_FOR_PROTOCOL = 'ERR_NO_HANDLER_FOR_PROTOCOL', - ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS = 'ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS', - ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS = 'ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS', - ERR_CONNECTION_DENIED = 'ERR_CONNECTION_DENIED', - ERR_TRANSFER_LIMIT_EXCEEDED = 'ERR_TRANSFER_LIMIT_EXCEEDED' +export class MissingServiceError extends Error { + constructor (message = 'Missing service') { + super(message) + this.name = 'MissingServiceError' + } +} + +export class UnmetServiceDependenciesError extends Error { + constructor (message = 'Unmet service dependencies') { + super(message) + this.name = 'UnmetServiceDependenciesError' + } +} + +export class NoContentRoutersError extends Error { + constructor (message = 'No content routers available') { + super(message) + this.name = 'NoContentRoutersError' + } +} + +export class NoPeerRoutersError extends Error { + constructor (message = 'No peer routers available') { + super(message) + this.name = 'NoPeerRoutersError' + } +} + +export class QueriedForSelfError extends Error { + constructor (message = 'Should not try to find self') { + super(message) + this.name = 'QueriedForSelfError' + } +} + +export class UnhandledProtocolError extends Error { + constructor (message = 'Unhandled protocol error') { + super(message) + this.name = 'UnhandledProtocolError' + } +} + +export class DuplicateProtocolHandlerError extends Error { + constructor (message = 'Duplicate protocol handler error') { + super(message) + this.name = 'DuplicateProtocolHandlerError' + } +} + +export class DialDeniedError extends Error { + constructor (message = 'Dial denied error') { + super(message) + this.name = 'DialDeniedError' + } +} + +export class NoValidAddressesError extends Error { + constructor (message = 'No valid addresses') { + super(message) + this.name = 'NoValidAddressesError' + } +} + +export class ConnectionInterceptedError extends Error { + constructor (message = 'Connection intercepted') { + super(message) + this.name = 'ConnectionInterceptedError' + } +} + +export class ConnectionDeniedError extends Error { + constructor (message = 'Connection denied') { + super(message) + this.name = 'ConnectionDeniedError' + } +} + +export class MuxerUnavailableError extends Error { + constructor (message = 'Stream is not multiplexed') { + super(message) + this.name = 'MuxerUnavailableError' + } +} + +export class EncryptionFailedError extends Error { + constructor (message = 'Encryption failed') { + super(message) + this.name = 'EncryptionFailedError' + } +} + +export class TransportUnavailableError extends Error { + constructor (message = 'Transport unavailable') { + super(message) + this.name = 'TransportUnavailableError' + } } diff --git a/packages/libp2p/src/get-peer.ts b/packages/libp2p/src/get-peer.ts index 7a06aea244..4828b87be4 100644 --- a/packages/libp2p/src/get-peer.ts +++ b/packages/libp2p/src/get-peer.ts @@ -1,7 +1,6 @@ -import { CodeError, isPeerId } from '@libp2p/interface' +import { InvalidMultiaddrError, InvalidParametersError, isPeerId } from '@libp2p/interface' import { peerIdFromString } from '@libp2p/peer-id' import { isMultiaddr } from '@multiformats/multiaddr' -import { codes } from './errors.js' import type { PeerId } from '@libp2p/interface' import type { Multiaddr } from '@multiformats/multiaddr' @@ -31,20 +30,20 @@ export function getPeerAddress (peer: PeerId | Multiaddr | Multiaddr[]): PeerAdd // ensure PeerId is either not set or is consistent peer.forEach(ma => { if (!isMultiaddr(ma)) { - throw new CodeError('Invalid Multiaddr', codes.ERR_INVALID_MULTIADDR) + throw new InvalidMultiaddrError('Invalid multiaddr') } const maPeerIdStr = ma.getPeerId() if (maPeerIdStr == null) { if (peerId != null) { - throw new CodeError('Multiaddrs must all have the same peer id or have no peer id', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Multiaddrs must all have the same peer id or have no peer id') } } else { const maPeerId = peerIdFromString(maPeerIdStr) if (peerId?.equals(maPeerId) !== true) { - throw new CodeError('Multiaddrs must all have the same peer id or have no peer id', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Multiaddrs must all have the same peer id or have no peer id') } } }) diff --git a/packages/libp2p/src/libp2p.ts b/packages/libp2p/src/libp2p.ts index de11a09fff..11529fa87c 100644 --- a/packages/libp2p/src/libp2p.ts +++ b/packages/libp2p/src/libp2p.ts @@ -1,5 +1,5 @@ import { unmarshalPrivateKey, unmarshalPublicKey } from '@libp2p/crypto/keys' -import { contentRoutingSymbol, CodeError, TypedEventEmitter, setMaxListeners, peerDiscoverySymbol, peerRoutingSymbol } from '@libp2p/interface' +import { contentRoutingSymbol, TypedEventEmitter, setMaxListeners, peerDiscoverySymbol, peerRoutingSymbol, InvalidParametersError, InvalidPeerIdError } from '@libp2p/interface' import { defaultLogger } from '@libp2p/logger' import { PeerSet } from '@libp2p/peer-collections' import { peerIdFromString } from '@libp2p/peer-id' @@ -16,7 +16,6 @@ import { validateConfig } from './config.js' import { DefaultConnectionManager } from './connection-manager/index.js' import { ConnectionMonitor } from './connection-monitor.js' import { CompoundContentRouting } from './content-routing.js' -import { codes } from './errors.js' import { DefaultPeerRouting } from './peer-routing.js' import { RandomWalk } from './random-walk.js' import { DefaultRegistrar } from './registrar.js' @@ -288,13 +287,13 @@ export class Libp2pNode extends TypedEventEmi async dialProtocol (peer: PeerId | Multiaddr | Multiaddr[], protocols: string | string[], options: NewStreamOptions = {}): Promise { if (protocols == null) { - throw new CodeError('no protocols were provided to open a stream', codes.ERR_INVALID_PROTOCOLS_FOR_STREAM) + throw new InvalidParametersError('no protocols were provided to open a stream') } protocols = Array.isArray(protocols) ? protocols : [protocols] if (protocols.length === 0) { - throw new CodeError('no protocols were provided to open a stream', codes.ERR_INVALID_PROTOCOLS_FOR_STREAM) + throw new InvalidParametersError('no protocols were provided to open a stream') } const connection = await this.dial(peer, options) @@ -335,7 +334,7 @@ export class Libp2pNode extends TypedEventEmi return peerInfo.id.publicKey } } catch (err: any) { - if (err.code !== codes.ERR_NOT_FOUND) { + if (err.name !== 'NotFoundError') { throw err } } @@ -402,7 +401,7 @@ export class Libp2pNode extends TypedEventEmi const { detail: peer } = evt if (peer.id.toString() === this.peerId.toString()) { - this.log.error(new Error(codes.ERR_DISCOVERED_SELF)) + this.log.error('peer discovery mechanism discovered self') return } @@ -421,7 +420,7 @@ export async function createLibp2pNode (opti const peerId = options.peerId ??= await createEd25519PeerId() if (peerId.privateKey == null) { - throw new CodeError('peer id was missing private key', 'ERR_MISSING_PRIVATE_KEY') + throw new InvalidPeerIdError('Peer id was missing private key') } options.privateKey ??= await unmarshalPrivateKey(peerId.privateKey) diff --git a/packages/libp2p/src/peer-routing.ts b/packages/libp2p/src/peer-routing.ts index 9b311f95a8..46d3d7bb08 100644 --- a/packages/libp2p/src/peer-routing.ts +++ b/packages/libp2p/src/peer-routing.ts @@ -1,8 +1,8 @@ -import { CodeError } from '@libp2p/interface' +import { NotFoundError } from '@libp2p/interface' import { createScalableCuckooFilter } from '@libp2p/utils/filters' import merge from 'it-merge' import parallel from 'it-parallel' -import { codes, messages } from './errors.js' +import { NoPeerRoutersError, QueriedForSelfError } from './errors.js' import type { Logger, PeerId, PeerInfo, PeerRouting, PeerStore, RoutingOptions } from '@libp2p/interface' import type { ComponentLogger } from '@libp2p/logger' @@ -36,11 +36,11 @@ export class DefaultPeerRouting implements PeerRouting { */ async findPeer (id: PeerId, options?: RoutingOptions): Promise { if (this.routers.length === 0) { - throw new CodeError('No peer routers available', codes.ERR_NO_ROUTERS_AVAILABLE) + throw new NoPeerRoutersError('No peer routers available') } if (id.toString() === this.peerId.toString()) { - throw new CodeError('Should not try to find self', codes.ERR_FIND_SELF) + throw new QueriedForSelfError('Should not try to find self') } const self = this @@ -69,7 +69,7 @@ export class DefaultPeerRouting implements PeerRouting { return peer } - throw new CodeError(messages.NOT_FOUND, codes.ERR_NOT_FOUND) + throw new NotFoundError() } /** @@ -77,7 +77,7 @@ export class DefaultPeerRouting implements PeerRouting { */ async * getClosestPeers (key: Uint8Array, options: RoutingOptions = {}): AsyncIterable { if (this.routers.length === 0) { - throw new CodeError('No peer routers available', codes.ERR_NO_ROUTERS_AVAILABLE) + throw new NoPeerRoutersError('No peer routers available') } const self = this diff --git a/packages/libp2p/src/registrar.ts b/packages/libp2p/src/registrar.ts index c775b14c95..91d2fe9b83 100644 --- a/packages/libp2p/src/registrar.ts +++ b/packages/libp2p/src/registrar.ts @@ -1,6 +1,6 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidParametersError } from '@libp2p/interface' import merge from 'merge-options' -import { codes } from './errors.js' +import * as errorsJs from './errors.js' import type { IdentifyResult, Libp2pEvents, Logger, PeerUpdate, TypedEventTarget, PeerId, PeerStore, Topology } from '@libp2p/interface' import type { ConnectionManager, StreamHandlerOptions, StreamHandlerRecord, Registrar, StreamHandler } from '@libp2p/interface-internal' import type { ComponentLogger } from '@libp2p/logger' @@ -52,7 +52,7 @@ export class DefaultRegistrar implements Registrar { const handler = this.handlers.get(protocol) if (handler == null) { - throw new CodeError(`No handler registered for protocol ${protocol}`, codes.ERR_NO_HANDLER_FOR_PROTOCOL) + throw new errorsJs.UnhandledProtocolError(`No handler registered for protocol ${protocol}`) } return handler @@ -75,7 +75,7 @@ export class DefaultRegistrar implements Registrar { */ async handle (protocol: string, handler: StreamHandler, opts?: StreamHandlerOptions): Promise { if (this.handlers.has(protocol)) { - throw new CodeError(`Handler already registered for protocol ${protocol}`, codes.ERR_PROTOCOL_HANDLER_ALREADY_REGISTERED) + throw new errorsJs.DuplicateProtocolHandlerError(`Handler already registered for protocol ${protocol}`) } const options = merge.bind({ ignoreUndefined: true })({ @@ -116,7 +116,7 @@ export class DefaultRegistrar implements Registrar { */ async register (protocol: string, topology: Topology): Promise { if (topology == null) { - throw new CodeError('invalid topology', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('invalid topology') } // Create topology @@ -176,7 +176,7 @@ export class DefaultRegistrar implements Registrar { } }) .catch(err => { - if (err.code === codes.ERR_NOT_FOUND) { + if (err.name === 'NotFoundError') { // peer has not completed identify so they are not in the peer store return } diff --git a/packages/libp2p/src/transport-manager.ts b/packages/libp2p/src/transport-manager.ts index 411b158cd9..ed48e8fdd7 100644 --- a/packages/libp2p/src/transport-manager.ts +++ b/packages/libp2p/src/transport-manager.ts @@ -1,7 +1,7 @@ -import { CodeError, FaultTolerance } from '@libp2p/interface' +import { FaultTolerance, InvalidParametersError, NotStartedError } from '@libp2p/interface' import { trackedMap } from '@libp2p/utils/tracked-map' import { CustomProgressEvent } from 'progress-events' -import { codes } from './errors.js' +import { NoValidAddressesError, TransportUnavailableError } from './errors.js' import type { Libp2pEvents, ComponentLogger, Logger, Connection, TypedEventTarget, Metrics, Startable, Listener, Transport, Upgrader } from '@libp2p/interface' import type { AddressManager, TransportManager, TransportManagerDialOptions } from '@libp2p/interface-internal' import type { Multiaddr } from '@multiformats/multiaddr' @@ -47,11 +47,11 @@ export class DefaultTransportManager implements TransportManager, Startable { const tag = transport[Symbol.toStringTag] if (tag == null) { - throw new CodeError('Transport must have a valid tag', codes.ERR_INVALID_KEY) + throw new InvalidParametersError('Transport must have a valid tag') } if (this.transports.has(tag)) { - throw new CodeError(`There is already a transport with the tag ${tag}`, codes.ERR_DUPLICATE_TRANSPORT) + throw new InvalidParametersError(`There is already a transport with the tag ${tag}`) } this.log('adding transport %s', tag) @@ -112,26 +112,18 @@ export class DefaultTransportManager implements TransportManager, Startable { const transport = this.dialTransportForMultiaddr(ma) if (transport == null) { - throw new CodeError(`No transport available for address ${String(ma)}`, codes.ERR_TRANSPORT_UNAVAILABLE) + throw new TransportUnavailableError(`No transport available for address ${String(ma)}`) } options?.onProgress?.(new CustomProgressEvent('transport-manager:selected-transport', transport[Symbol.toStringTag])) - try { - // @ts-expect-error the transport has a typed onProgress option but we - // can't predict what transport implementation we selected so all we can - // do is pass the onProgress handler in and hope for the best - return await transport.dial(ma, { - ...options, - upgrader: this.components.upgrader - }) - } catch (err: any) { - if (err.code == null) { - err.code = codes.ERR_TRANSPORT_DIAL_FAILED - } - - throw err - } + // @ts-expect-error the transport has a typed onProgress option but we + // can't predict what transport implementation we selected so all we can + // do is pass the onProgress handler in and hope for the best + return transport.dial(ma, { + ...options, + upgrader: this.components.upgrader + }) } /** @@ -192,7 +184,7 @@ export class DefaultTransportManager implements TransportManager, Startable { */ async listen (addrs: Multiaddr[]): Promise { if (!this.isStarted()) { - throw new CodeError('Not started', codes.ERR_NODE_NOT_STARTED) + throw new NotStartedError('Not started') } if (addrs == null || addrs.length === 0) { @@ -256,7 +248,7 @@ export class DefaultTransportManager implements TransportManager, Startable { // just wait for any (`p-any`) listener to succeed on each transport before returning const isListening = results.find(r => r.status === 'fulfilled') if ((isListening == null) && this.faultTolerance !== FaultTolerance.NO_FATAL) { - throw new CodeError(`Transport (${key}) could not listen on any available address`, codes.ERR_NO_VALID_ADDRESSES) + throw new NoValidAddressesError(`Transport (${key}) could not listen on any available address`) } } @@ -265,7 +257,7 @@ export class DefaultTransportManager implements TransportManager, Startable { if (couldNotListen.length === this.transports.size) { const message = `no valid addresses were provided for transports [${couldNotListen.join(', ')}]` if (this.faultTolerance === FaultTolerance.FATAL_ALL) { - throw new CodeError(message, codes.ERR_NO_VALID_ADDRESSES) + throw new NoValidAddressesError(message) } this.log(`libp2p in dial mode only: ${message}`) } diff --git a/packages/libp2p/src/upgrader.ts b/packages/libp2p/src/upgrader.ts index 70069e1f54..d3d8d49186 100644 --- a/packages/libp2p/src/upgrader.ts +++ b/packages/libp2p/src/upgrader.ts @@ -1,10 +1,10 @@ -import { CodeError, ERR_TIMEOUT, setMaxListeners } from '@libp2p/interface' +import { InvalidMultiaddrError, InvalidPeerIdError, TooManyInboundProtocolStreamsError, TooManyOutboundProtocolStreamsError, LimitedConnectionError, TimeoutError, setMaxListeners } from '@libp2p/interface' import * as mss from '@libp2p/multistream-select' import { peerIdFromString } from '@libp2p/peer-id' import { CustomProgressEvent } from 'progress-events' import { createConnection } from './connection/index.js' import { INBOUND_UPGRADE_TIMEOUT } from './connection-manager/constants.js' -import { codes } from './errors.js' +import { ConnectionDeniedError, ConnectionInterceptedError, EncryptionFailedError, MuxerUnavailableError } from './errors.js' import { DEFAULT_MAX_INBOUND_STREAMS, DEFAULT_MAX_OUTBOUND_STREAMS } from './registrar.js' import type { Libp2pEvents, AbortOptions, ComponentLogger, MultiaddrConnection, Connection, Stream, ConnectionProtector, NewStreamOptions, ConnectionEncrypter, SecuredConnection, ConnectionGater, TypedEventTarget, Metrics, PeerId, PeerStore, StreamMuxer, StreamMuxerFactory, Upgrader, UpgraderOptions, ConnectionLimits } from '@libp2p/interface' import type { ConnectionManager, Registrar } from '@libp2p/interface-internal' @@ -48,7 +48,7 @@ function findIncomingStreamLimit (protocol: string, registrar: Registrar): numbe return options.maxInboundStreams } catch (err: any) { - if (err.code !== codes.ERR_NO_HANDLER_FOR_PROTOCOL) { + if (err.name !== 'UnhandledProtocolError') { throw err } } @@ -64,7 +64,7 @@ function findOutgoingStreamLimit (protocol: string, registrar: Registrar, option return options.maxOutboundStreams } } catch (err: any) { - if (err.code !== codes.ERR_NO_HANDLER_FOR_PROTOCOL) { + if (err.name !== 'UnhandledProtocolError') { throw err } } @@ -130,7 +130,7 @@ export class DefaultUpgrader implements Upgrader { if (connectionGater !== undefined) { if (await connectionGater(remotePeer, maConn)) { - throw new CodeError(`The multiaddr connection is blocked by gater.${connectionType}`, codes.ERR_CONNECTION_INTERCEPTED) + throw new ConnectionInterceptedError(`The multiaddr connection is blocked by gater.${connectionType}`) } } } @@ -142,7 +142,7 @@ export class DefaultUpgrader implements Upgrader { const accept = await this.components.connectionManager.acceptIncomingConnection(maConn) if (!accept) { - throw new CodeError('connection denied', codes.ERR_CONNECTION_DENIED) + throw new ConnectionDeniedError('connection denied') } let encryptedConn: MultiaddrConnection @@ -154,7 +154,7 @@ export class DefaultUpgrader implements Upgrader { const signal = AbortSignal.timeout(this.inboundUpgradeTimeout) const onAbort = (): void => { - maConn.abort(new CodeError('inbound upgrade timeout', ERR_TIMEOUT)) + maConn.abort(new TimeoutError('inbound upgrade timeout')) } signal.addEventListener('abort', onAbort, { once: true }) @@ -163,7 +163,7 @@ export class DefaultUpgrader implements Upgrader { try { if ((await this.components.connectionGater.denyInboundConnection?.(maConn)) === true) { - throw new CodeError('The multiaddr connection is blocked by gater.acceptConnection', codes.ERR_CONNECTION_INTERCEPTED) + throw new ConnectionInterceptedError('The multiaddr connection is blocked by gater.acceptConnection') } this.components.metrics?.trackMultiaddrConnection(maConn) @@ -204,7 +204,7 @@ export class DefaultUpgrader implements Upgrader { const idStr = maConn.remoteAddr.getPeerId() if (idStr == null) { - throw new CodeError('inbound connection that skipped encryption must have a peer id', codes.ERR_INVALID_MULTIADDR) + throw new InvalidMultiaddrError('inbound connection that skipped encryption must have a peer id') } const remotePeerId = peerIdFromString(idStr) @@ -306,7 +306,7 @@ export class DefaultUpgrader implements Upgrader { await this.shouldBlockConnection(remotePeer, maConn, 'denyOutboundEncryptedConnection') } else { if (remotePeerId == null) { - throw new CodeError('Encryption was skipped but no peer id was passed', codes.ERR_INVALID_PEER) + throw new InvalidPeerIdError('Encryption was skipped but no peer id was passed') } cryptoProtocol = 'native' @@ -392,7 +392,7 @@ export class DefaultUpgrader implements Upgrader { const streamCount = countStreams(protocol, 'inbound', connection) if (streamCount === incomingLimit) { - const err = new CodeError(`Too many inbound protocol streams for protocol "${protocol}" - limit ${incomingLimit}`, codes.ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS) + const err = new TooManyInboundProtocolStreamsError(`Too many inbound protocol streams for protocol "${protocol}" - limit ${incomingLimit}`) muxedStream.abort(err) throw err @@ -441,7 +441,7 @@ export class DefaultUpgrader implements Upgrader { newStream = async (protocols: string[], options: NewStreamOptions = {}): Promise => { if (muxer == null) { - throw new CodeError('Stream is not multiplexed', codes.ERR_MUXER_UNAVAILABLE) + throw new MuxerUnavailableError('Connection is not multiplexed') } connection.log('starting new stream for protocols %s', protocols) @@ -478,7 +478,7 @@ export class DefaultUpgrader implements Upgrader { const streamCount = countStreams(protocol, 'outbound', connection) if (streamCount >= outgoingLimit) { - const err = new CodeError(`Too many outbound protocol streams for protocol "${protocol}" - ${streamCount}/${outgoingLimit}`, codes.ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS) + const err = new TooManyOutboundProtocolStreamsError(`Too many outbound protocol streams for protocol "${protocol}" - ${streamCount}/${outgoingLimit}`) muxedStream.abort(err) throw err @@ -521,11 +521,7 @@ export class DefaultUpgrader implements Upgrader { muxedStream.abort(err) } - if (err.code != null) { - throw err - } - - throw new CodeError(String(err), codes.ERR_UNSUPPORTED_PROTOCOL) + throw err } } @@ -566,7 +562,7 @@ export class DefaultUpgrader implements Upgrader { maConn.timeline.upgraded = Date.now() const errConnectionNotMultiplexed = (): any => { - throw new CodeError('connection is not multiplexed', codes.ERR_CONNECTION_NOT_MULTIPLEXED) + throw new MuxerUnavailableError('Connection is not multiplexed') } // Create the connection @@ -618,7 +614,7 @@ export class DefaultUpgrader implements Upgrader { const { handler, options } = this.components.registrar.getHandler(protocol) if (connection.limits != null && options.runOnLimitedConnection !== true) { - throw new CodeError('Cannot open protocol stream on limited connection', 'ERR_LIMITED_CONNECTION') + throw new LimitedConnectionError('Cannot open protocol stream on limited connection') } handler({ connection, stream }) @@ -649,7 +645,7 @@ export class DefaultUpgrader implements Upgrader { } } catch (err: any) { connection.log.error('encrypting inbound connection failed', err) - throw new CodeError(err.message, codes.ERR_ENCRYPTION_FAILED) + throw new EncryptionFailedError(err.message) } } @@ -686,7 +682,7 @@ export class DefaultUpgrader implements Upgrader { } } catch (err: any) { connection.log.error('encrypting outbound connection to %p failed', remotePeerId, err) - throw new CodeError(err.message, codes.ERR_ENCRYPTION_FAILED) + throw new EncryptionFailedError(err.message) } } @@ -714,7 +710,7 @@ export class DefaultUpgrader implements Upgrader { return { stream, muxerFactory } } catch (err: any) { connection.log.error('error multiplexing outbound connection', err) - throw new CodeError(String(err), codes.ERR_MUXER_UNAVAILABLE) + throw new MuxerUnavailableError(String(err)) } } @@ -734,7 +730,7 @@ export class DefaultUpgrader implements Upgrader { return { stream, muxerFactory } } catch (err: any) { connection.log.error('error multiplexing inbound connection', err) - throw new CodeError(String(err), codes.ERR_MUXER_UNAVAILABLE) + throw new MuxerUnavailableError(String(err)) } } } diff --git a/packages/libp2p/test/connection-manager/dial-queue.spec.ts b/packages/libp2p/test/connection-manager/dial-queue.spec.ts index ca52e9c67f..fac9afb6b7 100644 --- a/packages/libp2p/test/connection-manager/dial-queue.spec.ts +++ b/packages/libp2p/test/connection-manager/dial-queue.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { CodeError } from '@libp2p/interface' +import { NotFoundError } from '@libp2p/interface' import { matchMultiaddr } from '@libp2p/interface-compliance-tests/matchers' import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-compliance-tests/mocks' import { peerLogger } from '@libp2p/logger' @@ -89,7 +89,7 @@ describe('dial queue', () => { const connection = mockConnection(mockMultiaddrConnection(mockDuplex(), await createEd25519PeerId())) const ma = multiaddr('/ip4/127.0.0.1/tcp/4001') - components.peerStore.get.withArgs(peerId).rejects(new CodeError('Not found', 'ERR_NOT_FOUND')) + components.peerStore.get.withArgs(peerId).rejects(new NotFoundError('Not found')) components.peerRouting.findPeer.withArgs(peerId).resolves({ id: peerId, multiaddrs: [ @@ -198,7 +198,7 @@ describe('dial queue', () => { await dialer.dial(Object.keys(actions).map(str => multiaddr(str))) expect.fail('Should have thrown') } catch (err: any) { - expect(err).to.have.property('name', 'AggregateCodeError') + expect(err).to.have.property('name', 'AggregateError') } expect(actions['/ip4/127.0.0.1/tcp/1231']).to.have.property('callCount', 1) @@ -234,7 +234,7 @@ describe('dial queue', () => { await dialer.dial(Object.keys(actions).map(str => multiaddr(str))) expect.fail('Should have thrown') } catch (err: any) { - expect(err).to.have.property('name', 'AggregateCodeError') + expect(err).to.have.property('name', 'AggregateError') } expect(reject).to.have.property('callCount', addrs.length) diff --git a/packages/libp2p/test/connection-manager/direct.node.ts b/packages/libp2p/test/connection-manager/direct.node.ts index 271f85a594..653d6ba174 100644 --- a/packages/libp2p/test/connection-manager/direct.node.ts +++ b/packages/libp2p/test/connection-manager/direct.node.ts @@ -5,7 +5,7 @@ import os from 'node:os' import path from 'node:path' import { yamux } from '@chainsafe/libp2p-yamux' import { type Connection, type ConnectionProtector, isConnection, type PeerId, type Stream, type Libp2p } from '@libp2p/interface' -import { AbortError, ERR_TIMEOUT, TypedEventEmitter, start, stop } from '@libp2p/interface' +import { AbortError, TypedEventEmitter, start, stop } from '@libp2p/interface' import { mockConnection, mockConnectionGater, mockDuplex, mockMultiaddrConnection, mockUpgrader } from '@libp2p/interface-compliance-tests/mocks' import { defaultLogger } from '@libp2p/logger' import { mplex } from '@libp2p/mplex' @@ -29,7 +29,6 @@ import { DefaultAddressManager } from '../../src/address-manager/index.js' import { defaultComponents, type Components } from '../../src/components.js' import { DialQueue } from '../../src/connection-manager/dial-queue.js' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' -import { codes as ErrorCodes } from '../../src/errors.js' import { createLibp2p } from '../../src/index.js' import { createLibp2pNode } from '../../src/libp2p.js' import { DefaultPeerRouting } from '../../src/peer-routing.js' @@ -152,7 +151,7 @@ describe('dialing (direct, TCP)', () => { await expect(dialer.dial(unsupportedAddr)) .to.eventually.be.rejectedWith(Error) - .and.to.have.nested.property('.code', ErrorCodes.ERR_NO_VALID_ADDRESSES) + .and.to.have.nested.property('.name', 'NoValidAddressesError') }) it('should fail to connect if peer has no known addresses', async () => { @@ -161,7 +160,7 @@ describe('dialing (direct, TCP)', () => { await expect(dialer.dial(peerId)) .to.eventually.be.rejectedWith(Error) - .and.to.have.nested.property('.code', ErrorCodes.ERR_NO_VALID_ADDRESSES) + .and.to.have.nested.property('.name', 'NoValidAddressesError') }) it('should be able to connect to a given peer id', async () => { @@ -185,7 +184,7 @@ describe('dialing (direct, TCP)', () => { await expect(dialer.dial(remoteComponents.peerId)) .to.eventually.be.rejectedWith(Error) - .and.to.have.nested.property('.code', ErrorCodes.ERR_NO_VALID_ADDRESSES) + .and.to.have.nested.property('.name', 'NoValidAddressesError') }) it('should only try to connect to addresses supported by the transports configured', async () => { @@ -222,7 +221,7 @@ describe('dialing (direct, TCP)', () => { await expect(dialer.dial(remoteAddr)) .to.eventually.be.rejectedWith(Error) - .and.to.have.property('code', ERR_TIMEOUT) + .and.to.have.property('name', 'TimeoutError') }) it('should only dial to the max concurrency', async () => { @@ -441,11 +440,11 @@ describe('libp2p.dialer (direct, TCP)', () => { // @ts-expect-error invalid params await expect(libp2p.dialProtocol(remoteAddr)) .to.eventually.be.rejectedWith(Error) - .and.to.have.property('code', ErrorCodes.ERR_INVALID_PROTOCOLS_FOR_STREAM) + .and.to.have.property('name', 'InvalidParametersError') await expect(libp2p.dialProtocol(remoteAddr, [])) .to.eventually.be.rejectedWith(Error) - .and.to.have.property('code', ErrorCodes.ERR_INVALID_PROTOCOLS_FOR_STREAM) + .and.to.have.property('name', 'InvalidParametersError') }) it('should be able to use hangup to close connections', async () => { diff --git a/packages/libp2p/test/connection-manager/direct.spec.ts b/packages/libp2p/test/connection-manager/direct.spec.ts index ef2f3941f4..f1a86da40a 100644 --- a/packages/libp2p/test/connection-manager/direct.spec.ts +++ b/packages/libp2p/test/connection-manager/direct.spec.ts @@ -2,7 +2,7 @@ import { yamux } from '@chainsafe/libp2p-yamux' import { type Identify, identify } from '@libp2p/identify' -import { AbortError, ERR_TIMEOUT, TypedEventEmitter } from '@libp2p/interface' +import { AbortError, TypedEventEmitter } from '@libp2p/interface' import { mockConnectionGater, mockDuplex, mockMultiaddrConnection, mockUpgrader, mockConnection } from '@libp2p/interface-compliance-tests/mocks' import { defaultLogger } from '@libp2p/logger' import { mplex } from '@libp2p/mplex' @@ -24,7 +24,6 @@ import { stubInterface } from 'sinon-ts' import { defaultComponents, type Components } from '../../src/components.js' import { LAST_DIAL_FAILURE_KEY } from '../../src/connection-manager/constants.js' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' -import { codes as ErrorCodes } from '../../src/errors.js' import { createLibp2p } from '../../src/index.js' import { DefaultTransportManager } from '../../src/transport-manager.js' import type { Libp2p, Connection, PeerId, Transport } from '@libp2p/interface' @@ -101,7 +100,7 @@ describe('dialing (direct, WebSockets)', () => { await expect(connectionManager.openConnection(unsupportedAddr.encapsulate(`/p2p/${remoteComponents.peerId.toString()}`))) .to.eventually.be.rejectedWith(Error) - .and.to.have.nested.property('.code', ErrorCodes.ERR_NO_VALID_ADDRESSES) + .and.to.have.nested.property('.name', 'NoValidAddressesError') }) it('should mark a peer as having recently failed to connect', async () => { @@ -141,7 +140,7 @@ describe('dialing (direct, WebSockets)', () => { await expect(connectionManager.openConnection(remotePeerId)) .to.eventually.be.rejectedWith(Error) - .and.to.have.nested.property('.code', ErrorCodes.ERR_NO_VALID_ADDRESSES) + .and.to.have.nested.property('.name', 'NoValidAddressesError') }) it('should abort dials on queue task timeout', async () => { @@ -166,7 +165,7 @@ describe('dialing (direct, WebSockets)', () => { await expect(connectionManager.openConnection(remoteAddr)) .to.eventually.be.rejected() - .and.to.have.property('code', ERR_TIMEOUT) + .and.to.have.property('name', 'TimeoutError') }) it('should throw when a peer advertises more than the allowed number of addresses', async () => { @@ -182,7 +181,7 @@ describe('dialing (direct, WebSockets)', () => { await expect(connectionManager.openConnection(remotePeerId)) .to.eventually.be.rejected() - .and.to.have.property('code', ErrorCodes.ERR_TOO_MANY_ADDRESSES) + .and.to.have.property('name', 'DialError') }) it('should sort addresses on dial', async () => { @@ -291,7 +290,7 @@ describe('dialing (direct, WebSockets)', () => { // Perform dial await expect(connectionManager.openConnection([])).to.eventually.rejected - .with.property('code', 'ERR_NO_VALID_ADDRESSES') + .with.property('name', 'NoValidAddressesError') }) it('should throw if dialling multiaddrs with mismatched peer ids', async () => { @@ -303,7 +302,7 @@ describe('dialing (direct, WebSockets)', () => { multiaddr(`/ip4/0.0.0.0/tcp/8000/ws/p2p/${(await createEd25519PeerId()).toString()}`), multiaddr(`/ip4/0.0.0.0/tcp/8001/ws/p2p/${(await createEd25519PeerId()).toString()}`) ])).to.eventually.rejected - .with.property('code', 'ERR_INVALID_PARAMETERS') + .with.property('name', 'InvalidParametersError') }) it('should throw if dialling multiaddrs with inconsistent peer ids', async () => { @@ -315,14 +314,14 @@ describe('dialing (direct, WebSockets)', () => { multiaddr(`/ip4/0.0.0.0/tcp/8000/ws/p2p/${(await createEd25519PeerId()).toString()}`), multiaddr('/ip4/0.0.0.0/tcp/8001/ws') ])).to.eventually.rejected - .with.property('code', 'ERR_INVALID_PARAMETERS') + .with.property('name', 'InvalidParametersError') // Perform dial await expect(connectionManager.openConnection([ multiaddr('/ip4/0.0.0.0/tcp/8001/ws'), multiaddr(`/ip4/0.0.0.0/tcp/8000/ws/p2p/${(await createEd25519PeerId()).toString()}`) ])).to.eventually.rejected - .with.property('code', 'ERR_INVALID_PARAMETERS') + .with.property('name', 'InvalidParametersError') }) }) @@ -502,7 +501,7 @@ describe('libp2p.dialer (direct, WebSockets)', () => { await expect(libp2p.dial(multiaddr(`/ip4/127.0.0.1/tcp/1234/ws/p2p/${peerId.toString()}`))) .to.eventually.be.rejected() - .and.to.have.property('code', ErrorCodes.ERR_DIALED_SELF) + .and.to.have.property('name', 'DialError') }) it('should limit the maximum dial queue size', async () => { @@ -529,6 +528,6 @@ describe('libp2p.dialer (direct, WebSockets)', () => { libp2p.dial(multiaddr('/ip4/127.0.0.1/tcp/1234')), libp2p.dial(multiaddr('/ip4/127.0.0.1/tcp/1235')) ])).to.eventually.be.rejected - .with.property('code', 'ERR_DIAL_QUEUE_FULL') + .with.property('name', 'DialError') }) }) diff --git a/packages/libp2p/test/connection-manager/index.node.ts b/packages/libp2p/test/connection-manager/index.node.ts index cebad403d3..473279bfab 100644 --- a/packages/libp2p/test/connection-manager/index.node.ts +++ b/packages/libp2p/test/connection-manager/index.node.ts @@ -14,7 +14,6 @@ import sinon from 'sinon' import { stubInterface } from 'sinon-ts' import { defaultComponents } from '../../src/components.js' import { DefaultConnectionManager } from '../../src/connection-manager/index.js' -import { codes } from '../../src/errors.js' import { createBaseOptions } from '../fixtures/base-options.browser.js' import { createNode } from '../fixtures/creators/peer.js' import { ECHO_PROTOCOL, echo } from '../fixtures/echo-service.js' @@ -465,7 +464,7 @@ describe('libp2p.connections', () => { }) await expect(libp2p.dial(remoteLibp2p.peerId)) - .to.eventually.be.rejected().with.property('code', codes.ERR_PEER_DIAL_INTERCEPTED) + .to.eventually.be.rejected().with.property('name', 'DialDeniedError') }) it('intercept addr dial', async () => { diff --git a/packages/libp2p/test/content-routing/content-routing.spec.ts b/packages/libp2p/test/content-routing/content-routing.spec.ts index 00cfbca992..9d5a3b6442 100644 --- a/packages/libp2p/test/content-routing/content-routing.spec.ts +++ b/packages/libp2p/test/content-routing/content-routing.spec.ts @@ -32,15 +32,15 @@ describe('content-routing', () => { throw new Error('.findProviders should return an error') } catch (err: any) { expect(err).to.exist() - expect(err.code).to.equal('ERR_NO_ROUTERS_AVAILABLE') + expect(err.name).to.equal('NoContentRoutersError') } }) it('.provide should return an error', async () => { // @ts-expect-error invalid params await expect(node.contentRouting.provide('a cid')) - .to.eventually.be.rejected() - .and.to.have.property('code', 'ERR_NO_ROUTERS_AVAILABLE') + .to.eventually.be.rejected + .with.property('name', 'NoContentRoutersError') }) }) diff --git a/packages/libp2p/test/core/random-walk.spec.ts b/packages/libp2p/test/core/random-walk.spec.ts index 2305c001f0..fee9e6e92e 100644 --- a/packages/libp2p/test/core/random-walk.spec.ts +++ b/packages/libp2p/test/core/random-walk.spec.ts @@ -227,7 +227,7 @@ describe('random-walk', () => { await expect(drain(randomwalk.walk({ signal: AbortSignal.timeout(10) }))).to.eventually.be.rejected - .with.property('code', 'ABORT_ERR') + .with.property('name', 'AbortError') }) it('should allow an impatient consumer to abort a slow query but other consumers to receive values', async () => { @@ -250,7 +250,7 @@ describe('random-walk', () => { ]) expect(results).to.have.nested.property('[0].status', 'rejected') - expect(results).to.have.nested.property('[0].reason.code', 'ABORT_ERR') + expect(results).to.have.nested.property('[0].reason.name', 'AbortError') expect(results).to.have.nested.property('[1].status', 'fulfilled') expect(results).to.have.nested.property('[1].value').with.lengthOf(2) diff --git a/packages/libp2p/test/core/service-dependencies.spec.ts b/packages/libp2p/test/core/service-dependencies.spec.ts index bf11769599..ddc6cbdf23 100644 --- a/packages/libp2p/test/core/service-dependencies.spec.ts +++ b/packages/libp2p/test/core/service-dependencies.spec.ts @@ -53,7 +53,7 @@ describe('service dependencies', () => { b: serviceB() } })).to.eventually.be.rejected - .with.property('code', 'ERR_UNMET_SERVICE_DEPENDENCIES') + .with.property('name', 'UnmetServiceDependenciesError') }) it('should not error when service dependencies are met', async () => { diff --git a/packages/libp2p/test/peer-routing/peer-routing.spec.ts b/packages/libp2p/test/peer-routing/peer-routing.spec.ts index 134a873252..8cf7193bcf 100644 --- a/packages/libp2p/test/peer-routing/peer-routing.spec.ts +++ b/packages/libp2p/test/peer-routing/peer-routing.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { peerRoutingSymbol, CodeError } from '@libp2p/interface' +import { peerRoutingSymbol, NotFoundError } from '@libp2p/interface' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { multiaddr } from '@multiformats/multiaddr' import { expect } from 'aegir/chai' @@ -34,7 +34,7 @@ describe('peer-routing', () => { it('.findPeer should return an error', async () => { await expect(node.peerRouting.findPeer(peerId)) .to.eventually.be.rejected() - .and.to.have.property('code', 'ERR_NO_ROUTERS_AVAILABLE') + .and.to.have.property('name', 'NoPeerRoutersError') }) it('.getClosestPeers should return an error', async () => { @@ -43,7 +43,7 @@ describe('peer-routing', () => { throw new Error('.getClosestPeers should return an error') } catch (err: any) { expect(err).to.exist() - expect(err.code).to.equal('ERR_NO_ROUTERS_AVAILABLE') + expect(err.name).to.equal('NoPeerRoutersError') } }) }) @@ -104,7 +104,7 @@ describe('peer-routing', () => { it('should error when peer tries to find itself', async () => { await expect(node.peerRouting.findPeer(node.peerId)) .to.eventually.be.rejected() - .and.to.have.property('code', 'ERR_FIND_SELF') + .and.to.have.property('name', 'QueriedForSelfError') }) it('should handle error thrown synchronously during find peer', async () => { @@ -116,7 +116,7 @@ describe('peer-routing', () => { await expect(node.peerRouting.findPeer(unknownPeer)) .to.eventually.be.rejected() - .and.to.have.property('code', 'ERR_NOT_FOUND') + .and.to.have.property('name', 'NotFoundError') }) it('should handle error thrown asynchronously during find peer', async () => { @@ -128,7 +128,7 @@ describe('peer-routing', () => { await expect(node.peerRouting.findPeer(unknownPeer)) .to.eventually.be.rejected() - .and.to.have.property('code', 'ERR_NOT_FOUND') + .and.to.have.property('name', 'NotFoundError') }) it('should handle error thrown asynchronously after delay during find peer', async () => { @@ -141,7 +141,7 @@ describe('peer-routing', () => { await expect(node.peerRouting.findPeer(unknownPeer)) .to.eventually.be.rejected() - .and.to.have.property('code', 'ERR_NOT_FOUND') + .and.to.have.property('name', 'NotFoundError') }) }) @@ -200,7 +200,7 @@ describe('peer-routing', () => { it('should error when peer tries to find itself', async () => { await expect(node.peerRouting.findPeer(node.peerId)) .to.eventually.be.rejected() - .and.to.have.property('code', 'ERR_FIND_SELF') + .and.to.have.property('name', 'QueriedForSelfError') }) it('should handle errors from the delegate when finding closest peers', async () => { @@ -252,7 +252,7 @@ describe('peer-routing', () => { router.findPeer.callsFake(async function () { await delay(100) - throw new CodeError('Not found', 'ERR_NOT_FOUND') + throw new NotFoundError('Not found') }) delegate.findPeer.callsFake(async () => { return results @@ -275,7 +275,7 @@ describe('peer-routing', () => { router.findPeer.callsFake(async function () { await defer.promise - throw new CodeError('Not found', 'ERR_NOT_FOUND') + throw new NotFoundError('Not found') }) delegate.findPeer.callsFake(async () => { return results @@ -303,7 +303,7 @@ describe('peer-routing', () => { }) delegate.findPeer.callsFake(async () => { await defer.promise - throw new CodeError('Not found', 'ERR_NOT_FOUND') + throw new NotFoundError('Not found') }) const peer = await node.peerRouting.findPeer(remotePeerId) diff --git a/packages/libp2p/test/transports/transport-manager.spec.ts b/packages/libp2p/test/transports/transport-manager.spec.ts index 3fdb0d2892..ac6679bc70 100644 --- a/packages/libp2p/test/transports/transport-manager.spec.ts +++ b/packages/libp2p/test/transports/transport-manager.spec.ts @@ -11,7 +11,6 @@ import { multiaddr } from '@multiformats/multiaddr' import { expect } from 'aegir/chai' import sinon from 'sinon' import { DefaultAddressManager } from '../../src/address-manager/index.js' -import { codes as ErrorCodes } from '../../src/errors.js' import { createLibp2p } from '../../src/index.js' import { DefaultTransportManager } from '../../src/transport-manager.js' import type { Components } from '../../src/components.js' @@ -71,7 +70,7 @@ describe('Transport Manager (WebSockets)', () => { })) }) .to.throw() - .and.to.have.property('code', ErrorCodes.ERR_DUPLICATE_TRANSPORT) + .and.to.have.property('name', 'InvalidParametersError') }) it('should be able to dial', async () => { @@ -91,7 +90,7 @@ describe('Transport Manager (WebSockets)', () => { const addr = multiaddr('/ip4/127.0.0.1/tcp/0') await expect(tm.dial(addr)) .to.eventually.be.rejected() - .and.to.have.property('code', ErrorCodes.ERR_TRANSPORT_UNAVAILABLE) + .and.to.have.property('name', 'TransportUnavailableError') }) it('should fail to listen with no valid address', async () => { @@ -102,7 +101,7 @@ describe('Transport Manager (WebSockets)', () => { await expect(start(tm)) .to.eventually.be.rejected() - .and.to.have.property('code', ErrorCodes.ERR_NO_VALID_ADDRESSES) + .and.to.have.property('name', 'NoValidAddressesError') await stop(tm) }) @@ -136,7 +135,7 @@ describe('libp2p.transportManager (dial only)', () => { }) await expect(libp2p.start()).to.eventually.be.rejected - .with.property('code', ErrorCodes.ERR_NO_VALID_ADDRESSES) + .with.property('name', 'NoValidAddressesError') }) it('does not fail to start if provided listen multiaddr are not compatible to configured transports (when supporting dial only mode)', async () => { diff --git a/packages/libp2p/test/upgrading/upgrader.spec.ts b/packages/libp2p/test/upgrading/upgrader.spec.ts index 47bbee2b0c..8e71d835bb 100644 --- a/packages/libp2p/test/upgrading/upgrader.spec.ts +++ b/packages/libp2p/test/upgrading/upgrader.spec.ts @@ -25,7 +25,6 @@ import { type StubbedInstance, stubInterface } from 'sinon-ts' import { Uint8ArrayList } from 'uint8arraylist' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { type Components, defaultComponents } from '../../src/components.js' -import { codes } from '../../src/errors.js' import { createLibp2p } from '../../src/index.js' import { DEFAULT_MAX_OUTBOUND_STREAMS } from '../../src/registrar.js' import { DefaultUpgrader } from '../../src/upgrader.js' @@ -272,7 +271,7 @@ describe('Upgrader', () => { expect(results).to.have.length(2) results.forEach(result => { expect(result).to.have.property('status', 'rejected') - expect(result).to.have.nested.property('reason.code', codes.ERR_ENCRYPTION_FAILED) + expect(result).to.have.nested.property('reason.name', 'EncryptionFailedError') }) }) @@ -380,7 +379,7 @@ describe('Upgrader', () => { expect(results).to.have.length(2) results.forEach(result => { expect(result).to.have.property('status', 'rejected') - expect(result).to.have.nested.property('reason.code', codes.ERR_MUXER_UNAVAILABLE) + expect(result).to.have.nested.property('reason.name', 'MuxerUnavailableError') }) }) @@ -469,7 +468,7 @@ describe('Upgrader', () => { expect(results).to.have.length(2) results.forEach(result => { expect(result).to.have.property('status', 'rejected') - expect(result).to.have.nested.property('reason.code', codes.ERR_UNSUPPORTED_PROTOCOL) + expect(result).to.have.nested.property('reason.name', 'UnsupportedProtocolError') }) }) @@ -504,7 +503,7 @@ describe('Upgrader', () => { await expect(connections[0].newStream(['/echo/1.0.0', '/echo/1.0.1'], { signal })) - .to.eventually.be.rejected.with.property('code', 'ABORT_ERR') + .to.eventually.be.rejected.with.property('name', 'AbortError') }) it('should close streams when protocol negotiation fails', async () => { @@ -521,7 +520,7 @@ describe('Upgrader', () => { expect(connections[1].streams).to.have.lengthOf(0) await expect(connections[0].newStream(['/echo/1.0.0', '/echo/1.0.1'])) - .to.eventually.be.rejected.with.property('code', 'ERR_UNSUPPORTED_PROTOCOL') + .to.eventually.be.rejected.with.property('name', 'UnsupportedProtocolError') // wait for remote to close await delay(100) @@ -871,7 +870,7 @@ describe('libp2p.upgrader', () => { const s = await localToRemote.newStream(protocol) await expect(drain(s.source)).to.eventually.be.rejected() - .with.property('code', 'ERR_STREAM_RESET') + .with.property('name', 'StreamResetError') }) it('should limit the number of outgoing streams that can be opened using a protocol', async () => { @@ -947,7 +946,7 @@ describe('libp2p.upgrader', () => { expect(streamCount).to.equal(1) await expect(localToRemote.newStream(protocol)).to.eventually.be.rejected() - .with.property('code', codes.ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS) + .with.property('name', 'TooManyOutboundProtocolStreamsError') }) it('should allow overriding the number of outgoing streams that can be opened using a protocol without a handler', async () => { @@ -1025,12 +1024,12 @@ describe('libp2p.upgrader', () => { // should reject without overriding limit await expect(localToRemote.newStream(protocol)).to.eventually.be.rejected() - .with.property('code', codes.ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS) + .with.property('name', 'TooManyOutboundProtocolStreamsError') // should reject even with overriding limit await expect(localToRemote.newStream(protocol, { maxOutboundStreams: limit })).to.eventually.be.rejected() - .with.property('code', codes.ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS) + .with.property('name', 'TooManyOutboundProtocolStreamsError') }) }) diff --git a/packages/logger/package.json b/packages/logger/package.json index 00c439a64c..a1e6441d03 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -56,7 +56,7 @@ "dependencies": { "@libp2p/interface": "^1.7.0", "@multiformats/multiaddr": "^12.2.3", - "interface-datastore": "^8.2.11", + "interface-datastore": "^8.3.0", "multiformats": "^13.1.0", "weald": "^1.0.2" }, diff --git a/packages/multistream-select/src/multistream.ts b/packages/multistream-select/src/multistream.ts index 824b01a991..28d786b5fd 100644 --- a/packages/multistream-select/src/multistream.ts +++ b/packages/multistream-select/src/multistream.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidMessageError } from '@libp2p/interface' import { type Uint8ArrayList } from 'uint8arraylist' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' @@ -30,7 +30,7 @@ export async function read (reader: LengthPrefixedStream (stream: Stream, prot } } - throw new CodeError('protocol selection failed', 'ERR_UNSUPPORTED_PROTOCOL') + throw new UnsupportedProtocolError('protocol selection failed') } /** @@ -273,7 +273,7 @@ function optimisticSelect (stream: Stream, protoco options.log.trace('optimistic: read protocol "%s", expecting "%s"', response, protocol) if (response !== protocol) { - throw new CodeError('protocol selection failed', 'ERR_UNSUPPORTED_PROTOCOL') + throw new UnsupportedProtocolError('protocol selection failed') } } finally { readProtocol = true diff --git a/packages/multistream-select/test/dialer.spec.ts b/packages/multistream-select/test/dialer.spec.ts index dce48e7bee..b0a86807b3 100644 --- a/packages/multistream-select/test/dialer.spec.ts +++ b/packages/multistream-select/test/dialer.spec.ts @@ -123,7 +123,7 @@ describe('Dialer', () => { await expect(mss.select(outgoingStream, protocol, { log: logger('mss:test-outgoing') })).to.eventually.be.rejected - .with.property('code', 'ERR_UNSUPPORTED_PROTOCOL') + .with.property('name', 'UnsupportedProtocolError') }) }) diff --git a/packages/multistream-select/test/integration.spec.ts b/packages/multistream-select/test/integration.spec.ts index 90868cf395..008900a70c 100644 --- a/packages/multistream-select/test/integration.spec.ts +++ b/packages/multistream-select/test/integration.spec.ts @@ -133,7 +133,7 @@ describe('Dialer and Listener integration', () => { // should fail when we interact with the stream const input = [randomBytes(10), randomBytes(64), randomBytes(3)] await expect(pipe(input, dialerSelection.stream, async source => all(source))) - .to.eventually.be.rejected.with.property('code', 'ERR_UNSUPPORTED_PROTOCOL') + .to.eventually.be.rejected.with.property('name', 'UnsupportedProtocolError') }) it('should handle and optimistically select only by reading', async () => { @@ -216,7 +216,7 @@ describe('Dialer and Listener integration', () => { // should fail when we interact with the stream await expect(pipe(dialerSelection.stream, async source => all(source))) - .to.eventually.be.rejected.with.property('code', 'ERR_UNSUPPORTED_PROTOCOL') + .to.eventually.be.rejected.with.property('name', 'UnsupportedProtocolError') }) it('should abort an unhandled optimistically select', async () => { @@ -244,6 +244,6 @@ describe('Dialer and Listener integration', () => { // Dialer should fail to negotiate the single protocol await expect(dialerResultPromise).to.eventually.be.rejected() - .with.property('code', 'ERR_UNSUPPORTED_PROTOCOL') + .with.property('name', 'UnsupportedProtocolError') }) }) diff --git a/packages/multistream-select/test/multistream.spec.ts b/packages/multistream-select/test/multistream.spec.ts index 08e63a0b22..b4c0efb240 100644 --- a/packages/multistream-select/test/multistream.spec.ts +++ b/packages/multistream-select/test/multistream.spec.ts @@ -53,7 +53,7 @@ describe('Multistream', () => { await expect(Multistream.read(outputStream, { log: logger('mss:test') })).to.eventually.be.rejected() - .with.property('code', 'ERR_INVALID_MULTISTREAM_SELECT_MESSAGE') + .with.property('name', 'InvalidMessageError') }) it('should throw for a large message', async () => { @@ -71,7 +71,7 @@ describe('Multistream', () => { await expect(Multistream.read(outputStream, { log: logger('mss:test') })).to.eventually.be.rejected() - .with.property('code', 'ERR_MSG_DATA_TOO_LONG') + .with.property('name', 'InvalidDataLengthError') }) it('should throw for a 0-length message', async () => { @@ -86,7 +86,7 @@ describe('Multistream', () => { await expect(Multistream.read(outputStream, { log: logger('mss:test') })).to.eventually.be.rejected() - .with.property('code', 'ERR_INVALID_MULTISTREAM_SELECT_MESSAGE') + .with.property('name', 'InvalidMessageError') }) it('should be abortable', async () => { diff --git a/packages/peer-id-factory/src/proto.ts b/packages/peer-id-factory/src/proto.ts index bcd0a25d21..a0faf35a4c 100644 --- a/packages/peer-id-factory/src/proto.ts +++ b/packages/peer-id-factory/src/proto.ts @@ -4,8 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { encodeMessage, decodeMessage, message } from 'protons-runtime' -import type { Codec } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export interface PeerIdProto { @@ -42,7 +41,7 @@ export namespace PeerIdProto { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -51,18 +50,22 @@ export namespace PeerIdProto { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.id = reader.bytes() break - case 2: + } + case 2: { obj.pubKey = reader.bytes() break - case 3: + } + case 3: { obj.privKey = reader.bytes() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -77,7 +80,7 @@ export namespace PeerIdProto { return encodeMessage(obj, PeerIdProto.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): PeerIdProto => { - return decodeMessage(buf, PeerIdProto.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): PeerIdProto => { + return decodeMessage(buf, PeerIdProto.codec(), opts) } } diff --git a/packages/peer-id/src/index.ts b/packages/peer-id/src/index.ts index 07424dc68a..8e73c44e20 100644 --- a/packages/peer-id/src/index.ts +++ b/packages/peer-id/src/index.ts @@ -14,8 +14,7 @@ * ``` */ -import { CodeError } from '@libp2p/interface' -import { type Ed25519PeerId, type PeerIdType, type RSAPeerId, type URLPeerId, type Secp256k1PeerId, peerIdSymbol, type PeerId } from '@libp2p/interface' +import { InvalidParametersError, peerIdSymbol } from '@libp2p/interface' import { base58btc } from 'multiformats/bases/base58' import { bases } from 'multiformats/basics' import { CID } from 'multiformats/cid' @@ -25,6 +24,7 @@ import { sha256 } from 'multiformats/hashes/sha2' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' +import type { Ed25519PeerId, PeerIdType, RSAPeerId, URLPeerId, Secp256k1PeerId, PeerId } from '@libp2p/interface' import type { MultibaseDecoder } from 'multiformats/bases/interface' import type { MultihashDigest } from 'multiformats/hashes/interface' @@ -242,7 +242,7 @@ export function createPeerId (init: PeerIdInit): Ed25519PeerId | Secp256k1PeerId return new Secp256k1PeerIdImpl(init) } - throw new CodeError('Type must be "RSA", "Ed25519" or "secp256k1"', 'ERR_INVALID_PARAMETERS') + throw new InvalidParametersError('Type must be "RSA", "Ed25519" or "secp256k1"') } export function peerIdFromPeerId (other: any): Ed25519PeerId | Secp256k1PeerId | RSAPeerId { @@ -258,7 +258,7 @@ export function peerIdFromPeerId (other: any): Ed25519PeerId | Secp256k1PeerId | return new Secp256k1PeerIdImpl(other) } - throw new CodeError('Not a PeerId', 'ERR_INVALID_PARAMETERS') + throw new InvalidParametersError('Not a PeerId') } export function peerIdFromString (str: string, decoder?: MultibaseDecoder): Ed25519PeerId | Secp256k1PeerId | RSAPeerId | URLPeerId { diff --git a/packages/peer-record/src/envelope/envelope.ts b/packages/peer-record/src/envelope/envelope.ts index 5a5d6e640a..b60abde3fc 100644 --- a/packages/peer-record/src/envelope/envelope.ts +++ b/packages/peer-record/src/envelope/envelope.ts @@ -4,8 +4,8 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { encodeMessage, decodeMessage, message } from 'protons-runtime' -import type { Codec } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, message } from 'protons-runtime' +import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' import type { Uint8ArrayList } from 'uint8arraylist' export interface Envelope { @@ -48,12 +48,12 @@ export namespace Envelope { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { - publicKey: new Uint8Array(0), - payloadType: new Uint8Array(0), - payload: new Uint8Array(0), - signature: new Uint8Array(0) + publicKey: uint8ArrayAlloc(0), + payloadType: uint8ArrayAlloc(0), + payload: uint8ArrayAlloc(0), + signature: uint8ArrayAlloc(0) } const end = length == null ? reader.len : reader.pos + length @@ -62,21 +62,26 @@ export namespace Envelope { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.publicKey = reader.bytes() break - case 2: + } + case 2: { obj.payloadType = reader.bytes() break - case 3: + } + case 3: { obj.payload = reader.bytes() break - case 5: + } + case 5: { obj.signature = reader.bytes() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -91,7 +96,7 @@ export namespace Envelope { return encodeMessage(obj, Envelope.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Envelope => { - return decodeMessage(buf, Envelope.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Envelope => { + return decodeMessage(buf, Envelope.codec(), opts) } } diff --git a/packages/peer-record/src/envelope/errors.ts b/packages/peer-record/src/envelope/errors.ts new file mode 100644 index 0000000000..8442e107f1 --- /dev/null +++ b/packages/peer-record/src/envelope/errors.ts @@ -0,0 +1,9 @@ +/** + * The key in the record is not valid for the domain + */ +export class InvalidSignatureError extends Error { + constructor (message = 'Invalid signature') { + super(message) + this.name = 'InvalidSignatureError' + } +} diff --git a/packages/peer-record/src/envelope/index.ts b/packages/peer-record/src/envelope/index.ts index 8770af487c..4bc29e4ef0 100644 --- a/packages/peer-record/src/envelope/index.ts +++ b/packages/peer-record/src/envelope/index.ts @@ -1,12 +1,11 @@ import { unmarshalPrivateKey, unmarshalPublicKey } from '@libp2p/crypto/keys' -import { CodeError } from '@libp2p/interface' import { peerIdFromKeys } from '@libp2p/peer-id' import * as varint from 'uint8-varint' import { Uint8ArrayList } from 'uint8arraylist' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' import { fromString as uint8arraysFromString } from 'uint8arrays/from-string' -import { codes } from '../errors.js' import { Envelope as Protobuf } from './envelope.js' +import { InvalidSignatureError } from './errors.js' import type { PeerId, Record, Envelope } from '@libp2p/interface' export interface RecordEnvelopeInit { @@ -65,7 +64,7 @@ export class RecordEnvelope implements Envelope { const valid = await envelope.validate(domain) if (!valid) { - throw new CodeError('envelope signature is not valid for the given domain', codes.ERR_SIGNATURE_NOT_VALID) + throw new InvalidSignatureError('Envelope signature is not valid for the given domain') } return envelope diff --git a/packages/peer-record/src/errors.ts b/packages/peer-record/src/errors.ts deleted file mode 100644 index 84cee84f0e..0000000000 --- a/packages/peer-record/src/errors.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const codes = { - ERR_SIGNATURE_NOT_VALID: 'ERR_SIGNATURE_NOT_VALID' -} diff --git a/packages/peer-record/src/peer-record/peer-record.ts b/packages/peer-record/src/peer-record/peer-record.ts index 0e3e9814fa..f5b1f5a2ec 100644 --- a/packages/peer-record/src/peer-record/peer-record.ts +++ b/packages/peer-record/src/peer-record/peer-record.ts @@ -4,8 +4,8 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { encodeMessage, decodeMessage, message } from 'protons-runtime' -import type { Codec } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, MaxLengthError, message } from 'protons-runtime' +import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' import type { Uint8ArrayList } from 'uint8arraylist' export interface PeerRecord { @@ -37,9 +37,9 @@ export namespace PeerRecord { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { - multiaddr: new Uint8Array(0) + multiaddr: uint8ArrayAlloc(0) } const end = length == null ? reader.len : reader.pos + length @@ -48,12 +48,14 @@ export namespace PeerRecord { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.multiaddr = reader.bytes() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -68,8 +70,8 @@ export namespace PeerRecord { return encodeMessage(obj, AddressInfo.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): AddressInfo => { - return decodeMessage(buf, AddressInfo.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): AddressInfo => { + return decodeMessage(buf, AddressInfo.codec(), opts) } } @@ -102,9 +104,9 @@ export namespace PeerRecord { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { - peerId: new Uint8Array(0), + peerId: uint8ArrayAlloc(0), seq: 0n, addresses: [] } @@ -115,18 +117,28 @@ export namespace PeerRecord { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.peerId = reader.bytes() break - case 2: + } + case 2: { obj.seq = reader.uint64() break - case 3: - obj.addresses.push(PeerRecord.AddressInfo.codec().decode(reader, reader.uint32())) + } + case 3: { + if (opts.limits?.addresses != null && obj.addresses.length === opts.limits.addresses) { + throw new MaxLengthError('Decode error - map field "addresses" had too many elements') + } + + obj.addresses.push(PeerRecord.AddressInfo.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.addresses$ + })) break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -141,7 +153,7 @@ export namespace PeerRecord { return encodeMessage(obj, PeerRecord.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): PeerRecord => { - return decodeMessage(buf, PeerRecord.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): PeerRecord => { + return decodeMessage(buf, PeerRecord.codec(), opts) } } diff --git a/packages/peer-record/test/envelope.spec.ts b/packages/peer-record/test/envelope.spec.ts index 9da7b5295d..3b071faadc 100644 --- a/packages/peer-record/test/envelope.spec.ts +++ b/packages/peer-record/test/envelope.spec.ts @@ -3,7 +3,6 @@ import { expect } from 'aegir/chai' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' import { fromString as uint8arrayFromString } from 'uint8arrays/from-string' import { RecordEnvelope } from '../src/envelope/index.js' -import { codes as ErrorCodes } from '../src/errors.js' import type { PeerId, Record } from '@libp2p/interface' const domain = 'libp2p-testing' @@ -83,6 +82,6 @@ describe('Envelope', () => { await expect(RecordEnvelope.openAndCertify(rawEnvelope, '/bad-domain')) .to.eventually.be.rejected() - .and.to.have.property('code', ErrorCodes.ERR_SIGNATURE_NOT_VALID) + .and.to.have.property('name', 'InvalidSignatureError') }) }) diff --git a/packages/peer-store/package.json b/packages/peer-store/package.json index 711e1aad41..eb0692a297 100644 --- a/packages/peer-store/package.json +++ b/packages/peer-store/package.json @@ -64,7 +64,7 @@ "@libp2p/peer-id": "^4.2.4", "@libp2p/peer-record": "^7.0.25", "@multiformats/multiaddr": "^12.2.3", - "interface-datastore": "^8.2.11", + "interface-datastore": "^8.3.0", "it-all": "^3.0.6", "mortice": "^3.0.4", "multiformats": "^13.1.0", @@ -77,7 +77,7 @@ "@libp2p/peer-id-factory": "^4.2.4", "@types/sinon": "^17.0.3", "aegir": "^44.0.1", - "datastore-core": "^9.2.9", + "datastore-core": "^10.0.0", "delay": "^6.0.0", "p-defer": "^4.0.1", "p-event": "^6.0.1", diff --git a/packages/peer-store/src/errors.ts b/packages/peer-store/src/errors.ts deleted file mode 100644 index e4074784ca..0000000000 --- a/packages/peer-store/src/errors.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const codes = { - ERR_INVALID_PARAMETERS: 'ERR_INVALID_PARAMETERS' -} diff --git a/packages/peer-store/src/index.ts b/packages/peer-store/src/index.ts index 9e06a53548..73cccabab5 100644 --- a/packages/peer-store/src/index.ts +++ b/packages/peer-store/src/index.ts @@ -179,7 +179,7 @@ export class PersistentPeerStore implements PeerStore { try { peer = await this.get(envelope.peerId) } catch (err: any) { - if (err.code !== 'ERR_NOT_FOUND') { + if (err.name !== 'NotFoundError') { throw err } } diff --git a/packages/peer-store/src/pb/peer.ts b/packages/peer-store/src/pb/peer.ts index 9ceb63f5ee..c117de935d 100644 --- a/packages/peer-store/src/pb/peer.ts +++ b/packages/peer-store/src/pb/peer.ts @@ -4,8 +4,8 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { encodeMessage, decodeMessage, message } from 'protons-runtime' -import type { Codec } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, MaxLengthError, MaxSizeError, message } from 'protons-runtime' +import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' import type { Uint8ArrayList } from 'uint8arraylist' export interface Peer { @@ -46,10 +46,10 @@ export namespace Peer { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { key: '', - value: new Uint8Array(0) + value: uint8ArrayAlloc(0) } const end = length == null ? reader.len : reader.pos + length @@ -58,15 +58,18 @@ export namespace Peer { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.key = reader.string() break - case 2: + } + case 2: { obj.value = reader.bytes() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -81,8 +84,8 @@ export namespace Peer { return encodeMessage(obj, Peer$metadataEntry.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Peer$metadataEntry => { - return decodeMessage(buf, Peer$metadataEntry.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Peer$metadataEntry => { + return decodeMessage(buf, Peer$metadataEntry.codec(), opts) } } @@ -114,7 +117,7 @@ export namespace Peer { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { key: '' } @@ -125,15 +128,20 @@ export namespace Peer { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.key = reader.string() break - case 2: - obj.value = Tag.codec().decode(reader, reader.uint32()) + } + case 2: { + obj.value = Tag.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.value + }) break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -148,8 +156,8 @@ export namespace Peer { return encodeMessage(obj, Peer$tagsEntry.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Peer$tagsEntry => { - return decodeMessage(buf, Peer$tagsEntry.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Peer$tagsEntry => { + return decodeMessage(buf, Peer$tagsEntry.codec(), opts) } } @@ -203,7 +211,7 @@ export namespace Peer { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { addresses: [], protocols: [], @@ -217,31 +225,58 @@ export namespace Peer { const tag = reader.uint32() switch (tag >>> 3) { - case 1: - obj.addresses.push(Address.codec().decode(reader, reader.uint32())) + case 1: { + if (opts.limits?.addresses != null && obj.addresses.length === opts.limits.addresses) { + throw new MaxLengthError('Decode error - map field "addresses" had too many elements') + } + + obj.addresses.push(Address.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.addresses$ + })) break - case 2: + } + case 2: { + if (opts.limits?.protocols != null && obj.protocols.length === opts.limits.protocols) { + throw new MaxLengthError('Decode error - map field "protocols" had too many elements') + } + obj.protocols.push(reader.string()) break - case 4: + } + case 4: { obj.publicKey = reader.bytes() break - case 5: + } + case 5: { obj.peerRecordEnvelope = reader.bytes() break + } case 6: { + if (opts.limits?.metadata != null && obj.metadata.size === opts.limits.metadata) { + throw new MaxSizeError('Decode error - map field "metadata" had too many elements') + } + const entry = Peer.Peer$metadataEntry.codec().decode(reader, reader.uint32()) obj.metadata.set(entry.key, entry.value) break } case 7: { - const entry = Peer.Peer$tagsEntry.codec().decode(reader, reader.uint32()) + if (opts.limits?.tags != null && obj.tags.size === opts.limits.tags) { + throw new MaxSizeError('Decode error - map field "tags" had too many elements') + } + + const entry = Peer.Peer$tagsEntry.codec().decode(reader, reader.uint32(), { + limits: { + value: opts.limits?.tags$value + } + }) obj.tags.set(entry.key, entry.value) break } - default: + default: { reader.skipType(tag & 7) break + } } } @@ -256,8 +291,8 @@ export namespace Peer { return encodeMessage(obj, Peer.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Peer => { - return decodeMessage(buf, Peer.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Peer => { + return decodeMessage(buf, Peer.codec(), opts) } } @@ -289,9 +324,9 @@ export namespace Address { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { - multiaddr: new Uint8Array(0) + multiaddr: uint8ArrayAlloc(0) } const end = length == null ? reader.len : reader.pos + length @@ -300,15 +335,18 @@ export namespace Address { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.multiaddr = reader.bytes() break - case 2: + } + case 2: { obj.isCertified = reader.bool() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -323,8 +361,8 @@ export namespace Address { return encodeMessage(obj, Address.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Address => { - return decodeMessage(buf, Address.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions
): Address => { + return decodeMessage(buf, Address.codec(), opts) } } @@ -356,7 +394,7 @@ export namespace Tag { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { value: 0 } @@ -367,15 +405,18 @@ export namespace Tag { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.value = reader.uint32() break - case 2: + } + case 2: { obj.expiry = reader.uint64() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -390,7 +431,7 @@ export namespace Tag { return encodeMessage(obj, Tag.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Tag => { - return decodeMessage(buf, Tag.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Tag => { + return decodeMessage(buf, Tag.codec(), opts) } } diff --git a/packages/peer-store/src/store.ts b/packages/peer-store/src/store.ts index 920788b16a..3292e05988 100644 --- a/packages/peer-store/src/store.ts +++ b/packages/peer-store/src/store.ts @@ -1,10 +1,9 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidParametersError } from '@libp2p/interface' import { PeerMap } from '@libp2p/peer-collections' import { peerIdFromBytes } from '@libp2p/peer-id' import mortice, { type Mortice } from 'mortice' import { base32 } from 'multiformats/bases/base32' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' -import { codes } from './errors.js' import { Peer as PeerPB } from './pb/peer.js' import { bytesToPeer } from './utils/bytes-to-peer.js' import { NAMESPACE_COMMON, peerIdToDatastoreKey } from './utils/peer-id-to-datastore-key.js' @@ -77,7 +76,7 @@ export class PersistentStore { async delete (peerId: PeerId): Promise { if (this.peerId.equals(peerId)) { - throw new CodeError('Cannot delete self peer', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Cannot delete self peer') } await this.datastore.delete(peerIdToDatastoreKey(peerId)) @@ -155,7 +154,7 @@ export class PersistentStore { existingPeer } } catch (err: any) { - if (err.code !== 'ERR_NOT_FOUND') { + if (err.name !== 'NotFoundError') { throw err } } diff --git a/packages/peer-store/src/utils/dedupe-addresses.ts b/packages/peer-store/src/utils/dedupe-addresses.ts index f6d843a05d..d58668e949 100644 --- a/packages/peer-store/src/utils/dedupe-addresses.ts +++ b/packages/peer-store/src/utils/dedupe-addresses.ts @@ -1,6 +1,5 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidParametersError } from '@libp2p/interface' import { isMultiaddr, multiaddr } from '@multiformats/multiaddr' -import { codes } from '../errors.js' import type { AddressFilter } from '../index.js' import type { Address as AddressPB } from '../pb/peer.js' import type { PeerId, Address } from '@libp2p/interface' @@ -18,7 +17,7 @@ export async function dedupeFilterAndSortAddresses (peerId: PeerId, filter: Addr } if (!isMultiaddr(addr.multiaddr)) { - throw new CodeError('Multiaddr was invalid', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Multiaddr was invalid') } if (!(await filter(peerId, addr.multiaddr))) { diff --git a/packages/peer-store/src/utils/peer-data-to-datastore-peer.ts b/packages/peer-store/src/utils/peer-data-to-datastore-peer.ts index 1aca6b1ca5..e9a7944c0d 100644 --- a/packages/peer-store/src/utils/peer-data-to-datastore-peer.ts +++ b/packages/peer-store/src/utils/peer-data-to-datastore-peer.ts @@ -1,17 +1,16 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidParametersError } from '@libp2p/interface' import { isMultiaddr } from '@multiformats/multiaddr' import { equals as uint8arrayEquals } from 'uint8arrays/equals' -import { codes } from '../errors.js' import type { Peer as PeerPB } from '../pb/peer.js' import type { PeerId, PeerData } from '@libp2p/interface' export function toDatastorePeer (peerId: PeerId, data: PeerData): PeerPB { if (data == null) { - throw new CodeError('Invalid PeerData', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Invalid PeerData') } if (data.publicKey != null && peerId.publicKey != null && !uint8arrayEquals(data.publicKey, peerId.publicKey)) { - throw new CodeError('publicKey bytes do not match peer id publicKey bytes', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('publicKey bytes do not match peer id publicKey bytes') } // merge addresses and multiaddrs, and dedupe @@ -22,7 +21,7 @@ export function toDatastorePeer (peerId: PeerId, data: PeerData): PeerPB { .concat((data.multiaddrs ?? []).map(multiaddr => ({ multiaddr, isCertified: false }))) .filter(address => { if (!isMultiaddr(address.multiaddr)) { - throw new CodeError('Invalid mulitaddr', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Invalid mulitaddr') } if (addressSet.has(address.multiaddr.toString())) { @@ -52,7 +51,7 @@ export function toDatastorePeer (peerId: PeerId, data: PeerData): PeerPB { for (const [key, value] of metadataEntries) { if (typeof key !== 'string') { - throw new CodeError('Peer metadata keys must be strings', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Peer metadata keys must be strings') } if (value == null) { @@ -60,7 +59,7 @@ export function toDatastorePeer (peerId: PeerId, data: PeerData): PeerPB { } if (!(value instanceof Uint8Array)) { - throw new CodeError('Peer metadata values must be Uint8Arrays', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Peer metadata values must be Uint8Arrays') } output.metadata.set(key, value) @@ -72,7 +71,7 @@ export function toDatastorePeer (peerId: PeerId, data: PeerData): PeerPB { for (const [key, value] of tagsEntries) { if (typeof key !== 'string') { - throw new CodeError('Peer tag keys must be strings', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Peer tag keys must be strings') } if (value == null) { @@ -86,20 +85,20 @@ export function toDatastorePeer (peerId: PeerId, data: PeerData): PeerPB { } if (tag.value < 0 || tag.value > 100) { - throw new CodeError('Tag value must be between 0-100', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Tag value must be between 0-100') } if (parseInt(`${tag.value}`, 10) !== tag.value) { - throw new CodeError('Tag value must be an integer', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Tag value must be an integer') } if (tag.ttl != null) { if (tag.ttl < 0) { - throw new CodeError('Tag ttl must be between greater than 0', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Tag ttl must be between greater than 0') } if (parseInt(`${tag.ttl}`, 10) !== tag.ttl) { - throw new CodeError('Tag ttl must be an integer', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Tag ttl must be an integer') } } diff --git a/packages/peer-store/src/utils/peer-id-to-datastore-key.ts b/packages/peer-store/src/utils/peer-id-to-datastore-key.ts index 7d46e90c8e..076bff5a1b 100644 --- a/packages/peer-store/src/utils/peer-id-to-datastore-key.ts +++ b/packages/peer-store/src/utils/peer-id-to-datastore-key.ts @@ -1,13 +1,12 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidParametersError } from '@libp2p/interface' import { isPeerId, type PeerId } from '@libp2p/interface' import { Key } from 'interface-datastore/key' -import { codes } from '../errors.js' export const NAMESPACE_COMMON = '/peers/' export function peerIdToDatastoreKey (peerId: PeerId): Key { if (!isPeerId(peerId) || peerId.type == null) { - throw new CodeError('Invalid PeerId', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Invalid PeerId') } const b32key = peerId.toCID().toString() diff --git a/packages/peer-store/src/utils/to-peer-pb.ts b/packages/peer-store/src/utils/to-peer-pb.ts index 937da55d1d..bcf2600409 100644 --- a/packages/peer-store/src/utils/to-peer-pb.ts +++ b/packages/peer-store/src/utils/to-peer-pb.ts @@ -1,6 +1,5 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidParametersError } from '@libp2p/interface' import { equals as uint8arrayEquals } from 'uint8arrays/equals' -import { codes } from '../errors.js' import { dedupeFilterAndSortAddresses } from './dedupe-addresses.js' import type { AddressFilter } from '../index.js' import type { Tag, Peer as PeerPB } from '../pb/peer.js' @@ -13,17 +12,17 @@ export interface ToPBPeerOptions { export async function toPeerPB (peerId: PeerId, data: Partial, strategy: 'merge' | 'patch', options: ToPBPeerOptions): Promise { if (data == null) { - throw new CodeError('Invalid PeerData', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Invalid PeerData') } if (data.publicKey != null && peerId.publicKey != null && !uint8arrayEquals(data.publicKey, peerId.publicKey)) { - throw new CodeError('publicKey bytes do not match peer id publicKey bytes', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('publicKey bytes do not match peer id publicKey bytes') } const existingPeer = options.existingPeer if (existingPeer != null && !peerId.equals(existingPeer.id)) { - throw new CodeError('peer id did not match existing peer id', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('peer id did not match existing peer id') } let addresses: Address[] = existingPeer?.addresses ?? [] @@ -184,36 +183,36 @@ function createSortedMap (entries: Array<[string, V | undefined]>, op function validateMetadata (key: string, value: Uint8Array): void { if (typeof key !== 'string') { - throw new CodeError('Metadata key must be a string', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Metadata key must be a string') } if (!(value instanceof Uint8Array)) { - throw new CodeError('Metadata value must be a Uint8Array', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Metadata value must be a Uint8Array') } } function validateTag (key: string, tag: TagOptions): void { if (typeof key !== 'string') { - throw new CodeError('Tag name must be a string', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Tag name must be a string') } if (tag.value != null) { if (parseInt(`${tag.value}`, 10) !== tag.value) { - throw new CodeError('Tag value must be an integer', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Tag value must be an integer') } if (tag.value < 0 || tag.value > 100) { - throw new CodeError('Tag value must be between 0-100', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Tag value must be between 0-100') } } if (tag.ttl != null) { if (parseInt(`${tag.ttl}`, 10) !== tag.ttl) { - throw new CodeError('Tag ttl must be an integer', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Tag ttl must be an integer') } if (tag.ttl < 0) { - throw new CodeError('Tag ttl must be between greater than 0', codes.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('Tag ttl must be between greater than 0') } } } diff --git a/packages/peer-store/test/index.spec.ts b/packages/peer-store/test/index.spec.ts index 60ddfbee9a..04e821e913 100644 --- a/packages/peer-store/test/index.spec.ts +++ b/packages/peer-store/test/index.spec.ts @@ -70,7 +70,7 @@ describe('PersistentPeerStore', () => { }) await expect(peerStore.delete(peerId)).to.eventually.be.rejected() - .with.property('code', 'ERR_INVALID_PARAMETERS') + .with.property('name', 'InvalidParametersError') }) }) @@ -108,21 +108,21 @@ describe('PersistentPeerStore', () => { [name]: { value: -1 } } }), 'PeerStore contain tag for peer where value was too small') - .to.eventually.be.rejected().with.property('code', 'ERR_INVALID_PARAMETERS') + .to.eventually.be.rejected().with.property('name', 'InvalidParametersError') await expect(peerStore.save(peerId, { tags: { [name]: { value: 101 } } }), 'PeerStore contain tag for peer where value was too large') - .to.eventually.be.rejected().with.property('code', 'ERR_INVALID_PARAMETERS') + .to.eventually.be.rejected().with.property('name', 'InvalidParametersError') await expect(peerStore.save(peerId, { tags: { [name]: { value: 5.5 } } }), 'PeerStore contain tag for peer where value was not an integer') - .to.eventually.be.rejected().with.property('code', 'ERR_INVALID_PARAMETERS') + .to.eventually.be.rejected().with.property('name', 'InvalidParametersError') }) it('tags a peer with an expiring value', async () => { diff --git a/packages/peer-store/test/save.spec.ts b/packages/peer-store/test/save.spec.ts index 2afeaabc53..b1c27249a5 100644 --- a/packages/peer-store/test/save.spec.ts +++ b/packages/peer-store/test/save.spec.ts @@ -10,7 +10,6 @@ import { MemoryDatastore } from 'datastore-core/memory' import pDefer from 'p-defer' import { pEvent } from 'p-event' import sinon from 'sinon' -import { codes } from '../src/errors.js' import { PersistentPeerStore } from '../src/index.js' import { Peer as PeerPB } from '../src/pb/peer.js' @@ -38,13 +37,13 @@ describe('save', () => { it('throws invalid parameters error if invalid PeerId is provided', async () => { // @ts-expect-error invalid input await expect(peerStore.save('invalid peerId')) - .to.eventually.be.rejected.with.property('code', codes.ERR_INVALID_PARAMETERS) + .to.eventually.be.rejected.with.property('name', 'InvalidParametersError') }) it('throws invalid parameters error if no peer data provided', async () => { // @ts-expect-error invalid input await expect(peerStore.save(peerId)) - .to.eventually.be.rejected.with.property('code', codes.ERR_INVALID_PARAMETERS) + .to.eventually.be.rejected.with.property('name', 'InvalidParametersError') }) it('throws invalid parameters error if invalid multiaddrs are provided', async () => { @@ -52,7 +51,7 @@ describe('save', () => { // @ts-expect-error invalid input addresses: ['invalid multiaddr'] })) - .to.eventually.be.rejected.with.property('code', codes.ERR_INVALID_PARAMETERS) + .to.eventually.be.rejected.with.property('name', 'InvalidParametersError') }) it('replaces the stored content by default and emit change event', async () => { diff --git a/packages/pnet/src/errors.ts b/packages/pnet/src/errors.ts index a8288ac045..b09f68a2a1 100644 --- a/packages/pnet/src/errors.ts +++ b/packages/pnet/src/errors.ts @@ -3,4 +3,3 @@ export const INVALID_PSK = 'Your private shared key is invalid' export const NO_LOCAL_ID = 'No local private key provided' export const NO_HANDSHAKE_CONNECTION = 'No connection for the handshake provided' export const STREAM_ENDED = 'Stream ended prematurely' -export const ERR_INVALID_PARAMETERS = 'ERR_INVALID_PARAMETERS' diff --git a/packages/pnet/src/index.ts b/packages/pnet/src/index.ts index c14cf54745..b770d9d5bc 100644 --- a/packages/pnet/src/index.ts +++ b/packages/pnet/src/index.ts @@ -57,7 +57,7 @@ */ import { randomBytes } from '@libp2p/crypto' -import { CodeError } from '@libp2p/interface' +import { InvalidParametersError } from '@libp2p/interface' import { byteStream } from 'it-byte-stream' import map from 'it-map' import { duplexPair } from 'it-pair/duplex' @@ -67,7 +67,6 @@ import { createUnboxStream, decodeV1PSK } from './crypto.js' -import * as Errors from './errors.js' import { NONCE_LENGTH } from './key-generator.js' import type { ComponentLogger, Logger, ConnectionProtector, MultiaddrConnection } from '@libp2p/interface' import type { Uint8ArrayList } from 'uint8arraylist' @@ -119,7 +118,7 @@ class PreSharedKeyConnectionProtector implements ConnectionProtector { */ async protect (connection: MultiaddrConnection): Promise { if (connection == null) { - throw new CodeError(Errors.NO_HANDSHAKE_CONNECTION, Errors.ERR_INVALID_PARAMETERS) + throw new InvalidParametersError('No connection for the handshake provided') } // Exchange nonces diff --git a/packages/protocol-autonat/src/autonat.ts b/packages/protocol-autonat/src/autonat.ts index e9c51a3420..67e61c6186 100644 --- a/packages/protocol-autonat/src/autonat.ts +++ b/packages/protocol-autonat/src/autonat.ts @@ -1,4 +1,4 @@ -import { CodeError, ERR_TIMEOUT, setMaxListeners } from '@libp2p/interface' +import { AbortError, setMaxListeners } from '@libp2p/interface' import { peerIdFromBytes } from '@libp2p/peer-id' import { isPrivateIp } from '@libp2p/utils/private-ip' import { multiaddr, protocols } from '@multiformats/multiaddr' @@ -87,7 +87,7 @@ export class AutoNATService implements Startable { const signal = AbortSignal.timeout(this.timeout) const onAbort = (): void => { - data.stream.abort(new CodeError('handleIncomingAutonatStream timeout', ERR_TIMEOUT)) + data.stream.abort(new AbortError()) } signal.addEventListener('abort', onAbort, { once: true }) @@ -385,7 +385,7 @@ export class AutoNATService implements Startable { signal }) - onAbort = () => { stream.abort(new CodeError('verifyAddress timeout', ERR_TIMEOUT)) } + onAbort = () => { stream.abort(new AbortError()) } signal.addEventListener('abort', onAbort, { once: true }) diff --git a/packages/protocol-autonat/src/pb/index.ts b/packages/protocol-autonat/src/pb/index.ts index 0d9e840f03..ebd6f039a8 100644 --- a/packages/protocol-autonat/src/pb/index.ts +++ b/packages/protocol-autonat/src/pb/index.ts @@ -4,8 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { enumeration, encodeMessage, decodeMessage, message } from 'protons-runtime' -import type { Codec } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, enumeration, MaxLengthError, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export interface Message { @@ -83,7 +82,7 @@ export namespace Message { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { addrs: [] } @@ -94,15 +93,22 @@ export namespace Message { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.id = reader.bytes() break - case 2: + } + case 2: { + if (opts.limits?.addrs != null && obj.addrs.length === opts.limits.addrs) { + throw new MaxLengthError('Decode error - map field "addrs" had too many elements') + } + obj.addrs.push(reader.bytes()) break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -117,8 +123,8 @@ export namespace Message { return encodeMessage(obj, PeerInfo.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): PeerInfo => { - return decodeMessage(buf, PeerInfo.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): PeerInfo => { + return decodeMessage(buf, PeerInfo.codec(), opts) } } @@ -144,7 +150,7 @@ export namespace Message { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -153,12 +159,16 @@ export namespace Message { const tag = reader.uint32() switch (tag >>> 3) { - case 1: - obj.peer = Message.PeerInfo.codec().decode(reader, reader.uint32()) + case 1: { + obj.peer = Message.PeerInfo.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.peer + }) break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -173,8 +183,8 @@ export namespace Message { return encodeMessage(obj, Dial.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Dial => { - return decodeMessage(buf, Dial.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Dial => { + return decodeMessage(buf, Dial.codec(), opts) } } @@ -212,7 +222,7 @@ export namespace Message { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -221,18 +231,22 @@ export namespace Message { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.status = Message.ResponseStatus.codec().decode(reader) break - case 2: + } + case 2: { obj.statusText = reader.string() break - case 3: + } + case 3: { obj.addr = reader.bytes() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -247,8 +261,8 @@ export namespace Message { return encodeMessage(obj, DialResponse.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): DialResponse => { - return decodeMessage(buf, DialResponse.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): DialResponse => { + return decodeMessage(buf, DialResponse.codec(), opts) } } @@ -279,7 +293,7 @@ export namespace Message { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -288,18 +302,26 @@ export namespace Message { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.type = Message.MessageType.codec().decode(reader) break - case 2: - obj.dial = Message.Dial.codec().decode(reader, reader.uint32()) + } + case 2: { + obj.dial = Message.Dial.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.dial + }) break - case 3: - obj.dialResponse = Message.DialResponse.codec().decode(reader, reader.uint32()) + } + case 3: { + obj.dialResponse = Message.DialResponse.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.dialResponse + }) break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -314,7 +336,7 @@ export namespace Message { return encodeMessage(obj, Message.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Message => { - return decodeMessage(buf, Message.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Message => { + return decodeMessage(buf, Message.codec(), opts) } } diff --git a/packages/protocol-dcutr/package.json b/packages/protocol-dcutr/package.json index 7beb185064..594ba707e0 100644 --- a/packages/protocol-dcutr/package.json +++ b/packages/protocol-dcutr/package.json @@ -41,7 +41,7 @@ "build": "aegir build", "test": "aegir test", "clean": "aegir clean", - "generate": "protons ./src/pb/index.proto", + "generate": "protons ./src/pb/message.proto", "lint": "aegir lint", "test:chrome": "aegir test -t browser --cov", "test:chrome-webworker": "aegir test -t webworker", diff --git a/packages/protocol-dcutr/src/dcutr.ts b/packages/protocol-dcutr/src/dcutr.ts index 9476048fe8..c791ae2174 100644 --- a/packages/protocol-dcutr/src/dcutr.ts +++ b/packages/protocol-dcutr/src/dcutr.ts @@ -1,4 +1,4 @@ -import { CodeError, ERR_INVALID_MESSAGE, serviceDependencies } from '@libp2p/interface' +import { InvalidMessageError, serviceDependencies } from '@libp2p/interface' import { type Multiaddr, multiaddr } from '@multiformats/multiaddr' import { Circuit } from '@multiformats/multiaddr-matcher' import delay from 'delay' @@ -164,14 +164,14 @@ export class DefaultDCUtRService implements Startable { if (connect.type !== HolePunch.Type.CONNECT) { this.log('A sent wrong message type') - throw new CodeError('DCUtR message type was incorrect', ERR_INVALID_MESSAGE) + throw new InvalidMessageError('DCUtR message type was incorrect') } const multiaddrs = this.getDialableMultiaddrs(connect.observedAddresses) if (multiaddrs.length === 0) { this.log('A did not have any dialable multiaddrs') - throw new CodeError('DCUtR connect message had no multiaddrs', ERR_INVALID_MESSAGE) + throw new InvalidMessageError('DCUtR connect message had no multiaddrs') } const rtt = Date.now() - connectTimer @@ -301,19 +301,19 @@ export class DefaultDCUtRService implements Startable { if (connect.type !== HolePunch.Type.CONNECT) { this.log('B sent wrong message type') - throw new CodeError('DCUtR message type was incorrect', ERR_INVALID_MESSAGE) + throw new InvalidMessageError('DCUtR message type was incorrect') } if (connect.observedAddresses.length === 0) { this.log('B sent no multiaddrs') - throw new CodeError('DCUtR connect message had no multiaddrs', ERR_INVALID_MESSAGE) + throw new InvalidMessageError('DCUtR connect message had no multiaddrs') } const multiaddrs = this.getDialableMultiaddrs(connect.observedAddresses) if (multiaddrs.length === 0) { this.log('B had no dialable multiaddrs') - throw new CodeError('DCUtR connect message had no dialable multiaddrs', ERR_INVALID_MESSAGE) + throw new InvalidMessageError('DCUtR connect message had no dialable multiaddrs') } this.log('A sending connect') @@ -326,7 +326,7 @@ export class DefaultDCUtRService implements Startable { const sync = await pb.read(options) if (sync.type !== HolePunch.Type.SYNC) { - throw new CodeError('DCUtR message type was incorrect', ERR_INVALID_MESSAGE) + throw new InvalidMessageError('DCUtR message type was incorrect') } // TODO: when we have a QUIC transport, the dial step is different - for diff --git a/packages/protocol-dcutr/src/pb/message.ts b/packages/protocol-dcutr/src/pb/message.ts index 81fbfde1b1..6c8774c3bd 100644 --- a/packages/protocol-dcutr/src/pb/message.ts +++ b/packages/protocol-dcutr/src/pb/message.ts @@ -4,8 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { enumeration, encodeMessage, decodeMessage, message } from 'protons-runtime' -import type { Codec } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, enumeration, MaxLengthError, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export interface HolePunch { @@ -56,7 +55,7 @@ export namespace HolePunch { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { observedAddresses: [] } @@ -67,15 +66,22 @@ export namespace HolePunch { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.type = HolePunch.Type.codec().decode(reader) break - case 2: + } + case 2: { + if (opts.limits?.observedAddresses != null && obj.observedAddresses.length === opts.limits.observedAddresses) { + throw new MaxLengthError('Decode error - map field "observedAddresses" had too many elements') + } + obj.observedAddresses.push(reader.bytes()) break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -90,7 +96,7 @@ export namespace HolePunch { return encodeMessage(obj, HolePunch.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): HolePunch => { - return decodeMessage(buf, HolePunch.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): HolePunch => { + return decodeMessage(buf, HolePunch.codec(), opts) } } diff --git a/packages/protocol-echo/package.json b/packages/protocol-echo/package.json index 3d28c6389f..45a2229578 100644 --- a/packages/protocol-echo/package.json +++ b/packages/protocol-echo/package.json @@ -41,7 +41,6 @@ "build": "aegir build", "test": "aegir test", "clean": "aegir clean", - "generate": "protons ./src/pb/index.proto", "lint": "aegir lint", "test:chrome": "aegir test -t browser --cov", "test:chrome-webworker": "aegir test -t webworker", diff --git a/packages/protocol-fetch/package.json b/packages/protocol-fetch/package.json index 3170ff49f5..a2404bfb60 100644 --- a/packages/protocol-fetch/package.json +++ b/packages/protocol-fetch/package.json @@ -41,7 +41,7 @@ "build": "aegir build", "test": "aegir test", "clean": "aegir clean", - "generate": "protons ./src/pb/index.proto", + "generate": "protons ./src/pb/proto.proto", "lint": "aegir lint", "test:chrome": "aegir test -t browser --cov", "test:chrome-webworker": "aegir test -t webworker", diff --git a/packages/protocol-fetch/src/fetch.ts b/packages/protocol-fetch/src/fetch.ts index 6f9a808f19..73ef6c2361 100644 --- a/packages/protocol-fetch/src/fetch.ts +++ b/packages/protocol-fetch/src/fetch.ts @@ -1,4 +1,4 @@ -import { CodeError, ERR_INVALID_MESSAGE, ERR_INVALID_PARAMETERS, ERR_TIMEOUT, setMaxListeners } from '@libp2p/interface' +import { AbortError, InvalidMessageError, InvalidParametersError, ProtocolError, setMaxListeners } from '@libp2p/interface' import { pbStream } from 'it-protobuf-stream' import { fromString as uint8arrayFromString } from 'uint8arrays/from-string' import { toString as uint8arrayToString } from 'uint8arrays/to-string' @@ -87,7 +87,7 @@ export class Fetch implements Startable, FetchInterface { }) onAbort = () => { - stream?.abort(new CodeError('fetch timeout', ERR_TIMEOUT)) + stream?.abort(new AbortError()) } // make stream abortable @@ -115,11 +115,11 @@ export class Fetch implements Startable, FetchInterface { case (FetchResponse.StatusCode.ERROR): { this.log('received status for %s error', key) const errmsg = uint8arrayToString(response.data) - throw new CodeError('Error in fetch protocol response: ' + errmsg, ERR_INVALID_PARAMETERS) + throw new ProtocolError('Error in fetch protocol response: ' + errmsg) } default: { this.log('received status for %s unknown', key) - throw new CodeError('Unknown response status', ERR_INVALID_MESSAGE) + throw new InvalidMessageError('Unknown response status') } } } catch (err: any) { @@ -204,7 +204,7 @@ export class Fetch implements Startable, FetchInterface { */ registerLookupFunction (prefix: string, lookup: LookupFunction): void { if (this.lookupFunctions.has(prefix)) { - throw new CodeError(`Fetch protocol handler for key prefix '${prefix}' already registered`, 'ERR_KEY_ALREADY_EXISTS') + throw new InvalidParametersError(`Fetch protocol handler for key prefix '${prefix}' already registered`) } this.lookupFunctions.set(prefix, lookup) diff --git a/packages/protocol-fetch/src/pb/proto.ts b/packages/protocol-fetch/src/pb/proto.ts index 4eae212dcd..6ddb3d35ea 100644 --- a/packages/protocol-fetch/src/pb/proto.ts +++ b/packages/protocol-fetch/src/pb/proto.ts @@ -4,8 +4,8 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { encodeMessage, decodeMessage, message, enumeration } from 'protons-runtime' -import type { Codec } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, enumeration, message } from 'protons-runtime' +import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' import type { Uint8ArrayList } from 'uint8arraylist' export interface FetchRequest { @@ -30,7 +30,7 @@ export namespace FetchRequest { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { identifier: '' } @@ -41,12 +41,14 @@ export namespace FetchRequest { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.identifier = reader.string() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -61,8 +63,8 @@ export namespace FetchRequest { return encodeMessage(obj, FetchRequest.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): FetchRequest => { - return decodeMessage(buf, FetchRequest.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): FetchRequest => { + return decodeMessage(buf, FetchRequest.codec(), opts) } } @@ -112,10 +114,10 @@ export namespace FetchResponse { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { status: StatusCode.OK, - data: new Uint8Array(0) + data: uint8ArrayAlloc(0) } const end = length == null ? reader.len : reader.pos + length @@ -124,15 +126,18 @@ export namespace FetchResponse { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.status = FetchResponse.StatusCode.codec().decode(reader) break - case 2: + } + case 2: { obj.data = reader.bytes() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -147,7 +152,7 @@ export namespace FetchResponse { return encodeMessage(obj, FetchResponse.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): FetchResponse => { - return decodeMessage(buf, FetchResponse.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): FetchResponse => { + return decodeMessage(buf, FetchResponse.codec(), opts) } } diff --git a/packages/protocol-fetch/test/index.spec.ts b/packages/protocol-fetch/test/index.spec.ts index 6630cad008..9a151b6330 100644 --- a/packages/protocol-fetch/test/index.spec.ts +++ b/packages/protocol-fetch/test/index.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { ERR_INVALID_PARAMETERS, start, stop } from '@libp2p/interface' +import { start, stop } from '@libp2p/interface' import { defaultLogger } from '@libp2p/logger' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { expect } from 'aegir/chai' @@ -140,7 +140,7 @@ describe('fetch', () => { }, FetchResponse) await expect(result).to.eventually.be.rejected - .with.property('code', ERR_INVALID_PARAMETERS) + .with.property('name', 'ProtocolError') }) it('should time out fetching from another peer when waiting for the record', async () => { @@ -158,7 +158,7 @@ describe('fetch', () => { await expect(fetch.fetch(remotePeer, key, { signal: AbortSignal.timeout(10) })).to.eventually.be.rejected - .with.property('code', 'ABORT_ERR') + .with.property('name', 'AbortError') expect(outgoingStream.abort.called).to.be.true() }) @@ -264,7 +264,7 @@ describe('fetch', () => { }) expect(incomingStream.abort.called).to.be.true() - expect(incomingStream.abort.getCall(0).args[0]).to.have.property('code', 'ABORT_ERR') + expect(incomingStream.abort.getCall(0).args[0]).to.have.property('name', 'AbortError') }) }) }) diff --git a/packages/protocol-identify/package.json b/packages/protocol-identify/package.json index 4ba1a062c1..5390c1284c 100644 --- a/packages/protocol-identify/package.json +++ b/packages/protocol-identify/package.json @@ -41,7 +41,7 @@ "build": "aegir build", "test": "aegir test", "clean": "aegir clean", - "generate": "protons ./src/pb/index.proto", + "generate": "protons ./src/pb/message.proto", "lint": "aegir lint", "test:chrome": "aegir test -t browser --cov", "test:chrome-webworker": "aegir test -t webworker", diff --git a/packages/protocol-identify/src/identify.ts b/packages/protocol-identify/src/identify.ts index a031d2ac1d..6d82665421 100644 --- a/packages/protocol-identify/src/identify.ts +++ b/packages/protocol-identify/src/identify.ts @@ -1,6 +1,6 @@ /* eslint-disable complexity */ -import { CodeError, serviceCapabilities, setMaxListeners } from '@libp2p/interface' +import { InvalidMessageError, serviceCapabilities, setMaxListeners } from '@libp2p/interface' import { peerIdFromKeys } from '@libp2p/peer-id' import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record' import { protocols } from '@multiformats/multiaddr' @@ -81,17 +81,17 @@ export class Identify extends AbstractIdentify implements Startable, IdentifyInt } = message if (publicKey == null) { - throw new CodeError('public key was missing from identify message', 'ERR_MISSING_PUBLIC_KEY') + throw new InvalidMessageError('public key was missing from identify message') } const id = await peerIdFromKeys(publicKey) if (!connection.remotePeer.equals(id)) { - throw new CodeError('identified peer does not match the expected peer', 'ERR_INVALID_PEER') + throw new InvalidMessageError('identified peer does not match the expected peer') } if (this.peerId.equals(id)) { - throw new CodeError('identified peer is our own peer id?', 'ERR_INVALID_PEER') + throw new InvalidMessageError('identified peer is our own peer id?') } // Get the observedAddr if there is one diff --git a/packages/protocol-identify/src/pb/message.ts b/packages/protocol-identify/src/pb/message.ts index 2f6c2b276b..48c28ac67f 100644 --- a/packages/protocol-identify/src/pb/message.ts +++ b/packages/protocol-identify/src/pb/message.ts @@ -4,8 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { encodeMessage, decodeMessage, message } from 'protons-runtime' -import type { Codec } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, MaxLengthError, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export interface Identify { @@ -70,7 +69,7 @@ export namespace Identify { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { listenAddrs: [], protocols: [] @@ -82,30 +81,46 @@ export namespace Identify { const tag = reader.uint32() switch (tag >>> 3) { - case 5: + case 5: { obj.protocolVersion = reader.string() break - case 6: + } + case 6: { obj.agentVersion = reader.string() break - case 1: + } + case 1: { obj.publicKey = reader.bytes() break - case 2: + } + case 2: { + if (opts.limits?.listenAddrs != null && obj.listenAddrs.length === opts.limits.listenAddrs) { + throw new MaxLengthError('Decode error - map field "listenAddrs" had too many elements') + } + obj.listenAddrs.push(reader.bytes()) break - case 4: + } + case 4: { obj.observedAddr = reader.bytes() break - case 3: + } + case 3: { + if (opts.limits?.protocols != null && obj.protocols.length === opts.limits.protocols) { + throw new MaxLengthError('Decode error - map field "protocols" had too many elements') + } + obj.protocols.push(reader.string()) break - case 8: + } + case 8: { obj.signedPeerRecord = reader.bytes() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -120,7 +135,7 @@ export namespace Identify { return encodeMessage(obj, Identify.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Identify => { - return decodeMessage(buf, Identify.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Identify => { + return decodeMessage(buf, Identify.codec(), opts) } } diff --git a/packages/protocol-identify/src/utils.ts b/packages/protocol-identify/src/utils.ts index 1f436b3da6..b74cd74876 100644 --- a/packages/protocol-identify/src/utils.ts +++ b/packages/protocol-identify/src/utils.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidMessageError } from '@libp2p/interface' import { peerIdFromKeys } from '@libp2p/peer-id' import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record' import { type Multiaddr, multiaddr } from '@multiformats/multiaddr' @@ -56,7 +56,7 @@ export async function consumeIdentifyMessage (peerStore: PeerStore, events: Type log('received identify from %p', connection.remotePeer) if (message == null) { - throw new CodeError('message was null or undefined', 'ERR_INVALID_MESSAGE') + throw new InvalidMessageError('message was null or undefined') } const peer: PeerData = {} @@ -78,7 +78,7 @@ export async function consumeIdentifyMessage (peerStore: PeerStore, events: Type const peerId = await peerIdFromKeys(message.publicKey) if (!peerId.equals(connection.remotePeer)) { - throw new CodeError('public key did not match remote PeerId', 'ERR_INVALID_PUBLIC_KEY') + throw new InvalidMessageError('public key did not match remote PeerId') } } @@ -94,12 +94,12 @@ export async function consumeIdentifyMessage (peerStore: PeerStore, events: Type // Verify peerId if (!peerRecord.peerId.equals(envelope.peerId)) { - throw new CodeError('signing key does not match PeerId in the PeerRecord', 'ERR_INVALID_SIGNING_KEY') + throw new InvalidMessageError('signing key does not match PeerId in the PeerRecord') } // Make sure remote peer is the one sending the record if (!connection.remotePeer.equals(peerRecord.peerId)) { - throw new CodeError('signing key does not match remote PeerId', 'ERR_INVALID_PEER_RECORD_KEY') + throw new InvalidMessageError('signing key does not match remote PeerId') } let existingPeer: Peer | undefined @@ -107,7 +107,7 @@ export async function consumeIdentifyMessage (peerStore: PeerStore, events: Type try { existingPeer = await peerStore.get(peerRecord.peerId) } catch (err: any) { - if (err.code !== 'ERR_NOT_FOUND') { + if (err.name !== 'NotFoundError') { throw err } } diff --git a/packages/protocol-identify/test/index.spec.ts b/packages/protocol-identify/test/index.spec.ts index 200f7f7b14..6ee9067672 100644 --- a/packages/protocol-identify/test/index.spec.ts +++ b/packages/protocol-identify/test/index.spec.ts @@ -95,7 +95,7 @@ describe('identify', () => { // run identify await expect(identify.identify(connection)) .to.eventually.be.rejected() - .and.to.have.property('code', 'ERR_INVALID_PEER') + .and.to.have.property('name', 'InvalidMessageError') }) it('should store own host data and protocol version into metadataBook on start', async () => { @@ -135,7 +135,7 @@ describe('identify', () => { await expect(identify.identify(connection, { signal: AbortSignal.timeout(timeout) })) - .to.eventually.be.rejected.with.property('code', 'ABORT_ERR') + .to.eventually.be.rejected.with.property('name', 'AbortError') // should have aborted stream expect(stream.abort.called).to.be.true() @@ -163,7 +163,7 @@ describe('identify', () => { // run identify await expect(identify.identify(connection)) - .to.eventually.be.rejected.with.property('code', 'ERR_MSG_DATA_TOO_LONG') + .to.eventually.be.rejected.with.property('name', 'InvalidDataLengthError') // should have aborted stream expect(stream.abort.called).to.be.true() diff --git a/packages/protocol-ping/src/constants.ts b/packages/protocol-ping/src/constants.ts index 0e16b6b72b..29516820bb 100644 --- a/packages/protocol-ping/src/constants.ts +++ b/packages/protocol-ping/src/constants.ts @@ -13,5 +13,3 @@ export const TIMEOUT = 10000 // opening stream A even though the dialing peer is opening stream B and closing stream A). export const MAX_INBOUND_STREAMS = 2 export const MAX_OUTBOUND_STREAMS = 1 - -export const ERR_WRONG_PING_ACK = 'ERR_WRONG_PING_ACK' diff --git a/packages/protocol-ping/src/ping.ts b/packages/protocol-ping/src/ping.ts index 0cbfee54ba..5efabeeb4e 100644 --- a/packages/protocol-ping/src/ping.ts +++ b/packages/protocol-ping/src/ping.ts @@ -1,9 +1,9 @@ import { randomBytes } from '@libp2p/crypto' -import { CodeError, ERR_INVALID_MESSAGE, ERR_TIMEOUT } from '@libp2p/interface' +import { AbortError, InvalidMessageError, ProtocolError, TimeoutError } from '@libp2p/interface' import first from 'it-first' import { pipe } from 'it-pipe' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' -import { PROTOCOL_PREFIX, PROTOCOL_NAME, PING_LENGTH, PROTOCOL_VERSION, TIMEOUT, MAX_INBOUND_STREAMS, MAX_OUTBOUND_STREAMS, ERR_WRONG_PING_ACK } from './constants.js' +import { PROTOCOL_PREFIX, PROTOCOL_NAME, PING_LENGTH, PROTOCOL_VERSION, TIMEOUT, MAX_INBOUND_STREAMS, MAX_OUTBOUND_STREAMS } from './constants.js' import type { PingServiceComponents, PingServiceInit, PingService as PingServiceInterface } from './index.js' import type { AbortOptions, Logger, Stream, PeerId, Startable } from '@libp2p/interface' import type { IncomingStreamData } from '@libp2p/interface-internal' @@ -63,7 +63,7 @@ export class PingService implements Startable, PingServiceInterface { const signal = AbortSignal.timeout(this.timeout) signal.addEventListener('abort', () => { - stream?.abort(new CodeError('ping timeout', ERR_TIMEOUT)) + stream?.abort(new TimeoutError('ping timeout')) }) void pipe( @@ -75,7 +75,7 @@ export class PingService implements Startable, PingServiceInterface { received += buf.byteLength if (received > PING_LENGTH) { - stream?.abort(new CodeError('Too much data received', ERR_INVALID_MESSAGE)) + stream?.abort(new InvalidMessageError('Too much data received')) return } @@ -123,7 +123,7 @@ export class PingService implements Startable, PingServiceInterface { }) onAbort = () => { - stream?.abort(new CodeError('ping timeout', ERR_TIMEOUT)) + stream?.abort(new AbortError()) } // make stream abortable @@ -138,11 +138,11 @@ export class PingService implements Startable, PingServiceInterface { const ms = Date.now() - start if (result == null) { - throw new CodeError(`Did not receive a ping ack after ${ms}ms`, ERR_WRONG_PING_ACK) + throw new ProtocolError(`Did not receive a ping ack after ${ms}ms`) } if (!uint8ArrayEquals(data, result.subarray())) { - throw new CodeError(`Received wrong ping ack after ${ms}ms`, ERR_WRONG_PING_ACK) + throw new ProtocolError(`Received wrong ping ack after ${ms}ms`) } this.log('ping %p complete in %dms', connection.remotePeer, ms) diff --git a/packages/protocol-ping/test/index.spec.ts b/packages/protocol-ping/test/index.spec.ts index 4fef38c592..1ee29022f5 100644 --- a/packages/protocol-ping/test/index.spec.ts +++ b/packages/protocol-ping/test/index.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { ERR_TIMEOUT, start } from '@libp2p/interface' +import { start } from '@libp2p/interface' import { defaultLogger } from '@libp2p/logger' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { expect } from 'aegir/chai' @@ -84,8 +84,8 @@ describe('ping', () => { // Run ping, should time out await expect(ping.ping(remotePeer, { signal - })) - .to.eventually.be.rejected.with.property('code', ERR_TIMEOUT) + })).to.eventually.be.rejected + .with.property('name', 'AbortError') // should have aborted stream expect(stream.abort).to.have.property('called', true) diff --git a/packages/pubsub-floodsub/src/message/rpc.ts b/packages/pubsub-floodsub/src/message/rpc.ts index 395409bb4b..2fe0c50704 100644 --- a/packages/pubsub-floodsub/src/message/rpc.ts +++ b/packages/pubsub-floodsub/src/message/rpc.ts @@ -4,8 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { encodeMessage, decodeMessage, message } from 'protons-runtime' -import type { Codec } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, MaxLengthError, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export interface RPC { @@ -43,7 +42,7 @@ export namespace RPC { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -52,15 +51,18 @@ export namespace RPC { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.subscribe = reader.bool() break - case 2: + } + case 2: { obj.topic = reader.string() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -75,8 +77,8 @@ export namespace RPC { return encodeMessage(obj, SubOpts.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): SubOpts => { - return decodeMessage(buf, SubOpts.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): SubOpts => { + return decodeMessage(buf, SubOpts.codec(), opts) } } @@ -132,7 +134,7 @@ export namespace RPC { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -141,27 +143,34 @@ export namespace RPC { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.from = reader.bytes() break - case 2: + } + case 2: { obj.data = reader.bytes() break - case 3: + } + case 3: { obj.sequenceNumber = reader.bytes() break - case 4: + } + case 4: { obj.topic = reader.string() break - case 5: + } + case 5: { obj.signature = reader.bytes() break - case 6: + } + case 6: { obj.key = reader.bytes() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -176,8 +185,8 @@ export namespace RPC { return encodeMessage(obj, Message.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Message => { - return decodeMessage(buf, Message.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Message => { + return decodeMessage(buf, Message.codec(), opts) } } @@ -212,7 +221,7 @@ export namespace RPC { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { subscriptions: [], messages: [] @@ -224,18 +233,36 @@ export namespace RPC { const tag = reader.uint32() switch (tag >>> 3) { - case 1: - obj.subscriptions.push(RPC.SubOpts.codec().decode(reader, reader.uint32())) + case 1: { + if (opts.limits?.subscriptions != null && obj.subscriptions.length === opts.limits.subscriptions) { + throw new MaxLengthError('Decode error - map field "subscriptions" had too many elements') + } + + obj.subscriptions.push(RPC.SubOpts.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.subscriptions$ + })) break - case 2: - obj.messages.push(RPC.Message.codec().decode(reader, reader.uint32())) + } + case 2: { + if (opts.limits?.messages != null && obj.messages.length === opts.limits.messages) { + throw new MaxLengthError('Decode error - map field "messages" had too many elements') + } + + obj.messages.push(RPC.Message.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.messages$ + })) break - case 3: - obj.control = ControlMessage.codec().decode(reader, reader.uint32()) + } + case 3: { + obj.control = ControlMessage.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.control + }) break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -250,8 +277,8 @@ export namespace RPC { return encodeMessage(obj, RPC.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): RPC => { - return decodeMessage(buf, RPC.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): RPC => { + return decodeMessage(buf, RPC.codec(), opts) } } @@ -303,7 +330,7 @@ export namespace ControlMessage { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { ihave: [], iwant: [], @@ -317,21 +344,50 @@ export namespace ControlMessage { const tag = reader.uint32() switch (tag >>> 3) { - case 1: - obj.ihave.push(ControlIHave.codec().decode(reader, reader.uint32())) + case 1: { + if (opts.limits?.ihave != null && obj.ihave.length === opts.limits.ihave) { + throw new MaxLengthError('Decode error - map field "ihave" had too many elements') + } + + obj.ihave.push(ControlIHave.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.ihave$ + })) break - case 2: - obj.iwant.push(ControlIWant.codec().decode(reader, reader.uint32())) + } + case 2: { + if (opts.limits?.iwant != null && obj.iwant.length === opts.limits.iwant) { + throw new MaxLengthError('Decode error - map field "iwant" had too many elements') + } + + obj.iwant.push(ControlIWant.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.iwant$ + })) break - case 3: - obj.graft.push(ControlGraft.codec().decode(reader, reader.uint32())) + } + case 3: { + if (opts.limits?.graft != null && obj.graft.length === opts.limits.graft) { + throw new MaxLengthError('Decode error - map field "graft" had too many elements') + } + + obj.graft.push(ControlGraft.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.graft$ + })) break - case 4: - obj.prune.push(ControlPrune.codec().decode(reader, reader.uint32())) + } + case 4: { + if (opts.limits?.prune != null && obj.prune.length === opts.limits.prune) { + throw new MaxLengthError('Decode error - map field "prune" had too many elements') + } + + obj.prune.push(ControlPrune.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.prune$ + })) break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -346,8 +402,8 @@ export namespace ControlMessage { return encodeMessage(obj, ControlMessage.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): ControlMessage => { - return decodeMessage(buf, ControlMessage.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): ControlMessage => { + return decodeMessage(buf, ControlMessage.codec(), opts) } } @@ -381,7 +437,7 @@ export namespace ControlIHave { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { messageIDs: [] } @@ -392,15 +448,22 @@ export namespace ControlIHave { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.topic = reader.string() break - case 2: + } + case 2: { + if (opts.limits?.messageIDs != null && obj.messageIDs.length === opts.limits.messageIDs) { + throw new MaxLengthError('Decode error - map field "messageIDs" had too many elements') + } + obj.messageIDs.push(reader.bytes()) break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -415,8 +478,8 @@ export namespace ControlIHave { return encodeMessage(obj, ControlIHave.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): ControlIHave => { - return decodeMessage(buf, ControlIHave.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): ControlIHave => { + return decodeMessage(buf, ControlIHave.codec(), opts) } } @@ -444,7 +507,7 @@ export namespace ControlIWant { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { messageIDs: [] } @@ -455,12 +518,18 @@ export namespace ControlIWant { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { + if (opts.limits?.messageIDs != null && obj.messageIDs.length === opts.limits.messageIDs) { + throw new MaxLengthError('Decode error - map field "messageIDs" had too many elements') + } + obj.messageIDs.push(reader.bytes()) break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -475,8 +544,8 @@ export namespace ControlIWant { return encodeMessage(obj, ControlIWant.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): ControlIWant => { - return decodeMessage(buf, ControlIWant.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): ControlIWant => { + return decodeMessage(buf, ControlIWant.codec(), opts) } } @@ -502,7 +571,7 @@ export namespace ControlGraft { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -511,12 +580,14 @@ export namespace ControlGraft { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.topic = reader.string() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -531,8 +602,8 @@ export namespace ControlGraft { return encodeMessage(obj, ControlGraft.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): ControlGraft => { - return decodeMessage(buf, ControlGraft.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): ControlGraft => { + return decodeMessage(buf, ControlGraft.codec(), opts) } } @@ -572,7 +643,7 @@ export namespace ControlPrune { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { peers: [] } @@ -583,18 +654,28 @@ export namespace ControlPrune { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.topic = reader.string() break - case 2: - obj.peers.push(PeerInfo.codec().decode(reader, reader.uint32())) + } + case 2: { + if (opts.limits?.peers != null && obj.peers.length === opts.limits.peers) { + throw new MaxLengthError('Decode error - map field "peers" had too many elements') + } + + obj.peers.push(PeerInfo.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.peers$ + })) break - case 3: + } + case 3: { obj.backoff = reader.uint64() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -609,8 +690,8 @@ export namespace ControlPrune { return encodeMessage(obj, ControlPrune.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): ControlPrune => { - return decodeMessage(buf, ControlPrune.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): ControlPrune => { + return decodeMessage(buf, ControlPrune.codec(), opts) } } @@ -642,7 +723,7 @@ export namespace PeerInfo { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -651,15 +732,18 @@ export namespace PeerInfo { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.peerID = reader.bytes() break - case 2: + } + case 2: { obj.signedPeerRecord = reader.bytes() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -674,7 +758,7 @@ export namespace PeerInfo { return encodeMessage(obj, PeerInfo.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): PeerInfo => { - return decodeMessage(buf, PeerInfo.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): PeerInfo => { + return decodeMessage(buf, PeerInfo.codec(), opts) } } diff --git a/packages/pubsub/src/errors.ts b/packages/pubsub/src/errors.ts deleted file mode 100644 index a8429a7c9a..0000000000 --- a/packages/pubsub/src/errors.ts +++ /dev/null @@ -1,57 +0,0 @@ -export const codes = { - /** - * Signature policy is invalid - */ - ERR_INVALID_SIGNATURE_POLICY: 'ERR_INVALID_SIGNATURE_POLICY', - /** - * Signature policy is unhandled - */ - ERR_UNHANDLED_SIGNATURE_POLICY: 'ERR_UNHANDLED_SIGNATURE_POLICY', - - // Strict signing codes - - /** - * Message expected to have a `signature`, but doesn't - */ - ERR_MISSING_SIGNATURE: 'ERR_MISSING_SIGNATURE', - /** - * Message expected to have a `seqno`, but doesn't - */ - ERR_MISSING_SEQNO: 'ERR_MISSING_SEQNO', - /** - * Message expected to have a `key`, but doesn't - */ - ERR_MISSING_KEY: 'ERR_MISSING_KEY', - /** - * Message `signature` is invalid - */ - ERR_INVALID_SIGNATURE: 'ERR_INVALID_SIGNATURE', - /** - * Message expected to have a `from`, but doesn't - */ - ERR_MISSING_FROM: 'ERR_MISSING_FROM', - - // Strict no-signing codes - - /** - * Message expected to not have a `from`, but does - */ - ERR_UNEXPECTED_FROM: 'ERR_UNEXPECTED_FROM', - /** - * Message expected to not have a `signature`, but does - */ - ERR_UNEXPECTED_SIGNATURE: 'ERR_UNEXPECTED_SIGNATURE', - /** - * Message expected to not have a `key`, but does - */ - ERR_UNEXPECTED_KEY: 'ERR_UNEXPECTED_KEY', - /** - * Message expected to not have a `seqno`, but does - */ - ERR_UNEXPECTED_SEQNO: 'ERR_UNEXPECTED_SEQNO', - - /** - * Message failed topic validator - */ - ERR_TOPIC_VALIDATOR_REJECT: 'ERR_TOPIC_VALIDATOR_REJECT' -} diff --git a/packages/pubsub/src/index.ts b/packages/pubsub/src/index.ts index 536f3fc206..e692b8e2ea 100644 --- a/packages/pubsub/src/index.ts +++ b/packages/pubsub/src/index.ts @@ -30,11 +30,10 @@ * ``` */ -import { CodeError, TypedEventEmitter, TopicValidatorResult } from '@libp2p/interface' +import { TypedEventEmitter, TopicValidatorResult, InvalidMessageError, NotStartedError, InvalidParametersError } from '@libp2p/interface' import { PeerMap, PeerSet } from '@libp2p/peer-collections' import { pipe } from 'it-pipe' import Queue from 'p-queue' -import { codes } from './errors.js' import { PeerStreams as PeerStreamsImpl } from './peer-streams.js' import { signMessage, @@ -486,22 +485,22 @@ export abstract class PubSubBaseProtocol = Pu switch (signaturePolicy) { case 'StrictSign': if (msg.type !== 'signed') { - throw new CodeError('Message type should be "signed" when signature policy is StrictSign but it was not', codes.ERR_MISSING_SIGNATURE) + throw new InvalidMessageError('Message type should be "signed" when signature policy is StrictSign but it was not') } if (msg.sequenceNumber == null) { - throw new CodeError('Need seqno when signature policy is StrictSign but it was missing', codes.ERR_MISSING_SEQNO) + throw new InvalidMessageError('Need seqno when signature policy is StrictSign but it was missing') } if (msg.key == null) { - throw new CodeError('Need key when signature policy is StrictSign but it was missing', codes.ERR_MISSING_KEY) + throw new InvalidMessageError('Need key when signature policy is StrictSign but it was missing') } return msgId(msg.key, msg.sequenceNumber) case 'StrictNoSign': return noSignMsgId(msg.data) default: - throw new CodeError('Cannot get message id: unhandled signature policy', codes.ERR_UNHANDLED_SIGNATURE_POLICY) + throw new InvalidMessageError('Cannot get message id: unhandled signature policy') } } @@ -573,51 +572,51 @@ export abstract class PubSubBaseProtocol = Pu switch (signaturePolicy) { case 'StrictNoSign': if (message.type !== 'unsigned') { - throw new CodeError('Message type should be "unsigned" when signature policy is StrictNoSign but it was not', codes.ERR_MISSING_SIGNATURE) + throw new InvalidMessageError('Message type should be "unsigned" when signature policy is StrictNoSign but it was not') } // @ts-expect-error should not be present if (message.signature != null) { - throw new CodeError('StrictNoSigning: signature should not be present', codes.ERR_UNEXPECTED_SIGNATURE) + throw new InvalidMessageError('StrictNoSigning: signature should not be present') } // @ts-expect-error should not be present if (message.key != null) { - throw new CodeError('StrictNoSigning: key should not be present', codes.ERR_UNEXPECTED_KEY) + throw new InvalidMessageError('StrictNoSigning: key should not be present') } // @ts-expect-error should not be present if (message.sequenceNumber != null) { - throw new CodeError('StrictNoSigning: seqno should not be present', codes.ERR_UNEXPECTED_SEQNO) + throw new InvalidMessageError('StrictNoSigning: seqno should not be present') } break case 'StrictSign': if (message.type !== 'signed') { - throw new CodeError('Message type should be "signed" when signature policy is StrictSign but it was not', codes.ERR_MISSING_SIGNATURE) + throw new InvalidMessageError('Message type should be "signed" when signature policy is StrictSign but it was not') } if (message.signature == null) { - throw new CodeError('StrictSigning: Signing required and no signature was present', codes.ERR_MISSING_SIGNATURE) + throw new InvalidMessageError('StrictSigning: Signing required and no signature was present') } if (message.sequenceNumber == null) { - throw new CodeError('StrictSigning: Signing required and no sequenceNumber was present', codes.ERR_MISSING_SEQNO) + throw new InvalidMessageError('StrictSigning: Signing required and no sequenceNumber was present') } if (!(await verifySignature(message, this.encodeMessage.bind(this)))) { - throw new CodeError('StrictSigning: Invalid message signature', codes.ERR_INVALID_SIGNATURE) + throw new InvalidMessageError('StrictSigning: Invalid message signature') } break default: - throw new CodeError('Cannot validate message: unhandled signature policy', codes.ERR_UNHANDLED_SIGNATURE_POLICY) + throw new InvalidMessageError('Cannot validate message: unhandled signature policy') } const validatorFn = this.topicValidators.get(message.topic) if (validatorFn != null) { const result = await validatorFn(from, message) if (result === TopicValidatorResult.Reject || result === TopicValidatorResult.Ignore) { - throw new CodeError('Message validation failed', codes.ERR_TOPIC_VALIDATOR_REJECT) + throw new InvalidMessageError('Message validation failed') } } } @@ -637,7 +636,7 @@ export abstract class PubSubBaseProtocol = Pu ...message }) default: - throw new CodeError('Cannot build message: unhandled signature policy', codes.ERR_UNHANDLED_SIGNATURE_POLICY) + throw new InvalidMessageError('Cannot build message: unhandled signature policy') } } @@ -648,11 +647,11 @@ export abstract class PubSubBaseProtocol = Pu */ getSubscribers (topic: string): PeerId[] { if (!this.started) { - throw new CodeError('not started yet', 'ERR_NOT_STARTED_YET') + throw new NotStartedError('not started yet') } if (topic == null) { - throw new CodeError('topic is required', 'ERR_NOT_VALID_TOPIC') + throw new InvalidParametersError('Topic is required') } const peersInTopic = this.topics.get(topic.toString()) diff --git a/packages/pubsub/src/utils.ts b/packages/pubsub/src/utils.ts index 880be56781..420251ec51 100644 --- a/packages/pubsub/src/utils.ts +++ b/packages/pubsub/src/utils.ts @@ -1,10 +1,9 @@ import { randomBytes } from '@libp2p/crypto' -import { CodeError } from '@libp2p/interface' +import { InvalidMessageError } from '@libp2p/interface' import { peerIdFromBytes, peerIdFromKeys } from '@libp2p/peer-id' import { sha256 } from 'multiformats/hashes/sha2' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string' -import { codes } from './errors.js' import type { Message, PubSubRPCMessage } from '@libp2p/interface' /** @@ -86,7 +85,7 @@ const isSigned = async (message: PubSubRPCMessage): Promise => { export const toMessage = async (message: PubSubRPCMessage): Promise => { if (message.from == null) { - throw new CodeError('RPC message was missing from', codes.ERR_MISSING_FROM) + throw new InvalidMessageError('RPC message was missing from') } if (!await isSigned(message)) { @@ -110,7 +109,7 @@ export const toMessage = async (message: PubSubRPCMessage): Promise => } if (msg.key.length === 0) { - throw new CodeError('Signed RPC message was missing key', codes.ERR_MISSING_KEY) + throw new InvalidMessageError('Signed RPC message was missing key') } return msg diff --git a/packages/pubsub/test/message/rpc.ts b/packages/pubsub/test/message/rpc.ts index 68ccaa7ed6..5ec7b427a9 100644 --- a/packages/pubsub/test/message/rpc.ts +++ b/packages/pubsub/test/message/rpc.ts @@ -4,8 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { encodeMessage, decodeMessage, message } from 'protons-runtime' -import type { Codec } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, MaxLengthError, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export interface RPC { @@ -43,7 +42,7 @@ export namespace RPC { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -52,15 +51,18 @@ export namespace RPC { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.subscribe = reader.bool() break - case 2: + } + case 2: { obj.topic = reader.string() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -75,8 +77,8 @@ export namespace RPC { return encodeMessage(obj, SubOpts.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): SubOpts => { - return decodeMessage(buf, SubOpts.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): SubOpts => { + return decodeMessage(buf, SubOpts.codec(), opts) } } @@ -132,7 +134,7 @@ export namespace RPC { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -141,27 +143,34 @@ export namespace RPC { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.from = reader.bytes() break - case 2: + } + case 2: { obj.data = reader.bytes() break - case 3: + } + case 3: { obj.seqno = reader.bytes() break - case 4: + } + case 4: { obj.topic = reader.string() break - case 5: + } + case 5: { obj.signature = reader.bytes() break - case 6: + } + case 6: { obj.key = reader.bytes() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -176,8 +185,8 @@ export namespace RPC { return encodeMessage(obj, Message.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Message => { - return decodeMessage(buf, Message.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Message => { + return decodeMessage(buf, Message.codec(), opts) } } @@ -212,7 +221,7 @@ export namespace RPC { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { subscriptions: [], messages: [] @@ -224,18 +233,36 @@ export namespace RPC { const tag = reader.uint32() switch (tag >>> 3) { - case 1: - obj.subscriptions.push(RPC.SubOpts.codec().decode(reader, reader.uint32())) + case 1: { + if (opts.limits?.subscriptions != null && obj.subscriptions.length === opts.limits.subscriptions) { + throw new MaxLengthError('Decode error - map field "subscriptions" had too many elements') + } + + obj.subscriptions.push(RPC.SubOpts.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.subscriptions$ + })) break - case 2: - obj.messages.push(RPC.Message.codec().decode(reader, reader.uint32())) + } + case 2: { + if (opts.limits?.messages != null && obj.messages.length === opts.limits.messages) { + throw new MaxLengthError('Decode error - map field "messages" had too many elements') + } + + obj.messages.push(RPC.Message.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.messages$ + })) break - case 3: - obj.control = ControlMessage.codec().decode(reader, reader.uint32()) + } + case 3: { + obj.control = ControlMessage.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.control + }) break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -250,8 +277,8 @@ export namespace RPC { return encodeMessage(obj, RPC.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): RPC => { - return decodeMessage(buf, RPC.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): RPC => { + return decodeMessage(buf, RPC.codec(), opts) } } @@ -303,7 +330,7 @@ export namespace ControlMessage { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { ihave: [], iwant: [], @@ -317,21 +344,50 @@ export namespace ControlMessage { const tag = reader.uint32() switch (tag >>> 3) { - case 1: - obj.ihave.push(ControlIHave.codec().decode(reader, reader.uint32())) + case 1: { + if (opts.limits?.ihave != null && obj.ihave.length === opts.limits.ihave) { + throw new MaxLengthError('Decode error - map field "ihave" had too many elements') + } + + obj.ihave.push(ControlIHave.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.ihave$ + })) break - case 2: - obj.iwant.push(ControlIWant.codec().decode(reader, reader.uint32())) + } + case 2: { + if (opts.limits?.iwant != null && obj.iwant.length === opts.limits.iwant) { + throw new MaxLengthError('Decode error - map field "iwant" had too many elements') + } + + obj.iwant.push(ControlIWant.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.iwant$ + })) break - case 3: - obj.graft.push(ControlGraft.codec().decode(reader, reader.uint32())) + } + case 3: { + if (opts.limits?.graft != null && obj.graft.length === opts.limits.graft) { + throw new MaxLengthError('Decode error - map field "graft" had too many elements') + } + + obj.graft.push(ControlGraft.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.graft$ + })) break - case 4: - obj.prune.push(ControlPrune.codec().decode(reader, reader.uint32())) + } + case 4: { + if (opts.limits?.prune != null && obj.prune.length === opts.limits.prune) { + throw new MaxLengthError('Decode error - map field "prune" had too many elements') + } + + obj.prune.push(ControlPrune.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.prune$ + })) break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -346,8 +402,8 @@ export namespace ControlMessage { return encodeMessage(obj, ControlMessage.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): ControlMessage => { - return decodeMessage(buf, ControlMessage.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): ControlMessage => { + return decodeMessage(buf, ControlMessage.codec(), opts) } } @@ -381,7 +437,7 @@ export namespace ControlIHave { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { messageIDs: [] } @@ -392,15 +448,22 @@ export namespace ControlIHave { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.topic = reader.string() break - case 2: + } + case 2: { + if (opts.limits?.messageIDs != null && obj.messageIDs.length === opts.limits.messageIDs) { + throw new MaxLengthError('Decode error - map field "messageIDs" had too many elements') + } + obj.messageIDs.push(reader.bytes()) break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -415,8 +478,8 @@ export namespace ControlIHave { return encodeMessage(obj, ControlIHave.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): ControlIHave => { - return decodeMessage(buf, ControlIHave.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): ControlIHave => { + return decodeMessage(buf, ControlIHave.codec(), opts) } } @@ -444,7 +507,7 @@ export namespace ControlIWant { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { messageIDs: [] } @@ -455,12 +518,18 @@ export namespace ControlIWant { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { + if (opts.limits?.messageIDs != null && obj.messageIDs.length === opts.limits.messageIDs) { + throw new MaxLengthError('Decode error - map field "messageIDs" had too many elements') + } + obj.messageIDs.push(reader.bytes()) break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -475,8 +544,8 @@ export namespace ControlIWant { return encodeMessage(obj, ControlIWant.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): ControlIWant => { - return decodeMessage(buf, ControlIWant.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): ControlIWant => { + return decodeMessage(buf, ControlIWant.codec(), opts) } } @@ -502,7 +571,7 @@ export namespace ControlGraft { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -511,12 +580,14 @@ export namespace ControlGraft { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.topic = reader.string() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -531,8 +602,8 @@ export namespace ControlGraft { return encodeMessage(obj, ControlGraft.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): ControlGraft => { - return decodeMessage(buf, ControlGraft.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): ControlGraft => { + return decodeMessage(buf, ControlGraft.codec(), opts) } } @@ -572,7 +643,7 @@ export namespace ControlPrune { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { peers: [] } @@ -583,18 +654,28 @@ export namespace ControlPrune { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.topic = reader.string() break - case 2: - obj.peers.push(PeerInfo.codec().decode(reader, reader.uint32())) + } + case 2: { + if (opts.limits?.peers != null && obj.peers.length === opts.limits.peers) { + throw new MaxLengthError('Decode error - map field "peers" had too many elements') + } + + obj.peers.push(PeerInfo.codec().decode(reader, reader.uint32(), { + limits: opts.limits?.peers$ + })) break - case 3: + } + case 3: { obj.backoff = reader.uint64() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -609,8 +690,8 @@ export namespace ControlPrune { return encodeMessage(obj, ControlPrune.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): ControlPrune => { - return decodeMessage(buf, ControlPrune.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): ControlPrune => { + return decodeMessage(buf, ControlPrune.codec(), opts) } } @@ -642,7 +723,7 @@ export namespace PeerInfo { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -651,15 +732,18 @@ export namespace PeerInfo { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.peerID = reader.bytes() break - case 2: + } + case 2: { obj.signedPeerRecord = reader.bytes() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -674,7 +758,7 @@ export namespace PeerInfo { return encodeMessage(obj, PeerInfo.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): PeerInfo => { - return decodeMessage(buf, PeerInfo.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): PeerInfo => { + return decodeMessage(buf, PeerInfo.codec(), opts) } } diff --git a/packages/pubsub/test/pubsub.spec.ts b/packages/pubsub/test/pubsub.spec.ts index bd4da454b9..758888f393 100644 --- a/packages/pubsub/test/pubsub.spec.ts +++ b/packages/pubsub/test/pubsub.spec.ts @@ -389,7 +389,7 @@ describe('pubsub base implementation', () => { pubsub.getSubscribers(topic) } catch (err: any) { expect(err).to.exist() - expect(err.code).to.eql('ERR_NOT_STARTED_YET') + expect(err.name).to.equal('NotStartedError') return } throw new Error('should fail if pubsub is not started') @@ -404,7 +404,7 @@ describe('pubsub base implementation', () => { pubsub.getSubscribers() } catch (err: any) { expect(err).to.exist() - expect(err.code).to.eql('ERR_NOT_VALID_TOPIC') + expect(err.name).to.equal('InvalidParametersError') return } throw new Error('should fail if no topic is provided') diff --git a/packages/stream-multiplexer-mplex/src/decode.ts b/packages/stream-multiplexer-mplex/src/decode.ts index 2b85cd5e83..1a45598fe5 100644 --- a/packages/stream-multiplexer-mplex/src/decode.ts +++ b/packages/stream-multiplexer-mplex/src/decode.ts @@ -1,3 +1,4 @@ +import { InvalidMessageError } from '@libp2p/interface' import { Uint8ArrayList } from 'uint8arraylist' import { MessageTypeNames, MessageTypes } from './message-types.js' import type { Message } from './message-types.js' @@ -33,7 +34,7 @@ export class Decoder { this._buffer.append(chunk) if (this._buffer.byteLength > this._maxUnprocessedMessageQueueSize) { - throw Object.assign(new Error('unprocessed message queue size too large!'), { code: 'ERR_MSG_QUEUE_TOO_BIG' }) + throw new InvalidMessageError('Unprocessed message queue size too large!') } const msgs: Message[] = [] @@ -43,7 +44,7 @@ export class Decoder { try { this._headerInfo = this._decodeHeader(this._buffer) } catch (err: any) { - if (err.code === 'ERR_MSG_TOO_BIG') { + if (err.name === 'InvalidMessageError') { throw err } @@ -98,7 +99,7 @@ export class Decoder { // test message type varint + data length if (length > this._maxMessageSize) { - throw Object.assign(new Error('message size too large!'), { code: 'ERR_MSG_TOO_BIG' }) + throw new InvalidMessageError('Message size too large') } // @ts-expect-error h is a number not a CODE diff --git a/packages/stream-multiplexer-mplex/src/errors.ts b/packages/stream-multiplexer-mplex/src/errors.ts new file mode 100644 index 0000000000..3c111b59fa --- /dev/null +++ b/packages/stream-multiplexer-mplex/src/errors.ts @@ -0,0 +1,9 @@ +/** + * There was an error in the stream input buffer + */ +export class StreamInputBufferError extends Error { + constructor (message = 'Stream input buffer error') { + super(message) + this.name = 'StreamInputBufferError' + } +} diff --git a/packages/stream-multiplexer-mplex/src/mplex.ts b/packages/stream-multiplexer-mplex/src/mplex.ts index 23746b9660..c88b9154e9 100644 --- a/packages/stream-multiplexer-mplex/src/mplex.ts +++ b/packages/stream-multiplexer-mplex/src/mplex.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { TooManyOutboundProtocolStreamsError } from '@libp2p/interface' import { closeSource } from '@libp2p/utils/close-source' import { RateLimiter } from '@libp2p/utils/rate-limiter' import { pipe } from 'it-pipe' @@ -6,6 +6,7 @@ import { type Pushable, pushable } from 'it-pushable' import { toString as uint8ArrayToString } from 'uint8arrays' import { Decoder } from './decode.js' import { encode } from './encode.js' +import { StreamInputBufferError } from './errors.js' import { MessageTypes, MessageTypeNames, type Message } from './message-types.js' import { createStream, type MplexStream } from './stream.js' import type { MplexInit } from './index.js' @@ -205,7 +206,7 @@ export class MplexStreamMuxer implements StreamMuxer { this.log('new %s stream %s', type, id) if (type === 'initiator' && this._streams.initiators.size === (this._init.maxOutboundStreams ?? MAX_STREAMS_OUTBOUND_STREAMS_PER_CONNECTION)) { - throw new CodeError('Too many outbound streams open', 'ERR_TOO_MANY_OUTBOUND_STREAMS') + throw new TooManyOutboundProtocolStreamsError('Too many outbound streams open') } if (registry.has(id)) { @@ -345,7 +346,7 @@ export class MplexStreamMuxer implements StreamMuxer { }) // Inform the stream consumer they are not fast enough - throw new CodeError('Input buffer full - increase Mplex maxBufferSize to accommodate slow consumers', 'ERR_STREAM_INPUT_BUFFER_FULL') + throw new StreamInputBufferError('Input buffer full - increase Mplex maxBufferSize to accommodate slow consumers') } // We got data from the remote, push it into our local stream diff --git a/packages/stream-multiplexer-mplex/test/mplex.spec.ts b/packages/stream-multiplexer-mplex/test/mplex.spec.ts index 5e62614d76..c78d4ed745 100644 --- a/packages/stream-multiplexer-mplex/test/mplex.spec.ts +++ b/packages/stream-multiplexer-mplex/test/mplex.spec.ts @@ -34,7 +34,7 @@ describe('mplex', () => { await expect((async () => { await muxer.newStream() })()).eventually.be.rejected - .with.property('code', 'ERR_TOO_MANY_OUTBOUND_STREAMS') + .with.property('name', 'TooManyOutboundProtocolStreamsError') }) it('should restrict number of recipient streams per connection', async () => { @@ -172,7 +172,7 @@ describe('mplex', () => { // source should have errored with appropriate code const err = await streamSourceError.promise - expect(err).to.have.property('code', 'ERR_STREAM_INPUT_BUFFER_FULL') + expect(err).to.have.property('name', 'StreamInputBufferError') // should have sent reset message to peer for this stream await muxerFinished.promise diff --git a/packages/stream-multiplexer-mplex/test/restrict-size.spec.ts b/packages/stream-multiplexer-mplex/test/restrict-size.spec.ts index 5b531e524f..627259a5fd 100644 --- a/packages/stream-multiplexer-mplex/test/restrict-size.spec.ts +++ b/packages/stream-multiplexer-mplex/test/restrict-size.spec.ts @@ -35,7 +35,7 @@ describe('restrict size', () => { async (source) => { await drain(source) } ) } catch (err: any) { - expect(err).to.have.property('code', 'ERR_MSG_TOO_BIG') + expect(err).to.have.property('name', 'InvalidMessageError') expect(output).to.have.length(3) expect(output[0]).to.deep.equal(input[0]) expect(output[1]).to.deep.equal(input[1]) @@ -92,7 +92,7 @@ describe('restrict size', () => { async (source) => { await drain(source) } ) } catch (err: any) { - expect(err).to.have.property('code', 'ERR_MSG_QUEUE_TOO_BIG') + expect(err).to.have.property('name', 'InvalidMessageError') expect(output).to.have.length(0) return } @@ -115,7 +115,7 @@ describe('restrict size', () => { async (source) => { await drain(source) } ) } catch (err: any) { - expect(err).to.have.property('code', 'ERR_MSG_QUEUE_TOO_BIG') + expect(err).to.have.property('name', 'InvalidMessageError') expect(output).to.have.length(0) return } diff --git a/packages/stream-multiplexer-mplex/test/stream.spec.ts b/packages/stream-multiplexer-mplex/test/stream.spec.ts index a7104adad2..cd0d4c3bae 100644 --- a/packages/stream-multiplexer-mplex/test/stream.spec.ts +++ b/packages/stream-multiplexer-mplex/test/stream.spec.ts @@ -180,7 +180,7 @@ describe('stream', () => { const err = await deferred.promise expect(err).to.exist() - expect(err).to.have.property('code', 'ERR_STREAM_RESET') + expect(err).to.have.property('name', 'StreamResetError') }) it('should send data with MESSAGE_INITIATOR messages if stream initiator', async () => { @@ -554,7 +554,7 @@ describe('stream', () => { // cannot sink twice await expect(stream.sink([])) - .to.eventually.be.rejected.with.property('code', 'ERR_SINK_INVALID_STATE') + .to.eventually.be.rejected.with.property('name', 'StreamStateError') }) it('should error on double sink after sink has ended', async () => { @@ -567,7 +567,7 @@ describe('stream', () => { // cannot sink twice await expect(stream.sink([])) - .to.eventually.be.rejected.with.property('code', 'ERR_SINK_INVALID_STATE') + .to.eventually.be.rejected.with.property('name', 'StreamStateError') }) it('should chunk really big messages', async () => { diff --git a/packages/transport-circuit-relay-v2/src/constants.ts b/packages/transport-circuit-relay-v2/src/constants.ts index 7a47d1ba40..4ed60942ad 100644 --- a/packages/transport-circuit-relay-v2/src/constants.ts +++ b/packages/transport-circuit-relay-v2/src/constants.ts @@ -71,10 +71,5 @@ export const DEFAULT_ADVERT_BOOT_DELAY = 30 * second export const MAX_CONNECTIONS = 300 -export const ERR_NO_ROUTERS_AVAILABLE = 'ERR_NO_ROUTERS_AVAILABLE' -export const ERR_RELAYED_DIAL = 'ERR_RELAYED_DIAL' -export const ERR_HOP_REQUEST_FAILED = 'ERR_HOP_REQUEST_FAILED' -export const ERR_TRANSFER_LIMIT_EXCEEDED = 'ERR_TRANSFER_LIMIT_EXCEEDED' - export const DEFAULT_DISCOVERY_FILTER_SIZE = 4096 export const DEFAULT_DISCOVERY_FILTER_ERROR_RATE = 0.001 diff --git a/packages/transport-circuit-relay-v2/src/errors.ts b/packages/transport-circuit-relay-v2/src/errors.ts new file mode 100644 index 0000000000..eb3a4dd48f --- /dev/null +++ b/packages/transport-circuit-relay-v2/src/errors.ts @@ -0,0 +1,19 @@ +/** + * A transfer limit was hit + */ +export class TransferLimitError extends Error { + constructor (message = 'Transfer limit error') { + super(message) + this.name = 'TransferLimitError' + } +} + +/** + * A duration limit was hit + */ +export class DurationLimitError extends Error { + constructor (message = 'Duration limit error') { + super(message) + this.name = 'DurationLimitError' + } +} diff --git a/packages/transport-circuit-relay-v2/src/pb/index.ts b/packages/transport-circuit-relay-v2/src/pb/index.ts index 9073458e46..59b846aa20 100644 --- a/packages/transport-circuit-relay-v2/src/pb/index.ts +++ b/packages/transport-circuit-relay-v2/src/pb/index.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { type Codec, CodeError, decodeMessage, type DecodeOptions, encodeMessage, enumeration, message } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, enumeration, MaxLengthError, message } from 'protons-runtime' import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' import type { Uint8ArrayList } from 'uint8arraylist' @@ -286,7 +286,7 @@ export namespace Peer { } case 2: { if (opts.limits?.addrs != null && obj.addrs.length === opts.limits.addrs) { - throw new CodeError('decode error - map field "addrs" had too many elements', 'ERR_MAX_LENGTH') + throw new MaxLengthError('Decode error - map field "addrs" had too many elements') } obj.addrs.push(reader.bytes()) @@ -369,7 +369,7 @@ export namespace Reservation { } case 2: { if (opts.limits?.addrs != null && obj.addrs.length === opts.limits.addrs) { - throw new CodeError('decode error - map field "addrs" had too many elements', 'ERR_MAX_LENGTH') + throw new MaxLengthError('Decode error - map field "addrs" had too many elements') } obj.addrs.push(reader.bytes()) diff --git a/packages/transport-circuit-relay-v2/src/transport/listener.ts b/packages/transport-circuit-relay-v2/src/transport/listener.ts index 7058131aac..239345370f 100644 --- a/packages/transport-circuit-relay-v2/src/transport/listener.ts +++ b/packages/transport-circuit-relay-v2/src/transport/listener.ts @@ -1,4 +1,4 @@ -import { CodeError, TypedEventEmitter } from '@libp2p/interface' +import { ListenError, TypedEventEmitter } from '@libp2p/interface' import { PeerMap } from '@libp2p/peer-collections' import { multiaddr } from '@multiformats/multiaddr' import type { ReservationStore } from './reservation-store.js' @@ -51,7 +51,7 @@ class CircuitRelayTransportListener extends TypedEventEmitter im const reservation = this.relayStore.getReservation(relayConn.remotePeer) if (reservation == null) { - throw new CodeError('Did not have reservation after making reservation', 'ERR_NO_RESERVATION') + throw new ListenError('Did not have reservation after making reservation') } if (this.listeningAddrs.has(relayConn.remotePeer)) { diff --git a/packages/transport-circuit-relay-v2/src/transport/transport.ts b/packages/transport-circuit-relay-v2/src/transport/transport.ts index b79881b03d..a90b509053 100644 --- a/packages/transport-circuit-relay-v2/src/transport/transport.ts +++ b/packages/transport-circuit-relay-v2/src/transport/transport.ts @@ -1,4 +1,4 @@ -import { CodeError, serviceCapabilities, serviceDependencies, start, stop, transportSymbol } from '@libp2p/interface' +import { DialError, InvalidMessageError, serviceCapabilities, serviceDependencies, start, stop, transportSymbol } from '@libp2p/interface' import { peerFilter } from '@libp2p/peer-collections' import { peerIdFromBytes, peerIdFromString } from '@libp2p/peer-id' import { streamToMaConnection } from '@libp2p/utils/stream-to-ma-conn' @@ -6,7 +6,7 @@ import * as mafmt from '@multiformats/mafmt' import { multiaddr } from '@multiformats/multiaddr' import { pbStream } from 'it-protobuf-stream' import { CustomProgressEvent } from 'progress-events' -import { CIRCUIT_PROTO_CODE, DEFAULT_DISCOVERY_FILTER_ERROR_RATE, DEFAULT_DISCOVERY_FILTER_SIZE, ERR_HOP_REQUEST_FAILED, ERR_RELAYED_DIAL, MAX_CONNECTIONS, RELAY_V2_HOP_CODEC, RELAY_V2_STOP_CODEC } from '../constants.js' +import { CIRCUIT_PROTO_CODE, DEFAULT_DISCOVERY_FILTER_ERROR_RATE, DEFAULT_DISCOVERY_FILTER_SIZE, MAX_CONNECTIONS, RELAY_V2_HOP_CODEC, RELAY_V2_STOP_CODEC } from '../constants.js' import { StopMessage, HopMessage, Status } from '../pb/index.js' import { LimitTracker } from '../utils.js' import { RelayDiscovery } from './discovery.js' @@ -172,7 +172,7 @@ export class CircuitRelayTransport implements Transport if (ma.protoCodes().filter(code => code === CIRCUIT_PROTO_CODE).length !== 1) { const errMsg = 'Invalid circuit relay address' this.log.error(errMsg, ma) - throw new CodeError(errMsg, ERR_RELAYED_DIAL) + throw new DialError(errMsg) } // Check the multiaddr to see if it contains a relay and a destination peer @@ -185,7 +185,7 @@ export class CircuitRelayTransport implements Transport if (relayId == null || destinationId == null) { const errMsg = `Circuit relay dial to ${ma.toString()} failed as address did not have peer ids` this.log.error(errMsg) - throw new CodeError(errMsg, ERR_RELAYED_DIAL) + throw new DialError(errMsg) } const relayPeer = peerIdFromString(relayId) @@ -259,7 +259,7 @@ export class CircuitRelayTransport implements Transport const status = await hopstr.read() if (status.status !== Status.OK) { - throw new CodeError(`failed to connect via relay with status ${status?.status?.toString() ?? 'undefined'}`, ERR_HOP_REQUEST_FAILED) + throw new InvalidMessageError(`failed to connect via relay with status ${status?.status?.toString() ?? 'undefined'}`) } const limits = new LimitTracker(status.limit) diff --git a/packages/transport-circuit-relay-v2/src/utils.ts b/packages/transport-circuit-relay-v2/src/utils.ts index 51a5c35b45..d9b1886782 100644 --- a/packages/transport-circuit-relay-v2/src/utils.ts +++ b/packages/transport-circuit-relay-v2/src/utils.ts @@ -1,8 +1,7 @@ -import { CodeError } from '@libp2p/interface' import { anySignal } from 'any-signal' import { CID } from 'multiformats/cid' import { sha256 } from 'multiformats/hashes/sha2' -import { ERR_TRANSFER_LIMIT_EXCEEDED } from './constants.js' +import { DurationLimitError, TransferLimitError } from './errors.js' import type { Limit } from './pb/index.js' import type { ConnectionLimits, LoggerOptions, Stream } from '@libp2p/interface' import type { Source } from 'it-stream-types' @@ -27,7 +26,7 @@ async function * countStreamBytes (source: Source, options.log.error(err) } - throw new CodeError(`data limit of ${limitBytes} bytes exceeded`, ERR_TRANSFER_LIMIT_EXCEEDED) + throw new TransferLimitError(`data limit of ${limitBytes} bytes exceeded`) } limit.remaining -= len @@ -62,7 +61,7 @@ export function createLimitedRelay (src: Stream, dst: Stream, abortSignal: Abort queueMicrotask(() => { const onAbort = (): void => { - dst.abort(new CodeError(`duration limit of ${limit?.duration} ms exceeded`, ERR_TRANSFER_LIMIT_EXCEEDED)) + dst.abort(new DurationLimitError(`duration limit of ${limit?.duration} ms exceeded`)) } signal.addEventListener('abort', onAbort, { once: true }) @@ -84,7 +83,7 @@ export function createLimitedRelay (src: Stream, dst: Stream, abortSignal: Abort queueMicrotask(() => { const onAbort = (): void => { - src.abort(new CodeError(`duration limit of ${limit?.duration} ms exceeded`, ERR_TRANSFER_LIMIT_EXCEEDED)) + src.abort(new DurationLimitError(`duration limit of ${limit?.duration} ms exceeded`)) } signal.addEventListener('abort', onAbort, { once: true }) diff --git a/packages/transport-tcp/src/listener.ts b/packages/transport-tcp/src/listener.ts index 3776cba818..39254c198b 100644 --- a/packages/transport-tcp/src/listener.ts +++ b/packages/transport-tcp/src/listener.ts @@ -1,5 +1,5 @@ import net from 'net' -import { CodeError, TypedEventEmitter } from '@libp2p/interface' +import { AbortError, AlreadyStartedError, InvalidParametersError, NotStartedError, TypedEventEmitter } from '@libp2p/interface' import { CODE_P2P } from './constants.js' import { toMultiaddrConnection } from './socket-to-conn.js' import { @@ -107,7 +107,7 @@ export class TCPListener extends TypedEventEmitter implements Li if (context.closeServerOnMaxConnections != null) { // Sanity check options if (context.closeServerOnMaxConnections.closeAbove < context.closeServerOnMaxConnections.listenBelow) { - throw new CodeError('closeAbove must be >= listenBelow', 'ERR_CONNECTION_LIMITS') + throw new InvalidParametersError('closeAbove must be >= listenBelow') } } @@ -179,7 +179,7 @@ export class TCPListener extends TypedEventEmitter implements Li private onSocket (socket: net.Socket): void { if (this.status.code !== TCPListenerStatusCode.ACTIVE) { - throw new CodeError('Server is not listening yet', 'ERR_SERVER_NOT_RUNNING') + throw new NotStartedError('Server is not listening yet') } // Avoid uncaught errors caused by unstable connections socket.on('error', err => { @@ -304,7 +304,7 @@ export class TCPListener extends TypedEventEmitter implements Li async listen (ma: Multiaddr): Promise { if (this.status.code === TCPListenerStatusCode.ACTIVE || this.status.code === TCPListenerStatusCode.PAUSED) { - throw new CodeError('server is already listening', 'ERR_SERVER_ALREADY_LISTENING') + throw new AlreadyStartedError('server is already listening') } const peerId = ma.getPeerId() @@ -327,7 +327,7 @@ export class TCPListener extends TypedEventEmitter implements Li } async close (): Promise { - const err = new CodeError('Listener is closing', 'ERR_LISTENER_CLOSING') + const err = new AbortError('Listener is closing') // synchronously close each connection this.connections.forEach(conn => { @@ -372,9 +372,9 @@ export class TCPListener extends TypedEventEmitter implements Li this.log('closing server on %s', this.server.address()) // NodeJS implementation tracks listening status with `this._handle` property. - // - Server.close() sets this._handle to null immediately. If this._handle is null, ERR_SERVER_NOT_RUNNING is thrown + // - Server.close() sets this._handle to null immediately. If this._handle is null, NotStartedError is thrown // - Server.listening returns `this._handle !== null` https://github.com/nodejs/node/blob/386d761943bb1b217fba27d6b80b658c23009e60/lib/net.js#L1675 - // - Server.listen() if `this._handle !== null` throws ERR_SERVER_ALREADY_LISTEN + // - Server.listen() if `this._handle !== null` throws AlreadyStartedError // // NOTE: Both listen and close are technically not async actions, so it's not necessary to track // states 'pending-close' or 'pending-listen' diff --git a/packages/transport-tcp/src/socket-to-conn.ts b/packages/transport-tcp/src/socket-to-conn.ts index 9221c6698c..3c8f971cba 100644 --- a/packages/transport-tcp/src/socket-to-conn.ts +++ b/packages/transport-tcp/src/socket-to-conn.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { AbortError, InvalidParametersError, TimeoutError } from '@libp2p/interface' import { ipPortToMultiaddr as toMultiaddr } from '@libp2p/utils/ip-port-to-multiaddr' import { duplex } from 'stream-to-it' import { CLOSE_TIMEOUT, SOCKET_TIMEOUT } from './constants.js' @@ -47,7 +47,7 @@ export const toMultiaddrConnection = (socket: Socket, options: ToConnectionOptio if (socket.remoteAddress == null || socket.remotePort == null) { // this can be undefined if the socket is destroyed (for example, if the client disconnected) // https://nodejs.org/dist/latest-v16.x/docs/api/net.html#socketremoteaddress - throw new CodeError('Could not determine remote address or port', 'ERR_NO_REMOTE_ADDRESS') + throw new InvalidParametersError('Could not determine remote address or port') } remoteAddr = toMultiaddr(socket.remoteAddress, socket.remotePort) @@ -66,7 +66,7 @@ export const toMultiaddrConnection = (socket: Socket, options: ToConnectionOptio // only destroy with an error if the remote has not sent the FIN message let err: Error | undefined if (socket.readable) { - err = new CodeError('Socket read timeout', 'ERR_SOCKET_READ_TIMEOUT') + err = new TimeoutError('Socket read timeout') } // if the socket times out due to inactivity we must manually close the connection @@ -147,7 +147,7 @@ export const toMultiaddrConnection = (socket: Socket, options: ToConnectionOptio } const abortSignalListener = (): void => { - socket.destroy(new CodeError('Destroying socket after timeout', 'ERR_CLOSE_TIMEOUT')) + socket.destroy(new AbortError('Destroying socket after timeout')) } options.signal?.addEventListener('abort', abortSignalListener) diff --git a/packages/transport-tcp/test/socket-to-conn.spec.ts b/packages/transport-tcp/test/socket-to-conn.spec.ts index 017d5d454a..4750533a32 100644 --- a/packages/transport-tcp/test/socket-to-conn.spec.ts +++ b/packages/transport-tcp/test/socket-to-conn.spec.ts @@ -146,7 +146,7 @@ describe('socket-to-conn', () => { // client closed the connection - error code is platform specific if (os.platform() === 'linux') { - await expect(serverErrored.promise).to.eventually.have.property('code', 'ERR_SOCKET_READ_TIMEOUT') + await expect(serverErrored.promise).to.eventually.have.property('name', 'TimeoutError') } else { await expect(serverErrored.promise).to.eventually.have.property('code', 'ECONNRESET') } @@ -199,7 +199,7 @@ describe('socket-to-conn', () => { await expect(serverClosed.promise).to.eventually.be.true() // remote stopped sending us data - await expect(serverErrored.promise).to.eventually.have.property('code', 'ERR_SOCKET_READ_TIMEOUT') + await expect(serverErrored.promise).to.eventually.have.property('name', 'TimeoutError') // the connection closing was recorded expect(inboundMaConn.timeline.close).to.be.a('number') @@ -243,7 +243,7 @@ describe('socket-to-conn', () => { await expect(serverClosed.promise).to.eventually.be.true() // remote didn't send us any data - await expect(serverErrored.promise).to.eventually.have.property('code', 'ERR_SOCKET_READ_TIMEOUT') + await expect(serverErrored.promise).to.eventually.have.property('name', 'TimeoutError') // the connection closing was recorded expect(inboundMaConn.timeline.close).to.be.a('number') @@ -479,7 +479,7 @@ describe('socket-to-conn', () => { await expect(serverClosed.promise).to.eventually.be.true() // remote didn't read our data - await expect(serverErrored.promise).to.eventually.have.property('code', 'ERR_CLOSE_TIMEOUT') + await expect(serverErrored.promise).to.eventually.have.property('name', 'AbortError') // the connection closing was recorded expect(inboundMaConn.timeline.close).to.be.a('number') diff --git a/packages/transport-webrtc/src/error.ts b/packages/transport-webrtc/src/error.ts index cde25484aa..a49e5771a0 100644 --- a/packages/transport-webrtc/src/error.ts +++ b/packages/transport-webrtc/src/error.ts @@ -1,122 +1,76 @@ -import { CodeError } from '@libp2p/interface' -import type { Direction } from '@libp2p/interface' - -export enum codes { - ERR_ALREADY_ABORTED = 'ERR_ALREADY_ABORTED', - ERR_DATA_CHANNEL = 'ERR_DATA_CHANNEL', - ERR_CONNECTION_CLOSED = 'ERR_CONNECTION_CLOSED', - ERR_HASH_NOT_SUPPORTED = 'ERR_HASH_NOT_SUPPORTED', - ERR_INVALID_MULTIADDR = 'ERR_INVALID_MULTIADDR', - ERR_INVALID_FINGERPRINT = 'ERR_INVALID_FINGERPRINT', - ERR_INVALID_PARAMETERS = 'ERR_INVALID_PARAMETERS', - ERR_NOT_IMPLEMENTED = 'ERR_NOT_IMPLEMENTED', - ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS = 'ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS', - ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS = 'ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS', +export class WebRTCTransportError extends Error { + constructor (msg: string) { + super(`WebRTC transport error: ${msg}`) + this.name = 'WebRTCTransportError' + } } -export class WebRTCTransportError extends CodeError { - constructor (msg: string, code?: string) { - super(`WebRTC transport error: ${msg}`, code ?? '') - this.name = 'WebRTCTransportError' +export class SDPHandshakeFailedError extends WebRTCTransportError { + constructor (message = 'SDP handshake failed') { + super(message) + this.name = 'SDPHandshakeFailedError' } } export class ConnectionClosedError extends WebRTCTransportError { constructor (state: RTCPeerConnectionState, msg: string) { - super(`peerconnection moved to state: ${state}: ${msg}`, codes.ERR_CONNECTION_CLOSED) + super(`peerconnection moved to state: ${state}: ${msg}`) this.name = 'WebRTC/ConnectionClosed' } } -export function connectionClosedError (state: RTCPeerConnectionState, msg: string): ConnectionClosedError { - return new ConnectionClosedError(state, msg) -} - export class DataChannelError extends WebRTCTransportError { constructor (streamLabel: string, msg: string) { - super(`[stream: ${streamLabel}] data channel error: ${msg}`, codes.ERR_DATA_CHANNEL) + super(`[stream: ${streamLabel}] data channel error: ${msg}`) this.name = 'WebRTC/DataChannelError' } } -export function dataChannelError (streamLabel: string, msg: string): DataChannelError { - return new DataChannelError(streamLabel, msg) -} - export class InappropriateMultiaddrError extends WebRTCTransportError { constructor (msg: string) { - super(`There was a problem with the Multiaddr which was passed in: ${msg}`, codes.ERR_INVALID_MULTIADDR) + super(`There was a problem with the Multiaddr which was passed in: ${msg}`) this.name = 'WebRTC/InappropriateMultiaddrError' } } -export function inappropriateMultiaddr (msg: string): InappropriateMultiaddrError { - return new InappropriateMultiaddrError(msg) -} - export class InvalidArgumentError extends WebRTCTransportError { constructor (msg: string) { - super(`There was a problem with a provided argument: ${msg}`, codes.ERR_INVALID_PARAMETERS) + super(`There was a problem with a provided argument: ${msg}`) this.name = 'WebRTC/InvalidArgumentError' } } -export function invalidArgument (msg: string): InvalidArgumentError { - return new InvalidArgumentError(msg) -} - export class InvalidFingerprintError extends WebRTCTransportError { constructor (fingerprint: string, source: string) { - super(`Invalid fingerprint "${fingerprint}" within ${source}`, codes.ERR_INVALID_FINGERPRINT) + super(`Invalid fingerprint "${fingerprint}" within ${source}`) this.name = 'WebRTC/InvalidFingerprintError' } } -export function invalidFingerprint (fingerprint: string, source: string): InvalidFingerprintError { - return new InvalidFingerprintError(fingerprint, source) -} - export class OperationAbortedError extends WebRTCTransportError { constructor (context: string, abortReason: string) { - super(`Signalled to abort because (${abortReason}}) ${context}`, codes.ERR_ALREADY_ABORTED) + super(`Signalled to abort because (${abortReason}}) ${context}`) this.name = 'WebRTC/OperationAbortedError' } } -export function operationAborted (context: string, reason: string): OperationAbortedError { - return new OperationAbortedError(context, reason) -} - export class OverStreamLimitError extends WebRTCTransportError { constructor (msg: string) { - const code = msg.startsWith('inbound') ? codes.ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS : codes.ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS - super(msg, code) + super(msg) this.name = 'WebRTC/OverStreamLimitError' } } -export function overStreamLimit (dir: Direction, proto: string): OverStreamLimitError { - return new OverStreamLimitError(`${dir} stream limit reached for protocol - ${proto}`) -} - export class UnimplementedError extends WebRTCTransportError { constructor (methodName: string) { - super(`A method (${methodName}) was called though it has been intentionally left unimplemented.`, codes.ERR_NOT_IMPLEMENTED) + super(`A method (${methodName}) was called though it has been intentionally left unimplemented.`) this.name = 'WebRTC/UnimplementedError' } } -export function unimplemented (methodName: string): UnimplementedError { - return new UnimplementedError(methodName) -} - export class UnsupportedHashAlgorithmError extends WebRTCTransportError { constructor (algo: number) { - super(`unsupported hash algorithm code: ${algo} please see the codes at https://github.com/multiformats/multicodec/blob/master/table.csv `, codes.ERR_HASH_NOT_SUPPORTED) + super(`unsupported hash algorithm code: ${algo} please see the codes at https://github.com/multiformats/multicodec/blob/master/table.csv `) this.name = 'WebRTC/UnsupportedHashAlgorithmError' } } - -export function unsupportedHashAlgorithmCode (code: number): UnsupportedHashAlgorithmError { - return new UnsupportedHashAlgorithmError(code) -} diff --git a/packages/transport-webrtc/src/pb/message.ts b/packages/transport-webrtc/src/pb/message.ts index f8abb7a4a9..bf5c902bb3 100644 --- a/packages/transport-webrtc/src/pb/message.ts +++ b/packages/transport-webrtc/src/pb/message.ts @@ -4,8 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { enumeration, encodeMessage, decodeMessage, message } from 'protons-runtime' -import type { Codec } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, enumeration, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export interface Message { @@ -56,7 +55,7 @@ export namespace Message { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -65,15 +64,18 @@ export namespace Message { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.flag = Message.Flag.codec().decode(reader) break - case 2: + } + case 2: { obj.message = reader.bytes() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -88,7 +90,7 @@ export namespace Message { return encodeMessage(obj, Message.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Message => { - return decodeMessage(buf, Message.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Message => { + return decodeMessage(buf, Message.codec(), opts) } } diff --git a/packages/transport-webrtc/src/private-to-private/initiate-connection.ts b/packages/transport-webrtc/src/private-to-private/initiate-connection.ts index 167b1f5110..a4494c4a7e 100644 --- a/packages/transport-webrtc/src/private-to-private/initiate-connection.ts +++ b/packages/transport-webrtc/src/private-to-private/initiate-connection.ts @@ -1,7 +1,8 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidParametersError } from '@libp2p/interface' import { peerIdFromString } from '@libp2p/peer-id' import { pbStream } from 'it-protobuf-stream' import { CustomProgressEvent } from 'progress-events' +import { SDPHandshakeFailedError } from '../error.js' import { DataChannelMuxerFactory } from '../muxer.js' import { RTCPeerConnection, RTCSessionDescription } from '../webrtc/index.js' import { Message } from './pb/message.js' @@ -41,7 +42,7 @@ export async function initiateConnection ({ rtcConfiguration, dataChannel, signa const relayPeer = baseAddr.getPeerId() if (relayPeer == null) { - throw new CodeError('Relay peer was missing', 'ERR_INVALID_ADDRESS') + throw new InvalidParametersError('Relay peer was missing') } const connections = connectionManager.getConnections(peerIdFromString(relayPeer)) @@ -117,7 +118,7 @@ export async function initiateConnection ({ rtcConfiguration, dataChannel, signa // create an offer const offerSdp = await peerConnection.createOffer().catch(err => { log.error('could not execute createOffer', err) - throw new CodeError('Failed to set createOffer', 'ERR_SDP_HANDSHAKE_FAILED') + throw new SDPHandshakeFailedError('Failed to set createOffer') }) log.trace('initiator send SDP offer %s', offerSdp.sdp) @@ -132,7 +133,7 @@ export async function initiateConnection ({ rtcConfiguration, dataChannel, signa // set offer as local description await peerConnection.setLocalDescription(offerSdp).catch(err => { log.error('could not execute setLocalDescription', err) - throw new CodeError('Failed to set localDescription', 'ERR_SDP_HANDSHAKE_FAILED') + throw new SDPHandshakeFailedError('Failed to set localDescription') }) onProgress?.(new CustomProgressEvent('webrtc:read-sdp-answer')) @@ -143,7 +144,7 @@ export async function initiateConnection ({ rtcConfiguration, dataChannel, signa }) if (answerMessage.type !== Message.Type.SDP_ANSWER) { - throw new CodeError('Remote should send an SDP answer', 'ERR_SDP_HANDSHAKE_FAILED') + throw new SDPHandshakeFailedError('Remote should send an SDP answer') } log.trace('initiator receive SDP answer %s', answerMessage.data) @@ -151,7 +152,7 @@ export async function initiateConnection ({ rtcConfiguration, dataChannel, signa const answerSdp = new RTCSessionDescription({ type: 'answer', sdp: answerMessage.data }) await peerConnection.setRemoteDescription(answerSdp).catch(err => { log.error('could not execute setRemoteDescription', err) - throw new CodeError('Failed to set remoteDescription', 'ERR_SDP_HANDSHAKE_FAILED') + throw new SDPHandshakeFailedError('Failed to set remoteDescription') }) log.trace('initiator read candidates until connected') diff --git a/packages/transport-webrtc/src/private-to-private/pb/message.ts b/packages/transport-webrtc/src/private-to-private/pb/message.ts index b0824ed042..97cbf99f34 100644 --- a/packages/transport-webrtc/src/private-to-private/pb/message.ts +++ b/packages/transport-webrtc/src/private-to-private/pb/message.ts @@ -4,8 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { enumeration, encodeMessage, decodeMessage, message } from 'protons-runtime' -import type { Codec } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, enumeration, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export interface Message { @@ -54,7 +53,7 @@ export namespace Message { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -63,15 +62,18 @@ export namespace Message { const tag = reader.uint32() switch (tag >>> 3) { - case 1: + case 1: { obj.type = Message.Type.codec().decode(reader) break - case 2: + } + case 2: { obj.data = reader.string() break - default: + } + default: { reader.skipType(tag & 7) break + } } } @@ -86,7 +88,7 @@ export namespace Message { return encodeMessage(obj, Message.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Message => { - return decodeMessage(buf, Message.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Message => { + return decodeMessage(buf, Message.codec(), opts) } } diff --git a/packages/transport-webrtc/src/private-to-private/signaling-stream-handler.ts b/packages/transport-webrtc/src/private-to-private/signaling-stream-handler.ts index 945b105d70..067693dc5f 100644 --- a/packages/transport-webrtc/src/private-to-private/signaling-stream-handler.ts +++ b/packages/transport-webrtc/src/private-to-private/signaling-stream-handler.ts @@ -1,6 +1,6 @@ -import { CodeError } from '@libp2p/interface' import { multiaddr, type Multiaddr } from '@multiformats/multiaddr' import { pbStream } from 'it-protobuf-stream' +import { SDPHandshakeFailedError } from '../error.js' import { type RTCPeerConnection, RTCSessionDescription } from '../webrtc/index.js' import { Message } from './pb/message.js' import { readCandidatesUntilConnected } from './util.js' @@ -46,7 +46,7 @@ export async function handleIncomingStream ({ peerConnection, stream, signal, co }) if (pbOffer.type !== Message.Type.SDP_OFFER) { - throw new CodeError(`expected message type SDP_OFFER, received: ${pbOffer.type ?? 'undefined'} `, 'ERR_SDP_HANDSHAKE_FAILED') + throw new SDPHandshakeFailedError(`expected message type SDP_OFFER, received: ${pbOffer.type ?? 'undefined'} `) } log.trace('recipient receive SDP offer %s', pbOffer.data) @@ -58,13 +58,13 @@ export async function handleIncomingStream ({ peerConnection, stream, signal, co await peerConnection.setRemoteDescription(offer).catch(err => { log.error('could not execute setRemoteDescription', err) - throw new CodeError('Failed to set remoteDescription', 'ERR_SDP_HANDSHAKE_FAILED') + throw new SDPHandshakeFailedError('Failed to set remoteDescription') }) // create and write an SDP answer const answer = await peerConnection.createAnswer().catch(err => { log.error('could not execute createAnswer', err) - throw new CodeError('Failed to create answer', 'ERR_SDP_HANDSHAKE_FAILED') + throw new SDPHandshakeFailedError('Failed to create answer') }) log.trace('recipient send SDP answer %s', answer.sdp) @@ -76,7 +76,7 @@ export async function handleIncomingStream ({ peerConnection, stream, signal, co await peerConnection.setLocalDescription(answer).catch(err => { log.error('could not execute setLocalDescription', err) - throw new CodeError('Failed to set localDescription', 'ERR_SDP_HANDSHAKE_FAILED') + throw new SDPHandshakeFailedError('Failed to set localDescription') }) log.trace('recipient read candidates until connected') diff --git a/packages/transport-webrtc/src/private-to-private/transport.ts b/packages/transport-webrtc/src/private-to-private/transport.ts index b12095b48e..e6cf91d851 100644 --- a/packages/transport-webrtc/src/private-to-private/transport.ts +++ b/packages/transport-webrtc/src/private-to-private/transport.ts @@ -1,8 +1,7 @@ -import { CodeError, serviceCapabilities, serviceDependencies, setMaxListeners, transportSymbol } from '@libp2p/interface' +import { InvalidParametersError, serviceCapabilities, serviceDependencies, setMaxListeners, transportSymbol } from '@libp2p/interface' import { peerIdFromString } from '@libp2p/peer-id' import { multiaddr, type Multiaddr } from '@multiformats/multiaddr' import { WebRTC } from '@multiformats/multiaddr-matcher' -import { codes } from '../error.js' import { WebRTCMultiaddrConnection } from '../maconn.js' import { DataChannelMuxerFactory } from '../muxer.js' import { getRtcConfiguration } from '../util.js' @@ -247,11 +246,11 @@ export class WebRTCTransport implements Transport, Startable { export function splitAddr (ma: Multiaddr): { baseAddr: Multiaddr, peerId: PeerId } { const addrs = ma.toString().split(WEBRTC_TRANSPORT + '/') if (addrs.length !== 2) { - throw new CodeError('webrtc protocol was not present in multiaddr', codes.ERR_INVALID_MULTIADDR) + throw new InvalidParametersError('webrtc protocol was not present in multiaddr') } if (!addrs[0].includes(CIRCUIT_RELAY_TRANSPORT)) { - throw new CodeError('p2p-circuit protocol was not present in multiaddr', codes.ERR_INVALID_MULTIADDR) + throw new InvalidParametersError('p2p-circuit protocol was not present in multiaddr') } // look for remote peerId @@ -260,12 +259,12 @@ export function splitAddr (ma: Multiaddr): { baseAddr: Multiaddr, peerId: PeerId const destinationIdString = destination.getPeerId() if (destinationIdString == null) { - throw new CodeError('destination peer id was missing', codes.ERR_INVALID_MULTIADDR) + throw new InvalidParametersError('destination peer id was missing') } const lastProtoInRemote = remoteAddr.protos().pop() if (lastProtoInRemote === undefined) { - throw new CodeError('invalid multiaddr', codes.ERR_INVALID_MULTIADDR) + throw new InvalidParametersError('invalid multiaddr') } if (lastProtoInRemote.name !== 'p2p') { remoteAddr = remoteAddr.encapsulate(`/p2p/${destinationIdString}`) diff --git a/packages/transport-webrtc/src/private-to-private/util.ts b/packages/transport-webrtc/src/private-to-private/util.ts index 0516de5d5a..13b4828a0a 100644 --- a/packages/transport-webrtc/src/private-to-private/util.ts +++ b/packages/transport-webrtc/src/private-to-private/util.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { ConnectionFailedError, InvalidMessageError } from '@libp2p/interface' import pDefer from 'p-defer' import { CustomProgressEvent } from 'progress-events' import { isFirefox } from '../util.js' @@ -38,7 +38,7 @@ export const readCandidatesUntilConnected = async (pc: RTCPeerConnection, stream } if (message.type !== Message.Type.ICE_CANDIDATE) { - throw new CodeError('ICE candidate message expected', 'ERR_NOT_ICE_CANDIDATE') + throw new InvalidMessageError('ICE candidate message expected') } const candidateInit = JSON.parse(message.data ?? 'null') @@ -86,7 +86,7 @@ function resolveOnConnected (pc: RTCPeerConnection, promise: DeferredPromise tup[0] === CERTHASH_CODE).map((tup) => tup[1])[0] if (certhash === undefined || certhash === '') { - throw inappropriateMultiaddr(`Couldn't find a certhash component of multiaddr: ${ma.toString()}`) + throw new InvalidParametersError(`Couldn't find a certhash component of multiaddr: ${ma.toString()}`) } return certhash @@ -86,7 +87,7 @@ export function ma2Fingerprint (ma: Multiaddr): string[] { const sdp = fingerprint.match(/.{1,2}/g) if (sdp == null) { - throw invalidFingerprint(fingerprint, ma.toString()) + throw new InvalidFingerprintError(fingerprint, ma.toString()) } return [`${prefix} ${sdp.join(':').toUpperCase()}`, fingerprint] @@ -104,7 +105,7 @@ export function toSupportedHashFunction (code: number): 'SHA-1' | 'SHA-256' | 'S case 0x13: return 'SHA-512' default: - throw unsupportedHashAlgorithmCode(code) + throw new UnsupportedHashAlgorithmError(code) } } @@ -148,7 +149,7 @@ export function fromMultiAddr (ma: Multiaddr, ufrag: string): RTCSessionDescript */ export function munge (desc: RTCSessionDescriptionInit, ufrag: string): RTCSessionDescriptionInit { if (desc.sdp === undefined) { - throw invalidArgument("Can't munge a missing SDP") + throw new InvalidParametersError("Can't munge a missing SDP") } desc.sdp = desc.sdp diff --git a/packages/transport-webrtc/src/private-to-public/transport.ts b/packages/transport-webrtc/src/private-to-public/transport.ts index b5bf39ea87..d10b926cbc 100644 --- a/packages/transport-webrtc/src/private-to-public/transport.ts +++ b/packages/transport-webrtc/src/private-to-public/transport.ts @@ -1,12 +1,12 @@ import { noise } from '@chainsafe/libp2p-noise' -import { transportSymbol, serviceCapabilities } from '@libp2p/interface' +import { transportSymbol, serviceCapabilities, InvalidParametersError } from '@libp2p/interface' import * as p from '@libp2p/peer-id' import { protocols } from '@multiformats/multiaddr' import { WebRTCDirect } from '@multiformats/multiaddr-matcher' import * as Digest from 'multiformats/hashes/digest' import { concat } from 'uint8arrays/concat' import { fromString as uint8arrayFromString } from 'uint8arrays/from-string' -import { dataChannelError, inappropriateMultiaddr, unimplemented, invalidArgument } from '../error.js' +import { DataChannelError, InappropriateMultiaddrError, UnimplementedError } from '../error.js' import { WebRTCMultiaddrConnection } from '../maconn.js' import { DataChannelMuxerFactory } from '../muxer.js' import { createStream } from '../stream.js' @@ -96,7 +96,7 @@ export class WebRTCDirectTransport implements Transport { * Create transport listeners no supported by browsers */ createListener (options: CreateListenerOptions): Listener { - throw unimplemented('WebRTCTransport.createListener') + throw new UnimplementedError('WebRTCTransport.createListener') } /** @@ -122,7 +122,7 @@ export class WebRTCDirectTransport implements Transport { const remotePeerString = ma.getPeerId() if (remotePeerString === null) { - throw inappropriateMultiaddr("we need to have the remote's PeerId") + throw new InappropriateMultiaddrError("we need to have the remote's PeerId") } const theirPeerId = p.peerIdFromString(remotePeerString) @@ -153,7 +153,7 @@ export class WebRTCDirectTransport implements Transport { const error = `Data channel was never opened: state: ${handshakeDataChannel.readyState}` this.log.error(error) this.metrics?.dialerEvents.increment({ open_error: true }) - reject(dataChannelError('data', error)) + reject(new DataChannelError('data', error)) }, HANDSHAKE_TIMEOUT_MS) handshakeDataChannel.onopen = (_) => { @@ -169,7 +169,7 @@ export class WebRTCDirectTransport implements Transport { this.log.error(error) // NOTE: We use unknown error here but this could potentially be considered a reset by some standards. this.metrics?.dialerEvents.increment({ unknown_error: true }) - reject(dataChannelError('data', error)) + reject(new DataChannelError('data', error)) } }) @@ -273,14 +273,14 @@ export class WebRTCDirectTransport implements Transport { */ private generateNoisePrologue (pc: RTCPeerConnection, hashCode: number, ma: Multiaddr): Uint8Array { if (pc.getConfiguration().certificates?.length === 0) { - throw invalidArgument('no local certificate') + throw new InvalidParametersError('no local certificate') } const localFingerprint = sdp.getLocalFingerprint(pc, { log: this.log }) if (localFingerprint == null) { - throw invalidArgument('no local fingerprint found') + throw new InvalidParametersError('no local fingerprint found') } const localFpString = localFingerprint.trim().toLowerCase().replaceAll(':', '') diff --git a/packages/transport-webrtc/src/stream.ts b/packages/transport-webrtc/src/stream.ts index e7433255ab..694b9ba7ac 100644 --- a/packages/transport-webrtc/src/stream.ts +++ b/packages/transport-webrtc/src/stream.ts @@ -1,9 +1,9 @@ -import { CodeError } from '@libp2p/interface' +import { StreamStateError, TimeoutError } from '@libp2p/interface' import { AbstractStream, type AbstractStreamInit } from '@libp2p/utils/abstract-stream' import * as lengthPrefixed from 'it-length-prefixed' import { type Pushable, pushable } from 'it-pushable' import pDefer from 'p-defer' -import { pEvent, TimeoutError } from 'p-event' +import { pEvent } from 'p-event' import pTimeout from 'p-timeout' import { raceSignal } from 'race-signal' import { encodingLength } from 'uint8-varint' @@ -174,7 +174,7 @@ export class WebRTCStream extends AbstractStream { default: this.log.error('unknown datachannel state %s', this.channel.readyState) - throw new CodeError('Unknown datachannel state', 'ERR_INVALID_STATE') + throw new StreamStateError('Unknown datachannel state') } // handle RTCDataChannel events @@ -236,7 +236,7 @@ export class WebRTCStream extends AbstractStream { await pEvent(this.channel, 'bufferedamountlow', { timeout: this.bufferedAmountLowEventTimeout }) } catch (err: any) { if (err instanceof TimeoutError) { - throw new CodeError(`Timed out waiting for DataChannel buffer to clear after ${this.bufferedAmountLowEventTimeout}ms`, 'ERR_BUFFER_CLEAR_TIMEOUT') + throw new TimeoutError(`Timed out waiting for DataChannel buffer to clear after ${this.bufferedAmountLowEventTimeout}ms`) } throw err @@ -244,7 +244,7 @@ export class WebRTCStream extends AbstractStream { } if (this.channel.readyState === 'closed' || this.channel.readyState === 'closing') { - throw new CodeError(`Invalid datachannel state - ${this.channel.readyState}`, 'ERR_INVALID_STATE') + throw new StreamStateError(`Invalid datachannel state - ${this.channel.readyState}`) } if (this.channel.readyState !== 'open') { @@ -285,7 +285,7 @@ export class WebRTCStream extends AbstractStream { try { await raceSignal(this.receiveFinAck.promise, options?.signal, { errorMessage: 'sending close-write was aborted before FIN_ACK was received', - errorCode: 'ERR_FIN_ACK_NOT_RECEIVED' + errorName: 'FinAckNotReceivedError' }) } catch (err) { this.log.error('failed to await FIN_ACK', err) diff --git a/packages/transport-webrtc/test/stream.spec.ts b/packages/transport-webrtc/test/stream.spec.ts index 7460cfeef6..8b8c939746 100644 --- a/packages/transport-webrtc/test/stream.spec.ts +++ b/packages/transport-webrtc/test/stream.spec.ts @@ -107,7 +107,7 @@ describe('Max message size', () => { const t0 = Date.now() await expect(webrtcStream.sink([new Uint8Array(1)])).to.eventually.be.rejected - .with.property('code', 'ERR_BUFFER_CLEAR_TIMEOUT') + .with.property('name', 'TimeoutError') const t1 = Date.now() expect(t1 - t0).greaterThanOrEqual(timeout) expect(t1 - t0).lessThan(timeout + 1000) // Some upper bound diff --git a/packages/transport-webrtc/test/transport.spec.ts b/packages/transport-webrtc/test/transport.spec.ts index f28ac2cc58..11cea71488 100644 --- a/packages/transport-webrtc/test/transport.spec.ts +++ b/packages/transport-webrtc/test/transport.spec.ts @@ -88,7 +88,7 @@ describe('WebRTCDirect Transport', () => { try { await transport.dial(ma, ignoredDialOption()) - } catch (error) { + } catch (error: any) { const expected = 'WebRTC transport error: There was a problem with the Multiaddr which was passed in: we need to have the remote\'s PeerId' expectError(error, expected) } diff --git a/packages/transport-websockets/src/index.ts b/packages/transport-websockets/src/index.ts index 18cf573c9a..f18ffb5961 100644 --- a/packages/transport-websockets/src/index.ts +++ b/packages/transport-websockets/src/index.ts @@ -57,7 +57,7 @@ * ``` */ -import { CodeError, transportSymbol, serviceCapabilities } from '@libp2p/interface' +import { transportSymbol, serviceCapabilities, ConnectionFailedError } from '@libp2p/interface' import { multiaddrToUri as toUri } from '@multiformats/multiaddr-to-uri' import { connect, type WebSocketOptions } from 'it-ws/client' import pDefer from 'p-defer' @@ -152,7 +152,7 @@ class WebSockets implements Transport { // the WebSocket.ErrorEvent type doesn't actually give us any useful // information about what happened // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/error_event - const err = new CodeError(`Could not connect to ${ma.toString()}`, 'ERR_CONNECTION_FAILED') + const err = new ConnectionFailedError(`Could not connect to ${ma.toString()}`) this.log.error('connection error:', err) this.metrics?.dialerEvents.increment({ error: true }) errorPromise.reject(err) diff --git a/packages/transport-websockets/src/socket-to-conn.ts b/packages/transport-websockets/src/socket-to-conn.ts index eb98160c65..603e24cf51 100644 --- a/packages/transport-websockets/src/socket-to-conn.ts +++ b/packages/transport-websockets/src/socket-to-conn.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { AbortError } from '@libp2p/interface' import { CLOSE_TIMEOUT } from './constants.js' import type { AbortOptions, ComponentLogger, CounterGroup, MultiaddrConnection } from '@libp2p/interface' import type { Multiaddr } from '@multiformats/multiaddr' @@ -62,7 +62,7 @@ export function socketToMaConn (stream: DuplexWebSocket, remoteAddr: Multiaddr, log('timeout closing stream to %s:%s after %dms, destroying it manually', host, port, Date.now() - start) - this.abort(new CodeError('Socket close timeout', 'ERR_SOCKET_CLOSE_TIMEOUT')) + this.abort(new AbortError('Socket close timeout')) } options.signal?.addEventListener('abort', listener) diff --git a/packages/transport-webtransport/src/index.ts b/packages/transport-webtransport/src/index.ts index a75c3d8dc2..d87852eca5 100644 --- a/packages/transport-webtransport/src/index.ts +++ b/packages/transport-webtransport/src/index.ts @@ -30,7 +30,7 @@ */ import { noise } from '@chainsafe/libp2p-noise' -import { AbortError, CodeError, serviceCapabilities, transportSymbol } from '@libp2p/interface' +import { InvalidCryptoExchangeError, InvalidParametersError, serviceCapabilities, transportSymbol } from '@libp2p/interface' import { WebTransport as WebTransportMatcher } from '@multiformats/multiaddr-matcher' import { CustomProgressEvent } from 'progress-events' import { raceSignal } from 'race-signal' @@ -123,9 +123,7 @@ class WebTransportTransport implements Transport { ] async dial (ma: Multiaddr, options: DialTransportOptions): Promise { - if (options?.signal?.aborted === true) { - throw new AbortError() - } + options?.signal?.throwIfAborted() this.log('dialing %s', ma) @@ -204,7 +202,7 @@ class WebTransportTransport implements Transport { authenticated = await raceSignal(this.authenticateWebTransport({ wt, remotePeer, certhashes, ...options }), options.signal) if (!authenticated) { - throw new CodeError('Failed to authenticate webtransport', 'ERR_AUTHENTICATION_FAILED') + throw new InvalidCryptoExchangeError('Failed to authenticate webtransport') } this.metrics?.dialerEvents.increment({ open: true }) @@ -304,7 +302,7 @@ class WebTransportTransport implements Transport { // Verify the certhashes we used when dialing are a subset of the certhashes relayed by the remote peer if (!isSubset(remoteExtensions?.webtransportCerthashes ?? [], certhashes.map(ch => ch.bytes))) { - throw new Error("Our certhashes are not a subset of the remote's reported certhashes") + throw new InvalidParametersError("Our certhashes are not a subset of the remote's reported certhashes") } return true diff --git a/packages/transport-webtransport/src/utils/parse-multiaddr.ts b/packages/transport-webtransport/src/utils/parse-multiaddr.ts index 6788828ead..cfe4f64046 100644 --- a/packages/transport-webtransport/src/utils/parse-multiaddr.ts +++ b/packages/transport-webtransport/src/utils/parse-multiaddr.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidMultiaddrError } from '@libp2p/interface' import { peerIdFromString } from '@libp2p/peer-id' import { type Multiaddr, protocols } from '@multiformats/multiaddr' import { WebTransport } from '@multiformats/multiaddr-matcher' @@ -21,7 +21,7 @@ export interface ParsedMultiaddr { export function parseMultiaddr (ma: Multiaddr): ParsedMultiaddr { if (!WebTransport.matches(ma)) { - throw new CodeError('Invalid multiaddr, was not a WebTransport address', 'ERR_INVALID_MULTIADDR') + throw new InvalidMultiaddrError('Invalid multiaddr, was not a WebTransport address') } const parts = ma.stringTuples() diff --git a/packages/transport-webtransport/test/browser.ts b/packages/transport-webtransport/test/browser.ts index 7da82aee05..b2c7e10691 100644 --- a/packages/transport-webtransport/test/browser.ts +++ b/packages/transport-webtransport/test/browser.ts @@ -66,7 +66,7 @@ describe('libp2p-webtransport', () => { const ma = multiaddr(maStrNoCerthash + '/p2p/' + maStrP2p) await expect(node.dial(ma)).to.eventually.be.rejected() - .with.property('code', 'ERR_NO_VALID_ADDRESSES') + .with.property('name', 'NoValidAddressesError') }) it('fails to connect due to an aborted signal', async () => { diff --git a/packages/transport-webtransport/test/fixtures/random-bytes.ts b/packages/transport-webtransport/test/fixtures/random-bytes.ts index c34a09a5b4..f7339ae8c5 100644 --- a/packages/transport-webtransport/test/fixtures/random-bytes.ts +++ b/packages/transport-webtransport/test/fixtures/random-bytes.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { InvalidParametersError } from '@libp2p/interface' import { randomBytes as randB } from '@noble/hashes/utils' /** @@ -6,7 +6,8 @@ import { randomBytes as randB } from '@noble/hashes/utils' */ export function randomBytes (length: number): Uint8Array { if (isNaN(length) || length <= 0) { - throw new CodeError('random bytes length must be a Number bigger than 0', 'ERR_INVALID_LENGTH') + throw new InvalidParametersError('random bytes length must be a Number bigger than 0') } + return randB(length) } diff --git a/packages/transport-webtransport/test/utils/parse-multiaddr.spec.ts b/packages/transport-webtransport/test/utils/parse-multiaddr.spec.ts index e390f80168..c2eaacb3bd 100644 --- a/packages/transport-webtransport/test/utils/parse-multiaddr.spec.ts +++ b/packages/transport-webtransport/test/utils/parse-multiaddr.spec.ts @@ -59,7 +59,7 @@ describe('parse multiaddr', () => { const ma = multiaddr(`/ip4/123.123.123.123/udp/4001/p2p/${targetPeer}`) expect(() => parseMultiaddr(ma)).to.throw() - .with.property('code', 'ERR_INVALID_MULTIADDR') + .with.property('name', 'InvalidMultiaddrError') }) it('fails to parse a webtransport address without certhashes', () => { @@ -67,7 +67,7 @@ describe('parse multiaddr', () => { const ma = multiaddr(`/ip4/123.123.123.123/udp/4001/webtransport/p2p/${targetPeer}`) expect(() => parseMultiaddr(ma)).to.throw() - .with.property('code', 'ERR_INVALID_MULTIADDR') + .with.property('name', 'InvalidMultiaddrError') }) }) }) diff --git a/packages/upnp-nat/src/errors.ts b/packages/upnp-nat/src/errors.ts new file mode 100644 index 0000000000..1ab9ce7562 --- /dev/null +++ b/packages/upnp-nat/src/errors.ts @@ -0,0 +1,6 @@ +export class DoubleNATError extends Error { + constructor (message = 'Double NAT detected') { + super(message) + this.name = 'DoubleNATError' + } +} diff --git a/packages/upnp-nat/src/upnp-nat.ts b/packages/upnp-nat/src/upnp-nat.ts index d56d0c99d9..e15091261a 100644 --- a/packages/upnp-nat/src/upnp-nat.ts +++ b/packages/upnp-nat/src/upnp-nat.ts @@ -1,9 +1,10 @@ import { upnpNat, type NatAPI, type MapPortOptions } from '@achingbrain/nat-port-mapper' -import { CodeError, ERR_INVALID_PARAMETERS, serviceCapabilities } from '@libp2p/interface' +import { InvalidParametersError, serviceCapabilities } from '@libp2p/interface' import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback' import { isPrivateIp } from '@libp2p/utils/private-ip' import { fromNodeAddress } from '@multiformats/multiaddr' import { isBrowser } from 'wherearewe' +import { DoubleNATError } from './errors.js' import type { UPnPNATComponents, UPnPNATInit, UPnPNAT as UPnPNATInterface } from './index.js' import type { Logger, Startable } from '@libp2p/interface' @@ -40,7 +41,7 @@ export class UPnPNAT implements Startable, UPnPNATInterface { this.gateway = init.gateway if (this.ttl < DEFAULT_TTL) { - throw new CodeError(`NatManager ttl should be at least ${DEFAULT_TTL} seconds`, ERR_INVALID_PARAMETERS) + throw new InvalidParametersError(`NatManager ttl should be at least ${DEFAULT_TTL} seconds`) } this.client = upnpNat({ @@ -112,11 +113,11 @@ export class UPnPNAT implements Startable, UPnPNATInterface { const isPrivate = isPrivateIp(publicIp) if (isPrivate === true) { - throw new CodeError(`${publicIp} is private - please set config.nat.externalIp to an externally routable IP or ensure you are not behind a double NAT`, 'ERR_DOUBLE_NAT') + throw new DoubleNATError(`${publicIp} is private - please set config.nat.externalIp to an externally routable IP or ensure you are not behind a double NAT`) } if (isPrivate == null) { - throw new CodeError(`${publicIp} is not an IP address`, ERR_INVALID_PARAMETERS) + throw new InvalidParametersError(`${publicIp} is not an IP address`) } const publicPort = highPort() diff --git a/packages/upnp-nat/test/index.spec.ts b/packages/upnp-nat/test/index.spec.ts index 43ad25badc..c622a28114 100644 --- a/packages/upnp-nat/test/index.spec.ts +++ b/packages/upnp-nat/test/index.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import { ERR_INVALID_PARAMETERS, stop } from '@libp2p/interface' +import { stop } from '@libp2p/interface' import { defaultLogger } from '@libp2p/logger' import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { multiaddr } from '@multiformats/multiaddr' @@ -90,7 +90,7 @@ describe('UPnP NAT (TCP)', () => { ]) await expect(natManager.mapIpAddresses()).to.eventually.be.rejected - .with.property('code', 'ERR_DOUBLE_NAT') + .with.property('name', 'DoubleNATError') expect(client.map.called).to.be.false() expect(components.addressManager.addObservedAddr.called).to.be.false() @@ -188,6 +188,6 @@ describe('UPnP NAT (TCP)', () => { it('should specify large enough TTL', async () => { await expect(createNatManager({ ttl: 5, keepAlive: true })).to.eventually.be.rejected - .with.property('code', ERR_INVALID_PARAMETERS) + .with.property('name', 'InvalidParametersError') }) }) diff --git a/packages/utils/src/abstract-stream.ts b/packages/utils/src/abstract-stream.ts index 5cea6999f6..b5971c8a71 100644 --- a/packages/utils/src/abstract-stream.ts +++ b/packages/utils/src/abstract-stream.ts @@ -1,4 +1,4 @@ -import { CodeError } from '@libp2p/interface' +import { StreamResetError, StreamStateError } from '@libp2p/interface' import { type Pushable, pushable } from 'it-pushable' import defer, { type DeferredPromise } from 'p-defer' import pDefer from 'p-defer' @@ -9,8 +9,6 @@ import type { AbortOptions, Direction, ReadStatus, Stream, StreamStatus, StreamT import type { Logger } from '@libp2p/logger' import type { Source } from 'it-stream-types' -const ERR_STREAM_RESET = 'ERR_STREAM_RESET' -const ERR_SINK_INVALID_STATE = 'ERR_SINK_INVALID_STATE' const DEFAULT_SEND_CLOSE_WRITE_TIMEOUT = 5000 export interface AbstractStreamInit { @@ -150,7 +148,7 @@ export abstract class AbstractStream implements Stream { async sink (source: Source): Promise { if (this.writeStatus !== 'ready') { - throw new CodeError(`writable end state is "${this.writeStatus}" not "ready"`, ERR_SINK_INVALID_STATE) + throw new StreamStateError(`writable end state is "${this.writeStatus}" not "ready"`) } try { @@ -389,7 +387,7 @@ export abstract class AbstractStream implements Stream { return } - const err = new CodeError('stream reset', ERR_STREAM_RESET) + const err = new StreamResetError('stream reset') this.status = 'reset' this.timeline.reset = Date.now() diff --git a/packages/utils/src/errors.ts b/packages/utils/src/errors.ts new file mode 100644 index 0000000000..d2b62ebace --- /dev/null +++ b/packages/utils/src/errors.ts @@ -0,0 +1,20 @@ +import type { RateLimiterResult } from './rate-limiter.js' + +/** + * A rate limit was hit + */ +export class RateLimitError extends Error { + remainingPoints: number + msBeforeNext: number + consumedPoints: number + isFirstInDuration: boolean + + constructor (message = 'Rate limit exceeded', props: RateLimiterResult) { + super(message) + this.name = 'RateLimitError' + this.remainingPoints = props.remainingPoints + this.msBeforeNext = props.msBeforeNext + this.consumedPoints = props.consumedPoints + this.isFirstInDuration = props.isFirstInDuration + } +} diff --git a/packages/utils/src/ip-port-to-multiaddr.ts b/packages/utils/src/ip-port-to-multiaddr.ts index 9fb3d8b9f6..f0dff75420 100644 --- a/packages/utils/src/ip-port-to-multiaddr.ts +++ b/packages/utils/src/ip-port-to-multiaddr.ts @@ -1,19 +1,13 @@ import { isIPv4, isIPv6 } from '@chainsafe/is-ip' -import { CodeError } from '@libp2p/interface' +import { InvalidParametersError } from '@libp2p/interface' import { type Multiaddr, multiaddr } from '@multiformats/multiaddr' -export const Errors = { - ERR_INVALID_IP_PARAMETER: 'ERR_INVALID_IP_PARAMETER', - ERR_INVALID_PORT_PARAMETER: 'ERR_INVALID_PORT_PARAMETER', - ERR_INVALID_IP: 'ERR_INVALID_IP' -} - /** * Transform an IP, Port pair into a multiaddr */ export function ipPortToMultiaddr (ip: string, port: number | string): Multiaddr { if (typeof ip !== 'string') { - throw new CodeError(`invalid ip provided: ${ip}`, Errors.ERR_INVALID_IP_PARAMETER) // eslint-disable-line @typescript-eslint/restrict-template-expressions + throw new InvalidParametersError(`invalid ip provided: ${ip}`) // eslint-disable-line @typescript-eslint/restrict-template-expressions } if (typeof port === 'string') { @@ -21,7 +15,7 @@ export function ipPortToMultiaddr (ip: string, port: number | string): Multiaddr } if (isNaN(port)) { - throw new CodeError(`invalid port provided: ${port}`, Errors.ERR_INVALID_PORT_PARAMETER) + throw new InvalidParametersError(`invalid port provided: ${port}`) } if (isIPv4(ip)) { @@ -32,5 +26,5 @@ export function ipPortToMultiaddr (ip: string, port: number | string): Multiaddr return multiaddr(`/ip6/${ip}/tcp/${port}`) } - throw new CodeError(`invalid ip:port for creating a multiaddr: ${ip}:${port}`, Errors.ERR_INVALID_IP) + throw new InvalidParametersError(`invalid ip:port for creating a multiaddr: ${ip}:${port}`) } diff --git a/packages/utils/src/queue/index.ts b/packages/utils/src/queue/index.ts index f95627b56e..f35c386376 100644 --- a/packages/utils/src/queue/index.ts +++ b/packages/utils/src/queue/index.ts @@ -1,4 +1,4 @@ -import { AbortError, CodeError, TypedEventEmitter } from '@libp2p/interface' +import { AbortError, TypedEventEmitter } from '@libp2p/interface' import { pushable } from 'it-pushable' import { raceEvent } from 'race-event' import { Job } from './job.js' @@ -374,7 +374,7 @@ export class Queue { - cleanup(new CodeError('Queue aborted', 'ERR_QUEUE_ABORTED')) + cleanup(new AbortError('Queue aborted')) } // add listeners diff --git a/packages/utils/src/rate-limiter.ts b/packages/utils/src/rate-limiter.ts index 508bd43027..3b0099c430 100644 --- a/packages/utils/src/rate-limiter.ts +++ b/packages/utils/src/rate-limiter.ts @@ -1,5 +1,5 @@ -import { CodeError } from '@libp2p/interface' import delay from 'delay' +import { RateLimitError } from './errors.js' export interface RateLimiterInit { /** @@ -92,7 +92,7 @@ export class RateLimiter { res = this.memoryStorage.set(rlKey, res.consumedPoints, this.blockDuration) } - throw new CodeError('Rate limit exceeded', 'ERR_RATE_LIMIT_EXCEEDED', res) + throw new RateLimitError('Rate limit exceeded', res) } else if (this.execEvenly && res.msBeforeNext > 0 && !res.isFirstInDuration) { // Execute evenly let delayMs = Math.ceil(res.msBeforeNext / (res.remainingPoints + 2)) diff --git a/packages/utils/test/abstract-stream.spec.ts b/packages/utils/test/abstract-stream.spec.ts index 5cf860dcb2..b84afa3970 100644 --- a/packages/utils/test/abstract-stream.spec.ts +++ b/packages/utils/test/abstract-stream.spec.ts @@ -143,7 +143,7 @@ describe('abstract stream', () => { expect(stream).to.have.nested.property('timeline.abort').that.is.a('number') await expect(stream.sink([])).to.eventually.be.rejected - .with.property('code', 'ERR_SINK_INVALID_STATE') + .with.property('name', 'StreamStateError') await expect(drain(stream.source)).to.eventually.be.rejected .with('Urk!') }) @@ -161,9 +161,9 @@ describe('abstract stream', () => { expect(stream).to.not.have.nested.property('timeline.abort') await expect(stream.sink([])).to.eventually.be.rejected - .with.property('code', 'ERR_SINK_INVALID_STATE') + .with.property('name', 'StreamStateError') await expect(drain(stream.source)).to.eventually.be.rejected - .with.property('code', 'ERR_STREAM_RESET') + .with.property('name', 'StreamResetError') }) it('does not send close read when remote closes write', async () => { @@ -257,6 +257,6 @@ describe('abstract stream', () => { await expect(stream.close({ signal: AbortSignal.timeout(1) })).to.eventually.be.rejected - .with.property('code', 'ABORT_ERR') + .with.property('name', 'AbortError') }) }) diff --git a/packages/utils/test/ip-port-to-multiaddr.spec.ts b/packages/utils/test/ip-port-to-multiaddr.spec.ts index 7042d373ea..f30e592c87 100644 --- a/packages/utils/test/ip-port-to-multiaddr.spec.ts +++ b/packages/utils/test/ip-port-to-multiaddr.spec.ts @@ -1,7 +1,7 @@ /* eslint-env mocha */ import { expect } from 'aegir/chai' -import { ipPortToMultiaddr, Errors } from '../src/ip-port-to-multiaddr.js' +import { ipPortToMultiaddr } from '../src/ip-port-to-multiaddr.js' describe('IP and port to Multiaddr', () => { it('creates multiaddr from valid IPv4 IP and port', () => { @@ -30,18 +30,19 @@ describe('IP and port to Multiaddr', () => { it('throws for missing IP address', () => { // @ts-expect-error invalid args - expect(() => ipPortToMultiaddr()).to.throw('invalid ip provided').with.property('code', Errors.ERR_INVALID_IP_PARAMETER) + expect(() => ipPortToMultiaddr()).to.throw('invalid ip provided') + .with.property('name', 'InvalidParametersError') }) it('throws for invalid IP address', () => { const ip = 'aewmrn4awoew' const port = '234' - expect(() => ipPortToMultiaddr(ip, port)).to.throw('invalid ip:port for creating a multiaddr').with.property('code', Errors.ERR_INVALID_IP) + expect(() => ipPortToMultiaddr(ip, port)).to.throw('invalid ip:port for creating a multiaddr').with.property('name', 'InvalidParametersError') }) it('throws for invalid port', () => { const ip = '127.0.0.1' const port = 'garbage' - expect(() => ipPortToMultiaddr(ip, port)).to.throw('invalid port provided').with.property('code', Errors.ERR_INVALID_PORT_PARAMETER) + expect(() => ipPortToMultiaddr(ip, port)).to.throw('invalid port provided').with.property('name', 'InvalidParametersError') }) }) diff --git a/packages/utils/test/queue.spec.ts b/packages/utils/test/queue.spec.ts index b7612a9171..02d0faa56c 100644 --- a/packages/utils/test/queue.spec.ts +++ b/packages/utils/test/queue.spec.ts @@ -159,7 +159,7 @@ describe('queue', () => { await expect(queue.onEmpty({ signal: AbortSignal.timeout(10) })).to.eventually.be.rejected - .with.property('code', 'ABORT_ERR') + .with.property('name', 'AbortError') }) it('.onIdle()', async () => { @@ -201,7 +201,7 @@ describe('queue', () => { await expect(queue.onIdle({ signal: AbortSignal.timeout(10) })).to.eventually.be.rejected - .with.property('code', 'ABORT_ERR') + .with.property('name', 'AbortError') }) it('.onSizeLessThan()', async () => { @@ -245,7 +245,7 @@ describe('queue', () => { await expect(queue.onSizeLessThan(1, { signal: AbortSignal.timeout(10) })).to.eventually.be.rejected - .with.property('code', 'ABORT_ERR') + .with.property('name', 'AbortError') }) it('.onIdle() - no pending', async () => { @@ -676,7 +676,7 @@ describe('queue', () => { await expect(all(queue.toGenerator({ signal: controller.signal }))).to.eventually.be.rejected - .with.property('code', 'ERR_QUEUE_ABORTED') + .with.property('name', 'AbortError') }) it('can break out of a loop with a generator', async () => { @@ -742,7 +742,7 @@ describe('queue', () => { expect(signal.listenerCount('abort')).to.equal(0) await expect(jobResult).to.eventually.be.rejected - .with.property('code', 'ABORT_ERR') + .with.property('name', 'AbortError') }) it('rejects aborted jobs with the abort reason if supplied', async () => { @@ -790,7 +790,7 @@ describe('queue', () => { signal.abort() await expect(jobResult).to.eventually.be.rejected - .with.property('code', 'ABORT_ERR') + .with.property('name', 'AbortError') // counts updated expect(queue.size).to.equal(1) diff --git a/packages/utils/test/rate-limiter.spec.ts b/packages/utils/test/rate-limiter.spec.ts index c76c3e0765..ced27acc18 100644 --- a/packages/utils/test/rate-limiter.spec.ts +++ b/packages/utils/test/rate-limiter.spec.ts @@ -20,7 +20,7 @@ describe('RateLimiter with fixed window', function () { const rateLimiterMemory = new RateLimiter({ points: 1, duration: 5 }) await expect(rateLimiterMemory.consume(testKey, 2)).to.eventually.be.rejected - .with.nested.property('props.msBeforeNext').that.is.gte(0) + .with.property('msBeforeNext').that.is.gte(0) }) it('execute evenly over duration with minimum delay 20 ms', async () => { @@ -98,7 +98,7 @@ describe('RateLimiter with fixed window', function () { const rateLimiterMemory = new RateLimiter({ points: 1, duration: 1, blockDuration: 2 }) await expect(rateLimiterMemory.consume(testKey, 2)).to.eventually.be.rejected - .with.nested.property('props.msBeforeNext').that.is.greaterThan(1000) + .with.property('msBeforeNext').that.is.greaterThan(1000) }) it('do not block key second time until block expires no matter how many points consumed', async () => { @@ -110,7 +110,7 @@ describe('RateLimiter with fixed window', function () { await delay(1201) await expect(rateLimiterMemory.consume(testKey)).to.eventually.be.rejected() - .with.nested.property('props.msBeforeNext').that.is.lessThan(1000) + .with.property('msBeforeNext').that.is.lessThan(1000) }) it('block expires in blockDuration seconds', async () => { @@ -132,7 +132,7 @@ describe('RateLimiter with fixed window', function () { rateLimiterMemory.block(testKey, 2) await expect(rateLimiterMemory.consume(testKey)).to.eventually.be.rejected() - .with.nested.property('props.msBeforeNext').that.is.within(1000, 2000) + .with.property('msBeforeNext').that.is.within(1000, 2000) }) it('get by key', async () => {