@bytecodealliance/preview2-shim 0.14.1 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/lib/browser/filesystem.js +6 -5
- 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
|
@@ -0,0 +1,131 @@
|
|
|
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");
|
|
8
|
+
|
|
9
|
+
/** @type {Map<number, NodeJS.Socket>} */
|
|
10
|
+
export const openedSockets = new Map();
|
|
11
|
+
|
|
12
|
+
let socketCnt = 0;
|
|
13
|
+
|
|
14
|
+
export function getSocketOrThrow(socketId) {
|
|
15
|
+
const socket = openedSockets.get(socketId);
|
|
16
|
+
if (!socket) throw "invalid-socket";
|
|
17
|
+
return socket;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//-----------------------------------------------------
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {IpAddressFamily} addressFamily
|
|
24
|
+
* @returns {NodeJS.Socket}
|
|
25
|
+
*/
|
|
26
|
+
export function createTcpSocket() {
|
|
27
|
+
const socket = new TCP(TCPConstants.SOCKET | TCPConstants.SERVER);
|
|
28
|
+
openedSockets.set(++socketCnt, socket);
|
|
29
|
+
return Promise.resolve(socketCnt);
|
|
30
|
+
}
|
|
31
|
+
|
|
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;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return socket[bind](localAddress, localPort, flags);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function socketTcpConnect(id, payload) {
|
|
50
|
+
const socket = getSocketOrThrow(id);
|
|
51
|
+
const { remoteAddress, remotePort, localAddress, localPort, family } =
|
|
52
|
+
payload;
|
|
53
|
+
|
|
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
|
+
}
|
|
69
|
+
|
|
70
|
+
socket.onread = (_buffer) => {
|
|
71
|
+
// TODO: handle data received from the server
|
|
72
|
+
};
|
|
73
|
+
socket.readStart();
|
|
74
|
+
|
|
75
|
+
const err = socket[connect](connectReq, remoteAddress, remotePort);
|
|
76
|
+
resolve(err);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function socketTcpListen(id, payload) {
|
|
81
|
+
const socket = getSocketOrThrow(id);
|
|
82
|
+
const { backlogSize } = payload;
|
|
83
|
+
return socket.listen(backlogSize);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function socketTcpGetLocalAddress(id) {
|
|
87
|
+
const socket = getSocketOrThrow(id);
|
|
88
|
+
const out = {};
|
|
89
|
+
socket.getsockname(out);
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function socketTcpGetRemoteAddress(id) {
|
|
94
|
+
const socket = getSocketOrThrow(id);
|
|
95
|
+
const out = {};
|
|
96
|
+
socket.getpeername(out);
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
|
|
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
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function socketTcpSetKeepAlive(id, payload) {
|
|
121
|
+
const socket = getSocketOrThrow(id);
|
|
122
|
+
const { enable } = payload;
|
|
123
|
+
|
|
124
|
+
return socket.setKeepAlive(enable);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function socketTcpDispose(id) {
|
|
128
|
+
const socket = getSocketOrThrow(id);
|
|
129
|
+
socket.close();
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @typedef {import("../../types/interfaces/wasi-sockets-network").IpAddressFamily} IpAddressFamily
|
|
3
|
+
*/
|
|
4
|
+
import { createSocket } from "node:dgram";
|
|
5
|
+
|
|
6
|
+
const symbolSocketUdpIpUnspecified =
|
|
7
|
+
Symbol.symbolSocketUdpIpUnspecified ??
|
|
8
|
+
Symbol.for("symbolSocketUdpIpUnspecified");
|
|
9
|
+
|
|
10
|
+
/** @type {Map<number, NodeJS.Socket>} */
|
|
11
|
+
export const openedSockets = new Map();
|
|
12
|
+
|
|
13
|
+
/** @type {Map<number, Map<string, { data: Buffer, rinfo: { address: string, family: string, port: number, size: number } }>>} */
|
|
14
|
+
const queuedReceivedSocketDatagrams = new Map();
|
|
15
|
+
|
|
16
|
+
let socketCnt = 0;
|
|
17
|
+
|
|
18
|
+
export function getSocketOrThrow(socketId) {
|
|
19
|
+
const socket = openedSockets.get(socketId);
|
|
20
|
+
if (!socket) throw "invalid-state";
|
|
21
|
+
return socket;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function getSocketByPort(port) {
|
|
25
|
+
return Array.from(openedSockets.values()).find(
|
|
26
|
+
(socket) => socket.address().port === port
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getBoundSockets(socketId) {
|
|
31
|
+
return Array.from(openedSockets.entries())
|
|
32
|
+
.filter(([id, _socket]) => id !== socketId) // exclude source socket
|
|
33
|
+
.map(([_id, socket]) => socket.address());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function dequeueReceivedSocketDatagram(socketInfo, maxResults) {
|
|
37
|
+
const key = `PORT:${socketInfo.port}`;
|
|
38
|
+
const dgrams = queuedReceivedSocketDatagrams
|
|
39
|
+
.get(key)
|
|
40
|
+
.splice(0, Number(maxResults));
|
|
41
|
+
return dgrams;
|
|
42
|
+
}
|
|
43
|
+
export function enqueueReceivedSocketDatagram(socketInfo, { data, rinfo }) {
|
|
44
|
+
const key = `PORT:${socketInfo.port}`;
|
|
45
|
+
const chunk = {
|
|
46
|
+
data,
|
|
47
|
+
rinfo, // sender/remote socket info (source)
|
|
48
|
+
socketInfo, // receiver socket info (targeted socket)
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// create new queue if not exists
|
|
52
|
+
if (!queuedReceivedSocketDatagrams.has(key)) {
|
|
53
|
+
queuedReceivedSocketDatagrams.set(key, []);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// append to queue
|
|
57
|
+
const queue = queuedReceivedSocketDatagrams.get(key);
|
|
58
|
+
queue.push(chunk);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//-----------------------------------------------------
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {IpAddressFamily} addressFamily
|
|
65
|
+
* @returns {NodeJS.Socket}
|
|
66
|
+
*/
|
|
67
|
+
export function createUdpSocket(addressFamily, reuseAddr) {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const type = addressFamily === "ipv6" ? "udp6" : "udp4";
|
|
70
|
+
try {
|
|
71
|
+
const socket = createSocket({
|
|
72
|
+
type,
|
|
73
|
+
reuseAddr,
|
|
74
|
+
});
|
|
75
|
+
openedSockets.set(++socketCnt, socket);
|
|
76
|
+
resolve({
|
|
77
|
+
id: socketCnt,
|
|
78
|
+
socket,
|
|
79
|
+
});
|
|
80
|
+
} catch (e) {
|
|
81
|
+
reject(e);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function socketUdpBind(id, payload) {
|
|
87
|
+
const { localAddress, localPort } = payload;
|
|
88
|
+
const socket = getSocketOrThrow(id);
|
|
89
|
+
|
|
90
|
+
// Note: even if the client has bound to IPV4_UNSPECIFIED/IPV6_UNSPECIFIED (0.0.0.0 // ::),
|
|
91
|
+
// rinfo.address is resolved to IPV4_LOOPBACK/IPV6_LOOPBACK.
|
|
92
|
+
// We need to cache the original bound IP type and fix rinfo.address when receiving datagrams (see below)
|
|
93
|
+
// See https://github.com/WebAssembly/wasi-sockets/issues/86
|
|
94
|
+
socket[symbolSocketUdpIpUnspecified] = {
|
|
95
|
+
isUnspecified:
|
|
96
|
+
localAddress === "0.0.0.0" || localAddress === "0:0:0:0:0:0:0:0",
|
|
97
|
+
localAddress,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return new Promise((resolve) => {
|
|
101
|
+
socket.bind(
|
|
102
|
+
{
|
|
103
|
+
address: localAddress,
|
|
104
|
+
port: localPort,
|
|
105
|
+
},
|
|
106
|
+
() => {
|
|
107
|
+
openedSockets.set(id, socket);
|
|
108
|
+
resolve(0);
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
socket.on("message", (data, rinfo) => {
|
|
113
|
+
const remoteSocket = getSocketByPort(rinfo.port);
|
|
114
|
+
let { address, port } = socket.address();
|
|
115
|
+
|
|
116
|
+
if (remoteSocket[symbolSocketUdpIpUnspecified].isUnspecified) {
|
|
117
|
+
// cache original bound address
|
|
118
|
+
rinfo._address =
|
|
119
|
+
remoteSocket[symbolSocketUdpIpUnspecified].localAddress;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const receiverSocket = {
|
|
123
|
+
address,
|
|
124
|
+
port,
|
|
125
|
+
id,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
enqueueReceivedSocketDatagram(receiverSocket, { data, rinfo });
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// catch all errors
|
|
132
|
+
socket.once("error", (err) => {
|
|
133
|
+
resolve(err.errno);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function socketUdpCheckSend(id) {
|
|
139
|
+
const socket = getSocketOrThrow(id);
|
|
140
|
+
try {
|
|
141
|
+
return socket.getSendBufferSize() - socket.getSendQueueSize();
|
|
142
|
+
} catch (err) {
|
|
143
|
+
return err.errno;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function socketUdpSend(id, payload) {
|
|
148
|
+
let { remoteHost, remotePort, data } = payload;
|
|
149
|
+
const socket = getSocketOrThrow(id);
|
|
150
|
+
|
|
151
|
+
return new Promise((resolve) => {
|
|
152
|
+
const _callback = (err, _byteLength) => {
|
|
153
|
+
if (err) return resolve(err.errno);
|
|
154
|
+
resolve(0); // success
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Note: when remoteHost/remotePort is None, we broadcast to all bound sockets
|
|
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);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
socket.once("error", (err) => {
|
|
168
|
+
resolve(err.errno);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function SocketUdpReceive(id, payload) {
|
|
174
|
+
const { maxResults } = payload;
|
|
175
|
+
const socket = getSocketOrThrow(id);
|
|
176
|
+
const { address, port } = socket.address();
|
|
177
|
+
|
|
178
|
+
// set target socket info
|
|
179
|
+
// we use this to filter out datagrams that are were sent to this socket
|
|
180
|
+
const targetSocket = {
|
|
181
|
+
address,
|
|
182
|
+
port,
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const dgrams = dequeueReceivedSocketDatagram(targetSocket, maxResults);
|
|
186
|
+
return Promise.resolve(dgrams);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function socketUdpConnect(id, payload) {
|
|
190
|
+
const socket = getSocketOrThrow(id);
|
|
191
|
+
const { remoteAddress, remotePort } = payload;
|
|
192
|
+
return new Promise((resolve) => {
|
|
193
|
+
socket.connect(remotePort, remoteAddress, () => {
|
|
194
|
+
openedSockets.set(id, socket);
|
|
195
|
+
resolve(0);
|
|
196
|
+
});
|
|
197
|
+
socket.once("error", (err) => {
|
|
198
|
+
resolve(err.errno);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function socketUdpDisconnect(id) {
|
|
204
|
+
const socket = getSocketOrThrow(id);
|
|
205
|
+
return new Promise((resolve) => {
|
|
206
|
+
socket.disconnect();
|
|
207
|
+
resolve(0);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function socketUdpDispose(id) {
|
|
212
|
+
const socket = getSocketOrThrow(id);
|
|
213
|
+
return new Promise((resolve) => {
|
|
214
|
+
socket.close(() => {
|
|
215
|
+
openedSockets.delete(id);
|
|
216
|
+
resolve(0);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
}
|