@bytecodealliance/preview2-shim 0.14.1 → 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.
@@ -6,16 +6,39 @@
6
6
  * @typedef {import("../../types/interfaces/wasi-io-poll-poll").Pollable} Pollable
7
7
  */
8
8
 
9
- // See: https://github.com/nodejs/node/blob/main/src/udp_wrap.cc
10
- const { UDP, SendWrap } = process.binding("udp_wrap");
11
9
  import { isIP } from "node:net";
12
10
  import { assert } from "../../common/assert.js";
13
- import { pollableCreate } from "../../io/worker-io.js";
14
- import { cappedUint32, deserializeIpAddress, serializeIpAddress } from "./socket-common.js";
11
+ import {
12
+ SOCKET_UDP_BIND,
13
+ SOCKET_UDP_CHECK_SEND,
14
+ SOCKET_UDP_CONNECT,
15
+ SOCKET_UDP_CREATE_HANDLE,
16
+ SOCKET_UDP_DISCONNECT,
17
+ SOCKET_UDP_DISPOSE,
18
+ SOCKET_UDP_GET_LOCAL_ADDRESS,
19
+ SOCKET_UDP_GET_RECEIVE_BUFFER_SIZE,
20
+ SOCKET_UDP_GET_REMOTE_ADDRESS,
21
+ SOCKET_UDP_GET_SEND_BUFFER_SIZE,
22
+ SOCKET_UDP_RECEIVE,
23
+ SOCKET_UDP_SEND,
24
+ SOCKET_UDP_SET_RECEIVE_BUFFER_SIZE,
25
+ SOCKET_UDP_SET_SEND_BUFFER_SIZE,
26
+ SOCKET_UDP_SET_UNICAST_HOP_LIMIT,
27
+ } from "../../io/calls.js";
28
+ import { ioCall, pollableCreate } from "../../io/worker-io.js";
29
+ import {
30
+ deserializeIpAddress,
31
+ findUnusedLocalAddress,
32
+ isIPv4MappedAddress,
33
+ isWildcardAddress,
34
+ serializeIpAddress,
35
+ } from "./socket-common.js";
15
36
 
16
37
  const symbolDispose = Symbol.dispose || Symbol.for("dispose");
17
- const symbolSocketState = Symbol.SocketInternalState || Symbol.for("SocketInternalState");
18
- const symbolOperations = Symbol.SocketOperationsState || Symbol.for("SocketOperationsState");
38
+ const symbolSocketState =
39
+ Symbol.SocketInternalState || Symbol.for("SocketInternalState");
40
+ const symbolOperations =
41
+ Symbol.SocketOperationsState || Symbol.for("SocketOperationsState");
19
42
 
20
43
  // TODO: move to a common
