@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.
- package/README.md +2 -2
- package/lib/browser/filesystem.js +6 -5
- package/lib/browser/http.js +1 -3
- package/lib/browser/random.js +1 -1
- package/lib/io/calls.js +128 -1
- package/lib/io/worker-http.js +159 -65
- package/lib/io/worker-io.js +40 -43
- package/lib/io/worker-socket-tcp.js +131 -0
- package/lib/io/worker-socket-udp.js +219 -0
- package/lib/io/worker-thread.js +288 -82
- package/lib/nodejs/cli.js +27 -11
- package/lib/nodejs/filesystem.js +89 -38
- package/lib/nodejs/http.js +643 -522
- package/lib/nodejs/index.js +0 -1
- package/lib/nodejs/sockets/socket-common.js +15 -2
- package/lib/nodejs/sockets/tcp-socket-impl.js +279 -188
- package/lib/nodejs/sockets/udp-socket-impl.js +305 -165
- package/lib/nodejs/sockets/wasi-sockets.js +54 -33
- package/lib/nodejs/sockets.js +25 -11
- package/lib/synckit/index.js +22 -39
- package/package.json +1 -1
- package/types/interfaces/wasi-http-types.d.ts +53 -41
- package/types/interfaces/wasi-sockets-tcp.d.ts +5 -0
|
@@ -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 {
|
|
14
|
-
|
|
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 =
|
|
18
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
return stream;
|
|
105
|
-
}
|
|
129
|
+
#pollId = 0;
|
|
130
|
+
#socketId = 0;
|
|
106
131
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
171
|
+
for (const datagram of datagrams) {
|
|
153
172
|
const { data, remoteAddress } = datagram;
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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
|
|
214
|
+
export class UdpSocket {
|
|
179
215
|
id = 1;
|
|
180
|
-
|
|
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:
|
|
244
|
+
localAddress: "",
|
|
211
245
|
localPort: 0,
|
|
212
|
-
remoteAddress:
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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
|
|
290
|
+
globalBoundAddresses.set(serializeIpAddress(boundAddress, true), this.id);
|
|
235
291
|
}
|
|
236
292
|
|
|
237
293
|
/**
|
|
238
294
|
*
|
|
239
295
|
* @param {Network} network
|
|
240
|
-
* @param {
|
|
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(
|
|
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() !==
|
|
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
|
-
|
|
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
|
|
351
|
+
const { localAddress, localIpSocketAddress, localPort } =
|
|
352
|
+
this.#socketOptions;
|
|
286
353
|
assert(isIP(localAddress) === 0, "address-not-bindable");
|
|
287
|
-
assert(
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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 =
|
|
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 (
|
|
347
|
-
|
|
348
|
-
|
|
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(
|
|
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
|
-
|
|
378
|
-
|
|
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].
|
|
413
|
-
|
|
414
|
-
|
|
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 !==
|
|
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(
|
|
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(
|
|
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 &&
|
|
650
|
+
value === true &&
|
|
651
|
+
this.#socketOptions.family.toLocaleLowerCase() === "ipv4",
|
|
505
652
|
"not-supported",
|
|
506
653
|
"Socket is an IPv4 socket."
|
|
507
654
|
);
|
|
508
|
-
assert(
|
|
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
|
|
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
|
-
|
|
540
|
-
|
|
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
|
|
543
|
-
|
|
544
|
-
|
|
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
|
-
|
|
549
|
-
|
|
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
|
|
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
|
-
|
|
565
|
-
|
|
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
|
-
|
|
576
|
-
|
|
725
|
+
// TODO: should we throw if the socket is not bound?
|
|
726
|
+
// assert(this[symbolSocketState].isBound === false, "invalid-state");
|
|
577
727
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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
|
|
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
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
this
|
|
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
|
-
|
|
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;
|