@bytecodealliance/preview2-shim 0.14.0 → 0.14.2

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.
@@ -16,8 +16,8 @@ import {
16
16
  } from "../../io/calls.js";
17
17
  import { ioCall, pollableCreate } from "../../io/worker-io.js";
18
18
  import { deserializeIpAddress } from "./socket-common.js";
19
- import { TcpSocketImpl } from "./tcp-socket-impl.js";
20
- import { IncomingDatagramStream, OutgoingDatagramStream, UdpSocketImpl } from "./udp-socket-impl.js";
19
+ import { TcpSocket, tcpSocketImplCreate } from "./tcp-socket-impl.js";
20
+ import { IncomingDatagramStream, OutgoingDatagramStream, UdpSocket, udpSocketImplCreate } from "./udp-socket-impl.js";
21
21
 
22
22
  const symbolDispose = Symbol.dispose || Symbol.for("dispose");
23
23
 
@@ -120,11 +120,14 @@ export const IpAddressFamily = {
120
120
  };
121
121
 
122
122
  export class WasiSockets {
123
+ #allowDnsLookup = true;
124
+ #allowTcp = true;
125
+ #allowUdp = true;
123
126
  networkCnt = 1;
124
127
  socketCnt = 1;
125
128
 
126
129
  // TODO: figure out what the max number of sockets should be
127
- maxSockets = 100;
130
+ MAX_SOCKET_INSTANCES = 100;
128
131
 
129
132
  /** @type {Network} */ networkInstance = null;
130
133
  /** @type {Map<number,Network>} */ networks = new Map();
@@ -141,32 +144,12 @@ export class WasiSockets {
141
144
  }
142
145
  }
143
146
 
144
- class UdpSocket extends UdpSocketImpl {
145
- /**
146
- * @param {IpAddressFamily} addressFamily
147
- * */
148
- constructor(addressFamily) {
149
- super(addressFamily, net.socketCnt++);
150
- net.udpSockets.set(this.id, this);
151
- }
152
- }
153
-
154
147
  this.udp = {
155
148
  UdpSocket,
156
149
  OutgoingDatagramStream,
157
150
  IncomingDatagramStream,
158
151
  };
159
152
 
160
- class TcpSocket extends TcpSocketImpl {
161
- /**
162
- * @param {IpAddressFamily} addressFamily
163
- * */
164
- constructor(addressFamily) {
165
- super(addressFamily, TcpSocket, net.socketCnt++);
166
- net.tcpSockets.set(this.id, this);
167
- }
168
- }
169
-
170
153
  this.tcp = {
171
154
  TcpSocket,
172
155
  };
@@ -199,14 +182,23 @@ export class WasiSockets {
199
182
  );
200
183
 
201
184
  assert(
202
- net.socketCnt + 1 > net.maxSockets,
185
+ net.socketCnt + 1 > net.MAX_SOCKET_INSTANCES,
203
186
  errorCode.newSocketLimit,
204
187
  "The new socket resource could not be created because of a system limit"
205
188
  );
206
189
 
207
190
  try {
208
- return new UdpSocket(addressFamily);
191
+ const id = net.socketCnt++;
192
+ const udpSocket = udpSocketImplCreate(addressFamily, id);
193
+ udpSocket.allowed = () => {
194
+ return net.#allowUdp;
195
+ };
196
+ net.udpSockets.set(id, udpSocket);
197
+ return udpSocket;
209
198
  } catch (err) {
199
+ console.log("udp socket create error", {
200
+ err,
201
+ });
210
202
  assert(true, errorCode.notSupported, err);
211
203
  }
212
204
  },
@@ -227,17 +219,25 @@ export class WasiSockets {
227
219
  );
228
220
 
229
221
  assert(
230
- net.socketCnt + 1 > net.maxSockets,
222
+ net.socketCnt + 1 > net.MAX_SOCKET_INSTANCES,
231
223
  errorCode.newSocketLimit,
232
224
  "The new socket resource could not be created because of a system limit"
233
225
  );
234
226
 
