@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.
@@ -1,131 +1,285 @@
1
- // See: https://github.com/nodejs/node/blob/main/src/tcp_wrap.cc
2
- const {
3
- TCP,
4
- constants: TCPConstants,
5
- TCPConnectWrap,
6
- } = process.binding("tcp_wrap");
7
- const { ShutdownWrap } = process.binding("stream_wrap");
1
+ import {
2
+ createFuture,
3
+ createReadableStream,
4
+ createReadableStreamPollState,
5
+ createWritableStream,
6
+ futureDispose,
7
+ futureTakeValue,
8
+ pollStateReady,
9
+ verifyPollsDroppedForDrop,
10
+ } from "./worker-thread.js";
11
+ const { TCP, constants: TCPConstants } = process.binding("tcp_wrap");
12
+ import {
13
+ convertSocketError,
14
+ convertSocketErrorCode,
15
+ ipSocketAddress,
16
+ isIPv4MappedAddress,
17
+ isMulticastIpAddress,
18
+ isUnicastIpAddress,
19
+ isWildcardAddress,
20
+ noLookup,
21
+ serializeIpAddress,
22
+ SOCKET_STATE_BIND,
23
+ SOCKET_STATE_BOUND,
24
+ SOCKET_STATE_CLOSED,
25
+ SOCKET_STATE_CONNECT,
26
+ SOCKET_STATE_CONNECTION,
27
+ SOCKET_STATE_INIT,
28
+ SOCKET_STATE_LISTEN,
29
+ SOCKET_STATE_LISTENER,
30
+ } from "./worker-sockets.js";
31
+ import { Socket, Server } from "node:net";
8
32
 
9
- /** @type {Map<number, NodeJS.Socket>} */
10
- export const openedSockets = new Map();
11
-
12
- let socketCnt = 0;
33
+ /**
34
+ * @typedef {import("../../types/interfaces/wasi-sockets-network.js").IpSocketAddress} IpSocketAddress
35
+ * @typedef {import("../../../types/interfaces/wasi-sockets-tcp.js").IpAddressFamily} IpAddressFamily
36
+ * @typedef {import("node:net").Socket} TcpSocket
37
+ *
38
+ * @typedef {{
39
+ * tcpSocket: number,
40
+ * err: Error | null,
41
+ * pollState: PollState,
42
+ * }} PendingAccept
43
+ *
44
+ * @typedef {{
45
+ * state: number,
46
+ * future: number | null,
47
+ * socket: TcpSocket | null,
48
+ * listenBacklogSize: number,
49
+ * handle: TCP,
50
+ * pendingAccepts: PendingAccept[],
51
+ * pollState: PollState,
52
+ * }} TcpSocketRecord
53
+ */
13
54
 
14
- export function getSocketOrThrow(socketId) {
15
- const socket = openedSockets.get(socketId);
16
- if (!socket) throw "invalid-socket";
17
- return socket;
18
- }
55
+ /**
56
+ * @type {Map<number, TcpSocketRecord>}
57
+ */
58
+ export const tcpSockets = new Map();
19
59
 
20
- //-----------------------------------------------------
60
+ let tcpSocketCnt = 0;
21
61
 
22
62
  /**
23
63
  * @param {IpAddressFamily} addressFamily
24
- * @returns {NodeJS.Socket}
25
64
  */
26
65
  export function createTcpSocket() {
27
- const socket = new TCP(TCPConstants.SOCKET | TCPConstants.SERVER);
28
- openedSockets.set(++socketCnt, socket);
29
- return Promise.resolve(socketCnt);
66
+ const handle = new TCP(TCPConstants.SOCKET);
67
+ tcpSockets.set(++tcpSocketCnt, {
68
+ state: SOCKET_STATE_INIT,
69
+ future: null,
70
+ listenBacklogSize: 128,
71
+ handle,
72
+ pendingAccepts: [],
73
+ pollState: { ready: true, listener: null, polls: [], parentStream: null },
74
+ });
75
+ return tcpSocketCnt;
30
76
  }
