smoldot 0.3.6 → 0.7.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -21
- package/dist/cjs/base64.d.ts +22 -0
- package/dist/cjs/base64.js +104 -0
- package/dist/cjs/client.d.ts +287 -0
- package/dist/cjs/client.js +175 -0
- package/dist/cjs/index-browser.d.ts +11 -0
- package/dist/cjs/index-browser.js +465 -0
- package/dist/cjs/index-deno.d.ts +10 -0
- package/dist/cjs/index-deno.js +263 -0
- package/dist/cjs/index-nodejs.d.ts +10 -0
- package/dist/cjs/index-nodejs.js +174 -0
- package/dist/cjs/instance/autogen/wasm.d.ts +2 -0
- package/dist/cjs/instance/autogen/wasm.js +6 -0
- package/dist/cjs/instance/autogen/wasm0.d.ts +1 -0
- package/dist/cjs/instance/autogen/wasm0.js +4 -0
- package/dist/cjs/instance/autogen/wasm1.d.ts +1 -0
- package/dist/cjs/instance/autogen/wasm1.js +4 -0
- package/dist/cjs/instance/autogen/wasm2.d.ts +1 -0
- package/dist/cjs/instance/autogen/wasm2.js +4 -0
- package/dist/cjs/instance/bindings-smoldot-light.d.ts +154 -0
- package/dist/cjs/instance/bindings-smoldot-light.js +266 -0
- package/dist/cjs/instance/bindings-wasi.d.ts +23 -0
- package/dist/cjs/instance/bindings-wasi.js +142 -0
- package/dist/cjs/instance/bindings.d.ts +31 -0
- package/dist/cjs/instance/bindings.js +5 -0
- package/dist/cjs/instance/buffer.d.ts +10 -0
- package/dist/cjs/instance/buffer.js +53 -0
- package/dist/cjs/instance/instance.d.ts +45 -0
- package/dist/cjs/instance/instance.js +301 -0
- package/dist/cjs/instance/raw-instance.d.ts +65 -0
- package/dist/cjs/instance/raw-instance.js +72 -0
- package/dist/cjs/package.json +3 -0
- package/dist/mjs/base64.d.ts +22 -0
- package/dist/mjs/base64.js +97 -0
- package/dist/mjs/client.d.ts +287 -0
- package/dist/mjs/client.js +165 -0
- package/dist/mjs/index-browser.d.ts +11 -0
- package/dist/mjs/index-browser.js +455 -0
- package/dist/mjs/index-deno.d.ts +10 -0
- package/dist/mjs/index-deno.js +253 -0
- package/dist/mjs/index-nodejs.d.ts +10 -0
- package/dist/mjs/index-nodejs.js +164 -0
- package/dist/mjs/instance/autogen/wasm.d.ts +2 -0
- package/dist/mjs/instance/autogen/wasm.js +4 -0
- package/dist/mjs/instance/autogen/wasm0.d.ts +1 -0
- package/dist/mjs/instance/autogen/wasm0.js +1 -0
- package/dist/mjs/instance/autogen/wasm1.d.ts +1 -0
- package/dist/mjs/instance/autogen/wasm1.js +1 -0
- package/dist/mjs/instance/autogen/wasm2.d.ts +1 -0
- package/dist/mjs/instance/autogen/wasm2.js +1 -0
- package/dist/mjs/instance/bindings-smoldot-light.d.ts +154 -0
- package/dist/mjs/instance/bindings-smoldot-light.js +261 -0
- package/dist/mjs/instance/bindings-wasi.d.ts +23 -0
- package/{src → dist/mjs/instance}/bindings-wasi.js +47 -58
- package/dist/mjs/instance/bindings.d.ts +31 -0
- package/dist/mjs/instance/bindings.js +4 -0
- package/dist/mjs/instance/buffer.d.ts +10 -0
- package/dist/mjs/instance/buffer.js +46 -0
- package/dist/mjs/instance/instance.d.ts +45 -0
- package/dist/mjs/instance/instance.js +293 -0
- package/dist/mjs/instance/raw-instance.d.ts +65 -0
- package/dist/mjs/instance/raw-instance.js +67 -0
- package/dist/mjs/package.json +3 -0
- package/package.json +35 -19
- package/src/autogen/wasm.js +0 -1
- package/src/bindings-smoldot-js.js +0 -223
- package/src/compat-browser.js +0 -25
- package/src/compat-nodejs.js +0 -30
- package/src/health.js +0 -206
- package/src/index.d.ts +0 -68
- package/src/index.js +0 -192
- package/src/index.test-d.ts +0 -36
- package/src/tsconfig.json +0 -13
- package/src/worker.js +0 -151
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { SmoldotWasmInstance } from './bindings.js';
|
|
2
|
+
export interface Config {
|
|
3
|
+
instance?: SmoldotWasmInstance;
|
|
4
|
+
/**
|
|
5
|
+
* Returns the number of milliseconds since an arbitrary epoch.
|
|
6
|
+
*/
|
|
7
|
+
performanceNow: () => number;
|
|
8
|
+
/**
|
|
9
|
+
* Tries to open a new connection using the given configuration.
|
|
10
|
+
*
|
|
11
|
+
* @see Connection
|
|
12
|
+
* @throws {@link ConnectionError} If the multiaddress couldn't be parsed or contains an invalid protocol.
|
|
13
|
+
*/
|
|
14
|
+
connect(config: ConnectionConfig): Connection;
|
|
15
|
+
/**
|
|
16
|
+
* Closure to call when the Wasm instance calls `panic`.
|
|
17
|
+
*
|
|
18
|
+
* This callback will always be invoked from within a binding called the Wasm instance.
|
|
19
|
+
*/
|
|
20
|
+
onPanic: (message: string) => never;
|
|
21
|
+
logCallback: (level: number, target: string, message: string) => void;
|
|
22
|
+
jsonRpcResponsesNonEmptyCallback: (chainId: number) => void;
|
|
23
|
+
currentTaskCallback?: (taskName: string | null) => void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Connection to a remote node.
|
|
27
|
+
*
|
|
28
|
+
* At any time, a connection can be in one of the three following states:
|
|
29
|
+
*
|
|
30
|
+
* - `Opening` (initial state)
|
|
31
|
+
* - `Open`
|
|
32
|
+
* - `Reset`
|
|
33
|
+
*
|
|
34
|
+
* When in the `Opening` or `Open` state, the connection can transition to the `Reset` state
|
|
35
|
+
* if the remote closes the connection or refuses the connection altogether. When that
|
|
36
|
+
* happens, `config.onReset` is called. Once in the `Reset` state, the connection cannot
|
|
37
|
+
* transition back to another state.
|
|
38
|
+
*
|
|
39
|
+
* Initially in the `Opening` state, the connection can transition to the `Open` state if the
|
|
40
|
+
* remote accepts the connection. When that happens, `config.onOpen` is called.
|
|
41
|
+
*
|
|
42
|
+
* When in the `Open` state, the connection can receive messages. When a message is received,
|
|
43
|
+
* `config.onMessage` is called.
|
|
44
|
+
*
|
|
45
|
+
* @see connect
|
|
46
|
+
*/
|
|
47
|
+
export interface Connection {
|
|
48
|
+
/**
|
|
49
|
+
* Transitions the connection or one of its substreams to the `Reset` state.
|
|
50
|
+
*
|
|
51
|
+
* If the connection is of type "single-stream", the whole connection must be shut down.
|
|
52
|
+
* If the connection is of type "multi-stream", a `streamId` can be provided, in which case
|
|
53
|
+
* only the given substream is shut down.
|
|
54
|
+
*
|
|
55
|
+
* The `config.onReset` or `config.onStreamReset` callbacks are **not** called.
|
|
56
|
+
*
|
|
57
|
+
* The transition is performed in the background.
|
|
58
|
+
* If the whole connection is to be shut down, none of the callbacks passed to the `Config`
|
|
59
|
+
* must be called again. If only a substream is shut down, the `onStreamReset` and `onMessage`
|
|
60
|
+
* callbacks must not be called again with that substream.
|
|
61
|
+
*/
|
|
62
|
+
reset(streamId?: number): void;
|
|
63
|
+
/**
|
|
64
|
+
* Queues data to be sent on the given connection.
|
|
65
|
+
*
|
|
66
|
+
* The connection must currently be in the `Open` state.
|
|
67
|
+
*
|
|
68
|
+
* The `streamId` must be provided if and only if the connection is of type "multi-stream".
|
|
69
|
+
* It indicates which substream to send the data on.
|
|
70
|
+
*/
|
|
71
|
+
send(data: Uint8Array, streamId?: number): void;
|
|
72
|
+
/**
|
|
73
|
+
* Start opening an additional outbound substream on the given connection.
|
|
74
|
+
*
|
|
75
|
+
* The state of the connection must be `Open`. This function must only be called for
|
|
76
|
+
* connections of type "multi-stream".
|
|
77
|
+
*
|
|
78
|
+
* The `onStreamOpened` callback must later be called with an outbound direction.
|
|
79
|
+
*
|
|
80
|
+
* Note that no mechanism exists in this API to handle the situation where a substream fails
|
|
81
|
+
* to open, as this is not supposed to happen. If you need to handle such a situation, either
|
|
82
|
+
* try again opening a substream again or reset the entire connection.
|
|
83
|
+
*/
|
|
84
|
+
openOutSubstream(): void;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Configuration for a connection.
|
|
88
|
+
*
|
|
89
|
+
* @see connect
|
|
90
|
+
*/
|
|
91
|
+
export interface ConnectionConfig {
|
|
92
|
+
/**
|
|
93
|
+
* Multiaddress in string format that describes which node to try to connect to.
|
|
94
|
+
*
|
|
95
|
+
* Note that this address shouldn't be trusted. The value in this field might have been chosen
|
|
96
|
+
* by a potentially malicious peer.
|
|
97
|
+
*/
|
|
98
|
+
address: string;
|
|
99
|
+
/**
|
|
100
|
+
* Callback called when the connection transitions from the `Opening` to the `Open` state.
|
|
101
|
+
*
|
|
102
|
+
* Must only be called once per connection.
|
|
103
|
+
*/
|
|
104
|
+
onOpen: (info: {
|
|
105
|
+
type: 'single-stream';
|
|
106
|
+
handshake: 'multistream-select-noise-yamux';
|
|
107
|
+
} | {
|
|
108
|
+
type: 'multi-stream';
|
|
109
|
+
handshake: 'webrtc';
|
|
110
|
+
localTlsCertificateMultihash: Uint8Array;
|
|
111
|
+
remoteTlsCertificateMultihash: Uint8Array;
|
|
112
|
+
}) => void;
|
|
113
|
+
/**
|
|
114
|
+
* Callback called when the connection transitions to the `Reset` state.
|
|
115
|
+
*
|
|
116
|
+
* It it **not** called if `Connection.reset` is manually called by the API user.
|
|
117
|
+
*/
|
|
118
|
+
onConnectionReset: (message: string) => void;
|
|
119
|
+
/**
|
|
120
|
+
* Callback called when a new substream has been opened.
|
|
121
|
+
*
|
|
122
|
+
* This function must only be called for connections of type "multi-stream".
|
|
123
|
+
*/
|
|
124
|
+
onStreamOpened: (streamId: number, direction: 'inbound' | 'outbound') => void;
|
|
125
|
+
/**
|
|
126
|
+
* Callback called when a stream transitions to the `Reset` state.
|
|
127
|
+
*
|
|
128
|
+
* It it **not** called if `Connection.resetStream` is manually called by the API user.
|
|
129
|
+
*
|
|
130
|
+
* This function must only be called for connections of type "multi-stream".
|
|
131
|
+
*/
|
|
132
|
+
onStreamReset: (streamId: number) => void;
|
|
133
|
+
/**
|
|
134
|
+
* Callback called when a message sent by the remote has been received.
|
|
135
|
+
*
|
|
136
|
+
* Can only happen while the connection is in the `Open` state.
|
|
137
|
+
*
|
|
138
|
+
* The `streamId` parameter must be provided if and only if the connection is of type
|
|
139
|
+
* "multi-stream".
|
|
140
|
+
*/
|
|
141
|
+
onMessage: (message: Uint8Array, streamId?: number) => void;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Emitted by `connect` if the multiaddress couldn't be parsed or contains an invalid protocol.
|
|
145
|
+
*
|
|
146
|
+
* @see connect
|
|
147
|
+
*/
|
|
148
|
+
export declare class ConnectionError extends Error {
|
|
149
|
+
constructor(message: string);
|
|
150
|
+
}
|
|
151
|
+
export default function (config: Config): {
|
|
152
|
+
imports: WebAssembly.ModuleImports;
|
|
153
|
+
killAll: () => void;
|
|
154
|
+
};
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Smoldot
|
|
3
|
+
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
|
|
4
|
+
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ConnectionError = void 0;
|
|
7
|
+
// This program is free software: you can redistribute it and/or modify
|
|
8
|
+
// it under the terms of the GNU General Public License as published by
|
|
9
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
// (at your option) any later version.
|
|
11
|
+
// This program is distributed in the hope that it will be useful,
|
|
12
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
// GNU General Public License for more details.
|
|
15
|
+
// You should have received a copy of the GNU General Public License
|
|
16
|
+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
//! Exports a function that provides bindings for the bindings found in the Rust part of the code.
|
|
18
|
+
//!
|
|
19
|
+
//! In order to use this code, call the function passing an object, then fill the `instance` field
|
|
20
|
+
//! of that object with the Wasm instance.
|
|
21
|
+
const buffer = require("./buffer.js");
|
|
22
|
+
/**
|
|
23
|
+
* Emitted by `connect` if the multiaddress couldn't be parsed or contains an invalid protocol.
|
|
24
|
+
*
|
|
25
|
+
* @see connect
|
|
26
|
+
*/
|
|
27
|
+
class ConnectionError extends Error {
|
|
28
|
+
constructor(message) {
|
|
29
|
+
super(message);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.ConnectionError = ConnectionError;
|
|
33
|
+
function default_1(config) {
|
|
34
|
+
// Used below to store the list of all connections.
|
|
35
|
+
// The indices within this array are chosen by the Rust code.
|
|
36
|
+
let connections = {};
|
|
37
|
+
// Object containing a boolean indicating whether the `killAll` function has been invoked by
|
|
38
|
+
// the user.
|
|
39
|
+
const killedTracked = { killed: false };
|
|
40
|
+
const killAll = () => {
|
|
41
|
+
killedTracked.killed = true;
|
|
42
|
+
// TODO: kill timers as well?
|
|
43
|
+
for (const connection in connections) {
|
|
44
|
+
connections[connection].reset();
|
|
45
|
+
delete connections[connection];
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const imports = {
|
|
49
|
+
// Must exit with an error. A human-readable message can be found in the WebAssembly
|
|
50
|
+
// memory in the given buffer.
|
|
51
|
+
panic: (ptr, len) => {
|
|
52
|
+
const instance = config.instance;
|
|
53
|
+
ptr >>>= 0;
|
|
54
|
+
len >>>= 0;
|
|
55
|
+
const message = buffer.utf8BytesToString(new Uint8Array(instance.exports.memory.buffer), ptr, len);
|
|
56
|
+
config.onPanic(message);
|
|
57
|
+
},
|
|
58
|
+
// Used by the Rust side to notify that a JSON-RPC response or subscription notification
|
|
59
|
+
// is available in the queue of JSON-RPC responses.
|
|
60
|
+
json_rpc_responses_non_empty: (chainId) => {
|
|
61
|
+
if (killedTracked.killed)
|
|
62
|
+
return;
|
|
63
|
+
config.jsonRpcResponsesNonEmptyCallback(chainId);
|
|
64
|
+
},
|
|
65
|
+
// Used by the Rust side to emit a log entry.
|
|
66
|
+
// See also the `max_log_level` parameter in the configuration.
|
|
67
|
+
log: (level, targetPtr, targetLen, messagePtr, messageLen) => {
|
|
68
|
+
if (killedTracked.killed)
|
|
69
|
+
return;
|
|
70
|
+
const instance = config.instance;
|
|
71
|
+
targetPtr >>>= 0;
|
|
72
|
+
targetLen >>>= 0;
|
|
73
|
+
messagePtr >>>= 0;
|
|
74
|
+
messageLen >>>= 0;
|
|
75
|
+
if (config.logCallback) {
|
|
76
|
+
const mem = new Uint8Array(instance.exports.memory.buffer);
|
|
77
|
+
let target = buffer.utf8BytesToString(mem, targetPtr, targetLen);
|
|
78
|
+
let message = buffer.utf8BytesToString(mem, messagePtr, messageLen);
|
|
79
|
+
config.logCallback(level, target, message);
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
// Must return the UNIX time in milliseconds.
|
|
83
|
+
unix_time_ms: () => Date.now(),
|
|
84
|
+
// Must return the value of a monotonic clock in milliseconds.
|
|
85
|
+
monotonic_clock_ms: () => config.performanceNow(),
|
|
86
|
+
// Must call `timer_finished` after the given number of milliseconds has elapsed.
|
|
87
|
+
start_timer: (id, ms) => {
|
|
88
|
+
if (killedTracked.killed)
|
|
89
|
+
return;
|
|
90
|
+
const instance = config.instance;
|
|
91
|
+
// In both NodeJS and browsers, if `setTimeout` is called with a value larger than
|
|
92
|
+
// 2147483647, the delay is for some reason instead set to 1.
|
|
93
|
+
// As mentioned in the documentation of `start_timer`, it is acceptable to end the
|
|
94
|
+
// timer before the given number of milliseconds has passed.
|
|
95
|
+
if (ms > 2147483647)
|
|
96
|
+
ms = 2147483647;
|
|
97
|
+
// In browsers, `setTimeout` works as expected when `ms` equals 0. However, NodeJS
|
|
98
|
+
// requires a minimum of 1 millisecond (if `0` is passed, it is automatically replaced
|
|
99
|
+
// with `1`) and wants you to use `setImmediate` instead.
|
|
100
|
+
if (ms == 0 && typeof setImmediate === "function") {
|
|
101
|
+
setImmediate(() => {
|
|
102
|
+
if (killedTracked.killed)
|
|
103
|
+
return;
|
|
104
|
+
try {
|
|
105
|
+
instance.exports.timer_finished(id);
|
|
106
|
+
}
|
|
107
|
+
catch (_error) { }
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
setTimeout(() => {
|
|
112
|
+
if (killedTracked.killed)
|
|
113
|
+
return;
|
|
114
|
+
try {
|
|
115
|
+
instance.exports.timer_finished(id);
|
|
116
|
+
}
|
|
117
|
+
catch (_error) { }
|
|
118
|
+
}, ms);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
// Must create a new connection object. This implementation stores the created object in
|
|
122
|
+
// `connections`.
|
|
123
|
+
connection_new: (connectionId, addrPtr, addrLen, errorPtrPtr) => {
|
|
124
|
+
const instance = config.instance;
|
|
125
|
+
addrPtr >>>= 0;
|
|
126
|
+
addrLen >>>= 0;
|
|
127
|
+
errorPtrPtr >>>= 0;
|
|
128
|
+
if (!!connections[connectionId]) {
|
|
129
|
+
throw new Error("internal error: connection already allocated");
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
if (killedTracked.killed)
|
|
133
|
+
throw new Error("killAll invoked");
|
|
134
|
+
const address = buffer.utf8BytesToString(new Uint8Array(instance.exports.memory.buffer), addrPtr, addrLen);
|
|
135
|
+
const connec = config.connect({
|
|
136
|
+
address,
|
|
137
|
+
onOpen: (info) => {
|
|
138
|
+
if (killedTracked.killed)
|
|
139
|
+
return;
|
|
140
|
+
try {
|
|
141
|
+
switch (info.type) {
|
|
142
|
+
case 'single-stream': {
|
|
143
|
+
instance.exports.connection_open_single_stream(connectionId, 0);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case 'multi-stream': {
|
|
147
|
+
const bufferLen = 1 + info.localTlsCertificateMultihash.length + info.remoteTlsCertificateMultihash.length;
|
|
148
|
+
const ptr = instance.exports.alloc(bufferLen) >>> 0;
|
|
149
|
+
const mem = new Uint8Array(instance.exports.memory.buffer);
|
|
150
|
+
buffer.writeUInt8(mem, ptr, 0);
|
|
151
|
+
mem.set(info.localTlsCertificateMultihash, ptr + 1);
|
|
152
|
+
mem.set(info.remoteTlsCertificateMultihash, ptr + 1 + info.localTlsCertificateMultihash.length);
|
|
153
|
+
instance.exports.connection_open_multi_stream(connectionId, ptr, bufferLen);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (_error) { }
|
|
159
|
+
},
|
|
160
|
+
onConnectionReset: (message) => {
|
|
161
|
+
if (killedTracked.killed)
|
|
162
|
+
return;
|
|
163
|
+
try {
|
|
164
|
+
const encoded = new TextEncoder().encode(message);
|
|
165
|
+
const ptr = instance.exports.alloc(encoded.length) >>> 0;
|
|
166
|
+
new Uint8Array(instance.exports.memory.buffer).set(encoded, ptr);
|
|
167
|
+
instance.exports.connection_reset(connectionId, ptr, encoded.length);
|
|
168
|
+
}
|
|
169
|
+
catch (_error) { }
|
|
170
|
+
},
|
|
171
|
+
onMessage: (message, streamId) => {
|
|
172
|
+
if (killedTracked.killed)
|
|
173
|
+
return;
|
|
174
|
+
try {
|
|
175
|
+
const ptr = instance.exports.alloc(message.length) >>> 0;
|
|
176
|
+
new Uint8Array(instance.exports.memory.buffer).set(message, ptr);
|
|
177
|
+
instance.exports.stream_message(connectionId, streamId || 0, ptr, message.length);
|
|
178
|
+
}
|
|
179
|
+
catch (_error) { }
|
|
180
|
+
},
|
|
181
|
+
onStreamOpened: (streamId, direction) => {
|
|
182
|
+
if (killedTracked.killed)
|
|
183
|
+
return;
|
|
184
|
+
try {
|
|
185
|
+
instance.exports.connection_stream_opened(connectionId, streamId, direction === 'outbound' ? 1 : 0);
|
|
186
|
+
}
|
|
187
|
+
catch (_error) { }
|
|
188
|
+
},
|
|
189
|
+
onStreamReset: (streamId) => {
|
|
190
|
+
if (killedTracked.killed)
|
|
191
|
+
return;
|
|
192
|
+
try {
|
|
193
|
+
instance.exports.stream_reset(connectionId, streamId);
|
|
194
|
+
}
|
|
195
|
+
catch (_error) { }
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
connections[connectionId] = connec;
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
const isBadAddress = error instanceof ConnectionError;
|
|
203
|
+
let errorStr = "Unknown error";
|
|
204
|
+
if (error instanceof Error) {
|
|
205
|
+
errorStr = error.toString();
|
|
206
|
+
}
|
|
207
|
+
const mem = new Uint8Array(instance.exports.memory.buffer);
|
|
208
|
+
const encoded = new TextEncoder().encode(errorStr);
|
|
209
|
+
const ptr = instance.exports.alloc(encoded.length) >>> 0;
|
|
210
|
+
mem.set(encoded, ptr);
|
|
211
|
+
buffer.writeUInt32LE(mem, errorPtrPtr, ptr);
|
|
212
|
+
buffer.writeUInt32LE(mem, errorPtrPtr + 4, encoded.length);
|
|
213
|
+
buffer.writeUInt8(mem, errorPtrPtr + 8, isBadAddress ? 1 : 0);
|
|
214
|
+
return 1;
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
// Must close and destroy the connection object.
|
|
218
|
+
reset_connection: (connectionId) => {
|
|
219
|
+
if (killedTracked.killed)
|
|
220
|
+
return;
|
|
221
|
+
const connection = connections[connectionId];
|
|
222
|
+
connection.reset();
|
|
223
|
+
delete connections[connectionId];
|
|
224
|
+
},
|
|
225
|
+
// Opens a new substream on a multi-stream connection.
|
|
226
|
+
connection_stream_open: (connectionId) => {
|
|
227
|
+
const connection = connections[connectionId];
|
|
228
|
+
connection.openOutSubstream();
|
|
229
|
+
},
|
|
230
|
+
// Closes a substream on a multi-stream connection.
|
|
231
|
+
connection_stream_reset: (connectionId, streamId) => {
|
|
232
|
+
const connection = connections[connectionId];
|
|
233
|
+
connection.reset(streamId);
|
|
234
|
+
},
|
|
235
|
+
// Must queue the data found in the WebAssembly memory at the given pointer. It is assumed
|
|
236
|
+
// that this function is called only when the connection is in an open state.
|
|
237
|
+
stream_send: (connectionId, streamId, ptr, len) => {
|
|
238
|
+
if (killedTracked.killed)
|
|
239
|
+
return;
|
|
240
|
+
const instance = config.instance;
|
|
241
|
+
ptr >>>= 0;
|
|
242
|
+
len >>>= 0;
|
|
243
|
+
const data = new Uint8Array(instance.exports.memory.buffer).slice(ptr, ptr + len);
|
|
244
|
+
const connection = connections[connectionId];
|
|
245
|
+
connection.send(data, streamId); // TODO: docs says the streamId is provided only for multi-stream connections, but here it's always provided
|
|
246
|
+
},
|
|
247
|
+
current_task_entered: (ptr, len) => {
|
|
248
|
+
if (killedTracked.killed)
|
|
249
|
+
return;
|
|
250
|
+
const instance = config.instance;
|
|
251
|
+
ptr >>>= 0;
|
|
252
|
+
len >>>= 0;
|
|
253
|
+
const taskName = buffer.utf8BytesToString(new Uint8Array(instance.exports.memory.buffer), ptr, len);
|
|
254
|
+
if (config.currentTaskCallback)
|
|
255
|
+
config.currentTaskCallback(taskName);
|
|
256
|
+
},
|
|
257
|
+
current_task_exit: () => {
|
|
258
|
+
if (killedTracked.killed)
|
|
259
|
+
return;
|
|
260
|
+
if (config.currentTaskCallback)
|
|
261
|
+
config.currentTaskCallback(null);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
return { imports, killAll };
|
|
265
|
+
}
|
|
266
|
+
exports.default = default_1;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { SmoldotWasmInstance } from './bindings.js';
|
|
2
|
+
export interface Config {
|
|
3
|
+
instance?: SmoldotWasmInstance;
|
|
4
|
+
/**
|
|
5
|
+
* Fills the given buffer with randomly-generated bytes.
|
|
6
|
+
*/
|
|
7
|
+
getRandomValues: (buffer: Uint8Array) => void;
|
|
8
|
+
/**
|
|
9
|
+
* List of environment variables to feed to the Rust program. An array of strings.
|
|
10
|
+
* Example: `["RUST_BACKTRACE=1", "RUST_LOG=foo"];`
|
|
11
|
+
*
|
|
12
|
+
* Must never be modified after the bindings have been initialized.
|
|
13
|
+
*/
|
|
14
|
+
envVars: string[];
|
|
15
|
+
/**
|
|
16
|
+
* Closure to call when the Wasm instance calls `proc_exit`.
|
|
17
|
+
*
|
|
18
|
+
* This callback will always be invoked from within a binding called the Wasm instance.
|
|
19
|
+
*/
|
|
20
|
+
onProcExit: (retCode: number) => never;
|
|
21
|
+
}
|
|
22
|
+
declare const _default: (config: Config) => WebAssembly.ModuleImports;
|
|
23
|
+
export default _default;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Smoldot
|
|
3
|
+
// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
|
|
4
|
+
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
// This program is free software: you can redistribute it and/or modify
|
|
7
|
+
// it under the terms of the GNU General Public License as published by
|
|
8
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
// (at your option) any later version.
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU General Public License for more details.
|
|
14
|
+
// You should have received a copy of the GNU General Public License
|
|
15
|
+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
//! Exports a function that provides bindings for the Wasi interface.
|
|
17
|
+
//!
|
|
18
|
+
//! These bindings can then be used by the Wasm virtual machine to invoke Wasi-related functions.
|
|
19
|
+
//! See <https://wasi.dev/>.
|
|
20
|
+
//!
|
|
21
|
+
//! In order to use this code, call the function passing an object, then fill the `instance` field
|
|
22
|
+
//! of that object with the Wasm instance.
|
|
23
|
+
const buffer = require("./buffer.js");
|
|
24
|
+
exports.default = (config) => {
|
|
25
|
+
// Buffers holding temporary data being written by the Rust code to respectively stdout and
|
|
26
|
+
// stderr.
|
|
27
|
+
let stdoutBuffer = "";
|
|
28
|
+
let stderrBuffer = "";
|
|
29
|
+
return {
|
|
30
|
+
// Need to fill the buffer described by `ptr` and `len` with random data.
|
|
31
|
+
// This data will be used in order to generate secrets. Do not use a dummy implementation!
|
|
32
|
+
random_get: (ptr, len) => {
|
|
33
|
+
const instance = config.instance;
|
|
34
|
+
ptr >>>= 0;
|
|
35
|
+
len >>>= 0;
|
|
36
|
+
const baseBuffer = new Uint8Array(instance.exports.memory.buffer)
|
|
37
|
+
.subarray(ptr, ptr + len);
|
|
38
|
+
for (let iter = 0; iter < len; iter += 65536) {
|
|
39
|
+
// `baseBuffer.subarray` automatically saturates at the end of the buffer
|
|
40
|
+
config.getRandomValues(baseBuffer.subarray(iter, iter + 65536));
|
|
41
|
+
}
|
|
42
|
+
return 0;
|
|
43
|
+
},
|
|
44
|
+
// Writing to a file descriptor is used in order to write to stdout/stderr.
|
|
45
|
+
fd_write: (fd, addr, num, outPtr) => {
|
|
46
|
+
const instance = config.instance;
|
|
47
|
+
outPtr >>>= 0;
|
|
48
|
+
// Only stdout and stderr are open for writing.
|
|
49
|
+
if (fd != 1 && fd != 2) {
|
|
50
|
+
return 8;
|
|
51
|
+
}
|
|
52
|
+
const mem = new Uint8Array(instance.exports.memory.buffer);
|
|
53
|
+
// `fd_write` passes a buffer containing itself a list of pointers and lengths to the
|
|
54
|
+
// actual buffers. See writev(2).
|
|
55
|
+
let toWrite = "";
|
|
56
|
+
let totalLength = 0;
|
|
57
|
+
for (let i = 0; i < num; i++) {
|
|
58
|
+
const buf = buffer.readUInt32LE(mem, addr + 4 * i * 2);
|
|
59
|
+
const bufLen = buffer.readUInt32LE(mem, addr + 4 * (i * 2 + 1));
|
|
60
|
+
toWrite += buffer.utf8BytesToString(mem, buf, bufLen);
|
|
61
|
+
totalLength += bufLen;
|
|
62
|
+
}
|
|
63
|
+
const flushBuffer = (string) => {
|
|
64
|
+
// As documented in the documentation of `println!`, lines are always split by a
|
|
65
|
+
// single `\n` in Rust.
|
|
66
|
+
while (true) {
|
|
67
|
+
const index = string.indexOf('\n');
|
|
68
|
+
if (index != -1) {
|
|
69
|
+
// Note that it is questionnable to use `console.log` from within a
|
|
70
|
+
// library. However this simply reflects the usage of `println!` in the
|
|
71
|
+
// Rust code. In other words, it is `println!` that shouldn't be used in
|
|
72
|
+
// the first place. The harm of not showing text printed with `println!`
|
|
73
|
+
// at all is greater than the harm possibly caused by accidentally leaving
|
|
74
|
+
// a `println!` in the code.
|
|
75
|
+
console.log(string.substring(0, index));
|
|
76
|
+
string = string.substring(index + 1);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
return string;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
// Append the newly-written data to either `stdout_buffer` or `stderr_buffer`, and
|
|
84
|
+
// print their content if necessary.
|
|
85
|
+
if (fd == 1) {
|
|
86
|
+
stdoutBuffer += toWrite;
|
|
87
|
+
stdoutBuffer = flushBuffer(stdoutBuffer);
|
|
88
|
+
}
|
|
89
|
+
else if (fd == 2) {
|
|
90
|
+
stderrBuffer += toWrite;
|
|
91
|
+
stderrBuffer = flushBuffer(stderrBuffer);
|
|
92
|
+
}
|
|
93
|
+
// Need to write in `out_ptr` how much data was "written".
|
|
94
|
+
buffer.writeUInt32LE(mem, outPtr, totalLength);
|
|
95
|
+
return 0;
|
|
96
|
+
},
|
|
97
|
+
// It's unclear how to properly implement yielding, but a no-op works fine as well.
|
|
98
|
+
sched_yield: () => {
|
|
99
|
+
return 0;
|
|
100
|
+
},
|
|
101
|
+
// Used by Rust in catastrophic situations, such as a double panic.
|
|
102
|
+
proc_exit: (retCode) => {
|
|
103
|
+
config.onProcExit(retCode);
|
|
104
|
+
},
|
|
105
|
+
// Return the number of environment variables and the total size of all environment
|
|
106
|
+
// variables. This is called in order to initialize buffers before `environ_get`.
|
|
107
|
+
environ_sizes_get: (argcOut, argvBufSizeOut) => {
|
|
108
|
+
const instance = config.instance;
|
|
109
|
+
argcOut >>>= 0;
|
|
110
|
+
argvBufSizeOut >>>= 0;
|
|
111
|
+
let totalLen = 0;
|
|
112
|
+
config.envVars.forEach(e => totalLen += new TextEncoder().encode(e).length + 1); // +1 for trailing \0
|
|
113
|
+
const mem = new Uint8Array(instance.exports.memory.buffer);
|
|
114
|
+
buffer.writeUInt32LE(mem, argcOut, config.envVars.length);
|
|
115
|
+
buffer.writeUInt32LE(mem, argvBufSizeOut, totalLen);
|
|
116
|
+
return 0;
|
|
117
|
+
},
|
|
118
|
+
// Write the environment variables to the given pointers.
|
|
119
|
+
// `argv` is a pointer to a buffer that must be overwritten with a list of pointers to
|
|
120
|
+
// environment variables, and `argvBuf` is a pointer to a buffer where to actually store
|
|
121
|
+
// the environment variables.
|
|
122
|
+
// The sizes of the buffers were determined by calling `environ_sizes_get`.
|
|
123
|
+
environ_get: (argv, argvBuf) => {
|
|
124
|
+
const instance = config.instance;
|
|
125
|
+
argv >>>= 0;
|
|
126
|
+
argvBuf >>>= 0;
|
|
127
|
+
const mem = new Uint8Array(instance.exports.memory.buffer);
|
|
128
|
+
let argvPos = 0;
|
|
129
|
+
let argvBufPos = 0;
|
|
130
|
+
config.envVars.forEach(envVar => {
|
|
131
|
+
const encoded = new TextEncoder().encode(envVar);
|
|
132
|
+
buffer.writeUInt32LE(mem, argv + argvPos, argvBuf + argvBufPos);
|
|
133
|
+
argvPos += 4;
|
|
134
|
+
mem.set(encoded, argvBuf + argvBufPos);
|
|
135
|
+
argvBufPos += encoded.length;
|
|
136
|
+
buffer.writeUInt8(mem, argvBuf + argvBufPos, 0);
|
|
137
|
+
argvBufPos += 1;
|
|
138
|
+
});
|
|
139
|
+
return 0;
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface that the Wasm module exports. Contains the functions that are exported by the Rust
|
|
3
|
+
* code.
|
|
4
|
+
*
|
|
5
|
+
* Must match the bindings found in the Rust code.
|
|
6
|
+
*/
|
|
7
|
+
export interface SmoldotWasmExports extends WebAssembly.Exports {
|
|
8
|
+
memory: WebAssembly.Memory;
|
|
9
|
+
init: (maxLogLevel: number, enableCurrentTask: number, cpuRateLimit: number, periodicallyYield: number) => void;
|
|
10
|
+
set_periodically_yield: (periodicallyYield: number) => void;
|
|
11
|
+
start_shutdown: () => void;
|
|
12
|
+
alloc: (len: number) => number;
|
|
13
|
+
add_chain: (chainSpecPointer: number, chainSpecLen: number, databaseContentPointer: number, databaseContentLen: number, jsonRpcRunning: number, potentialRelayChainsPtr: number, potentialRelayChainsLen: number) => number;
|
|
14
|
+
remove_chain: (chainId: number) => void;
|
|
15
|
+
chain_is_ok: (chainId: number) => number;
|
|
16
|
+
chain_error_len: (chainId: number) => number;
|
|
17
|
+
chain_error_ptr: (chainId: number) => number;
|
|
18
|
+
json_rpc_send: (textPtr: number, textLen: number, chainId: number) => number;
|
|
19
|
+
json_rpc_responses_peek: (chainId: number) => number;
|
|
20
|
+
json_rpc_responses_pop: (chainId: number) => void;
|
|
21
|
+
timer_finished: (timerId: number) => void;
|
|
22
|
+
connection_open_single_stream: (connectionId: number, handshakeTy: number) => void;
|
|
23
|
+
connection_open_multi_stream: (connectionId: number, handshakeTyPtr: number, handshakeTyLen: number) => void;
|
|
24
|
+
stream_message: (connectionId: number, streamId: number, ptr: number, len: number) => void;
|
|
25
|
+
connection_stream_opened: (connectionId: number, streamId: number, outbound: number) => void;
|
|
26
|
+
connection_reset: (connectionId: number, ptr: number, len: number) => void;
|
|
27
|
+
stream_reset: (connectionId: number, streamId: number) => void;
|
|
28
|
+
}
|
|
29
|
+
export interface SmoldotWasmInstance extends WebAssembly.Instance {
|
|
30
|
+
readonly exports: SmoldotWasmExports;
|
|
31
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function utf8BytesToString(buffer: Uint8Array, offset: number, length: number): string;
|
|
2
|
+
export declare function readUInt32LE(buffer: Uint8Array, offset: number): number;
|
|
3
|
+
/**
|
|
4
|
+
* Sets the value of a given byte in the buffer.
|
|
5
|
+
*
|
|
6
|
+
* This function is equivalent to `buffer[offset] = value`, except that an exception is thrown
|
|
7
|
+
* if `offset` is out of range.
|
|
8
|
+
*/
|
|
9
|
+
export declare function writeUInt8(buffer: Uint8Array, offset: number, value: number): void;
|
|
10
|
+
export declare function writeUInt32LE(buffer: Uint8Array, offset: number, value: number): void;
|