From 0a7acbe57a9b56734a25da169b7379a53e89ba51 Mon Sep 17 00:00:00 2001 From: John Turpish Date: Fri, 12 Aug 2022 15:10:35 -0400 Subject: [PATCH 1/8] Getting more specific with the errors being thrown. --- src/connection.ts | 12 +++++------ src/error.ts | 53 +++++++++++++++++++++++++++++++++++++++-------- src/sdp.ts | 18 ++++++---------- src/transport.ts | 5 +++-- 4 files changed, 59 insertions(+), 29 deletions(-) diff --git a/src/connection.ts b/src/connection.ts index feb996f..6e60cd6 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -11,6 +11,7 @@ import { WebRTCStream } from './stream'; import { select as msselect, handle as mshandle } from '@libp2p/multistream-select'; import { Duplex } from 'it-stream-types'; import { Uint8ArrayList } from 'uint8arraylist'; +import { DataChannelError, OperationAbortedError, StreamingLimitationError } from './error'; const log = logger('libp2p:webrtc:connection'); @@ -73,8 +74,7 @@ export class WebRTCConnection implements ic.Connection { await Promise.race([openPromise.promise, abortPromise.promise]); if (controller.signal.aborted) { - // TODO: Better errors - throw Error(controller.signal.reason); + throw new OperationAbortedError('prior to a new stream incoming.', controller.signal.reason); } let rawStream = new WebRTCStream({ @@ -162,8 +162,8 @@ export class WebRTCConnection implements ic.Connection { } options.signal.onabort = () => { + openError = new OperationAbortedError('.', options.signal?.reason || 'aborted'); log.trace(`[stream: ${label}] abort called - ${options.signal?.reason}`); - openError = new Error(options.signal?.reason || 'aborted'); abortedPromise.resolve(); }; @@ -174,8 +174,8 @@ export class WebRTCConnection implements ic.Connection { openPromise.resolve(); }; channel.onerror = (_evt) => { - log.trace(`[stream: ${label}] data channel error: ${(_evt as RTCErrorEvent).error}`); - openError = new Error(`data channel error`); + openError = new DataChannelError(label, (_evt as RTCErrorEvent).error.message); + log.trace(openError.message); abortedPromise.resolve(); }; @@ -217,7 +217,7 @@ export class WebRTCConnection implements ic.Connection { let direction = stream.stat.direction; if (this.countStream(protocol, direction) === this.findStreamLimit(protocol, direction)) { log(`${direction} stream limit reached for protocol - ${protocol}`); - let err = new Error(`${direction} stream limit reached for protocol - ${protocol}`); + let err = new StreamingLimitationError(`${direction} stream limit reached for protocol - ${protocol}`); stream.abort(err); throw err; } diff --git a/src/error.ts b/src/error.ts index c973d34..cd0e303 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1,13 +1,48 @@ export class WebRTCTransportError extends Error { - constructor(msg: string) { - super(msg); - this.name = 'WebRTCTransportError'; - } + constructor(msg: string) { + super('WebRTC transport error: ' + msg); + this.name = 'WebRTCTransportError'; + } } export class InvalidArgumentError extends WebRTCTransportError { - constructor(msg: string) { - super(msg); - this.name = 'WebRTC/InvalidArgumentError'; - } -} \ No newline at end of file + constructor(msg: string) { + super('There was a problem with a provided argument: ' + msg); + this.name = 'WebRTC/InvalidArgumentError'; + } +} + +export class UnimplementedError extends WebRTCTransportError { + constructor(methodName: string) { + super('A method (' + methodName + ') was called though it has been intentionally left unimplemented.'); + this.name = 'WebRTC/UnimplementedError'; + } +} + +export class InappropriateMultiaddrError extends WebRTCTransportError { + constructor(msg: string) { + super('There was a problem with the Multiaddr which was passed in: ' + msg); + this.name = 'WebRTC/InappropriateMultiaddrError'; + } +} + +export class OperationAbortedError extends WebRTCTransportError { + constructor(context: string, abortReason: string) { + super(`Signalled to abort because (${abortReason}})${context}`); + this.name = 'WebRTC/OperationAbortedError'; + } +} + +export class DataChannelError extends WebRTCTransportError { + constructor(streamLabel: string, errorMessage: string) { + super(`[stream: ${streamLabel}] data channel error: ${errorMessage}`); + this.name = 'WebRTC/DataChannelError'; + } +} + +export class StreamingLimitationError extends WebRTCTransportError { + constructor(msg: string) { + super(msg); + this.name = 'WebRTC/StreamingLimitationError'; + } +} diff --git a/src/sdp.ts b/src/sdp.ts index 65f131d..ed6fa4b 100644 --- a/src/sdp.ts +++ b/src/sdp.ts @@ -1,4 +1,4 @@ -import { InvalidArgumentError } from './error.js' +import { InappropriateMultiaddrError, InvalidArgumentError } from './error.js'; import { logger } from '@libp2p/logger'; import { Multiaddr } from '@multiformats/multiaddr'; @@ -39,19 +39,16 @@ function port(ma: Multiaddr): number { } function certhash(ma: Multiaddr): string { let tups = ma.stringTuples(); - let certhash_value = tups - .filter((tup) => tup[0] == CERTHASH_CODE) - .map((tup) => tup[1])[0]; + let certhash_value = tups.filter((tup) => tup[0] == CERTHASH_CODE).map((tup) => tup[1])[0]; if (certhash_value) { return certhash_value; } else { - throw new Error("Couldn't find a certhash component of multiaddr:" + ma.toString()); + throw new InappropriateMultiaddrError("Couldn't find a certhash component of multiaddr:" + ma.toString()); } } function ma2sdp(ma: Multiaddr, ufrag: string): string { - return ANSWER_SDP_FORMAT - .replace('%s', ipv(ma)) + return ANSWER_SDP_FORMAT.replace('%s', ipv(ma)) .replace('%s', ip(ma)) .replace('%s', ipv(ma)) .replace('%s', ip(ma)) @@ -70,11 +67,8 @@ export function fromMultiAddr(ma: Multiaddr, ufrag: string): RTCSessionDescripti export function munge(desc: RTCSessionDescriptionInit, ufrag: string): RTCSessionDescriptionInit { if (desc.sdp) { - desc.sdp = desc.sdp - .replace(/\na=ice-ufrag:[^\n]*\n/, '\na=ice-ufrag:' + ufrag + '\n') - .replace(/\na=ice-pwd:[^\n]*\n/, '\na=ice-pwd:' + ufrag + '\n') - ; - return desc; + desc.sdp = desc.sdp.replace(/\na=ice-ufrag:[^\n]*\n/, '\na=ice-ufrag:' + ufrag + '\n').replace(/\na=ice-pwd:[^\n]*\n/, '\na=ice-pwd:' + ufrag + '\n'); + return desc; } else { throw new InvalidArgumentError("Can't munge a missing SDP"); } diff --git a/src/transport.ts b/src/transport.ts index 2fb137b..8f5f299 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -11,6 +11,7 @@ import { logger } from '@libp2p/logger'; import { Multiaddr } from '@multiformats/multiaddr'; import { v4 as genUuid } from 'uuid'; import defer, { DeferredPromise } from 'p-defer'; +import { InappropriateMultiaddrError, UnimplementedError } from './error'; const log = logger('libp2p:webrtc:transport'); const utf8 = new TextEncoder(); @@ -31,7 +32,7 @@ export class WebRTCTransport implements Transport, Initializable { } createListener(options: CreateListenerOptions): Listener { - throw new Error('TODO - replace with an exception more appropriate to the fact that this will not be implemented.'); + throw new UnimplementedError('WebRTCTransport.createListener'); } filter(multiaddrs: Multiaddr[]): Multiaddr[] { @@ -87,7 +88,7 @@ export class WebRTCTransport implements Transport, Initializable { let myPeerId = this.components!.getPeerId(); let rps = ma.getPeerId(); if (!rps) { - throw new Error('TODO Do we really need a peer ID ?'); + throw new InappropriateMultiaddrError("we need to have the remote's PeerId"); } let theirPeerId = p.peerIdFromString(rps); From 3073031df3847ecad6fbf0fbce5a0f1e5e15b823 Mon Sep 17 00:00:00 2001 From: Chinmay Kousik Date: Mon, 15 Aug 2022 18:08:29 +0530 Subject: [PATCH 2/8] Fix noise prologue generation --- package.json | 4 ++- src/error.ts | 26 +++++++++------ src/sdp.ts | 56 ++++++++++++++++++++++++--------- src/transport.ts | 51 +++++++++++++++++++++++++++--- test/connection.browser.spec.ts | 2 +- test/sdp.spec.ts | 6 ++-- tsconfig.json | 3 +- 7 files changed, 114 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index e0041b0..9b995a7 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,11 @@ "@libp2p/logger": "^2.0.0", "@libp2p/multistream-select": "^3.0.0", "@libp2p/peer-id": "^1.1.15", - "@multiformats/multiaddr": "../js-multiaddr/", + "@multiformats/multiaddr": "file:../js-multiaddr", "abortable-iterator": "^4.0.2", "it-merge": "^1.0.4", + "multiformats": "^9.7.1", + "multihashes": "^4.0.3", "p-defer": "^4.0.0", "socket.io-client": "^4.1.2", "timeout-abort-controller": "^3.0.0", diff --git a/src/error.ts b/src/error.ts index c973d34..35ac731 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1,13 +1,21 @@ export class WebRTCTransportError extends Error { - constructor(msg: string) { - super(msg); - this.name = 'WebRTCTransportError'; - } + constructor(msg: string) { + super(msg); + this.name = 'WebRTCTransportError'; + } } export class InvalidArgumentError extends WebRTCTransportError { - constructor(msg: string) { - super(msg); - this.name = 'WebRTC/InvalidArgumentError'; - } -} \ No newline at end of file + constructor(msg: string) { + super(msg); + this.name = 'WebRTC/InvalidArgumentError'; + } +} + +export class UnsupportedHashAlgorithmError extends WebRTCTransportError { + constructor(algo: string) { + let msg = `unsupported hash algorithm: ${algo}`; + super(msg); + this.name = 'WebRTC/UnsupportedHashAlgorithmError'; + } +} diff --git a/src/sdp.ts b/src/sdp.ts index 65f131d..8c83835 100644 --- a/src/sdp.ts +++ b/src/sdp.ts @@ -1,9 +1,13 @@ -import { InvalidArgumentError } from './error.js' +import { InvalidArgumentError, UnsupportedHashAlgorithmError } from './error.js'; import { logger } from '@libp2p/logger'; import { Multiaddr } from '@multiformats/multiaddr'; +import { base64 } from 'multiformats/bases/base64'; +import * as multihashes from 'multihashes'; const log = logger('libp2p:webrtc:sdp'); +// const mbdecoder = base64.decoder.or(base58btc.decoder).or(base32.decoder).or(base16.decoder); + const CERTHASH_CODE: number = 466; const ANSWER_SDP_FORMAT: string = ` v=0 @@ -39,19 +43,38 @@ function port(ma: Multiaddr): number { } function certhash(ma: Multiaddr): string { let tups = ma.stringTuples(); - let certhash_value = tups - .filter((tup) => tup[0] == CERTHASH_CODE) - .map((tup) => tup[1])[0]; - if (certhash_value) { - return certhash_value; - } else { - throw new Error("Couldn't find a certhash component of multiaddr:" + ma.toString()); + let certhash_value = tups.filter((tup) => tup[0] == CERTHASH_CODE).map((tup) => tup[1])[0]; + if (!certhash_value) { + throw new InvalidArgumentError('certhash not found in multiaddress'); } + + // certhash_value is a multibase encoded multihash encoded string + // the multiformats PR always encodes in base64 + let mbdecoded = base64.decode(certhash_value); + let mhdecoded = multihashes.decode(mbdecoded); + let prefix = ''; + switch (mhdecoded.name) { + case 'md5': + prefix = 'md5'; + break; + case 'sha2-256': + prefix = 'sha-256'; + break; + case 'sha2-512': + prefix = 'sha-512'; + break; + default: + throw new UnsupportedHashAlgorithmError(mhdecoded.name); + } + + let fp = mhdecoded.digest.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), ''); + fp = fp.match(/.{1,2}/g)!.join(':'); + + return `${prefix} ${fp}`; } function ma2sdp(ma: Multiaddr, ufrag: string): string { - return ANSWER_SDP_FORMAT - .replace('%s', ipv(ma)) + return ANSWER_SDP_FORMAT.replace('%s', ipv(ma)) .replace('%s', ip(ma)) .replace('%s', ipv(ma)) .replace('%s', ip(ma)) @@ -70,12 +93,15 @@ export function fromMultiAddr(ma: Multiaddr, ufrag: string): RTCSessionDescripti export function munge(desc: RTCSessionDescriptionInit, ufrag: string): RTCSessionDescriptionInit { if (desc.sdp) { - desc.sdp = desc.sdp - .replace(/\na=ice-ufrag:[^\n]*\n/, '\na=ice-ufrag:' + ufrag + '\n') - .replace(/\na=ice-pwd:[^\n]*\n/, '\na=ice-pwd:' + ufrag + '\n') - ; - return desc; + desc.sdp = desc.sdp.replace(/\na=ice-ufrag:[^\n]*\n/, '\na=ice-ufrag:' + ufrag + '\n').replace(/\na=ice-pwd:[^\n]*\n/, '\na=ice-pwd:' + ufrag + '\n'); + return desc; } else { throw new InvalidArgumentError("Can't munge a missing SDP"); } } + +export function getCerthashFromMultiaddr(ma: Multiaddr): string | undefined { + let tups = ma.stringTuples(); + let certhash_value = tups.filter((tup) => tup[0] == CERTHASH_CODE).map((tup) => tup[1])[0]; + return certhash_value; +} diff --git a/src/transport.ts b/src/transport.ts index 2fb137b..7a8bf63 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -11,9 +11,13 @@ import { logger } from '@libp2p/logger'; import { Multiaddr } from '@multiformats/multiaddr'; import { v4 as genUuid } from 'uuid'; import defer, { DeferredPromise } from 'p-defer'; +import { base64 } from 'multiformats/bases/base64'; +import { fromString as uint8arrayFromString } from 'uint8arrays/from-string'; +import { concat } from 'uint8arrays/concat'; +import * as multihashes from 'multihashes'; +import { InvalidArgumentError, UnsupportedHashAlgorithmError } from './error'; const log = logger('libp2p:webrtc:transport'); -const utf8 = new TextEncoder(); export class WebRTCTransport implements Transport, Initializable { private componentsPromise: DeferredPromise = defer(); @@ -87,15 +91,15 @@ export class WebRTCTransport implements Transport, Initializable { let myPeerId = this.components!.getPeerId(); let rps = ma.getPeerId(); if (!rps) { - throw new Error('TODO Do we really need a peer ID ?'); + throw new Error('could not get remote peerId'); } let theirPeerId = p.peerIdFromString(rps); // do noise handshake //set the Noise Prologue to libp2p-webrtc-noise: before starting the actual Noise handshake. // is the concatenation of the of the two TLS fingerprints of A and B in their multihash byte representation, sorted in ascending order. - let fingerprintsPrologue = [myPeerId.multihash, theirPeerId.multihash].sort().join(''); - let noise = new Noise(myPeerId.privateKey, undefined, stablelib, utf8.encode(fingerprintsPrologue)); + let fingerprintsPrologue = this.generateNoisePrologue(peerConnection, ma); + let noise = new Noise(myPeerId.privateKey, undefined, stablelib, fingerprintsPrologue); let wrappedChannel = new WebRTCStream({ channel: handshakeDataChannel, stat: { direction: 'outbound', timeline: { open: 1 } } }); let wrappedDuplex = { ...wrappedChannel, @@ -120,4 +124,43 @@ export class WebRTCTransport implements Transport, Initializable { remotePeer: theirPeerId, }); } + + private generateNoisePrologue(pc: RTCPeerConnection, ma: Multiaddr): Uint8Array { + let remoteCerthash = sdp.getCerthashFromMultiaddr(ma); + if (!remoteCerthash) { + throw new InvalidArgumentError('no remote tls fingerprint in multiaddr'); + } + let remote = base64.decode(remoteCerthash); + if (pc.getConfiguration().certificates?.length === 0) { + throw new InvalidArgumentError('no local certificate'); + } + let localCert = pc.getConfiguration().certificates![0]; + if (localCert.getFingerprints().length === 0) { + throw new InvalidArgumentError('no fingerprint on local certificate'); + } + + let localFingerprint = localCert.getFingerprints()[0]; + let localFpString = localFingerprint.value!.replaceAll(':', ''); + let localFpArray = uint8arrayFromString(localFpString, 'hex'); + let local: Uint8Array; + switch (localFingerprint.algorithm!) { + case 'md5': + local = multihashes.encode(localFpArray, multihashes.names['md5']); + break; + case 'sha-256': + local = multihashes.encode(localFpArray, multihashes.names['sha2-256']); + break; + case 'sha-512': + local = multihashes.encode(localFpArray, multihashes.names['sha2-512']); + break; + default: + throw new UnsupportedHashAlgorithmError(localFingerprint.algorithm || 'none'); + } + + let prefix = uint8arrayFromString('libp2p-webrtc-noise:'); + let fps = [local, remote].sort(); + + let result = concat([prefix, ...fps]); + return result; + } } diff --git a/test/connection.browser.spec.ts b/test/connection.browser.spec.ts index 9b27227..e5b8e07 100644 --- a/test/connection.browser.spec.ts +++ b/test/connection.browser.spec.ts @@ -1,6 +1,6 @@ /* eslint-env mocha */ -import {createConnectionPair, echoHandler} from "./util"; +import {createConnectionPair, echoHandler} from "../test/util.js"; import { expect } from 'aegir/chai'; import { pipe } from 'it-pipe'; import all from 'it-all'; diff --git a/test/sdp.spec.ts b/test/sdp.spec.ts index f576a9e..f4651db 100644 --- a/test/sdp.spec.ts +++ b/test/sdp.spec.ts @@ -13,7 +13,7 @@ a=mid:0 a=ice-options:ice2 a=ice-ufrag:MyUserFragment a=ice-pwd:MyUserFragment -a=fingerprint:mTXVsdGliYXNlIGlzIGF3ZXNvbWUhIFxvLw +a=fingerprint:sha-256 b9:2e:11:cf:23:ff:da:31:bb:bb:5c:0a:9d:d9:0e:20:07:e2:bb:61:2f:1f:94:cf:e5:2e:0e:05:5c:4e:8a:88 a=setup:actpass a=sctp-port:5000 a=max-message-size:100000 @@ -21,7 +21,7 @@ a=max-message-size:100000 describe('SDP creation', () => { it('handles simple blue sky easily enough', async () => { - let ma = new Multiaddr('/ip4/192.168.0.152/udp/2345/webrtc/certhash/zYAjKoNbau5KiqmHPmSxYCvn66dA1vLmwbt'); + let ma = new Multiaddr('/ip4/192.168.0.152/udp/2345/webrtc/certhash/uEiC5LhHPI__aMbu7XAqd2Q4gB-K7YS8flM_lLg4FXE6KiA'); let ufrag = 'MyUserFragment'; let sdp = underTest.fromMultiAddr(ma, ufrag); expect(sdp.sdp).to.equal(an_sdp); @@ -42,7 +42,7 @@ a=mid:0 a=ice-options:ice2 a=ice-ufrag:someotheruserfragmentstring a=ice-pwd:someotheruserfragmentstring -a=fingerprint:mTXVsdGliYXNlIGlzIGF3ZXNvbWUhIFxvLw +a=fingerprint:sha-256 b9:2e:11:cf:23:ff:da:31:bb:bb:5c:0a:9d:d9:0e:20:07:e2:bb:61:2f:1f:94:cf:e5:2e:0e:05:5c:4e:8a:88 a=setup:actpass a=sctp-port:5000 a=max-message-size:100000 diff --git a/tsconfig.json b/tsconfig.json index 9fff416..db30ecc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,8 @@ "outDir": "dist", "emitDeclarationOnly": false, "module": "ES2020", - "importsNotUsedAsValues": "preserve" + "importsNotUsedAsValues": "preserve", + "moduleResolution": "node" }, "include": [ "src", From 425b830cee04973299d72e44dab08049edc376c1 Mon Sep 17 00:00:00 2001 From: John Turpish Date: Mon, 15 Aug 2022 10:46:31 -0400 Subject: [PATCH 3/8] Shifting approach. --- package.json | 1 + src/connection.ts | 12 ++++++------ src/error.ts | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index e0041b0..f64cfe3 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@libp2p/peer-id": "^1.1.15", "@multiformats/multiaddr": "../js-multiaddr/", "abortable-iterator": "^4.0.2", + "err-code": "^3.0.1", "it-merge": "^1.0.4", "p-defer": "^4.0.0", "socket.io-client": "^4.1.2", diff --git a/src/connection.ts b/src/connection.ts index 6e60cd6..ec381bd 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -11,7 +11,7 @@ import { WebRTCStream } from './stream'; import { select as msselect, handle as mshandle } from '@libp2p/multistream-select'; import { Duplex } from 'it-stream-types'; import { Uint8ArrayList } from 'uint8arraylist'; -import { DataChannelError, OperationAbortedError, StreamingLimitationError } from './error'; +import { dataChannelError, operationAborted, overStreamLimit } from './error'; const log = logger('libp2p:webrtc:connection'); @@ -74,7 +74,7 @@ export class WebRTCConnection implements ic.Connection { await Promise.race([openPromise.promise, abortPromise.promise]); if (controller.signal.aborted) { - throw new OperationAbortedError('prior to a new stream incoming.', controller.signal.reason); + throw operationAborted('prior to a new stream incoming.', controller.signal.reason); } let rawStream = new WebRTCStream({ @@ -162,7 +162,7 @@ export class WebRTCConnection implements ic.Connection { } options.signal.onabort = () => { - openError = new OperationAbortedError('.', options.signal?.reason || 'aborted'); + openError = operationAborted('.', options.signal?.reason || 'aborted'); log.trace(`[stream: ${label}] abort called - ${options.signal?.reason}`); abortedPromise.resolve(); }; @@ -174,7 +174,7 @@ export class WebRTCConnection implements ic.Connection { openPromise.resolve(); }; channel.onerror = (_evt) => { - openError = new DataChannelError(label, (_evt as RTCErrorEvent).error.message); + openError = dataChannelError(label, (_evt as RTCErrorEvent).error.message); log.trace(openError.message); abortedPromise.resolve(); }; @@ -216,8 +216,8 @@ export class WebRTCConnection implements ic.Connection { let protocol = stream.stat.protocol!; let direction = stream.stat.direction; if (this.countStream(protocol, direction) === this.findStreamLimit(protocol, direction)) { - log(`${direction} stream limit reached for protocol - ${protocol}`); - let err = new StreamingLimitationError(`${direction} stream limit reached for protocol - ${protocol}`); + let err = overStreamLimit(direction, protocol); + log(err.message); stream.abort(err); throw err; } diff --git a/src/error.ts b/src/error.ts index cd0e303..3871ea8 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1,3 +1,6 @@ +import { default as createError } from 'err-code'; +import { Direction } from '@libp2p/interface-connection'; + export class WebRTCTransportError extends Error { constructor(msg: string) { super('WebRTC transport error: ' + msg); @@ -5,6 +8,16 @@ export class WebRTCTransportError extends Error { } } +export enum codes { + ERR_ALREADY_ABORTED = 'ERR_ALREADY_ABORTED', + ERR_DATA_CHANNEL = 'ERR_DATA_CHANNEL', + ERR_INVALID_MULTIADDR = 'ERR_INVALID_MULTIADDR', + 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 InvalidArgumentError extends WebRTCTransportError { constructor(msg: string) { super('There was a problem with a provided argument: ' + msg); @@ -12,6 +25,10 @@ export class InvalidArgumentError extends WebRTCTransportError { } } +export function invalidArgument(msg: string) { + return createError(new InvalidArgumentError(msg), codes.ERR_INVALID_PARAMETERS); +} + export class UnimplementedError extends WebRTCTransportError { constructor(methodName: string) { super('A method (' + methodName + ') was called though it has been intentionally left unimplemented.'); @@ -19,6 +36,10 @@ export class UnimplementedError extends WebRTCTransportError { } } +export function unimplemented(methodName: string) { + return createError(new UnimplementedError(methodName), codes.ERR_NOT_IMPLEMENTED); +} + export class InappropriateMultiaddrError extends WebRTCTransportError { constructor(msg: string) { super('There was a problem with the Multiaddr which was passed in: ' + msg); @@ -26,6 +47,10 @@ export class InappropriateMultiaddrError extends WebRTCTransportError { } } +export function wrongMultiaddr(msg: string) { + return createError(new InappropriateMultiaddrError(msg), codes.ERR_INVALID_MULTIADDR); +} + export class OperationAbortedError extends WebRTCTransportError { constructor(context: string, abortReason: string) { super(`Signalled to abort because (${abortReason}})${context}`); @@ -33,6 +58,10 @@ export class OperationAbortedError extends WebRTCTransportError { } } +export function operationAborted(context: string, reason: string) { + return createError(new OperationAbortedError(context, reason), codes.ERR_ALREADY_ABORTED); +} + export class DataChannelError extends WebRTCTransportError { constructor(streamLabel: string, errorMessage: string) { super(`[stream: ${streamLabel}] data channel error: ${errorMessage}`); @@ -40,9 +69,18 @@ export class DataChannelError extends WebRTCTransportError { } } +export function dataChannelError(streamLabel: string, msg: string) { + return createError(new OperationAbortedError(streamLabel, msg), codes.ERR_DATA_CHANNEL); +} + export class StreamingLimitationError extends WebRTCTransportError { constructor(msg: string) { super(msg); this.name = 'WebRTC/StreamingLimitationError'; } } + +export function overStreamLimit(dir: Direction, proto: string) { + let code = dir == 'inbound' ? codes.ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS : codes.ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS; + return createError(new StreamingLimitationError(`${dir} stream limit reached for protocol - ${proto}`), code); +} From d5f0db9a7598748f1d9070c79e38759def3cc753 Mon Sep 17 00:00:00 2001 From: John Turpish Date: Mon, 15 Aug 2022 11:25:36 -0400 Subject: [PATCH 4/8] Using error codes tied to error types by way of convenience functions. --- src/error.ts | 2 +- src/sdp.ts | 6 +++--- src/transport.ts | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/error.ts b/src/error.ts index 3871ea8..dd41843 100644 --- a/src/error.ts +++ b/src/error.ts @@ -47,7 +47,7 @@ export class InappropriateMultiaddrError extends WebRTCTransportError { } } -export function wrongMultiaddr(msg: string) { +export function inappropriateMultiaddr(msg: string) { return createError(new InappropriateMultiaddrError(msg), codes.ERR_INVALID_MULTIADDR); } diff --git a/src/sdp.ts b/src/sdp.ts index ed6fa4b..220dcc1 100644 --- a/src/sdp.ts +++ b/src/sdp.ts @@ -1,4 +1,4 @@ -import { InappropriateMultiaddrError, InvalidArgumentError } from './error.js'; +import { inappropriateMultiaddr, invalidArgument } from './error.js'; import { logger } from '@libp2p/logger'; import { Multiaddr } from '@multiformats/multiaddr'; @@ -43,7 +43,7 @@ function certhash(ma: Multiaddr): string { if (certhash_value) { return certhash_value; } else { - throw new InappropriateMultiaddrError("Couldn't find a certhash component of multiaddr:" + ma.toString()); + throw inappropriateMultiaddr("Couldn't find a certhash component of multiaddr:" + ma.toString()); } } @@ -70,6 +70,6 @@ export function munge(desc: RTCSessionDescriptionInit, ufrag: string): RTCSessio desc.sdp = desc.sdp.replace(/\na=ice-ufrag:[^\n]*\n/, '\na=ice-ufrag:' + ufrag + '\n').replace(/\na=ice-pwd:[^\n]*\n/, '\na=ice-pwd:' + ufrag + '\n'); return desc; } else { - throw new InvalidArgumentError("Can't munge a missing SDP"); + throw invalidArgument("Can't munge a missing SDP"); } } diff --git a/src/transport.ts b/src/transport.ts index 8f5f299..9d3810d 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -11,7 +11,7 @@ import { logger } from '@libp2p/logger'; import { Multiaddr } from '@multiformats/multiaddr'; import { v4 as genUuid } from 'uuid'; import defer, { DeferredPromise } from 'p-defer'; -import { InappropriateMultiaddrError, UnimplementedError } from './error'; +import { inappropriateMultiaddr, unimplemented } from './error'; const log = logger('libp2p:webrtc:transport'); const utf8 = new TextEncoder(); @@ -32,7 +32,7 @@ export class WebRTCTransport implements Transport, Initializable { } createListener(options: CreateListenerOptions): Listener { - throw new UnimplementedError('WebRTCTransport.createListener'); + throw unimplemented('WebRTCTransport.createListener'); } filter(multiaddrs: Multiaddr[]): Multiaddr[] { @@ -88,7 +88,7 @@ export class WebRTCTransport implements Transport, Initializable { let myPeerId = this.components!.getPeerId(); let rps = ma.getPeerId(); if (!rps) { - throw new InappropriateMultiaddrError("we need to have the remote's PeerId"); + throw inappropriateMultiaddr("we need to have the remote's PeerId"); } let theirPeerId = p.peerIdFromString(rps); From fd63ecde6871793c382d431d49cc90846a20eaff Mon Sep 17 00:00:00 2001 From: Chinmay Kousik Date: Mon, 15 Aug 2022 23:04:30 +0530 Subject: [PATCH 5/8] fix multibase --- src/sdp.ts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/sdp.ts b/src/sdp.ts index 8c83835..8d9e939 100644 --- a/src/sdp.ts +++ b/src/sdp.ts @@ -1,12 +1,17 @@ import { InvalidArgumentError, UnsupportedHashAlgorithmError } from './error.js'; import { logger } from '@libp2p/logger'; import { Multiaddr } from '@multiformats/multiaddr'; -import { base64 } from 'multiformats/bases/base64'; import * as multihashes from 'multihashes'; +import { bases } from 'multiformats/basics'; const log = logger('libp2p:webrtc:sdp'); -// const mbdecoder = base64.decoder.or(base58btc.decoder).or(base32.decoder).or(base16.decoder); +const mbdecoder = (function () { + const decoders = Object.values(bases).map((b) => b.decoder); + let acc = decoders[0].or(decoders[1]); + decoders.slice(2).forEach((d) => (acc = acc.or(d))); + return acc; +})(); const CERTHASH_CODE: number = 466; const ANSWER_SDP_FORMAT: string = ` @@ -41,16 +46,21 @@ function ip(ma: Multiaddr): string { function port(ma: Multiaddr): number { return ma.toOptions().port; } -function certhash(ma: Multiaddr): string { + +export function certhash(ma: Multiaddr): string { let tups = ma.stringTuples(); let certhash_value = tups.filter((tup) => tup[0] == CERTHASH_CODE).map((tup) => tup[1])[0]; if (!certhash_value) { throw new InvalidArgumentError('certhash not found in multiaddress'); } + return certhash_value; +} +function certhashToFingerprint(ma: Multiaddr): string { + let certhash_value = certhash(ma); // certhash_value is a multibase encoded multihash encoded string // the multiformats PR always encodes in base64 - let mbdecoded = base64.decode(certhash_value); + let mbdecoded = mbdecoder.decode(certhash_value); let mhdecoded = multihashes.decode(mbdecoded); let prefix = ''; switch (mhdecoded.name) { @@ -81,7 +91,7 @@ function ma2sdp(ma: Multiaddr, ufrag: string): string { .replace('%d', port(ma).toString()) .replace('%s', ufrag) .replace('%s', ufrag) - .replace('%s', certhash(ma)); + .replace('%s', certhashToFingerprint(ma)); } export function fromMultiAddr(ma: Multiaddr, ufrag: string): RTCSessionDescriptionInit { From 2b845e0f4092e75601f6d9c16a6e64da02e1016d Mon Sep 17 00:00:00 2001 From: Chinmay Kousik Date: Mon, 15 Aug 2022 23:06:45 +0530 Subject: [PATCH 6/8] fix duplicate function --- src/sdp.ts | 6 ------ src/transport.ts | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/sdp.ts b/src/sdp.ts index 8d9e939..495ac7d 100644 --- a/src/sdp.ts +++ b/src/sdp.ts @@ -109,9 +109,3 @@ export function munge(desc: RTCSessionDescriptionInit, ufrag: string): RTCSessio throw new InvalidArgumentError("Can't munge a missing SDP"); } } - -export function getCerthashFromMultiaddr(ma: Multiaddr): string | undefined { - let tups = ma.stringTuples(); - let certhash_value = tups.filter((tup) => tup[0] == CERTHASH_CODE).map((tup) => tup[1])[0]; - return certhash_value; -} diff --git a/src/transport.ts b/src/transport.ts index 7a8bf63..d546b8d 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -126,7 +126,7 @@ export class WebRTCTransport implements Transport, Initializable { } private generateNoisePrologue(pc: RTCPeerConnection, ma: Multiaddr): Uint8Array { - let remoteCerthash = sdp.getCerthashFromMultiaddr(ma); + let remoteCerthash = sdp.certhash(ma); if (!remoteCerthash) { throw new InvalidArgumentError('no remote tls fingerprint in multiaddr'); } From 60df44867871964b3d5e25f965ca71a3e71754ed Mon Sep 17 00:00:00 2001 From: Chinmay Kousik Date: Mon, 15 Aug 2022 23:15:24 +0530 Subject: [PATCH 7/8] use new errors --- src/error.ts | 5 +++++ src/sdp.ts | 4 ++-- src/transport.ts | 10 +++++----- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/error.ts b/src/error.ts index 30d33ba..88d085f 100644 --- a/src/error.ts +++ b/src/error.ts @@ -13,6 +13,7 @@ export enum codes { ERR_DATA_CHANNEL = 'ERR_DATA_CHANNEL', ERR_INVALID_MULTIADDR = 'ERR_INVALID_MULTIADDR', ERR_INVALID_PARAMETERS = 'ERR_INVALID_PARAMETERS', + ERR_HASH_NOT_SUPPORTED = 'ERR_HASH_NOT_SUPPORTED', 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', @@ -25,6 +26,10 @@ export class InvalidArgumentError extends WebRTCTransportError { } } +export function unsupportedHashAlgorithm(algorithm: string) { + return createError(new UnsupportedHashAlgorithmError(algorithm), codes.ERR_HASH_NOT_SUPPORTED); +} + export class UnsupportedHashAlgorithmError extends WebRTCTransportError { constructor(algo: string) { let msg = `unsupported hash algorithm: ${algo}`; diff --git a/src/sdp.ts b/src/sdp.ts index 21c5fcf..f6640ef 100644 --- a/src/sdp.ts +++ b/src/sdp.ts @@ -1,4 +1,4 @@ -import { UnsupportedHashAlgorithmError, inappropriateMultiaddr, invalidArgument } from './error.js'; +import { inappropriateMultiaddr, invalidArgument, unsupportedHashAlgorithm } from './error.js'; import { logger } from '@libp2p/logger'; import { Multiaddr } from '@multiformats/multiaddr'; import * as multihashes from 'multihashes'; @@ -75,7 +75,7 @@ function certhashToFingerprint(ma: Multiaddr): string { prefix = 'sha-512'; break; default: - throw new UnsupportedHashAlgorithmError(mhdecoded.name); + throw unsupportedHashAlgorithm(mhdecoded.name); } let fp = mhdecoded.digest.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), ''); diff --git a/src/transport.ts b/src/transport.ts index 4421e34..c320af9 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -15,7 +15,7 @@ import { base64 } from 'multiformats/bases/base64'; import { fromString as uint8arrayFromString } from 'uint8arrays/from-string'; import { concat } from 'uint8arrays/concat'; import * as multihashes from 'multihashes'; -import { inappropriateMultiaddr, unimplemented, InvalidArgumentError, UnsupportedHashAlgorithmError } from './error'; +import { inappropriateMultiaddr, unimplemented, invalidArgument, unsupportedHashAlgorithm } from './error'; const log = logger('libp2p:webrtc:transport'); @@ -128,15 +128,15 @@ export class WebRTCTransport implements Transport, Initializable { private generateNoisePrologue(pc: RTCPeerConnection, ma: Multiaddr): Uint8Array { let remoteCerthash = sdp.certhash(ma); if (!remoteCerthash) { - throw new InvalidArgumentError('no remote tls fingerprint in multiaddr'); + throw inappropriateMultiaddr('no remote tls fingerprint in multiaddr'); } let remote = base64.decode(remoteCerthash); if (pc.getConfiguration().certificates?.length === 0) { - throw new InvalidArgumentError('no local certificate'); + throw invalidArgument('no local certificate'); } let localCert = pc.getConfiguration().certificates![0]; if (localCert.getFingerprints().length === 0) { - throw new InvalidArgumentError('no fingerprint on local certificate'); + throw invalidArgument('no fingerprint on local certificate'); } let localFingerprint = localCert.getFingerprints()[0]; @@ -154,7 +154,7 @@ export class WebRTCTransport implements Transport, Initializable { local = multihashes.encode(localFpArray, multihashes.names['sha2-512']); break; default: - throw new UnsupportedHashAlgorithmError(localFingerprint.algorithm || 'none'); + throw unsupportedHashAlgorithm(localFingerprint.algorithm || 'none'); } let prefix = uint8arrayFromString('libp2p-webrtc-noise:'); From cbb9864c31e27b269c83e33f0e0b2110e85c16df Mon Sep 17 00:00:00 2001 From: Chinmay Kousik Date: Mon, 15 Aug 2022 23:18:24 +0530 Subject: [PATCH 8/8] remove comment --- src/sdp.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sdp.ts b/src/sdp.ts index f6640ef..b4cc7dd 100644 --- a/src/sdp.ts +++ b/src/sdp.ts @@ -60,7 +60,6 @@ export function certhash(ma: Multiaddr): string { function certhashToFingerprint(ma: Multiaddr): string { let certhash_value = certhash(ma); // certhash_value is a multibase encoded multihash encoded string - // the multiformats PR always encodes in base64 let mbdecoded = mbdecoder.decode(certhash_value); let mhdecoded = multihashes.decode(mbdecoded); let prefix = '';