Comparing version 1.0.11 to 1.0.12
export declare function utf8BytesToString(buffer: Uint8Array, offset: number, length: number): string; | ||
export declare function readUInt8(buffer: Uint8Array, offset: number): number; | ||
export declare function readUInt16BE(buffer: Uint8Array, offset: number): number; | ||
export declare function readUInt32LE(buffer: Uint8Array, offset: number): number; | ||
@@ -3,0 +5,0 @@ /** |
@@ -6,3 +6,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.writeUInt64LE = exports.writeUInt32LE = exports.writeUInt8 = exports.readUInt32LE = exports.utf8BytesToString = void 0; | ||
exports.writeUInt64LE = exports.writeUInt32LE = exports.writeUInt8 = exports.readUInt32LE = exports.readUInt16BE = exports.readUInt8 = exports.utf8BytesToString = void 0; | ||
// This program is free software: you can redistribute it and/or modify | ||
@@ -25,2 +25,12 @@ // it under the terms of the GNU General Public License as published by | ||
exports.utf8BytesToString = utf8BytesToString; | ||
function readUInt8(buffer, offset) { | ||
checkRange(buffer, offset, 1); | ||
return buffer[offset]; | ||
} | ||
exports.readUInt8 = readUInt8; | ||
function readUInt16BE(buffer, offset) { | ||
checkRange(buffer, offset, 2); | ||
return ((buffer[offset] << 8) | buffer[offset + 1]); | ||
} | ||
exports.readUInt16BE = readUInt16BE; | ||
function readUInt32LE(buffer, offset) { | ||
@@ -27,0 +37,0 @@ checkRange(buffer, offset, 4); |
@@ -123,8 +123,7 @@ import { Client, ClientOptions, SmoldotBytecode } from '../public-types.js'; | ||
initialWritableBytes: number; | ||
writeClosable: boolean; | ||
} | { | ||
type: 'multi-stream'; | ||
handshake: 'webrtc'; | ||
localTlsCertificateMultihash: Uint8Array; | ||
remoteTlsCertificateMultihash: Uint8Array; | ||
localTlsCertificateSha256: Uint8Array; | ||
remoteTlsCertificateSha256: Uint8Array; | ||
}) => void; | ||
@@ -131,0 +130,0 @@ /** |
@@ -290,4 +290,7 @@ "use strict"; | ||
} | ||
// Sanitize `databaseContent`. | ||
if (options.databaseContent !== undefined && typeof options.databaseContent !== 'string') | ||
throw new public_types_js_1.AddChainError("`databaseContent` is not a string"); | ||
const promise = new Promise((resolve) => state.addChainResults.push(resolve)); | ||
state.instance.instance.addChain(options.chainSpec, typeof options.databaseContent === 'string' ? options.databaseContent : "", potentialRelayChainsIds, !!options.disableJsonRpc, jsonRpcMaxPendingRequests, jsonRpcMaxSubscriptions); | ||
state.instance.instance.addChain(options.chainSpec, options.databaseContent || "", potentialRelayChainsIds, !!options.disableJsonRpc, jsonRpcMaxPendingRequests, jsonRpcMaxSubscriptions); | ||
const outcome = yield promise; | ||
@@ -294,0 +297,0 @@ if (!outcome.success) |
@@ -87,6 +87,6 @@ /** | ||
ty: "webrtc"; | ||
targetPort: string; | ||
targetPort: number; | ||
ipVersion: string; | ||
targetIp: string; | ||
remoteCertMultibase: string; | ||
remoteTlsCertificateSha256: Uint8Array; | ||
}; | ||
@@ -111,8 +111,7 @@ export interface Instance { | ||
initialWritableBytes: number; | ||
writeClosable: boolean; | ||
} | { | ||
type: 'multi-stream'; | ||
handshake: 'webrtc'; | ||
localTlsCertificateMultihash: Uint8Array; | ||
remoteTlsCertificateMultihash: Uint8Array; | ||
localTlsCertificateSha256: Uint8Array; | ||
remoteTlsCertificateSha256: Uint8Array; | ||
}) => void; | ||
@@ -138,8 +137,1 @@ connectionReset: (connectionId: number, message: string) => void; | ||
export declare function startLocalInstance(config: Config, wasmModule: WebAssembly.Module, eventCallback: (event: Event) => void): Promise<Instance>; | ||
export declare function parseMultiaddr(address: string, forbidTcp: boolean, forbidWs: boolean, forbidNonLocalWs: boolean, forbidWss: boolean, forbidWebRtc: boolean): { | ||
success: true; | ||
address: ParsedMultiaddr; | ||
} | { | ||
success: false; | ||
error: string; | ||
}; |
@@ -15,3 +15,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.parseMultiaddr = exports.startLocalInstance = void 0; | ||
exports.startLocalInstance = void 0; | ||
// This program is free software: you can redistribute it and/or modify | ||
@@ -131,22 +131,86 @@ // it under the terms of the GNU General Public License as published by | ||
}, | ||
// Must indicate whether the given connection type is supported. | ||
connection_type_supported: (ty) => { | ||
// TODO: consider extracting config options so user can't change the fields dynamically | ||
switch (ty) { | ||
case 0: | ||
case 1: | ||
case 2: { | ||
return config.forbidTcp ? 0 : 1; | ||
} | ||
case 4: | ||
case 5: | ||
case 6: { | ||
return config.forbidWs ? 0 : 1; | ||
} | ||
case 7: { | ||
return config.forbidNonLocalWs ? 0 : 1; | ||
} | ||
case 14: { | ||
return config.forbidWss ? 0 : 1; | ||
} | ||
case 16: | ||
case 17: { | ||
return config.forbidWebRtc ? 0 : 1; | ||
} | ||
default: | ||
// Indicates a bug somewhere. | ||
throw new Error("Invalid connection type passed to `connection_type_supported`"); | ||
} | ||
}, | ||
// Must create a new connection object. This implementation stores the created object in | ||
// `connections`. | ||
connection_new: (connectionId, addrPtr, addrLen, errorBufferIndexPtr) => { | ||
connection_new: (connectionId, addrPtr, addrLen) => { | ||
const instance = state.instance; | ||
const mem = new Uint8Array(instance.exports.memory.buffer); | ||
addrPtr >>>= 0; | ||
addrLen >>>= 0; | ||
errorBufferIndexPtr >>>= 0; | ||
const address = buffer.utf8BytesToString(new Uint8Array(instance.exports.memory.buffer), addrPtr, addrLen); | ||
// TODO: consider extracting config options so user can't change the fields dynamically | ||
const result = parseMultiaddr(address, config.forbidTcp, config.forbidWs, config.forbidNonLocalWs, config.forbidWss, config.forbidWebRtc); | ||
if (result.success) { | ||
eventCallback({ ty: "new-connection", connectionId, address: result.address }); | ||
return 0; | ||
let address; | ||
switch (buffer.readUInt8(mem, addrPtr)) { | ||
case 0: | ||
case 1: | ||
case 2: { | ||
const port = buffer.readUInt16BE(mem, addrPtr + 1); | ||
const hostname = buffer.utf8BytesToString(mem, addrPtr + 3, addrLen - 3); | ||
address = { ty: "tcp", port, hostname }; | ||
break; | ||
} | ||
case 4: | ||
case 6: { | ||
const port = buffer.readUInt16BE(mem, addrPtr + 1); | ||
const hostname = buffer.utf8BytesToString(mem, addrPtr + 3, addrLen - 3); | ||
address = { ty: "websocket", url: "ws://" + hostname + ":" + port }; | ||
break; | ||
} | ||
case 5: { | ||
const port = buffer.readUInt16BE(mem, addrPtr + 1); | ||
const hostname = buffer.utf8BytesToString(mem, addrPtr + 3, addrLen - 3); | ||
address = { ty: "websocket", url: "ws://[" + hostname + "]:" + port }; | ||
break; | ||
} | ||
case 14: { | ||
const port = buffer.readUInt16BE(mem, addrPtr + 1); | ||
const hostname = buffer.utf8BytesToString(mem, addrPtr + 3, addrLen - 3); | ||
address = { ty: "websocket", url: "wss://" + hostname + ":" + port }; | ||
break; | ||
} | ||
case 16: { | ||
const targetPort = buffer.readUInt16BE(mem, addrPtr + 1); | ||
const remoteTlsCertificateSha256 = mem.slice(addrPtr + 3, addrPtr + 35); | ||
const targetIp = buffer.utf8BytesToString(mem, addrPtr + 35, addrLen - 3); | ||
address = { ty: "webrtc", ipVersion: '4', remoteTlsCertificateSha256, targetIp, targetPort }; | ||
break; | ||
} | ||
case 17: { | ||
const targetPort = buffer.readUInt16BE(mem, addrPtr + 1); | ||
const remoteTlsCertificateSha256 = mem.slice(addrPtr + 3, addrPtr + 35); | ||
const targetIp = buffer.utf8BytesToString(mem, addrPtr + 35, addrLen - 3); | ||
address = { ty: "webrtc", ipVersion: '6', remoteTlsCertificateSha256, targetIp, targetPort }; | ||
break; | ||
} | ||
default: | ||
// Indicates a bug somewhere. | ||
throw new Error("Invalid encoded address passed to `connection_new`"); | ||
} | ||
else { | ||
const mem = new Uint8Array(instance.exports.memory.buffer); | ||
state.bufferIndices[0] = new TextEncoder().encode(result.error); | ||
buffer.writeUInt32LE(mem, errorBufferIndexPtr, 0); | ||
return 1; | ||
} | ||
eventCallback({ ty: "new-connection", connectionId, address }); | ||
}, | ||
@@ -481,10 +545,10 @@ // Must close and destroy the connection object. | ||
case 'single-stream': { | ||
state.instance.exports.connection_open_single_stream(connectionId, 0, info.initialWritableBytes, info.writeClosable ? 1 : 0); | ||
state.instance.exports.connection_open_single_stream(connectionId, info.initialWritableBytes); | ||
break; | ||
} | ||
case 'multi-stream': { | ||
const handshakeTy = new Uint8Array(1 + info.localTlsCertificateMultihash.length + info.remoteTlsCertificateMultihash.length); | ||
const handshakeTy = new Uint8Array(1 + info.localTlsCertificateSha256.length + info.remoteTlsCertificateSha256.length); | ||
buffer.writeUInt8(handshakeTy, 0, 0); | ||
handshakeTy.set(info.localTlsCertificateMultihash, 1); | ||
handshakeTy.set(info.remoteTlsCertificateMultihash, 1 + info.localTlsCertificateMultihash.length); | ||
handshakeTy.set(info.localTlsCertificateSha256, 1); | ||
handshakeTy.set(info.remoteTlsCertificateSha256, 1 + info.localTlsCertificateSha256.length); | ||
state.bufferIndices[0] = handshakeTy; | ||
@@ -530,47 +594,1 @@ state.instance.exports.connection_open_multi_stream(connectionId, 0); | ||
exports.startLocalInstance = startLocalInstance; | ||
// TODO: consider moving this function somewhere else or something | ||
function parseMultiaddr(address, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss, forbidWebRtc) { | ||
const tcpParsed = address.match(/^\/(ip4|ip6|dns4|dns6|dns)\/(.*?)\/tcp\/(.*?)$/); | ||
// TODO: remove support for `/wss` in a long time (https://github.com/paritytech/smoldot/issues/1940) | ||
const wsParsed = address.match(/^\/(ip4|ip6|dns4|dns6|dns)\/(.*?)\/tcp\/(.*?)\/(ws|wss|tls\/ws)$/); | ||
const webRTCParsed = address.match(/^\/(ip4|ip6)\/(.*?)\/udp\/(.*?)\/webrtc-direct\/certhash\/(.*?)$/); | ||
if (wsParsed != null) { | ||
const proto = (wsParsed[4] == 'ws') ? 'ws' : 'wss'; | ||
if (proto == 'ws' && forbidWs) { | ||
return { success: false, error: 'WebSocket connections not available' }; | ||
} | ||
if (proto == 'wss' && forbidWss) { | ||
return { success: false, error: 'WebSocket secure connections not available' }; | ||
} | ||
if (proto == 'ws' && wsParsed[2] != 'localhost' && wsParsed[2] != '127.0.0.1' && forbidNonLocalWs) { | ||
return { success: false, error: 'Non-local WebSocket connections not available' }; | ||
} | ||
const url = (wsParsed[1] == 'ip6') ? | ||
(proto + "://[" + wsParsed[2] + "]:" + wsParsed[3]) : | ||
(proto + "://" + wsParsed[2] + ":" + wsParsed[3]); | ||
return { success: true, address: { ty: "websocket", url } }; | ||
} | ||
else if (tcpParsed != null) { | ||
if (forbidTcp) { | ||
return { success: false, error: 'TCP connections not available' }; | ||
} | ||
return { success: true, address: { ty: "tcp", hostname: tcpParsed[2], port: parseInt(tcpParsed[3]) } }; | ||
} | ||
else if (webRTCParsed != null) { | ||
const targetPort = webRTCParsed[3]; | ||
if (forbidWebRtc) { | ||
return { success: false, error: 'WebRTC connections not available' }; | ||
} | ||
if (targetPort === '0') { | ||
return { success: false, error: 'Invalid WebRTC target port' }; | ||
} | ||
const ipVersion = webRTCParsed[1] == 'ip4' ? '4' : '6'; | ||
const targetIp = webRTCParsed[2]; | ||
const remoteCertMultibase = webRTCParsed[4]; | ||
return { success: true, address: { ty: "webrtc", targetPort, ipVersion, targetIp, remoteCertMultibase } }; | ||
} | ||
else { | ||
return { success: false, error: 'Unrecognized multiaddr format' }; | ||
} | ||
} | ||
exports.parseMultiaddr = parseMultiaddr; |
@@ -17,3 +17,2 @@ "use strict"; | ||
const client_js_1 = require("./internals/client.js"); | ||
const base64_js_1 = require("./internals/base64.js"); | ||
var public_types_js_1 = require("./public-types.js"); | ||
@@ -111,3 +110,3 @@ Object.defineProperty(exports, "AddChainError", { enumerable: true, get: function () { return public_types_js_1.AddChainError; } }); | ||
type: 'single-stream', handshake: 'multistream-select-noise-yamux', | ||
initialWritableBytes: 1024 * 1024, writeClosable: false, | ||
initialWritableBytes: 1024 * 1024 | ||
}); | ||
@@ -164,9 +163,3 @@ }; | ||
else if (config.address.ty === "webrtc") { | ||
const { targetPort, ipVersion, targetIp, remoteCertMultibase } = config.address; | ||
// The payload of `/certhash` is the hash of the self-generated certificate that the | ||
// server presents. | ||
// This function throws an exception if the certhash isn't correct. For this reason, this call | ||
// is performed as part of the parsing of the multiaddr. | ||
const remoteCertMultihash = (0, base64_js_1.multibaseBase64Decode)(remoteCertMultibase); | ||
const remoteCertSha256Hash = multihashToSha256(remoteCertMultihash); | ||
const { targetPort, ipVersion, targetIp, remoteTlsCertificateSha256 } = config.address; | ||
// TODO: detect localhost for Firefox? https://bugzilla.mozilla.org/show_bug.cgi?id=1659672 | ||
@@ -185,6 +178,6 @@ // Note that `pc` can be the connection, but also null or undefined. | ||
let handshakeDataChannel; | ||
// Multihash-encoded DTLS certificate of the local node. Unknown as long as it hasn't been | ||
// SHA256 hash of the DTLS certificate of the local node. Unknown as long as it hasn't been | ||
// generated. | ||
// TODO: could be merged with `pc` in one variable, and maybe even the other fields as well | ||
let localTlsCertificateMultihash; | ||
let localTlsCertificateSha256; | ||
// Kills all the JavaScript objects (the connection and all its substreams), ensuring that no | ||
@@ -239,4 +232,4 @@ // callback will be called again. Doesn't report anything to smoldot, as this should be done | ||
// value is always defined. | ||
localTlsCertificateMultihash: localTlsCertificateMultihash, | ||
remoteTlsCertificateMultihash: remoteCertMultihash | ||
localTlsCertificateSha256: localTlsCertificateSha256, | ||
remoteTlsCertificateSha256, | ||
}); | ||
@@ -346,5 +339,4 @@ } | ||
} | ||
localTlsCertificateMultihash = new Uint8Array(34); | ||
localTlsCertificateMultihash.set([0x12, 32], 0); | ||
localTlsCertificateMultihash.set(localTlsCertificateHex.split(':').map((s) => parseInt(s, 16)), 2); | ||
localTlsCertificateSha256 = new Uint8Array(32); | ||
localTlsCertificateSha256.set(localTlsCertificateHex.split(':').map((s) => parseInt(s, 16)), 0); | ||
// `onconnectionstatechange` is used to detect when the connection has closed or has failed | ||
@@ -385,3 +377,3 @@ // to open. | ||
// Transform certificate hash into fingerprint (upper-hex; each byte separated by ":"). | ||
const fingerprint = Array.from(remoteCertSha256Hash).map((n) => ("0" + n.toString(16)).slice(-2).toUpperCase()).join(':'); | ||
const fingerprint = Array.from(remoteTlsCertificateSha256).map((n) => ("0" + n.toString(16)).slice(-2).toUpperCase()).join(':'); | ||
// Note that the trailing line feed is important, as otherwise Chrome | ||
@@ -413,3 +405,3 @@ // fails to parse the payload. | ||
// RFCs: 8839, 8866, 8841 | ||
"m=application " + targetPort + " " + "UDP/DTLS/SCTP webrtc-datachannel" + "\n" + | ||
"m=application " + String(targetPort) + " " + "UDP/DTLS/SCTP webrtc-datachannel" + "\n" + | ||
// Indicates the IP address of the remote. | ||
@@ -445,3 +437,3 @@ // Note that "IN" means "Internet" (and not "input"). | ||
// checks (RFC8839). | ||
"a=candidate:1 1 UDP 1 " + targetIp + " " + targetPort + " typ host" + "\n"; | ||
"a=candidate:1 1 UDP 1 " + targetIp + " " + String(targetPort) + " typ host" + "\n"; | ||
yield pc.setRemoteDescription({ type: "answer", sdp: remoteSdp }); | ||
@@ -518,10 +510,1 @@ }); | ||
} | ||
/// Parses a multihash-multibase-encoded string into a SHA256 hash. | ||
/// | ||
/// Throws an exception if the multihash algorithm isn't SHA256. | ||
const multihashToSha256 = (certMultihash) => { | ||
if (certMultihash.length != 34 || certMultihash[0] != 0x12 || certMultihash[1] != 32) { | ||
throw new Error('Certificate multihash is not SHA-256'); | ||
} | ||
return new Uint8Array(certMultihash.slice(2)); | ||
}; |
@@ -83,3 +83,3 @@ "use strict"; | ||
socket.onopen = () => { | ||
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024, writeClosable: false }); | ||
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024 }); | ||
}; | ||
@@ -139,3 +139,3 @@ socket.onclose = (event) => { | ||
established === null || established === void 0 ? void 0 : established.setNoDelay(); | ||
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024, writeClosable: true }); | ||
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024 }); | ||
// Spawns an asynchronous task that continuously reads from the socket. | ||
@@ -142,0 +142,0 @@ // Every time data is read, the task re-executes itself in order to continue reading. |
@@ -79,3 +79,3 @@ "use strict"; | ||
socket.onopen = () => { | ||
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024, writeClosable: false }); | ||
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024 }); | ||
}; | ||
@@ -135,3 +135,3 @@ socket.onclose = (event) => { | ||
type: 'single-stream', handshake: 'multistream-select-noise-yamux', | ||
initialWritableBytes: socket.writableHighWaterMark, writeClosable: true | ||
initialWritableBytes: socket.writableHighWaterMark | ||
}); | ||
@@ -138,0 +138,0 @@ }); |
export declare function utf8BytesToString(buffer: Uint8Array, offset: number, length: number): string; | ||
export declare function readUInt8(buffer: Uint8Array, offset: number): number; | ||
export declare function readUInt16BE(buffer: Uint8Array, offset: number): number; | ||
export declare function readUInt32LE(buffer: Uint8Array, offset: number): number; | ||
@@ -3,0 +5,0 @@ /** |
@@ -20,2 +20,10 @@ // Smoldot | ||
} | ||
export function readUInt8(buffer, offset) { | ||
checkRange(buffer, offset, 1); | ||
return buffer[offset]; | ||
} | ||
export function readUInt16BE(buffer, offset) { | ||
checkRange(buffer, offset, 2); | ||
return ((buffer[offset] << 8) | buffer[offset + 1]); | ||
} | ||
export function readUInt32LE(buffer, offset) { | ||
@@ -22,0 +30,0 @@ checkRange(buffer, offset, 4); |
@@ -123,8 +123,7 @@ import { Client, ClientOptions, SmoldotBytecode } from '../public-types.js'; | ||
initialWritableBytes: number; | ||
writeClosable: boolean; | ||
} | { | ||
type: 'multi-stream'; | ||
handshake: 'webrtc'; | ||
localTlsCertificateMultihash: Uint8Array; | ||
remoteTlsCertificateMultihash: Uint8Array; | ||
localTlsCertificateSha256: Uint8Array; | ||
remoteTlsCertificateSha256: Uint8Array; | ||
}) => void; | ||
@@ -131,0 +130,0 @@ /** |
@@ -287,4 +287,7 @@ // Smoldot | ||
} | ||
// Sanitize `databaseContent`. | ||
if (options.databaseContent !== undefined && typeof options.databaseContent !== 'string') | ||
throw new AddChainError("`databaseContent` is not a string"); | ||
const promise = new Promise((resolve) => state.addChainResults.push(resolve)); | ||
state.instance.instance.addChain(options.chainSpec, typeof options.databaseContent === 'string' ? options.databaseContent : "", potentialRelayChainsIds, !!options.disableJsonRpc, jsonRpcMaxPendingRequests, jsonRpcMaxSubscriptions); | ||
state.instance.instance.addChain(options.chainSpec, options.databaseContent || "", potentialRelayChainsIds, !!options.disableJsonRpc, jsonRpcMaxPendingRequests, jsonRpcMaxSubscriptions); | ||
const outcome = yield promise; | ||
@@ -291,0 +294,0 @@ if (!outcome.success) |
@@ -87,6 +87,6 @@ /** | ||
ty: "webrtc"; | ||
targetPort: string; | ||
targetPort: number; | ||
ipVersion: string; | ||
targetIp: string; | ||
remoteCertMultibase: string; | ||
remoteTlsCertificateSha256: Uint8Array; | ||
}; | ||
@@ -111,8 +111,7 @@ export interface Instance { | ||
initialWritableBytes: number; | ||
writeClosable: boolean; | ||
} | { | ||
type: 'multi-stream'; | ||
handshake: 'webrtc'; | ||
localTlsCertificateMultihash: Uint8Array; | ||
remoteTlsCertificateMultihash: Uint8Array; | ||
localTlsCertificateSha256: Uint8Array; | ||
remoteTlsCertificateSha256: Uint8Array; | ||
}) => void; | ||
@@ -138,8 +137,1 @@ connectionReset: (connectionId: number, message: string) => void; | ||
export declare function startLocalInstance(config: Config, wasmModule: WebAssembly.Module, eventCallback: (event: Event) => void): Promise<Instance>; | ||
export declare function parseMultiaddr(address: string, forbidTcp: boolean, forbidWs: boolean, forbidNonLocalWs: boolean, forbidWss: boolean, forbidWebRtc: boolean): { | ||
success: true; | ||
address: ParsedMultiaddr; | ||
} | { | ||
success: false; | ||
error: string; | ||
}; |
@@ -127,22 +127,86 @@ // Smoldot | ||
}, | ||
// Must indicate whether the given connection type is supported. | ||
connection_type_supported: (ty) => { | ||
// TODO: consider extracting config options so user can't change the fields dynamically | ||
switch (ty) { | ||
case 0: | ||
case 1: | ||
case 2: { | ||
return config.forbidTcp ? 0 : 1; | ||
} | ||
case 4: | ||
case 5: | ||
case 6: { | ||
return config.forbidWs ? 0 : 1; | ||
} | ||
case 7: { | ||
return config.forbidNonLocalWs ? 0 : 1; | ||
} | ||
case 14: { | ||
return config.forbidWss ? 0 : 1; | ||
} | ||
case 16: | ||
case 17: { | ||
return config.forbidWebRtc ? 0 : 1; | ||
} | ||
default: | ||
// Indicates a bug somewhere. | ||
throw new Error("Invalid connection type passed to `connection_type_supported`"); | ||
} | ||
}, | ||
// Must create a new connection object. This implementation stores the created object in | ||
// `connections`. | ||
connection_new: (connectionId, addrPtr, addrLen, errorBufferIndexPtr) => { | ||
connection_new: (connectionId, addrPtr, addrLen) => { | ||
const instance = state.instance; | ||
const mem = new Uint8Array(instance.exports.memory.buffer); | ||
addrPtr >>>= 0; | ||
addrLen >>>= 0; | ||
errorBufferIndexPtr >>>= 0; | ||
const address = buffer.utf8BytesToString(new Uint8Array(instance.exports.memory.buffer), addrPtr, addrLen); | ||
// TODO: consider extracting config options so user can't change the fields dynamically | ||
const result = parseMultiaddr(address, config.forbidTcp, config.forbidWs, config.forbidNonLocalWs, config.forbidWss, config.forbidWebRtc); | ||
if (result.success) { | ||
eventCallback({ ty: "new-connection", connectionId, address: result.address }); | ||
return 0; | ||
let address; | ||
switch (buffer.readUInt8(mem, addrPtr)) { | ||
case 0: | ||
case 1: | ||
case 2: { | ||
const port = buffer.readUInt16BE(mem, addrPtr + 1); | ||
const hostname = buffer.utf8BytesToString(mem, addrPtr + 3, addrLen - 3); | ||
address = { ty: "tcp", port, hostname }; | ||
break; | ||
} | ||
case 4: | ||
case 6: { | ||
const port = buffer.readUInt16BE(mem, addrPtr + 1); | ||
const hostname = buffer.utf8BytesToString(mem, addrPtr + 3, addrLen - 3); | ||
address = { ty: "websocket", url: "ws://" + hostname + ":" + port }; | ||
break; | ||
} | ||
case 5: { | ||
const port = buffer.readUInt16BE(mem, addrPtr + 1); | ||
const hostname = buffer.utf8BytesToString(mem, addrPtr + 3, addrLen - 3); | ||
address = { ty: "websocket", url: "ws://[" + hostname + "]:" + port }; | ||
break; | ||
} | ||
case 14: { | ||
const port = buffer.readUInt16BE(mem, addrPtr + 1); | ||
const hostname = buffer.utf8BytesToString(mem, addrPtr + 3, addrLen - 3); | ||
address = { ty: "websocket", url: "wss://" + hostname + ":" + port }; | ||
break; | ||
} | ||
case 16: { | ||
const targetPort = buffer.readUInt16BE(mem, addrPtr + 1); | ||
const remoteTlsCertificateSha256 = mem.slice(addrPtr + 3, addrPtr + 35); | ||
const targetIp = buffer.utf8BytesToString(mem, addrPtr + 35, addrLen - 3); | ||
address = { ty: "webrtc", ipVersion: '4', remoteTlsCertificateSha256, targetIp, targetPort }; | ||
break; | ||
} | ||
case 17: { | ||
const targetPort = buffer.readUInt16BE(mem, addrPtr + 1); | ||
const remoteTlsCertificateSha256 = mem.slice(addrPtr + 3, addrPtr + 35); | ||
const targetIp = buffer.utf8BytesToString(mem, addrPtr + 35, addrLen - 3); | ||
address = { ty: "webrtc", ipVersion: '6', remoteTlsCertificateSha256, targetIp, targetPort }; | ||
break; | ||
} | ||
default: | ||
// Indicates a bug somewhere. | ||
throw new Error("Invalid encoded address passed to `connection_new`"); | ||
} | ||
else { | ||
const mem = new Uint8Array(instance.exports.memory.buffer); | ||
state.bufferIndices[0] = new TextEncoder().encode(result.error); | ||
buffer.writeUInt32LE(mem, errorBufferIndexPtr, 0); | ||
return 1; | ||
} | ||
eventCallback({ ty: "new-connection", connectionId, address }); | ||
}, | ||
@@ -477,10 +541,10 @@ // Must close and destroy the connection object. | ||
case 'single-stream': { | ||
state.instance.exports.connection_open_single_stream(connectionId, 0, info.initialWritableBytes, info.writeClosable ? 1 : 0); | ||
state.instance.exports.connection_open_single_stream(connectionId, info.initialWritableBytes); | ||
break; | ||
} | ||
case 'multi-stream': { | ||
const handshakeTy = new Uint8Array(1 + info.localTlsCertificateMultihash.length + info.remoteTlsCertificateMultihash.length); | ||
const handshakeTy = new Uint8Array(1 + info.localTlsCertificateSha256.length + info.remoteTlsCertificateSha256.length); | ||
buffer.writeUInt8(handshakeTy, 0, 0); | ||
handshakeTy.set(info.localTlsCertificateMultihash, 1); | ||
handshakeTy.set(info.remoteTlsCertificateMultihash, 1 + info.localTlsCertificateMultihash.length); | ||
handshakeTy.set(info.localTlsCertificateSha256, 1); | ||
handshakeTy.set(info.remoteTlsCertificateSha256, 1 + info.localTlsCertificateSha256.length); | ||
state.bufferIndices[0] = handshakeTy; | ||
@@ -525,46 +589,1 @@ state.instance.exports.connection_open_multi_stream(connectionId, 0); | ||
} | ||
// TODO: consider moving this function somewhere else or something | ||
export function parseMultiaddr(address, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss, forbidWebRtc) { | ||
const tcpParsed = address.match(/^\/(ip4|ip6|dns4|dns6|dns)\/(.*?)\/tcp\/(.*?)$/); | ||
// TODO: remove support for `/wss` in a long time (https://github.com/paritytech/smoldot/issues/1940) | ||
const wsParsed = address.match(/^\/(ip4|ip6|dns4|dns6|dns)\/(.*?)\/tcp\/(.*?)\/(ws|wss|tls\/ws)$/); | ||
const webRTCParsed = address.match(/^\/(ip4|ip6)\/(.*?)\/udp\/(.*?)\/webrtc-direct\/certhash\/(.*?)$/); | ||
if (wsParsed != null) { | ||
const proto = (wsParsed[4] == 'ws') ? 'ws' : 'wss'; | ||
if (proto == 'ws' && forbidWs) { | ||
return { success: false, error: 'WebSocket connections not available' }; | ||
} | ||
if (proto == 'wss' && forbidWss) { | ||
return { success: false, error: 'WebSocket secure connections not available' }; | ||
} | ||
if (proto == 'ws' && wsParsed[2] != 'localhost' && wsParsed[2] != '127.0.0.1' && forbidNonLocalWs) { | ||
return { success: false, error: 'Non-local WebSocket connections not available' }; | ||
} | ||
const url = (wsParsed[1] == 'ip6') ? | ||
(proto + "://[" + wsParsed[2] + "]:" + wsParsed[3]) : | ||
(proto + "://" + wsParsed[2] + ":" + wsParsed[3]); | ||
return { success: true, address: { ty: "websocket", url } }; | ||
} | ||
else if (tcpParsed != null) { | ||
if (forbidTcp) { | ||
return { success: false, error: 'TCP connections not available' }; | ||
} | ||
return { success: true, address: { ty: "tcp", hostname: tcpParsed[2], port: parseInt(tcpParsed[3]) } }; | ||
} | ||
else if (webRTCParsed != null) { | ||
const targetPort = webRTCParsed[3]; | ||
if (forbidWebRtc) { | ||
return { success: false, error: 'WebRTC connections not available' }; | ||
} | ||
if (targetPort === '0') { | ||
return { success: false, error: 'Invalid WebRTC target port' }; | ||
} | ||
const ipVersion = webRTCParsed[1] == 'ip4' ? '4' : '6'; | ||
const targetIp = webRTCParsed[2]; | ||
const remoteCertMultibase = webRTCParsed[4]; | ||
return { success: true, address: { ty: "webrtc", targetPort, ipVersion, targetIp, remoteCertMultibase } }; | ||
} | ||
else { | ||
return { success: false, error: 'Unrecognized multiaddr format' }; | ||
} | ||
} |
@@ -14,3 +14,2 @@ // Smoldot | ||
import { start as innerStart } from './internals/client.js'; | ||
import { multibaseBase64Decode } from './internals/base64.js'; | ||
export { AddChainError, AlreadyDestroyedError, CrashError, JsonRpcDisabledError, MalformedJsonRpcError, QueueFullError } from './public-types.js'; | ||
@@ -101,3 +100,3 @@ /** | ||
type: 'single-stream', handshake: 'multistream-select-noise-yamux', | ||
initialWritableBytes: 1024 * 1024, writeClosable: false, | ||
initialWritableBytes: 1024 * 1024 | ||
}); | ||
@@ -154,9 +153,3 @@ }; | ||
else if (config.address.ty === "webrtc") { | ||
const { targetPort, ipVersion, targetIp, remoteCertMultibase } = config.address; | ||
// The payload of `/certhash` is the hash of the self-generated certificate that the | ||
// server presents. | ||
// This function throws an exception if the certhash isn't correct. For this reason, this call | ||
// is performed as part of the parsing of the multiaddr. | ||
const remoteCertMultihash = multibaseBase64Decode(remoteCertMultibase); | ||
const remoteCertSha256Hash = multihashToSha256(remoteCertMultihash); | ||
const { targetPort, ipVersion, targetIp, remoteTlsCertificateSha256 } = config.address; | ||
// TODO: detect localhost for Firefox? https://bugzilla.mozilla.org/show_bug.cgi?id=1659672 | ||
@@ -175,6 +168,6 @@ // Note that `pc` can be the connection, but also null or undefined. | ||
let handshakeDataChannel; | ||
// Multihash-encoded DTLS certificate of the local node. Unknown as long as it hasn't been | ||
// SHA256 hash of the DTLS certificate of the local node. Unknown as long as it hasn't been | ||
// generated. | ||
// TODO: could be merged with `pc` in one variable, and maybe even the other fields as well | ||
let localTlsCertificateMultihash; | ||
let localTlsCertificateSha256; | ||
// Kills all the JavaScript objects (the connection and all its substreams), ensuring that no | ||
@@ -229,4 +222,4 @@ // callback will be called again. Doesn't report anything to smoldot, as this should be done | ||
// value is always defined. | ||
localTlsCertificateMultihash: localTlsCertificateMultihash, | ||
remoteTlsCertificateMultihash: remoteCertMultihash | ||
localTlsCertificateSha256: localTlsCertificateSha256, | ||
remoteTlsCertificateSha256, | ||
}); | ||
@@ -336,5 +329,4 @@ } | ||
} | ||
localTlsCertificateMultihash = new Uint8Array(34); | ||
localTlsCertificateMultihash.set([0x12, 32], 0); | ||
localTlsCertificateMultihash.set(localTlsCertificateHex.split(':').map((s) => parseInt(s, 16)), 2); | ||
localTlsCertificateSha256 = new Uint8Array(32); | ||
localTlsCertificateSha256.set(localTlsCertificateHex.split(':').map((s) => parseInt(s, 16)), 0); | ||
// `onconnectionstatechange` is used to detect when the connection has closed or has failed | ||
@@ -375,3 +367,3 @@ // to open. | ||
// Transform certificate hash into fingerprint (upper-hex; each byte separated by ":"). | ||
const fingerprint = Array.from(remoteCertSha256Hash).map((n) => ("0" + n.toString(16)).slice(-2).toUpperCase()).join(':'); | ||
const fingerprint = Array.from(remoteTlsCertificateSha256).map((n) => ("0" + n.toString(16)).slice(-2).toUpperCase()).join(':'); | ||
// Note that the trailing line feed is important, as otherwise Chrome | ||
@@ -403,3 +395,3 @@ // fails to parse the payload. | ||
// RFCs: 8839, 8866, 8841 | ||
"m=application " + targetPort + " " + "UDP/DTLS/SCTP webrtc-datachannel" + "\n" + | ||
"m=application " + String(targetPort) + " " + "UDP/DTLS/SCTP webrtc-datachannel" + "\n" + | ||
// Indicates the IP address of the remote. | ||
@@ -435,3 +427,3 @@ // Note that "IN" means "Internet" (and not "input"). | ||
// checks (RFC8839). | ||
"a=candidate:1 1 UDP 1 " + targetIp + " " + targetPort + " typ host" + "\n"; | ||
"a=candidate:1 1 UDP 1 " + targetIp + " " + String(targetPort) + " typ host" + "\n"; | ||
yield pc.setRemoteDescription({ type: "answer", sdp: remoteSdp }); | ||
@@ -508,10 +500,1 @@ }); | ||
} | ||
/// Parses a multihash-multibase-encoded string into a SHA256 hash. | ||
/// | ||
/// Throws an exception if the multihash algorithm isn't SHA256. | ||
const multihashToSha256 = (certMultihash) => { | ||
if (certMultihash.length != 34 || certMultihash[0] != 0x12 || certMultihash[1] != 32) { | ||
throw new Error('Certificate multihash is not SHA-256'); | ||
} | ||
return new Uint8Array(certMultihash.slice(2)); | ||
}; |
@@ -73,3 +73,3 @@ // Smoldot | ||
socket.onopen = () => { | ||
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024, writeClosable: false }); | ||
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024 }); | ||
}; | ||
@@ -129,3 +129,3 @@ socket.onclose = (event) => { | ||
established === null || established === void 0 ? void 0 : established.setNoDelay(); | ||
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024, writeClosable: true }); | ||
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024 }); | ||
// Spawns an asynchronous task that continuously reads from the socket. | ||
@@ -132,0 +132,0 @@ // Every time data is read, the task re-executes itself in order to continue reading. |
@@ -69,3 +69,3 @@ // Smoldot | ||
socket.onopen = () => { | ||
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024, writeClosable: false }); | ||
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024 }); | ||
}; | ||
@@ -125,3 +125,3 @@ socket.onclose = (event) => { | ||
type: 'single-stream', handshake: 'multistream-select-noise-yamux', | ||
initialWritableBytes: socket.writableHighWaterMark, writeClosable: true | ||
initialWritableBytes: socket.writableHighWaterMark | ||
}); | ||
@@ -128,0 +128,0 @@ }); |
{ | ||
"name": "smoldot", | ||
"version": "1.0.11", | ||
"version": "1.0.12", | ||
"description": "Light client that connects to Polkadot and Substrate-based blockchains", | ||
@@ -5,0 +5,0 @@ "contributors": [ |
@@ -128,1 +128,8 @@ # Light client for Polkadot and Substrate-based chains | ||
``` | ||
Note that importing sub-paths (for example importing `smoldot/worker`) relies on a relatively | ||
modern JavaScript feature. If you import a smoldot sub-path from a TypeScript file, you might have | ||
to configure TypeScript to use `"moduleResolution": "node16"`. [The official TypeScript | ||
documentation itself recommends setting this configuration option to | ||
`node`](https://www.typescriptlang.org/docs/handbook/module-resolution.html#module-resolution-strategies), | ||
and it is likely that `node16` becomes the go-to module resolution scheme in the future. |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
135
6311663
88
30579