@volter/browser-wali 0.1.166 → 0.1.168
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 +8 -0
- package/README.md +9 -0
- package/dist/browser-wali-command.d.ts +7 -0
- package/dist/browser-wali-command.d.ts.map +1 -1
- package/dist/browser-wali-command.js +70 -18
- package/dist/browser-wali-command.js.map +1 -1
- package/dist/bulk-memory.d.ts +4 -2
- package/dist/bulk-memory.d.ts.map +1 -1
- package/dist/bulk-memory.js +32 -9
- package/dist/bulk-memory.js.map +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +6 -2
- package/dist/capabilities.js.map +1 -1
- package/dist/child-processes.d.ts +2 -0
- package/dist/child-processes.d.ts.map +1 -1
- package/dist/child-processes.js +9 -1
- package/dist/child-processes.js.map +1 -1
- package/dist/clocks.d.ts +3 -0
- package/dist/clocks.d.ts.map +1 -0
- package/dist/clocks.js +44 -0
- package/dist/clocks.js.map +1 -0
- package/dist/coherent-file-locks.d.ts.map +1 -1
- package/dist/coherent-file-locks.js +2 -1
- package/dist/coherent-file-locks.js.map +1 -1
- package/dist/credentials.d.ts +4 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +46 -0
- package/dist/credentials.js.map +1 -0
- package/dist/descriptors.d.ts +14 -0
- package/dist/descriptors.d.ts.map +1 -1
- package/dist/descriptors.js +45 -5
- package/dist/descriptors.js.map +1 -1
- package/dist/environment.d.ts +4 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +23 -0
- package/dist/environment.js.map +1 -0
- package/dist/epoll-ready.d.ts +27 -0
- package/dist/epoll-ready.d.ts.map +1 -0
- package/dist/epoll-ready.js +88 -0
- package/dist/epoll-ready.js.map +1 -0
- package/dist/events.d.ts +1 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +55 -24
- package/dist/events.js.map +1 -1
- package/dist/filesystem-host-session.d.ts +65 -0
- package/dist/filesystem-host-session.d.ts.map +1 -0
- package/dist/filesystem-host-session.js +951 -0
- package/dist/filesystem-host-session.js.map +1 -0
- package/dist/filesystem-host.d.ts +13 -81
- package/dist/filesystem-host.d.ts.map +1 -1
- package/dist/filesystem-host.js +14 -882
- package/dist/filesystem-host.js.map +1 -1
- package/dist/filesystem-protocol.d.ts +3 -1
- package/dist/filesystem-protocol.d.ts.map +1 -1
- package/dist/filesystem-remote.d.ts +8 -3
- package/dist/filesystem-remote.d.ts.map +1 -1
- package/dist/filesystem-remote.js +43 -20
- package/dist/filesystem-remote.js.map +1 -1
- package/dist/filesystem-service-client.d.ts +4 -0
- package/dist/filesystem-service-client.d.ts.map +1 -0
- package/dist/filesystem-service-client.js +16 -0
- package/dist/filesystem-service-client.js.map +1 -0
- package/dist/filesystem.d.ts +26 -4
- package/dist/filesystem.d.ts.map +1 -1
- package/dist/filesystem.js +633 -175
- package/dist/filesystem.js.map +1 -1
- package/dist/fork-memory.d.ts +23 -0
- package/dist/fork-memory.d.ts.map +1 -0
- package/dist/fork-memory.js +93 -0
- package/dist/fork-memory.js.map +1 -0
- package/dist/fork-worker.js +32 -11
- package/dist/fork-worker.js.map +1 -1
- package/dist/jumps.d.ts +7 -0
- package/dist/jumps.d.ts.map +1 -1
- package/dist/jumps.js +19 -1
- package/dist/jumps.js.map +1 -1
- package/dist/kernel-filesystem-worker.d.ts +2 -0
- package/dist/kernel-filesystem-worker.d.ts.map +1 -0
- package/dist/kernel-filesystem-worker.js +28 -0
- package/dist/kernel-filesystem-worker.js.map +1 -0
- package/dist/kernel-filesystem.d.ts +12 -0
- package/dist/kernel-filesystem.d.ts.map +1 -0
- package/dist/kernel-filesystem.js +45 -0
- package/dist/kernel-filesystem.js.map +1 -0
- package/dist/kernel-network-worker.d.ts +2 -0
- package/dist/kernel-network-worker.d.ts.map +1 -0
- package/dist/kernel-network-worker.js +14 -0
- package/dist/kernel-network-worker.js.map +1 -0
- package/dist/kernel-network.d.ts +14 -0
- package/dist/kernel-network.d.ts.map +1 -0
- package/dist/kernel-network.js +50 -0
- package/dist/kernel-network.js.map +1 -0
- package/dist/kernel-process-client.d.ts +6 -1
- package/dist/kernel-process-client.d.ts.map +1 -1
- package/dist/kernel-process-client.js +10 -1
- package/dist/kernel-process-client.js.map +1 -1
- package/dist/module-layout.d.ts +8 -0
- package/dist/module-layout.d.ts.map +1 -1
- package/dist/module-layout.js +263 -2
- package/dist/module-layout.js.map +1 -1
- package/dist/network-host.d.ts +6 -0
- package/dist/network-host.d.ts.map +1 -0
- package/dist/network-host.js +1582 -0
- package/dist/network-host.js.map +1 -0
- package/dist/network-protocol.d.ts +9 -2
- package/dist/network-protocol.d.ts.map +1 -1
- package/dist/network-protocol.js +3 -0
- package/dist/network-protocol.js.map +1 -1
- package/dist/network-service-client.d.ts +4 -0
- package/dist/network-service-client.d.ts.map +1 -0
- package/dist/network-service-client.js +16 -0
- package/dist/network-service-client.js.map +1 -0
- package/dist/network-worker.js +5 -1150
- package/dist/network-worker.js.map +1 -1
- package/dist/network.d.ts +13 -1
- package/dist/network.d.ts.map +1 -1
- package/dist/network.js +175 -48
- package/dist/network.js.map +1 -1
- package/dist/paths.d.ts +11 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +97 -0
- package/dist/paths.js.map +1 -0
- package/dist/process-bridge.d.ts +12 -4
- package/dist/process-bridge.d.ts.map +1 -1
- package/dist/process-bridge.js +53 -17
- package/dist/process-bridge.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +8 -0
- package/dist/program.js.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +150 -92
- package/dist/runtime.js.map +1 -1
- package/dist/shared-buffer-pool.d.ts +18 -5
- package/dist/shared-buffer-pool.d.ts.map +1 -1
- package/dist/shared-buffer-pool.js +86 -14
- package/dist/shared-buffer-pool.js.map +1 -1
- package/dist/shared-file-layer.d.ts +3 -1
- package/dist/shared-file-layer.d.ts.map +1 -1
- package/dist/shared-file-layer.js +150 -35
- package/dist/shared-file-layer.js.map +1 -1
- package/dist/shared-pipes.d.ts +3 -1
- package/dist/shared-pipes.d.ts.map +1 -1
- package/dist/shared-pipes.js +105 -33
- package/dist/shared-pipes.js.map +1 -1
- package/dist/signal-interruption.d.ts +9 -0
- package/dist/signal-interruption.d.ts.map +1 -0
- package/dist/signal-interruption.js +6 -0
- package/dist/signal-interruption.js.map +1 -0
- package/dist/signals.d.ts +17 -6
- package/dist/signals.d.ts.map +1 -1
- package/dist/signals.js +101 -22
- package/dist/signals.js.map +1 -1
- package/dist/snapshot-filesystem.d.ts +12 -1
- package/dist/snapshot-filesystem.d.ts.map +1 -1
- package/dist/snapshot-filesystem.js +241 -73
- package/dist/snapshot-filesystem.js.map +1 -1
- package/dist/socket-flags.d.ts +3 -0
- package/dist/socket-flags.d.ts.map +1 -0
- package/dist/socket-flags.js +9 -0
- package/dist/socket-flags.js.map +1 -0
- package/dist/special-files.d.ts +6 -0
- package/dist/special-files.d.ts.map +1 -0
- package/dist/special-files.js +56 -0
- package/dist/special-files.js.map +1 -0
- package/dist/stdin.d.ts.map +1 -1
- package/dist/stdin.js +13 -5
- package/dist/stdin.js.map +1 -1
- package/dist/symlink.d.ts +2 -1
- package/dist/symlink.d.ts.map +1 -1
- package/dist/symlink.js +25 -93
- package/dist/symlink.js.map +1 -1
- package/dist/system.d.ts +2 -4
- package/dist/system.d.ts.map +1 -1
- package/dist/system.js +46 -29
- package/dist/system.js.map +1 -1
- package/dist/thread-coordinator-worker.js +58 -24
- package/dist/thread-coordinator-worker.js.map +1 -1
- package/dist/thread-lifecycle.d.ts +9 -0
- package/dist/thread-lifecycle.d.ts.map +1 -0
- package/dist/thread-lifecycle.js +30 -0
- package/dist/thread-lifecycle.js.map +1 -0
- package/dist/thread-worker.js +125 -44
- package/dist/thread-worker.js.map +1 -1
- package/dist/threads.d.ts +6 -0
- package/dist/threads.d.ts.map +1 -1
- package/dist/threads.js +46 -6
- package/dist/threads.js.map +1 -1
- package/dist/tty-job-control.d.ts +13 -0
- package/dist/tty-job-control.d.ts.map +1 -0
- package/dist/tty-job-control.js +56 -0
- package/dist/tty-job-control.js.map +1 -0
- package/dist/tty-settings.d.ts +3 -0
- package/dist/tty-settings.d.ts.map +1 -0
- package/dist/tty-settings.js +25 -0
- package/dist/tty-settings.js.map +1 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/wait-state.d.ts +8 -0
- package/dist/wait-state.d.ts.map +1 -1
- package/dist/wait-state.js +15 -0
- package/dist/wait-state.js.map +1 -1
- package/dist/worker-program-protocol.d.ts +22 -2
- package/dist/worker-program-protocol.d.ts.map +1 -1
- package/dist/worker-program-worker.js +31 -9
- package/dist/worker-program-worker.js.map +1 -1
- package/dist/worker-program.d.ts +12 -2
- package/dist/worker-program.d.ts.map +1 -1
- package/dist/worker-program.js +214 -36
- package/dist/worker-program.js.map +1 -1
- package/dist/write-vector.d.ts +3 -0
- package/dist/write-vector.d.ts.map +1 -0
- package/dist/write-vector.js +35 -0
- package/dist/write-vector.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1582 @@
|
|
|
1
|
+
// Socket peer/error/peek rules adapted from Emscripten src/lib/libsockfs.js
|
|
2
|
+
// and libsyscall.js, commit 862dc0d48acc4134a0088d27ac8f5dd18baba1cd.
|
|
3
|
+
// Copyright 2013, 2021 The Emscripten Authors. SPDX-License-Identifier: MIT
|
|
4
|
+
// Modifications: Linux descriptor flags/accept semantics; transport, grants,
|
|
5
|
+
// process ownership and port arbitration remain this kernel's adapters.
|
|
6
|
+
import { WaliEpollSet } from "./epoll-ready.js";
|
|
7
|
+
import { NETWORK_FD_MIN, NETWORK_PAYLOAD_BYTES, NetworkOperation, } from "./network-protocol.js";
|
|
8
|
+
import { WaliChildProcesses } from "./child-processes.js";
|
|
9
|
+
import { WaliSignals } from "./signals.js";
|
|
10
|
+
import { pollWaliSharedPipeState, waliSharedPipeGeneration } from "./shared-pipes.js";
|
|
11
|
+
import { SharedWaliStdinReader } from "./stdin.js";
|
|
12
|
+
import { monotonicMilliseconds } from "./monotonic-clock.js";
|
|
13
|
+
import { inPrivateTreeWindow, PRIVATE_TREE_WINDOW_BASE, PRIVATE_TREE_WINDOW_SIZE } from "./private-tree.js";
|
|
14
|
+
/** A process reactor hosted by either its own worker or the kernel worker. */
|
|
15
|
+
export function createWaliNetworkSession(reply) {
|
|
16
|
+
const scope = globalThis;
|
|
17
|
+
let disposed = false;
|
|
18
|
+
const channels = new Set();
|
|
19
|
+
const EPOLL_EVENT_BYTES = 16;
|
|
20
|
+
const EPOLL_DATA_OFFSET = 8;
|
|
21
|
+
const sockets = new Map();
|
|
22
|
+
const socketDescriptorFlags = new Map();
|
|
23
|
+
const activeReceives = new Map();
|
|
24
|
+
const epolls = new Map();
|
|
25
|
+
const epollDescriptorFlags = new Map();
|
|
26
|
+
const allowedHosts = new Set();
|
|
27
|
+
const hostByAddress = new Map();
|
|
28
|
+
const addressIdByHost = new Map();
|
|
29
|
+
let relayUrl = "/api/wali/tcp";
|
|
30
|
+
let maxConnections = 16;
|
|
31
|
+
let maxBytes = 64 * 1024 * 1024;
|
|
32
|
+
let networkBytes = 0;
|
|
33
|
+
const externalConnections = new Set();
|
|
34
|
+
let descriptorLimits;
|
|
35
|
+
const descriptorLimit = () => descriptorLimits ? Atomics.load(descriptorLimits, 0) : 65_536;
|
|
36
|
+
/** The next descriptor of this worker's, never one of a private tree's window (ADR-0047). */
|
|
37
|
+
function takeFd(minimum = NETWORK_FD_MIN) {
|
|
38
|
+
for (let fd = Math.max(minimum, NETWORK_FD_MIN); fd < descriptorLimit(); fd++) {
|
|
39
|
+
if (inPrivateTreeWindow(fd)) {
|
|
40
|
+
fd = PRIVATE_TREE_WINDOW_BASE + PRIVATE_TREE_WINDOW_SIZE - 1;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (!sockets.has(fd) && !epolls.has(fd) && !children.owns(fd))
|
|
44
|
+
return fd;
|
|
45
|
+
}
|
|
46
|
+
throw Object.assign(new Error("EMFILE"), { code: "EMFILE" });
|
|
47
|
+
}
|
|
48
|
+
const descriptorWaiters = new Set();
|
|
49
|
+
let signals;
|
|
50
|
+
let activity;
|
|
51
|
+
function notifyDescriptors() { for (const resolve of [...descriptorWaiters])
|
|
52
|
+
resolve(); }
|
|
53
|
+
const children = new WaliChildProcesses((minimum = NETWORK_FD_MIN) => takeFd(minimum), (pid, status, code) => { signals?.raise(17, pid, status, code, "masked"); });
|
|
54
|
+
children.onChange(notifyDescriptors);
|
|
55
|
+
let sharedPipes;
|
|
56
|
+
let stdin;
|
|
57
|
+
let terminalStdin;
|
|
58
|
+
let stdioAliasRegistry;
|
|
59
|
+
let loopbackPort;
|
|
60
|
+
let nextLoopbackRequestId = 1;
|
|
61
|
+
let nextOutboundConnectionId = -1;
|
|
62
|
+
const loopbackAcks = new Map();
|
|
63
|
+
const listenersByPort = new Map();
|
|
64
|
+
const loopbackConnections = new Map();
|
|
65
|
+
function receive(event) {
|
|
66
|
+
if (event.data.kind === "dispose") {
|
|
67
|
+
if (disposed)
|
|
68
|
+
return;
|
|
69
|
+
disposed = true;
|
|
70
|
+
// No old dispatch may write into a buffer after this acknowledgement.
|
|
71
|
+
for (const port of channels) {
|
|
72
|
+
port.onmessage = null;
|
|
73
|
+
port.close();
|
|
74
|
+
}
|
|
75
|
+
channels.clear();
|
|
76
|
+
for (const socket of new Set([...sockets.values(), ...activeReceives.keys()]))
|
|
77
|
+
closeSocket(socket);
|
|
78
|
+
sockets.clear();
|
|
79
|
+
socketDescriptorFlags.clear();
|
|
80
|
+
epolls.clear();
|
|
81
|
+
epollDescriptorFlags.clear();
|
|
82
|
+
for (const resolve of loopbackAcks.values())
|
|
83
|
+
resolve(-9);
|
|
84
|
+
loopbackAcks.clear();
|
|
85
|
+
children.dispose();
|
|
86
|
+
notifyDescriptors();
|
|
87
|
+
if (signals)
|
|
88
|
+
Atomics.notify(signals.words, 1);
|
|
89
|
+
loopbackPort?.close();
|
|
90
|
+
reply({ kind: "disposed" });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (disposed)
|
|
94
|
+
return;
|
|
95
|
+
if (event.data.kind === "initialize") {
|
|
96
|
+
descriptorLimits = event.data.descriptorLimits ? new Uint32Array(event.data.descriptorLimits) : undefined;
|
|
97
|
+
activity = event.data.activity ? new Int32Array(event.data.activity) : undefined;
|
|
98
|
+
allowedHosts.clear();
|
|
99
|
+
for (const host of event.data.allowedHosts)
|
|
100
|
+
allowedHosts.add(host);
|
|
101
|
+
relayUrl = event.data.relayUrl;
|
|
102
|
+
maxConnections = event.data.maxConnections;
|
|
103
|
+
maxBytes = event.data.maxBytes;
|
|
104
|
+
networkBytes = 0;
|
|
105
|
+
externalConnections.clear();
|
|
106
|
+
sharedPipes = event.data.sharedPipes;
|
|
107
|
+
stdin = event.data.stdin ? new SharedWaliStdinReader(event.data.stdin) : undefined;
|
|
108
|
+
terminalStdin = event.data.terminalStdin ? new SharedWaliStdinReader(event.data.terminalStdin) : undefined;
|
|
109
|
+
stdioAliasRegistry = event.data.stdioAliasRegistry
|
|
110
|
+
? new Int32Array(event.data.stdioAliasRegistry)
|
|
111
|
+
: undefined;
|
|
112
|
+
if (event.data.signals) {
|
|
113
|
+
signals = new WaliSignals(event.data.signals);
|
|
114
|
+
const shared = signals.words;
|
|
115
|
+
const waitAsync = Atomics.waitAsync;
|
|
116
|
+
if (!waitAsync) {
|
|
117
|
+
reply({ kind: "error", message: "WALI signal delivery requires Atomics.waitAsync." });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
void (async () => {
|
|
121
|
+
while (!disposed) {
|
|
122
|
+
const generation = Atomics.load(shared, 1);
|
|
123
|
+
// wakes blocked polls and child waits, and so notifies descriptors
|
|
124
|
+
children.wake();
|
|
125
|
+
await waitAsync(shared, 1, generation).value;
|
|
126
|
+
}
|
|
127
|
+
})();
|
|
128
|
+
}
|
|
129
|
+
loopbackPort = event.data.loopbackPort;
|
|
130
|
+
if (loopbackPort) {
|
|
131
|
+
loopbackPort.onmessage = (message) => {
|
|
132
|
+
handleLoopbackMessage(message.data);
|
|
133
|
+
};
|
|
134
|
+
loopbackPort.start();
|
|
135
|
+
}
|
|
136
|
+
reply({ kind: "ready" });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (event.data.kind === "attach")
|
|
140
|
+
attach(event.data);
|
|
141
|
+
}
|
|
142
|
+
function attach(channel) {
|
|
143
|
+
channels.add(channel.port);
|
|
144
|
+
const control = new Int32Array(channel.control);
|
|
145
|
+
const payload = new Uint8Array(channel.payload);
|
|
146
|
+
const waitState = channel.waitState && new Int32Array(channel.waitState);
|
|
147
|
+
channel.port.onmessage = (event) => {
|
|
148
|
+
void dispatch(control, payload, typeof event.data === "number" ? event.data : event.data.requestId, typeof event.data === "number" ? undefined : event.data.process, waitState);
|
|
149
|
+
};
|
|
150
|
+
channel.port.start();
|
|
151
|
+
}
|
|
152
|
+
async function dispatch(control, payload, requestId, process, waitState) {
|
|
153
|
+
if (disposed || Atomics.load(control, 0) !== requestId)
|
|
154
|
+
return;
|
|
155
|
+
// Reaping and receive consumption are irreversible. Claim the request
|
|
156
|
+
// before it starts, so transport cancellation cannot lose status or bytes.
|
|
157
|
+
const claimed = [NetworkOperation.ChildWait, NetworkOperation.PollMany, NetworkOperation.EpollWait, NetworkOperation.Receive].includes(Atomics.load(control, 1));
|
|
158
|
+
if (claimed && Atomics.compareExchange(control, 0, requestId, -requestId) !== requestId)
|
|
159
|
+
return;
|
|
160
|
+
let result = -38;
|
|
161
|
+
let output = new Uint8Array();
|
|
162
|
+
let checkedActivity = activity ? Atomics.load(activity, 0) : 0;
|
|
163
|
+
const parked = (state) => {
|
|
164
|
+
if (disposed || !waitState)
|
|
165
|
+
return;
|
|
166
|
+
if (state === 0)
|
|
167
|
+
checkedActivity = activity ? Atomics.load(activity, 0) : 0;
|
|
168
|
+
Atomics.store(waitState, 1, checkedActivity);
|
|
169
|
+
Atomics.store(waitState, 0, state);
|
|
170
|
+
Atomics.notify(control, 0);
|
|
171
|
+
};
|
|
172
|
+
parked(0);
|
|
173
|
+
try {
|
|
174
|
+
const operation = Atomics.load(control, 1);
|
|
175
|
+
const inputLength = Atomics.load(control, 3);
|
|
176
|
+
const args = Array.from({ length: 10 }, (_, index) => Atomics.load(control, 5 + index));
|
|
177
|
+
const input = payload.slice(0, inputLength);
|
|
178
|
+
({ result, output } = await operate(operation, args, input, process, parked));
|
|
179
|
+
if ([NetworkOperation.EpollControl, NetworkOperation.Send, NetworkOperation.Receive,
|
|
180
|
+
NetworkOperation.Close, NetworkOperation.Shutdown, NetworkOperation.TimerFdSetTime].includes(operation))
|
|
181
|
+
notifyDescriptors();
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
result = error?.code === "EMFILE" ? -24 : -5;
|
|
185
|
+
}
|
|
186
|
+
if (disposed)
|
|
187
|
+
return;
|
|
188
|
+
parked(0);
|
|
189
|
+
if (output.byteLength > payload.byteLength) {
|
|
190
|
+
result = -90;
|
|
191
|
+
output = new Uint8Array();
|
|
192
|
+
}
|
|
193
|
+
// Claim this generation before touching the shared response buffers. A
|
|
194
|
+
// watchdog may have cancelled it and started a later request while operate()
|
|
195
|
+
// was pending; stale completions must never corrupt that request.
|
|
196
|
+
if (!claimed && Atomics.compareExchange(control, 0, requestId, -requestId) !== requestId)
|
|
197
|
+
return;
|
|
198
|
+
if (output.byteLength > 0)
|
|
199
|
+
payload.set(output);
|
|
200
|
+
Atomics.store(control, 2, result);
|
|
201
|
+
Atomics.store(control, 4, output.byteLength);
|
|
202
|
+
Atomics.store(control, 0, requestId + 1);
|
|
203
|
+
Atomics.notify(control, 0);
|
|
204
|
+
}
|
|
205
|
+
async function operate(operation, args, input, process, parked = () => { }) {
|
|
206
|
+
if (operation === NetworkOperation.ChildRegister)
|
|
207
|
+
return scalar(process ? children.register(args[0], process) : -22);
|
|
208
|
+
if (operation === NetworkOperation.ChildForget)
|
|
209
|
+
return scalar(children.forget(args[0]));
|
|
210
|
+
if (operation === NetworkOperation.ChildExists)
|
|
211
|
+
return scalar(children.has(args[0]) ? 1 : 0);
|
|
212
|
+
if (operation === NetworkOperation.ChildWait) {
|
|
213
|
+
const mask = signalMask(args[2] ?? 0, args[3] ?? 0);
|
|
214
|
+
return scalar(process ? await children.wait(args[0], args[1], process, () => disposed || (!!signals && (signals.pending() & ~mask) !== 0n), value => parked(value ? 1 : 0)) : -22);
|
|
215
|
+
}
|
|
216
|
+
if (operation === NetworkOperation.PidFdOpen)
|
|
217
|
+
return scalar(children.open(args[0], args[1]));
|
|
218
|
+
if (operation === NetworkOperation.PollMany)
|
|
219
|
+
return pollMany(input, args[0], signalMask(args[1], args[2]), parked);
|
|
220
|
+
if (operation === NetworkOperation.Socket)
|
|
221
|
+
return scalar(createSocket(args[0], args[1], args[2]));
|
|
222
|
+
if (operation === NetworkOperation.EpollCreate)
|
|
223
|
+
return scalar(createEpoll(args[0] ?? 0));
|
|
224
|
+
if (operation === NetworkOperation.EventFd)
|
|
225
|
+
return scalar(createEventFd(args[0], args[1]));
|
|
226
|
+
if (operation === NetworkOperation.TimerFdCreate)
|
|
227
|
+
return scalar(createTimerFd(args[0], args[1]));
|
|
228
|
+
if (operation === NetworkOperation.SocketPair)
|
|
229
|
+
return createSocketPair(args[0], args[1], args[2]);
|
|
230
|
+
if (operation === NetworkOperation.EpollControl)
|
|
231
|
+
return scalar(epollControl(args[0], args[1], args[2], input));
|
|
232
|
+
if (operation === NetworkOperation.EpollWait)
|
|
233
|
+
return epollWait(args[0], args[1], args[2], signalMask(args[3], args[4]), parked);
|
|
234
|
+
const fd = args[0];
|
|
235
|
+
if (children.owns(fd)) {
|
|
236
|
+
if (operation === NetworkOperation.Close)
|
|
237
|
+
return scalar(children.close(fd));
|
|
238
|
+
if (operation === NetworkOperation.Poll)
|
|
239
|
+
return scalar(children.poll(fd, args[1]));
|
|
240
|
+
if (operation === NetworkOperation.Fcntl) {
|
|
241
|
+
if ((args[1] === 0 || args[1] === 1030) && args[2] >= descriptorLimit())
|
|
242
|
+
return scalar(-22);
|
|
243
|
+
return scalar(children.fcntl(fd, args[1], args[2]));
|
|
244
|
+
}
|
|
245
|
+
if (operation === NetworkOperation.Duplicate)
|
|
246
|
+
return scalar(children.fcntl(fd, 0, 0));
|
|
247
|
+
if (operation === NetworkOperation.Ioctl) {
|
|
248
|
+
if (args[1] === 0x5451 || args[1] === 0x5450)
|
|
249
|
+
return scalar(children.fcntl(fd, 2, args[1] === 0x5451 ? 1 : 0));
|
|
250
|
+
return scalar(-25);
|
|
251
|
+
}
|
|
252
|
+
return scalar(-22);
|
|
253
|
+
}
|
|
254
|
+
if (operation === NetworkOperation.Close && epolls.has(fd)) {
|
|
255
|
+
epolls.delete(fd);
|
|
256
|
+
epollDescriptorFlags.delete(fd);
|
|
257
|
+
return scalar(0);
|
|
258
|
+
}
|
|
259
|
+
if (operation === NetworkOperation.Fcntl && epolls.has(fd)) {
|
|
260
|
+
if (args[1] === 0 || args[1] === 1030) {
|
|
261
|
+
if (args[2] >= descriptorLimit())
|
|
262
|
+
return scalar(-22);
|
|
263
|
+
return scalar(duplicateEpoll(fd, args[2], args[1] === 1030));
|
|
264
|
+
}
|
|
265
|
+
if (args[1] === 1)
|
|
266
|
+
return scalar(epollDescriptorFlags.get(fd) ?? 0);
|
|
267
|
+
if (args[1] === 2) {
|
|
268
|
+
epollDescriptorFlags.set(fd, args[2] & 1);
|
|
269
|
+
return scalar(0);
|
|
270
|
+
}
|
|
271
|
+
return scalar(args[1] === 3 || args[1] === 4 ? 0 : -22);
|
|
272
|
+
}
|
|
273
|
+
if (operation === NetworkOperation.Ioctl && epolls.has(fd))
|
|
274
|
+
return scalar(ioctlDescriptor(fd, args[1]));
|
|
275
|
+
const socket = sockets.get(fd);
|
|
276
|
+
if (!socket)
|
|
277
|
+
return scalar(-9);
|
|
278
|
+
if (operation === NetworkOperation.Ioctl)
|
|
279
|
+
return scalar(ioctlSocket(fd, socket, args[1], args[2]));
|
|
280
|
+
if (operation === NetworkOperation.Connect)
|
|
281
|
+
return scalar(await connectSocket(socket, input));
|
|
282
|
+
if (operation === NetworkOperation.Bind)
|
|
283
|
+
return scalar(bindSocket(socket, input));
|
|
284
|
+
if (operation === NetworkOperation.Listen)
|
|
285
|
+
return scalar(await listenSocket(socket, args[1]));
|
|
286
|
+
if (operation === NetworkOperation.Accept)
|
|
287
|
+
return acceptSocket(socket, args[1]);
|
|
288
|
+
if (operation === NetworkOperation.Send) {
|
|
289
|
+
if ((args[1] & ~(0x40 | 0x4000)) !== 0)
|
|
290
|
+
return scalar(-95);
|
|
291
|
+
return scalar(sendSocket(socket, input));
|
|
292
|
+
}
|
|
293
|
+
if (operation === NetworkOperation.Receive) {
|
|
294
|
+
const response = await receiveSocketBlocking(socket, args[1], args[2] ?? 0, signalMask(args[3] ?? 0, args[4] ?? 0), parked);
|
|
295
|
+
if (response.result > 0 && (pollSocket(socket, 1) & 1) === 0)
|
|
296
|
+
rearmEpollReadable(fd);
|
|
297
|
+
return response;
|
|
298
|
+
}
|
|
299
|
+
if (operation === NetworkOperation.Poll) {
|
|
300
|
+
const result = pollSocket(socket, args[1]);
|
|
301
|
+
return scalar(result);
|
|
302
|
+
}
|
|
303
|
+
if (operation === NetworkOperation.TimerFdSetTime)
|
|
304
|
+
return setTimerFd(socket, args[1], input);
|
|
305
|
+
if (operation === NetworkOperation.Fcntl)
|
|
306
|
+
return scalar(fcntlSocket(fd, socket, args[1], args[2]));
|
|
307
|
+
if (operation === NetworkOperation.SocketName)
|
|
308
|
+
return socketName(socket, Boolean(args[1]));
|
|
309
|
+
if (operation === NetworkOperation.GetSocketOption)
|
|
310
|
+
return socketOption(socket, args[1], args[2]);
|
|
311
|
+
if (operation === NetworkOperation.SetSocketOption) {
|
|
312
|
+
// Never claim to apply an option that the relay transport cannot honor.
|
|
313
|
+
return scalar(socket.kind === "event" || socket.kind === "timer" ? -88 : -92);
|
|
314
|
+
}
|
|
315
|
+
if (operation === NetworkOperation.Duplicate)
|
|
316
|
+
return scalar(duplicateSocket(socket));
|
|
317
|
+
if (operation === NetworkOperation.Shutdown)
|
|
318
|
+
return scalar(shutdownSocket(socket, args[1]));
|
|
319
|
+
if (operation === NetworkOperation.Close) {
|
|
320
|
+
sockets.delete(fd);
|
|
321
|
+
socketDescriptorFlags.delete(fd);
|
|
322
|
+
if (!activeReceives.has(socket) && ![...sockets.values()].includes(socket))
|
|
323
|
+
closeSocket(socket);
|
|
324
|
+
return scalar(0);
|
|
325
|
+
}
|
|
326
|
+
return scalar(-38);
|
|
327
|
+
}
|
|
328
|
+
function createEpoll(flags = 0) {
|
|
329
|
+
if (flags & ~0x80000)
|
|
330
|
+
return -22;
|
|
331
|
+
const fd = takeFd();
|
|
332
|
+
epolls.set(fd, new WaliEpollSet());
|
|
333
|
+
epollDescriptorFlags.set(fd, flags & 0x80000 ? 1 : 0);
|
|
334
|
+
return fd;
|
|
335
|
+
}
|
|
336
|
+
function signalMask(low, high) { return BigInt(low >>> 0) | (BigInt(high >>> 0) << 32n); }
|
|
337
|
+
async function pollMany(input, timeout, mask = 0n, parked = () => { }) {
|
|
338
|
+
if (input.byteLength % 8 !== 0)
|
|
339
|
+
return scalar(-22);
|
|
340
|
+
const output = input.slice();
|
|
341
|
+
const view = new DataView(output.buffer);
|
|
342
|
+
const original = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
|
343
|
+
const deadline = timeout < 0 ? Infinity : Date.now() + timeout;
|
|
344
|
+
for (;;) {
|
|
345
|
+
if (disposed)
|
|
346
|
+
return scalar(-9);
|
|
347
|
+
if (signals && (signals.pending() & ~mask) !== 0n)
|
|
348
|
+
return scalar(-4);
|
|
349
|
+
let count = 0;
|
|
350
|
+
let processOnly = true;
|
|
351
|
+
for (let offset = 0; offset < input.byteLength; offset += 8) {
|
|
352
|
+
const fd = view.getInt32(offset, true);
|
|
353
|
+
const events = view.getInt16(offset + 4, true);
|
|
354
|
+
const socket = sockets.get(fd);
|
|
355
|
+
const ready = fd < 0 ? 0 : children.poll(fd, events)
|
|
356
|
+
?? (sharedPipes ? pollWaliSharedPipeState(sharedPipes, fd, events) : undefined)
|
|
357
|
+
?? terminalReadiness(fd, events)
|
|
358
|
+
?? (socket ? pollSocket(socket, events) : undefined)
|
|
359
|
+
?? (fd >= NETWORK_FD_MIN && !inPrivateTreeWindow(fd) ? 0x20 : original.getInt16(offset + 6, true));
|
|
360
|
+
if (fd >= 0 && !children.owns(fd))
|
|
361
|
+
processOnly = false;
|
|
362
|
+
view.setInt16(offset + 6, ready, true);
|
|
363
|
+
if (ready !== 0)
|
|
364
|
+
count++;
|
|
365
|
+
}
|
|
366
|
+
if (count || timeout === 0 || Date.now() >= deadline)
|
|
367
|
+
return { result: count, output };
|
|
368
|
+
const fds = Array.from({ length: input.byteLength / 8 }, (_, index) => view.getInt32(index * 8, true));
|
|
369
|
+
parked(fds.some(outsideWake) ? 2 : 1);
|
|
370
|
+
try {
|
|
371
|
+
await waitForDescriptorChange(processOnly ? deadline - Date.now() : 2);
|
|
372
|
+
}
|
|
373
|
+
finally {
|
|
374
|
+
parked(0);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
function waitForDescriptorChange(delay) {
|
|
379
|
+
return new Promise((resolve) => {
|
|
380
|
+
const done = () => { if (timer !== undefined)
|
|
381
|
+
clearTimeout(timer); descriptorWaiters.delete(done); resolve(); };
|
|
382
|
+
descriptorWaiters.add(done);
|
|
383
|
+
const timer = Number.isFinite(delay) ? setTimeout(done, Math.max(0, delay)) : undefined;
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
function duplicateEpoll(fd, minimum, closeOnExec = false) {
|
|
387
|
+
const registrations = epolls.get(fd);
|
|
388
|
+
if (!registrations)
|
|
389
|
+
return -9;
|
|
390
|
+
const duplicate = takeFd(minimum);
|
|
391
|
+
epolls.set(duplicate, registrations);
|
|
392
|
+
epollDescriptorFlags.set(duplicate, closeOnExec ? 1 : 0);
|
|
393
|
+
return duplicate;
|
|
394
|
+
}
|
|
395
|
+
function createEventFd(initial, flags) {
|
|
396
|
+
if (initial < 0)
|
|
397
|
+
return -22;
|
|
398
|
+
if (flags & ~0x8_0801)
|
|
399
|
+
return -22;
|
|
400
|
+
const fd = takeFd();
|
|
401
|
+
socketDescriptorFlags.set(fd, flags & 0x80000 ? 1 : 0);
|
|
402
|
+
sockets.set(fd, {
|
|
403
|
+
kind: "event",
|
|
404
|
+
counter: BigInt(initial >>> 0),
|
|
405
|
+
generation: 0,
|
|
406
|
+
semaphore: Boolean(flags & 1),
|
|
407
|
+
nonblocking: Boolean(flags & 0x800),
|
|
408
|
+
});
|
|
409
|
+
return fd;
|
|
410
|
+
}
|
|
411
|
+
function createTimerFd(clockId, flags) {
|
|
412
|
+
if (![0, 1, 7].includes(clockId) || (flags & ~0x8_0800) !== 0)
|
|
413
|
+
return -22;
|
|
414
|
+
const fd = takeFd();
|
|
415
|
+
socketDescriptorFlags.set(fd, flags & 0x80000 ? 1 : 0);
|
|
416
|
+
sockets.set(fd, {
|
|
417
|
+
kind: "timer",
|
|
418
|
+
clockId,
|
|
419
|
+
intervalNanoseconds: 0n,
|
|
420
|
+
deadlineNanoseconds: undefined,
|
|
421
|
+
expirations: 0n,
|
|
422
|
+
nonblocking: Boolean(flags & 0x800),
|
|
423
|
+
});
|
|
424
|
+
return fd;
|
|
425
|
+
}
|
|
426
|
+
function setTimerFd(socket, flags, input) {
|
|
427
|
+
if (socket.kind !== "timer")
|
|
428
|
+
return scalar(-9);
|
|
429
|
+
if ((flags & ~1) !== 0 || input.byteLength !== 32)
|
|
430
|
+
return scalar(-22);
|
|
431
|
+
const view = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
|
432
|
+
const interval = readTimespec(view, 0);
|
|
433
|
+
const value = readTimespec(view, 16);
|
|
434
|
+
if (interval === undefined || value === undefined)
|
|
435
|
+
return scalar(-22);
|
|
436
|
+
updateTimer(socket);
|
|
437
|
+
const output = new Uint8Array(32);
|
|
438
|
+
const outputView = new DataView(output.buffer);
|
|
439
|
+
writeTimespec(outputView, 0, socket.intervalNanoseconds);
|
|
440
|
+
writeTimespec(outputView, 16, timerRemaining(socket));
|
|
441
|
+
socket.intervalNanoseconds = interval;
|
|
442
|
+
socket.deadlineNanoseconds = value === 0n ? undefined
|
|
443
|
+
: (flags & 1) ? value : nowNanoseconds(socket.clockId) + value;
|
|
444
|
+
socket.expirations = 0n;
|
|
445
|
+
return { result: 0, output };
|
|
446
|
+
}
|
|
447
|
+
function createSocketPair(domain, type, protocol) {
|
|
448
|
+
if (domain !== 1)
|
|
449
|
+
return scalar(-97);
|
|
450
|
+
if (type & ~0x8_080f)
|
|
451
|
+
return scalar(-22);
|
|
452
|
+
if ((type & 0xf) !== 1 || protocol !== 0)
|
|
453
|
+
return scalar(-93);
|
|
454
|
+
const left = { kind: "local", readShutdown: false, writeShutdown: false, nonblocking: Boolean(type & 0x800), chunks: [], byteLength: 0, closed: false, peerClosed: false };
|
|
455
|
+
const right = { kind: "local", readShutdown: false, writeShutdown: false, nonblocking: Boolean(type & 0x800), chunks: [], byteLength: 0, closed: false, peerClosed: false };
|
|
456
|
+
left.peer = right;
|
|
457
|
+
right.peer = left;
|
|
458
|
+
const leftFd = takeFd();
|
|
459
|
+
const rightFd = takeFd(leftFd + 1);
|
|
460
|
+
sockets.set(leftFd, left);
|
|
461
|
+
sockets.set(rightFd, right);
|
|
462
|
+
socketDescriptorFlags.set(leftFd, type & 0x80000 ? 1 : 0);
|
|
463
|
+
socketDescriptorFlags.set(rightFd, type & 0x80000 ? 1 : 0);
|
|
464
|
+
const output = new Uint8Array(8);
|
|
465
|
+
const view = new DataView(output.buffer);
|
|
466
|
+
view.setInt32(0, leftFd, true);
|
|
467
|
+
view.setInt32(4, rightFd, true);
|
|
468
|
+
return { result: 0, output };
|
|
469
|
+
}
|
|
470
|
+
function epollControl(epollFd, operation, fd, input) {
|
|
471
|
+
const sharedPipe = sharedPipes ? pollWaliSharedPipeState(sharedPipes, fd, 0) : undefined;
|
|
472
|
+
if (!sockets.has(fd) && !children.owns(fd) && sharedPipe === undefined && terminalReadiness(fd, 0) === undefined)
|
|
473
|
+
return -9;
|
|
474
|
+
const epoll = epolls.get(epollFd);
|
|
475
|
+
if (!epoll)
|
|
476
|
+
return -9;
|
|
477
|
+
const registrations = epoll.registrations;
|
|
478
|
+
if (operation === 2) {
|
|
479
|
+
const entry = registrations.get(fd);
|
|
480
|
+
if (!entry)
|
|
481
|
+
return -2;
|
|
482
|
+
epoll.evict(entry);
|
|
483
|
+
return 0;
|
|
484
|
+
}
|
|
485
|
+
if (operation !== 1 && operation !== 3 || input.byteLength < EPOLL_EVENT_BYTES)
|
|
486
|
+
return -22;
|
|
487
|
+
if (operation === 1 && registrations.has(fd))
|
|
488
|
+
return -17;
|
|
489
|
+
if (operation === 3 && !registrations.has(fd))
|
|
490
|
+
return -2;
|
|
491
|
+
const view = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
|
492
|
+
const events = view.getUint32(0, true);
|
|
493
|
+
const socket = sockets.get(fd);
|
|
494
|
+
epoll.set({
|
|
495
|
+
fd,
|
|
496
|
+
events,
|
|
497
|
+
data: input.slice(EPOLL_DATA_OFFSET, EPOLL_EVENT_BYTES),
|
|
498
|
+
lastReady: 0,
|
|
499
|
+
lastPipeGeneration: sharedPipes ? waliSharedPipeGeneration(sharedPipes, fd) ?? 0 : 0,
|
|
500
|
+
lastStdinGeneration: descriptorInput(fd)?.generation() ?? 0,
|
|
501
|
+
lastEventGeneration: socket?.kind === "event"
|
|
502
|
+
? socket.generation
|
|
503
|
+
: 0,
|
|
504
|
+
observedReady: 0,
|
|
505
|
+
});
|
|
506
|
+
return 0;
|
|
507
|
+
}
|
|
508
|
+
async function epollWait(epollFd, maximum, timeout, mask = 0n, parked = () => { }) {
|
|
509
|
+
const epoll = epolls.get(epollFd);
|
|
510
|
+
if (!epoll)
|
|
511
|
+
return scalar(-9);
|
|
512
|
+
const registrations = epoll.registrations;
|
|
513
|
+
if (maximum <= 0 || maximum > 0x7fffffff / EPOLL_EVENT_BYTES)
|
|
514
|
+
return scalar(-22);
|
|
515
|
+
const startedAt = Date.now();
|
|
516
|
+
// This blocking RPC is claimed before dispatch, so an infinite guest wait
|
|
517
|
+
// needs no synthetic timeout to placate the request transport watchdog.
|
|
518
|
+
const deadline = timeout < 0 ? Infinity : startedAt + timeout;
|
|
519
|
+
do {
|
|
520
|
+
if (disposed)
|
|
521
|
+
return scalar(-9);
|
|
522
|
+
if (signals && (signals.pending() & ~mask) !== 0n)
|
|
523
|
+
return scalar(-4);
|
|
524
|
+
for (const [fd, registration] of registrations) {
|
|
525
|
+
const socket = sockets.get(fd);
|
|
526
|
+
const pollEvents = ((registration.events & 1) ? 1 : 0) | ((registration.events & 4) ? 4 : 0);
|
|
527
|
+
// Read the generation first so a write racing this poll is still seen
|
|
528
|
+
// on the next poll, even if readiness was already observed here.
|
|
529
|
+
const pipeGeneration = sharedPipes
|
|
530
|
+
? waliSharedPipeGeneration(sharedPipes, fd) ?? registration.lastPipeGeneration
|
|
531
|
+
: registration.lastPipeGeneration;
|
|
532
|
+
const externalReady = children.poll(fd, pollEvents)
|
|
533
|
+
?? (sharedPipes ? pollWaliSharedPipeState(sharedPipes, fd, pollEvents) : undefined);
|
|
534
|
+
const terminalReady = terminalReadiness(fd, pollEvents);
|
|
535
|
+
if (!socket && externalReady === undefined && terminalReady === undefined) {
|
|
536
|
+
epoll.evict(registration);
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
const currentReady = (terminalReady ?? externalReady ?? pollSocket(socket, pollEvents))
|
|
540
|
+
& (registration.events | 0x18);
|
|
541
|
+
const stdinGeneration = descriptorInput(fd)?.generation() ?? registration.lastStdinGeneration;
|
|
542
|
+
const edgeReady = (currentReady & ~registration.lastReady)
|
|
543
|
+
| (pipeGeneration !== registration.lastPipeGeneration ? currentReady : 0)
|
|
544
|
+
| (stdinGeneration !== registration.lastStdinGeneration ? currentReady : 0)
|
|
545
|
+
| (socket?.kind === "event" && socket.generation !== registration.lastEventGeneration
|
|
546
|
+
? currentReady
|
|
547
|
+
: 0);
|
|
548
|
+
// A browser terminal's shared byte queue behaves like a TTY, not a
|
|
549
|
+
// packet socket. Keep reporting readable until the consumer drains it;
|
|
550
|
+
// otherwise one-byte reads can strand the final key behind EPOLLET.
|
|
551
|
+
const ready = isStdinDescriptor(fd)
|
|
552
|
+
? currentReady
|
|
553
|
+
: (registration.events & 0x8000_0000) !== 0 ? edgeReady : currentReady;
|
|
554
|
+
registration.lastReady = currentReady;
|
|
555
|
+
registration.lastPipeGeneration = pipeGeneration;
|
|
556
|
+
registration.lastStdinGeneration = stdinGeneration;
|
|
557
|
+
registration.lastEventGeneration = socket?.kind === "event"
|
|
558
|
+
? socket.generation
|
|
559
|
+
: registration.lastEventGeneration;
|
|
560
|
+
registration.observedReady = currentReady;
|
|
561
|
+
if (ready !== 0)
|
|
562
|
+
epoll.notify(registration);
|
|
563
|
+
}
|
|
564
|
+
const events = epoll.take(maximum, entry => entry.observedReady).map(({ entry, ready }) => ({ ready, data: entry.data }));
|
|
565
|
+
if (events.length > 0) {
|
|
566
|
+
const output = new Uint8Array(events.length * EPOLL_EVENT_BYTES);
|
|
567
|
+
const view = new DataView(output.buffer);
|
|
568
|
+
events.forEach((event, index) => {
|
|
569
|
+
const offset = index * EPOLL_EVENT_BYTES;
|
|
570
|
+
view.setUint32(offset, event.ready, true);
|
|
571
|
+
output.set(event.data, offset + EPOLL_DATA_OFFSET);
|
|
572
|
+
});
|
|
573
|
+
return { result: events.length, output };
|
|
574
|
+
}
|
|
575
|
+
if (timeout === 0 || Date.now() >= deadline)
|
|
576
|
+
return { result: 0, output: new Uint8Array() };
|
|
577
|
+
// Other descriptor sources retain the existing shared-pipe/socket scan.
|
|
578
|
+
let childrenOnly = true;
|
|
579
|
+
for (const fd of registrations.keys())
|
|
580
|
+
if (!children.owns(fd)) {
|
|
581
|
+
childrenOnly = false;
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
parked([...registrations.keys()].some(outsideWake) ? 2 : 1);
|
|
585
|
+
try {
|
|
586
|
+
await waitForDescriptorChange(childrenOnly ? deadline - Date.now() : 2);
|
|
587
|
+
}
|
|
588
|
+
finally {
|
|
589
|
+
parked(0);
|
|
590
|
+
}
|
|
591
|
+
} while (true);
|
|
592
|
+
}
|
|
593
|
+
function outsideWake(fd) {
|
|
594
|
+
const socket = sockets.get(fd);
|
|
595
|
+
if (socket?.kind === "tcp" || socket?.kind === "dns")
|
|
596
|
+
return true;
|
|
597
|
+
if (socket?.kind === "timer" && socket.deadlineNanoseconds !== undefined)
|
|
598
|
+
return true;
|
|
599
|
+
const input = descriptorInput(fd);
|
|
600
|
+
if (input)
|
|
601
|
+
return !input.closed;
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
function descriptorStream(fd) {
|
|
605
|
+
if (fd < 0 || fd >= 65_536)
|
|
606
|
+
return 0;
|
|
607
|
+
if (sharedPipes) {
|
|
608
|
+
const id = Atomics.load(new Int32Array(sharedPipes.table), fd);
|
|
609
|
+
return Atomics.load(new Int32Array(sharedPipes.descriptions), id * 3 + 2);
|
|
610
|
+
}
|
|
611
|
+
const alias = stdioAliasRegistry && fd < stdioAliasRegistry.length ? Atomics.load(stdioAliasRegistry, fd) : fd;
|
|
612
|
+
return alias >= 0 && alias <= 2 ? alias + 1 : 0;
|
|
613
|
+
}
|
|
614
|
+
function descriptorInput(fd) {
|
|
615
|
+
const stream = descriptorStream(fd);
|
|
616
|
+
return stream === 4 ? terminalStdin : stream === 1 ? stdin : undefined;
|
|
617
|
+
}
|
|
618
|
+
function isStdinDescriptor(fd) { return descriptorInput(fd) !== undefined; }
|
|
619
|
+
function terminalReadiness(fd, events) {
|
|
620
|
+
const stream = descriptorStream(fd);
|
|
621
|
+
if (!stream)
|
|
622
|
+
return;
|
|
623
|
+
if (stream === 4 && sharedPipes) {
|
|
624
|
+
const id = Atomics.load(new Int32Array(sharedPipes.table), fd);
|
|
625
|
+
const mode = Atomics.load(new Int32Array(sharedPipes.descriptions), id * 3 + 1) & 3;
|
|
626
|
+
return ((events & 1) && mode !== 1 && terminalStdin?.poll() ? 1 : 0)
|
|
627
|
+
| ((events & 4) && mode !== 0 ? 4 : 0);
|
|
628
|
+
}
|
|
629
|
+
return stream === 1 ? ((events & 1) && stdin?.poll() ? 1 : 0) : events & 4;
|
|
630
|
+
}
|
|
631
|
+
function scalar(result) {
|
|
632
|
+
return { result, output: new Uint8Array() };
|
|
633
|
+
}
|
|
634
|
+
function createSocket(domain, type, protocol) {
|
|
635
|
+
if (type & ~0x8_080f)
|
|
636
|
+
return -22;
|
|
637
|
+
const baseType = type & 0xf;
|
|
638
|
+
if (baseType !== 1 && baseType !== 2)
|
|
639
|
+
return -22;
|
|
640
|
+
let socket;
|
|
641
|
+
if ((domain === 2 || domain === 10) && baseType === 2 && (protocol === 0 || protocol === 17)) {
|
|
642
|
+
socket = { kind: "dns", domain, responses: [], nonblocking: Boolean(type & 0x800) };
|
|
643
|
+
}
|
|
644
|
+
else if ((domain === 2 || domain === 10) && baseType === 1 && (protocol === 0 || protocol === 6)) {
|
|
645
|
+
socket = {
|
|
646
|
+
kind: "tcp",
|
|
647
|
+
domain,
|
|
648
|
+
chunks: [],
|
|
649
|
+
byteLength: 0,
|
|
650
|
+
open: false,
|
|
651
|
+
connecting: false,
|
|
652
|
+
connectGeneration: 0,
|
|
653
|
+
closed: false,
|
|
654
|
+
error: 0,
|
|
655
|
+
nonblocking: Boolean(type & 0x800),
|
|
656
|
+
listening: false,
|
|
657
|
+
backlog: 0,
|
|
658
|
+
pending: [],
|
|
659
|
+
acceptWaiters: [],
|
|
660
|
+
peerClosed: false,
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
return domain === 2 || domain === 10 ? -93 : -97;
|
|
665
|
+
}
|
|
666
|
+
const fd = takeFd();
|
|
667
|
+
sockets.set(fd, socket);
|
|
668
|
+
socketDescriptorFlags.set(fd, type & 0x80000 ? 1 : 0);
|
|
669
|
+
return fd;
|
|
670
|
+
}
|
|
671
|
+
async function connectSocket(socket, address) {
|
|
672
|
+
if (socket.kind === "dns") {
|
|
673
|
+
const target = parseSocketAddress(address);
|
|
674
|
+
if (!target)
|
|
675
|
+
return -22;
|
|
676
|
+
if (new DataView(address.buffer, address.byteOffset, address.byteLength).getUint16(0, true) !== socket.domain)
|
|
677
|
+
return -97;
|
|
678
|
+
socket.peerAddress = target;
|
|
679
|
+
return 0;
|
|
680
|
+
}
|
|
681
|
+
if (socket.kind === "event" || socket.kind === "timer")
|
|
682
|
+
return -88;
|
|
683
|
+
if (socket.kind === "local")
|
|
684
|
+
return -106;
|
|
685
|
+
const target = parseSocketAddress(address);
|
|
686
|
+
if (!target)
|
|
687
|
+
return -22;
|
|
688
|
+
if (new DataView(address.buffer, address.byteOffset, address.byteLength).getUint16(0, true) !== socket.domain)
|
|
689
|
+
return -97;
|
|
690
|
+
if (socket.connecting)
|
|
691
|
+
return -114; // EALREADY
|
|
692
|
+
if (socket.open || socket.listening)
|
|
693
|
+
return -106; // EISCONN
|
|
694
|
+
const loopback = isLoopbackConnectAddress(target.address);
|
|
695
|
+
let hostname;
|
|
696
|
+
if (loopback) {
|
|
697
|
+
if (!loopbackPort)
|
|
698
|
+
return -111;
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
if (target.port !== 443)
|
|
702
|
+
return -111;
|
|
703
|
+
hostname = hostByAddress.get(target.address);
|
|
704
|
+
if (!hostname || !allowedHosts.has(hostname))
|
|
705
|
+
return -13;
|
|
706
|
+
if (externalConnections.size >= maxConnections)
|
|
707
|
+
return -24;
|
|
708
|
+
}
|
|
709
|
+
// SOCKFS's connecting state, with Linux EINPROGRESS instead of its
|
|
710
|
+
// optimistic success. Every callback belongs to exactly one attempt.
|
|
711
|
+
socket.connecting = true;
|
|
712
|
+
socket.closed = false;
|
|
713
|
+
socket.peerClosed = false;
|
|
714
|
+
socket.error = 0;
|
|
715
|
+
const generation = ++socket.connectGeneration;
|
|
716
|
+
const completion = new Promise((resolve) => {
|
|
717
|
+
let settled = false;
|
|
718
|
+
let timeout;
|
|
719
|
+
const current = () => !disposed && socket.connectGeneration === generation;
|
|
720
|
+
const settle = (value) => {
|
|
721
|
+
if (settled)
|
|
722
|
+
return;
|
|
723
|
+
settled = true;
|
|
724
|
+
if (timeout !== undefined)
|
|
725
|
+
clearTimeout(timeout);
|
|
726
|
+
delete socket.cancelConnect;
|
|
727
|
+
if (current()) {
|
|
728
|
+
socket.connecting = false;
|
|
729
|
+
socket.open = value === 0;
|
|
730
|
+
socket.closed = value < 0;
|
|
731
|
+
socket.error = value < 0 ? -value : 0;
|
|
732
|
+
if (value === 0)
|
|
733
|
+
socket.peerAddress = target;
|
|
734
|
+
else {
|
|
735
|
+
// A timeout/error must not be undone by a later open callback.
|
|
736
|
+
socket.connectGeneration += 1;
|
|
737
|
+
externalConnections.delete(socket);
|
|
738
|
+
if (socket.loopbackConnectionId !== undefined) {
|
|
739
|
+
loopbackConnections.delete(socket.loopbackConnectionId);
|
|
740
|
+
postLoopback({ type: "closed", connectionId: socket.loopbackConnectionId });
|
|
741
|
+
delete socket.loopbackConnectionId;
|
|
742
|
+
}
|
|
743
|
+
try {
|
|
744
|
+
socket.websocket?.close();
|
|
745
|
+
}
|
|
746
|
+
catch { }
|
|
747
|
+
}
|
|
748
|
+
notifyDescriptors();
|
|
749
|
+
}
|
|
750
|
+
resolve(value);
|
|
751
|
+
};
|
|
752
|
+
socket.cancelConnect = () => settle(-9);
|
|
753
|
+
if (loopback) {
|
|
754
|
+
const connectionId = nextOutboundConnectionId--;
|
|
755
|
+
socket.loopbackConnectionId = connectionId;
|
|
756
|
+
socket.peerClosed = false;
|
|
757
|
+
// Register before requesting: ingress data/close can accompany the ack.
|
|
758
|
+
loopbackConnections.set(connectionId, socket);
|
|
759
|
+
void requestLoopbackConnect(target.port, connectionId).then((result) => {
|
|
760
|
+
if (!current()) {
|
|
761
|
+
postLoopback({ type: "closed", connectionId });
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
if (result === 0)
|
|
765
|
+
socket.bound ??= { address: socket.domain === 10 ? "::1" : "127.0.0.1", port: 0 };
|
|
766
|
+
settle(result);
|
|
767
|
+
}, () => settle(-5));
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
externalConnections.add(socket);
|
|
771
|
+
try {
|
|
772
|
+
const url = new URL(relayUrl, scope.location.href);
|
|
773
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
774
|
+
url.search = "";
|
|
775
|
+
url.searchParams.append("host", hostname);
|
|
776
|
+
url.searchParams.append("port", "443");
|
|
777
|
+
const websocket = new WebSocket(url);
|
|
778
|
+
socket.websocket = websocket;
|
|
779
|
+
websocket.binaryType = "arraybuffer";
|
|
780
|
+
timeout = setTimeout(() => settle(-110), 15_000);
|
|
781
|
+
websocket.addEventListener("open", () => {
|
|
782
|
+
if (!current()) {
|
|
783
|
+
try {
|
|
784
|
+
websocket.close();
|
|
785
|
+
}
|
|
786
|
+
catch { }
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
settle(0);
|
|
790
|
+
});
|
|
791
|
+
websocket.addEventListener("message", (event) => {
|
|
792
|
+
if (!current())
|
|
793
|
+
return;
|
|
794
|
+
if (!(event.data instanceof ArrayBuffer)) {
|
|
795
|
+
socket.error = 5;
|
|
796
|
+
notifyDescriptors();
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
const chunk = new Uint8Array(event.data);
|
|
800
|
+
if (!consumeNetworkBytes(chunk.byteLength)) {
|
|
801
|
+
closeSocket(socket);
|
|
802
|
+
socket.error = 122;
|
|
803
|
+
notifyDescriptors();
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
socket.chunks.push(chunk);
|
|
807
|
+
socket.byteLength += chunk.byteLength;
|
|
808
|
+
notifyDescriptors();
|
|
809
|
+
});
|
|
810
|
+
websocket.addEventListener("close", () => {
|
|
811
|
+
if (!current())
|
|
812
|
+
return;
|
|
813
|
+
externalConnections.delete(socket);
|
|
814
|
+
if (!settled)
|
|
815
|
+
settle(-111);
|
|
816
|
+
else {
|
|
817
|
+
socket.closed = true;
|
|
818
|
+
socket.open = false;
|
|
819
|
+
notifyDescriptors();
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
websocket.addEventListener("error", () => {
|
|
823
|
+
if (!current())
|
|
824
|
+
return;
|
|
825
|
+
if (!settled)
|
|
826
|
+
settle(-111);
|
|
827
|
+
else {
|
|
828
|
+
socket.error = 111;
|
|
829
|
+
notifyDescriptors();
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
catch {
|
|
834
|
+
settle(-5);
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
return socket.nonblocking ? -115 : completion;
|
|
838
|
+
}
|
|
839
|
+
function bindSocket(socket, address) {
|
|
840
|
+
if (socket.kind === "dns") {
|
|
841
|
+
if (socket.bound)
|
|
842
|
+
return -22;
|
|
843
|
+
const target = parseSocketAddress(address);
|
|
844
|
+
if (!target || !isLoopbackBindAddress(target.address))
|
|
845
|
+
return target ? -99 : -22;
|
|
846
|
+
socket.bound = target;
|
|
847
|
+
return 0;
|
|
848
|
+
}
|
|
849
|
+
if (socket.kind !== "tcp")
|
|
850
|
+
return -88;
|
|
851
|
+
if (socket.open || socket.connecting || socket.bound)
|
|
852
|
+
return -22;
|
|
853
|
+
const target = parseSocketAddress(address);
|
|
854
|
+
if (!target || !isLoopbackBindAddress(target.address))
|
|
855
|
+
return target ? -99 : -22;
|
|
856
|
+
const port = target.port === 0 ? allocateLoopbackPort() : target.port;
|
|
857
|
+
if (port <= 0 || listenersByPort.has(port))
|
|
858
|
+
return -98;
|
|
859
|
+
socket.bound = { address: target.address, port, ...(target.port === 0 ? { ephemeral: true } : {}) };
|
|
860
|
+
return 0;
|
|
861
|
+
}
|
|
862
|
+
async function listenSocket(socket, backlog) {
|
|
863
|
+
if (socket.kind !== "tcp")
|
|
864
|
+
return -95;
|
|
865
|
+
if (socket.connecting || (socket.open && !socket.listening))
|
|
866
|
+
return -22;
|
|
867
|
+
if (!socket.bound)
|
|
868
|
+
return -22;
|
|
869
|
+
if (socket.listening)
|
|
870
|
+
return 0;
|
|
871
|
+
if (!loopbackPort)
|
|
872
|
+
return -97;
|
|
873
|
+
const existing = listenersByPort.get(socket.bound.port);
|
|
874
|
+
if (existing && existing !== socket)
|
|
875
|
+
return -98;
|
|
876
|
+
let result = await requestLoopbackListen(socket.bound.port);
|
|
877
|
+
// This worker sees its own listeners, the project's port table every
|
|
878
|
+
// server's: a port it chose for port 0 that another server holds is
|
|
879
|
+
// passed over for the next free one, as the kernel would never have chosen it.
|
|
880
|
+
while (result === -98 && socket.bound.ephemeral) {
|
|
881
|
+
const next = allocateLoopbackPort(socket.bound.port + 1);
|
|
882
|
+
if (next <= 0)
|
|
883
|
+
break;
|
|
884
|
+
socket.bound = { ...socket.bound, port: next };
|
|
885
|
+
result = await requestLoopbackListen(next);
|
|
886
|
+
}
|
|
887
|
+
if (result < 0)
|
|
888
|
+
return result;
|
|
889
|
+
socket.listening = true;
|
|
890
|
+
socket.open = true;
|
|
891
|
+
socket.backlog = Math.max(1, Math.min(128, backlog));
|
|
892
|
+
listenersByPort.set(socket.bound.port, socket);
|
|
893
|
+
return 0;
|
|
894
|
+
}
|
|
895
|
+
async function acceptSocket(socket, flags) {
|
|
896
|
+
if (socket.kind !== "tcp" || !socket.listening)
|
|
897
|
+
return scalar(-22);
|
|
898
|
+
if ((flags & ~0x8_0800) !== 0)
|
|
899
|
+
return scalar(-22);
|
|
900
|
+
let accepted = socket.pending.shift();
|
|
901
|
+
if (!accepted && socket.nonblocking)
|
|
902
|
+
return scalar(-11);
|
|
903
|
+
if (!accepted) {
|
|
904
|
+
accepted = await new Promise((resolve) => socket.acceptWaiters.push(resolve));
|
|
905
|
+
}
|
|
906
|
+
if (!accepted)
|
|
907
|
+
return scalar(-9);
|
|
908
|
+
accepted.nonblocking = Boolean(flags & 0x800);
|
|
909
|
+
let fd;
|
|
910
|
+
try {
|
|
911
|
+
fd = takeFd();
|
|
912
|
+
}
|
|
913
|
+
catch (error) {
|
|
914
|
+
socket.pending.unshift(accepted);
|
|
915
|
+
throw error;
|
|
916
|
+
}
|
|
917
|
+
sockets.set(fd, accepted);
|
|
918
|
+
socketDescriptorFlags.set(fd, flags & 0x80000 ? 1 : 0);
|
|
919
|
+
// The ingress wire does not yet carry the remote endpoint. Do not invent it.
|
|
920
|
+
return { result: fd, output: socketAddress(accepted.domain, accepted.domain === 10 ? "::" : "0.0.0.0", 0) };
|
|
921
|
+
}
|
|
922
|
+
function requestLoopbackListen(port) {
|
|
923
|
+
if (!loopbackPort)
|
|
924
|
+
return Promise.resolve(-97);
|
|
925
|
+
const requestId = nextLoopbackRequestId++;
|
|
926
|
+
return new Promise((resolve) => {
|
|
927
|
+
const timeout = setTimeout(() => {
|
|
928
|
+
if (!loopbackAcks.delete(requestId))
|
|
929
|
+
return;
|
|
930
|
+
resolve(-110);
|
|
931
|
+
}, 5_000);
|
|
932
|
+
loopbackAcks.set(requestId, (result) => {
|
|
933
|
+
clearTimeout(timeout);
|
|
934
|
+
resolve(result);
|
|
935
|
+
});
|
|
936
|
+
postLoopback({ type: "listen", requestId, port });
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
function requestLoopbackConnect(port, connectionId) {
|
|
940
|
+
if (!loopbackPort)
|
|
941
|
+
return Promise.resolve(-111);
|
|
942
|
+
const requestId = nextLoopbackRequestId++;
|
|
943
|
+
return new Promise((resolve) => {
|
|
944
|
+
const timeout = setTimeout(() => {
|
|
945
|
+
if (!loopbackAcks.delete(requestId))
|
|
946
|
+
return;
|
|
947
|
+
resolve(-110);
|
|
948
|
+
}, 5_000);
|
|
949
|
+
loopbackAcks.set(requestId, (result) => {
|
|
950
|
+
clearTimeout(timeout);
|
|
951
|
+
resolve(result);
|
|
952
|
+
});
|
|
953
|
+
postLoopback({ type: "connect", requestId, connectionId, port });
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
function handleLoopbackMessage(message) {
|
|
957
|
+
if (message.type === "ack") {
|
|
958
|
+
const resolve = loopbackAcks.get(message.requestId);
|
|
959
|
+
if (!resolve)
|
|
960
|
+
return;
|
|
961
|
+
loopbackAcks.delete(message.requestId);
|
|
962
|
+
resolve(message.result);
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
if (message.type === "peer-close") {
|
|
966
|
+
const socket = loopbackConnections.get(message.connectionId);
|
|
967
|
+
if (socket)
|
|
968
|
+
socket.peerClosed = true;
|
|
969
|
+
notifyDescriptors();
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
if (message.type === "data") {
|
|
973
|
+
const socket = loopbackConnections.get(message.connectionId);
|
|
974
|
+
if (!socket || socket.closed)
|
|
975
|
+
return;
|
|
976
|
+
const bytes = message.bytes.slice();
|
|
977
|
+
socket.chunks.push(bytes);
|
|
978
|
+
socket.byteLength += bytes.byteLength;
|
|
979
|
+
notifyDescriptors();
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
const listener = listenersByPort.get(message.port);
|
|
983
|
+
if (!listener || !listener.listening || listener.closed
|
|
984
|
+
|| listener.pending.length >= listener.backlog) {
|
|
985
|
+
postLoopback({ type: "closed", connectionId: message.connectionId });
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
const accepted = {
|
|
989
|
+
kind: "tcp",
|
|
990
|
+
domain: listener.domain,
|
|
991
|
+
chunks: [message.bytes.slice()],
|
|
992
|
+
byteLength: message.bytes.byteLength,
|
|
993
|
+
open: true,
|
|
994
|
+
connecting: false,
|
|
995
|
+
connectGeneration: 0,
|
|
996
|
+
closed: false,
|
|
997
|
+
error: 0,
|
|
998
|
+
nonblocking: false,
|
|
999
|
+
listening: false,
|
|
1000
|
+
backlog: 0,
|
|
1001
|
+
pending: [],
|
|
1002
|
+
acceptWaiters: [],
|
|
1003
|
+
loopbackConnectionId: message.connectionId,
|
|
1004
|
+
peerClosed: false,
|
|
1005
|
+
...(listener.bound ? { bound: listener.bound } : {}),
|
|
1006
|
+
};
|
|
1007
|
+
loopbackConnections.set(message.connectionId, accepted);
|
|
1008
|
+
const waiter = listener.acceptWaiters.shift();
|
|
1009
|
+
if (waiter)
|
|
1010
|
+
waiter(accepted);
|
|
1011
|
+
else
|
|
1012
|
+
listener.pending.push(accepted);
|
|
1013
|
+
notifyDescriptors();
|
|
1014
|
+
}
|
|
1015
|
+
function postLoopback(message) {
|
|
1016
|
+
loopbackPort?.postMessage(message);
|
|
1017
|
+
}
|
|
1018
|
+
function isLoopbackBindAddress(address) {
|
|
1019
|
+
return address === "0.0.0.0" || address === "127.0.0.1" || address === "::" || address === "::1";
|
|
1020
|
+
}
|
|
1021
|
+
function isLoopbackConnectAddress(address) {
|
|
1022
|
+
return address === "127.0.0.1" || address === "::1";
|
|
1023
|
+
}
|
|
1024
|
+
function allocateLoopbackPort(from = 49_152) {
|
|
1025
|
+
for (let port = from; port <= 65_535; port += 1) {
|
|
1026
|
+
if (!listenersByPort.has(port))
|
|
1027
|
+
return port;
|
|
1028
|
+
}
|
|
1029
|
+
return -1;
|
|
1030
|
+
}
|
|
1031
|
+
function sendSocket(socket, input) {
|
|
1032
|
+
if (socket.kind === "event") {
|
|
1033
|
+
if (input.byteLength < 8)
|
|
1034
|
+
return -22;
|
|
1035
|
+
const increment = new DataView(input.buffer, input.byteOffset, input.byteLength).getBigUint64(0, true);
|
|
1036
|
+
if (increment === 0xffffffffffffffffn)
|
|
1037
|
+
return -22;
|
|
1038
|
+
socket.counter += increment;
|
|
1039
|
+
socket.generation = socket.generation >= 0x7fff_ffff ? 1 : socket.generation + 1;
|
|
1040
|
+
return 8;
|
|
1041
|
+
}
|
|
1042
|
+
if (socket.kind === "local") {
|
|
1043
|
+
if (socket.closed)
|
|
1044
|
+
return -9;
|
|
1045
|
+
if (socket.writeShutdown || !socket.peer || socket.peer.closed || socket.peer.readShutdown)
|
|
1046
|
+
return -32;
|
|
1047
|
+
const copy = input.slice();
|
|
1048
|
+
socket.peer.chunks.push(copy);
|
|
1049
|
+
socket.peer.byteLength += copy.byteLength;
|
|
1050
|
+
return copy.byteLength;
|
|
1051
|
+
}
|
|
1052
|
+
if (socket.kind === "timer")
|
|
1053
|
+
return -22;
|
|
1054
|
+
if (socket.kind === "dns") {
|
|
1055
|
+
const response = dnsResponse(input);
|
|
1056
|
+
if (!response)
|
|
1057
|
+
return -13;
|
|
1058
|
+
socket.responses.push(response);
|
|
1059
|
+
return input.byteLength;
|
|
1060
|
+
}
|
|
1061
|
+
if (socket.connecting)
|
|
1062
|
+
return -11;
|
|
1063
|
+
if (socket.loopbackConnectionId !== undefined) {
|
|
1064
|
+
if (socket.closed || socket.peerClosed)
|
|
1065
|
+
return -32;
|
|
1066
|
+
postLoopback({ type: "outgoing", connectionId: socket.loopbackConnectionId, bytes: input.slice() });
|
|
1067
|
+
return input.byteLength;
|
|
1068
|
+
}
|
|
1069
|
+
if (socket.error) {
|
|
1070
|
+
const error = socket.error;
|
|
1071
|
+
socket.error = 0;
|
|
1072
|
+
return -error;
|
|
1073
|
+
}
|
|
1074
|
+
if (!socket.open || !socket.websocket)
|
|
1075
|
+
return -32;
|
|
1076
|
+
if (!consumeNetworkBytes(input.byteLength)) {
|
|
1077
|
+
closeSocket(socket);
|
|
1078
|
+
return -122;
|
|
1079
|
+
}
|
|
1080
|
+
try {
|
|
1081
|
+
socket.websocket.send(input);
|
|
1082
|
+
}
|
|
1083
|
+
catch {
|
|
1084
|
+
return -22;
|
|
1085
|
+
}
|
|
1086
|
+
return input.byteLength;
|
|
1087
|
+
}
|
|
1088
|
+
/** A readiness wait, not a polling loop or a transport watchdog. */
|
|
1089
|
+
async function receiveSocketBlocking(socket, maximum, flags, mask, parked) {
|
|
1090
|
+
if (maximum < 0)
|
|
1091
|
+
return scalar(-22);
|
|
1092
|
+
// Receive only supported flags; no false claim of WAITALL/ancillary/OOB.
|
|
1093
|
+
if (flags & ~(2 | 0x40))
|
|
1094
|
+
return scalar(-95);
|
|
1095
|
+
// Linux I/O pins the open description, even if another thread closes
|
|
1096
|
+
// the descriptor while the call is waiting. Close transport only at last use.
|
|
1097
|
+
activeReceives.set(socket, (activeReceives.get(socket) ?? 0) + 1);
|
|
1098
|
+
try {
|
|
1099
|
+
for (;;) {
|
|
1100
|
+
if (disposed)
|
|
1101
|
+
return scalar(-9);
|
|
1102
|
+
// A stream may return fewer bytes than requested. Bound consumption to
|
|
1103
|
+
// this channel's output capacity so a large read cannot lose queued bytes.
|
|
1104
|
+
const response = receiveSocket(socket, Math.min(maximum, NETWORK_PAYLOAD_BYTES), flags);
|
|
1105
|
+
if (response.result !== -11 || socket.nonblocking || Boolean(flags & 0x40))
|
|
1106
|
+
return response;
|
|
1107
|
+
if (signals && (signals.pending() & ~mask) !== 0n)
|
|
1108
|
+
return scalar(-4);
|
|
1109
|
+
let delay = Infinity;
|
|
1110
|
+
if (socket.kind === "timer" && socket.deadlineNanoseconds !== undefined) {
|
|
1111
|
+
delay = Number(timerRemaining(socket)) / 1_000_000;
|
|
1112
|
+
}
|
|
1113
|
+
parked(socket.kind === "tcp" || socket.kind === "dns"
|
|
1114
|
+
|| (socket.kind === "timer" && socket.deadlineNanoseconds !== undefined) ? 2 : 1);
|
|
1115
|
+
// The owner installs the wake before yielding; events arriving while the
|
|
1116
|
+
// request is being serviced cannot slip between the scan and subscription.
|
|
1117
|
+
try {
|
|
1118
|
+
await waitForDescriptorChange(delay);
|
|
1119
|
+
}
|
|
1120
|
+
finally {
|
|
1121
|
+
parked(0);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
finally {
|
|
1126
|
+
const remaining = activeReceives.get(socket) - 1;
|
|
1127
|
+
if (remaining > 0)
|
|
1128
|
+
activeReceives.set(socket, remaining);
|
|
1129
|
+
else {
|
|
1130
|
+
activeReceives.delete(socket);
|
|
1131
|
+
if (![...sockets.values()].includes(socket)) {
|
|
1132
|
+
closeSocket(socket);
|
|
1133
|
+
notifyDescriptors();
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
function receiveSocket(socket, maximum, flags = 0) {
|
|
1139
|
+
if (socket.kind === "event") {
|
|
1140
|
+
if (maximum < 8)
|
|
1141
|
+
return { result: -22, output: new Uint8Array() };
|
|
1142
|
+
if (socket.counter === 0n)
|
|
1143
|
+
return { result: socket.nonblocking ? -11 : -11, output: new Uint8Array() };
|
|
1144
|
+
const value = socket.semaphore ? 1n : socket.counter;
|
|
1145
|
+
socket.counter -= value;
|
|
1146
|
+
const output = new Uint8Array(8);
|
|
1147
|
+
new DataView(output.buffer).setBigUint64(0, value, true);
|
|
1148
|
+
return { result: 8, output };
|
|
1149
|
+
}
|
|
1150
|
+
if (socket.kind === "local") {
|
|
1151
|
+
if (socket.closed)
|
|
1152
|
+
return { result: -9, output: new Uint8Array() };
|
|
1153
|
+
if (maximum === 0)
|
|
1154
|
+
return scalar(0);
|
|
1155
|
+
// Already queued bytes precede EOF, including after local SHUT_RD.
|
|
1156
|
+
if (socket.byteLength === 0)
|
|
1157
|
+
return { result: socket.readShutdown ? 0 : -11, output: new Uint8Array() };
|
|
1158
|
+
return drainChunks(socket, maximum, Boolean(flags & 2));
|
|
1159
|
+
}
|
|
1160
|
+
if (socket.kind === "timer") {
|
|
1161
|
+
if (maximum < 8)
|
|
1162
|
+
return { result: -22, output: new Uint8Array() };
|
|
1163
|
+
updateTimer(socket);
|
|
1164
|
+
if (socket.expirations === 0n)
|
|
1165
|
+
return { result: -11, output: new Uint8Array() };
|
|
1166
|
+
const output = new Uint8Array(8);
|
|
1167
|
+
new DataView(output.buffer).setBigUint64(0, socket.expirations, true);
|
|
1168
|
+
socket.expirations = 0n;
|
|
1169
|
+
return { result: 8, output };
|
|
1170
|
+
}
|
|
1171
|
+
if (socket.kind === "dns") {
|
|
1172
|
+
const response = flags & 2 ? socket.responses[0] : socket.responses.shift();
|
|
1173
|
+
if (!response)
|
|
1174
|
+
return { result: -11, output: new Uint8Array() };
|
|
1175
|
+
const output = response.subarray(0, maximum);
|
|
1176
|
+
return { result: output.byteLength, output };
|
|
1177
|
+
}
|
|
1178
|
+
if (maximum === 0)
|
|
1179
|
+
return scalar(0);
|
|
1180
|
+
if (socket.listening)
|
|
1181
|
+
return scalar(-107);
|
|
1182
|
+
if (socket.byteLength === 0 && socket.error) {
|
|
1183
|
+
const error = socket.error;
|
|
1184
|
+
socket.error = 0;
|
|
1185
|
+
return scalar(-error);
|
|
1186
|
+
}
|
|
1187
|
+
if (!socket.open && !socket.closed && !socket.peerClosed)
|
|
1188
|
+
return scalar(-107);
|
|
1189
|
+
if (socket.byteLength === 0)
|
|
1190
|
+
return {
|
|
1191
|
+
result: socket.closed || socket.peerClosed ? 0 : -11,
|
|
1192
|
+
output: new Uint8Array(),
|
|
1193
|
+
};
|
|
1194
|
+
return drainChunks(socket, maximum, Boolean(flags & 2));
|
|
1195
|
+
}
|
|
1196
|
+
function drainChunks(socket, maximum, peek = false) {
|
|
1197
|
+
const output = new Uint8Array(Math.min(maximum, socket.byteLength));
|
|
1198
|
+
let offset = 0;
|
|
1199
|
+
let index = 0;
|
|
1200
|
+
while (offset < output.byteLength) {
|
|
1201
|
+
const chunk = socket.chunks[peek ? index++ : 0];
|
|
1202
|
+
const count = Math.min(chunk.byteLength, output.byteLength - offset);
|
|
1203
|
+
output.set(chunk.subarray(0, count), offset);
|
|
1204
|
+
offset += count;
|
|
1205
|
+
if (!peek) {
|
|
1206
|
+
socket.byteLength -= count;
|
|
1207
|
+
if (count === chunk.byteLength)
|
|
1208
|
+
socket.chunks.shift();
|
|
1209
|
+
else
|
|
1210
|
+
socket.chunks[0] = chunk.subarray(count);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
return { result: output.byteLength, output };
|
|
1214
|
+
}
|
|
1215
|
+
function pollSocket(socket, events) {
|
|
1216
|
+
if (socket.kind === "event")
|
|
1217
|
+
return ((events & 1) && socket.counter > 0n ? 1 : 0) | ((events & 4) ? 4 : 0);
|
|
1218
|
+
if (socket.kind === "timer") {
|
|
1219
|
+
updateTimer(socket);
|
|
1220
|
+
return (events & 1) && socket.expirations > 0n ? 1 : 0;
|
|
1221
|
+
}
|
|
1222
|
+
if (socket.kind === "local") {
|
|
1223
|
+
const eof = socket.readShutdown;
|
|
1224
|
+
return (socket.peerClosed || (socket.readShutdown && socket.writeShutdown) ? 0x10 : 0)
|
|
1225
|
+
| (eof ? events & 0x2000 : 0) // POLLRDHUP is requested, unlike HUP.
|
|
1226
|
+
| (socket.byteLength > 0 || eof ? events & (1 | 0x40) : 0)
|
|
1227
|
+
// EPIPE is immediate, so a shut write half is still ready for write.
|
|
1228
|
+
| (events & (4 | 0x100 | 0x200));
|
|
1229
|
+
}
|
|
1230
|
+
if (socket.kind === "dns")
|
|
1231
|
+
return ((events & 1) && socket.responses.length ? 1 : 0) | ((events & 4) ? 4 : 0);
|
|
1232
|
+
if (socket.listening)
|
|
1233
|
+
return (events & 1) && socket.pending.length > 0 ? 1 : 0;
|
|
1234
|
+
let ready = (socket.closed || socket.peerClosed ? 0x10 : 0) | (socket.error ? 0x8 : 0);
|
|
1235
|
+
if ((events & 1) && (socket.byteLength > 0 || socket.closed || socket.peerClosed))
|
|
1236
|
+
ready |= 1;
|
|
1237
|
+
// Failed connection completion remains writable after SO_ERROR is consumed.
|
|
1238
|
+
if ((events & 4) && !socket.connecting && ((socket.open && !socket.peerClosed) || socket.closed))
|
|
1239
|
+
ready |= 4;
|
|
1240
|
+
return ready;
|
|
1241
|
+
}
|
|
1242
|
+
function rearmEpollReadable(fd) {
|
|
1243
|
+
const target = sockets.get(fd);
|
|
1244
|
+
for (const epoll of epolls.values()) {
|
|
1245
|
+
for (const [registeredFd, registration] of epoll.registrations) {
|
|
1246
|
+
if (registeredFd === fd || (target && sockets.get(registeredFd) === target)) {
|
|
1247
|
+
registration.lastReady &= ~1;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
// FIOCLEX/FIONCLEX mutate descriptor flags, never the shared status flags.
|
|
1253
|
+
function ioctlDescriptor(fd, request) {
|
|
1254
|
+
if (request !== 0x5451 && request !== 0x5450)
|
|
1255
|
+
return -25;
|
|
1256
|
+
const flags = epolls.has(fd) ? epollDescriptorFlags : socketDescriptorFlags;
|
|
1257
|
+
flags.set(fd, request === 0x5451 ? 1 : 0);
|
|
1258
|
+
return 0;
|
|
1259
|
+
}
|
|
1260
|
+
function ioctlSocket(fd, socket, request, value) {
|
|
1261
|
+
if (request === 0x5451 || request === 0x5450)
|
|
1262
|
+
return ioctlDescriptor(fd, request);
|
|
1263
|
+
if (request === 0x5421) {
|
|
1264
|
+
socket.nonblocking = value !== 0;
|
|
1265
|
+
return 0;
|
|
1266
|
+
}
|
|
1267
|
+
if (socket.kind === "event" || socket.kind === "timer")
|
|
1268
|
+
return -25; // FIONBIO
|
|
1269
|
+
if (request !== 0x541b)
|
|
1270
|
+
return -25; // FIONREAD/SIOCINQ
|
|
1271
|
+
if (socket.kind === "tcp" && socket.listening)
|
|
1272
|
+
return -22;
|
|
1273
|
+
const length = socket.kind === "dns" ? socket.responses[0]?.byteLength ?? 0 : socket.byteLength;
|
|
1274
|
+
return Math.min(length, 0x7fff_ffff);
|
|
1275
|
+
}
|
|
1276
|
+
function fcntlSocket(fd, socket, command, argument) {
|
|
1277
|
+
if (command === 0 || command === 1030) {
|
|
1278
|
+
return argument < 0 || argument >= descriptorLimit() ? -22 : duplicateSocket(socket, Math.max(argument, NETWORK_FD_MIN), command === 1030);
|
|
1279
|
+
}
|
|
1280
|
+
if (command === 1)
|
|
1281
|
+
return socketDescriptorFlags.get(fd) ?? 0;
|
|
1282
|
+
if (command === 2) {
|
|
1283
|
+
socketDescriptorFlags.set(fd, argument & 1);
|
|
1284
|
+
return 0;
|
|
1285
|
+
}
|
|
1286
|
+
if (command === 4) {
|
|
1287
|
+
socket.nonblocking = Boolean(argument & 0x800);
|
|
1288
|
+
return 0;
|
|
1289
|
+
}
|
|
1290
|
+
if (command === 3)
|
|
1291
|
+
return 2 | (socket.nonblocking ? 0x800 : 0);
|
|
1292
|
+
return -22;
|
|
1293
|
+
}
|
|
1294
|
+
function duplicateSocket(socket, minimum = NETWORK_FD_MIN, closeOnExec = false) {
|
|
1295
|
+
const fd = takeFd(minimum);
|
|
1296
|
+
sockets.set(fd, socket);
|
|
1297
|
+
socketDescriptorFlags.set(fd, closeOnExec ? 1 : 0);
|
|
1298
|
+
return fd;
|
|
1299
|
+
}
|
|
1300
|
+
// libsyscall.js only supports SO_ERROR. Additional read-only Linux queries
|
|
1301
|
+
// below expose actual owner state, never a fabricated successful setting.
|
|
1302
|
+
function socketOption(socket, level, name) {
|
|
1303
|
+
if (socket.kind === "event" || socket.kind === "timer")
|
|
1304
|
+
return scalar(-88);
|
|
1305
|
+
if (level !== 1)
|
|
1306
|
+
return scalar(-92);
|
|
1307
|
+
let value;
|
|
1308
|
+
switch (name) {
|
|
1309
|
+
case 3:
|
|
1310
|
+
value = socket.kind === "dns" ? 2 : 1;
|
|
1311
|
+
break; // SO_TYPE
|
|
1312
|
+
case 4: // SO_ERROR is consumed across all aliases of the description.
|
|
1313
|
+
value = socket.kind === "tcp" ? socket.error : 0;
|
|
1314
|
+
if (socket.kind === "tcp")
|
|
1315
|
+
socket.error = 0;
|
|
1316
|
+
break;
|
|
1317
|
+
case 30:
|
|
1318
|
+
value = socket.kind === "tcp" && socket.listening ? 1 : 0;
|
|
1319
|
+
break;
|
|
1320
|
+
case 38:
|
|
1321
|
+
value = socket.kind === "tcp" ? 6 : socket.kind === "dns" ? 17 : 0;
|
|
1322
|
+
break;
|
|
1323
|
+
case 39:
|
|
1324
|
+
value = socket.kind === "local" ? 1 : socket.domain;
|
|
1325
|
+
break;
|
|
1326
|
+
default: return scalar(-92);
|
|
1327
|
+
}
|
|
1328
|
+
const output = new Uint8Array(4);
|
|
1329
|
+
new DataView(output.buffer).setInt32(0, value, true);
|
|
1330
|
+
return { result: 0, output };
|
|
1331
|
+
}
|
|
1332
|
+
/** shutdown changes the shared description; it never removes an fd alias. */
|
|
1333
|
+
function shutdownSocket(socket, how) {
|
|
1334
|
+
if (socket.kind === "event" || socket.kind === "timer")
|
|
1335
|
+
return -88;
|
|
1336
|
+
if (how < 0 || how > 2 || !Number.isInteger(how))
|
|
1337
|
+
return -22;
|
|
1338
|
+
if (socket.kind === "dns")
|
|
1339
|
+
return socket.peerAddress ? -95 : -107;
|
|
1340
|
+
if (socket.kind === "local") {
|
|
1341
|
+
if (!socket.peer)
|
|
1342
|
+
return -107;
|
|
1343
|
+
if (how === 0 || how === 2) {
|
|
1344
|
+
socket.readShutdown = true;
|
|
1345
|
+
socket.peer.writeShutdown = true;
|
|
1346
|
+
}
|
|
1347
|
+
if (how === 1 || how === 2) {
|
|
1348
|
+
socket.writeShutdown = true;
|
|
1349
|
+
socket.peer.readShutdown = true;
|
|
1350
|
+
}
|
|
1351
|
+
if (socket.readShutdown && socket.writeShutdown)
|
|
1352
|
+
socket.peer.peerClosed = true;
|
|
1353
|
+
return 0;
|
|
1354
|
+
}
|
|
1355
|
+
if (socket.listening || socket.connecting || (!socket.open && !socket.peerAddress && socket.loopbackConnectionId === undefined))
|
|
1356
|
+
return -107;
|
|
1357
|
+
// The relay/loopback protocol carries full close, not independent FIN.
|
|
1358
|
+
// Do not silently destroy the other half while claiming SHUT_RD/SHUT_WR.
|
|
1359
|
+
if (how !== 2)
|
|
1360
|
+
return -95;
|
|
1361
|
+
closeSocket(socket);
|
|
1362
|
+
return 0;
|
|
1363
|
+
}
|
|
1364
|
+
function closeSocket(socket) {
|
|
1365
|
+
if (socket.kind === "tcp") {
|
|
1366
|
+
socket.cancelConnect?.();
|
|
1367
|
+
socket.connectGeneration += 1;
|
|
1368
|
+
socket.connecting = false;
|
|
1369
|
+
externalConnections.delete(socket);
|
|
1370
|
+
if (socket.listening && socket.bound) {
|
|
1371
|
+
listenersByPort.delete(socket.bound.port);
|
|
1372
|
+
postLoopback({ type: "unlisten", port: socket.bound.port });
|
|
1373
|
+
for (const resolve of socket.acceptWaiters.splice(0))
|
|
1374
|
+
resolve(undefined);
|
|
1375
|
+
for (const accepted of socket.pending.splice(0))
|
|
1376
|
+
closeSocket(accepted);
|
|
1377
|
+
}
|
|
1378
|
+
if (socket.loopbackConnectionId !== undefined) {
|
|
1379
|
+
loopbackConnections.delete(socket.loopbackConnectionId);
|
|
1380
|
+
postLoopback({ type: "closed", connectionId: socket.loopbackConnectionId });
|
|
1381
|
+
}
|
|
1382
|
+
socket.closed = true;
|
|
1383
|
+
socket.open = false;
|
|
1384
|
+
try {
|
|
1385
|
+
socket.websocket?.close();
|
|
1386
|
+
}
|
|
1387
|
+
catch { }
|
|
1388
|
+
}
|
|
1389
|
+
if (socket.kind === "local") {
|
|
1390
|
+
socket.closed = true;
|
|
1391
|
+
socket.readShutdown = true;
|
|
1392
|
+
socket.writeShutdown = true;
|
|
1393
|
+
if (socket.peer) {
|
|
1394
|
+
socket.peer.peerClosed = true;
|
|
1395
|
+
socket.peer.readShutdown = true;
|
|
1396
|
+
socket.peer.writeShutdown = true;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
function consumeNetworkBytes(bytes) {
|
|
1401
|
+
if (bytes < 0 || networkBytes > maxBytes - bytes)
|
|
1402
|
+
return false;
|
|
1403
|
+
networkBytes += bytes;
|
|
1404
|
+
return true;
|
|
1405
|
+
}
|
|
1406
|
+
function socketName(socket, peer) {
|
|
1407
|
+
if (socket.kind === "event" || socket.kind === "timer")
|
|
1408
|
+
return scalar(-88);
|
|
1409
|
+
if (socket.kind === "local") {
|
|
1410
|
+
const output = new Uint8Array(2);
|
|
1411
|
+
new DataView(output.buffer).setUint16(0, 1, true);
|
|
1412
|
+
return { result: 0, output };
|
|
1413
|
+
}
|
|
1414
|
+
const endpoint = peer ? socket.peerAddress : socket.bound;
|
|
1415
|
+
if (peer && !endpoint) {
|
|
1416
|
+
// A connected ingress socket has a peer, but the ingress wire supplies no
|
|
1417
|
+
// endpoint. Name that transport gap instead of reporting it unconnected.
|
|
1418
|
+
return scalar(socket.kind === "tcp" && socket.open && !socket.listening ? -95 : -107);
|
|
1419
|
+
}
|
|
1420
|
+
return { result: 0, output: socketAddress(socket.domain, endpoint?.address ?? (socket.domain === 10 ? "::" : "0.0.0.0"), endpoint?.port ?? 0) };
|
|
1421
|
+
}
|
|
1422
|
+
function socketAddress(domain, address, port) {
|
|
1423
|
+
const output = new Uint8Array(domain === 10 ? 28 : 16);
|
|
1424
|
+
const view = new DataView(output.buffer);
|
|
1425
|
+
view.setUint16(0, domain, true);
|
|
1426
|
+
view.setUint16(2, port, false);
|
|
1427
|
+
if (domain === 2) {
|
|
1428
|
+
output.set(address.split(".").map(Number), 4);
|
|
1429
|
+
}
|
|
1430
|
+
else {
|
|
1431
|
+
const [left, right] = address.split("::");
|
|
1432
|
+
const head = left ? left.split(":") : [];
|
|
1433
|
+
const tail = right ? right.split(":") : [];
|
|
1434
|
+
const groups = right === undefined ? head : [...head, ...Array(8 - head.length - tail.length).fill("0"), ...tail];
|
|
1435
|
+
for (let index = 0; index < 8; index++)
|
|
1436
|
+
view.setUint16(8 + index * 2, parseInt(groups[index] ?? "0", 16), false);
|
|
1437
|
+
}
|
|
1438
|
+
return output;
|
|
1439
|
+
}
|
|
1440
|
+
function updateTimer(timer) {
|
|
1441
|
+
if (timer.deadlineNanoseconds === undefined)
|
|
1442
|
+
return;
|
|
1443
|
+
const now = nowNanoseconds(timer.clockId);
|
|
1444
|
+
if (now < timer.deadlineNanoseconds)
|
|
1445
|
+
return;
|
|
1446
|
+
const elapsed = now - timer.deadlineNanoseconds;
|
|
1447
|
+
const count = timer.intervalNanoseconds === 0n ? 1n : 1n + elapsed / timer.intervalNanoseconds;
|
|
1448
|
+
const next = timer.expirations + count;
|
|
1449
|
+
timer.expirations = next > 0xffffffffffffffffn ? 0xffffffffffffffffn : next;
|
|
1450
|
+
timer.deadlineNanoseconds = timer.intervalNanoseconds === 0n
|
|
1451
|
+
? undefined : timer.deadlineNanoseconds + count * timer.intervalNanoseconds;
|
|
1452
|
+
}
|
|
1453
|
+
function timerRemaining(timer) {
|
|
1454
|
+
if (timer.deadlineNanoseconds === undefined)
|
|
1455
|
+
return 0n;
|
|
1456
|
+
const remaining = timer.deadlineNanoseconds - nowNanoseconds(timer.clockId);
|
|
1457
|
+
return remaining > 0n ? remaining : 0n;
|
|
1458
|
+
}
|
|
1459
|
+
function readTimespec(view, offset) {
|
|
1460
|
+
const seconds = view.getBigInt64(offset, true);
|
|
1461
|
+
const nanoseconds = view.getBigInt64(offset + 8, true);
|
|
1462
|
+
if (seconds < 0n || nanoseconds < 0n || nanoseconds >= 1000000000n)
|
|
1463
|
+
return;
|
|
1464
|
+
return seconds * 1000000000n + nanoseconds;
|
|
1465
|
+
}
|
|
1466
|
+
function writeTimespec(view, offset, nanoseconds) {
|
|
1467
|
+
view.setBigInt64(offset, nanoseconds / 1000000000n, true);
|
|
1468
|
+
view.setBigInt64(offset + 8, nanoseconds % 1000000000n, true);
|
|
1469
|
+
}
|
|
1470
|
+
function nowNanoseconds(clockId) {
|
|
1471
|
+
const milliseconds = clockId === 0 ? performance.timeOrigin + performance.now() : monotonicMilliseconds();
|
|
1472
|
+
return BigInt(Math.floor(milliseconds * 1_000_000));
|
|
1473
|
+
}
|
|
1474
|
+
function dnsResponse(query) {
|
|
1475
|
+
if (query.byteLength < 17)
|
|
1476
|
+
return;
|
|
1477
|
+
let offset = 12;
|
|
1478
|
+
const labels = [];
|
|
1479
|
+
while (offset < query.byteLength) {
|
|
1480
|
+
const length = query[offset++];
|
|
1481
|
+
if (length === 0)
|
|
1482
|
+
break;
|
|
1483
|
+
if (length > 63 || offset + length > query.byteLength)
|
|
1484
|
+
return;
|
|
1485
|
+
labels.push(new TextDecoder().decode(query.subarray(offset, offset + length)));
|
|
1486
|
+
offset += length;
|
|
1487
|
+
}
|
|
1488
|
+
if (offset + 4 > query.byteLength)
|
|
1489
|
+
return;
|
|
1490
|
+
const hostname = labels.join(".").toLowerCase();
|
|
1491
|
+
const qtype = (query[offset] << 8) | query[offset + 1];
|
|
1492
|
+
const questionEnd = offset + 4;
|
|
1493
|
+
const allowed = allowedHosts.has(hostname) && (qtype === 1 || qtype === 28);
|
|
1494
|
+
const answerLength = allowed ? (qtype === 1 ? 16 : 28) : 0;
|
|
1495
|
+
const response = new Uint8Array(questionEnd + answerLength);
|
|
1496
|
+
response.set(query.subarray(0, questionEnd));
|
|
1497
|
+
response[2] = 0x81;
|
|
1498
|
+
response[3] = allowed ? 0x80 : 0x83;
|
|
1499
|
+
response[6] = 0;
|
|
1500
|
+
response[7] = allowed ? 1 : 0;
|
|
1501
|
+
response.fill(0, 8, 12);
|
|
1502
|
+
if (!allowed)
|
|
1503
|
+
return response;
|
|
1504
|
+
const identifier = addressId(hostname);
|
|
1505
|
+
let answer = questionEnd;
|
|
1506
|
+
response[answer++] = 0xc0;
|
|
1507
|
+
response[answer++] = 0x0c;
|
|
1508
|
+
response[answer++] = 0;
|
|
1509
|
+
response[answer++] = qtype;
|
|
1510
|
+
response[answer++] = 0;
|
|
1511
|
+
response[answer++] = 1;
|
|
1512
|
+
response[answer++] = 0;
|
|
1513
|
+
response[answer++] = 0;
|
|
1514
|
+
response[answer++] = 0;
|
|
1515
|
+
response[answer++] = 60;
|
|
1516
|
+
response[answer++] = 0;
|
|
1517
|
+
response[answer++] = qtype === 1 ? 4 : 16;
|
|
1518
|
+
if (qtype === 1) {
|
|
1519
|
+
const address = `203.0.113.${identifier}`;
|
|
1520
|
+
hostByAddress.set(address, hostname);
|
|
1521
|
+
response.set([203, 0, 113, identifier], answer);
|
|
1522
|
+
}
|
|
1523
|
+
else {
|
|
1524
|
+
const address = `2001:db8::${identifier.toString(16)}`;
|
|
1525
|
+
hostByAddress.set(address, hostname);
|
|
1526
|
+
response.set([0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, identifier], answer);
|
|
1527
|
+
}
|
|
1528
|
+
return response;
|
|
1529
|
+
}
|
|
1530
|
+
function addressId(hostname) {
|
|
1531
|
+
const current = addressIdByHost.get(hostname);
|
|
1532
|
+
if (current)
|
|
1533
|
+
return current;
|
|
1534
|
+
const next = addressIdByHost.size + 1;
|
|
1535
|
+
if (next > 254)
|
|
1536
|
+
throw new Error("Too many WALI DNS targets.");
|
|
1537
|
+
addressIdByHost.set(hostname, next);
|
|
1538
|
+
return next;
|
|
1539
|
+
}
|
|
1540
|
+
function parseSocketAddress(value) {
|
|
1541
|
+
if (value.byteLength < 8)
|
|
1542
|
+
return;
|
|
1543
|
+
const view = new DataView(value.buffer, value.byteOffset, value.byteLength);
|
|
1544
|
+
const family = view.getUint16(0, true);
|
|
1545
|
+
const port = view.getUint16(2, false);
|
|
1546
|
+
if (family === 2 && value.byteLength >= 16) {
|
|
1547
|
+
return { address: [...value.subarray(4, 8)].join("."), port };
|
|
1548
|
+
}
|
|
1549
|
+
if (family === 10 && value.byteLength >= 28) {
|
|
1550
|
+
const groups = [];
|
|
1551
|
+
for (let offset = 8; offset < 24; offset += 2)
|
|
1552
|
+
groups.push(view.getUint16(offset, false).toString(16));
|
|
1553
|
+
return { address: compressIpv6(groups), port };
|
|
1554
|
+
}
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
function compressIpv6(groups) {
|
|
1558
|
+
let bestStart = -1;
|
|
1559
|
+
let bestLength = 0;
|
|
1560
|
+
for (let start = 0; start < groups.length;) {
|
|
1561
|
+
if (groups[start] !== "0") {
|
|
1562
|
+
start += 1;
|
|
1563
|
+
continue;
|
|
1564
|
+
}
|
|
1565
|
+
let end = start;
|
|
1566
|
+
while (groups[end] === "0")
|
|
1567
|
+
end += 1;
|
|
1568
|
+
if (end - start > bestLength) {
|
|
1569
|
+
bestStart = start;
|
|
1570
|
+
bestLength = end - start;
|
|
1571
|
+
}
|
|
1572
|
+
start = end;
|
|
1573
|
+
}
|
|
1574
|
+
if (bestLength < 2)
|
|
1575
|
+
return groups.join(":");
|
|
1576
|
+
const before = groups.slice(0, bestStart).join(":");
|
|
1577
|
+
const after = groups.slice(bestStart + bestLength).join(":");
|
|
1578
|
+
return `${before}::${after}`;
|
|
1579
|
+
}
|
|
1580
|
+
return { receive };
|
|
1581
|
+
}
|
|
1582
|
+
//# sourceMappingURL=network-host.js.map
|