@bytecodealliance/preview2-shim 0.14.2 → 0.15.0
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/lib/browser/sockets.js +0 -14
- package/lib/io/calls.js +72 -135
- package/lib/io/worker-http.js +21 -18
- package/lib/io/worker-io.js +186 -44
- package/lib/io/worker-socket-tcp.js +250 -96
- package/lib/io/worker-socket-udp.js +524 -167
- package/lib/io/worker-sockets.js +371 -0
- package/lib/io/worker-thread.js +677 -489
- package/lib/nodejs/cli.js +3 -3
- package/lib/nodejs/clocks.js +9 -6
- package/lib/nodejs/filesystem.js +87 -25
- package/lib/nodejs/http.js +106 -96
- package/lib/nodejs/index.js +0 -2
- package/lib/nodejs/sockets.js +563 -17
- package/lib/synckit/index.js +4 -3
- package/package.json +2 -2
- package/lib/common/assert.js +0 -7
- package/lib/nodejs/sockets/socket-common.js +0 -129
- package/lib/nodejs/sockets/socketopts-bindings.js +0 -94
- package/lib/nodejs/sockets/tcp-socket-impl.js +0 -885
- package/lib/nodejs/sockets/udp-socket-impl.js +0 -768
- package/lib/nodejs/sockets/wasi-sockets.js +0 -341
- package/lib/synckit/index.d.ts +0 -71
|
@@ -1,768 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {import("../../types/interfaces/wasi-sockets-network").Network} Network
|
|
3
|
-
* @typedef {import("../../types/interfaces/wasi-sockets-network").IpSocketAddress} IpSocketAddress
|
|
4
|
-
* @typedef {import("../../types/interfaces/wasi-sockets-network").IpAddressFamily} IpAddressFamily
|
|
5
|
-
* @typedef {import("../../types/interfaces/wasi-sockets-udp").Datagram} Datagram
|
|
6
|
-
* @typedef {import("../../types/interfaces/wasi-io-poll-poll").Pollable} Pollable
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { isIP } from "node:net";
|
|
10
|
-
import { assert } from "../../common/assert.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";
|
|
36
|
-
|
|
37
|
-
const symbolDispose = Symbol.dispose || Symbol.for("dispose");
|
|
38
|
-
const symbolSocketState =
|
|
39
|
-
Symbol.SocketInternalState || Symbol.for("SocketInternalState");
|
|
40
|
-
const symbolOperations =
|
|
41
|
-
Symbol.SocketOperationsState || Symbol.for("SocketOperationsState");
|
|
42
|
-
|
|
43
|
-
// TODO: move to a common
|
|
44
|
-
const SocketConnectionState = {
|
|
45
|
-
Error: "Error",
|
|
46
|
-
Closed: "Closed",
|
|
47
|
-
Connecting: "Connecting",
|
|
48
|
-
Connected: "Connected",
|
|
49
|
-
Listening: "Listening",
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
// As a workaround, we store the bound address in a global map
|
|
53
|
-
// this is needed because 'address-in-use' is not always thrown when binding
|
|
54
|
-
// more than one socket to the same address
|
|
55
|
-
// TODO: remove this workaround when we figure out why!
|
|
56
|
-
/** @type {Map<string, number>} */
|
|
57
|
-
const globalBoundAddresses = new Map();
|
|
58
|
-
|
|
59
|
-
export class IncomingDatagramStream {
|
|
60
|
-
#pollId = 0;
|
|
61
|
-
#socketId = 0;
|
|
62
|
-
static _create(socketId) {
|
|
63
|
-
const stream = new IncomingDatagramStream();
|
|
64
|
-
stream.#socketId = socketId;
|
|
65
|
-
return stream;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
*
|
|
70
|
-
* @param {bigint} maxResults
|
|
71
|
-
* @returns {Datagram[]}
|
|
72
|
-
* @throws {invalid-state} The socket is not bound to any local address. (EINVAL)
|
|
73
|
-
* @throws {not-in-progress} The remote address is not reachable. (ECONNREFUSED, ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN)
|
|
74
|
-
* @throws {remote-unreachable} The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET)
|
|
75
|
-
* @throws {connection-refused} The connection was refused. (ECONNREFUSED)
|
|
76
|
-
* @throws {would-block} There is no pending data available to be read at the moment. (EWOULDBLOCK, EAGAIN)
|
|
77
|
-
*/
|
|
78
|
-
receive(maxResults) {
|
|
79
|
-
if (maxResults === 0n) {
|
|
80
|
-
return [];
|
|
81
|
-
}
|
|
82
|
-
|
|
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
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
*
|
|
114
|
-
* @returns {Pollable} A pollable which will resolve once the stream is ready to receive again.
|
|
115
|
-
*/
|
|
116
|
-
subscribe() {
|
|
117
|
-
if (this.#pollId) return pollableCreate(this.#pollId);
|
|
118
|
-
return pollableCreate(0);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
[symbolDispose]() {
|
|
122
|
-
// TODO: stop receiving
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
const incomingDatagramStreamCreate = IncomingDatagramStream._create;
|
|
126
|
-
delete IncomingDatagramStream._create;
|
|
127
|
-
|
|
128
|
-
export class OutgoingDatagramStream {
|
|
129
|
-
#pollId = 0;
|
|
130
|
-
#socketId = 0;
|
|
131
|
-
|
|
132
|
-
static _create(socketId) {
|
|
133
|
-
const stream = new OutgoingDatagramStream(socketId);
|
|
134
|
-
stream.#socketId = socketId;
|
|
135
|
-
return stream;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
*
|
|
140
|
-
* @returns {bigint}
|
|
141
|
-
*/
|
|
142
|
-
checkSend() {
|
|
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;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
*
|
|
152
|
-
* @param {Datagram[]} datagrams
|
|
153
|
-
* @returns {bigint}
|
|
154
|
-
* @throws {invalid-argument} The `remote-address` has the wrong address family. (EAFNOSUPPORT)
|
|
155
|
-
* @throws {invalid-argument} `remote-address` is a non-IPv4-mapped IPv6 address, but the socket was bound to a specific IPv4-mapped IPv6 address. (or vice versa)
|
|
156
|
-
* @throws {invalid-argument} The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL)
|
|
157
|
-
* @throws {invalid-argument} The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL)
|
|
158
|
-
* @throws {invalid-argument} The socket is in "connected" mode and the `datagram.remote-address` does not match the address passed to `connect`. (EISCONN)
|
|
159
|
-
* @throws {invalid-argument} The socket is not "connected" and no value for `remote-address` was provided. (EDESTADDRREQ)
|
|
160
|
-
* @throws {remote-unreachable} The remote address is not reachable. (ECONNREFUSED, ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN)
|
|
161
|
-
* @throws {connection-refused} The connection was refused. (ECONNREFUSED)
|
|
162
|
-
* @throws {datagram-too-large} The datagram is too large. (EMSGSIZE)
|
|
163
|
-
*/
|
|
164
|
-
send(datagrams) {
|
|
165
|
-
if (datagrams.length === 0) {
|
|
166
|
-
return 0n;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
let datagramsSent = 0n;
|
|
170
|
-
|
|
171
|
-
for (const datagram of datagrams) {
|
|
172
|
-
const { data, remoteAddress } = datagram;
|
|
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;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
*
|
|
200
|
-
* @returns {Pollable} A pollable which will resolve once the stream is ready to send again.
|
|
201
|
-
*/
|
|
202
|
-
subscribe() {
|
|
203
|
-
if (this.#pollId) return pollableCreate(this.#pollId);
|
|
204
|
-
return pollableCreate(0);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
[symbolDispose]() {
|
|
208
|
-
// TODO: stop sending
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
const outgoingDatagramStreamCreate = OutgoingDatagramStream._create;
|
|
212
|
-
delete OutgoingDatagramStream._create;
|
|
213
|
-
|
|
214
|
-
export class UdpSocket {
|
|
215
|
-
id = 1;
|
|
216
|
-
#pollId = 0;
|
|
217
|
-
/** @type {Network} */ network = null;
|
|
218
|
-
|
|
219
|
-
// track in-progress operations
|
|
220
|
-
// counter must be 0 for the operation to be considered complete
|
|
221
|
-
// we increment the counter when the operation starts
|
|
222
|
-
// and decrement it when the operation finishes
|
|
223
|
-
[symbolOperations] = {
|
|
224
|
-
bind: 0,
|
|
225
|
-
connect: 0,
|
|
226
|
-
listen: 0,
|
|
227
|
-
accept: 0,
|
|
228
|
-
receive: 0,
|
|
229
|
-
send: 0,
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
[symbolSocketState] = {
|
|
233
|
-
lastErrorState: null,
|
|
234
|
-
isBound: false,
|
|
235
|
-
ipv6Only: false,
|
|
236
|
-
connectionState: SocketConnectionState.Closed,
|
|
237
|
-
|
|
238
|
-
// TODO: what these default values should be?
|
|
239
|
-
unicastHopLimit: 255, // 1-255
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
#socketOptions = {
|
|
243
|
-
family: "ipv4",
|
|
244
|
-
localAddress: "",
|
|
245
|
-
localPort: 0,
|
|
246
|
-
remoteAddress: "",
|
|
247
|
-
remotePort: 0,
|
|
248
|
-
reuseAddr: true,
|
|
249
|
-
localIpSocketAddress: null,
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
get _pollId() {
|
|
253
|
-
return this.#pollId;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* @param {IpAddressFamily} addressFamily
|
|
258
|
-
* @returns {void}
|
|
259
|
-
*/
|
|
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
|
-
}
|
|
271
|
-
|
|
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();
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
#cacheBoundAddress() {
|
|
284
|
-
let { localIpSocketAddress: boundAddress, localPort } = this.#socketOptions;
|
|
285
|
-
// when port is 0, the OS will assign an ephemeral port
|
|
286
|
-
// we need to get the actual port assigned by the OS
|
|
287
|
-
if (localPort === 0) {
|
|
288
|
-
boundAddress = this.localAddress();
|
|
289
|
-
}
|
|
290
|
-
globalBoundAddresses.set(serializeIpAddress(boundAddress, true), this.id);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
*
|
|
295
|
-
* @param {Network} network
|
|
296
|
-
* @param {IpSocketAddress} localAddress
|
|
297
|
-
* @returns {void}
|
|
298
|
-
* @throws {invalid-argument} The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows)
|
|
299
|
-
* @throws {invalid-state} The socket is already bound. (EINVAL)
|
|
300
|
-
*/
|
|
301
|
-
startBind(network, localAddress) {
|
|
302
|
-
if (!this.allowed()) throw "access-denied";
|
|
303
|
-
try {
|
|
304
|
-
assert(
|
|
305
|
-
this[symbolSocketState].isBound,
|
|
306
|
-
"invalid-state",
|
|
307
|
-
"The socket is already bound"
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
const address = serializeIpAddress(localAddress);
|
|
311
|
-
const ipFamily = `ipv${isIP(address)}`;
|
|
312
|
-
|
|
313
|
-
assert(
|
|
314
|
-
this.#socketOptions.family.toLocaleLowerCase() !==
|
|
315
|
-
ipFamily.toLocaleLowerCase(),
|
|
316
|
-
"invalid-argument",
|
|
317
|
-
"The `local-address` has the wrong address family"
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
assert(
|
|
321
|
-
isIPv4MappedAddress(localAddress) && this.ipv6Only(),
|
|
322
|
-
"invalid-argument"
|
|
323
|
-
);
|
|
324
|
-
|
|
325
|
-
const { port } = localAddress.val;
|
|
326
|
-
this.#socketOptions.localIpSocketAddress = localAddress;
|
|
327
|
-
this.#socketOptions.localAddress = address;
|
|
328
|
-
this.#socketOptions.localPort = port;
|
|
329
|
-
this.network = network;
|
|
330
|
-
this[symbolOperations].bind++;
|
|
331
|
-
this[symbolSocketState].lastErrorState = null;
|
|
332
|
-
} catch (err) {
|
|
333
|
-
this[symbolSocketState].lastErrorState = err;
|
|
334
|
-
throw err;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
*
|
|
340
|
-
* @returns {void}
|
|
341
|
-
* @throws {address-in-use} No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows)
|
|
342
|
-
* @throws {address-in-use} Address is already in use. (EADDRINUSE)
|
|
343
|
-
* @throws {address-not-bindable} `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL)
|
|
344
|
-
* @throws {not-in-progress} A `bind` operation is not in progress.
|
|
345
|
-
* @throws {would-block} Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN)
|
|
346
|
-
**/
|
|
347
|
-
finishBind() {
|
|
348
|
-
try {
|
|
349
|
-
assert(this[symbolOperations].bind === 0, "not-in-progress");
|
|
350
|
-
|
|
351
|
-
const { localAddress, localIpSocketAddress, localPort } =
|
|
352
|
-
this.#socketOptions;
|
|
353
|
-
assert(isIP(localAddress) === 0, "address-not-bindable");
|
|
354
|
-
assert(
|
|
355
|
-
globalBoundAddresses.has(
|
|
356
|
-
serializeIpAddress(localIpSocketAddress, true)
|
|
357
|
-
),
|
|
358
|
-
"address-in-use"
|
|
359
|
-
);
|
|
360
|
-
|
|
361
|
-
const err = ioCall(SOCKET_UDP_BIND, this.id, {
|
|
362
|
-
localAddress,
|
|
363
|
-
localPort,
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
if (err === 0) {
|
|
367
|
-
this[symbolSocketState].isBound = true;
|
|
368
|
-
} else {
|
|
369
|
-
assert(err === -22, "address-in-use");
|
|
370
|
-
assert(err === -48, "address-in-use"); // macos
|
|
371
|
-
assert(err === -49, "address-not-bindable");
|
|
372
|
-
assert(err === -98, "address-in-use"); // WSL
|
|
373
|
-
assert(err === -99, "address-not-bindable"); // EADDRNOTAVAIL
|
|
374
|
-
// catch all other errors
|
|
375
|
-
assert(true, "unknown", err);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
this[symbolSocketState].lastErrorState = null;
|
|
379
|
-
this[symbolSocketState].isBound = true;
|
|
380
|
-
this[symbolOperations].bind--;
|
|
381
|
-
|
|
382
|
-
this.#cacheBoundAddress();
|
|
383
|
-
} catch (err) {
|
|
384
|
-
this[symbolSocketState].lastErrorState = err;
|
|
385
|
-
throw err;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* Alias for startBind() and finishBind()
|
|
391
|
-
* @param {Network} network
|
|
392
|
-
* @param {IpAddressFamily} localAddress
|
|
393
|
-
*/
|
|
394
|
-
bind(network, localAddress) {
|
|
395
|
-
this.startBind(network, localAddress);
|
|
396
|
-
this.finishBind();
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
/**
|
|
400
|
-
*
|
|
401
|
-
* @param {Network} network
|
|
402
|
-
* @param {IpAddressFamily | undefined} remoteAddress
|
|
403
|
-
* @returns {void}
|
|
404
|
-
* @throws {invalid-argument} The `remote-address` has the wrong address family. (EAFNOSUPPORT)
|
|
405
|
-
* @throws {invalid-argument} `remote-address` is a non-IPv4-mapped IPv6 address, but the socket was bound to a specific IPv4-mapped IPv6 address. (or vice versa)
|
|
406
|
-
* @throws {invalid-argument} The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL)
|
|
407
|
-
* @throws {invalid-argument} The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows)
|
|
408
|
-
* @throws {invalid-argument} The socket is already bound to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`.
|
|
409
|
-
*/
|
|
410
|
-
#startConnect(network, remoteAddress = undefined) {
|
|
411
|
-
this[symbolOperations].connect++;
|
|
412
|
-
|
|
413
|
-
if (
|
|
414
|
-
remoteAddress === undefined ||
|
|
415
|
-
this[symbolSocketState].connectionState ===
|
|
416
|
-
SocketConnectionState.Connected
|
|
417
|
-
) {
|
|
418
|
-
this.#socketOptions.remoteAddress = undefined;
|
|
419
|
-
this.#socketOptions.remotePort = 0;
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
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
|
-
|
|
438
|
-
const host = serializeIpAddress(remoteAddress);
|
|
439
|
-
const ipFamily = `ipv${isIP(host)}`;
|
|
440
|
-
|
|
441
|
-
assert(ipFamily.toLocaleLowerCase() === "ipv0", "invalid-argument");
|
|
442
|
-
assert(
|
|
443
|
-
this.#socketOptions.family.toLocaleLowerCase() !==
|
|
444
|
-
ipFamily.toLocaleLowerCase(),
|
|
445
|
-
"invalid-argument"
|
|
446
|
-
);
|
|
447
|
-
|
|
448
|
-
const { port } = remoteAddress.val;
|
|
449
|
-
this.#socketOptions.remoteAddress = host; // can be undefined
|
|
450
|
-
this.#socketOptions.remotePort = port;
|
|
451
|
-
this.network = network;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
/**
|
|
455
|
-
*
|
|
456
|
-
* @returns {void}
|
|
457
|
-
* @throws {address-in-use} Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD)
|
|
458
|
-
* @throws {not-in-progress} A `connect` operation is not in progress.
|
|
459
|
-
* @throws {would-block} Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN)
|
|
460
|
-
*/
|
|
461
|
-
#finishConnect() {
|
|
462
|
-
// Note: remoteAddress can be undefined
|
|
463
|
-
const { remoteAddress, remotePort } = this.#socketOptions;
|
|
464
|
-
this[symbolSocketState].connectionState = SocketConnectionState.Connecting;
|
|
465
|
-
|
|
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
|
-
});
|
|
482
|
-
|
|
483
|
-
if (!err) {
|
|
484
|
-
this[symbolSocketState].connectionState = SocketConnectionState.Connected;
|
|
485
|
-
} else {
|
|
486
|
-
assert(err === -22, "invalid-argument");
|
|
487
|
-
assert(true, "unknown", err);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
this[symbolOperations].connect--;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
/**
|
|
494
|
-
* Alias for startBind() and finishBind()
|
|
495
|
-
*/
|
|
496
|
-
#connect(network, remoteAddress = undefined) {
|
|
497
|
-
this.#startConnect(network, remoteAddress);
|
|
498
|
-
this.#finishConnect();
|
|
499
|
-
}
|
|
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
|
-
|
|
513
|
-
/**
|
|
514
|
-
*
|
|
515
|
-
* @param {IpSocketAddress | undefined} remoteAddress
|
|
516
|
-
* @returns {Array<IncomingDatagramStream, OutgoingDatagramStream>}
|
|
517
|
-
* @throws {invalid-argument} The `remote-address` has the wrong address family. (EAFNOSUPPORT)
|
|
518
|
-
* @throws {invalid-argument} remote-address` is a non-IPv4-mapped IPv6 address, but the socket was bound to a specific IPv4-mapped IPv6 address. (or vice versa)
|
|
519
|
-
* @throws {invalid-argument} The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / :`). (EDESTADDRREQ, EADDRNOTAVAIL)
|
|
520
|
-
* @throws {invalid-argument} The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL)
|
|
521
|
-
* @throws {invalid-state} The socket is not bound.
|
|
522
|
-
* @throws {address-in-use} Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD)
|
|
523
|
-
* @throws {remote-unreachable} The remote address is not reachable. (ECONNRESET, ENETRESET, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET)
|
|
524
|
-
* @throws {connection-refused} The connection was refused. (ECONNREFUSED)
|
|
525
|
-
*/
|
|
526
|
-
stream(remoteAddress = undefined) {
|
|
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
|
-
];
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
*
|
|
560
|
-
* @returns {IpSocketAddress}
|
|
561
|
-
* @throws {invalid-state} The socket is not bound to any local address.
|
|
562
|
-
*/
|
|
563
|
-
localAddress() {
|
|
564
|
-
assert(this[symbolSocketState].isBound === false, "invalid-state");
|
|
565
|
-
|
|
566
|
-
const out = ioCall(SOCKET_UDP_GET_LOCAL_ADDRESS, this.id);
|
|
567
|
-
|
|
568
|
-
const { address, port, family } = out;
|
|
569
|
-
this.#socketOptions.localAddress = address;
|
|
570
|
-
this.#socketOptions.localPort = port;
|
|
571
|
-
this.#socketOptions.family = family.toLocaleLowerCase();
|
|
572
|
-
|
|
573
|
-
return {
|
|
574
|
-
tag: family.toLocaleLowerCase(),
|
|
575
|
-
val: {
|
|
576
|
-
address: deserializeIpAddress(address, family),
|
|
577
|
-
port,
|
|
578
|
-
},
|
|
579
|
-
};
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
/**
|
|
583
|
-
*
|
|
584
|
-
* @returns {IpSocketAddress}
|
|
585
|
-
* @throws {invalid-state} The socket is not streaming to a specific remote address. (ENOTCONN)
|
|
586
|
-
*/
|
|
587
|
-
remoteAddress() {
|
|
588
|
-
assert(
|
|
589
|
-
this[symbolSocketState].connectionState !==
|
|
590
|
-
SocketConnectionState.Connected,
|
|
591
|
-
"invalid-state",
|
|
592
|
-
"The socket is not streaming to a specific remote address"
|
|
593
|
-
);
|
|
594
|
-
|
|
595
|
-
const out = ioCall(SOCKET_UDP_GET_REMOTE_ADDRESS, this.id);
|
|
596
|
-
|
|
597
|
-
assert(
|
|
598
|
-
out.address === undefined,
|
|
599
|
-
"invalid-state",
|
|
600
|
-
"The socket is not streaming to a specific remote address"
|
|
601
|
-
);
|
|
602
|
-
|
|
603
|
-
const { address, port, family } = out;
|
|
604
|
-
this.#socketOptions.remoteAddress = address;
|
|
605
|
-
this.#socketOptions.remotePort = port;
|
|
606
|
-
this.#socketOptions.family = family.toLocaleLowerCase();
|
|
607
|
-
|
|
608
|
-
return {
|
|
609
|
-
tag: family.toLocaleLowerCase(),
|
|
610
|
-
val: {
|
|
611
|
-
address: deserializeIpAddress(address, family),
|
|
612
|
-
port,
|
|
613
|
-
},
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
/**
|
|
618
|
-
*
|
|
619
|
-
* @returns {IpAddressFamily}
|
|
620
|
-
*/
|
|
621
|
-
addressFamily() {
|
|
622
|
-
return this.#socketOptions.family;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
/**
|
|
626
|
-
*
|
|
627
|
-
* @returns {boolean}
|
|
628
|
-
* @throws {not-supported} (get/set) `this` socket is an IPv4 socket.
|
|
629
|
-
*/
|
|
630
|
-
ipv6Only() {
|
|
631
|
-
assert(
|
|
632
|
-
this.#socketOptions.family.toLocaleLowerCase() === "ipv4",
|
|
633
|
-
"not-supported",
|
|
634
|
-
"Socket is an IPv4 socket."
|
|
635
|
-
);
|
|
636
|
-
|
|
637
|
-
return this[symbolSocketState].ipv6Only;
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
/**
|
|
641
|
-
*
|
|
642
|
-
* @param {boolean} value
|
|
643
|
-
* @returns {void}
|
|
644
|
-
* @throws {not-supported} (get/set) `this` socket is an IPv4 socket.
|
|
645
|
-
* @throws {invalid-state} (set) The socket is already bound.
|
|
646
|
-
* @throws {not-supported} (set) Host does not support dual-stack sockets. (Implementations are not required to.)
|
|
647
|
-
*/
|
|
648
|
-
setIpv6Only(value) {
|
|
649
|
-
assert(
|
|
650
|
-
value === true &&
|
|
651
|
-
this.#socketOptions.family.toLocaleLowerCase() === "ipv4",
|
|
652
|
-
"not-supported",
|
|
653
|
-
"Socket is an IPv4 socket."
|
|
654
|
-
);
|
|
655
|
-
assert(
|
|
656
|
-
this[symbolSocketState].isBound,
|
|
657
|
-
"invalid-state",
|
|
658
|
-
"The socket is already bound"
|
|
659
|
-
);
|
|
660
|
-
|
|
661
|
-
this[symbolSocketState].ipv6Only = value;
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
/**
|
|
665
|
-
*
|
|
666
|
-
* @returns {number}
|
|
667
|
-
*/
|
|
668
|
-
unicastHopLimit() {
|
|
669
|
-
return this[symbolSocketState].unicastHopLimit;
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
/**
|
|
673
|
-
*
|
|
674
|
-
* @param {number} value
|
|
675
|
-
* @returns {void}
|
|
676
|
-
* @throws {invalid-argument} The TTL value must be 1 or higher.
|
|
677
|
-
*/
|
|
678
|
-
setUnicastHopLimit(value) {
|
|
679
|
-
assert(value < 1, "invalid-argument", "The TTL value must be 1 or higher");
|
|
680
|
-
|
|
681
|
-
ioCall(SOCKET_UDP_SET_UNICAST_HOP_LIMIT, this.id, { value });
|
|
682
|
-
this[symbolSocketState].unicastHopLimit = value;
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
/**
|
|
686
|
-
*
|
|
687
|
-
* @returns {bigint}
|
|
688
|
-
*/
|
|
689
|
-
receiveBufferSize() {
|
|
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");
|
|
693
|
-
|
|
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);
|
|
697
|
-
}
|
|
698
|
-
|
|
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
|
|
703
|
-
|
|
704
|
-
return ret;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
/**
|
|
708
|
-
*
|
|
709
|
-
* @param {bigint} value
|
|
710
|
-
* @returns {void}
|
|
711
|
-
* @throws {invalid-argument} The provided value was 0.
|
|
712
|
-
*/
|
|
713
|
-
setReceiveBufferSize(value) {
|
|
714
|
-
assert(value === 0n, "invalid-argument", "The provided value was 0");
|
|
715
|
-
|
|
716
|
-
value = Number(value);
|
|
717
|
-
ioCall(SOCKET_UDP_SET_RECEIVE_BUFFER_SIZE, this.id, { value });
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
/**
|
|
721
|
-
*
|
|
722
|
-
* @returns {bigint}
|
|
723
|
-
*/
|
|
724
|
-
sendBufferSize() {
|
|
725
|
-
// TODO: should we throw if the socket is not bound?
|
|
726
|
-
// assert(this[symbolSocketState].isBound === false, "invalid-state");
|
|
727
|
-
|
|
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
|
-
// }
|
|
734
|
-
|
|
735
|
-
return ret;
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
/**
|
|
739
|
-
*
|
|
740
|
-
* @param {bigint} value
|
|
741
|
-
* @returns {void}
|
|
742
|
-
* @throws {invalid-argument} The provided value was 0.
|
|
743
|
-
*/
|
|
744
|
-
setSendBufferSize(value) {
|
|
745
|
-
assert(value === 0n, "invalid-argument", "The provided value was 0");
|
|
746
|
-
|
|
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);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
/**
|
|
754
|
-
*
|
|
755
|
-
* @returns {Pollable}
|
|
756
|
-
*/
|
|
757
|
-
subscribe() {
|
|
758
|
-
if (this.#pollId) return pollableCreate(this.#pollId);
|
|
759
|
-
return pollableCreate(0);
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
[symbolDispose]() {
|
|
763
|
-
ioCall(SOCKET_UDP_DISPOSE, this.id);
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
export const udpSocketImplCreate = UdpSocket._create;
|
|
768
|
-
delete UdpSocket._create;
|