235
- // try {
236
- return new TcpSocket(addressFamily);
237
- // } catch (err) {
238
- // // assert(true, errorCode.unknown, err);
239
- // throw err;
240
- // }
227
+ try {
228
+ const id = net.socketCnt++;
229
+ const tcpSocket = tcpSocketImplCreate(addressFamily, id);
230
+ tcpSocket.allowed = () => {
231
+ return net.#allowTcp;
232
+ };
233
+ net.tcpSockets.set(id, tcpSocket);
234
+ return tcpSocket;
235
+ } catch (err) {
236
+ console.log("tcp socket create error", {
237
+ err,
238
+ });
239
+ assert(true, errorCode.notSupported, err);
240
+ }
241
241
  },
242
242
  };
243
243
 
@@ -255,7 +255,7 @@ export class WasiSockets {
255
255
  const family = `ipv${isIP(address)}`;
256
256
  return {
257
257
  tag: family,
258
- val: deserializeIpAddress(address),
258
+ val: deserializeIpAddress(address, family),
259
259
  };
260
260
  });
261
261
  }
@@ -305,13 +305,34 @@ export class WasiSockets {
305
305
  * @throws {invalid-argument} `name` is a syntactically invalid domain name or IP address.
306
306
  */
307
307
  resolveAddresses(network, name) {
308
+ if (!net.#allowDnsLookup)
309
+ throw 'permanent-resolver-failure';
308
310
  // TODO: bind to network
309
311
  return resolveAddressStreamCreate(name);
310
312
  },
311
313
  };
312
314
  }
315
+
316
+ static _denyDnsLookup (sockets) {
317
+ sockets.#allowDnsLookup = false;
318
+ }
319
+ static _denyTcp (sockets) {
320
+ sockets.#allowTcp = false;
321
+ }
322
+ static _denyUdp (sockets) {
323
+ sockets.#allowUdp = false;
324
+ }
313
325
  }
314
326
 
