@volter/browser-wali 0.1.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/LICENSE +21 -0
- package/README.md +20 -0
- package/dist/browser-wali-command.d.ts +22 -0
- package/dist/browser-wali-command.d.ts.map +1 -0
- package/dist/browser-wali-command.js +44 -0
- package/dist/browser-wali-command.js.map +1 -0
- package/dist/capabilities.d.ts +3 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +23 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/coherent-file-locks.d.ts +23 -0
- package/dist/coherent-file-locks.d.ts.map +1 -0
- package/dist/coherent-file-locks.js +134 -0
- package/dist/coherent-file-locks.js.map +1 -0
- package/dist/events.d.ts +30 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +289 -0
- package/dist/events.js.map +1 -0
- package/dist/filesystem-protocol.d.ts +48 -0
- package/dist/filesystem-protocol.d.ts.map +1 -0
- package/dist/filesystem-protocol.js +27 -0
- package/dist/filesystem-protocol.js.map +1 -0
- package/dist/filesystem-remote.d.ts +65 -0
- package/dist/filesystem-remote.d.ts.map +1 -0
- package/dist/filesystem-remote.js +271 -0
- package/dist/filesystem-remote.js.map +1 -0
- package/dist/filesystem-worker.d.ts +2 -0
- package/dist/filesystem-worker.d.ts.map +1 -0
- package/dist/filesystem-worker.js +269 -0
- package/dist/filesystem-worker.js.map +1 -0
- package/dist/filesystem.d.ts +115 -0
- package/dist/filesystem.d.ts.map +1 -0
- package/dist/filesystem.js +1179 -0
- package/dist/filesystem.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect.d.ts +7 -0
- package/dist/inspect.d.ts.map +1 -0
- package/dist/inspect.js +32 -0
- package/dist/inspect.js.map +1 -0
- package/dist/local-sockets.d.ts +13 -0
- package/dist/local-sockets.d.ts.map +1 -0
- package/dist/local-sockets.js +139 -0
- package/dist/local-sockets.js.map +1 -0
- package/dist/loopback-host.d.ts +14 -0
- package/dist/loopback-host.d.ts.map +1 -0
- package/dist/loopback-host.js +476 -0
- package/dist/loopback-host.js.map +1 -0
- package/dist/loopback-protocol.d.ts +41 -0
- package/dist/loopback-protocol.d.ts.map +1 -0
- package/dist/loopback-protocol.js +2 -0
- package/dist/loopback-protocol.js.map +1 -0
- package/dist/memory.d.ts +39 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +520 -0
- package/dist/memory.js.map +1 -0
- package/dist/module-layout.d.ts +9 -0
- package/dist/module-layout.d.ts.map +1 -0
- package/dist/module-layout.js +105 -0
- package/dist/module-layout.js.map +1 -0
- package/dist/network-protocol.d.ts +43 -0
- package/dist/network-protocol.d.ts.map +1 -0
- package/dist/network-protocol.js +26 -0
- package/dist/network-protocol.js.map +1 -0
- package/dist/network-worker.d.ts +2 -0
- package/dist/network-worker.d.ts.map +1 -0
- package/dist/network-worker.js +943 -0
- package/dist/network-worker.js.map +1 -0
- package/dist/network.d.ts +52 -0
- package/dist/network.d.ts.map +1 -0
- package/dist/network.js +427 -0
- package/dist/network.js.map +1 -0
- package/dist/process-adapter.d.ts +14 -0
- package/dist/process-adapter.d.ts.map +1 -0
- package/dist/process-adapter.js +45 -0
- package/dist/process-adapter.js.map +1 -0
- package/dist/process-bridge.d.ts +54 -0
- package/dist/process-bridge.d.ts.map +1 -0
- package/dist/process-bridge.js +150 -0
- package/dist/process-bridge.js.map +1 -0
- package/dist/program.d.ts +49 -0
- package/dist/program.d.ts.map +1 -0
- package/dist/program.js +204 -0
- package/dist/program.js.map +1 -0
- package/dist/runtime.d.ts +9 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +528 -0
- package/dist/runtime.js.map +1 -0
- package/dist/shared-pipes.d.ts +44 -0
- package/dist/shared-pipes.d.ts.map +1 -0
- package/dist/shared-pipes.js +306 -0
- package/dist/shared-pipes.js.map +1 -0
- package/dist/snapshot-filesystem.d.ts +31 -0
- package/dist/snapshot-filesystem.d.ts.map +1 -0
- package/dist/snapshot-filesystem.js +247 -0
- package/dist/snapshot-filesystem.js.map +1 -0
- package/dist/stdin.d.ts +36 -0
- package/dist/stdin.d.ts.map +1 -0
- package/dist/stdin.js +143 -0
- package/dist/stdin.js.map +1 -0
- package/dist/symlink.d.ts +8 -0
- package/dist/symlink.d.ts.map +1 -0
- package/dist/symlink.js +66 -0
- package/dist/symlink.js.map +1 -0
- package/dist/system.d.ts +41 -0
- package/dist/system.d.ts.map +1 -0
- package/dist/system.js +305 -0
- package/dist/system.js.map +1 -0
- package/dist/thread-coordinator-worker.d.ts +25 -0
- package/dist/thread-coordinator-worker.d.ts.map +1 -0
- package/dist/thread-coordinator-worker.js +49 -0
- package/dist/thread-coordinator-worker.js.map +1 -0
- package/dist/thread-worker.d.ts +2 -0
- package/dist/thread-worker.d.ts.map +1 -0
- package/dist/thread-worker.js +725 -0
- package/dist/thread-worker.js.map +1 -0
- package/dist/threads.d.ts +44 -0
- package/dist/threads.d.ts.map +1 -0
- package/dist/threads.js +154 -0
- package/dist/threads.js.map +1 -0
- package/dist/types.d.ts +112 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/websocket-peer.d.ts +38 -0
- package/dist/websocket-peer.d.ts.map +1 -0
- package/dist/websocket-peer.js +407 -0
- package/dist/websocket-peer.js.map +1 -0
- package/dist/worker-program-protocol.d.ts +71 -0
- package/dist/worker-program-protocol.d.ts.map +1 -0
- package/dist/worker-program-protocol.js +2 -0
- package/dist/worker-program-protocol.js.map +1 -0
- package/dist/worker-program-worker.d.ts +2 -0
- package/dist/worker-program-worker.d.ts.map +1 -0
- package/dist/worker-program-worker.js +109 -0
- package/dist/worker-program-worker.js.map +1 -0
- package/dist/worker-program.d.ts +58 -0
- package/dist/worker-program.d.ts.map +1 -0
- package/dist/worker-program.js +425 -0
- package/dist/worker-program.js.map +1 -0
- package/package.json +23 -0
|
@@ -0,0 +1,943 @@
|
|
|
1
|
+
import { NETWORK_FD_MIN, NetworkOperation, } from "./network-protocol.js";
|
|
2
|
+
import { pollWaliSharedPipeState } from "./shared-pipes.js";
|
|
3
|
+
import { SharedWaliStdinReader } from "./stdin.js";
|
|
4
|
+
const scope = globalThis;
|
|
5
|
+
const EPOLL_EVENT_BYTES = 16;
|
|
6
|
+
const EPOLL_DATA_OFFSET = 8;
|
|
7
|
+
const sockets = new Map();
|
|
8
|
+
const epolls = new Map();
|
|
9
|
+
const allowedHosts = new Set();
|
|
10
|
+
const hostByAddress = new Map();
|
|
11
|
+
const addressIdByHost = new Map();
|
|
12
|
+
let relayUrl = "/api/wali/tcp";
|
|
13
|
+
let nextFd = NETWORK_FD_MIN;
|
|
14
|
+
let sharedPipes;
|
|
15
|
+
let stdin;
|
|
16
|
+
let stdioAliasRegistry;
|
|
17
|
+
let loopbackPort;
|
|
18
|
+
let nextLoopbackRequestId = 1;
|
|
19
|
+
let nextOutboundConnectionId = -1;
|
|
20
|
+
const loopbackAcks = new Map();
|
|
21
|
+
const listenersByPort = new Map();
|
|
22
|
+
const loopbackConnections = new Map();
|
|
23
|
+
scope.onmessage = (event) => {
|
|
24
|
+
if (event.data.kind === "initialize") {
|
|
25
|
+
allowedHosts.clear();
|
|
26
|
+
for (const host of event.data.allowedHosts)
|
|
27
|
+
allowedHosts.add(host);
|
|
28
|
+
relayUrl = event.data.relayUrl;
|
|
29
|
+
sharedPipes = event.data.sharedPipes;
|
|
30
|
+
stdin = event.data.stdin ? new SharedWaliStdinReader(event.data.stdin) : undefined;
|
|
31
|
+
stdioAliasRegistry = event.data.stdioAliasRegistry
|
|
32
|
+
? new Int32Array(event.data.stdioAliasRegistry)
|
|
33
|
+
: undefined;
|
|
34
|
+
loopbackPort = event.data.loopbackPort;
|
|
35
|
+
if (loopbackPort) {
|
|
36
|
+
loopbackPort.onmessage = (message) => {
|
|
37
|
+
handleLoopbackMessage(message.data);
|
|
38
|
+
};
|
|
39
|
+
loopbackPort.start();
|
|
40
|
+
}
|
|
41
|
+
scope.postMessage({ kind: "ready" });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (event.data.kind === "attach")
|
|
45
|
+
attach(event.data);
|
|
46
|
+
};
|
|
47
|
+
function attach(channel) {
|
|
48
|
+
const control = new Int32Array(channel.control);
|
|
49
|
+
const payload = new Uint8Array(channel.payload);
|
|
50
|
+
channel.port.onmessage = (event) => { void dispatch(control, payload, event.data); };
|
|
51
|
+
channel.port.start();
|
|
52
|
+
}
|
|
53
|
+
async function dispatch(control, payload, requestId) {
|
|
54
|
+
if (Atomics.load(control, 0) !== requestId)
|
|
55
|
+
return;
|
|
56
|
+
let result = -38;
|
|
57
|
+
let output = new Uint8Array();
|
|
58
|
+
try {
|
|
59
|
+
const operation = Atomics.load(control, 1);
|
|
60
|
+
const inputLength = Atomics.load(control, 3);
|
|
61
|
+
const args = Array.from({ length: 10 }, (_, index) => Atomics.load(control, 5 + index));
|
|
62
|
+
const input = payload.slice(0, inputLength);
|
|
63
|
+
({ result, output } = await operate(operation, args, input));
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
result = -5;
|
|
67
|
+
}
|
|
68
|
+
if (output.byteLength > payload.byteLength) {
|
|
69
|
+
result = -90;
|
|
70
|
+
output = new Uint8Array();
|
|
71
|
+
}
|
|
72
|
+
// Claim this generation before touching the shared response buffers. A
|
|
73
|
+
// watchdog may have retired it and started a later request while operate()
|
|
74
|
+
// was pending; stale completions must never corrupt that request.
|
|
75
|
+
if (Atomics.compareExchange(control, 0, requestId, -requestId) !== requestId)
|
|
76
|
+
return;
|
|
77
|
+
if (output.byteLength > 0)
|
|
78
|
+
payload.set(output);
|
|
79
|
+
Atomics.store(control, 2, result);
|
|
80
|
+
Atomics.store(control, 4, output.byteLength);
|
|
81
|
+
Atomics.store(control, 0, requestId + 1);
|
|
82
|
+
Atomics.notify(control, 0);
|
|
83
|
+
}
|
|
84
|
+
async function operate(operation, args, input) {
|
|
85
|
+
if (operation === NetworkOperation.Socket)
|
|
86
|
+
return scalar(createSocket(args[0], args[1], args[2]));
|
|
87
|
+
if (operation === NetworkOperation.EpollCreate)
|
|
88
|
+
return scalar(createEpoll());
|
|
89
|
+
if (operation === NetworkOperation.EventFd)
|
|
90
|
+
return scalar(createEventFd(args[0], args[1]));
|
|
91
|
+
if (operation === NetworkOperation.TimerFdCreate)
|
|
92
|
+
return scalar(createTimerFd(args[0], args[1]));
|
|
93
|
+
if (operation === NetworkOperation.SocketPair)
|
|
94
|
+
return createSocketPair(args[0], args[1], args[2]);
|
|
95
|
+
if (operation === NetworkOperation.EpollControl)
|
|
96
|
+
return scalar(epollControl(args[0], args[1], args[2], input));
|
|
97
|
+
if (operation === NetworkOperation.EpollWait)
|
|
98
|
+
return epollWait(args[0], args[1], args[2]);
|
|
99
|
+
const fd = args[0];
|
|
100
|
+
if (operation === NetworkOperation.Close && epolls.has(fd)) {
|
|
101
|
+
epolls.delete(fd);
|
|
102
|
+
return scalar(0);
|
|
103
|
+
}
|
|
104
|
+
if (operation === NetworkOperation.Fcntl && epolls.has(fd)) {
|
|
105
|
+
if (args[1] === 0 || args[1] === 1030)
|
|
106
|
+
return scalar(duplicateEpoll(fd, args[2]));
|
|
107
|
+
return scalar(args[1] === 3 || args[1] === 1 || args[1] === 2 || args[1] === 4 ? 0 : -22);
|
|
108
|
+
}
|
|
109
|
+
const socket = sockets.get(fd);
|
|
110
|
+
if (!socket)
|
|
111
|
+
return scalar(-9);
|
|
112
|
+
if (operation === NetworkOperation.Connect)
|
|
113
|
+
return scalar(await connectSocket(socket, input));
|
|
114
|
+
if (operation === NetworkOperation.Bind)
|
|
115
|
+
return scalar(bindSocket(socket, input));
|
|
116
|
+
if (operation === NetworkOperation.Listen)
|
|
117
|
+
return scalar(await listenSocket(socket, args[1]));
|
|
118
|
+
if (operation === NetworkOperation.Accept)
|
|
119
|
+
return acceptSocket(socket, args[1]);
|
|
120
|
+
if (operation === NetworkOperation.Send)
|
|
121
|
+
return scalar(sendSocket(socket, input));
|
|
122
|
+
if (operation === NetworkOperation.Receive) {
|
|
123
|
+
const response = receiveSocket(socket, Math.max(0, args[1]));
|
|
124
|
+
if (response.result > 0 && (pollSocket(socket, 1) & 1) === 0)
|
|
125
|
+
rearmEpollReadable(fd);
|
|
126
|
+
return response;
|
|
127
|
+
}
|
|
128
|
+
if (operation === NetworkOperation.Poll) {
|
|
129
|
+
const result = pollSocket(socket, args[1]);
|
|
130
|
+
return scalar(result);
|
|
131
|
+
}
|
|
132
|
+
if (operation === NetworkOperation.TimerFdSetTime)
|
|
133
|
+
return setTimerFd(socket, args[1], input);
|
|
134
|
+
if (operation === NetworkOperation.Fcntl)
|
|
135
|
+
return scalar(fcntlSocket(fd, socket, args[1], args[2]));
|
|
136
|
+
if (operation === NetworkOperation.SocketName)
|
|
137
|
+
return { result: 0, output: socketName(socket) };
|
|
138
|
+
if (operation === NetworkOperation.Duplicate)
|
|
139
|
+
return scalar(duplicateSocket(socket));
|
|
140
|
+
if (operation === NetworkOperation.Shutdown) {
|
|
141
|
+
closeSocket(socket);
|
|
142
|
+
return scalar(0);
|
|
143
|
+
}
|
|
144
|
+
if (operation === NetworkOperation.Close) {
|
|
145
|
+
sockets.delete(fd);
|
|
146
|
+
if (![...sockets.values()].includes(socket))
|
|
147
|
+
closeSocket(socket);
|
|
148
|
+
return scalar(0);
|
|
149
|
+
}
|
|
150
|
+
return scalar(-38);
|
|
151
|
+
}
|
|
152
|
+
function createEpoll() {
|
|
153
|
+
const fd = nextFd++;
|
|
154
|
+
epolls.set(fd, new Map());
|
|
155
|
+
return fd;
|
|
156
|
+
}
|
|
157
|
+
function duplicateEpoll(fd, minimum) {
|
|
158
|
+
const registrations = epolls.get(fd);
|
|
159
|
+
if (!registrations)
|
|
160
|
+
return -9;
|
|
161
|
+
nextFd = Math.max(nextFd, minimum, NETWORK_FD_MIN);
|
|
162
|
+
const duplicate = nextFd++;
|
|
163
|
+
epolls.set(duplicate, registrations);
|
|
164
|
+
return duplicate;
|
|
165
|
+
}
|
|
166
|
+
function createEventFd(initial, flags) {
|
|
167
|
+
if (initial < 0)
|
|
168
|
+
return -22;
|
|
169
|
+
const fd = nextFd++;
|
|
170
|
+
sockets.set(fd, {
|
|
171
|
+
kind: "event",
|
|
172
|
+
counter: BigInt(initial >>> 0),
|
|
173
|
+
generation: 0,
|
|
174
|
+
semaphore: Boolean(flags & 1),
|
|
175
|
+
nonblocking: Boolean(flags & 0x800),
|
|
176
|
+
});
|
|
177
|
+
return fd;
|
|
178
|
+
}
|
|
179
|
+
function createTimerFd(clockId, flags) {
|
|
180
|
+
if (![0, 1, 7].includes(clockId) || (flags & ~0x8_0800) !== 0)
|
|
181
|
+
return -22;
|
|
182
|
+
const fd = nextFd++;
|
|
183
|
+
sockets.set(fd, {
|
|
184
|
+
kind: "timer",
|
|
185
|
+
clockId,
|
|
186
|
+
intervalNanoseconds: 0n,
|
|
187
|
+
deadlineNanoseconds: undefined,
|
|
188
|
+
expirations: 0n,
|
|
189
|
+
nonblocking: Boolean(flags & 0x800),
|
|
190
|
+
});
|
|
191
|
+
return fd;
|
|
192
|
+
}
|
|
193
|
+
function setTimerFd(socket, flags, input) {
|
|
194
|
+
if (socket.kind !== "timer")
|
|
195
|
+
return scalar(-9);
|
|
196
|
+
if ((flags & ~1) !== 0 || input.byteLength !== 32)
|
|
197
|
+
return scalar(-22);
|
|
198
|
+
const view = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
|
199
|
+
const interval = readTimespec(view, 0);
|
|
200
|
+
const value = readTimespec(view, 16);
|
|
201
|
+
if (interval === undefined || value === undefined)
|
|
202
|
+
return scalar(-22);
|
|
203
|
+
updateTimer(socket);
|
|
204
|
+
const output = new Uint8Array(32);
|
|
205
|
+
const outputView = new DataView(output.buffer);
|
|
206
|
+
writeTimespec(outputView, 0, socket.intervalNanoseconds);
|
|
207
|
+
writeTimespec(outputView, 16, timerRemaining(socket));
|
|
208
|
+
socket.intervalNanoseconds = interval;
|
|
209
|
+
socket.deadlineNanoseconds = value === 0n ? undefined
|
|
210
|
+
: (flags & 1) ? value : nowNanoseconds(socket.clockId) + value;
|
|
211
|
+
socket.expirations = 0n;
|
|
212
|
+
return { result: 0, output };
|
|
213
|
+
}
|
|
214
|
+
function createSocketPair(domain, type, protocol) {
|
|
215
|
+
if (domain !== 1)
|
|
216
|
+
return scalar(-97);
|
|
217
|
+
if ((type & 0xf) !== 1 || protocol !== 0)
|
|
218
|
+
return scalar(-93);
|
|
219
|
+
const left = { kind: "local", chunks: [], byteLength: 0, closed: false, peerClosed: false };
|
|
220
|
+
const right = { kind: "local", chunks: [], byteLength: 0, closed: false, peerClosed: false };
|
|
221
|
+
left.peer = right;
|
|
222
|
+
right.peer = left;
|
|
223
|
+
const leftFd = nextFd++;
|
|
224
|
+
const rightFd = nextFd++;
|
|
225
|
+
sockets.set(leftFd, left);
|
|
226
|
+
sockets.set(rightFd, right);
|
|
227
|
+
const output = new Uint8Array(8);
|
|
228
|
+
const view = new DataView(output.buffer);
|
|
229
|
+
view.setInt32(0, leftFd, true);
|
|
230
|
+
view.setInt32(4, rightFd, true);
|
|
231
|
+
return { result: 0, output };
|
|
232
|
+
}
|
|
233
|
+
function epollControl(epollFd, operation, fd, input) {
|
|
234
|
+
const sharedPipe = sharedPipes ? pollWaliSharedPipeState(sharedPipes, fd, 0) : undefined;
|
|
235
|
+
if (!sockets.has(fd) && sharedPipe === undefined && !(isStdinDescriptor(fd) && stdin))
|
|
236
|
+
return -9;
|
|
237
|
+
let registrations = epolls.get(epollFd);
|
|
238
|
+
if (!registrations) {
|
|
239
|
+
registrations = new Map();
|
|
240
|
+
epolls.set(epollFd, registrations);
|
|
241
|
+
}
|
|
242
|
+
if (operation === 2)
|
|
243
|
+
return registrations.delete(fd) ? 0 : -2;
|
|
244
|
+
if (operation !== 1 && operation !== 3 || input.byteLength < EPOLL_EVENT_BYTES)
|
|
245
|
+
return -22;
|
|
246
|
+
if (operation === 1 && registrations.has(fd))
|
|
247
|
+
return -17;
|
|
248
|
+
if (operation === 3 && !registrations.has(fd))
|
|
249
|
+
return -2;
|
|
250
|
+
const view = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
|
251
|
+
const events = view.getUint32(0, true);
|
|
252
|
+
const socket = sockets.get(fd);
|
|
253
|
+
registrations.set(fd, {
|
|
254
|
+
events,
|
|
255
|
+
data: input.slice(EPOLL_DATA_OFFSET, EPOLL_EVENT_BYTES),
|
|
256
|
+
lastReady: 0,
|
|
257
|
+
lastStdinGeneration: isStdinDescriptor(fd) && stdin ? stdin.generation() : 0,
|
|
258
|
+
lastEventGeneration: socket?.kind === "event"
|
|
259
|
+
? socket.generation
|
|
260
|
+
: 0,
|
|
261
|
+
});
|
|
262
|
+
return 0;
|
|
263
|
+
}
|
|
264
|
+
async function epollWait(epollFd, maximum, timeout) {
|
|
265
|
+
const registrations = epolls.get(epollFd);
|
|
266
|
+
if (!registrations)
|
|
267
|
+
return scalar(-9);
|
|
268
|
+
const startedAt = Date.now();
|
|
269
|
+
// The synchronous pthread RPC has a finite watchdog. Wake long and infinite
|
|
270
|
+
// epoll waits cooperatively so Tokio can recompute timers instead of turning
|
|
271
|
+
// the transport watchdog into the impossible Linux result -ETIMEDOUT.
|
|
272
|
+
const deadline = startedAt + (timeout < 0 ? 1_000 : Math.min(timeout, 1_000));
|
|
273
|
+
do {
|
|
274
|
+
const events = [];
|
|
275
|
+
for (const [fd, registration] of registrations) {
|
|
276
|
+
const socket = sockets.get(fd);
|
|
277
|
+
const pollEvents = ((registration.events & 1) ? 1 : 0) | ((registration.events & 4) ? 4 : 0);
|
|
278
|
+
const externalReady = sharedPipes ? pollWaliSharedPipeState(sharedPipes, fd, pollEvents) : undefined;
|
|
279
|
+
const terminalReady = isStdinDescriptor(fd) && stdin
|
|
280
|
+
? ((pollEvents & 1) && stdin.poll() ? 1 : 0)
|
|
281
|
+
: undefined;
|
|
282
|
+
if (!socket && externalReady === undefined && terminalReady === undefined)
|
|
283
|
+
continue;
|
|
284
|
+
const currentReady = (terminalReady ?? externalReady ?? pollSocket(socket, pollEvents))
|
|
285
|
+
& (registration.events | 0x18);
|
|
286
|
+
const stdinGeneration = isStdinDescriptor(fd) && stdin
|
|
287
|
+
? stdin.generation()
|
|
288
|
+
: registration.lastStdinGeneration;
|
|
289
|
+
const edgeReady = (currentReady & ~registration.lastReady)
|
|
290
|
+
| (stdinGeneration !== registration.lastStdinGeneration ? currentReady : 0)
|
|
291
|
+
| (socket?.kind === "event" && socket.generation !== registration.lastEventGeneration
|
|
292
|
+
? currentReady
|
|
293
|
+
: 0);
|
|
294
|
+
// A browser terminal's shared byte queue behaves like a TTY, not a
|
|
295
|
+
// packet socket. Keep reporting readable until the consumer drains it;
|
|
296
|
+
// otherwise one-byte reads can strand the final key behind EPOLLET.
|
|
297
|
+
const ready = isStdinDescriptor(fd)
|
|
298
|
+
? currentReady
|
|
299
|
+
: (registration.events & 0x8000_0000) !== 0 ? edgeReady : currentReady;
|
|
300
|
+
registration.lastReady = currentReady;
|
|
301
|
+
registration.lastStdinGeneration = stdinGeneration;
|
|
302
|
+
registration.lastEventGeneration = socket?.kind === "event"
|
|
303
|
+
? socket.generation
|
|
304
|
+
: registration.lastEventGeneration;
|
|
305
|
+
if (ready !== 0)
|
|
306
|
+
events.push({ ready, data: registration.data });
|
|
307
|
+
if (events.length >= maximum)
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
if (events.length > 0) {
|
|
311
|
+
const output = new Uint8Array(events.length * EPOLL_EVENT_BYTES);
|
|
312
|
+
const view = new DataView(output.buffer);
|
|
313
|
+
events.forEach((event, index) => {
|
|
314
|
+
const offset = index * EPOLL_EVENT_BYTES;
|
|
315
|
+
view.setUint32(offset, event.ready, true);
|
|
316
|
+
output.set(event.data, offset + EPOLL_DATA_OFFSET);
|
|
317
|
+
});
|
|
318
|
+
return { result: events.length, output };
|
|
319
|
+
}
|
|
320
|
+
if (timeout === 0 || Date.now() >= deadline)
|
|
321
|
+
return { result: 0, output: new Uint8Array() };
|
|
322
|
+
await new Promise((resolve) => setTimeout(resolve, 2));
|
|
323
|
+
} while (true);
|
|
324
|
+
}
|
|
325
|
+
function isStdinDescriptor(fd) {
|
|
326
|
+
if (fd === 0)
|
|
327
|
+
return true;
|
|
328
|
+
if (!stdioAliasRegistry || fd < 0 || fd >= stdioAliasRegistry.length)
|
|
329
|
+
return false;
|
|
330
|
+
return Atomics.load(stdioAliasRegistry, fd) === 0;
|
|
331
|
+
}
|
|
332
|
+
function scalar(result) {
|
|
333
|
+
return { result, output: new Uint8Array() };
|
|
334
|
+
}
|
|
335
|
+
function createSocket(domain, type, protocol) {
|
|
336
|
+
const baseType = type & 0xf;
|
|
337
|
+
let socket;
|
|
338
|
+
if ((domain === 2 || domain === 10) && baseType === 2 && (protocol === 0 || protocol === 17)) {
|
|
339
|
+
socket = { kind: "dns", domain, responses: [] };
|
|
340
|
+
}
|
|
341
|
+
else if ((domain === 2 || domain === 10) && baseType === 1 && (protocol === 0 || protocol === 6)) {
|
|
342
|
+
socket = {
|
|
343
|
+
kind: "tcp",
|
|
344
|
+
domain,
|
|
345
|
+
chunks: [],
|
|
346
|
+
byteLength: 0,
|
|
347
|
+
open: false,
|
|
348
|
+
closed: false,
|
|
349
|
+
failed: false,
|
|
350
|
+
nonblocking: Boolean(type & 0x800),
|
|
351
|
+
listening: false,
|
|
352
|
+
backlog: 0,
|
|
353
|
+
pending: [],
|
|
354
|
+
acceptWaiters: [],
|
|
355
|
+
peerClosed: false,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
return domain === 2 || domain === 10 ? -93 : -97;
|
|
360
|
+
}
|
|
361
|
+
const fd = nextFd++;
|
|
362
|
+
sockets.set(fd, socket);
|
|
363
|
+
return fd;
|
|
364
|
+
}
|
|
365
|
+
async function connectSocket(socket, address) {
|
|
366
|
+
if (socket.kind === "dns")
|
|
367
|
+
return 0;
|
|
368
|
+
if (socket.kind === "event" || socket.kind === "timer")
|
|
369
|
+
return -88;
|
|
370
|
+
if (socket.kind === "local")
|
|
371
|
+
return -106;
|
|
372
|
+
const target = parseSocketAddress(address);
|
|
373
|
+
if (!target)
|
|
374
|
+
return -111;
|
|
375
|
+
if (isLoopbackConnectAddress(target.address)) {
|
|
376
|
+
if (!loopbackPort || socket.open)
|
|
377
|
+
return socket.open ? -106 : -111;
|
|
378
|
+
const connectionId = nextOutboundConnectionId--;
|
|
379
|
+
const result = await requestLoopbackConnect(target.port, connectionId);
|
|
380
|
+
if (result < 0)
|
|
381
|
+
return result;
|
|
382
|
+
socket.loopbackConnectionId = connectionId;
|
|
383
|
+
socket.open = true;
|
|
384
|
+
socket.closed = false;
|
|
385
|
+
socket.peerClosed = false;
|
|
386
|
+
socket.bound = { address: socket.domain === 10 ? "::1" : "127.0.0.1", port: 0 };
|
|
387
|
+
loopbackConnections.set(connectionId, socket);
|
|
388
|
+
return 0;
|
|
389
|
+
}
|
|
390
|
+
if (target.port !== 443)
|
|
391
|
+
return -111;
|
|
392
|
+
const hostname = hostByAddress.get(target.address);
|
|
393
|
+
if (!hostname || !allowedHosts.has(hostname))
|
|
394
|
+
return -13;
|
|
395
|
+
if (socket.open)
|
|
396
|
+
return -106;
|
|
397
|
+
const url = new URL(relayUrl, scope.location.href);
|
|
398
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
399
|
+
url.search = "";
|
|
400
|
+
url.searchParams.append("host", hostname);
|
|
401
|
+
url.searchParams.append("port", "443");
|
|
402
|
+
return new Promise((resolve) => {
|
|
403
|
+
const websocket = new WebSocket(url);
|
|
404
|
+
socket.websocket = websocket;
|
|
405
|
+
websocket.binaryType = "arraybuffer";
|
|
406
|
+
let settled = false;
|
|
407
|
+
const settle = (value) => {
|
|
408
|
+
if (settled)
|
|
409
|
+
return;
|
|
410
|
+
settled = true;
|
|
411
|
+
clearTimeout(timeout);
|
|
412
|
+
resolve(value);
|
|
413
|
+
};
|
|
414
|
+
const timeout = setTimeout(() => {
|
|
415
|
+
try {
|
|
416
|
+
websocket.close();
|
|
417
|
+
}
|
|
418
|
+
catch { }
|
|
419
|
+
settle(-110);
|
|
420
|
+
}, 15_000);
|
|
421
|
+
websocket.addEventListener("open", () => { socket.open = true; settle(0); });
|
|
422
|
+
websocket.addEventListener("message", (event) => {
|
|
423
|
+
if (!(event.data instanceof ArrayBuffer)) {
|
|
424
|
+
socket.failed = true;
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const chunk = new Uint8Array(event.data);
|
|
428
|
+
socket.chunks.push(chunk);
|
|
429
|
+
socket.byteLength += chunk.byteLength;
|
|
430
|
+
});
|
|
431
|
+
websocket.addEventListener("close", () => { socket.closed = true; socket.open = false; settle(-111); });
|
|
432
|
+
websocket.addEventListener("error", () => { socket.failed = true; settle(-111); });
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
function bindSocket(socket, address) {
|
|
436
|
+
if (socket.kind === "dns") {
|
|
437
|
+
if (socket.bound)
|
|
438
|
+
return -22;
|
|
439
|
+
const target = parseSocketAddress(address);
|
|
440
|
+
if (!target || !isLoopbackBindAddress(target.address))
|
|
441
|
+
return target ? -99 : -22;
|
|
442
|
+
socket.bound = target;
|
|
443
|
+
return 0;
|
|
444
|
+
}
|
|
445
|
+
if (socket.kind !== "tcp")
|
|
446
|
+
return -88;
|
|
447
|
+
if (socket.open || socket.bound)
|
|
448
|
+
return -22;
|
|
449
|
+
const target = parseSocketAddress(address);
|
|
450
|
+
if (!target || !isLoopbackBindAddress(target.address))
|
|
451
|
+
return target ? -99 : -22;
|
|
452
|
+
const port = target.port === 0 ? allocateLoopbackPort() : target.port;
|
|
453
|
+
if (port <= 0 || listenersByPort.has(port))
|
|
454
|
+
return -98;
|
|
455
|
+
socket.bound = { address: target.address, port };
|
|
456
|
+
return 0;
|
|
457
|
+
}
|
|
458
|
+
async function listenSocket(socket, backlog) {
|
|
459
|
+
if (socket.kind !== "tcp")
|
|
460
|
+
return -95;
|
|
461
|
+
if (!socket.bound)
|
|
462
|
+
return -22;
|
|
463
|
+
if (socket.listening)
|
|
464
|
+
return 0;
|
|
465
|
+
if (!loopbackPort)
|
|
466
|
+
return -97;
|
|
467
|
+
const existing = listenersByPort.get(socket.bound.port);
|
|
468
|
+
if (existing && existing !== socket)
|
|
469
|
+
return -98;
|
|
470
|
+
const result = await requestLoopbackListen(socket.bound.port);
|
|
471
|
+
if (result < 0)
|
|
472
|
+
return result;
|
|
473
|
+
socket.listening = true;
|
|
474
|
+
socket.open = true;
|
|
475
|
+
socket.backlog = Math.max(1, Math.min(128, backlog));
|
|
476
|
+
listenersByPort.set(socket.bound.port, socket);
|
|
477
|
+
return 0;
|
|
478
|
+
}
|
|
479
|
+
async function acceptSocket(socket, flags) {
|
|
480
|
+
if (socket.kind !== "tcp" || !socket.listening)
|
|
481
|
+
return scalar(-22);
|
|
482
|
+
if ((flags & ~0x8_0800) !== 0)
|
|
483
|
+
return scalar(-22);
|
|
484
|
+
let accepted = socket.pending.shift();
|
|
485
|
+
if (!accepted && (socket.nonblocking || Boolean(flags & 0x800)))
|
|
486
|
+
return scalar(-11);
|
|
487
|
+
if (!accepted) {
|
|
488
|
+
accepted = await new Promise((resolve) => socket.acceptWaiters.push(resolve));
|
|
489
|
+
}
|
|
490
|
+
if (!accepted)
|
|
491
|
+
return scalar(-9);
|
|
492
|
+
accepted.nonblocking = accepted.nonblocking || Boolean(flags & 0x800);
|
|
493
|
+
const fd = nextFd++;
|
|
494
|
+
sockets.set(fd, accepted);
|
|
495
|
+
return { result: fd, output: socketAddress(accepted.domain, "127.0.0.1", 0) };
|
|
496
|
+
}
|
|
497
|
+
function requestLoopbackListen(port) {
|
|
498
|
+
if (!loopbackPort)
|
|
499
|
+
return Promise.resolve(-97);
|
|
500
|
+
const requestId = nextLoopbackRequestId++;
|
|
501
|
+
return new Promise((resolve) => {
|
|
502
|
+
const timeout = setTimeout(() => {
|
|
503
|
+
if (!loopbackAcks.delete(requestId))
|
|
504
|
+
return;
|
|
505
|
+
resolve(-110);
|
|
506
|
+
}, 5_000);
|
|
507
|
+
loopbackAcks.set(requestId, (result) => {
|
|
508
|
+
clearTimeout(timeout);
|
|
509
|
+
resolve(result);
|
|
510
|
+
});
|
|
511
|
+
postLoopback({ type: "listen", requestId, port });
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
function requestLoopbackConnect(port, connectionId) {
|
|
515
|
+
if (!loopbackPort)
|
|
516
|
+
return Promise.resolve(-111);
|
|
517
|
+
const requestId = nextLoopbackRequestId++;
|
|
518
|
+
return new Promise((resolve) => {
|
|
519
|
+
const timeout = setTimeout(() => {
|
|
520
|
+
if (!loopbackAcks.delete(requestId))
|
|
521
|
+
return;
|
|
522
|
+
resolve(-110);
|
|
523
|
+
}, 5_000);
|
|
524
|
+
loopbackAcks.set(requestId, (result) => {
|
|
525
|
+
clearTimeout(timeout);
|
|
526
|
+
resolve(result);
|
|
527
|
+
});
|
|
528
|
+
postLoopback({ type: "connect", requestId, connectionId, port });
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
function handleLoopbackMessage(message) {
|
|
532
|
+
if (message.type === "ack") {
|
|
533
|
+
const resolve = loopbackAcks.get(message.requestId);
|
|
534
|
+
if (!resolve)
|
|
535
|
+
return;
|
|
536
|
+
loopbackAcks.delete(message.requestId);
|
|
537
|
+
resolve(message.result);
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
if (message.type === "peer-close") {
|
|
541
|
+
const socket = loopbackConnections.get(message.connectionId);
|
|
542
|
+
if (socket)
|
|
543
|
+
socket.peerClosed = true;
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (message.type === "data") {
|
|
547
|
+
const socket = loopbackConnections.get(message.connectionId);
|
|
548
|
+
if (!socket || socket.closed)
|
|
549
|
+
return;
|
|
550
|
+
const bytes = message.bytes.slice();
|
|
551
|
+
socket.chunks.push(bytes);
|
|
552
|
+
socket.byteLength += bytes.byteLength;
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const listener = listenersByPort.get(message.port);
|
|
556
|
+
if (!listener || !listener.listening || listener.closed
|
|
557
|
+
|| listener.pending.length >= listener.backlog) {
|
|
558
|
+
postLoopback({ type: "closed", connectionId: message.connectionId });
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
const accepted = {
|
|
562
|
+
kind: "tcp",
|
|
563
|
+
domain: listener.domain,
|
|
564
|
+
chunks: [message.bytes.slice()],
|
|
565
|
+
byteLength: message.bytes.byteLength,
|
|
566
|
+
open: true,
|
|
567
|
+
closed: false,
|
|
568
|
+
failed: false,
|
|
569
|
+
nonblocking: false,
|
|
570
|
+
listening: false,
|
|
571
|
+
backlog: 0,
|
|
572
|
+
pending: [],
|
|
573
|
+
acceptWaiters: [],
|
|
574
|
+
loopbackConnectionId: message.connectionId,
|
|
575
|
+
peerClosed: false,
|
|
576
|
+
...(listener.bound ? { bound: listener.bound } : {}),
|
|
577
|
+
};
|
|
578
|
+
loopbackConnections.set(message.connectionId, accepted);
|
|
579
|
+
const waiter = listener.acceptWaiters.shift();
|
|
580
|
+
if (waiter)
|
|
581
|
+
waiter(accepted);
|
|
582
|
+
else
|
|
583
|
+
listener.pending.push(accepted);
|
|
584
|
+
}
|
|
585
|
+
function postLoopback(message) {
|
|
586
|
+
loopbackPort?.postMessage(message);
|
|
587
|
+
}
|
|
588
|
+
function isLoopbackBindAddress(address) {
|
|
589
|
+
return address === "0.0.0.0" || address === "127.0.0.1" || address === "::" || address === "::1";
|
|
590
|
+
}
|
|
591
|
+
function isLoopbackConnectAddress(address) {
|
|
592
|
+
return address === "127.0.0.1" || address === "::1";
|
|
593
|
+
}
|
|
594
|
+
function allocateLoopbackPort() {
|
|
595
|
+
for (let port = 49_152; port <= 65_535; port += 1) {
|
|
596
|
+
if (!listenersByPort.has(port))
|
|
597
|
+
return port;
|
|
598
|
+
}
|
|
599
|
+
return -1;
|
|
600
|
+
}
|
|
601
|
+
function sendSocket(socket, input) {
|
|
602
|
+
if (socket.kind === "event") {
|
|
603
|
+
if (input.byteLength < 8)
|
|
604
|
+
return -22;
|
|
605
|
+
const increment = new DataView(input.buffer, input.byteOffset, input.byteLength).getBigUint64(0, true);
|
|
606
|
+
if (increment === 0xffffffffffffffffn)
|
|
607
|
+
return -22;
|
|
608
|
+
socket.counter += increment;
|
|
609
|
+
socket.generation = socket.generation >= 0x7fff_ffff ? 1 : socket.generation + 1;
|
|
610
|
+
return 8;
|
|
611
|
+
}
|
|
612
|
+
if (socket.kind === "local") {
|
|
613
|
+
if (socket.closed)
|
|
614
|
+
return -9;
|
|
615
|
+
if (!socket.peer || socket.peer.closed)
|
|
616
|
+
return -32;
|
|
617
|
+
const copy = input.slice();
|
|
618
|
+
socket.peer.chunks.push(copy);
|
|
619
|
+
socket.peer.byteLength += copy.byteLength;
|
|
620
|
+
return copy.byteLength;
|
|
621
|
+
}
|
|
622
|
+
if (socket.kind === "timer")
|
|
623
|
+
return -22;
|
|
624
|
+
if (socket.kind === "dns") {
|
|
625
|
+
const response = dnsResponse(input);
|
|
626
|
+
if (!response)
|
|
627
|
+
return -13;
|
|
628
|
+
socket.responses.push(response);
|
|
629
|
+
return input.byteLength;
|
|
630
|
+
}
|
|
631
|
+
if (socket.loopbackConnectionId !== undefined) {
|
|
632
|
+
if (socket.closed || socket.peerClosed)
|
|
633
|
+
return -32;
|
|
634
|
+
postLoopback({ type: "outgoing", connectionId: socket.loopbackConnectionId, bytes: input.slice() });
|
|
635
|
+
return input.byteLength;
|
|
636
|
+
}
|
|
637
|
+
if (!socket.open || !socket.websocket)
|
|
638
|
+
return socket.closed ? -32 : -11;
|
|
639
|
+
socket.websocket.send(input);
|
|
640
|
+
return input.byteLength;
|
|
641
|
+
}
|
|
642
|
+
function receiveSocket(socket, maximum) {
|
|
643
|
+
if (socket.kind === "event") {
|
|
644
|
+
if (maximum < 8)
|
|
645
|
+
return { result: -22, output: new Uint8Array() };
|
|
646
|
+
if (socket.counter === 0n)
|
|
647
|
+
return { result: socket.nonblocking ? -11 : -11, output: new Uint8Array() };
|
|
648
|
+
const value = socket.semaphore ? 1n : socket.counter;
|
|
649
|
+
socket.counter -= value;
|
|
650
|
+
const output = new Uint8Array(8);
|
|
651
|
+
new DataView(output.buffer).setBigUint64(0, value, true);
|
|
652
|
+
return { result: 8, output };
|
|
653
|
+
}
|
|
654
|
+
if (socket.kind === "local") {
|
|
655
|
+
if (socket.closed)
|
|
656
|
+
return { result: -9, output: new Uint8Array() };
|
|
657
|
+
if (socket.byteLength === 0)
|
|
658
|
+
return { result: socket.peerClosed ? 0 : -11, output: new Uint8Array() };
|
|
659
|
+
return drainChunks(socket, maximum);
|
|
660
|
+
}
|
|
661
|
+
if (socket.kind === "timer") {
|
|
662
|
+
if (maximum < 8)
|
|
663
|
+
return { result: -22, output: new Uint8Array() };
|
|
664
|
+
updateTimer(socket);
|
|
665
|
+
if (socket.expirations === 0n)
|
|
666
|
+
return { result: -11, output: new Uint8Array() };
|
|
667
|
+
const output = new Uint8Array(8);
|
|
668
|
+
new DataView(output.buffer).setBigUint64(0, socket.expirations, true);
|
|
669
|
+
socket.expirations = 0n;
|
|
670
|
+
return { result: 8, output };
|
|
671
|
+
}
|
|
672
|
+
if (maximum === 0)
|
|
673
|
+
return { result: 0, output: new Uint8Array() };
|
|
674
|
+
if (socket.kind === "dns") {
|
|
675
|
+
const response = socket.responses.shift();
|
|
676
|
+
if (!response)
|
|
677
|
+
return { result: -11, output: new Uint8Array() };
|
|
678
|
+
const output = response.subarray(0, maximum);
|
|
679
|
+
return { result: output.byteLength, output };
|
|
680
|
+
}
|
|
681
|
+
if (socket.byteLength === 0)
|
|
682
|
+
return {
|
|
683
|
+
result: socket.closed || socket.peerClosed ? 0 : socket.failed ? -5 : -11,
|
|
684
|
+
output: new Uint8Array(),
|
|
685
|
+
};
|
|
686
|
+
return drainChunks(socket, maximum);
|
|
687
|
+
}
|
|
688
|
+
function drainChunks(socket, maximum) {
|
|
689
|
+
const output = new Uint8Array(Math.min(maximum, socket.byteLength));
|
|
690
|
+
let offset = 0;
|
|
691
|
+
while (offset < output.byteLength) {
|
|
692
|
+
const chunk = socket.chunks[0];
|
|
693
|
+
const count = Math.min(chunk.byteLength, output.byteLength - offset);
|
|
694
|
+
output.set(chunk.subarray(0, count), offset);
|
|
695
|
+
offset += count;
|
|
696
|
+
socket.byteLength -= count;
|
|
697
|
+
if (count === chunk.byteLength)
|
|
698
|
+
socket.chunks.shift();
|
|
699
|
+
else
|
|
700
|
+
socket.chunks[0] = chunk.subarray(count);
|
|
701
|
+
}
|
|
702
|
+
return { result: output.byteLength, output };
|
|
703
|
+
}
|
|
704
|
+
function pollSocket(socket, events) {
|
|
705
|
+
if (socket.kind === "event")
|
|
706
|
+
return ((events & 1) && socket.counter > 0n ? 1 : 0) | ((events & 4) ? 4 : 0);
|
|
707
|
+
if (socket.kind === "timer") {
|
|
708
|
+
updateTimer(socket);
|
|
709
|
+
return (events & 1) && socket.expirations > 0n ? 1 : 0;
|
|
710
|
+
}
|
|
711
|
+
if (socket.kind === "local") {
|
|
712
|
+
return (socket.peerClosed ? 0x10 : 0)
|
|
713
|
+
| ((events & 1) && (socket.byteLength > 0 || socket.peerClosed) ? 1 : 0)
|
|
714
|
+
| ((events & 4) && !socket.peerClosed ? 4 : 0);
|
|
715
|
+
}
|
|
716
|
+
if (socket.kind === "dns")
|
|
717
|
+
return ((events & 1) && socket.responses.length ? 1 : 0) | ((events & 4) ? 4 : 0);
|
|
718
|
+
if (socket.listening)
|
|
719
|
+
return (events & 1) && socket.pending.length > 0 ? 1 : 0;
|
|
720
|
+
let ready = socket.closed || socket.peerClosed ? 0x10 : socket.failed ? 0x8 : 0;
|
|
721
|
+
if ((events & 1) && (socket.byteLength > 0 || socket.closed || socket.peerClosed))
|
|
722
|
+
ready |= 1;
|
|
723
|
+
if ((events & 4) && socket.open && !socket.peerClosed)
|
|
724
|
+
ready |= 4;
|
|
725
|
+
return ready;
|
|
726
|
+
}
|
|
727
|
+
function rearmEpollReadable(fd) {
|
|
728
|
+
const target = sockets.get(fd);
|
|
729
|
+
for (const registrations of epolls.values()) {
|
|
730
|
+
for (const [registeredFd, registration] of registrations) {
|
|
731
|
+
if (registeredFd === fd || (target && sockets.get(registeredFd) === target)) {
|
|
732
|
+
registration.lastReady &= ~1;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
function fcntlSocket(_fd, socket, command, argument) {
|
|
738
|
+
if (command === 0 || command === 1030)
|
|
739
|
+
return duplicateSocket(socket, Math.max(argument, NETWORK_FD_MIN));
|
|
740
|
+
if (command === 1 || command === 2 || command === 4)
|
|
741
|
+
return 0;
|
|
742
|
+
if (command === 3)
|
|
743
|
+
return socket.kind === "timer" ? (socket.nonblocking ? 0x800 : 0) : 0x802;
|
|
744
|
+
return -22;
|
|
745
|
+
}
|
|
746
|
+
function duplicateSocket(socket, minimum = NETWORK_FD_MIN) {
|
|
747
|
+
nextFd = Math.max(nextFd, minimum);
|
|
748
|
+
const fd = nextFd++;
|
|
749
|
+
sockets.set(fd, socket);
|
|
750
|
+
return fd;
|
|
751
|
+
}
|
|
752
|
+
function closeSocket(socket) {
|
|
753
|
+
if (socket.kind === "tcp") {
|
|
754
|
+
if (socket.listening && socket.bound) {
|
|
755
|
+
listenersByPort.delete(socket.bound.port);
|
|
756
|
+
postLoopback({ type: "unlisten", port: socket.bound.port });
|
|
757
|
+
for (const resolve of socket.acceptWaiters.splice(0))
|
|
758
|
+
resolve(undefined);
|
|
759
|
+
for (const accepted of socket.pending.splice(0))
|
|
760
|
+
closeSocket(accepted);
|
|
761
|
+
}
|
|
762
|
+
if (socket.loopbackConnectionId !== undefined) {
|
|
763
|
+
loopbackConnections.delete(socket.loopbackConnectionId);
|
|
764
|
+
postLoopback({ type: "closed", connectionId: socket.loopbackConnectionId });
|
|
765
|
+
}
|
|
766
|
+
socket.closed = true;
|
|
767
|
+
socket.open = false;
|
|
768
|
+
try {
|
|
769
|
+
socket.websocket?.close();
|
|
770
|
+
}
|
|
771
|
+
catch { }
|
|
772
|
+
}
|
|
773
|
+
if (socket.kind === "local") {
|
|
774
|
+
socket.closed = true;
|
|
775
|
+
if (socket.peer)
|
|
776
|
+
socket.peer.peerClosed = true;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
function socketName(socket) {
|
|
780
|
+
if (socket.kind === "event" || socket.kind === "timer")
|
|
781
|
+
return new Uint8Array();
|
|
782
|
+
if (socket.kind === "local")
|
|
783
|
+
return new Uint8Array();
|
|
784
|
+
if (socket.kind === "dns")
|
|
785
|
+
return socketAddress(socket.domain, socket.bound?.address ?? (socket.domain === 10 ? "::" : "0.0.0.0"), socket.bound?.port ?? 0);
|
|
786
|
+
return socketAddress(socket.domain, socket.bound?.address ?? (socket.domain === 10 ? "::" : "0.0.0.0"), socket.bound?.port ?? 0);
|
|
787
|
+
}
|
|
788
|
+
function socketAddress(domain, address, port) {
|
|
789
|
+
const output = new Uint8Array(domain === 10 ? 28 : 16);
|
|
790
|
+
const view = new DataView(output.buffer);
|
|
791
|
+
view.setUint16(0, domain, true);
|
|
792
|
+
view.setUint16(2, port, false);
|
|
793
|
+
if (domain === 2) {
|
|
794
|
+
const parts = address === "0.0.0.0" ? [0, 0, 0, 0]
|
|
795
|
+
: address === "127.0.0.1" ? [127, 0, 0, 1] : [127, 0, 0, 1];
|
|
796
|
+
output.set(parts, 4);
|
|
797
|
+
}
|
|
798
|
+
else if (address === "::1") {
|
|
799
|
+
output[23] = 1;
|
|
800
|
+
}
|
|
801
|
+
return output;
|
|
802
|
+
}
|
|
803
|
+
function updateTimer(timer) {
|
|
804
|
+
if (timer.deadlineNanoseconds === undefined)
|
|
805
|
+
return;
|
|
806
|
+
const now = nowNanoseconds(timer.clockId);
|
|
807
|
+
if (now < timer.deadlineNanoseconds)
|
|
808
|
+
return;
|
|
809
|
+
const elapsed = now - timer.deadlineNanoseconds;
|
|
810
|
+
const count = timer.intervalNanoseconds === 0n ? 1n : 1n + elapsed / timer.intervalNanoseconds;
|
|
811
|
+
const next = timer.expirations + count;
|
|
812
|
+
timer.expirations = next > 0xffffffffffffffffn ? 0xffffffffffffffffn : next;
|
|
813
|
+
timer.deadlineNanoseconds = timer.intervalNanoseconds === 0n
|
|
814
|
+
? undefined : timer.deadlineNanoseconds + count * timer.intervalNanoseconds;
|
|
815
|
+
}
|
|
816
|
+
function timerRemaining(timer) {
|
|
817
|
+
if (timer.deadlineNanoseconds === undefined)
|
|
818
|
+
return 0n;
|
|
819
|
+
const remaining = timer.deadlineNanoseconds - nowNanoseconds(timer.clockId);
|
|
820
|
+
return remaining > 0n ? remaining : 0n;
|
|
821
|
+
}
|
|
822
|
+
function readTimespec(view, offset) {
|
|
823
|
+
const seconds = view.getBigInt64(offset, true);
|
|
824
|
+
const nanoseconds = view.getBigInt64(offset + 8, true);
|
|
825
|
+
if (seconds < 0n || nanoseconds < 0n || nanoseconds >= 1000000000n)
|
|
826
|
+
return;
|
|
827
|
+
return seconds * 1000000000n + nanoseconds;
|
|
828
|
+
}
|
|
829
|
+
function writeTimespec(view, offset, nanoseconds) {
|
|
830
|
+
view.setBigInt64(offset, nanoseconds / 1000000000n, true);
|
|
831
|
+
view.setBigInt64(offset + 8, nanoseconds % 1000000000n, true);
|
|
832
|
+
}
|
|
833
|
+
function nowNanoseconds(clockId) {
|
|
834
|
+
const milliseconds = clockId === 0 ? performance.timeOrigin + performance.now() : performance.now();
|
|
835
|
+
return BigInt(Math.floor(milliseconds * 1_000_000));
|
|
836
|
+
}
|
|
837
|
+
function dnsResponse(query) {
|
|
838
|
+
if (query.byteLength < 17)
|
|
839
|
+
return;
|
|
840
|
+
let offset = 12;
|
|
841
|
+
const labels = [];
|
|
842
|
+
while (offset < query.byteLength) {
|
|
843
|
+
const length = query[offset++];
|
|
844
|
+
if (length === 0)
|
|
845
|
+
break;
|
|
846
|
+
if (length > 63 || offset + length > query.byteLength)
|
|
847
|
+
return;
|
|
848
|
+
labels.push(new TextDecoder().decode(query.subarray(offset, offset + length)));
|
|
849
|
+
offset += length;
|
|
850
|
+
}
|
|
851
|
+
if (offset + 4 > query.byteLength)
|
|
852
|
+
return;
|
|
853
|
+
const hostname = labels.join(".").toLowerCase();
|
|
854
|
+
const qtype = (query[offset] << 8) | query[offset + 1];
|
|
855
|
+
const questionEnd = offset + 4;
|
|
856
|
+
const allowed = allowedHosts.has(hostname) && (qtype === 1 || qtype === 28);
|
|
857
|
+
const answerLength = allowed ? (qtype === 1 ? 16 : 28) : 0;
|
|
858
|
+
const response = new Uint8Array(questionEnd + answerLength);
|
|
859
|
+
response.set(query.subarray(0, questionEnd));
|
|
860
|
+
response[2] = 0x81;
|
|
861
|
+
response[3] = allowed ? 0x80 : 0x83;
|
|
862
|
+
response[6] = 0;
|
|
863
|
+
response[7] = allowed ? 1 : 0;
|
|
864
|
+
response.fill(0, 8, 12);
|
|
865
|
+
if (!allowed)
|
|
866
|
+
return response;
|
|
867
|
+
const identifier = addressId(hostname);
|
|
868
|
+
let answer = questionEnd;
|
|
869
|
+
response[answer++] = 0xc0;
|
|
870
|
+
response[answer++] = 0x0c;
|
|
871
|
+
response[answer++] = 0;
|
|
872
|
+
response[answer++] = qtype;
|
|
873
|
+
response[answer++] = 0;
|
|
874
|
+
response[answer++] = 1;
|
|
875
|
+
response[answer++] = 0;
|
|
876
|
+
response[answer++] = 0;
|
|
877
|
+
response[answer++] = 0;
|
|
878
|
+
response[answer++] = 60;
|
|
879
|
+
response[answer++] = 0;
|
|
880
|
+
response[answer++] = qtype === 1 ? 4 : 16;
|
|
881
|
+
if (qtype === 1) {
|
|
882
|
+
const address = `203.0.113.${identifier}`;
|
|
883
|
+
hostByAddress.set(address, hostname);
|
|
884
|
+
response.set([203, 0, 113, identifier], answer);
|
|
885
|
+
}
|
|
886
|
+
else {
|
|
887
|
+
const address = `2001:db8::${identifier.toString(16)}`;
|
|
888
|
+
hostByAddress.set(address, hostname);
|
|
889
|
+
response.set([0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, identifier], answer);
|
|
890
|
+
}
|
|
891
|
+
return response;
|
|
892
|
+
}
|
|
893
|
+
function addressId(hostname) {
|
|
894
|
+
const current = addressIdByHost.get(hostname);
|
|
895
|
+
if (current)
|
|
896
|
+
return current;
|
|
897
|
+
const next = addressIdByHost.size + 1;
|
|
898
|
+
if (next > 254)
|
|
899
|
+
throw new Error("Too many WALI DNS targets.");
|
|
900
|
+
addressIdByHost.set(hostname, next);
|
|
901
|
+
return next;
|
|
902
|
+
}
|
|
903
|
+
function parseSocketAddress(value) {
|
|
904
|
+
if (value.byteLength < 8)
|
|
905
|
+
return;
|
|
906
|
+
const view = new DataView(value.buffer, value.byteOffset, value.byteLength);
|
|
907
|
+
const family = view.getUint16(0, true);
|
|
908
|
+
const port = view.getUint16(2, false);
|
|
909
|
+
if (family === 2 && value.byteLength >= 8) {
|
|
910
|
+
return { address: [...value.subarray(4, 8)].join("."), port };
|
|
911
|
+
}
|
|
912
|
+
if (family === 10 && value.byteLength >= 24) {
|
|
913
|
+
const groups = [];
|
|
914
|
+
for (let offset = 8; offset < 24; offset += 2)
|
|
915
|
+
groups.push(view.getUint16(offset, false).toString(16));
|
|
916
|
+
return { address: compressIpv6(groups), port };
|
|
917
|
+
}
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
function compressIpv6(groups) {
|
|
921
|
+
let bestStart = -1;
|
|
922
|
+
let bestLength = 0;
|
|
923
|
+
for (let start = 0; start < groups.length;) {
|
|
924
|
+
if (groups[start] !== "0") {
|
|
925
|
+
start += 1;
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
let end = start;
|
|
929
|
+
while (groups[end] === "0")
|
|
930
|
+
end += 1;
|
|
931
|
+
if (end - start > bestLength) {
|
|
932
|
+
bestStart = start;
|
|
933
|
+
bestLength = end - start;
|
|
934
|
+
}
|
|
935
|
+
start = end;
|
|
936
|
+
}
|
|
937
|
+
if (bestLength < 2)
|
|
938
|
+
return groups.join(":");
|
|
939
|
+
const before = groups.slice(0, bestStart).join(":");
|
|
940
|
+
const after = groups.slice(bestStart + bestLength).join(":");
|
|
941
|
+
return `${before}::${after}`;
|
|
942
|
+
}
|
|
943
|
+
//# sourceMappingURL=network-worker.js.map
|