@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.
@@ -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
+ }