@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,219 +1,576 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* @typedef {import("../../types/interfaces/wasi-sockets-network").IpAddressFamily} IpAddressFamily
|
|
3
|
-
*/
|
|
4
1
|
import { createSocket } from "node:dgram";
|
|
2
|
+
import {
|
|
3
|
+
createFuture,
|
|
4
|
+
futureDispose,
|
|
5
|
+
futureTakeValue,
|
|
6
|
+
pollStateReady,
|
|
7
|
+
verifyPollsDroppedForDrop,
|
|
8
|
+
} from "./worker-thread.js";
|
|
9
|
+
import {
|
|
10
|
+
convertSocketError,
|
|
11
|
+
convertSocketErrorCode,
|
|
12
|
+
getDefaultReceiveBufferSize,
|
|
13
|
+
getDefaultSendBufferSize,
|
|
14
|
+
ipSocketAddress,
|
|
15
|
+
isIPv4MappedAddress,
|
|
16
|
+
isWildcardAddress,
|
|
17
|
+
noLookup,
|
|
18
|
+
serializeIpAddress,
|
|
19
|
+
SOCKET_STATE_BIND,
|
|
20
|
+
SOCKET_STATE_BOUND,
|
|
21
|
+
SOCKET_STATE_CLOSED,
|
|
22
|
+
SOCKET_STATE_CONNECTION,
|
|
23
|
+
SOCKET_STATE_INIT,
|
|
24
|
+
} from "./worker-sockets.js";
|
|
5
25
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
26
|
+
// Experimental support for batched UDP sends. Set this to true to enable.
|
|
27
|
+
// This is not enabled by default because we need to figure out how to know
|
|
28
|
+
// how many datagrams were sent when there is an error in a batch.
|
|
29
|
+
// See the err path in "handler" in the "doSendBatch" of socketOutgoingDatagramStreamSend.
|
|
30
|
+
const UDP_BATCH_SENDS = false;
|
|
9
31
|
|
|
10
|
-
/**
|
|
11
|
-
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {import("../../types/interfaces/wasi-sockets-network.js").IpSocketAddress} IpSocketAddress
|
|
34
|
+
* @typedef {import("../../../types/interfaces/wasi-sockets-tcp.js").IpAddressFamily} IpAddressFamily
|
|
35
|
+
*
|
|
36
|
+
*
|
|
37
|
+
* @typedef {{
|
|
38
|
+
* state: number,
|
|
39
|
+
* remoteAddress: string | null,
|
|
40
|
+
* remotePort: number | null,
|
|
41
|
+
* sendBufferSize: number | null,
|
|
42
|
+
* receiveBufferSize: number | null,
|
|
43
|
+
* unicastHopLimit: number,
|
|
44
|
+
* udpSocket: import('node:dgram').Socket,
|
|
45
|
+
* future: number | null,
|
|
46
|
+
* serializedLocalAddress: string | null,
|
|
47
|
+
* pollState: PollState,
|
|
48
|
+
* incomingDatagramStream: number | null,
|
|
49
|
+
* outgoingDatagramStream: number | null,
|
|
50
|
+
* }} UdpSocketRecord
|
|
51
|
+
*
|
|
52
|
+
* @typedef {{
|
|
53
|
+
* active: bool,
|
|
54
|
+
* error: any | null,
|
|
55
|
+
* socket: UdpSocketRecord,
|
|
56
|
+
* pollState: PollState,
|
|
57
|
+
* queue?: Buffer[],
|
|
58
|
+
* cleanup: () => void | null,
|
|
59
|
+
* }} DatagramStreamRecord
|
|
60
|
+
*
|
|
61
|
+
*/
|
|
12
62
|
|
|
13
|
-
|
|
14
|
-
|
|
63
|
+
let udpSocketCnt = 0,
|
|
64
|
+
datagramStreamCnt = 0;
|
|
15
65
|
|
|
16
|
-
|
|
66
|
+
/**
|
|
67
|
+
* @type {Map<number, UdpSocketRecord>}
|
|
68
|
+
*/
|
|
69
|
+
export const udpSockets = new Map();
|
|
17
70
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
71
|
+
/**
|
|
72
|
+
* @type {Map<number, DatagramStreamRecord>}
|
|
73
|
+
*/
|
|
74
|
+
export const datagramStreams = new Map();
|
|
23
75
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
76
|
+
/**
|
|
77
|
+
* @param {IpAddressFamily} addressFamily
|
|
78
|
+
* @returns {number}
|
|
79
|
+
*/
|
|
80
|
+
export function createUdpSocket({ family, unicastHopLimit }) {
|
|
81
|
+
const udpSocket = createSocket({
|
|
82
|
+
type: family === "ipv6" ? "udp6" : "udp4",
|
|
83
|
+
reuseAddr: false,
|
|
84
|
+
ipv6Only: family === "ipv6",
|
|
85
|
+
lookup: noLookup,
|
|
86
|
+
});
|
|
87
|
+
udpSockets.set(++udpSocketCnt, {
|
|
88
|
+
state: SOCKET_STATE_INIT,
|
|
89
|
+
remoteAddress: null,
|
|
90
|
+
remotePort: null,
|
|
91
|
+
sendBufferSize: null,
|
|
92
|
+
receiveBufferSize: null,
|
|
93
|
+
unicastHopLimit,
|
|
94
|
+
udpSocket,
|
|
95
|
+
future: null,
|
|
96
|
+
serializedLocalAddress: null,
|
|
97
|
+
pollState: { ready: true, listener: null, polls: [], parentStream: null },
|
|
98
|
+
incomingDatagramStream: null,
|
|
99
|
+
outgoingDatagramStream: null,
|
|
100
|
+
});
|
|
101
|
+
return udpSocketCnt;
|
|
28
102
|
}
|
|
29
103
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
104
|
+
/**
|
|
105
|
+
* @param {UdpSocketRecord} socket
|
|
106
|
+
* @returns {DatagramStreamRecord}
|
|
107
|
+
*/
|
|
108
|
+
function createIncomingDatagramStream(socket) {
|
|
109
|
+
const id = ++datagramStreamCnt;
|
|
110
|
+
const pollState = {
|
|
111
|
+
ready: false,
|
|
112
|
+
listener: null,
|
|
113
|
+
polls: [],
|
|
114
|
+
parentStream: null,
|
|
115
|
+
};
|
|
116
|
+
const datagramStream = {
|
|
117
|
+
id,
|
|
118
|
+
active: true,
|
|
119
|
+
error: null,
|
|
120
|
+
socket,
|
|
121
|
+
queue: [],
|
|
122
|
+
cleanup,
|
|
123
|
+
pollState,
|
|
124
|
+
};
|
|
125
|
+
const { udpSocket } = socket;
|
|
126
|
+
datagramStreams.set(id, datagramStream);
|
|
127
|
+
function cleanup() {
|
|
128
|
+
udpSocket.off("message", onMessage);
|
|
129
|
+
udpSocket.off("error", onError);
|
|
130
|
+
}
|
|
131
|
+
function onMessage(data, rinfo) {
|
|
132
|
+
const family = rinfo.family.toLowerCase();
|
|
133
|
+
datagramStream.queue.push({
|
|
134
|
+
data,
|
|
135
|
+
remoteAddress: ipSocketAddress(family, rinfo.address, rinfo.port),
|
|
136
|
+
});
|
|
137
|
+
if (!pollState.ready) pollStateReady(pollState);
|
|
138
|
+
}
|
|
139
|
+
function onError(err) {
|
|
140
|
+
datagramStream.error = err;
|
|
141
|
+
pollStateReady(datagramStream.pollState);
|
|
142
|
+
}
|
|
143
|
+
udpSocket.on("message", onMessage);
|
|
144
|
+
udpSocket.once("error", onError);
|
|
145
|
+
return datagramStream;
|
|
34
146
|
}
|
|
35
147
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
148
|
+
/**
|
|
149
|
+
* @param {UdpSocketRecord} socket
|
|
150
|
+
* @returns {DatagramStreamRecord}
|
|
151
|
+
*/
|
|
152
|
+
function createOutgoingDatagramStream(socket) {
|
|
153
|
+
const id = ++datagramStreamCnt;
|
|
154
|
+
const datagramStream = {
|
|
155
|
+
id,
|
|
156
|
+
active: true,
|
|
157
|
+
error: null,
|
|
158
|
+
socket,
|
|
159
|
+
cleanup,
|
|
160
|
+
pollState: { ready: true, listener: null, polls: [], parentStream: null },
|
|
49
161
|
};
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
162
|
+
const { udpSocket } = socket;
|
|
163
|
+
datagramStreams.set(id, datagramStream);
|
|
164
|
+
udpSocket.on("error", onError);
|
|
165
|
+
function onError(err) {
|
|
166
|
+
datagramStream.error = err;
|
|
167
|
+
pollStateReady(datagramStream.pollState);
|
|
168
|
+
}
|
|
169
|
+
function cleanup() {
|
|
170
|
+
udpSocket.off("error", onError);
|
|
54
171
|
}
|
|
172
|
+
return datagramStream;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function socketUdpBindStart(id, localAddress, family) {
|
|
176
|
+
const socket = udpSockets.get(id);
|
|
55
177
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
178
|
+
if (family !== localAddress.tag || isIPv4MappedAddress(localAddress))
|
|
179
|
+
throw "invalid-argument";
|
|
180
|
+
|
|
181
|
+
const serializedLocalAddress = serializeIpAddress(localAddress);
|
|
182
|
+
|
|
183
|
+
if (socket.state !== SOCKET_STATE_INIT) throw "invalid-state";
|
|
184
|
+
socket.state = SOCKET_STATE_BIND;
|
|
185
|
+
const { udpSocket } = socket;
|
|
186
|
+
socket.future = createFuture(
|
|
187
|
+
new Promise((resolve, reject) => {
|
|
188
|
+
function bindOk() {
|
|
189
|
+
resolve();
|
|
190
|
+
udpSocket.off("error", bindErr);
|
|
191
|
+
}
|
|
192
|
+
function bindErr(err) {
|
|
193
|
+
reject(convertSocketError(err));
|
|
194
|
+
udpSocket.off("listening", bindOk);
|
|
195
|
+
}
|
|
196
|
+
udpSocket.once("listening", bindOk);
|
|
197
|
+
udpSocket.once("error", bindErr);
|
|
198
|
+
udpSocket.bind(localAddress.val.port, serializedLocalAddress);
|
|
199
|
+
}),
|
|
200
|
+
socket.pollState
|
|
201
|
+
);
|
|
59
202
|
}
|
|
60
203
|
|
|
61
|
-
|
|
204
|
+
export function socketUdpBindFinish(id) {
|
|
205
|
+
const socket = udpSockets.get(id);
|
|
206
|
+
if (socket.state !== SOCKET_STATE_BIND) throw "not-in-progress";
|
|
207
|
+
if (!socket.pollState.ready) throw "would-block";
|
|
208
|
+
const { tag, val } = futureTakeValue(socket.future).val;
|
|
209
|
+
futureDispose(socket.future, false);
|
|
210
|
+
socket.future = null;
|
|
211
|
+
if (tag === "err") {
|
|
212
|
+
socket.state = SOCKET_STATE_CLOSED;
|
|
213
|
+
throw val;
|
|
214
|
+
} else {
|
|
215
|
+
// once bound, we can now set the options
|
|
216
|
+
// since Node.js doesn't support setting them until bound
|
|
217
|
+
socket.udpSocket.setTTL(socket.unicastHopLimit);
|
|
218
|
+
if (socket.sendBufferSize)
|
|
219
|
+
socket.udpSocket.setRecvBufferSize(socket.sendBufferSize);
|
|
220
|
+
if (socket.receieveBufferSize)
|
|
221
|
+
socket.udpSocket.setSendBufferSize(socket.receiveBufferSize);
|
|
222
|
+
socket.state = SOCKET_STATE_BOUND;
|
|
223
|
+
return val;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
62
226
|
|
|
63
227
|
/**
|
|
64
|
-
* @param {
|
|
65
|
-
* @returns {
|
|
228
|
+
* @param {number} id
|
|
229
|
+
* @returns {IpSocketAddress}
|
|
66
230
|
*/
|
|
67
|
-
export function
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
resolve({
|
|
77
|
-
id: socketCnt,
|
|
78
|
-
socket,
|
|
79
|
-
});
|
|
80
|
-
} catch (e) {
|
|
81
|
-
reject(e);
|
|
82
|
-
}
|
|
83
|
-
});
|
|
231
|
+
export function socketUdpGetLocalAddress(id) {
|
|
232
|
+
const { udpSocket } = udpSockets.get(id);
|
|
233
|
+
let address, family, port;
|
|
234
|
+
try {
|
|
235
|
+
({ address, family, port } = udpSocket.address());
|
|
236
|
+
} catch (err) {
|
|
237
|
+
throw convertSocketError(err);
|
|
238
|
+
}
|
|
239
|
+
return ipSocketAddress(family.toLowerCase(), address, port);
|
|
84
240
|
}
|
|
85
241
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
242
|
+
/**
|
|
243
|
+
* @param {number} id
|
|
244
|
+
* @returns {IpSocketAddress}
|
|
245
|
+
*/
|
|
246
|
+
export function socketUdpGetRemoteAddress(id) {
|
|
247
|
+
const { udpSocket } = udpSockets.get(id);
|
|
248
|
+
let address, family, port;
|
|
249
|
+
try {
|
|
250
|
+
({ address, family, port } = udpSocket.remoteAddress());
|
|
251
|
+
} catch (err) {
|
|
252
|
+
throw convertSocketError(err);
|
|
253
|
+
}
|
|
254
|
+
return ipSocketAddress(family.toLowerCase(), address, port);
|
|
255
|
+
}
|
|
99
256
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
address: localAddress,
|
|
104
|
-
port: localPort,
|
|
105
|
-
},
|
|
106
|
-
() => {
|
|
107
|
-
openedSockets.set(id, socket);
|
|
108
|
-
resolve(0);
|
|
109
|
-
}
|
|
110
|
-
);
|
|
257
|
+
export function socketUdpStream(id, remoteAddress) {
|
|
258
|
+
const socket = udpSockets.get(id);
|
|
259
|
+
const { udpSocket } = socket;
|
|
111
260
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
261
|
+
if (
|
|
262
|
+
socket.state !== SOCKET_STATE_BOUND &&
|
|
263
|
+
socket.state !== SOCKET_STATE_CONNECTION
|
|
264
|
+
)
|
|
265
|
+
throw "invalid-state";
|
|
115
266
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
rinfo._address =
|
|
119
|
-
remoteSocket[symbolSocketUdpIpUnspecified].localAddress;
|
|
120
|
-
}
|
|
267
|
+
if (socket.state === SOCKET_STATE_INIT && !remoteAddress)
|
|
268
|
+
throw "invalid-state";
|
|
121
269
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
270
|
+
if (
|
|
271
|
+
remoteAddress &&
|
|
272
|
+
(remoteAddress.val.port === 0 ||
|
|
273
|
+
isWildcardAddress(remoteAddress) ||
|
|
274
|
+
(remoteAddress.tag === "ipv6" && isIPv4MappedAddress(remoteAddress)))
|
|
275
|
+
)
|
|
276
|
+
throw "invalid-argument";
|
|
127
277
|
|
|
128
|
-
|
|
129
|
-
|
|
278
|
+
if (socket.state === SOCKET_STATE_CONNECTION) {
|
|
279
|
+
socketDatagramStreamClear(socket.incomingDatagramStream);
|
|
280
|
+
socketDatagramStreamClear(socket.outgoingDatagramStream);
|
|
281
|
+
try {
|
|
282
|
+
udpSocket.disconnect();
|
|
283
|
+
} catch (e) {
|
|
284
|
+
throw convertSocketErrorCode(e);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
130
287
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
288
|
+
if (remoteAddress) {
|
|
289
|
+
const serializedRemoteAddress = serializeIpAddress(remoteAddress);
|
|
290
|
+
socket.remoteAddress = serializedRemoteAddress;
|
|
291
|
+
socket.remotePort = remoteAddress.val.port;
|
|
292
|
+
return new Promise((resolve, reject) => {
|
|
293
|
+
function connectOk() {
|
|
294
|
+
if (socket.state === SOCKET_STATE_INIT) {
|
|
295
|
+
socket.udpSocket.setTTL(socket.unicastHopLimit);
|
|
296
|
+
socket.udpSocket.setRecvBufferSize(socket.sendBufferSize);
|
|
297
|
+
socket.udpSocket.setSendBufferSize(socket.receiveBufferSize);
|
|
298
|
+
}
|
|
299
|
+
udpSocket.off("error", connectErr);
|
|
300
|
+
socket.state = SOCKET_STATE_CONNECTION;
|
|
301
|
+
resolve([
|
|
302
|
+
(socket.incomingDatagramStream = createIncomingDatagramStream(socket))
|
|
303
|
+
.id,
|
|
304
|
+
(socket.outgoingDatagramStream = createOutgoingDatagramStream(socket))
|
|
305
|
+
.id,
|
|
306
|
+
]);
|
|
307
|
+
}
|
|
308
|
+
function connectErr(err) {
|
|
309
|
+
udpSocket.off("connect", connectOk);
|
|
310
|
+
reject(convertSocketError(err));
|
|
311
|
+
}
|
|
312
|
+
udpSocket.once("connect", connectOk);
|
|
313
|
+
udpSocket.once("error", connectErr);
|
|
314
|
+
udpSocket.connect(remoteAddress.val.port, serializedRemoteAddress);
|
|
134
315
|
});
|
|
135
|
-
}
|
|
316
|
+
} else {
|
|
317
|
+
socket.state = SOCKET_STATE_BOUND;
|
|
318
|
+
socket.remoteAddress = null;
|
|
319
|
+
socket.remotePort = null;
|
|
320
|
+
return [
|
|
321
|
+
(socket.incomingDatagramStream = createIncomingDatagramStream(socket)).id,
|
|
322
|
+
(socket.outgoingDatagramStream = createOutgoingDatagramStream(socket)).id,
|
|
323
|
+
];
|
|
324
|
+
}
|
|
136
325
|
}
|
|
137
326
|
|
|
138
|
-
export function
|
|
139
|
-
const socket =
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
327
|
+
export function socketUdpSetReceiveBufferSize(id, bufferSize) {
|
|
328
|
+
const socket = udpSockets.get(id);
|
|
329
|
+
bufferSize = Number(bufferSize);
|
|
330
|
+
if (
|
|
331
|
+
socket.state !== SOCKET_STATE_INIT &&
|
|
332
|
+
socket.state !== SOCKET_STATE_BIND
|
|
333
|
+
) {
|
|
334
|
+
try {
|
|
335
|
+
socket.udpSocket.setRecvBufferSize(bufferSize);
|
|
336
|
+
} catch (err) {
|
|
337
|
+
throw convertSocketError(err);
|
|
338
|
+
}
|
|
144
339
|
}
|
|
340
|
+
socket.receiveBufferSize = bufferSize;
|
|
145
341
|
}
|
|
146
342
|
|
|
147
|
-
export function
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
// except the source socket
|
|
159
|
-
if (remotePort === undefined || remoteHost === undefined) {
|
|
160
|
-
getBoundSockets(id).forEach((adr) => {
|
|
161
|
-
socket.send(data, adr.port, adr.address, _callback);
|
|
162
|
-
});
|
|
163
|
-
} else {
|
|
164
|
-
socket.send(data, remotePort, remoteHost, _callback);
|
|
343
|
+
export function socketUdpSetSendBufferSize(id, bufferSize) {
|
|
344
|
+
const socket = udpSockets.get(id);
|
|
345
|
+
bufferSize = Number(bufferSize);
|
|
346
|
+
if (
|
|
347
|
+
socket.state !== SOCKET_STATE_INIT &&
|
|
348
|
+
socket.state !== SOCKET_STATE_BIND
|
|
349
|
+
) {
|
|
350
|
+
try {
|
|
351
|
+
socket.udpSocket.setSendBufferSize(bufferSize);
|
|
352
|
+
} catch (err) {
|
|
353
|
+
throw convertSocketError(err);
|
|
165
354
|
}
|
|
355
|
+
}
|
|
356
|
+
socket.sendBufferSize = bufferSize;
|
|
357
|
+
}
|
|
166
358
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
359
|
+
export function socketUdpSetUnicastHopLimit(id, hopLimit) {
|
|
360
|
+
const socket = udpSockets.get(id);
|
|
361
|
+
if (
|
|
362
|
+
socket.state !== SOCKET_STATE_INIT &&
|
|
363
|
+
socket.state !== SOCKET_STATE_BIND
|
|
364
|
+
) {
|
|
365
|
+
try {
|
|
366
|
+
socket.udpSocket.setTTL(hopLimit);
|
|
367
|
+
} catch (err) {
|
|
368
|
+
throw convertSocketError(err);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
socket.unicastHopLimit = hopLimit;
|
|
171
372
|
}
|
|
172
373
|
|
|
173
|
-
export function
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
374
|
+
export async function socketUdpGetReceiveBufferSize(id) {
|
|
375
|
+
const socket = udpSockets.get(id);
|
|
376
|
+
if (socket.receiveBufferSize) return BigInt(socket.receiveBufferSize);
|
|
377
|
+
if (
|
|
378
|
+
socket.state !== SOCKET_STATE_INIT &&
|
|
379
|
+
socket.state !== SOCKET_STATE_BIND
|
|
380
|
+
) {
|
|
381
|
+
try {
|
|
382
|
+
return BigInt(
|
|
383
|
+
(socket.receiveBufferSize = socket.udpSocket.getRecvBufferSize())
|
|
384
|
+
);
|
|
385
|
+
} catch (err) {
|
|
386
|
+
throw convertSocketError(err);
|
|
387
|
+
}
|
|
388
|
+
} else {
|
|
389
|
+
return BigInt(
|
|
390
|
+
(socket.receiveBufferSize = await getDefaultReceiveBufferSize())
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
177
394
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
395
|
+
export async function socketUdpGetSendBufferSize(id) {
|
|
396
|
+
const socket = udpSockets.get(id);
|
|
397
|
+
if (socket.sendBufferSize) return BigInt(socket.sendBufferSize);
|
|
398
|
+
if (
|
|
399
|
+
socket.state !== SOCKET_STATE_INIT &&
|
|
400
|
+
socket.state !== SOCKET_STATE_BIND
|
|
401
|
+
) {
|
|
402
|
+
try {
|
|
403
|
+
return BigInt(
|
|
404
|
+
(socket.sendBufferSize = socket.udpSocket.getSendBufferSize())
|
|
405
|
+
);
|
|
406
|
+
} catch (err) {
|
|
407
|
+
throw convertSocketError(err);
|
|
408
|
+
}
|
|
409
|
+
} else {
|
|
410
|
+
return BigInt((socket.sendBufferSize = await getDefaultSendBufferSize()));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
184
413
|
|
|
185
|
-
|
|
186
|
-
|
|
414
|
+
export function socketUdpGetUnicastHopLimit(id) {
|
|
415
|
+
const { unicastHopLimit } = udpSockets.get(id);
|
|
416
|
+
return unicastHopLimit;
|
|
187
417
|
}
|
|
188
418
|
|
|
189
|
-
export function
|
|
190
|
-
const
|
|
191
|
-
const { remoteAddress, remotePort } = payload;
|
|
419
|
+
export function socketUdpDispose(id) {
|
|
420
|
+
const { udpSocket } = udpSockets.get(id);
|
|
192
421
|
return new Promise((resolve) => {
|
|
193
|
-
|
|
194
|
-
|
|
422
|
+
udpSocket.close(() => {
|
|
423
|
+
udpSockets.delete(id);
|
|
195
424
|
resolve(0);
|
|
196
425
|
});
|
|
197
|
-
socket.once("error", (err) => {
|
|
198
|
-
resolve(err.errno);
|
|
199
|
-
});
|
|
200
426
|
});
|
|
201
427
|
}
|
|
202
428
|
|
|
203
|
-
export function
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
429
|
+
export function socketIncomingDatagramStreamReceive(id, maxResults) {
|
|
430
|
+
const datagramStream = datagramStreams.get(id);
|
|
431
|
+
if (!datagramStream.active)
|
|
432
|
+
throw new Error(
|
|
433
|
+
"wasi-io trap: attempt to receive on inactive incoming datagram stream"
|
|
434
|
+
);
|
|
435
|
+
if (maxResults === 0n || datagramStream.queue.length === 0) return [];
|
|
436
|
+
if (datagramStream.error) throw convertSocketError(datagramStream.error);
|
|
437
|
+
return datagramStream.queue.splice(0, Number(maxResults));
|
|
209
438
|
}
|
|
210
439
|
|
|
211
|
-
export function
|
|
212
|
-
const socket =
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
440
|
+
export async function socketOutgoingDatagramStreamSend(id, datagrams) {
|
|
441
|
+
const { active, socket } = datagramStreams.get(id);
|
|
442
|
+
if (!active)
|
|
443
|
+
throw new Error(
|
|
444
|
+
"wasi-io trap: writing to inactive outgoing datagram stream"
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
const { udpSocket } = socket;
|
|
448
|
+
let sendQueue = [],
|
|
449
|
+
sendQueueAddress,
|
|
450
|
+
sendQueuePort;
|
|
451
|
+
let datagramsSent = 0;
|
|
452
|
+
for (const { data, remoteAddress } of datagrams) {
|
|
453
|
+
const address = remoteAddress
|
|
454
|
+
? serializeIpAddress(remoteAddress)
|
|
455
|
+
: socket.remoteAddress;
|
|
456
|
+
const port = remoteAddress?.val.port ?? socket.remotePort;
|
|
457
|
+
let sendLastBatch = !UDP_BATCH_SENDS;
|
|
458
|
+
if (sendQueue.length > 0) {
|
|
459
|
+
if (sendQueueAddress === address && sendQueuePort === port) {
|
|
460
|
+
sendQueue.push(data);
|
|
461
|
+
} else {
|
|
462
|
+
sendLastBatch = true;
|
|
463
|
+
}
|
|
464
|
+
} else {
|
|
465
|
+
sendQueueAddress = address;
|
|
466
|
+
sendQueuePort = port;
|
|
467
|
+
sendQueue.push(data);
|
|
468
|
+
}
|
|
469
|
+
if (sendLastBatch) {
|
|
470
|
+
const err = await doSendBatch();
|
|
471
|
+
if (err) return BigInt(datagramsSent);
|
|
472
|
+
if (UDP_BATCH_SENDS) {
|
|
473
|
+
sendQueue = [data];
|
|
474
|
+
sendQueuePort = port;
|
|
475
|
+
sendQueueAddress = address;
|
|
476
|
+
} else {
|
|
477
|
+
sendQueue = [];
|
|
478
|
+
sendQueuePort = port;
|
|
479
|
+
sendQueueAddress = address;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (sendQueue.length) {
|
|
484
|
+
const err = await doSendBatch();
|
|
485
|
+
if (err) return BigInt(datagramsSent);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (datagramsSent !== datagrams.length)
|
|
489
|
+
throw new Error("wasi-io trap: expected to have sent all the datagrams");
|
|
490
|
+
return BigInt(datagramsSent);
|
|
491
|
+
|
|
492
|
+
function doSendBatch() {
|
|
493
|
+
return new Promise((resolve, reject) => {
|
|
494
|
+
if (socket.remoteAddress) {
|
|
495
|
+
if (sendQueueAddress !== socket.remoteAddress || sendQueuePort !== socket.remotePort)
|
|
496
|
+
return void reject("invalid-argument");
|
|
497
|
+
udpSocket.send(sendQueue, handler);
|
|
498
|
+
} else {
|
|
499
|
+
if (!sendQueueAddress)
|
|
500
|
+
return void reject("invalid-argument");
|
|
501
|
+
udpSocket.send(sendQueue, sendQueuePort, sendQueueAddress, handler);
|
|
502
|
+
}
|
|
503
|
+
function handler(err, _sentBytes) {
|
|
504
|
+
if (err) {
|
|
505
|
+
// TODO: update datagramsSent properly on error for multiple sends
|
|
506
|
+
// to enable send batching. Perhaps a Node.js PR could
|
|
507
|
+
// still set the second sendBytes arg?
|
|
508
|
+
if (datagramsSent > 0) resolve(datagramsSent);
|
|
509
|
+
else reject(convertSocketError(err));
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
datagramsSent += sendQueue.length;
|
|
513
|
+
resolve(false);
|
|
514
|
+
}
|
|
217
515
|
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function checkSend(socket) {
|
|
520
|
+
try {
|
|
521
|
+
return Math.floor(
|
|
522
|
+
(socket.udpSocket.getSendBufferSize() -
|
|
523
|
+
socket.udpSocket.getSendQueueSize()) /
|
|
524
|
+
1500
|
|
525
|
+
);
|
|
526
|
+
} catch (err) {
|
|
527
|
+
throw convertSocketError(err);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function pollSend(socket) {
|
|
532
|
+
socket.pollState.ready = false;
|
|
533
|
+
// The only way we have of dealing with getting a backpressure
|
|
534
|
+
// ready signal in Node.js is to just poll on the queue reducing.
|
|
535
|
+
// Ideally this should implement backoff on the poll interval,
|
|
536
|
+
// but that work should be done alongside careful benchmarking
|
|
537
|
+
// in due course.
|
|
538
|
+
setTimeout(() => {
|
|
539
|
+
const remaining = checkSend(socket);
|
|
540
|
+
if (remaining > 0) {
|
|
541
|
+
pollStateReady(socket.pollState);
|
|
542
|
+
} else {
|
|
543
|
+
pollSend(socket);
|
|
544
|
+
}
|
|
218
545
|
});
|
|
219
546
|
}
|
|
547
|
+
|
|
548
|
+
export function socketOutgoingDatagramStreamCheckSend(id) {
|
|
549
|
+
const { active, socket } = datagramStreams.get(id);
|
|
550
|
+
if (!active)
|
|
551
|
+
throw new Error(
|
|
552
|
+
"wasi-io trap: check send on inactive outgoing datagram stream"
|
|
553
|
+
);
|
|
554
|
+
const remaining = checkSend(socket);
|
|
555
|
+
if (remaining <= 0) pollSend(socket);
|
|
556
|
+
return BigInt(remaining);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function socketDatagramStreamClear(datagramStream) {
|
|
560
|
+
datagramStream.active = false;
|
|
561
|
+
if (datagramStream.cleanup) {
|
|
562
|
+
datagramStream.cleanup();
|
|
563
|
+
datagramStream.cleanup = null;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export function socketDatagramStreamDispose(id) {
|
|
568
|
+
const datagramStream = datagramStreams.get(id);
|
|
569
|
+
datagramStream.active = false;
|
|
570
|
+
if (datagramStream.cleanup) {
|
|
571
|
+
datagramStream.cleanup();
|
|
572
|
+
datagramStream.cleanup = null;
|
|
573
|
+
}
|
|
574
|
+
verifyPollsDroppedForDrop(datagramStream.pollState, "datagram stream");
|
|
575
|
+
datagramStreams.delete(id);
|
|
576
|
+
}
|