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.
Files changed (74) hide show
  1. package/README.md +18 -21
  2. package/dist/cjs/base64.d.ts +22 -0
  3. package/dist/cjs/base64.js +104 -0
  4. package/dist/cjs/client.d.ts +287 -0
  5. package/dist/cjs/client.js +175 -0
  6. package/dist/cjs/index-browser.d.ts +11 -0
  7. package/dist/cjs/index-browser.js +465 -0
  8. package/dist/cjs/index-deno.d.ts +10 -0
  9. package/dist/cjs/index-deno.js +263 -0
  10. package/dist/cjs/index-nodejs.d.ts +10 -0
  11. package/dist/cjs/index-nodejs.js +174 -0
  12. package/dist/cjs/instance/autogen/wasm.d.ts +2 -0
  13. package/dist/cjs/instance/autogen/wasm.js +6 -0
  14. package/dist/cjs/instance/autogen/wasm0.d.ts +1 -0
  15. package/dist/cjs/instance/autogen/wasm0.js +4 -0
  16. package/dist/cjs/instance/autogen/wasm1.d.ts +1 -0
  17. package/dist/cjs/instance/autogen/wasm1.js +4 -0
  18. package/dist/cjs/instance/autogen/wasm2.d.ts +1 -0
  19. package/dist/cjs/instance/autogen/wasm2.js +4 -0
  20. package/dist/cjs/instance/bindings-smoldot-light.d.ts +154 -0
  21. package/dist/cjs/instance/bindings-smoldot-light.js +266 -0
  22. package/dist/cjs/instance/bindings-wasi.d.ts +23 -0
  23. package/dist/cjs/instance/bindings-wasi.js +142 -0
  24. package/dist/cjs/instance/bindings.d.ts +31 -0
  25. package/dist/cjs/instance/bindings.js +5 -0
  26. package/dist/cjs/instance/buffer.d.ts +10 -0
  27. package/dist/cjs/instance/buffer.js +53 -0
  28. package/dist/cjs/instance/instance.d.ts +45 -0
  29. package/dist/cjs/instance/instance.js +301 -0
  30. package/dist/cjs/instance/raw-instance.d.ts +65 -0
  31. package/dist/cjs/instance/raw-instance.js +72 -0
  32. package/dist/cjs/package.json +3 -0
  33. package/dist/mjs/base64.d.ts +22 -0
  34. package/dist/mjs/base64.js +97 -0
  35. package/dist/mjs/client.d.ts +287 -0
  36. package/dist/mjs/client.js +165 -0
  37. package/dist/mjs/index-browser.d.ts +11 -0
  38. package/dist/mjs/index-browser.js +455 -0
  39. package/dist/mjs/index-deno.d.ts +10 -0
  40. package/dist/mjs/index-deno.js +253 -0
  41. package/dist/mjs/index-nodejs.d.ts +10 -0
  42. package/dist/mjs/index-nodejs.js +164 -0
  43. package/dist/mjs/instance/autogen/wasm.d.ts +2 -0
  44. package/dist/mjs/instance/autogen/wasm.js +4 -0
  45. package/dist/mjs/instance/autogen/wasm0.d.ts +1 -0
  46. package/dist/mjs/instance/autogen/wasm0.js +1 -0
  47. package/dist/mjs/instance/autogen/wasm1.d.ts +1 -0
  48. package/dist/mjs/instance/autogen/wasm1.js +1 -0
  49. package/dist/mjs/instance/autogen/wasm2.d.ts +1 -0
  50. package/dist/mjs/instance/autogen/wasm2.js +1 -0
  51. package/dist/mjs/instance/bindings-smoldot-light.d.ts +154 -0
  52. package/dist/mjs/instance/bindings-smoldot-light.js +261 -0
  53. package/dist/mjs/instance/bindings-wasi.d.ts +23 -0
  54. package/{src → dist/mjs/instance}/bindings-wasi.js +47 -58
  55. package/dist/mjs/instance/bindings.d.ts +31 -0
  56. package/dist/mjs/instance/bindings.js +4 -0
  57. package/dist/mjs/instance/buffer.d.ts +10 -0
  58. package/dist/mjs/instance/buffer.js +46 -0
  59. package/dist/mjs/instance/instance.d.ts +45 -0
  60. package/dist/mjs/instance/instance.js +293 -0
  61. package/dist/mjs/instance/raw-instance.d.ts +65 -0
  62. package/dist/mjs/instance/raw-instance.js +67 -0
  63. package/dist/mjs/package.json +3 -0
  64. package/package.json +35 -19
  65. package/src/autogen/wasm.js +0 -1
  66. package/src/bindings-smoldot-js.js +0 -223
  67. package/src/compat-browser.js +0 -25
  68. package/src/compat-nodejs.js +0 -30
  69. package/src/health.js +0 -206
  70. package/src/index.d.ts +0 -68
  71. package/src/index.js +0 -192
  72. package/src/index.test-d.ts +0 -36
  73. package/src/tsconfig.json +0 -13
  74. 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,5 @@
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 });
@@ -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;