31
77
 
32
- export function socketTcpBind(id, payload) {
33
- const { localAddress, localPort, family, isIpV6Only } = payload;
34
- const socket = getSocketOrThrow(id);
35
-
36
- let bind = "bind"; // ipv4
37
- if (family.toLocaleLowerCase() === "ipv6") {
38
- bind = "bind6"; // ipv6
39
- }
40
-
41
- let flags = 0;
42
- if (isIpV6Only) {
43
- flags |= TCPConstants.UV_TCP_IPV6ONLY;
78
+ export function socketTcpFinish(id, fromState, toState) {
79
+ const socket = tcpSockets.get(id);
80
+ if (socket.state !== fromState) throw "not-in-progress";
81
+ if (!socket.pollState.ready) throw "would-block";
82
+ const { tag, val } = futureTakeValue(socket.future).val;
83
+ futureDispose(socket.future, false);
84
+ socket.future = null;
85
+ if (tag === "err") {
86
+ socket.state = SOCKET_STATE_CLOSED;
87
+ throw val;
88
+ } else {
89
+ socket.state = toState;
90
+ // for the listener, we must immediately transition back to unresolved
91
+ if (toState === SOCKET_STATE_LISTENER) socket.pollState.ready = false;
92
+ return val;
44
93
  }
45
-
46
- return socket[bind](localAddress, localPort, flags);
47
94
  }
48
95
 
49
- export function socketTcpConnect(id, payload) {
50
- const socket = getSocketOrThrow(id);
51
- const { remoteAddress, remotePort, localAddress, localPort, family } =
52
- payload;
96
+ export function socketTcpBindStart(id, localAddress, family) {
97
+ const socket = tcpSockets.get(id);
98
+ if (socket.state !== SOCKET_STATE_INIT) throw "invalid-state";
99
+ if (
100
+ family !== localAddress.tag ||
101
+ !isUnicastIpAddress(localAddress) ||
102
+ isIPv4MappedAddress(localAddress)
103
+ )
104
+ throw "invalid-argument";
105
+ socket.state = SOCKET_STATE_BIND;
106
+ const { handle } = socket;
107
+ socket.future = createFuture(
108
+ (async () => {
109
+ const address = serializeIpAddress(localAddress);
110
+ const port = localAddress.val.port;
111
+ const code =
112
+ localAddress.tag === "ipv6"
113
+ ? handle.bind6(address, port, TCPConstants.UV_TCP_IPV6ONLY)
114
+ : handle.bind(address, port);
115
+ if (code !== 0) throw convertSocketErrorCode(-code);
116
+ // This is a Node.js / libuv quirk to force the bind error to be thrown
117
+ // (specifically address-in-use).
118
+ {
119
+ const out = {};
120
+ const code = handle.getsockname(out);
121
+ if (code !== 0) throw convertSocketErrorCode(-code);
122
+ }
123
+ })(),
124
+ socket.pollState
125
+ );
126
+ }
53
127
 
54
- return new Promise((resolve) => {
55
- const _onClientConnectComplete = (err) => {
56
- if (err) resolve(err);
57
- resolve(0);
58
- };
59
- const connectReq = new TCPConnectWrap();
60
- connectReq.oncomplete = _onClientConnectComplete;
61
- connectReq.address = remoteAddress;
62
- connectReq.port = remotePort;
63
- connectReq.localAddress = localAddress;
64
- connectReq.localPort = localPort;
65
- let connect = "connect"; // ipv4
66
- if (family.toLocaleLowerCase() === "ipv6") {
67
- connect = "connect6";
68
- }
128
+ export function socketTcpConnectStart(id, remoteAddress, family) {
129
+ const socket = tcpSockets.get(id);
130
+ if (socket.state !== SOCKET_STATE_INIT && socket.state !== SOCKET_STATE_BOUND)
131
+ throw "invalid-state";
132
+ if (
133
+ isWildcardAddress(remoteAddress) ||
134
+ family !== remoteAddress.tag ||
135
+ !isUnicastIpAddress(remoteAddress) ||
136
+ isMulticastIpAddress(remoteAddress) ||
137
+ remoteAddress.val.port === 0 ||
138
+ isIPv4MappedAddress(remoteAddress)
139
+ ) {
140
+ throw "invalid-argument";
141
+ }
142
+ socket.state = SOCKET_STATE_CONNECT;
143
+ socket.future = createFuture(
144
+ new Promise((resolve, reject) => {
145
+ const tcpSocket = (socket.tcpSocket = new Socket({
146
+ handle: socket.handle,
147
+ pauseOnCreate: true,
148
+ allowHalfOpen: true,
149
+ }));
150
+ function handleErr(err) {
151
+ tcpSocket.off("connect", handleConnect);
152
+ reject(convertSocketError(err));
153
+ }
154
+ function handleConnect() {
155
+ tcpSocket.off("error", handleErr);
156
+ resolve([
157
+ createReadableStream(tcpSocket),
158
+ createWritableStream(tcpSocket),
159
+ ]);
160
+ }
161
+ tcpSocket.once("connect", handleConnect);
162
+ tcpSocket.once("error", handleErr);
163
+ tcpSocket.connect({
164
+ port: remoteAddress.val.port,
165
+ host: serializeIpAddress(remoteAddress),
166
+ lookup: noLookup,
167
+ });
168
+ }),
169
+ socket.pollState
170
+ );
171
+ }
69
172
 
70
- socket.onread = (_buffer) => {
71
- // TODO: handle data received from the server
72
- };
73
- socket.readStart();
173
+ export function socketTcpListenStart(id) {
174
+ const socket = tcpSockets.get(id);
175
+ if (socket.state !== SOCKET_STATE_BOUND) throw "invalid-state";
176
+ const { handle } = socket;
177
+ socket.state = SOCKET_STATE_LISTEN;
178
+ socket.future = createFuture(
179
+ new Promise((resolve, reject) => {
180
+ const server = new Server({ pauseOnConnect: true, allowHalfOpen: true });
181
+ function handleErr(err) {
182
+ server.off("listening", handleListen);
183
+ reject(convertSocketError(err));
184
+ }
185
+ function handleListen() {
186
+ server.off("error", handleErr);
187
+ server.on("connection", (tcpSocket) => {
188
+ pollStateReady(socket.pollState);
189
+ const pollState = createReadableStreamPollState(tcpSocket);
190
+ socket.pendingAccepts.push({ tcpSocket, err: null, pollState });
191
+ });
192
+ server.on("error", (err) => {
193
+ pollStateReady(socket.pollState);
194
+ socket.pendingAccepts.push({ tcpSocket: null, err, pollState: null });
195
+ });
196
+ resolve();
197
+ }
198
+ server.once("listening", handleListen);
199
+ server.once("error", handleErr);
200
+ server.listen(handle, socket.listenBacklogSize);
201
+ }),
202
+ socket.pollState
203
+ );
204
+ }
74
205
 
75
- const err = socket[connect](connectReq, remoteAddress, remotePort);
76
- resolve(err);
206
+ export function socketTcpAccept(id) {
207
+ const socket = tcpSockets.get(id);
208
+ if (socket.state !== SOCKET_STATE_LISTENER) throw "invalid-state";
209
+ if (socket.pendingAccepts.length === 0) throw "would-block";
210
+ const accept = socket.pendingAccepts.shift();
211
+ if (accept.err) {
212
+ socket.state = SOCKET_STATE_CLOSED;
213
+ throw convertSocketError(accept.err);
214
+ }
215
+ if (socket.pendingAccepts.length === 0) socket.pollState.ready = false;
216
+ tcpSockets.set(++tcpSocketCnt, {
217
+ state: SOCKET_STATE_CONNECTION,
218
+ future: null,
219
+ listenBacklogSize: 128,
220
+ handle: accept.tcpSocket._handle,
221
+ pendingAccepts: [],
222
+ pollState: accept.pollState,
77
223
  });
224
+ return [
225
+ tcpSocketCnt,
226
+ createReadableStream(accept.tcpSocket, accept.pollState),
227
+ createWritableStream(accept.tcpSocket),
228
+ ];
78
229
  }
79
230
 
80
- export function socketTcpListen(id, payload) {
81
- const socket = getSocketOrThrow(id);
82
- const { backlogSize } = payload;
83
- return socket.listen(backlogSize);
231
+ export function socketTcpSetListenBacklogSize(id, backlogSize) {
232
+ const socket = tcpSockets.get(id);
233
+ if (
234
+ socket.state === SOCKET_STATE_LISTEN ||
235
+ socket.state === SOCKET_STATE_LISTENER
236
+ )
237
+ throw "not-supported";
238
+ if (
239
+ socket.state !== SOCKET_STATE_INIT &&
240
+ socket.state !== SOCKET_STATE_BIND &&
241
+ socket.state !== SOCKET_STATE_BOUND
242
+ )
243
+ throw "invalid-state";
244
+ socket.listenBacklogSize = Number(backlogSize);
84
245
  }
85
246
 
86
247
  export function socketTcpGetLocalAddress(id) {
87
- const socket = getSocketOrThrow(id);
248
+ const { handle } = tcpSockets.get(id);
88
249
  const out = {};
89
- socket.getsockname(out);
90
- return out;
250
+ const code = handle.getsockname(out);
251
+ if (code !== 0) throw convertSocketErrorCode(-code);
252
+ return ipSocketAddress(out.family.toLowerCase(), out.address, out.port);
91
253
  }
92
254
 
93
255
  export function socketTcpGetRemoteAddress(id) {
94
- const socket = getSocketOrThrow(id);
256
+ const { handle } = tcpSockets.get(id);
95
257
  const out = {};
96
- socket.getpeername(out);
97
- return out;
258
+ const code = handle.getpeername(out);
259
+ if (code !== 0) throw convertSocketErrorCode(-code);
260
+ return ipSocketAddress(out.family.toLowerCase(), out.address, out.port);
98
261
  }
99
262
 
100
- export function socketTcpShutdown(id, payload) {
101
- const socket = getSocketOrThrow(id);
102
-
103
- // eslint-disable-next-line no-unused-vars
104
- const { shutdownType } = payload;
105
-
106
- return new Promise((resolve) => {
107
- const req = new ShutdownWrap();
108
- req.oncomplete = () => {
109
- resolve(0);
110
- };
111
- req.handle = socket;
112
- req.callback = () => {
113
- resolve(0);
114
- };
115
- const err = socket.shutdown(req);
116
- resolve(err);
117
- });
263
+ export function socketTcpShutdown(id, shutdownType) {
264
+ const socket = tcpSockets.get(id);
265
+ if (socket.state !== SOCKET_STATE_CONNECTION) throw "invalid-state";
266
+ // Node.js only supports a write shutdown, which is triggered on end
267
+ if (shutdownType === "send" || shutdownType === "both")
268
+ socket.tcpSocket.end();
118
269
  }
119
270
 
120
- export function socketTcpSetKeepAlive(id, payload) {
121
- const socket = getSocketOrThrow(id);
122
- const { enable } = payload;
123
-
124
- return socket.setKeepAlive(enable);
271
+ export function socketTcpSetKeepAlive(id, { keepAlive, keepAliveIdleTime }) {
272
+ const { handle } = tcpSockets.get(id);
273
+ const code = handle.setKeepAlive(
274
+ keepAlive,
275
+ Number(keepAliveIdleTime / 1_000_000_000n)
276
+ );
277
+ if (code !== 0) throw convertSocketErrorCode(-code);
125
278
  }
126
279
 
127
280
  export function socketTcpDispose(id) {
128
- const socket = getSocketOrThrow(id);
129
- socket.close();
130
- return 0;
281
+ const socket = tcpSockets.get(id);
282
+ verifyPollsDroppedForDrop(socket.pollState, "tcp socket");
283
+ socket.handle.close();
284
+ tcpSockets.delete(id);
131
285
  }