327
+ export const denyDnsLookup = WasiSockets._denyDnsLookup;
328
+ delete WasiSockets._denyDnsLookup;
329
+
330
+ export const denyTcp = WasiSockets._denyTcp;
331
+ delete WasiSockets._denyTcp;
332
+
333
+ export const denyUdp = WasiSockets._denyUdp;
334
+ delete WasiSockets._denyUdp;
335
+
315
336
  function convertResolveAddressError(err) {
316
337
  switch (err.code) {
317
338
  default:
@@ -1,11 +1,25 @@
1
- import { WasiSockets } from "./sockets/wasi-sockets.js";
2
-
3
- export const {
4
- ipNameLookup,
5
- instanceNetwork,
6
- network,
7
- tcpCreateSocket,
8
- udpCreateSocket,
9
- tcp,
10
- udp,
11
- } = new WasiSockets();
1
+ import { WasiSockets, denyDnsLookup, denyTcp, denyUdp } from "./sockets/wasi-sockets.js";
2
+
3
+ export function _denyDnsLookup() {
4
+ denyDnsLookup(sockets);
5
+ }
6
+
7
+ export function _denyTcp() {
8
+ denyTcp(sockets);
9
+ }
10
+
11
+ export function _denyUdp() {
12
+ denyUdp(sockets);
13
+ }
14
+
15
+ const sockets = new WasiSockets();
16
+
17
+ export const {
18
+ ipNameLookup,
19
+ instanceNetwork,
20
+ network,
21
+ tcpCreateSocket,
22
+ udpCreateSocket,
23
+ tcp,
24
+ udp,
25
+ } = sockets;
@@ -33,7 +33,6 @@ import {
33
33
  } from "node:worker_threads";
34
34
 
35
35
  const DEFAULT_WORKER_BUFFER_SIZE = 1024;
36
- const syncFnCache = new Map();
37
36
 
38
37
  function extractProperties(object) {
39
38
  if (object && typeof object === "object") {
@@ -45,61 +44,45 @@ function extractProperties(object) {
45
44
  }
46
45
  }
47
46
 
48
- export function createSyncFn(workerPath, bufferSizeOrOptions, timeout) {
47
+ const CALL_TIMEOUT = undefined;
48
+
49
+ export function createSyncFn(workerPath, callbackHandler) {
49
50
  if (!path.isAbsolute(workerPath)) {
50
51
  throw new Error("`workerPath` must be absolute");
51
52
  }
52
- const cachedSyncFn = syncFnCache.get(workerPath);
53
- if (cachedSyncFn) {
54
- return cachedSyncFn;
55
- }
56
- const syncFn = startWorkerThread(
57
- workerPath,
58
- typeof bufferSizeOrOptions === "number"
59
- ? { bufferSize: bufferSizeOrOptions, timeout }
60
- : bufferSizeOrOptions
61
- );
62
- syncFnCache.set(workerPath, syncFn);
63
- return syncFn;
64
- }
65
-
66
- function startWorkerThread(
67
- workerPath,
68
- {
69
- bufferSize = DEFAULT_WORKER_BUFFER_SIZE,
70
- timeout = undefined,
71
- execArgv = [],
72
- } = {}
73
- ) {
74
53
  const { port1: mainPort, port2: workerPort } = new MessageChannel();
75
54
  const worker = new Worker(workerPath, {
76
55
  workerData: { workerPort },
77
56
  transferList: [workerPort],
78
- execArgv: execArgv
57
+ execArgv: []
58
+ });
59
+ worker.on('message', ({ type, id, payload }) => {
60
+ if (!type)
61
+ throw new Error('Internal error: Expected a type of a worker callback');
62
+ callbackHandler(type, id, payload);
79
63
  });
80
64
  let nextID = 0;
81
65
  const syncFn = (...args) => {
82
- const id = nextID++;
83
- const sharedBuffer = new SharedArrayBuffer(bufferSize);
66
+ const cid = nextID++;
67
+ const sharedBuffer = new SharedArrayBuffer(DEFAULT_WORKER_BUFFER_SIZE);
84
68
  const sharedBufferView = new Int32Array(sharedBuffer);
85
- const msg = { sharedBuffer, id, args };
69
+ const msg = { sharedBuffer, cid, args };
86
70
  worker.postMessage(msg);
87
- const status = Atomics.wait(sharedBufferView, 0, 0, timeout);
71
+ const status = Atomics.wait(sharedBufferView, 0, 0, CALL_TIMEOUT);
88
72
  if (!["ok", "not-equal"].includes(status)) {
89
73
  throw new Error("Internal error: Atomics.wait() failed: " + status);
90
74
  }
91
75
  const {
92
- id: id2,
76
+ cid: cid2,
93
77
  result,
94
78
  error,
95
79
  properties,
96
80
  } = receiveMessageOnPort(mainPort).message;
97
- if (id !== id2) {
98
- throw new Error(`Internal error: Expected id ${id} but got id ${id2}`);
81
+ if (cid !== cid2) {
82
+ throw new Error(`Internal error: Expected id ${cid} but got id ${cid2}`);
99
83
  }
100
84
  if (error) {
101
- if (error instanceof Error)
102
- throw Object.assign(error, properties);
85
+ if (error instanceof Error) throw Object.assign(error, properties);
103
86
  throw error;
104
87
  }
105
88
  return result;
@@ -114,14 +97,14 @@ export function runAsWorker(fn) {
114
97
  }
115
98
  const { workerPort } = workerData;
116
99
  try {
117
- parentPort.on("message", ({ sharedBuffer, id, args }) => {
100
+ parentPort.on("message", ({ sharedBuffer, cid, args }) => {
118
101
  (async () => {
119
102
  const sharedBufferView = new Int32Array(sharedBuffer);
120
103
  let msg;
121
104
  try {
122
- msg = { id, result: await fn(...args) };
105
+ msg = { cid, result: await fn(...args) };
123
106
  } catch (error) {
124
- msg = { id, error, properties: extractProperties(error) };
107
+ msg = { cid, error, properties: extractProperties(error) };
125
108
  }
126
109
  workerPort.postMessage(msg);
127
110
  Atomics.add(sharedBufferView, 0, 1);
@@ -129,10 +112,10 @@ export function runAsWorker(fn) {
129
112
  })();
130
113
  });
131
114
  } catch (error) {
132
- parentPort.on("message", ({ sharedBuffer, id }) => {
115
+ parentPort.on("message", ({ sharedBuffer, cid }) => {
133
116
  const sharedBufferView = new Int32Array(sharedBuffer);
134
117
  workerPort.postMessage({
135
- id,
118
+ cid,
136
119
  error,
137
120
  properties: extractProperties(error),
138
121
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytecodealliance/preview2-shim",
3
- "version": "0.14.0",
3
+ "version": "0.14.2",
4
4
  "description": "WASI Preview2 shim for JS environments",
5
5
  "author": "Guy Bedford, Eduardo Rodrigues<16357187+eduardomourar@users.noreply.github.com>",
6
6
  "type": "module",
@@ -37,7 +37,14 @@ export namespace WasiHttpTypes {
37
37
  * syntactically invalid, or if a header was forbidden.
38
38
  */
39
39
  /**
40
- * Get all of the values corresponding to a key.
40
+ * Get all of the values corresponding to a key. If the key is not present
41
+ * in this `fields`, an empty list is returned. However, if the key is
42
+ * present but empty, this is represented by a list with one or more
43
+ * empty field-values present.
44
+ */
45
+ /**
46
+ * Returns `true` when the key is present in this `fields`. If the key is
47
+ * syntactically invalid, `false` is returned.
41
48
  */
42
49
  /**
43
50
  * Set all of the values for a key. Clears any existing values for that
@@ -255,10 +262,14 @@ export namespace WasiHttpTypes {
255
262
  * The outer `option` represents future readiness. Users can wait on this
256
263
  * `option` to become `some` using the `subscribe` method.
257
264
  *
258
- * The `result` represents that either the HTTP Request or Response body,
259
- * as well as any trailers, were received successfully, or that an error
260
- * occured receiving them. The optional `trailers` indicates whether or not
261
- * trailers were present in the body.
265
+ * The outer `result` is used to retrieve the trailers or error at most
266
+ * once. It will be success on the first call in which the outer option
267
+ * is `some`, and error on subsequent calls.
268
+ *
269
+ * The inner `result` represents that either the HTTP Request or Response
270
+ * body, as well as any trailers, were received successfully, or that an
271
+ * error occured receiving them. The optional `trailers` indicates whether
272
+ * or not trailers were present in the body.
262
273
  *
263
274
  * When some `trailers` are returned by this method, the `trailers`
264
275
  * resource is immutable, and a child. Use of the `set`, `append`, or
@@ -349,7 +360,7 @@ import type { InputStream } from '../interfaces/wasi-io-streams.js';
349
360
  export { InputStream };
350
361
  import type { OutputStream } from '../interfaces/wasi-io-streams.js';
351
362
  export { OutputStream };
352
- import type { IoError } from '../interfaces/wasi-io-error.js';
363
+ import type { Error as IoError } from '../interfaces/wasi-io-error.js';
353
364
  export { IoError };
354
365
  import type { Pollable } from '../interfaces/wasi-io-poll.js';
355
366
  export { Pollable };
@@ -618,15 +629,16 @@ export type Trailers = Fields;
618
629
  export type StatusCode = number;
619
630
  export type Result<T, E> = { tag: 'ok', val: T } | { tag: 'err', val: E };
620
631
 
621
- export class FutureIncomingResponse {
622
- subscribe(): Pollable;
623
- get(): Result<Result<IncomingResponse, ErrorCode>, void> | undefined;
632
+ export class OutgoingBody {
633
+ write(): OutputStream;
634
+ static finish(this_: OutgoingBody, trailers: Trailers | undefined): void;
624
635
  }
625
636
 
626
637
  export class Fields {
627
638
  constructor()
628
639
  static fromList(entries: [FieldKey, FieldValue][]): Fields;
629
640
  get(name: FieldKey): FieldValue[];
641
+ has(name: FieldKey): boolean;
630
642
  set(name: FieldKey, value: FieldValue[]): void;
631
643
  delete(name: FieldKey): void;
632
644
  append(name: FieldKey, value: FieldValue): void;
@@ -634,33 +646,18 @@ export class Fields {
634
646
  clone(): Fields;
635
647
  }
636
648
 
637
- export class FutureTrailers {
649
+ export class FutureIncomingResponse {
638
650
  subscribe(): Pollable;
639
- get(): Result<Trailers | undefined, ErrorCode> | undefined;
651
+ get(): Result<Result<IncomingResponse, ErrorCode>, void> | undefined;
640
652
  }
641
653
 
642
- export class OutgoingRequest {
643
- constructor(headers: Headers)
644
- body(): OutgoingBody;
654
+ export class IncomingRequest {
645
655
  method(): Method;
646
- setMethod(method: Method): void;
647
656
  pathWithQuery(): string | undefined;
648
- setPathWithQuery(pathWithQuery: string | undefined): void;
649
657
  scheme(): Scheme | undefined;
650
- setScheme(scheme: Scheme | undefined): void;
651
658
  authority(): string | undefined;
652
- setAuthority(authority: string | undefined): void;
653
659
  headers(): Headers;
654
- }
655
-
656
- export class RequestOptions {
657
- constructor()
658
- connectTimeoutMs(): Duration | undefined;
659
- setConnectTimeoutMs(ms: Duration | undefined): void;
660
- firstByteTimeoutMs(): Duration | undefined;
661
- setFirstByteTimeoutMs(ms: Duration | undefined): void;
662
- betweenBytesTimeoutMs(): Duration | undefined;
663
- setBetweenBytesTimeoutMs(ms: Duration | undefined): void;
660
+ consume(): IncomingBody;
664
661
  }
665
662
 
666
663
  export class IncomingBody {
@@ -668,15 +665,13 @@ export class IncomingBody {
668
665
  static finish(this_: IncomingBody): FutureTrailers;
669
666
  }
670
667
 
671
- export class ResponseOutparam {
672
- static set(param: ResponseOutparam, response: Result<OutgoingResponse, ErrorCode>): void;
668
+ export class FutureTrailers {
669
+ subscribe(): Pollable;
670
+ get(): Result<Result<Trailers | undefined, ErrorCode>, void> | undefined;
673
671
  }
674
672
 
675
- export class IncomingRequest {
676
- method(): Method;
677
- pathWithQuery(): string | undefined;
678
- scheme(): Scheme | undefined;
679
- authority(): string | undefined;
673
+ export class IncomingResponse {
674
+ status(): StatusCode;
680
675
  headers(): Headers;
681
676
  consume(): IncomingBody;
682
677
  }
@@ -689,13 +684,30 @@ export class OutgoingResponse {
689
684
  body(): OutgoingBody;
690
685
  }
691
686
 
692
- export class IncomingResponse {
693
- status(): StatusCode;
687
+ export class OutgoingRequest {
688
+ constructor(headers: Headers)
689
+ body(): OutgoingBody;
690
+ method(): Method;
691
+ setMethod(method: Method): void;
692
+ pathWithQuery(): string | undefined;
693
+ setPathWithQuery(pathWithQuery: string | undefined): void;
694
+ scheme(): Scheme | undefined;
695
+ setScheme(scheme: Scheme | undefined): void;
696
+ authority(): string | undefined;
697
+ setAuthority(authority: string | undefined): void;
694
698
  headers(): Headers;
695
- consume(): IncomingBody;
696
699
  }
697
700
 
698
- export class OutgoingBody {
699
- write(): OutputStream;
700
- static finish(this_: OutgoingBody, trailers: Trailers | undefined): void;
701
+ export class RequestOptions {
702
+ constructor()
703
+ connectTimeout(): Duration | undefined;
704
+ setConnectTimeout(duration: Duration | undefined): void;
705
+ firstByteTimeout(): Duration | undefined;
706
+ setFirstByteTimeout(duration: Duration | undefined): void;
707
+ betweenBytesTimeout(): Duration | undefined;
708
+ setBetweenBytesTimeout(duration: Duration | undefined): void;
709
+ }
710
+
711
+ export class ResponseOutparam {
712
+ static set(param: ResponseOutparam, response: Result<OutgoingResponse, ErrorCode>): void;
701
713
  }
@@ -24,6 +24,11 @@ export namespace WasiSocketsTcp {
24
24
  * - `not-in-progress`: A `bind` operation is not in progress.
25
25
  * - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN)
26
26
  *
27
+ * # Implementors note
28
+ * When binding to a non-zero port, this bind operation shouldn't be affected by the TIME_WAIT
29
+ * state of a recently closed socket on the same local address (i.e. the SO_REUSEADDR socket
30
+ * option should be set implicitly on platforms that require it).
31
+ *
27
32
  * # References
28
33
  * - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html>
29
34
  * - <https://man7.org/linux/man-pages/man2/bind.2.html>