21
44
  const SocketConnectionState = {
@@ -26,36 +49,22 @@ const SocketConnectionState = {
26
49
  Listening: "Listening",
27
50
  };
28
51
 
29
- // see https://github.com/libuv/libuv/blob/master/docs/src/udp.rst
30
- // TODO: move to a common
31
- const Flags = {
32
- UV_UDP_IPV6ONLY: 1,
33
- UV_UDP_REUSEADDR: 4,
34
- };
35
-
36
- // TODO: move to a common
37
- const BufferSizeFlags = {
38
- SO_RCVBUF: true,
39
- SO_SNDBUF: false,
40
- };
41
-
42
52
  // As a workaround, we store the bound address in a global map
43
53
  // this is needed because 'address-in-use' is not always thrown when binding
44
54
  // more than one socket to the same address
45
55
  // TODO: remove this workaround when we figure out why!
56
+ /** @type {Map<string, number>} */
46
57
  const globalBoundAddresses = new Map();
47
58
 
48
59
  export class IncomingDatagramStream {
49
- static _create(socket) {
50
- const stream = new IncomingDatagramStream(socket);
60
+ #pollId = 0;
61
+ #socketId = 0;
62
+ static _create(socketId) {
63
+ const stream = new IncomingDatagramStream();
64
+ stream.#socketId = socketId;
51
65
  return stream;
52
66
  }
53
67
 
54
- #socket = null;
55
- constructor(socket) {
56
- this.#socket = socket;
57
- }
58
-
59
68
  /**
60
69
  *
61
70
  * @param {bigint} maxResults
@@ -67,20 +76,37 @@ export class IncomingDatagramStream {
67
76
  * @throws {would-block} There is no pending data available to be read at the moment. (EWOULDBLOCK, EAGAIN)
68
77
  */
69
78
  receive(maxResults) {
70
- assert(self[symbolSocketState].isBound === false, "invalid-state");
71
- assert(self[symbolOperations].receive === 0, "not-in-progress");
72
-
73
79
  if (maxResults === 0n) {
74
80
  return [];
75
81
  }
76
82
 
77
- // TODO: not sure this is the right API to use!
78
- const socket = this.#socket;
79
- socket.onmessage = (...args) => console.log("recv onmessage", args[2].toString());
80
- socket.onerror = (err) => console.log("recv error", err);
81
- socket.recvStart();
82
- const datagrams = [];
83
- return datagrams;
83
+ const datagrams = ioCall(
84
+ SOCKET_UDP_RECEIVE,
85
+ // socket that's receiving the datagrams
86
+ this.#socketId,
87
+ {
88
+ maxResults,
89
+ }
90
+ );
91
+
92
+ return datagrams.map(({ data, rinfo }) => {
93
+ let address = rinfo.address;
94
+ if (rinfo._address) {
95
+ // set the original address that the socket was bound to
96
+ address = rinfo._address;
97
+ }
98
+ const remoteAddress = {
99
+ tag: rinfo.family.toLocaleLowerCase(),
100
+ val: {
101
+ address: deserializeIpAddress(address, rinfo.family),
102
+ port: rinfo.port,
103
+ },
104
+ };
105
+ return {
106
+ data,
107
+ remoteAddress,
108
+ };
109
+ });
84
110
  }
85
111
 
86
112
  /**
@@ -88,7 +114,8 @@ export class IncomingDatagramStream {
88
114
  * @returns {Pollable} A pollable which will resolve once the stream is ready to receive again.
89
115
  */
90
116
  subscribe() {
91
- throw new Error("Not implemented");
117
+ if (this.#pollId) return pollableCreate(this.#pollId);
118
+ return pollableCreate(0);
92
119
  }
93
120
 
94
121
  [symbolDispose]() {
@@ -99,23 +126,25 @@ const incomingDatagramStreamCreate = IncomingDatagramStream._create;
99
126
  delete IncomingDatagramStream._create;
100
127
 
101
128
  export class OutgoingDatagramStream {
102
- static _create(socket) {
103
- const stream = new OutgoingDatagramStream(socket);
104
- return stream;
105
- }
129
+ #pollId = 0;
130
+ #socketId = 0;
106
131
 
107
- #socket = null;
108
- constructor(socket) {
109
- this.#socket = socket;
132
+ static _create(socketId) {
133
+ const stream = new OutgoingDatagramStream(socketId);
134
+ stream.#socketId = socketId;
135
+ return stream;
110
136
  }
111
137
 
112
138
  /**
113
139
  *
114
140
  * @returns {bigint}
115
- * @throws {invalid-state} The socket is not bound to any local address. (EINVAL)
116
141
  */
117
142
  checkSend() {
118
- throw new Error("Not implemented");
143
+ const ret = ioCall(SOCKET_UDP_CHECK_SEND, this.#socketId);
144
+ // TODO: When this function returns ok(0), the `subscribe` pollable will
145
+ // become ready when this function will report at least ok(1), or an
146
+ // error.
147
+ return ret;
119
148
  }
120
149
 
121
150
  /**
@@ -133,31 +162,37 @@ export class OutgoingDatagramStream {
133
162
  * @throws {datagram-too-large} The datagram is too large. (EMSGSIZE)
134
163
  */
135
164
  send(datagrams) {
136
- const req = new SendWrap();
137
- const doSend = (data, port, host, family) => {
138
- // setting hasCallback to false will make send() synchronous
139
- // TODO: handle async send
140
- const hasCallback = false;
141
- const socket = this.#socket;
142
-
143
- let err = null;
144
- if (family.toLocaleLowerCase() === "ipv4") {
145
- err = socket.send(req, data, data.length, port, host, hasCallback);
146
- } else if (family.toLocaleLowerCase() === "ipv6") {
147
- err = socket.send6(req, data, data.length, port, host, hasCallback);
148
- }
149
- return err;
150
- };
165
+ if (datagrams.length === 0) {
166
+ return 0n;
167
+ }
168
+
169
+ let datagramsSent = 0n;
151
170
 
152
- datagrams.forEach((datagram) => {
171
+ for (const datagram of datagrams) {
153
172
  const { data, remoteAddress } = datagram;
154
- const { tag: family, val } = remoteAddress;
155
- const { /*address, */port } = val;
156
- const err = doSend(data, port, serializeIpAddress(remoteAddress), family);
157
- console.error({
158
- err,
159
- });
160
- });
173
+ const remotePort = remoteAddress?.val?.port || undefined;
174
+ const host = serializeIpAddress(remoteAddress, false);
175
+
176
+ assert(this.checkSend() < data.length, "datagram-too-large");
177
+ // TODO: add the other assertions
178
+
179
+ const ret = ioCall(
180
+ SOCKET_UDP_SEND,
181
+ this.#socketId, // socket that's sending the datagrams
182
+ {
183
+ data,
184
+ remotePort,
185
+ remoteHost: host,
186
+ }
187
+ );
188
+ if (ret === 0) {
189
+ datagramsSent++;
190
+ } else {
191
+ assert(ret === -65, "remote-unreachable");
192
+ }
193
+ }
194
+
195
+ return datagramsSent;
161
196
  }
162
197
 
163
198
  /**
@@ -165,7 +200,8 @@ export class OutgoingDatagramStream {
165
200
  * @returns {Pollable} A pollable which will resolve once the stream is ready to send again.
166
201
  */
167
202
  subscribe() {
168
- throw new Error("Not implemented");
203
+ if (this.#pollId) return pollableCreate(this.#pollId);
204
+ return pollableCreate(0);
169
205
  }
170
206
 
171
207
  [symbolDispose]() {
@@ -175,9 +211,9 @@ export class OutgoingDatagramStream {
175
211
  const outgoingDatagramStreamCreate = OutgoingDatagramStream._create;
176
212
  delete OutgoingDatagramStream._create;
177
213
 
178
- export class UdpSocketImpl {
214
+ export class UdpSocket {
179
215
  id = 1;
180
- /** @type {UDP} */ #socket = null;
216
+ #pollId = 0;
181
217
  /** @type {Network} */ network = null;
182
218
 
183
219
  // track in-progress operations
@@ -200,28 +236,48 @@ export class UdpSocketImpl {
200
236
  connectionState: SocketConnectionState.Closed,
201
237
 
202
238
  // TODO: what these default values should be?
203
- unicastHopLimit: 1,
204
- receiveBufferSize: 1,
205
- sendBufferSize: 1,
239
+ unicastHopLimit: 255, // 1-255
206
240
  };
207
241
 
208
242
  #socketOptions = {
209
243
  family: "ipv4",
210
- localAddress: undefined,
244
+ localAddress: "",
211
245
  localPort: 0,
212
- remoteAddress: undefined,
246
+ remoteAddress: "",
213
247
  remotePort: 0,
248
+ reuseAddr: true,
249
+ localIpSocketAddress: null,
214
250
  };
215
251
 
252
+ get _pollId() {
253
+ return this.#pollId;
254
+ }
255
+
216
256
  /**
217
257
  * @param {IpAddressFamily} addressFamily
218
258
  * @returns {void}
219
259
  */
220
- constructor(addressFamily, id) {
221
- this.id = id;
222
- this.#socketOptions.family = addressFamily;
260
+ static _create(addressFamily, id) {
261
+ const socket = new UdpSocket();
262
+ socket.id = id;
263
+ socket.#socketOptions.family = addressFamily;
264
+ socket.#pollId = ioCall(SOCKET_UDP_CREATE_HANDLE, null, {
265
+ addressFamily,
266
+ // force reuse the address, even if another process has already bound a socket on it!
267
+ reuseAddr: true,
268
+ });
269
+ return socket;
270
+ }
223
271
 
224
- this.#socket = new UDP();
272
+ #autoBind(network, ipFamily) {
273
+ const localAddress = findUnusedLocalAddress(ipFamily);
274
+ this.#socketOptions.localAddress = serializeIpAddress(
275
+ localAddress,
276
+ this.#socketOptions.family
277
+ );
278
+ this.#socketOptions.localPort = localAddress.val.port;
279
+ this.startBind(network, localAddress);
280
+ this.finishBind();
225
281
  }
226
282
 
227
283
  #cacheBoundAddress() {
@@ -231,30 +287,40 @@ export class UdpSocketImpl {
231
287
  if (localPort === 0) {
232
288
  boundAddress = this.localAddress();
233
289
  }
234
- globalBoundAddresses.set(serializeIpAddress(boundAddress, true), this.#socket);
290
+ globalBoundAddresses.set(serializeIpAddress(boundAddress, true), this.id);
235
291
  }
236
292
 
237
293
  /**
238
294
  *
239
295
  * @param {Network} network
240
- * @param {IpAddressFamily} localAddress
296
+ * @param {IpSocketAddress} localAddress
241
297
  * @returns {void}
242
298
  * @throws {invalid-argument} The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows)
243
299
  * @throws {invalid-state} The socket is already bound. (EINVAL)
244
300
  */
245
301
  startBind(network, localAddress) {
302
+ if (!this.allowed()) throw "access-denied";
246
303
  try {
247
- assert(this[symbolSocketState].isBound, "invalid-state", "The socket is already bound");
304
+ assert(
305
+ this[symbolSocketState].isBound,
306
+ "invalid-state",
307
+ "The socket is already bound"
308
+ );
248
309
 
249
310
  const address = serializeIpAddress(localAddress);
250
311
  const ipFamily = `ipv${isIP(address)}`;
251
312
 
252
313
  assert(
253
- this.#socketOptions.family.toLocaleLowerCase() !== ipFamily.toLocaleLowerCase(),
314
+ this.#socketOptions.family.toLocaleLowerCase() !==
315
+ ipFamily.toLocaleLowerCase(),
254
316
  "invalid-argument",
255
317
  "The `local-address` has the wrong address family"
256
318
  );
257
- assert(this[symbolSocketState].ipv6Only, "invalid-argument", "The `local-address` has the wrong address family");
319
+
320
+ assert(
321
+ isIPv4MappedAddress(localAddress) && this.ipv6Only(),
322
+ "invalid-argument"
323
+ );
258
324
 
259
325
  const { port } = localAddress.val;
260
326
  this.#socketOptions.localIpSocketAddress = localAddress;
@@ -282,22 +348,20 @@ export class UdpSocketImpl {
282
348
  try {
283
349
  assert(this[symbolOperations].bind === 0, "not-in-progress");
284
350
 
285
- const { localAddress, localIpSocketAddress, localPort, family } = this.#socketOptions;
351
+ const { localAddress, localIpSocketAddress, localPort } =
352
+ this.#socketOptions;
286
353
  assert(isIP(localAddress) === 0, "address-not-bindable");
287
- assert(globalBoundAddresses.has(serializeIpAddress(localIpSocketAddress, true)), "address-in-use");
288
-
289
- let flags = 0;
290
- if (this[symbolSocketState].ipv6Only) {
291
- flags |= Flags.UV_UDP_IPV6ONLY;
292
- }
293
-
294
- let err = null;
295
- let bind = "bind"; // ipv4
296
- if (family.toLocaleLowerCase() === "ipv6") {
297
- bind = "bind6";
298
- }
354
+ assert(
355
+ globalBoundAddresses.has(
356
+ serializeIpAddress(localIpSocketAddress, true)
357
+ ),
358
+ "address-in-use"
359
+ );
299
360
 
300
- err = this.#socket[bind](localAddress, localPort, flags);
361
+ const err = ioCall(SOCKET_UDP_BIND, this.id, {
362
+ localAddress,
363
+ localPort,
364
+ });
301
365
 
302
366
  if (err === 0) {
303
367
  this[symbolSocketState].isBound = true;
@@ -307,6 +371,7 @@ export class UdpSocketImpl {
307
371
  assert(err === -49, "address-not-bindable");
308
372
  assert(err === -98, "address-in-use"); // WSL
309
373
  assert(err === -99, "address-not-bindable"); // EADDRNOTAVAIL
374
+ // catch all other errors
310
375
  assert(true, "unknown", err);
311
376
  }
312
377
 
@@ -323,6 +388,8 @@ export class UdpSocketImpl {
323
388
 
324
389
  /**
325
390
  * Alias for startBind() and finishBind()
391
+ * @param {Network} network
392
+ * @param {IpAddressFamily} localAddress
326
393
  */
327
394
  bind(network, localAddress) {
328
395
  this.startBind(network, localAddress);
@@ -343,22 +410,44 @@ export class UdpSocketImpl {
343
410
  #startConnect(network, remoteAddress = undefined) {
344
411
  this[symbolOperations].connect++;
345
412
 
346
- if (remoteAddress === undefined || this[symbolSocketState].connectionState === SocketConnectionState.Connected) {
347
- // TODO: should we reuse a connected socket if remoteAddress is undefined?
348
- // See #finishConnect()
413
+ if (
414
+ remoteAddress === undefined ||
415
+ this[symbolSocketState].connectionState ===
416
+ SocketConnectionState.Connected
417
+ ) {
418
+ this.#socketOptions.remoteAddress = undefined;
419
+ this.#socketOptions.remotePort = 0;
349
420
  return;
350
421
  }
351
422
 
423
+ assert(
424
+ isWildcardAddress(remoteAddress),
425
+ "invalid-argument",
426
+ "The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`)"
427
+ );
428
+ assert(
429
+ isIPv4MappedAddress(remoteAddress) && this.ipv6Only(),
430
+ "invalid-argument"
431
+ );
432
+ assert(
433
+ remoteAddress.val.port === 0,
434
+ "invalid-argument",
435
+ "The port in `remote-address` is set to 0"
436
+ );
437
+
352
438
  const host = serializeIpAddress(remoteAddress);
353
439
  const ipFamily = `ipv${isIP(host)}`;
354
440
 
355
441
  assert(ipFamily.toLocaleLowerCase() === "ipv0", "invalid-argument");
356
- assert(this.#socketOptions.family.toLocaleLowerCase() !== ipFamily.toLocaleLowerCase(), "invalid-argument");
442
+ assert(
443
+ this.#socketOptions.family.toLocaleLowerCase() !==
444
+ ipFamily.toLocaleLowerCase(),
445
+ "invalid-argument"
446
+ );
357
447
 
358
448
  const { port } = remoteAddress.val;
359
449
  this.#socketOptions.remoteAddress = host; // can be undefined
360
450
  this.#socketOptions.remotePort = port;
361
-
362
451
  this.network = network;
363
452
  }
364
453
 
@@ -374,8 +463,22 @@ export class UdpSocketImpl {
374
463
  const { remoteAddress, remotePort } = this.#socketOptions;
375
464
  this[symbolSocketState].connectionState = SocketConnectionState.Connecting;
376
465
 
377
- // TODO: figure out how to reuse a connected socket
378
- const err = this.#socket.connect(remoteAddress ?? null, remotePort);
466
+ if (
467
+ remoteAddress === undefined ||
468
+ this[symbolSocketState].connectionState ===
469
+ SocketConnectionState.Connected
470
+ ) {
471
+ return;
472
+ }
473
+
474
+ if (this[symbolSocketState].isBound === false) {
475
+ // this.bind(this.network, this.#socketOptions.localIpSocketAddress);
476
+ }
477
+
478
+ const err = ioCall(SOCKET_UDP_CONNECT, this.id, {
479
+ remoteAddress,
480
+ remotePort,
481
+ });
379
482
 
380
483
  if (!err) {
381
484
  this[symbolSocketState].connectionState = SocketConnectionState.Connected;
@@ -395,6 +498,18 @@ export class UdpSocketImpl {
395
498
  this.#finishConnect();
396
499
  }
397
500
 
501
+ #disconnect() {
502
+ const ret = ioCall(SOCKET_UDP_DISCONNECT, this.id);
503
+
504
+ if (ret === 0) {
505
+ this[symbolSocketState].connectionState = SocketConnectionState.Closed;
506
+ this[symbolSocketState].lastErrorState = null;
507
+ this[symbolSocketState].isBound = false;
508
+ }
509
+
510
+ assert(ret !== 0, "unknown");
511
+ }
512
+
398
513
  /**
399
514
  *
400
515
  * @param {IpSocketAddress | undefined} remoteAddress
@@ -409,22 +524,46 @@ export class UdpSocketImpl {
409
524
  * @throws {connection-refused} The connection was refused. (ECONNREFUSED)
410
525
  */
411
526
  stream(remoteAddress = undefined) {
412
- assert(this[symbolSocketState].isBound === false, "invalid-state");
413
- this.#connect(this.network, remoteAddress);
414
- return [incomingDatagramStreamCreate(this.#socket), outgoingDatagramStreamCreate(this.#socket)];
527
+ assert(this[symbolSocketState].lastErrorState !== null, "invalid-state");
528
+
529
+ // Note: to comply with test programs, we cannot throw if the socket is not bound (as required by the spec - see udp.wit)
530
+ // assert(this[symbolSocketState].isBound === false, "invalid-state");
531
+
532
+ if (
533
+ this[symbolSocketState].connectionState ===
534
+ SocketConnectionState.Connected
535
+ ) {
536
+ // stream() can be called multiple times, so we need to disconnect first if we are already connected
537
+ // Note: disconnect() will also reset the connection state but does not close the socket handle!
538
+ this.#disconnect();
539
+ }
540
+
541
+ if (remoteAddress) {
542
+ this.#connect(this.network, remoteAddress);
543
+ }
544
+
545
+ // reconfigure remote host and port.
546
+ // Note: remoteAddress can be undefined
547
+ const host = serializeIpAddress(remoteAddress);
548
+ const { port } = remoteAddress?.val || { port: 0 };
549
+ this.#socketOptions.remoteAddress = host; // host can be undefined
550
+ this.#socketOptions.remotePort = port;
551
+
552
+ return [
553
+ incomingDatagramStreamCreate(this.id),
554
+ outgoingDatagramStreamCreate(this.id),
555
+ ];
415
556
  }
416
557
 
417
558
  /**
418
559
  *
419
- * Note: Concurrent invocations of this test can yield port to be 0 on Windows/WSL.
420
560
  * @returns {IpSocketAddress}
421
561
  * @throws {invalid-state} The socket is not bound to any local address.
422
562
  */
423
563
  localAddress() {
424
564
  assert(this[symbolSocketState].isBound === false, "invalid-state");
425
565
 
426
- const out = {};
427
- this.#socket.getsockname(out);
566
+ const out = ioCall(SOCKET_UDP_GET_LOCAL_ADDRESS, this.id);
428
567
 
429
568
  const { address, port, family } = out;
430
569
  this.#socketOptions.localAddress = address;
@@ -446,17 +585,20 @@ export class UdpSocketImpl {
446
585
  * @throws {invalid-state} The socket is not streaming to a specific remote address. (ENOTCONN)
447
586
  */
448
587
  remoteAddress() {
449
- console.log("remoteAddress", this[symbolSocketState]);
450
588
  assert(
451
- this[symbolSocketState].connectionState !== SocketConnectionState.Connected,
589
+ this[symbolSocketState].connectionState !==
590
+ SocketConnectionState.Connected,
452
591
  "invalid-state",
453
592
  "The socket is not streaming to a specific remote address"
454
593
  );
455
594
 
456
- const out = {};
457
- this.#socket.getpeername(out);
595
+ const out = ioCall(SOCKET_UDP_GET_REMOTE_ADDRESS, this.id);
458
596
 
459
- assert(out.address === undefined, "invalid-state", "The socket is not streaming to a specific remote address");
597
+ assert(
598
+ out.address === undefined,
599
+ "invalid-state",
600
+ "The socket is not streaming to a specific remote address"
601
+ );
460
602
 
461
603
  const { address, port, family } = out;
462
604
  this.#socketOptions.remoteAddress = address;
@@ -466,7 +608,7 @@ export class UdpSocketImpl {
466
608
  return {
467
609
  tag: family.toLocaleLowerCase(),
468
610
  val: {
469
- address: deserializeIpAddress(address),
611
+ address: deserializeIpAddress(address, family),
470
612
  port,
471
613
  },
472
614
  };
@@ -486,7 +628,11 @@ export class UdpSocketImpl {
486
628
  * @throws {not-supported} (get/set) `this` socket is an IPv4 socket.
487
629
  */
488
630
  ipv6Only() {
489
- assert(this.#socketOptions.family.toLocaleLowerCase() === "ipv4", "not-supported", "Socket is an IPv4 socket.");
631
+ assert(
632
+ this.#socketOptions.family.toLocaleLowerCase() === "ipv4",
633
+ "not-supported",
634
+ "Socket is an IPv4 socket."
635
+ );
490
636
 
491
637
  return this[symbolSocketState].ipv6Only;
492
638
  }
@@ -501,11 +647,16 @@ export class UdpSocketImpl {
501
647
  */
502
648
  setIpv6Only(value) {
503
649
  assert(
504
- value === true && this.#socketOptions.family.toLocaleLowerCase() === "ipv4",
650
+ value === true &&
651
+ this.#socketOptions.family.toLocaleLowerCase() === "ipv4",
505
652
  "not-supported",
506
653
  "Socket is an IPv4 socket."
507
654
  );
508
- assert(this[symbolSocketState].isBound, "invalid-state", "The socket is already bound");
655
+ assert(
656
+ this[symbolSocketState].isBound,
657
+ "invalid-state",
658
+ "The socket is already bound"
659
+ );
509
660
 
510
661
  this[symbolSocketState].ipv6Only = value;
511
662
  }
@@ -527,7 +678,7 @@ export class UdpSocketImpl {
527
678
  setUnicastHopLimit(value) {
528
679
  assert(value < 1, "invalid-argument", "The TTL value must be 1 or higher");
529
680
 
530
- this.#socket.setTTL(value);
681
+ ioCall(SOCKET_UDP_SET_UNICAST_HOP_LIMIT, this.id, { value });
531
682
  this[symbolSocketState].unicastHopLimit = value;
532
683
  }
533
684
 
@@ -536,20 +687,21 @@ export class UdpSocketImpl {
536
687
  * @returns {bigint}
537
688
  */
538
689
  receiveBufferSize() {
539
- const exceptionInfo = {};
540
- const value = this.#socket.bufferSize(0, BufferSizeFlags.SO_RCVBUF, exceptionInfo);
690
+ // `receiveBufferSize()` would throws EBADF if called on an unbound socket.
691
+ // TODO: should we throw if the socket is not bound?
692
+ // assert(this[symbolSocketState].isBound === false, "invalid-state");
541
693
 
542
- if (exceptionInfo.code === "EBADF") {
543
- // TODO: handle the case where bad file descriptor is returned
544
- // This happens when the socket is not bound
545
- return this[symbolSocketState].receiveBufferSize;
694
+ // or we can auto-bind the socket if it's not bound?
695
+ if (this[symbolSocketState].isBound === false) {
696
+ this.#autoBind(this.network, this.#socketOptions.family);
546
697
  }
547
698
 
548
- console.log({
549
- value,
550
- });
699
+ // Note: on WSL, this may report a different value than the one set!
700
+ const ret = ioCall(SOCKET_UDP_GET_RECEIVE_BUFFER_SIZE, this.id);
701
+
702
+ assert(ret === -9, "invalid-state"); // EBADF
551
703
 
552
- return value;
704
+ return ret;
553
705
  }
554
706
 
555
707
  /**
@@ -561,10 +713,8 @@ export class UdpSocketImpl {
561
713
  setReceiveBufferSize(value) {
562
714
  assert(value === 0n, "invalid-argument", "The provided value was 0");
563
715
 
564
- const cappedValue = cappedUint32(value);
565
- const exceptionInfo = {};
566
- this.#socket.bufferSize(Number(cappedValue), BufferSizeFlags.SO_RCVBUF, exceptionInfo);
567
- this[symbolSocketState].receiveBufferSize = cappedValue;
716
+ value = Number(value);
717
+ ioCall(SOCKET_UDP_SET_RECEIVE_BUFFER_SIZE, this.id, { value });
568
718
  }
569
719
 
570
720
  /**
@@ -572,16 +722,17 @@ export class UdpSocketImpl {
572
722
  * @returns {bigint}
573
723
  */
574
724
  sendBufferSize() {
575
- const exceptionInfo = {};
576
- const value = this.#socket.bufferSize(0, BufferSizeFlags.SO_SNDBUF, exceptionInfo);
725
+ // TODO: should we throw if the socket is not bound?
726
+ // assert(this[symbolSocketState].isBound === false, "invalid-state");
577
727
 
578
- if (exceptionInfo.code === "EBADF") {
579
- // TODO: handle the case where bad file descriptor is returned
580
- // This happens when the socket is not bound
581
- return this[symbolSocketState].sendBufferSize;
582
- }
728
+ const ret = ioCall(SOCKET_UDP_GET_SEND_BUFFER_SIZE, this.id);
729
+ // if (ret === -9) {
730
+ // // TODO: handle the case where bad file descriptor (EBADF) is returned
731
+ // // This happens when the socket is not bound
732
+ // return this[symbolSocketState].sendBufferSize;
733
+ // }
583
734
 
584
- return value;
735
+ return ret;
585
736
  }
586
737
 
587
738
  /**
@@ -593,10 +744,10 @@ export class UdpSocketImpl {
593
744
  setSendBufferSize(value) {
594
745
  assert(value === 0n, "invalid-argument", "The provided value was 0");
595
746
 
596
- const cappedValue = cappedUint32(value);
597
- const exceptionInfo = {};
598
- this.#socket.bufferSize(Number(cappedValue), BufferSizeFlags.SO_SNDBUF, exceptionInfo);
599
- this[symbolSocketState].sendBufferSize = cappedValue;
747
+ // value = cappedUint32(value);
748
+ ioCall(SOCKET_UDP_SET_SEND_BUFFER_SIZE, this.id, { value });
749
+
750
+ // this.#socket.bufferSize(Number(cappedValue), BufferSizeFlags.SO_SNDBUF, exceptionInfo);
600
751
  }
601
752
 
602
753
  /**
@@ -604,25 +755,14 @@ export class UdpSocketImpl {
604
755
  * @returns {Pollable}
605
756
  */
606
757
  subscribe() {
758
+ if (this.#pollId) return pollableCreate(this.#pollId);
607
759
  return pollableCreate(0);
608
760
  }
609
761
 
610
762
  [symbolDispose]() {
611
- let err = null;
612
- err = this.#socket.recvStop((...args) => {
613
- console.log("stop recv", args);
614
- });
615
-
616
- if (err) {
617
- assert(err === -9, "invalid-state", "Interface is not currently Up");
618
- assert(err === -11, "not-in-progress");
619
- assert(true, "", err);
620
- }
621
-
622
- this.#socket.close();
623
- }
624
-
625
- handle() {
626
- return this.#socket;
763
+ ioCall(SOCKET_UDP_DISPOSE, this.id);
627
764
  }
628
765
  }
766
+
767
+ export const udpSocketImplCreate = UdpSocket._create;
768
+ delete UdpSocket._create;