@ai-ecoverse/slicc-kernel 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,152 @@
1
+ // src/fs/meta.ts
2
+ var META_DB = "slicc-kernel";
3
+ var META_VERSION = 1;
4
+ var META_STORE = "entries";
5
+ function parentOf(path) {
6
+ return path.slice(0, path.lastIndexOf("/")) || "/";
7
+ }
8
+ function moved(entry, path) {
9
+ return entry.link === void 0 ? { ...entry, path } : { ...entry, path, dir: parentOf(path) };
10
+ }
11
+ function under(path, root) {
12
+ return path === root || path.startsWith(`${root}/`);
13
+ }
14
+ var MemoryMeta = class {
15
+ entries = /* @__PURE__ */ new Map();
16
+ async get(paths) {
17
+ return paths.map((path) => this.entries.get(path));
18
+ }
19
+ async update(path, change) {
20
+ this.set(path, change(this.entries.get(path)));
21
+ }
22
+ set(path, entry) {
23
+ if (entry) this.entries.set(path, entry);
24
+ else this.entries.delete(path);
25
+ }
26
+ async move(from, to, root) {
27
+ await this.remove([to]);
28
+ const carried = [...this.entries.values()].filter((entry) => under(entry.path, from));
29
+ for (const entry of carried) this.entries.delete(entry.path);
30
+ for (const entry of carried) {
31
+ const path = to + entry.path.slice(from.length);
32
+ if (path !== to) this.entries.set(path, moved(entry, path));
33
+ }
34
+ this.entries.set(to, moved(root(carried.find((entry) => entry.path === from)), to));
35
+ }
36
+ async remove(paths) {
37
+ for (const key of [...this.entries.keys()]) {
38
+ if (paths.some((path) => under(key, path))) this.entries.delete(key);
39
+ }
40
+ }
41
+ async links(dir) {
42
+ return [...this.entries.values()].filter((entry) => entry.dir === dir);
43
+ }
44
+ async under(path) {
45
+ return [...this.entries.values()].filter((entry) => under(entry.path, path));
46
+ }
47
+ async all() {
48
+ return [...this.entries.values()];
49
+ }
50
+ };
51
+ function descendants(path) {
52
+ return IDBKeyRange.bound(`${path}/`, `${path}0`, false, true);
53
+ }
54
+ function settle(transaction, value) {
55
+ return new Promise((resolve, reject) => {
56
+ transaction.oncomplete = () => resolve(value());
57
+ transaction.onabort = () => reject(transaction.error ?? new Error("metadata transaction aborted"));
58
+ });
59
+ }
60
+ var IndexedDbMeta = class {
61
+ db;
62
+ constructor(db) {
63
+ this.db = db;
64
+ db.onversionchange = () => db.close();
65
+ }
66
+ transaction(mode) {
67
+ const transaction = this.db.transaction(META_STORE, mode);
68
+ return [transaction, transaction.objectStore(META_STORE)];
69
+ }
70
+ async get(paths) {
71
+ const [transaction, store] = this.transaction("readonly");
72
+ const found = paths.map(() => void 0);
73
+ paths.forEach((path, i) => {
74
+ const request2 = store.get(path);
75
+ request2.onsuccess = () => {
76
+ found[i] = request2.result;
77
+ };
78
+ });
79
+ return settle(transaction, () => found);
80
+ }
81
+ async update(path, change) {
82
+ const [transaction, store] = this.transaction("readwrite");
83
+ const request2 = store.get(path);
84
+ request2.onsuccess = () => {
85
+ const next = change(request2.result);
86
+ if (next) store.put(next);
87
+ else store.delete(path);
88
+ };
89
+ return settle(transaction, () => void 0);
90
+ }
91
+ async move(from, to, root) {
92
+ const [transaction, store] = this.transaction("readwrite");
93
+ store.delete(to);
94
+ store.delete(descendants(to));
95
+ const source = store.get(from);
96
+ const below = store.getAll(descendants(from));
97
+ below.onsuccess = () => {
98
+ store.delete(from);
99
+ store.delete(descendants(from));
100
+ for (const entry of below.result) {
101
+ store.put(moved(entry, to + entry.path.slice(from.length)));
102
+ }
103
+ store.put(moved(root(source.result), to));
104
+ };
105
+ return settle(transaction, () => void 0);
106
+ }
107
+ async remove(paths) {
108
+ const [transaction, store] = this.transaction("readwrite");
109
+ for (const path of paths) {
110
+ store.delete(path);
111
+ store.delete(descendants(path));
112
+ }
113
+ return settle(transaction, () => void 0);
114
+ }
115
+ async links(dir) {
116
+ const [transaction, store] = this.transaction("readonly");
117
+ const request2 = store.index("dir").getAll(dir);
118
+ return settle(transaction, () => request2.result);
119
+ }
120
+ async under(path) {
121
+ const [transaction, store] = this.transaction("readonly");
122
+ const self = store.get(path);
123
+ const below = store.getAll(descendants(path));
124
+ return settle(
125
+ transaction,
126
+ () => [self.result, ...below.result].filter(
127
+ (entry) => entry !== void 0
128
+ )
129
+ );
130
+ }
131
+ async all() {
132
+ const [transaction, store] = this.transaction("readonly");
133
+ const request2 = store.getAll();
134
+ return settle(transaction, () => request2.result);
135
+ }
136
+ };
137
+ function openMeta(name2, factory) {
138
+ return new Promise((resolve, reject) => {
139
+ const request2 = factory.open(name2, META_VERSION);
140
+ request2.onupgradeneeded = (event) => {
141
+ if (event.oldVersion < 1) {
142
+ request2.result.createObjectStore(META_STORE, { keyPath: "path" }).createIndex("dir", "dir");
143
+ }
144
+ };
145
+ request2.onsuccess = () => resolve(new IndexedDbMeta(request2.result));
146
+ request2.onerror = () => reject(request2.error);
147
+ });
148
+ }
149
+
1
150
  // src/fs/types.ts
2
151
  function fsError(code, path) {
3
152
  return Object.assign(new Error(`${code}: ${path}`), { code });
@@ -14,12 +163,26 @@ function normalizePath(path) {
14
163
  function resolvePath(base, path) {
15
164
  return normalizePath(path.startsWith("/") ? path : `${base}/${path}`);
16
165
  }
166
+ function inodeOf(path) {
167
+ let h1 = 3735928559;
168
+ let h2 = 1103547991;
169
+ for (let i = 0; i < path.length; i++) {
170
+ const c = path.charCodeAt(i);
171
+ h1 = Math.imul(h1 ^ c, 2654435761);
172
+ h2 = Math.imul(h2 ^ c, 1597334677);
173
+ }
174
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
175
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
176
+ return 4294967296 * (2097151 & h2) + (h1 >>> 0) || 1;
177
+ }
17
178
 
18
179
  // src/fs/opfs.ts
19
180
  var S_IFDIR = 16384;
20
181
  var S_IFREG = 32768;
182
+ var S_IFLNK = 40960;
21
183
  var PERM_MASK = 4095;
22
184
  var READ_ATTEMPTS = 3;
185
+ var MAX_LINKS = 40;
23
186
  var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
24
187
  var ERRNO_BY_DOM_ERROR = {
25
188
  NotFoundError: "ENOENT",
@@ -30,29 +193,46 @@ var ERRNO_BY_DOM_ERROR = {
30
193
  };
31
194
  function translate(err, path) {
32
195
  if (typeof err?.code === "string") return err;
33
- const name = err?.name;
34
- return fsError(typeof name === "string" && ERRNO_BY_DOM_ERROR[name] || "EIO", path);
196
+ const name2 = err?.name;
197
+ return fsError(typeof name2 === "string" && ERRNO_BY_DOM_ERROR[name2] || "EIO", path);
198
+ }
199
+ function missing(err) {
200
+ const code = err?.code;
201
+ return code === "ENOENT" || code === "ENOTDIR";
35
202
  }
36
203
  function split(path) {
37
204
  return path.split("/").filter(Boolean);
38
205
  }
206
+ function parentOf2(path) {
207
+ return path.slice(0, path.lastIndexOf("/")) || "/";
208
+ }
209
+ function baseOf(path) {
210
+ return path.slice(path.lastIndexOf("/") + 1);
211
+ }
212
+ function prefixes(path) {
213
+ const parts = split(path);
214
+ return parts.map((_, i) => `/${parts.slice(0, i + 1).join("/")}`);
215
+ }
39
216
  function within(path, root) {
40
217
  return path === root || path.startsWith(`${root}/`);
41
218
  }
219
+ function isHandle(child) {
220
+ return "kind" in child;
221
+ }
42
222
  var OpfsFs = class {
43
- modes = /* @__PURE__ */ new Map();
44
- times = /* @__PURE__ */ new Map();
45
223
  root;
46
- constructor(root) {
224
+ meta;
225
+ constructor(root, meta = new MemoryMeta()) {
47
226
  this.root = root;
227
+ this.meta = meta;
48
228
  }
49
229
  resolvePath(base, path) {
50
230
  return resolvePath(base, path);
51
231
  }
52
- async dir(path, create = false) {
232
+ async dir(path) {
53
233
  let dir = this.root;
54
234
  try {
55
- for (const part of split(path)) dir = await dir.getDirectoryHandle(part, { create });
235
+ for (const part of split(path)) dir = await dir.getDirectoryHandle(part);
56
236
  } catch (err) {
57
237
  throw translate(err, path);
58
238
  }
@@ -60,48 +240,103 @@ var OpfsFs = class {
60
240
  }
61
241
  async parent(path) {
62
242
  const parts = split(path);
63
- const name = parts.pop();
64
- if (name === void 0) throw fsError("EBUSY", path);
65
- return [await this.dir(`/${parts.join("/")}`), name];
243
+ const name2 = parts.pop();
244
+ if (name2 === void 0) throw fsError("EBUSY", path);
245
+ return [await this.dir(`/${parts.join("/")}`), name2];
66
246
  }
67
247
  async handle(path) {
68
248
  if (split(path).length === 0) return this.root;
69
- const [dir, name] = await this.parent(path);
249
+ const [dir, name2] = await this.parent(path);
70
250
  try {
71
- return await dir.getFileHandle(name);
251
+ return await dir.getFileHandle(name2);
72
252
  } catch (err) {
73
253
  if (err?.name !== "TypeMismatchError") throw translate(err, path);
74
254
  }
75
- return dir.getDirectoryHandle(name);
255
+ return dir.getDirectoryHandle(name2);
256
+ }
257
+ real(path) {
258
+ return this.handle(path).then(
259
+ () => true,
260
+ () => false
261
+ );
262
+ }
263
+ async linkAlong(path) {
264
+ for (const entry of await this.meta.get(prefixes(path))) {
265
+ if (entry?.link === void 0) continue;
266
+ if (!await this.real(entry.path) && await this.real(parentOf2(entry.path))) return entry;
267
+ }
268
+ return void 0;
269
+ }
270
+ async locate(path, follow) {
271
+ let current = path;
272
+ for (let hops = 0; hops <= MAX_LINKS; hops++) {
273
+ try {
274
+ return { path: current, handle: await this.handle(current) };
275
+ } catch (err) {
276
+ if (!missing(err)) throw err;
277
+ const link2 = await this.linkAlong(current);
278
+ if (!link2) throw err;
279
+ if (link2.path === current && !follow) return { path: current, link: link2 };
280
+ const rest = current.slice(link2.path.length);
281
+ current = resolvePath(parentOf2(link2.path), `${link2.link}${rest}`);
282
+ }
283
+ }
284
+ throw fsError("ELOOP", path);
76
285
  }
77
- mode(path, directory) {
286
+ mode(path, directory, meta) {
78
287
  const fallback = directory || EXECUTABLE.test(path) ? 493 : 420;
79
- return (directory ? S_IFDIR : S_IFREG) | (this.modes.get(path) ?? fallback);
288
+ return (directory ? S_IFDIR : S_IFREG) | (meta?.mode ?? fallback);
80
289
  }
81
- async stat(path) {
82
- const handle = await this.handle(path);
290
+ async statOf(path, handle, meta) {
83
291
  const directory = handle.kind === "directory";
84
292
  const file = directory ? void 0 : await handle.getFile();
293
+ const modified = file?.lastModified ?? 0;
294
+ const mtime = meta?.mtimeMs !== void 0 && meta.mtimeFor === modified ? meta.mtimeMs : modified;
85
295
  return {
86
296
  isFile: !directory,
87
297
  isDirectory: directory,
88
298
  isSymbolicLink: false,
89
299
  size: file?.size ?? 0,
90
- mode: this.mode(path, directory),
91
- mtime: new Date(this.times.get(path) ?? file?.lastModified ?? 0)
300
+ mode: this.mode(path, directory, meta),
301
+ mtime: new Date(mtime),
302
+ atime: new Date(meta?.atimeMs ?? mtime),
303
+ ctime: new Date(Math.max(meta?.ctimeMs ?? mtime, modified)),
304
+ ino: meta?.ino ?? inodeOf(path)
305
+ };
306
+ }
307
+ linkStat(entry) {
308
+ const time2 = new Date(entry.mtimeMs ?? 0);
309
+ return {
310
+ isFile: false,
311
+ isDirectory: false,
312
+ isSymbolicLink: true,
313
+ size: new TextEncoder().encode(entry.link).length,
314
+ mode: S_IFLNK | 511,
315
+ mtime: time2,
316
+ atime: time2,
317
+ ctime: new Date(entry.ctimeMs ?? 0),
318
+ ino: entry.ino ?? inodeOf(entry.path)
92
319
  };
93
320
  }
94
- lstat(path) {
95
- return this.stat(path);
321
+ async describe(found) {
322
+ if (found.link) return this.linkStat(found.link);
323
+ const [meta] = await this.meta.get([found.path]);
324
+ return this.statOf(found.path, found.handle, meta);
325
+ }
326
+ async stat(path) {
327
+ return this.describe(await this.locate(path, true));
328
+ }
329
+ async lstat(path) {
330
+ return this.describe(await this.locate(path, false));
96
331
  }
97
332
  async exists(path) {
98
- return this.handle(path).then(
333
+ return this.locate(path, true).then(
99
334
  () => true,
100
335
  () => false
101
336
  );
102
337
  }
103
338
  async readFileBuffer(path) {
104
- const handle = await this.handle(path);
339
+ const handle = (await this.locate(path, true)).handle;
105
340
  if (handle.kind === "directory") throw fsError("EISDIR", path);
106
341
  for (let attempt = 1; ; attempt++) {
107
342
  try {
@@ -117,11 +352,22 @@ var OpfsFs = class {
117
352
  async readFile(path) {
118
353
  return new TextDecoder().decode(await this.readFileBuffer(path));
119
354
  }
355
+ async writeTarget(path) {
356
+ try {
357
+ return (await this.locate(path, true)).path;
358
+ } catch (err) {
359
+ if (!missing(err)) throw err;
360
+ }
361
+ const [entry] = await this.meta.get([path]);
362
+ const wanted = entry?.link === void 0 ? path : resolvePath(parentOf2(path), entry.link);
363
+ return resolvePath((await this.locate(parentOf2(wanted), true)).path, baseOf(wanted));
364
+ }
120
365
  async writeFile(path, content) {
121
- const [dir, name] = await this.parent(path);
366
+ const target = await this.writeTarget(path);
367
+ const [dir, name2] = await this.parent(target);
122
368
  let file;
123
369
  try {
124
- file = await dir.getFileHandle(name, { create: true });
370
+ file = await dir.getFileHandle(name2, { create: true });
125
371
  } catch (err) {
126
372
  const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
127
373
  throw code ? fsError(code, path) : translate(err, path);
@@ -129,357 +375,256 @@ var OpfsFs = class {
129
375
  const writable = await file.createWritable();
130
376
  await writable.write(content);
131
377
  await writable.close();
132
- this.times.delete(path);
133
378
  }
134
- async readdir(path) {
135
- const handle = await this.handle(path);
379
+ async listing(path) {
380
+ const found = await this.locate(path, true);
381
+ const handle = found.handle;
136
382
  if (handle.kind !== "directory") throw fsError("ENOTDIR", path);
137
- const names = [];
138
- for await (const name of handle.keys()) names.push(name);
139
- return names.sort();
383
+ const children2 = /* @__PURE__ */ new Map();
384
+ for await (const child of handle.values()) children2.set(child.name, child);
385
+ for (const link2 of await this.meta.links(found.path)) {
386
+ const name2 = baseOf(link2.path);
387
+ if (!children2.has(name2)) children2.set(name2, link2);
388
+ }
389
+ return [found.path, [...children2].sort(([a], [b]) => a < b ? -1 : 1)];
390
+ }
391
+ async readdir(path) {
392
+ return (await this.listing(path))[1].map(([name2]) => name2);
393
+ }
394
+ async readdirStat(path) {
395
+ const [dir, children2] = await this.listing(path);
396
+ const base = dir === "/" ? "" : dir;
397
+ const metas = await this.meta.get(children2.map(([name2]) => `${base}/${name2}`));
398
+ return Promise.all(
399
+ children2.map(async ([name2, child], i) => {
400
+ if (!isHandle(child)) return [name2, this.linkStat(child)];
401
+ const stat = await this.statOf(`${base}/${name2}`, child, metas[i]).catch(() => null);
402
+ return [name2, stat];
403
+ })
404
+ );
140
405
  }
141
406
  async mkdir(path, options = {}) {
142
- if (options.recursive) {
143
- await this.dir(path, true);
144
- return;
407
+ let found;
408
+ try {
409
+ found = await this.locate(path, true);
410
+ } catch (err) {
411
+ if (!missing(err)) throw err;
412
+ }
413
+ if (found) {
414
+ const directory = found.handle?.kind === "directory";
415
+ if (options.recursive && directory) return;
416
+ throw fsError(options.recursive ? "ENOTDIR" : "EEXIST", path);
145
417
  }
146
- if (await this.exists(path)) throw fsError("EEXIST", path);
147
- const [dir, name] = await this.parent(path);
148
- await dir.getDirectoryHandle(name, { create: true });
418
+ const [entry] = await this.meta.get([path]);
419
+ if (entry?.link !== void 0) throw fsError("EEXIST", path);
420
+ if (options.recursive) await this.mkdir(parentOf2(path), options);
421
+ const parent = await this.locate(parentOf2(path), true);
422
+ if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
423
+ await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
149
424
  }
150
425
  async rm(path, options = {}) {
426
+ let found;
151
427
  try {
152
- const [dir, name] = await this.parent(path);
153
- await dir.removeEntry(name, { recursive: options.recursive === true });
428
+ found = await this.locate(path, false);
154
429
  } catch (err) {
155
430
  const error = translate(err, path);
156
431
  if (options.force && error.code === "ENOENT") return;
157
432
  throw error;
158
433
  }
159
- this.forget(path);
160
- }
161
- forget(path) {
162
- for (const map of [this.modes, this.times]) {
163
- for (const key of [...map.keys()]) if (within(key, path)) map.delete(key);
164
- }
165
- }
166
- carry(from, to) {
167
- for (const map of [this.modes, this.times]) {
168
- for (const [key, value] of [...map]) {
169
- if (!within(key, from)) continue;
170
- map.delete(key);
171
- map.set(to + key.slice(from.length), value);
434
+ if (found.handle) {
435
+ const [dir, name2] = await this.parent(found.path);
436
+ try {
437
+ await dir.removeEntry(name2, { recursive: options.recursive === true });
438
+ } catch (err) {
439
+ throw translate(err, path);
172
440
  }
173
441
  }
442
+ await this.meta.remove([found.path]);
174
443
  }
175
444
  async rename(from, to) {
176
445
  if (from === to) return;
177
- const source = await this.stat(from);
178
- if (source.isDirectory && within(to, from)) throw fsError("EINVAL", to);
179
- const target = await this.stat(to).catch(() => null);
180
- if (target?.isDirectory && !source.isDirectory) throw fsError("EISDIR", to);
181
- if (target && !target.isDirectory && source.isDirectory) throw fsError("ENOTDIR", to);
182
- if (target?.isDirectory && (await this.readdir(to)).length > 0) {
446
+ const source = await this.locate(from, false);
447
+ const parent = await this.locate(parentOf2(to), true);
448
+ const destination = resolvePath(parent.path, baseOf(to));
449
+ const sourceStat = await this.describe(source);
450
+ if (sourceStat.isDirectory && within(destination, source.path)) throw fsError("EINVAL", to);
451
+ const target = await this.locate(destination, false).catch((err) => {
452
+ if (missing(err)) return null;
453
+ throw err;
454
+ });
455
+ const targetStat = target && await this.describe(target);
456
+ if (targetStat?.isDirectory && !sourceStat.isDirectory) throw fsError("EISDIR", to);
457
+ if (targetStat && !targetStat.isDirectory && sourceStat.isDirectory) {
458
+ throw fsError("ENOTDIR", to);
459
+ }
460
+ if (targetStat?.isDirectory && (await this.readdir(destination)).length > 0) {
183
461
  throw fsError("ENOTEMPTY", to);
184
462
  }
185
- const [dir, name] = await this.parent(to);
186
- const handle = await this.handle(from);
187
- const swap = target?.isDirectory === true;
188
- const staging = swap ? `.${name}.${crypto.randomUUID()}` : name;
189
- const moved = await this.place(handle, dir, staging);
190
- if (swap) await this.rm(to, { recursive: true });
191
- this.carry(from, to);
192
- if (!moved) await this.rm(from, { recursive: true });
193
- if (!swap) return;
194
- const staged = await dir.getDirectoryHandle(staging);
195
- if (!await this.place(staged, dir, name)) await dir.removeEntry(staging, { recursive: true });
196
- }
197
- async place(handle, dir, name) {
198
- const moved = await handle.move?.(dir, name).then(
463
+ const now = Date.now();
464
+ const carry = (entry) => ({
465
+ ino: inodeOf(source.path),
466
+ ...entry,
467
+ path: destination,
468
+ ctimeMs: now
469
+ });
470
+ if (source.link) {
471
+ if (target?.handle) await this.rm(destination);
472
+ await this.meta.move(source.path, destination, carry);
473
+ return;
474
+ }
475
+ const dir = parent.handle;
476
+ const name2 = baseOf(destination);
477
+ const swap = targetStat?.isDirectory === true;
478
+ const staging = swap ? `.${name2}.${crypto.randomUUID()}` : name2;
479
+ const timed = await this.explicitTimes(source.path);
480
+ const moved2 = await this.place(source.handle, dir, staging);
481
+ if (swap) await this.rm(destination, { recursive: true });
482
+ await this.meta.move(source.path, destination, carry);
483
+ if (!moved2) await this.rm(source.path, { recursive: true });
484
+ let copied = !moved2;
485
+ if (swap) {
486
+ const staged = await dir.getDirectoryHandle(staging);
487
+ const placed = await this.place(staged, dir, name2);
488
+ if (!placed) await dir.removeEntry(staging, { recursive: true });
489
+ copied ||= !placed;
490
+ }
491
+ if (copied) await this.restamp(destination, timed);
492
+ }
493
+ async modified(path) {
494
+ const handle = await this.handle(path);
495
+ return handle.kind === "file" ? (await handle.getFile()).lastModified : 0;
496
+ }
497
+ async explicitTimes(path) {
498
+ const timed = [];
499
+ for (const entry of await this.meta.under(path)) {
500
+ if (entry.mtimeFor === void 0 || entry.link !== void 0) continue;
501
+ const current = await this.modified(entry.path).catch(() => void 0);
502
+ if (current === entry.mtimeFor) timed.push(entry.path.slice(path.length));
503
+ }
504
+ return timed;
505
+ }
506
+ async restamp(path, timed) {
507
+ for (const rest of timed) {
508
+ const mtimeFor = await this.modified(path + rest);
509
+ await this.meta.update(path + rest, (entry) => entry && { ...entry, mtimeFor });
510
+ }
511
+ }
512
+ async place(handle, dir, name2) {
513
+ const moved2 = await handle.move?.(dir, name2).then(
199
514
  () => true,
200
515
  () => false
201
516
  );
202
- if (moved) return true;
517
+ if (moved2) return true;
203
518
  try {
204
- await this.copy(handle, dir, name);
519
+ await this.copy(handle, dir, name2);
205
520
  } catch (err) {
206
- if (handle.kind === "directory") await dir.removeEntry(name, { recursive: true });
207
- throw translate(err, name);
521
+ if (handle.kind === "directory") await dir.removeEntry(name2, { recursive: true });
522
+ throw translate(err, name2);
208
523
  }
209
524
  return false;
210
525
  }
211
- async copy(handle, dir, name) {
526
+ async copy(handle, dir, name2) {
212
527
  if (handle.kind === "file") {
213
- const writable = await (await dir.getFileHandle(name, { create: true })).createWritable();
528
+ const writable = await (await dir.getFileHandle(name2, { create: true })).createWritable();
214
529
  await writable.write(await handle.getFile());
215
530
  await writable.close();
216
531
  return;
217
532
  }
218
- const target = await dir.getDirectoryHandle(name, { create: true });
533
+ const target = await dir.getDirectoryHandle(name2, { create: true });
219
534
  for await (const child of handle.values()) await this.copy(child, target, child.name);
220
535
  }
221
- async symlink(_target, path) {
222
- throw fsError("ENOSYS", path);
536
+ async symlink(target, path) {
537
+ const parent = await this.locate(parentOf2(path), true);
538
+ if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
539
+ const at = resolvePath(parent.path, baseOf(path));
540
+ if (await this.locate(at, false).then(
541
+ () => true,
542
+ (err) => !missing(err)
543
+ )) {
544
+ throw fsError("EEXIST", path);
545
+ }
546
+ const now = Date.now();
547
+ await this.meta.update(at, () => ({
548
+ path: at,
549
+ link: target,
550
+ dir: parent.path,
551
+ ino: inodeOf(at),
552
+ mtimeMs: now,
553
+ ctimeMs: now
554
+ }));
223
555
  }
224
556
  async readlink(path) {
225
- await this.handle(path);
557
+ const found = await this.locate(path, false);
558
+ if (found.link) return found.link.link;
226
559
  throw fsError("EINVAL", path);
227
560
  }
228
561
  async chmod(path, mode) {
229
- await this.handle(path);
230
- this.modes.set(path, mode & PERM_MASK);
231
- }
232
- async utimes(path, _atime, mtime) {
233
- await this.handle(path);
234
- this.times.set(path, mtime.getTime());
562
+ const found = await this.locate(path, true);
563
+ const ctimeMs = Date.now();
564
+ await this.meta.update(found.path, (entry) => ({
565
+ ...entry,
566
+ path: found.path,
567
+ mode: mode & PERM_MASK,
568
+ ctimeMs
569
+ }));
570
+ }
571
+ async utimes(path, atime, mtime) {
572
+ const found = await this.locate(path, true);
573
+ const handle = found.handle;
574
+ const file = handle.kind === "file" ? await handle.getFile() : void 0;
575
+ const ctimeMs = Date.now();
576
+ await this.meta.update(found.path, (entry) => ({
577
+ ...entry,
578
+ path: found.path,
579
+ atimeMs: atime.getTime(),
580
+ mtimeMs: mtime.getTime(),
581
+ mtimeFor: file?.lastModified ?? 0,
582
+ ctimeMs
583
+ }));
584
+ }
585
+ async reconcile() {
586
+ let removed = 0;
587
+ for (const entry of await this.meta.all()) {
588
+ const real = await this.real(entry.path);
589
+ const stale = entry.link === void 0 ? !real : real || !await this.real(parentOf2(entry.path));
590
+ if (!stale) continue;
591
+ await this.meta.update(entry.path, () => void 0);
592
+ removed++;
593
+ }
594
+ return removed;
235
595
  }
236
596
  };
237
597
 
238
- // src/kernel/signals.ts
239
- var SIG = {
240
- HUP: 1,
241
- INT: 2,
242
- QUIT: 3,
243
- ILL: 4,
244
- TRAP: 5,
245
- ABRT: 6,
246
- BUS: 7,
247
- FPE: 8,
248
- KILL: 9,
249
- USR1: 10,
250
- SEGV: 11,
251
- USR2: 12,
252
- PIPE: 13,
253
- ALRM: 14,
254
- TERM: 15,
255
- STKFLT: 16,
256
- CHLD: 17,
257
- CONT: 18,
258
- STOP: 19,
259
- TSTP: 20,
260
- TTIN: 21,
261
- TTOU: 22,
262
- URG: 23,
263
- XCPU: 24,
264
- XFSZ: 25,
265
- VTALRM: 26,
266
- PROF: 27,
267
- WINCH: 28,
268
- POLL: 29,
269
- PWR: 30,
270
- SYS: 31
271
- };
272
- var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
273
- var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
274
- function defaultAction(sig) {
275
- if (sig === SIG.CONT) return "continue";
276
- if (STOPPING.has(sig)) return "stop";
277
- return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
278
- }
279
- function isSignal(sig) {
280
- return Number.isInteger(sig) && sig >= 1 && sig <= 31;
281
- }
282
- function sigbit(sig) {
283
- return 1 << sig;
284
- }
285
- var SIGNAL_BY_NAME = {
286
- SIGINT: SIG.INT,
287
- SIGTERM: SIG.TERM,
288
- SIGKILL: SIG.KILL,
289
- SIGSTOP: SIG.STOP,
290
- SIGCONT: SIG.CONT
598
+ // src/kernel/pipe.ts
599
+ var PIPE_CAPACITY = 64 * 1024;
600
+ var PipeError = class extends Error {
601
+ code;
602
+ constructor(code) {
603
+ super(code);
604
+ this.code = code;
605
+ }
291
606
  };
292
-
293
- // src/commands.ts
294
- var NAME = /^[A-Za-z0-9._+-]+$/;
295
- var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
296
- var PACKAGE = /\$\{package\}/g;
297
- function inside(pkg, rel) {
298
- if (typeof rel !== "string" || rel === "") return void 0;
299
- const clean = rel.replace(/^\.\//, "");
300
- if (clean.startsWith("/") || clean.split("/").includes("..")) return void 0;
301
- return `${pkg}/${clean}`;
302
- }
303
- function envOf(pkg, raw) {
304
- const env = {};
305
- if (!raw || typeof raw !== "object") return env;
306
- for (const [key, value] of Object.entries(raw)) {
307
- if (ENV_KEY.test(key) && typeof value === "string") env[key] = value.replace(PACKAGE, pkg);
607
+ var KernelPipe = class {
608
+ chunks = [];
609
+ size = 0;
610
+ readers = 0;
611
+ writers = 0;
612
+ waiters = [];
613
+ capacity;
614
+ constructor(capacity = PIPE_CAPACITY) {
615
+ this.capacity = capacity;
308
616
  }
309
- return env;
310
- }
311
- function argsOf(pkg, raw) {
312
- if (!Array.isArray(raw) || raw.length === 0) return void 0;
313
- if (!raw.every((arg) => typeof arg === "string")) return void 0;
314
- return raw.map((arg) => arg.replace(PACKAGE, pkg));
315
- }
316
- function commandsOf(pkg, manifest) {
317
- const slicc = manifest.slicc;
318
- if (!slicc || typeof slicc !== "object" || (slicc.abi ?? "emscripten") !== "emscripten")
319
- return [];
320
- const entries = slicc.commands;
321
- if (!entries || typeof entries !== "object") return [];
322
- const shared = envOf(pkg, slicc.env);
323
- const out = [];
324
- for (const [name, raw] of Object.entries(entries)) {
325
- if (!NAME.test(name) || name === "." || name === ".." || !raw || typeof raw !== "object") {
326
- continue;
327
- }
328
- const glue = inside(pkg, raw.glue);
329
- const wasm = inside(pkg, raw.wasm);
330
- if ((raw.abi ?? "emscripten") !== "emscripten" || !glue || !wasm) continue;
331
- const argv0 = typeof raw.argv0 === "string" && raw.argv0 ? raw.argv0 : name;
332
- const args = argsOf(pkg, raw.args);
333
- const env = { ...shared, ...envOf(pkg, raw.env) };
334
- out.push({
335
- name,
336
- glue,
337
- wasm,
338
- argv0,
339
- ...args ? { args } : {},
340
- ...Object.keys(env).length > 0 ? { env } : {}
341
- });
617
+ get buffered() {
618
+ return this.size;
342
619
  }
343
- return out;
344
- }
345
- async function packages(fs, modules) {
346
- const names = await fs.readdir(modules).catch(() => []);
347
- const dirs = [];
348
- for (const name of names) {
349
- if (name.startsWith(".")) continue;
350
- if (!name.startsWith("@")) {
351
- dirs.push(`${modules}/${name}`);
352
- continue;
353
- }
354
- for (const inner of await fs.readdir(`${modules}/${name}`).catch(() => [])) {
355
- dirs.push(`${modules}/${name}/${inner}`);
356
- }
620
+ get readReady() {
621
+ return this.size > 0 || this.writers === 0;
357
622
  }
358
- return dirs;
359
- }
360
- async function scanCommands(fs, modules) {
361
- const found = /* @__PURE__ */ new Map();
362
- for (const pkg of await packages(fs, modules)) {
363
- let manifest;
364
- try {
365
- manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
366
- } catch {
367
- continue;
368
- }
369
- for (const command of commandsOf(pkg, manifest)) {
370
- if (!found.has(command.name)) found.set(command.name, command);
371
- }
623
+ get writeReady() {
624
+ return this.size < this.capacity || this.readers === 0;
372
625
  }
373
- if (!found.has("sh") && found.has("bash")) {
374
- found.set("sh", { ...found.get("bash"), name: "sh", argv0: "sh" });
375
- }
376
- return found;
377
- }
378
-
379
- // src/fs/commands.ts
380
- var S_IFDIR2 = 16384;
381
- var S_IFREG2 = 32768;
382
- var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
383
- function missing(err) {
384
- return err?.code === "ENOENT";
385
- }
386
- function synthetic(directory) {
387
- return {
388
- isFile: !directory,
389
- isDirectory: directory,
390
- isSymbolicLink: false,
391
- size: 0,
392
- mode: (directory ? S_IFDIR2 : S_IFREG2) | 493,
393
- mtime: /* @__PURE__ */ new Date(0)
394
- };
395
- }
396
- function withCommandDirs(fs, names) {
397
- async function children(path) {
398
- if (path === "/") return ["bin", "usr"];
399
- if (path === "/usr") return ["bin"];
400
- if (path === "/bin" || path === "/usr/bin") return [...await names()];
401
- return void 0;
402
- }
403
- async function virtual(path) {
404
- if (path === "/usr" || path === "/bin" || path === "/usr/bin") return synthetic(true);
405
- const name = COMMAND.exec(path)?.[1];
406
- return name !== void 0 && (await names()).has(name) ? synthetic(false) : void 0;
407
- }
408
- async function stat(path, real) {
409
- try {
410
- return await real(path);
411
- } catch (err) {
412
- const found = missing(err) ? await virtual(path) : void 0;
413
- if (found) return found;
414
- throw err;
415
- }
416
- }
417
- return {
418
- resolvePath: (base, path) => fs.resolvePath(base, path),
419
- readFile: (path) => fs.readFile(path),
420
- async readFileBuffer(path) {
421
- try {
422
- return await fs.readFileBuffer(path);
423
- } catch (err) {
424
- if (missing(err) && (await virtual(path))?.isFile) return new Uint8Array(0);
425
- throw err;
426
- }
427
- },
428
- writeFile: (path, content) => fs.writeFile(path, content),
429
- async exists(path) {
430
- return await fs.exists(path) || await virtual(path) !== void 0;
431
- },
432
- stat: (path) => stat(path, (p) => fs.stat(p)),
433
- lstat: (path) => stat(path, (p) => fs.lstat(p)),
434
- async readdir(path) {
435
- const extra = await children(path);
436
- if (!extra) return fs.readdir(path);
437
- const real = await fs.readdir(path).catch((err) => {
438
- if (missing(err)) return [];
439
- throw err;
440
- });
441
- return [.../* @__PURE__ */ new Set([...real, ...extra])].sort();
442
- },
443
- mkdir: (path, options) => fs.mkdir(path, options),
444
- rm: (path, options) => fs.rm(path, options),
445
- rename: (from, to) => fs.rename(from, to),
446
- symlink: (target, path) => fs.symlink(target, path),
447
- readlink: (path) => fs.readlink(path),
448
- chmod: (path, mode) => fs.chmod(path, mode),
449
- utimes: (path, atime, mtime) => fs.utimes(path, atime, mtime)
450
- };
451
- }
452
-
453
- // src/kernel/pipe.ts
454
- var PIPE_CAPACITY = 64 * 1024;
455
- var PipeError = class extends Error {
456
- code;
457
- constructor(code) {
458
- super(code);
459
- this.code = code;
460
- }
461
- };
462
- var KernelPipe = class {
463
- chunks = [];
464
- size = 0;
465
- readers = 0;
466
- writers = 0;
467
- waiters = [];
468
- capacity;
469
- constructor(capacity = PIPE_CAPACITY) {
470
- this.capacity = capacity;
471
- }
472
- get buffered() {
473
- return this.size;
474
- }
475
- get readReady() {
476
- return this.size > 0 || this.writers === 0;
477
- }
478
- get writeReady() {
479
- return this.size < this.capacity || this.readers === 0;
480
- }
481
- get writersGone() {
482
- return this.writers === 0;
626
+ get writersGone() {
627
+ return this.writers === 0;
483
628
  }
484
629
  get readersGone() {
485
630
  return this.readers === 0;
@@ -518,10 +663,10 @@ var KernelPipe = class {
518
663
  }
519
664
  const out = new Uint8Array(Math.min(max, this.size));
520
665
  let filled = 0;
521
- for (const chunk of this.chunks) {
666
+ for (const chunk2 of this.chunks) {
522
667
  if (filled === out.length) break;
523
- const count = Math.min(chunk.length, out.length - filled);
524
- out.set(chunk.subarray(0, count), filled);
668
+ const count = Math.min(chunk2.length, out.length - filled);
669
+ out.set(chunk2.subarray(0, count), filled);
525
670
  filled += count;
526
671
  }
527
672
  return out;
@@ -742,57 +887,1752 @@ var FdTable = class _FdTable {
742
887
  statusFlags(fd) {
743
888
  return this.status.get(fd);
744
889
  }
745
- install(file, min = 0) {
746
- for (let fd = min; fd < _FdTable.MAX_FDS; fd++) {
747
- if (!this.fds.has(fd)) {
748
- this.fds.set(fd, file);
749
- return fd;
750
- }
890
+ install(file, min = 0) {
891
+ for (let fd = min; fd < _FdTable.MAX_FDS; fd++) {
892
+ if (!this.fds.has(fd)) {
893
+ this.fds.set(fd, file);
894
+ return fd;
895
+ }
896
+ }
897
+ void Promise.resolve(file.release()).catch(() => void 0);
898
+ throw new KernelError("EMFILE");
899
+ }
900
+ installAt(fd, file) {
901
+ if (fd < 0 || fd >= _FdTable.MAX_FDS) {
902
+ void Promise.resolve(file.release()).catch(() => void 0);
903
+ throw new KernelError("EBADF");
904
+ }
905
+ const previous = this.fds.get(fd);
906
+ this.fds.set(fd, file);
907
+ this.cloexec.delete(fd);
908
+ this.status.delete(fd);
909
+ void Promise.resolve(previous?.release()).catch(() => void 0);
910
+ }
911
+ dup(fd, min = 0) {
912
+ return this.install(this.get(fd).retain(), min);
913
+ }
914
+ dup2(oldFd, newFd) {
915
+ const file = this.get(oldFd);
916
+ if (oldFd !== newFd) this.installAt(newFd, file.retain());
917
+ return newFd;
918
+ }
919
+ close(fd) {
920
+ const file = this.get(fd);
921
+ this.fds.delete(fd);
922
+ this.cloexec.delete(fd);
923
+ this.status.delete(fd);
924
+ return file.release();
925
+ }
926
+ fork() {
927
+ const child = new _FdTable();
928
+ for (const [fd, file] of this.fds) child.fds.set(fd, file.retain());
929
+ for (const fd of this.cloexec) child.cloexec.add(fd);
930
+ for (const [fd, flags] of this.status) child.status.set(fd, flags);
931
+ return child;
932
+ }
933
+ async closeAll() {
934
+ const files = [...this.fds.values()];
935
+ this.fds.clear();
936
+ this.cloexec.clear();
937
+ this.status.clear();
938
+ await Promise.allSettled(files.map((file) => Promise.resolve().then(() => file.release())));
939
+ }
940
+ };
941
+
942
+ // src/kernel/net/http1.ts
943
+ var HttpError = class extends Error {
944
+ status;
945
+ constructor(status, message2) {
946
+ super(message2);
947
+ this.status = status;
948
+ }
949
+ };
950
+ var CR = 13;
951
+ var LF = 10;
952
+ var READ_SIZE = 64 * 1024;
953
+ var TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
954
+ var REASON = {
955
+ 100: "Continue",
956
+ 200: "OK",
957
+ 400: "Bad Request",
958
+ 403: "Forbidden",
959
+ 408: "Request Timeout",
960
+ 411: "Length Required",
961
+ 413: "Content Too Large",
962
+ 417: "Expectation Failed",
963
+ 421: "Misdirected Request",
964
+ 431: "Request Header Fields Too Large",
965
+ 501: "Not Implemented",
966
+ 502: "Bad Gateway",
967
+ 503: "Service Unavailable",
968
+ 505: "HTTP Version Not Supported"
969
+ };
970
+ var Incoming = class {
971
+ source;
972
+ buf = new Uint8Array(0);
973
+ eof = false;
974
+ constructor(source) {
975
+ this.source = source;
976
+ }
977
+ get buffered() {
978
+ return this.buf.length;
979
+ }
980
+ async fill(signal) {
981
+ if (this.eof) return false;
982
+ const chunk2 = await this.source.read(READ_SIZE, signal);
983
+ if (chunk2.length === 0) {
984
+ this.eof = true;
985
+ return false;
986
+ }
987
+ const next = new Uint8Array(this.buf.length + chunk2.length);
988
+ next.set(this.buf);
989
+ next.set(chunk2, this.buf.length);
990
+ this.buf = next;
991
+ return true;
992
+ }
993
+ take(n) {
994
+ const out = this.buf.slice(0, n);
995
+ this.buf = this.buf.subarray(n);
996
+ return out;
997
+ }
998
+ async some(max, signal) {
999
+ if (this.buf.length > 0) return this.take(Math.min(max, this.buf.length));
1000
+ if (this.eof) return new Uint8Array(0);
1001
+ const chunk2 = await this.source.read(max, signal);
1002
+ if (chunk2.length === 0) this.eof = true;
1003
+ return chunk2;
1004
+ }
1005
+ async head(limit, signal) {
1006
+ let from = 0;
1007
+ for (; ; ) {
1008
+ const end = headEnd(this.buf, from);
1009
+ if (end > 0) {
1010
+ if (end > limit) throw new HttpError(431, "request head too large");
1011
+ return this.take(end);
1012
+ }
1013
+ if (this.buf.length > limit) throw new HttpError(431, "request head too large");
1014
+ from = Math.max(0, this.buf.length - 3);
1015
+ if (!await this.fill(signal)) {
1016
+ if (this.buf.length === 0) return null;
1017
+ throw new HttpError(400, "connection closed inside a request head");
1018
+ }
1019
+ }
1020
+ }
1021
+ async line(limit, signal) {
1022
+ let from = 0;
1023
+ for (; ; ) {
1024
+ const at = this.buf.indexOf(LF, from);
1025
+ if (at >= 0) {
1026
+ const line = this.take(at + 1);
1027
+ const text = latin1(line.subarray(0, line[at - 1] === CR ? at - 1 : at));
1028
+ return text;
1029
+ }
1030
+ if (this.buf.length > limit) throw new HttpError(400, "line too long");
1031
+ from = this.buf.length;
1032
+ if (!await this.fill(signal)) throw new HttpError(400, "connection closed inside a body");
1033
+ }
1034
+ }
1035
+ async exactly(n, signal) {
1036
+ while (this.buf.length < n) {
1037
+ if (!await this.fill(signal)) throw new HttpError(400, "connection closed inside a body");
1038
+ }
1039
+ return this.take(n);
1040
+ }
1041
+ };
1042
+ function headEnd(buf, from) {
1043
+ for (let i = from; i < buf.length; i++) {
1044
+ if (buf[i] !== LF) continue;
1045
+ if (buf[i + 1] === LF) return i + 2;
1046
+ if (buf[i + 1] === CR && buf[i + 2] === LF) return i + 3;
1047
+ }
1048
+ return 0;
1049
+ }
1050
+ function latin1(bytes) {
1051
+ let out = "";
1052
+ for (let i = 0; i < bytes.length; i += 32768) {
1053
+ out += String.fromCharCode(...bytes.subarray(i, i + 32768));
1054
+ }
1055
+ return out;
1056
+ }
1057
+ function latin1Bytes(text) {
1058
+ const out = new Uint8Array(text.length);
1059
+ for (let i = 0; i < text.length; i++) out[i] = text.charCodeAt(i) & 255;
1060
+ return out;
1061
+ }
1062
+ function parseRequestHead(bytes) {
1063
+ const lines = latin1(bytes).split(/\r?\n/);
1064
+ while (lines.length > 0 && lines[0] === "") lines.shift();
1065
+ const match = /^([!#$%&'*+\-.^_`|~0-9A-Za-z]+) (\S+) HTTP\/(\d)\.(\d)$/.exec(lines.shift() ?? "");
1066
+ if (!match) throw new HttpError(400, "malformed request line");
1067
+ if (match[3] !== "1") throw new HttpError(505, `HTTP/${match[3]}.${match[4]} is not supported`);
1068
+ return {
1069
+ method: match[1],
1070
+ target: match[2],
1071
+ minor: Number(match[4]),
1072
+ headers: parseFields(lines)
1073
+ };
1074
+ }
1075
+ function parseFields(lines) {
1076
+ const headers = [];
1077
+ for (const line of lines) {
1078
+ if (line === "") continue;
1079
+ if (line.startsWith(" ") || line.startsWith(" ")) {
1080
+ throw new HttpError(400, "obsolete line folding");
1081
+ }
1082
+ const colon = line.indexOf(":");
1083
+ const name2 = colon > 0 ? line.slice(0, colon) : "";
1084
+ if (!TOKEN.test(name2)) throw new HttpError(400, "malformed header field");
1085
+ headers.push([name2, line.slice(colon + 1).trim()]);
1086
+ }
1087
+ return headers;
1088
+ }
1089
+ function fieldValues(headers, name2) {
1090
+ const lower = name2.toLowerCase();
1091
+ return headers.filter(([n]) => n.toLowerCase() === lower).map(([, v]) => v);
1092
+ }
1093
+ function fieldTokens(headers, name2) {
1094
+ return fieldValues(headers, name2).flatMap((v) => v.split(",")).map((t) => t.trim().toLowerCase()).filter((t) => t !== "");
1095
+ }
1096
+ function requestFraming(headers) {
1097
+ const codings = fieldTokens(headers, "transfer-encoding");
1098
+ const lengths = fieldValues(headers, "content-length");
1099
+ if (codings.length > 0) {
1100
+ if (lengths.length > 0) throw new HttpError(400, "both Transfer-Encoding and Content-Length");
1101
+ if (codings.length !== 1 || codings[0] !== "chunked") {
1102
+ throw new HttpError(501, `unsupported Transfer-Encoding: ${codings.join(", ")}`);
1103
+ }
1104
+ return { kind: "chunked" };
1105
+ }
1106
+ if (lengths.length === 0) return { kind: "none" };
1107
+ const distinct = new Set(lengths.flatMap((v) => v.split(",")).map((v) => v.trim()));
1108
+ const [only] = distinct;
1109
+ if (distinct.size !== 1 || !/^\d{1,15}$/.test(only)) {
1110
+ throw new HttpError(400, "invalid Content-Length");
1111
+ }
1112
+ const length2 = Number(only);
1113
+ return length2 === 0 ? { kind: "none" } : { kind: "length", length: length2 };
1114
+ }
1115
+ var MAX_CHUNK_LINE = 4096;
1116
+ var MAX_TRAILERS = 64 * 1024;
1117
+ async function readBody(incoming, framing, cap, signal) {
1118
+ if (framing.kind === "none") return void 0;
1119
+ if (framing.kind === "length") {
1120
+ if (framing.length > cap) throw new HttpError(413, `request body over ${cap} bytes`);
1121
+ return incoming.exactly(framing.length, signal);
1122
+ }
1123
+ const chunks = [];
1124
+ let total = 0;
1125
+ for (; ; ) {
1126
+ const sizeLine = await incoming.line(MAX_CHUNK_LINE, signal);
1127
+ const size = /^([0-9A-Fa-f]{1,12})[ \t]*(;.*)?$/.exec(sizeLine);
1128
+ if (!size) throw new HttpError(400, "malformed chunk size");
1129
+ const n = Number.parseInt(size[1], 16);
1130
+ if (n === 0) break;
1131
+ total += n;
1132
+ if (total > cap) throw new HttpError(413, `request body over ${cap} bytes`);
1133
+ chunks.push(await incoming.exactly(n, signal));
1134
+ if (await incoming.line(2, signal) !== "") throw new HttpError(400, "malformed chunk");
1135
+ }
1136
+ let trailers = 0;
1137
+ for (let line = await incoming.line(MAX_TRAILERS, signal); line !== ""; ) {
1138
+ trailers += line.length;
1139
+ if (trailers > MAX_TRAILERS) throw new HttpError(431, "trailers too large");
1140
+ line = await incoming.line(MAX_TRAILERS, signal);
1141
+ }
1142
+ return concat(chunks, total);
1143
+ }
1144
+ function concat(chunks, total = chunks.reduce((n, c) => n + c.length, 0)) {
1145
+ const out = new Uint8Array(total);
1146
+ let at = 0;
1147
+ for (const chunk2 of chunks) {
1148
+ out.set(chunk2, at);
1149
+ at += chunk2.length;
1150
+ }
1151
+ return out;
1152
+ }
1153
+ function safeValue(value) {
1154
+ return !/[\r\n\0]/.test(value);
1155
+ }
1156
+ function responseHead(status, reason, headers) {
1157
+ const phrase = /^[\t\x20-\x7e\x80-\xff]*$/.test(reason) ? reason : "";
1158
+ let head = `HTTP/1.1 ${status} ${phrase}\r
1159
+ `;
1160
+ for (const [name2, value] of headers) {
1161
+ if (TOKEN.test(name2) && safeValue(value)) head += `${name2}: ${value}\r
1162
+ `;
1163
+ }
1164
+ return latin1Bytes(`${head}\r
1165
+ `);
1166
+ }
1167
+ function chunk(bytes) {
1168
+ const size = latin1Bytes(`${bytes.length.toString(16)}\r
1169
+ `);
1170
+ const out = new Uint8Array(size.length + bytes.length + 2);
1171
+ out.set(size);
1172
+ out.set(bytes, size.length);
1173
+ out[out.length - 2] = CR;
1174
+ out[out.length - 1] = LF;
1175
+ return out;
1176
+ }
1177
+ var LAST_CHUNK = latin1Bytes("0\r\n\r\n");
1178
+
1179
+ // src/kernel/net/proxy-service.ts
1180
+ var REALM_PROXY_PORT = 3128;
1181
+ var DEFAULT_LIMITS = {
1182
+ maxConnections: 64,
1183
+ maxHead: 64 * 1024,
1184
+ idleMs: 12e4,
1185
+ bodyBudget: 128 * 1024 * 1024
1186
+ };
1187
+ var HOP_BY_HOP = /* @__PURE__ */ new Set([
1188
+ "connection",
1189
+ "keep-alive",
1190
+ "proxy-connection",
1191
+ "proxy-authenticate",
1192
+ "proxy-authorization",
1193
+ "te",
1194
+ "trailer",
1195
+ "transfer-encoding",
1196
+ "upgrade"
1197
+ ]);
1198
+ var REQUEST_OWN = /* @__PURE__ */ new Set(["host", "content-length", "expect"]);
1199
+ var Budget = class {
1200
+ free;
1201
+ waiters = [];
1202
+ constructor(free) {
1203
+ this.free = free;
1204
+ }
1205
+ async acquire(n, signal) {
1206
+ while (this.free < n) {
1207
+ signal.throwIfAborted();
1208
+ await new Promise((resolve) => {
1209
+ const done3 = () => {
1210
+ signal.removeEventListener("abort", done3);
1211
+ resolve();
1212
+ };
1213
+ this.waiters.push(done3);
1214
+ signal.addEventListener("abort", done3, { once: true });
1215
+ });
1216
+ }
1217
+ this.free -= n;
1218
+ }
1219
+ release(n) {
1220
+ this.free += n;
1221
+ const waiters = this.waiters;
1222
+ this.waiters = [];
1223
+ for (const wake of waiters) wake();
1224
+ }
1225
+ };
1226
+ function canonicalHost(host) {
1227
+ const bare = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
1228
+ try {
1229
+ return new URL(`http://${bare}/`).hostname;
1230
+ } catch {
1231
+ return void 0;
1232
+ }
1233
+ }
1234
+ function localV4(a, b) {
1235
+ return a === 127 || a === 0 || a === 169 && b === 254;
1236
+ }
1237
+ function hextets(address) {
1238
+ const [head, tail, extra] = address.split("::");
1239
+ if (extra !== void 0) return void 0;
1240
+ const part = (text) => text ? text.split(":").map((h) => Number.parseInt(h, 16)) : [];
1241
+ const front = part(head);
1242
+ const back = part(tail);
1243
+ const groups = tail === void 0 ? front : [...front, ...new Array(8 - front.length - back.length).fill(0), ...back];
1244
+ return groups.length === 8 && groups.every((g) => g >= 0 && g <= 65535) ? groups : void 0;
1245
+ }
1246
+ function localV6(address) {
1247
+ const g = hextets(address);
1248
+ if (!g) return true;
1249
+ if ((g[0] & 65472) === 65152) return true;
1250
+ const zeroPrefix = g.slice(0, 5).every((x) => x === 0);
1251
+ if (!zeroPrefix) return false;
1252
+ if (g[5] === 65535 || g[5] === 0 && g[6] !== 0) return localV4(g[6] >> 8, g[6] & 255);
1253
+ return g[5] === 0 && g[6] === 0;
1254
+ }
1255
+ function isLoopbackHost(hostname) {
1256
+ const canonical = canonicalHost(hostname);
1257
+ if (canonical === void 0) return false;
1258
+ const host = canonical.replace(/\.+$/, "");
1259
+ if (host === "localhost" || host.endsWith(".localhost")) return true;
1260
+ if (host.startsWith("[")) return localV6(host.slice(1, -1));
1261
+ const v4 = /^(\d+)\.(\d+)\.\d+\.\d+$/.exec(host);
1262
+ return v4 ? localV4(Number(v4[1]), Number(v4[2])) : false;
1263
+ }
1264
+ function dropped(headers) {
1265
+ return /* @__PURE__ */ new Set([...HOP_BY_HOP, ...fieldTokens(headers, "connection")]);
1266
+ }
1267
+ function forwardRequestHeaders(headers) {
1268
+ const drop = dropped(headers);
1269
+ return headers.filter(([name2]) => {
1270
+ const lower = name2.toLowerCase();
1271
+ return !drop.has(lower) && !REQUEST_OWN.has(lower);
1272
+ });
1273
+ }
1274
+ function forwardResponseHeaders(headers, opts) {
1275
+ const drop = dropped(headers);
1276
+ if (!opts.encodedBodies) drop.add("content-encoding");
1277
+ if (!(opts.encodedBodies && opts.bodiless)) drop.add("content-length");
1278
+ return headers.filter(([name2]) => !drop.has(name2.toLowerCase()));
1279
+ }
1280
+ function keepsAlive(req) {
1281
+ const tokens = fieldTokens(req.headers, "connection");
1282
+ if (tokens.includes("close")) return false;
1283
+ return req.minor >= 1 || tokens.includes("keep-alive");
1284
+ }
1285
+ function tunnelRequestUrl(req, origin) {
1286
+ const base = new URL(origin);
1287
+ let url;
1288
+ try {
1289
+ url = new URL(req.target, req.target.startsWith("/") ? base : void 0);
1290
+ } catch {
1291
+ throw new HttpError(400, "malformed request target");
1292
+ }
1293
+ const hosts = fieldValues(req.headers, "host");
1294
+ const hostOk = hosts.every(
1295
+ (h) => URL.canParse(`https://${h}`) && new URL(`https://${h}`).host === base.host
1296
+ );
1297
+ if (url.origin !== base.origin || !hostOk) {
1298
+ throw new HttpError(421, `this tunnel is for ${base.host}`);
1299
+ }
1300
+ return url.href;
1301
+ }
1302
+ function requestUrl(req) {
1303
+ if (req.target.startsWith("/")) {
1304
+ throw new HttpError(400, "this is a proxy: send the absolute URL (GET http://host/path)");
1305
+ }
1306
+ let url;
1307
+ try {
1308
+ url = new URL(req.target);
1309
+ } catch {
1310
+ throw new HttpError(400, "malformed request target");
1311
+ }
1312
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
1313
+ throw new HttpError(400, `unsupported scheme ${url.protocol}`);
1314
+ }
1315
+ if (isLoopbackHost(url.hostname)) {
1316
+ throw new HttpError(
1317
+ 403,
1318
+ `${url.hostname} is the realm's own loopback, which the proxy does not reach: list it in no_proxy`
1319
+ );
1320
+ }
1321
+ return url.href;
1322
+ }
1323
+ function tunnelTarget(req) {
1324
+ const match = /^([A-Za-z0-9.-]+|\[[0-9A-Fa-f:.]+\]):(\d{1,5})$/.exec(req.target);
1325
+ const port = Number(match?.[2]);
1326
+ if (!match || port < 1 || port > 65535) {
1327
+ throw new HttpError(400, "CONNECT needs host:port");
1328
+ }
1329
+ const host = canonicalHost(match[1]);
1330
+ if (host === void 0) throw new HttpError(400, "CONNECT needs host:port");
1331
+ if (isLoopbackHost(host)) {
1332
+ throw new HttpError(
1333
+ 403,
1334
+ `${host} is the realm's own loopback, which the proxy does not reach: list it in no_proxy`
1335
+ );
1336
+ }
1337
+ return { host, port };
1338
+ }
1339
+ async function watchHangup(conn, abort, until) {
1340
+ while (!until.aborted && !abort.signal.aborted) {
1341
+ if (conn.poll().hangup) {
1342
+ abort.abort(new KernelError("EPIPE"));
1343
+ return;
1344
+ }
1345
+ try {
1346
+ await conn.changed(until);
1347
+ } catch {
1348
+ return;
1349
+ }
1350
+ }
1351
+ }
1352
+ function untilAborted(response, signal) {
1353
+ return new Promise((resolve, reject) => {
1354
+ const onAbort = () => {
1355
+ reject(signal.reason ?? new KernelError("EINTR"));
1356
+ response.then((late) => late.cancel()).catch(() => void 0);
1357
+ };
1358
+ if (signal.aborted) return onAbort();
1359
+ signal.addEventListener("abort", onAbort, { once: true });
1360
+ response.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
1361
+ });
1362
+ }
1363
+ function plainResponse(status, message2) {
1364
+ const body = latin1Bytes(`slicc realm proxy: ${message2}
1365
+ `);
1366
+ return {
1367
+ status,
1368
+ statusText: REASON[status] ?? "",
1369
+ headers: [["Content-Type", "text/plain; charset=utf-8"]],
1370
+ body: (async function* () {
1371
+ yield body;
1372
+ })(),
1373
+ cancel: async () => void 0
1374
+ };
1375
+ }
1376
+ function timedSource(conn, idleMs, stop) {
1377
+ return {
1378
+ read: (max) => conn.read(max, AbortSignal.any([stop, AbortSignal.timeout(idleMs)]))
1379
+ };
1380
+ }
1381
+ var RealmProxy = class {
1382
+ options;
1383
+ listener;
1384
+ stop = new AbortController();
1385
+ limits;
1386
+ slots;
1387
+ bodies;
1388
+ connections = /* @__PURE__ */ new Set();
1389
+ closed;
1390
+ port;
1391
+ constructor(options) {
1392
+ this.options = options;
1393
+ this.limits = { ...DEFAULT_LIMITS, ...options.limits };
1394
+ this.slots = new Budget(this.limits.maxConnections);
1395
+ this.bodies = new Budget(this.limits.bodyBudget);
1396
+ this.listener = options.net.listen({
1397
+ family: "inet",
1398
+ host: options.host ?? "127.0.0.1",
1399
+ port: options.port ?? REALM_PROXY_PORT
1400
+ });
1401
+ this.port = this.listener.local?.family === "inet" ? this.listener.local.port : 0;
1402
+ this.closed = this.acceptLoop();
1403
+ }
1404
+ get stopped() {
1405
+ return this.stop.signal.aborted;
1406
+ }
1407
+ close() {
1408
+ if (this.stop.signal.aborted) return;
1409
+ this.stop.abort();
1410
+ this.listener.close();
1411
+ for (const conn of this.connections) conn.close();
1412
+ }
1413
+ async acceptLoop() {
1414
+ const served = /* @__PURE__ */ new Set();
1415
+ try {
1416
+ for (; ; ) {
1417
+ await this.slots.acquire(1, this.stop.signal);
1418
+ let conn;
1419
+ try {
1420
+ conn = await this.listener.accept(this.stop.signal);
1421
+ } catch {
1422
+ this.slots.release(1);
1423
+ break;
1424
+ }
1425
+ const done3 = this.serve(conn).finally(() => {
1426
+ this.slots.release(1);
1427
+ served.delete(done3);
1428
+ });
1429
+ served.add(done3);
1430
+ }
1431
+ } catch {
1432
+ }
1433
+ await Promise.allSettled([...served]);
1434
+ }
1435
+ async serve(conn) {
1436
+ this.connections.add(conn);
1437
+ const incoming = new Incoming(timedSource(conn, this.limits.idleMs, this.stop.signal));
1438
+ try {
1439
+ await this.requests({ sink: conn, incoming, conn, socket: conn });
1440
+ } finally {
1441
+ this.connections.delete(conn);
1442
+ conn.close();
1443
+ }
1444
+ }
1445
+ async requests(ctx) {
1446
+ try {
1447
+ for (; ; ) {
1448
+ const head = await ctx.incoming.head(this.limits.maxHead);
1449
+ if (!head || !await this.exchange(ctx, head)) break;
1450
+ }
1451
+ } catch (e) {
1452
+ if (e instanceof HttpError) await this.refuse(ctx.sink, e);
1453
+ }
1454
+ }
1455
+ async refuse(sink, error) {
1456
+ try {
1457
+ await this.relay(sink, "GET", 1, plainResponse(error.status, error.message), false);
1458
+ } catch {
1459
+ }
1460
+ }
1461
+ async exchange(ctx, head) {
1462
+ const { sink: conn, incoming } = ctx;
1463
+ const req = parseRequestHead(head);
1464
+ if (req.method === "CONNECT") {
1465
+ if (!ctx.conn) throw new HttpError(400, "CONNECT inside a tunnel");
1466
+ await this.connect(ctx.conn, incoming, req);
1467
+ return false;
1468
+ }
1469
+ const url = ctx.origin ? tunnelRequestUrl(req, ctx.origin) : requestUrl(req);
1470
+ const keep = keepsAlive(req);
1471
+ const framing = requestFraming(req.headers);
1472
+ const reserve = framing.kind === "length" ? framing.length : framing.kind === "chunked" ? this.cap() : 0;
1473
+ if (reserve > this.cap()) throw new HttpError(413, `request body over ${this.cap()} bytes`);
1474
+ await this.expectContinue(conn, req, framing.kind !== "none");
1475
+ await this.bodies.acquire(reserve, this.stop.signal);
1476
+ let response;
1477
+ const abort = new AbortController();
1478
+ const stopExchange = () => abort.abort();
1479
+ this.stop.signal.addEventListener("abort", stopExchange, { once: true });
1480
+ try {
1481
+ try {
1482
+ const body = await readBody(incoming, framing, this.cap());
1483
+ const waiting = new AbortController();
1484
+ void watchHangup(ctx.socket, abort, waiting.signal);
1485
+ try {
1486
+ response = await this.upstream(req, url, body, abort.signal);
1487
+ } finally {
1488
+ waiting.abort();
1489
+ }
1490
+ } finally {
1491
+ this.bodies.release(reserve);
1492
+ }
1493
+ return await this.relay(conn, req.method, req.minor, response, keep);
1494
+ } finally {
1495
+ this.stop.signal.removeEventListener("abort", stopExchange);
1496
+ abort.abort();
1497
+ }
1498
+ }
1499
+ cap() {
1500
+ return Math.min(this.options.transport.traits.maxRequestBody, this.limits.bodyBudget);
1501
+ }
1502
+ async expectContinue(conn, req, hasBody) {
1503
+ const expect = fieldTokens(req.headers, "expect");
1504
+ if (expect.length === 0) return;
1505
+ if (expect.length !== 1 || expect[0] !== "100-continue") {
1506
+ throw new HttpError(417, `unsupported expectation: ${expect.join(", ")}`);
1507
+ }
1508
+ if (hasBody && req.minor >= 1) {
1509
+ await conn.write(latin1Bytes("HTTP/1.1 100 Continue\r\n\r\n"), this.stop.signal);
1510
+ }
1511
+ }
1512
+ async upstream(req, url, body, signal) {
1513
+ try {
1514
+ const fetching = this.options.transport.fetch({
1515
+ url,
1516
+ method: req.method.toUpperCase(),
1517
+ headers: forwardRequestHeaders(req.headers),
1518
+ body: req.method === "GET" || req.method === "HEAD" ? void 0 : body,
1519
+ signal
1520
+ });
1521
+ return await untilAborted(fetching, signal);
1522
+ } catch (e) {
1523
+ if (signal.aborted) throw e;
1524
+ const message2 = e instanceof Error ? e.message : String(e);
1525
+ const status = e?.status;
1526
+ const own = typeof status === "number" && status >= 400 && status <= 599 ? status : 502;
1527
+ return plainResponse(own, message2 || "upstream request failed");
1528
+ }
1529
+ }
1530
+ async relay(conn, method, minor, response, keepAlive) {
1531
+ const status = response.status;
1532
+ const bodiless = method === "HEAD" || status === 204 || status === 304 || status < 200;
1533
+ const headers = forwardResponseHeaders(response.headers, {
1534
+ encodedBodies: this.options.transport.traits.encodedBodies,
1535
+ bodiless
1536
+ });
1537
+ const chunked = !bodiless && minor >= 1;
1538
+ const keep = keepAlive && (bodiless || chunked);
1539
+ headers.push(["Connection", keep ? "keep-alive" : "close"]);
1540
+ if (chunked) headers.push(["Transfer-Encoding", "chunked"]);
1541
+ const signal = this.stop.signal;
1542
+ await conn.write(
1543
+ responseHead(status, response.statusText || REASON[status] || "", headers),
1544
+ signal
1545
+ );
1546
+ if (bodiless) {
1547
+ await response.cancel();
1548
+ return keep;
1549
+ }
1550
+ try {
1551
+ for await (const piece of response.body) {
1552
+ if (piece.length > 0) await conn.write(chunked ? chunk(piece) : piece, signal);
1553
+ }
1554
+ } catch {
1555
+ await response.cancel().catch(() => void 0);
1556
+ return false;
1557
+ }
1558
+ if (chunked) await conn.write(LAST_CHUNK, signal);
1559
+ return keep;
1560
+ }
1561
+ async connect(conn, incoming, req) {
1562
+ const target = tunnelTarget(req);
1563
+ const tunnel = this.options.tunnel;
1564
+ if (!tunnel) {
1565
+ throw new HttpError(
1566
+ 501,
1567
+ `CONNECT ${target.host}:${target.port}: no tunnels through this proxy`
1568
+ );
1569
+ }
1570
+ await this.options.tunnelReady?.().catch((e) => {
1571
+ throw new HttpError(
1572
+ 501,
1573
+ `CONNECT ${target.host}:${target.port}: ${e instanceof Error ? e.message : String(e)}`
1574
+ );
1575
+ });
1576
+ await conn.write(latin1Bytes("HTTP/1.1 200 Connection Established\r\n\r\n"), this.stop.signal);
1577
+ try {
1578
+ await tunnel(
1579
+ conn,
1580
+ incoming,
1581
+ target,
1582
+ this.stop.signal,
1583
+ (source, sink, origin) => this.requests({ sink, incoming: new Incoming(source), origin, socket: conn })
1584
+ );
1585
+ } catch {
1586
+ }
1587
+ }
1588
+ };
1589
+
1590
+ // src/kernel/net/x509.ts
1591
+ function concat2(parts) {
1592
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
1593
+ let at = 0;
1594
+ for (const p of parts) {
1595
+ out.set(p, at);
1596
+ at += p.length;
1597
+ }
1598
+ return out;
1599
+ }
1600
+ function length(n) {
1601
+ if (n < 128) return Uint8Array.of(n);
1602
+ const bytes = [];
1603
+ for (let v = n; v > 0; v = Math.floor(v / 256)) bytes.unshift(v & 255);
1604
+ return Uint8Array.of(128 | bytes.length, ...bytes);
1605
+ }
1606
+ function tlv(tag, ...contents2) {
1607
+ const body = concat2(contents2);
1608
+ return concat2([Uint8Array.of(tag), length(body.length), body]);
1609
+ }
1610
+ var seq = (...parts) => tlv(48, ...parts);
1611
+ var set = (...parts) => tlv(49, ...parts);
1612
+ var octets = (bytes) => tlv(4, bytes);
1613
+ var bits = (bytes, unused = 0) => tlv(3, Uint8Array.of(unused), bytes);
1614
+ var TRUE = tlv(1, Uint8Array.of(255));
1615
+ function integer(magnitude) {
1616
+ let start = 0;
1617
+ while (start < magnitude.length - 1 && magnitude[start] === 0) start++;
1618
+ const trimmed = magnitude.subarray(start);
1619
+ return tlv(2, trimmed[0] & 128 ? concat2([Uint8Array.of(0), trimmed]) : trimmed);
1620
+ }
1621
+ function oid(dotted) {
1622
+ const [a, b, ...rest] = dotted.split(".").map(Number);
1623
+ const out = [40 * a + b];
1624
+ for (const arc of rest) {
1625
+ const groups = [];
1626
+ for (let v = arc; ; v = Math.floor(v / 128)) {
1627
+ groups.unshift(v & 127);
1628
+ if (v < 128) break;
1629
+ }
1630
+ out.push(...groups.map((g, i) => i < groups.length - 1 ? g | 128 : g));
1631
+ }
1632
+ return tlv(6, Uint8Array.from(out));
1633
+ }
1634
+ var utf8 = (text) => tlv(12, new TextEncoder().encode(text));
1635
+ var ascii = (text) => Uint8Array.from(text, (c) => c.charCodeAt(0) & 127);
1636
+ function time(date) {
1637
+ const iso = date.toISOString().replace(/[-:T]/g, "").slice(0, 14);
1638
+ const year = date.getUTCFullYear();
1639
+ return year < 2050 ? tlv(23, ascii(`${iso.slice(2)}Z`)) : tlv(24, ascii(`${iso}Z`));
1640
+ }
1641
+ var ECDSA_SHA256 = seq(oid("1.2.840.10045.4.3.2"));
1642
+ function name(dn) {
1643
+ const rdns = [];
1644
+ if (dn.organization) rdns.push(set(seq(oid("2.5.4.10"), utf8(dn.organization))));
1645
+ rdns.push(set(seq(oid("2.5.4.3"), utf8(dn.commonName))));
1646
+ return seq(...rdns);
1647
+ }
1648
+ function extension(id, critical, value) {
1649
+ return critical ? seq(oid(id), TRUE, octets(value)) : seq(oid(id), octets(value));
1650
+ }
1651
+ function children(der) {
1652
+ const out = [];
1653
+ const [, bodyStart, bodyEnd] = header(der, 0);
1654
+ for (let at = bodyStart; at < bodyEnd; ) {
1655
+ const [, , end] = header(der, at);
1656
+ out.push(der.subarray(at, end));
1657
+ at = end;
1658
+ }
1659
+ return out;
1660
+ }
1661
+ function header(der, at) {
1662
+ const tag = der[at];
1663
+ let len = der[at + 1];
1664
+ let start = at + 2;
1665
+ if (len & 128) {
1666
+ const count = len & 127;
1667
+ len = 0;
1668
+ for (let i = 0; i < count; i++) len = len * 256 + der[start + i];
1669
+ start += count;
1670
+ }
1671
+ if (start + len > der.length) throw new Error("truncated DER");
1672
+ return [tag, start, start + len];
1673
+ }
1674
+ function contents(element) {
1675
+ const [, start, end] = header(element, 0);
1676
+ return element.subarray(start, end);
1677
+ }
1678
+ function publicKeyBits(spki) {
1679
+ const key = children(spki)[1];
1680
+ return contents(key).subarray(1);
1681
+ }
1682
+ async function keyId(spki) {
1683
+ return new Uint8Array(
1684
+ await crypto.subtle.digest("SHA-1", publicKeyBits(spki))
1685
+ );
1686
+ }
1687
+ function ecdsaDer(raw) {
1688
+ const half = raw.length / 2;
1689
+ return seq(integer(raw.subarray(0, half)), integer(raw.subarray(half)));
1690
+ }
1691
+ function randomSerial() {
1692
+ const serial = crypto.getRandomValues(new Uint8Array(16));
1693
+ serial[0] = serial[0] & 127 | 1;
1694
+ return serial;
1695
+ }
1696
+ function ipv4(host) {
1697
+ const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
1698
+ if (!m) return void 0;
1699
+ const parts = m.slice(1).map(Number);
1700
+ return parts.every((p) => p <= 255) ? Uint8Array.from(parts) : void 0;
1701
+ }
1702
+ function ipv6(host) {
1703
+ if (!host.includes(":") || !/^[0-9A-Fa-f:.]+$/.test(host)) return void 0;
1704
+ const v4 = /(\d{1,3}(?:\.\d{1,3}){3})$/.exec(host);
1705
+ let text = host;
1706
+ const tail = [];
1707
+ if (v4) {
1708
+ const bytes = ipv4(v4[1]);
1709
+ if (!bytes) return void 0;
1710
+ tail.push(bytes[0] << 8 | bytes[1], bytes[2] << 8 | bytes[3]);
1711
+ text = host.slice(0, -v4[1].length).replace(/:$/, host.endsWith(`::${v4[1]}`) ? ":" : "");
1712
+ }
1713
+ const halves = text.split("::");
1714
+ if (halves.length > 2) return void 0;
1715
+ const groups = (part) => part === "" ? [] : part.split(":");
1716
+ const head = groups(halves[0]);
1717
+ const back = halves.length === 2 ? groups(halves[1]) : [];
1718
+ if (![...head, ...back].every((g) => /^[0-9A-Fa-f]{1,4}$/.test(g))) return void 0;
1719
+ const words = [...head, ...back].map((g) => Number.parseInt(g, 16));
1720
+ const missing3 = 8 - words.length - tail.length;
1721
+ if (halves.length === 2 ? missing3 < 1 : missing3 !== 0) return void 0;
1722
+ const all = [
1723
+ ...head.map((g) => Number.parseInt(g, 16)),
1724
+ ...new Array(halves.length === 2 ? missing3 : 0).fill(0),
1725
+ ...back.map((g) => Number.parseInt(g, 16)),
1726
+ ...tail
1727
+ ];
1728
+ return Uint8Array.from(all.flatMap((w) => [w >> 8, w & 255]));
1729
+ }
1730
+ async function extensions(req) {
1731
+ const list = [extension("2.5.29.14", false, octets(await keyId(req.spki)))];
1732
+ if (req.kind.ca) {
1733
+ list.push(extension("2.5.29.19", true, seq(TRUE, integer(Uint8Array.of(0)))));
1734
+ list.push(extension("2.5.29.15", true, bits(Uint8Array.of(134), 1)));
1735
+ } else {
1736
+ list.push(extension("2.5.29.35", false, seq(tlv(128, await keyId(req.issuerSpki)))));
1737
+ list.push(extension("2.5.29.19", true, seq()));
1738
+ list.push(extension("2.5.29.15", true, bits(Uint8Array.of(128), 7)));
1739
+ list.push(extension("2.5.29.37", false, seq(oid("1.3.6.1.5.5.7.3.1"))));
1740
+ const ip = ipv4(req.kind.host) ?? ipv6(req.kind.host);
1741
+ const altName = ip ? tlv(135, ip) : tlv(130, ascii(req.kind.host));
1742
+ list.push(extension("2.5.29.17", false, seq(altName)));
1743
+ }
1744
+ return tlv(163, seq(...list));
1745
+ }
1746
+ async function certificate(req) {
1747
+ const tbs = seq(
1748
+ tlv(160, integer(Uint8Array.of(2))),
1749
+ integer(req.serial),
1750
+ ECDSA_SHA256,
1751
+ name(req.issuer),
1752
+ seq(time(req.notBefore), time(req.notAfter)),
1753
+ name(req.subject),
1754
+ req.spki,
1755
+ await extensions(req)
1756
+ );
1757
+ const signature = ecdsaDer(await req.sign(tbs));
1758
+ return seq(tbs, ECDSA_SHA256, bits(signature));
1759
+ }
1760
+ function pem(der) {
1761
+ let b64 = "";
1762
+ for (let i = 0; i < der.length; i += 32768) {
1763
+ b64 += String.fromCharCode(...der.subarray(i, i + 32768));
1764
+ }
1765
+ const lines = btoa(b64).match(/.{1,64}/g) ?? [];
1766
+ return `-----BEGIN CERTIFICATE-----
1767
+ ${lines.join("\n")}
1768
+ -----END CERTIFICATE-----
1769
+ `;
1770
+ }
1771
+
1772
+ // src/kernel/net/realm-ca.ts
1773
+ function sameCert(a, b) {
1774
+ if (!a || !b) return a === b;
1775
+ return a.cert.length === b.cert.length && a.cert.every((byte, i) => byte === b.cert[i]);
1776
+ }
1777
+ function winner(current, expected, record) {
1778
+ return sameCert(current, expected) ? record : current;
1779
+ }
1780
+ var DAY = 24 * 60 * 60 * 1e3;
1781
+ var CA_LIFETIME = 3650 * DAY;
1782
+ var CA_RENEW_BEFORE = 30 * DAY;
1783
+ var LEAF_LIFETIME = 7 * DAY;
1784
+ var ECDSA = { name: "ECDSA", namedCurve: "P-256" };
1785
+ var SIGN = { name: "ECDSA", hash: "SHA-256" };
1786
+ function request(req) {
1787
+ return new Promise((resolve, reject) => {
1788
+ req.onsuccess = () => resolve(req.result);
1789
+ req.onerror = () => reject(req.error);
1790
+ });
1791
+ }
1792
+ function indexedDbCaStore(dbName = "slicc-realm-ca") {
1793
+ let db;
1794
+ const open = () => {
1795
+ db ??= new Promise((resolve, reject) => {
1796
+ const req = indexedDB.open(dbName, 1);
1797
+ req.onupgradeneeded = () => req.result.createObjectStore("ca");
1798
+ req.onsuccess = () => resolve(req.result);
1799
+ req.onerror = () => reject(req.error);
1800
+ });
1801
+ db.catch(() => {
1802
+ db = void 0;
1803
+ });
1804
+ return db;
1805
+ };
1806
+ return {
1807
+ async get(owner) {
1808
+ const store = (await open()).transaction("ca").objectStore("ca");
1809
+ return await request(store.get(owner));
1810
+ },
1811
+ async swap(owner, expected, record) {
1812
+ const tx = (await open()).transaction("ca", "readwrite");
1813
+ const store = tx.objectStore("ca");
1814
+ let stored = record;
1815
+ const read = store.get(owner);
1816
+ read.onsuccess = () => {
1817
+ const current = read.result;
1818
+ const kept = winner(current, expected, record);
1819
+ if (kept === record) store.put(record, owner);
1820
+ else stored = kept;
1821
+ };
1822
+ await new Promise((resolve, reject) => {
1823
+ tx.oncomplete = () => resolve();
1824
+ tx.onerror = () => reject(tx.error);
1825
+ });
1826
+ return stored;
1827
+ }
1828
+ };
1829
+ }
1830
+ var LeafNameError = class extends Error {
1831
+ };
1832
+ function validLeafName(host) {
1833
+ if (ipv6(host)) return true;
1834
+ if (host.length === 0 || host.length > 253) return false;
1835
+ return host.split(".").every((label) => /^(?!-)[a-z0-9-]{1,63}(?<!-)$/i.test(label));
1836
+ }
1837
+ var RealmCa = class _RealmCa {
1838
+ record;
1839
+ constructor(record) {
1840
+ this.record = record;
1841
+ }
1842
+ static async open(owner, store, now = Date.now()) {
1843
+ const stored = await store.get(owner).catch(() => void 0);
1844
+ if (stored && stored.notAfter - now > CA_RENEW_BEFORE) return new _RealmCa(stored);
1845
+ const record = await _RealmCa.create(owner, now);
1846
+ return new _RealmCa(await store.swap(owner, stored, record));
1847
+ }
1848
+ static async create(owner, now) {
1849
+ const pair = await crypto.subtle.generateKey(ECDSA, false, ["sign", "verify"]);
1850
+ const spki = new Uint8Array(await crypto.subtle.exportKey("spki", pair.publicKey));
1851
+ const id = Array.from(
1852
+ crypto.getRandomValues(new Uint8Array(4)),
1853
+ (b) => b.toString(16).padStart(2, "0")
1854
+ ).join("");
1855
+ const name2 = { organization: "SLICC", commonName: `SLICC realm CA ${owner} ${id}` };
1856
+ const notAfter = now + CA_LIFETIME;
1857
+ const cert = await certificate({
1858
+ serial: randomSerial(),
1859
+ issuer: name2,
1860
+ subject: name2,
1861
+ notBefore: new Date(now - DAY),
1862
+ notAfter: new Date(notAfter),
1863
+ spki,
1864
+ issuerSpki: spki,
1865
+ kind: { ca: true },
1866
+ sign: signer(pair.privateKey)
1867
+ });
1868
+ return { key: pair.privateKey, cert, spki, notAfter, name: name2 };
1869
+ }
1870
+ get cert() {
1871
+ return this.record.cert;
1872
+ }
1873
+ get pem() {
1874
+ return pem(this.record.cert);
1875
+ }
1876
+ async issue(host, spki, now = Date.now()) {
1877
+ const name2 = host.toLowerCase();
1878
+ if (!validLeafName(name2)) throw new LeafNameError(`no certificate for ${host}`);
1879
+ return certificate({
1880
+ serial: randomSerial(),
1881
+ issuer: this.record.name,
1882
+ subject: { commonName: name2.length <= 64 ? name2 : "SLICC realm leaf" },
1883
+ notBefore: new Date(now - 60 * 60 * 1e3),
1884
+ notAfter: new Date(now + LEAF_LIFETIME),
1885
+ spki,
1886
+ issuerSpki: this.record.spki,
1887
+ kind: { ca: false, host: name2 },
1888
+ sign: signer(this.record.key)
1889
+ });
1890
+ }
1891
+ };
1892
+ function signer(key) {
1893
+ return async (tbs) => new Uint8Array(await crypto.subtle.sign(SIGN, key, tbs));
1894
+ }
1895
+ var opened = /* @__PURE__ */ new WeakMap();
1896
+ function realmCa(owner, store) {
1897
+ const cas = opened.get(store) ?? /* @__PURE__ */ new Map();
1898
+ opened.set(store, cas);
1899
+ let ca = cas.get(owner);
1900
+ if (!ca) {
1901
+ const opening = RealmCa.open(owner, store);
1902
+ ca = opening;
1903
+ cas.set(owner, opening);
1904
+ opening.catch(() => {
1905
+ if (cas.get(owner) === opening) cas.delete(owner);
1906
+ });
1907
+ }
1908
+ return ca;
1909
+ }
1910
+
1911
+ // src/kernel/net/tls-engine.ts
1912
+ var SCRATCH = 64 * 1024;
1913
+ var EOF = -1;
1914
+ var TlsError = class extends Error {
1915
+ code;
1916
+ constructor(code, message2) {
1917
+ super(message2);
1918
+ this.code = code;
1919
+ }
1920
+ };
1921
+ var TlsEngine = class {
1922
+ m;
1923
+ scratch;
1924
+ constructor(m) {
1925
+ this.m = m;
1926
+ this.scratch = m._malloc(SCRATCH);
1927
+ if (!this.scratch) throw new Error("tls engine: out of memory");
1928
+ }
1929
+ put(bytes) {
1930
+ const ptr = this.m._malloc(bytes.length + 1);
1931
+ if (!ptr) throw new Error("tls engine: out of memory");
1932
+ this.m.HEAPU8.set(bytes, ptr);
1933
+ this.m.HEAPU8[ptr + bytes.length] = 0;
1934
+ return ptr;
1935
+ }
1936
+ taken(n) {
1937
+ return this.m.HEAPU8.slice(this.scratch, this.scratch + n);
1938
+ }
1939
+ error(code, what) {
1940
+ this.m._tls_strerror(code, this.scratch, 256);
1941
+ const end = this.m.HEAPU8.indexOf(0, this.scratch);
1942
+ const text = new TextDecoder().decode(this.m.HEAPU8.subarray(this.scratch, end));
1943
+ return new TlsError(code, `${what}: ${text || code}`);
1944
+ }
1945
+ leaf() {
1946
+ const ptr = this.m._tls_leaf_new();
1947
+ if (!ptr) throw this.error(this.m._tls_last_error(), "leaf key");
1948
+ return new TlsLeaf(this, this.m, ptr);
1949
+ }
1950
+ session(leaf, host) {
1951
+ const name2 = this.put(new TextEncoder().encode(host));
1952
+ try {
1953
+ const ptr = this.m._tls_session_new(leaf.ptr, name2);
1954
+ if (!ptr) throw this.error(this.m._tls_last_error(), "tls session");
1955
+ return new TlsSession(this, this.m, ptr);
1956
+ } finally {
1957
+ this.m._free(name2);
1958
+ }
1959
+ }
1960
+ withBytes(bytes, fn) {
1961
+ const ptr = this.put(bytes);
1962
+ try {
1963
+ return fn(ptr);
1964
+ } finally {
1965
+ this.m._free(ptr);
1966
+ }
1967
+ }
1968
+ intoScratch(fn) {
1969
+ const n = fn(this.scratch, SCRATCH);
1970
+ return n > 0 ? this.taken(n) : n;
1971
+ }
1972
+ };
1973
+ var TlsLeaf = class {
1974
+ engine;
1975
+ m;
1976
+ ptr;
1977
+ constructor(engine, m, ptr) {
1978
+ this.engine = engine;
1979
+ this.m = m;
1980
+ this.ptr = ptr;
1981
+ }
1982
+ spki() {
1983
+ const out = this.engine.intoScratch((ptr, max) => this.m._tls_leaf_spki(this.ptr, ptr, max));
1984
+ if (typeof out === "number") throw this.engine.error(out, "leaf public key");
1985
+ return out;
1986
+ }
1987
+ addCert(der) {
1988
+ const ret = this.engine.withBytes(
1989
+ der,
1990
+ (ptr) => this.m._tls_leaf_add_cert(this.ptr, ptr, der.length)
1991
+ );
1992
+ if (ret !== 0) throw this.engine.error(ret, "leaf certificate");
1993
+ }
1994
+ release() {
1995
+ this.m._tls_leaf_free(this.ptr);
1996
+ }
1997
+ };
1998
+ var TlsSession = class {
1999
+ engine;
2000
+ m;
2001
+ ptr;
2002
+ constructor(engine, m, ptr) {
2003
+ this.engine = engine;
2004
+ this.m = m;
2005
+ this.ptr = ptr;
2006
+ }
2007
+ feed(bytes) {
2008
+ return this.engine.withBytes(bytes, (ptr) => this.m._tls_feed(this.ptr, ptr, bytes.length));
2009
+ }
2010
+ feedEnd() {
2011
+ this.m._tls_feed_eof(this.ptr);
2012
+ }
2013
+ read() {
2014
+ const out = this.engine.intoScratch((ptr, max) => this.m._tls_read(this.ptr, ptr, max));
2015
+ if (out instanceof Uint8Array) return out;
2016
+ if (out === 0) return "more";
2017
+ if (out === EOF) return "eof";
2018
+ throw this.engine.error(out, "tls");
2019
+ }
2020
+ write(bytes) {
2021
+ const n = this.engine.withBytes(bytes, (ptr) => this.m._tls_write(this.ptr, ptr, bytes.length));
2022
+ if (n < 0) throw this.engine.error(n, "tls");
2023
+ return n;
2024
+ }
2025
+ take() {
2026
+ if (this.m._tls_out_size(this.ptr) === 0) return new Uint8Array(0);
2027
+ const out = this.engine.intoScratch((ptr, max) => this.m._tls_out_take(this.ptr, ptr, max));
2028
+ return out instanceof Uint8Array ? out : new Uint8Array(0);
2029
+ }
2030
+ close() {
2031
+ this.m._tls_close(this.ptr);
2032
+ }
2033
+ free() {
2034
+ this.m._tls_session_free(this.ptr);
2035
+ }
2036
+ };
2037
+ var loading = /* @__PURE__ */ new WeakMap();
2038
+ function loadTlsEngine(load) {
2039
+ let engine = loading.get(load);
2040
+ if (!engine) {
2041
+ const started = load().then((m) => new TlsEngine(m));
2042
+ engine = started;
2043
+ loading.set(load, started);
2044
+ started.catch(() => {
2045
+ if (loading.get(load) === started) loading.delete(load);
2046
+ });
2047
+ }
2048
+ return engine;
2049
+ }
2050
+
2051
+ // src/kernel/net/tls-tunnel.ts
2052
+ var LEAF_REUSE = LEAF_LIFETIME - 24 * 60 * 60 * 1e3;
2053
+ var LEAF_CACHE = 256;
2054
+ var CIPHER_READ = 64 * 1024;
2055
+ var TlsStream = class {
2056
+ session;
2057
+ cipherIn;
2058
+ cipherOut;
2059
+ pending = new Uint8Array(0);
2060
+ ended = false;
2061
+ constructor(session, cipherIn, cipherOut) {
2062
+ this.session = session;
2063
+ this.cipherIn = cipherIn;
2064
+ this.cipherOut = cipherOut;
2065
+ }
2066
+ async flush(signal) {
2067
+ for (let out = this.session.take(); out.length > 0; out = this.session.take()) {
2068
+ await this.cipherOut.write(out, signal);
2069
+ }
2070
+ }
2071
+ feedPending() {
2072
+ if (this.pending.length === 0) return;
2073
+ const n = this.session.feed(this.pending);
2074
+ this.pending = this.pending.subarray(n);
2075
+ }
2076
+ async read(_max, signal) {
2077
+ for (; ; ) {
2078
+ this.feedPending();
2079
+ const got = this.session.read();
2080
+ await this.flush(signal);
2081
+ if (got instanceof Uint8Array) return got;
2082
+ if (got === "eof") return new Uint8Array(0);
2083
+ if (this.pending.length > 0) continue;
2084
+ if (this.ended) {
2085
+ this.session.feedEnd();
2086
+ const last = this.session.read();
2087
+ await this.flush(signal);
2088
+ return last instanceof Uint8Array ? last : new Uint8Array(0);
2089
+ }
2090
+ const cipher = await this.cipherIn.read(CIPHER_READ, signal);
2091
+ if (cipher.length === 0) this.ended = true;
2092
+ else this.pending = cipher;
2093
+ }
2094
+ }
2095
+ async write(bytes, signal) {
2096
+ for (let at = 0; at < bytes.length; ) {
2097
+ const n = this.session.write(bytes.subarray(at, at + 16 * 1024));
2098
+ await this.flush(signal);
2099
+ at += n;
2100
+ }
2101
+ return bytes.length;
2102
+ }
2103
+ async close(signal) {
2104
+ this.session.close();
2105
+ await this.flush(signal);
2106
+ }
2107
+ };
2108
+ var TlsTerminator = class {
2109
+ options;
2110
+ leaves = /* @__PURE__ */ new Map();
2111
+ now;
2112
+ constructor(options) {
2113
+ this.options = options;
2114
+ this.now = options.now ?? Date.now;
2115
+ }
2116
+ leafFor(engine, host) {
2117
+ const now = this.now();
2118
+ const cached = this.leaves.get(host);
2119
+ if (cached && cached.expires > now) {
2120
+ this.leaves.delete(host);
2121
+ this.leaves.set(host, cached);
2122
+ return cached.leaf;
2123
+ }
2124
+ if (cached) void this.drop(host, cached);
2125
+ const leaf = this.issue(engine, host, now);
2126
+ const entry = { leaf, expires: now + LEAF_REUSE };
2127
+ this.leaves.set(host, entry);
2128
+ leaf.catch(() => {
2129
+ if (this.leaves.get(host) === entry) this.leaves.delete(host);
2130
+ });
2131
+ for (const [oldest, old] of this.leaves) {
2132
+ if (this.leaves.size <= LEAF_CACHE) break;
2133
+ void this.drop(oldest, old);
2134
+ }
2135
+ return leaf;
2136
+ }
2137
+ async drop(host, entry) {
2138
+ if (this.leaves.get(host) === entry) this.leaves.delete(host);
2139
+ (await entry.leaf.catch(() => void 0))?.release();
2140
+ }
2141
+ async issue(engine, host, now) {
2142
+ const ca = await this.options.ca();
2143
+ const leaf = engine.leaf();
2144
+ try {
2145
+ leaf.addCert(await ca.issue(host, leaf.spki(), now));
2146
+ leaf.addCert(ca.cert);
2147
+ return leaf;
2148
+ } catch (e) {
2149
+ leaf.release();
2150
+ throw e;
2151
+ }
2152
+ }
2153
+ async close() {
2154
+ await Promise.all([...this.leaves].map(([host, entry]) => this.drop(host, entry)));
2155
+ }
2156
+ handler = async (conn, incoming, target, signal, serveHttp) => {
2157
+ const engine = await this.options.engine();
2158
+ const host = target.host.replace(/^\[|\]$/g, "");
2159
+ const leaf = await this.leafFor(engine, host);
2160
+ const session = engine.session(leaf, host);
2161
+ try {
2162
+ const stream = new TlsStream(session, cipherSource(incoming), conn);
2163
+ const origin = `https://${authority(target)}`;
2164
+ await serveHttp(stream, stream, origin);
2165
+ await stream.close(signal).catch(() => void 0);
2166
+ } finally {
2167
+ session.free();
2168
+ }
2169
+ };
2170
+ };
2171
+ function authority(target) {
2172
+ return target.port === 443 ? target.host : `${target.host}:${target.port}`;
2173
+ }
2174
+ function cipherSource(incoming) {
2175
+ return { read: (max, signal) => incoming.some(max, signal) };
2176
+ }
2177
+
2178
+ // src/kernel/net/network.ts
2179
+ var NO_TRANSPORT = "slicc-kernel: no network transport (createKernel({ network: { transport } }))";
2180
+ var TLS_ENGINE_PACKAGE = "@ai-ecoverse/wasm-tls-engine";
2181
+ var CA_PATH = "/etc/ssl/certs/slicc-kernel-ca.pem";
2182
+ var PROXY_URL = `http://127.0.0.1:${REALM_PROXY_PORT}`;
2183
+ var NO_PROXY = "localhost,.localhost,127.0.0.1,127.0.0.0/8";
2184
+ var CA_OWNER = "slicc-kernel";
2185
+ function networkEnv() {
2186
+ return {
2187
+ http_proxy: PROXY_URL,
2188
+ https_proxy: PROXY_URL,
2189
+ HTTP_PROXY: PROXY_URL,
2190
+ HTTPS_PROXY: PROXY_URL,
2191
+ no_proxy: NO_PROXY,
2192
+ NO_PROXY,
2193
+ SSL_CERT_FILE: CA_PATH,
2194
+ CURL_CA_BUNDLE: CA_PATH,
2195
+ GIT_SSL_CAINFO: CA_PATH
2196
+ };
2197
+ }
2198
+ function missingTransport() {
2199
+ return {
2200
+ traits: { manualRedirects: true, encodedBodies: true, maxRequestBody: 0 },
2201
+ fetch: () => Promise.reject(Object.assign(new Error(NO_TRANSPORT), { status: 502 }))
2202
+ };
2203
+ }
2204
+ function memoryCaStore() {
2205
+ const records = /* @__PURE__ */ new Map();
2206
+ return {
2207
+ get: async (owner) => records.get(owner),
2208
+ swap: async (owner, expected, record) => {
2209
+ const kept = winner(records.get(owner), expected, record);
2210
+ records.set(owner, kept);
2211
+ return kept;
2212
+ }
2213
+ };
2214
+ }
2215
+ function caStore(metadata) {
2216
+ return metadata === false ? memoryCaStore() : indexedDbCaStore(`${metadata}-ca`);
2217
+ }
2218
+ function base64(bytes) {
2219
+ let text = "";
2220
+ for (let i = 0; i < bytes.length; i += 32768) {
2221
+ text += String.fromCharCode(...bytes.subarray(i, i + 32768));
2222
+ }
2223
+ return btoa(text);
2224
+ }
2225
+ function packageTlsEngine(fs, modules) {
2226
+ const dir = `${modules.replace(/\/+$/, "")}/${TLS_ENGINE_PACKAGE}/dist`;
2227
+ return async () => {
2228
+ let glue;
2229
+ let wasmBinary;
2230
+ try {
2231
+ [glue, wasmBinary] = await Promise.all([
2232
+ fs.readFileBuffer(`${dir}/slicc-tls-engine.mjs`),
2233
+ fs.readFileBuffer(`${dir}/slicc-tls-engine.wasm`)
2234
+ ]);
2235
+ } catch {
2236
+ throw new Error(`TLS needs ${TLS_ENGINE_PACKAGE} installed in ${modules}`);
2237
+ }
2238
+ const url = `data:text/javascript;base64,${base64(glue)}`;
2239
+ const factory = await import(url);
2240
+ return factory.default({ wasmBinary, locateFile: (path) => path });
2241
+ };
2242
+ }
2243
+ function kernelCa(store) {
2244
+ return () => realmCa(CA_OWNER, store);
2245
+ }
2246
+ function kernelTlsEngine(load) {
2247
+ return () => loadTlsEngine(load);
2248
+ }
2249
+ async function writeCaFile(fs, ca) {
2250
+ const { pem: pem2 } = await ca();
2251
+ const current = await fs.exists(CA_PATH) ? await fs.readFile(CA_PATH) : void 0;
2252
+ if (current === pem2) return;
2253
+ await fs.mkdir(CA_PATH.slice(0, CA_PATH.lastIndexOf("/")), { recursive: true });
2254
+ await fs.writeFile(CA_PATH, pem2);
2255
+ }
2256
+ function enableNetwork(net, options) {
2257
+ let proxy;
2258
+ const transport = options.transport ?? missingTransport();
2259
+ net.activate({ family: "inet", host: "127.0.0.1", port: REALM_PROXY_PORT }, () => {
2260
+ const tls = new TlsTerminator({
2261
+ ca: options.ca,
2262
+ engine: options.engine,
2263
+ ...options.now ? { now: options.now } : {}
2264
+ });
2265
+ const started = new RealmProxy({
2266
+ net,
2267
+ transport,
2268
+ tunnel: tls.handler,
2269
+ tunnelReady: async () => {
2270
+ await options.engine();
2271
+ }
2272
+ });
2273
+ proxy = started;
2274
+ void started.closed.then(async () => {
2275
+ if (proxy === started) proxy = void 0;
2276
+ await tls.close();
2277
+ });
2278
+ });
2279
+ return () => proxy;
2280
+ }
2281
+
2282
+ // src/kernel/net/remote-transport.ts
2283
+ var TransportError = class extends Error {
2284
+ status;
2285
+ constructor(message2, status) {
2286
+ super(message2);
2287
+ this.status = status;
2288
+ }
2289
+ };
2290
+ var RemoteTransport = class {
2291
+ traits;
2292
+ port;
2293
+ waiting = /* @__PURE__ */ new Map();
2294
+ nextId = 0;
2295
+ constructor(port, traits) {
2296
+ this.port = port;
2297
+ this.traits = traits;
2298
+ }
2299
+ receive(reply) {
2300
+ const waiter = this.waiting.get(reply.nid);
2301
+ if (!waiter) return;
2302
+ this.waiting.delete(reply.nid);
2303
+ if (reply.net === "error") waiter.reject(new TransportError(reply.message, reply.status));
2304
+ else waiter.resolve(reply);
2305
+ }
2306
+ ask(call) {
2307
+ return new Promise((resolve, reject) => {
2308
+ this.waiting.set(call.nid, { resolve, reject });
2309
+ this.port.postMessage(call);
2310
+ });
2311
+ }
2312
+ cancel(nid) {
2313
+ const waiter = this.waiting.get(nid);
2314
+ this.waiting.delete(nid);
2315
+ waiter?.reject(new TransportError("the request was cancelled"));
2316
+ this.port.postMessage({ net: "cancel", nid });
2317
+ }
2318
+ async fetch(request2) {
2319
+ const nid = ++this.nextId;
2320
+ const { signal } = request2;
2321
+ signal.throwIfAborted();
2322
+ let finished = false;
2323
+ const abort = () => {
2324
+ finished = true;
2325
+ this.cancel(nid);
2326
+ };
2327
+ signal.addEventListener("abort", abort, { once: true });
2328
+ const finish = () => {
2329
+ if (finished) return;
2330
+ finished = true;
2331
+ signal.removeEventListener("abort", abort);
2332
+ };
2333
+ let head;
2334
+ try {
2335
+ head = await this.ask({
2336
+ net: "fetch",
2337
+ nid,
2338
+ url: request2.url,
2339
+ method: request2.method,
2340
+ headers: request2.headers,
2341
+ ...request2.body ? { body: request2.body } : {}
2342
+ });
2343
+ } catch (e) {
2344
+ finish();
2345
+ throw e;
2346
+ }
2347
+ const { status, statusText, headers } = head;
2348
+ const read = async () => {
2349
+ if (finished) return void 0;
2350
+ const reply = await this.ask({ net: "read", nid }).catch((e) => {
2351
+ finish();
2352
+ throw e;
2353
+ });
2354
+ if (reply.net === "chunk") return reply.bytes;
2355
+ finish();
2356
+ return void 0;
2357
+ };
2358
+ const cancel = async () => {
2359
+ if (finished) return;
2360
+ finish();
2361
+ this.cancel(nid);
2362
+ };
2363
+ return {
2364
+ status,
2365
+ statusText,
2366
+ headers,
2367
+ cancel,
2368
+ body: {
2369
+ [Symbol.asyncIterator]: () => ({
2370
+ next: async () => {
2371
+ const bytes = await read();
2372
+ return bytes ? { value: bytes, done: false } : { value: void 0, done: true };
2373
+ },
2374
+ return: async () => {
2375
+ await cancel();
2376
+ return { value: void 0, done: true };
2377
+ }
2378
+ })
2379
+ }
2380
+ };
2381
+ }
2382
+ };
2383
+
2384
+ // src/kernel/signals.ts
2385
+ var SIG = {
2386
+ HUP: 1,
2387
+ INT: 2,
2388
+ QUIT: 3,
2389
+ ILL: 4,
2390
+ TRAP: 5,
2391
+ ABRT: 6,
2392
+ BUS: 7,
2393
+ FPE: 8,
2394
+ KILL: 9,
2395
+ USR1: 10,
2396
+ SEGV: 11,
2397
+ USR2: 12,
2398
+ PIPE: 13,
2399
+ ALRM: 14,
2400
+ TERM: 15,
2401
+ STKFLT: 16,
2402
+ CHLD: 17,
2403
+ CONT: 18,
2404
+ STOP: 19,
2405
+ TSTP: 20,
2406
+ TTIN: 21,
2407
+ TTOU: 22,
2408
+ URG: 23,
2409
+ XCPU: 24,
2410
+ XFSZ: 25,
2411
+ VTALRM: 26,
2412
+ PROF: 27,
2413
+ WINCH: 28,
2414
+ POLL: 29,
2415
+ PWR: 30,
2416
+ SYS: 31
2417
+ };
2418
+ var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
2419
+ var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
2420
+ function defaultAction(sig) {
2421
+ if (sig === SIG.CONT) return "continue";
2422
+ if (STOPPING.has(sig)) return "stop";
2423
+ return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
2424
+ }
2425
+ function isSignal(sig) {
2426
+ return Number.isInteger(sig) && sig >= 1 && sig <= 31;
2427
+ }
2428
+ function sigbit(sig) {
2429
+ return 1 << sig;
2430
+ }
2431
+ var SIGNAL_BY_NAME = {
2432
+ SIGINT: SIG.INT,
2433
+ SIGTERM: SIG.TERM,
2434
+ SIGKILL: SIG.KILL,
2435
+ SIGSTOP: SIG.STOP,
2436
+ SIGCONT: SIG.CONT
2437
+ };
2438
+
2439
+ // src/commands.ts
2440
+ var NAME = /^[A-Za-z0-9._+-]+$/;
2441
+ var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
2442
+ var PACKAGE = /\$\{package\}/g;
2443
+ function inside(pkg, rel) {
2444
+ if (typeof rel !== "string" || rel === "") return void 0;
2445
+ const clean = rel.replace(/^\.\//, "");
2446
+ if (clean.startsWith("/") || clean.split("/").includes("..")) return void 0;
2447
+ return `${pkg}/${clean}`;
2448
+ }
2449
+ function envOf(pkg, raw) {
2450
+ const env = {};
2451
+ if (!raw || typeof raw !== "object") return env;
2452
+ for (const [key, value] of Object.entries(raw)) {
2453
+ if (ENV_KEY.test(key) && typeof value === "string") env[key] = value.replace(PACKAGE, pkg);
2454
+ }
2455
+ return env;
2456
+ }
2457
+ function argsOf(pkg, raw) {
2458
+ if (!Array.isArray(raw) || raw.length === 0) return void 0;
2459
+ if (!raw.every((arg) => typeof arg === "string")) return void 0;
2460
+ return raw.map((arg) => arg.replace(PACKAGE, pkg));
2461
+ }
2462
+ function commandsOf(pkg, manifest) {
2463
+ const slicc = manifest.slicc;
2464
+ if (!slicc || typeof slicc !== "object" || (slicc.abi ?? "emscripten") !== "emscripten")
2465
+ return [];
2466
+ const entries = slicc.commands;
2467
+ if (!entries || typeof entries !== "object") return [];
2468
+ const shared = envOf(pkg, slicc.env);
2469
+ const out = [];
2470
+ for (const [name2, raw] of Object.entries(entries)) {
2471
+ if (!NAME.test(name2) || name2 === "." || name2 === ".." || !raw || typeof raw !== "object") {
2472
+ continue;
2473
+ }
2474
+ const glue = inside(pkg, raw.glue);
2475
+ const wasm = inside(pkg, raw.wasm);
2476
+ if ((raw.abi ?? "emscripten") !== "emscripten" || !glue || !wasm) continue;
2477
+ const argv0 = typeof raw.argv0 === "string" && raw.argv0 ? raw.argv0 : name2;
2478
+ const args = argsOf(pkg, raw.args);
2479
+ const env = { ...shared, ...envOf(pkg, raw.env) };
2480
+ out.push({
2481
+ name: name2,
2482
+ glue,
2483
+ wasm,
2484
+ argv0,
2485
+ ...args ? { args } : {},
2486
+ ...Object.keys(env).length > 0 ? { env } : {}
2487
+ });
2488
+ }
2489
+ return out;
2490
+ }
2491
+ async function packages(fs, modules) {
2492
+ const names = await fs.readdir(modules).catch(() => []);
2493
+ const dirs = [];
2494
+ for (const name2 of names) {
2495
+ if (name2.startsWith(".")) continue;
2496
+ if (!name2.startsWith("@")) {
2497
+ dirs.push(`${modules}/${name2}`);
2498
+ continue;
2499
+ }
2500
+ for (const inner of await fs.readdir(`${modules}/${name2}`).catch(() => [])) {
2501
+ dirs.push(`${modules}/${name2}/${inner}`);
751
2502
  }
752
- void Promise.resolve(file.release()).catch(() => void 0);
753
- throw new KernelError("EMFILE");
754
2503
  }
755
- installAt(fd, file) {
756
- if (fd < 0 || fd >= _FdTable.MAX_FDS) {
757
- void Promise.resolve(file.release()).catch(() => void 0);
758
- throw new KernelError("EBADF");
2504
+ return dirs;
2505
+ }
2506
+ async function scanCommands(fs, modules) {
2507
+ const found = /* @__PURE__ */ new Map();
2508
+ for (const pkg of await packages(fs, modules)) {
2509
+ let manifest;
2510
+ try {
2511
+ manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
2512
+ } catch {
2513
+ continue;
2514
+ }
2515
+ for (const command of commandsOf(pkg, manifest)) {
2516
+ if (!found.has(command.name)) found.set(command.name, command);
759
2517
  }
760
- const previous = this.fds.get(fd);
761
- this.fds.set(fd, file);
762
- this.cloexec.delete(fd);
763
- this.status.delete(fd);
764
- void Promise.resolve(previous?.release()).catch(() => void 0);
765
2518
  }
766
- dup(fd, min = 0) {
767
- return this.install(this.get(fd).retain(), min);
2519
+ if (!found.has("sh") && found.has("bash")) {
2520
+ found.set("sh", { ...found.get("bash"), name: "sh", argv0: "sh" });
768
2521
  }
769
- dup2(oldFd, newFd) {
770
- const file = this.get(oldFd);
771
- if (oldFd !== newFd) this.installAt(newFd, file.retain());
772
- return newFd;
2522
+ return found;
2523
+ }
2524
+
2525
+ // src/fs/commands.ts
2526
+ var S_IFDIR2 = 16384;
2527
+ var S_IFREG2 = 32768;
2528
+ var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
2529
+ var MAX_LINKS2 = 40;
2530
+ function missing2(err) {
2531
+ return err?.code === "ENOENT";
2532
+ }
2533
+ function synthetic(directory, path) {
2534
+ const epoch = /* @__PURE__ */ new Date(0);
2535
+ return {
2536
+ isFile: !directory,
2537
+ isDirectory: directory,
2538
+ isSymbolicLink: false,
2539
+ size: 0,
2540
+ mode: (directory ? S_IFDIR2 : S_IFREG2) | 493,
2541
+ mtime: epoch,
2542
+ atime: epoch,
2543
+ ctime: epoch,
2544
+ ino: inodeOf(path)
2545
+ };
2546
+ }
2547
+ async function followLinks(fs, path) {
2548
+ let current = path;
2549
+ for (let hops = 0; hops < MAX_LINKS2; hops++) {
2550
+ const st = await fs.lstat(current).catch(() => void 0);
2551
+ if (!st?.isSymbolicLink) return current;
2552
+ const parent = current.slice(0, current.lastIndexOf("/")) || "/";
2553
+ current = fs.resolvePath(parent, await fs.readlink(current));
2554
+ }
2555
+ return current;
2556
+ }
2557
+ function withCommandDirs(fs, names) {
2558
+ async function children2(path) {
2559
+ if (path === "/") return ["bin", "usr"];
2560
+ if (path === "/usr") return ["bin"];
2561
+ if (path === "/bin" || path === "/usr/bin") return [...await names()];
2562
+ return void 0;
773
2563
  }
774
- close(fd) {
775
- const file = this.get(fd);
776
- this.fds.delete(fd);
777
- this.cloexec.delete(fd);
778
- this.status.delete(fd);
779
- return file.release();
2564
+ async function virtual(path) {
2565
+ if (path === "/usr" || path === "/bin" || path === "/usr/bin") return synthetic(true, path);
2566
+ const name2 = COMMAND.exec(path)?.[1];
2567
+ return name2 !== void 0 && (await names()).has(name2) ? synthetic(false, path) : void 0;
780
2568
  }
781
- fork() {
782
- const child = new _FdTable();
783
- for (const [fd, file] of this.fds) child.fds.set(fd, file.retain());
784
- for (const fd of this.cloexec) child.cloexec.add(fd);
785
- for (const [fd, flags] of this.status) child.status.set(fd, flags);
786
- return child;
2569
+ async function linked(path) {
2570
+ return await virtual(path) ?? virtual(await followLinks(fs, path));
787
2571
  }
788
- async closeAll() {
789
- const files = [...this.fds.values()];
790
- this.fds.clear();
791
- this.cloexec.clear();
792
- this.status.clear();
793
- await Promise.allSettled(files.map((file) => Promise.resolve().then(() => file.release())));
2572
+ async function stat(path, real) {
2573
+ try {
2574
+ return await real(path);
2575
+ } catch (err) {
2576
+ const found = missing2(err) ? await linked(path) : void 0;
2577
+ if (found) return found;
2578
+ throw err;
2579
+ }
794
2580
  }
795
- };
2581
+ return {
2582
+ resolvePath: (base, path) => fs.resolvePath(base, path),
2583
+ readFile: (path) => fs.readFile(path),
2584
+ async readFileBuffer(path) {
2585
+ try {
2586
+ return await fs.readFileBuffer(path);
2587
+ } catch (err) {
2588
+ if (missing2(err) && (await linked(path))?.isFile) return new Uint8Array(0);
2589
+ throw err;
2590
+ }
2591
+ },
2592
+ writeFile: (path, content) => fs.writeFile(path, content),
2593
+ async exists(path) {
2594
+ return await fs.exists(path) || await linked(path) !== void 0;
2595
+ },
2596
+ stat: (path) => stat(path, (p) => fs.stat(p)),
2597
+ lstat: (path) => stat(path, (p) => fs.lstat(p)),
2598
+ async readdir(path) {
2599
+ const extra = await children2(path);
2600
+ if (!extra) return fs.readdir(path);
2601
+ const real = await fs.readdir(path).catch((err) => {
2602
+ if (missing2(err)) return [];
2603
+ throw err;
2604
+ });
2605
+ return [.../* @__PURE__ */ new Set([...real, ...extra])].sort();
2606
+ },
2607
+ async readdirStat(path) {
2608
+ const extra = await children2(path);
2609
+ if (!extra) return fs.readdirStat(path);
2610
+ const real = await fs.readdirStat(path).catch((err) => {
2611
+ if (missing2(err)) return [];
2612
+ throw err;
2613
+ });
2614
+ const seen = new Set(real.map(([name2]) => name2));
2615
+ const base = path === "/" ? "" : path;
2616
+ const added = extra.filter((name2) => !seen.has(name2));
2617
+ const virtuals = await Promise.all(
2618
+ added.map(
2619
+ async (name2) => [
2620
+ name2,
2621
+ await virtual(`${base}/${name2}`)
2622
+ ]
2623
+ )
2624
+ );
2625
+ return [...real, ...virtuals].sort(([a], [b]) => a < b ? -1 : 1);
2626
+ },
2627
+ mkdir: (path, options) => fs.mkdir(path, options),
2628
+ rm: (path, options) => fs.rm(path, options),
2629
+ rename: (from, to) => fs.rename(from, to),
2630
+ symlink: (target, path) => fs.symlink(target, path),
2631
+ readlink: (path) => fs.readlink(path),
2632
+ chmod: (path, mode) => fs.chmod(path, mode),
2633
+ utimes: (path, atime, mtime) => fs.utimes(path, atime, mtime)
2634
+ };
2635
+ }
796
2636
 
797
2637
  // src/kernel/children.ts
798
2638
  var SpawnError = class extends Error {
@@ -816,12 +2656,12 @@ var CONTINUED_STATUS = 65535;
816
2656
  function waitStatus(code, termsig) {
817
2657
  return termsig ? termsig & 127 : (code & 255) << 8;
818
2658
  }
819
- function concat(chunks) {
2659
+ function concat3(chunks) {
820
2660
  const out = new Uint8Array(chunks.reduce((n, c) => n + c.length, 0));
821
2661
  let offset = 0;
822
- for (const chunk of chunks) {
823
- out.set(chunk, offset);
824
- offset += chunk.length;
2662
+ for (const chunk2 of chunks) {
2663
+ out.set(chunk2, offset);
2664
+ offset += chunk2.length;
825
2665
  }
826
2666
  return out;
827
2667
  }
@@ -973,7 +2813,7 @@ var ChildTable = class {
973
2813
  if (!slots || !chunks) return new Uint8Array(0);
974
2814
  slots.delete(slot);
975
2815
  if (slots.size === 0) this.leftovers.delete(pid);
976
- return concat(chunks);
2816
+ return concat3(chunks);
977
2817
  }
978
2818
  };
979
2819
 
@@ -999,12 +2839,12 @@ function syncError(code, label) {
999
2839
 
1000
2840
  // src/realm/sync-fs-dispatch.ts
1001
2841
  function toErrno(err) {
1002
- const message = err instanceof Error ? err.message : String(err);
2842
+ const message2 = err instanceof Error ? err.message : String(err);
1003
2843
  const code = err?.code;
1004
2844
  if (typeof code === "string" && /^E[A-Z]+$/.test(code)) {
1005
- return { ok: false, errno: code, message };
2845
+ return { ok: false, errno: code, message: message2 };
1006
2846
  }
1007
- return { ok: false, errno: "EIO", message };
2847
+ return { ok: false, errno: "EIO", message: message2 };
1008
2848
  }
1009
2849
  function statJson(s) {
1010
2850
  return {
@@ -1013,21 +2853,14 @@ function statJson(s) {
1013
2853
  isSymbolicLink: s.isSymbolicLink,
1014
2854
  size: s.size,
1015
2855
  mode: s.mode,
1016
- mtimeMs: s.mtime.getTime()
2856
+ mtimeMs: s.mtime.getTime(),
2857
+ atimeMs: s.atime.getTime(),
2858
+ ctimeMs: s.ctime.getTime(),
2859
+ ino: s.ino
1017
2860
  };
1018
2861
  }
1019
2862
  async function readdirStat(fs, dir) {
1020
- const names = await fs.readdir(dir);
1021
- const base = dir === "/" ? "" : dir;
1022
- return Promise.all(
1023
- names.map(async (name) => {
1024
- try {
1025
- return [name, statJson(await fs.lstat(`${base}/${name}`))];
1026
- } catch {
1027
- return [name, null];
1028
- }
1029
- })
1030
- );
2863
+ return (await fs.readdirStat(dir)).map(([name2, stat]) => [name2, stat && statJson(stat)]);
1031
2864
  }
1032
2865
  var done = { ok: true, kind: "void" };
1033
2866
  function json(value) {
@@ -1146,7 +2979,7 @@ function sabViews(sab) {
1146
2979
  var PENDING_TTL_MS = SYNC_FS_REQUEST_TIMEOUT_MS + 5e3;
1147
2980
  function attachSyncSabResponder(port, sab, token, opts) {
1148
2981
  const views = sabViews(sab);
1149
- const { header, window } = views;
2982
+ const { header: header2, window } = views;
1150
2983
  const pending = /* @__PURE__ */ new Map();
1151
2984
  let disposed = false;
1152
2985
  const { dispatch } = opts;
@@ -1157,18 +2990,18 @@ function attachSyncSabResponder(port, sab, token, opts) {
1157
2990
  pending.delete(id);
1158
2991
  }
1159
2992
  function publish(id, entry, offset) {
1160
- const chunk = Math.min(window.byteLength, entry.payload.byteLength - offset);
1161
- if (chunk > 0) window.set(entry.payload.subarray(offset, offset + chunk));
1162
- Atomics.store(header, SAB_I_STATUS, entry.status);
1163
- Atomics.store(header, SAB_I_TOTAL, entry.payload.byteLength);
1164
- Atomics.store(header, SAB_I_CHUNK, chunk);
1165
- Atomics.store(header, SAB_I_OFFSET, offset);
1166
- Atomics.store(header, SAB_I_SEQ, id);
1167
- Atomics.store(header, SAB_I_STATE, SAB_STATE_READY);
1168
- Atomics.notify(header, SAB_I_STATE);
1169
- if (offset + chunk >= entry.payload.byteLength) drop(id);
1170
- }
1171
- function settle(id, result) {
2993
+ const chunk2 = Math.min(window.byteLength, entry.payload.byteLength - offset);
2994
+ if (chunk2 > 0) window.set(entry.payload.subarray(offset, offset + chunk2));
2995
+ Atomics.store(header2, SAB_I_STATUS, entry.status);
2996
+ Atomics.store(header2, SAB_I_TOTAL, entry.payload.byteLength);
2997
+ Atomics.store(header2, SAB_I_CHUNK, chunk2);
2998
+ Atomics.store(header2, SAB_I_OFFSET, offset);
2999
+ Atomics.store(header2, SAB_I_SEQ, id);
3000
+ Atomics.store(header2, SAB_I_STATE, SAB_STATE_READY);
3001
+ Atomics.notify(header2, SAB_I_STATE);
3002
+ if (offset + chunk2 >= entry.payload.byteLength) drop(id);
3003
+ }
3004
+ function settle2(id, result) {
1172
3005
  if (disposed) return;
1173
3006
  const { status, payload } = encodeSabResult(result);
1174
3007
  drop(id);
@@ -1187,7 +3020,7 @@ function attachSyncSabResponder(port, sab, token, opts) {
1187
3020
  const entry = pending.get(data.id);
1188
3021
  const offset = data.offset;
1189
3022
  if (!entry || typeof offset !== "number" || !Number.isInteger(offset) || offset < 0 || offset > entry.payload.byteLength) {
1190
- settle(data.id, { ok: false, errno: "EIO", message: "sync-sab: bad continuation" });
3023
+ settle2(data.id, { ok: false, errno: "EIO", message: "sync-sab: bad continuation" });
1191
3024
  return;
1192
3025
  }
1193
3026
  publish(data.id, entry, offset);
@@ -1201,7 +3034,7 @@ function attachSyncSabResponder(port, sab, token, opts) {
1201
3034
  try {
1202
3035
  dispatched = dispatch(req);
1203
3036
  } catch (err) {
1204
- settle(id, {
3037
+ settle2(id, {
1205
3038
  ok: false,
1206
3039
  errno: "EIO",
1207
3040
  message: err instanceof Error ? err.message : String(err)
@@ -1209,8 +3042,8 @@ function attachSyncSabResponder(port, sab, token, opts) {
1209
3042
  return;
1210
3043
  }
1211
3044
  void dispatched.then(
1212
- (result) => settle(id, result),
1213
- (err) => settle(id, {
3045
+ (result) => settle2(id, result),
3046
+ (err) => settle2(id, {
1214
3047
  ok: false,
1215
3048
  errno: "EIO",
1216
3049
  message: err instanceof Error ? err.message : String(err)
@@ -1228,6 +3061,112 @@ function attachSyncSabResponder(port, sab, token, opts) {
1228
3061
  };
1229
3062
  }
1230
3063
 
3064
+ // src/kernel/net/http-syscalls.ts
3065
+ var HTTP_OPS = ["net-request", "net-read", "net-close"];
3066
+ var EMPTY = new Uint8Array(0);
3067
+ function message(e) {
3068
+ return e instanceof Error ? e.message : String(e);
3069
+ }
3070
+ async function* once(bytes) {
3071
+ yield bytes;
3072
+ }
3073
+ var HttpHandles = class {
3074
+ transport;
3075
+ open = /* @__PURE__ */ new Map();
3076
+ nextHandle = 0;
3077
+ constructor(transport) {
3078
+ this.transport = transport;
3079
+ }
3080
+ syscall(req) {
3081
+ if (req.op === "net-request") return this.request(req);
3082
+ if (req.op === "net-read") return this.read(req.handle, req.max);
3083
+ return this.close(req.handle);
3084
+ }
3085
+ add(abort, body, cancel) {
3086
+ const handle = ++this.nextHandle;
3087
+ this.open.set(handle, {
3088
+ abort,
3089
+ body: body[Symbol.asyncIterator](),
3090
+ left: EMPTY,
3091
+ done: false,
3092
+ reading: Promise.resolve(),
3093
+ cancel
3094
+ });
3095
+ return handle;
3096
+ }
3097
+ async request(req) {
3098
+ const abort = new AbortController();
3099
+ try {
3100
+ const response = await this.transport.fetch({
3101
+ url: req.url,
3102
+ method: req.method,
3103
+ headers: req.headers,
3104
+ ...req.body ? { body: req.body } : {},
3105
+ signal: abort.signal
3106
+ });
3107
+ const handle = this.add(abort, response.body, () => response.cancel());
3108
+ const head = {
3109
+ handle,
3110
+ status: response.status,
3111
+ statusText: response.statusText,
3112
+ url: req.url,
3113
+ headers: response.headers
3114
+ };
3115
+ return { ok: true, kind: "json", json: head };
3116
+ } catch (e) {
3117
+ const text = message(e);
3118
+ if (text === NO_TRANSPORT) return { ok: false, errno: "ENETUNREACH", message: text };
3119
+ const status = e.status;
3120
+ if (typeof status !== "number" || status === 502) {
3121
+ return { ok: false, errno: "ECONNREFUSED", message: text };
3122
+ }
3123
+ const body = new TextEncoder().encode(text);
3124
+ const handle = this.add(abort, once(body), async () => void 0);
3125
+ const head = {
3126
+ handle,
3127
+ status,
3128
+ statusText: "",
3129
+ url: req.url,
3130
+ headers: [["content-type", "text/plain; charset=utf-8"]]
3131
+ };
3132
+ return { ok: true, kind: "json", json: head };
3133
+ }
3134
+ }
3135
+ async read(handle, max) {
3136
+ const entry = this.open.get(handle);
3137
+ if (!entry) return { ok: false, errno: "EBADF", message: `no request ${handle}` };
3138
+ const turn = entry.reading.then(() => this.take(entry, max));
3139
+ entry.reading = turn.then(() => void 0);
3140
+ return turn;
3141
+ }
3142
+ async take(entry, max) {
3143
+ if (entry.left.length === 0 && !entry.done) {
3144
+ try {
3145
+ const next = await entry.body.next();
3146
+ if (next.done) entry.done = true;
3147
+ else entry.left = next.value;
3148
+ } catch (e) {
3149
+ entry.done = true;
3150
+ return { ok: false, errno: "EIO", message: message(e) };
3151
+ }
3152
+ }
3153
+ const bytes = entry.left.subarray(0, Math.max(0, max));
3154
+ entry.left = entry.left.subarray(bytes.length);
3155
+ return { ok: true, kind: "bytes", bytes };
3156
+ }
3157
+ async close(handle) {
3158
+ const entry = this.open.get(handle);
3159
+ if (!entry) return { ok: false, errno: "EBADF", message: `no request ${handle}` };
3160
+ this.open.delete(handle);
3161
+ entry.abort.abort();
3162
+ await entry.cancel().catch(() => void 0);
3163
+ return { ok: true, kind: "void" };
3164
+ }
3165
+ async closeAll() {
3166
+ await Promise.all([...this.open.keys()].map((handle) => this.close(handle)));
3167
+ }
3168
+ };
3169
+
1231
3170
  // src/kernel/tty.ts
1232
3171
  var ICRNL = 256;
1233
3172
  var IUTF8 = 16384;
@@ -1396,9 +3335,9 @@ var KernelTty = class {
1396
3335
  return n;
1397
3336
  }
1398
3337
  rubOut(n) {
1399
- const dropped = this.line.splice(this.line.length - n, n);
1400
- const utf8 = (this.termios.c_iflag & IUTF8) !== 0;
1401
- const cells = utf8 ? dropped.filter((b) => !isContinuation(b)).length : dropped.length;
3338
+ const dropped2 = this.line.splice(this.line.length - n, n);
3339
+ const utf82 = (this.termios.c_iflag & IUTF8) !== 0;
3340
+ const cells = utf82 ? dropped2.filter((b) => !isContinuation(b)).length : dropped2.length;
1402
3341
  if (cells > 0 && this.termios.c_lflag & ECHO) {
1403
3342
  this.output(encoder2.encode("\b \b".repeat(cells)), false);
1404
3343
  }
@@ -1448,9 +3387,9 @@ var KernelTty = class {
1448
3387
  }
1449
3388
  const out = new Uint8Array(n);
1450
3389
  let at = 0;
1451
- for (const chunk of chunks) {
1452
- out.set(chunk, at);
1453
- at += chunk.length;
3390
+ for (const chunk2 of chunks) {
3391
+ out.set(chunk2, at);
3392
+ at += chunk2.length;
1454
3393
  }
1455
3394
  return out;
1456
3395
  }
@@ -1547,9 +3486,9 @@ var PtyPair = class {
1547
3486
  }
1548
3487
  const out = new Uint8Array(status + n);
1549
3488
  let at = status;
1550
- for (const chunk of chunks) {
1551
- out.set(chunk, at);
1552
- at += chunk.length;
3489
+ for (const chunk2 of chunks) {
3490
+ out.set(chunk2, at);
3491
+ at += chunk2.length;
1553
3492
  }
1554
3493
  return out;
1555
3494
  }
@@ -1720,6 +3659,391 @@ async function waitReady(fds, read, write, deadline, interrupted2) {
1720
3659
  }
1721
3660
  }
1722
3661
 
3662
+ // src/kernel/socket.ts
3663
+ var SOMAXCONN = 4096;
3664
+ var EPHEMERAL_FIRST = 32768;
3665
+ var EPHEMERAL_LAST = 60999;
3666
+ var SOL_SOCKET = 1;
3667
+ var SO_TYPE = 3;
3668
+ var SO_ERROR = 4;
3669
+ var SO_SNDBUF = 7;
3670
+ var SO_RCVBUF = 8;
3671
+ var SO_ACCEPTCONN = 30;
3672
+ var SO_DOMAIN = 39;
3673
+ var SOCK_STREAM = 1;
3674
+ var AF_UNIX = 1;
3675
+ var AF_INET = 2;
3676
+ var SHUT_RD = 0;
3677
+ var SHUT_WR = 1;
3678
+ var SHUT_RDWR = 2;
3679
+ function isLoopback(host) {
3680
+ return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host);
3681
+ }
3682
+ async function pipeCall(op) {
3683
+ try {
3684
+ return await op();
3685
+ } catch (e) {
3686
+ if (e instanceof PipeError) throw new KernelError(e.code);
3687
+ throw e;
3688
+ }
3689
+ }
3690
+ var Changes = class {
3691
+ waiters = [];
3692
+ wait(signal) {
3693
+ if (signal?.aborted) return Promise.reject(new KernelError("EINTR"));
3694
+ return new Promise((resolve, reject) => {
3695
+ const onAbort = () => {
3696
+ this.waiters = this.waiters.filter((w) => w !== waiter);
3697
+ reject(new KernelError("EINTR"));
3698
+ };
3699
+ const waiter = () => {
3700
+ signal?.removeEventListener("abort", onAbort);
3701
+ resolve();
3702
+ };
3703
+ this.waiters.push(waiter);
3704
+ signal?.addEventListener("abort", onAbort, { once: true });
3705
+ });
3706
+ }
3707
+ wake() {
3708
+ const waiters = this.waiters;
3709
+ this.waiters = [];
3710
+ for (const resolve of waiters) resolve();
3711
+ }
3712
+ };
3713
+ function link(a, b) {
3714
+ const ab = new KernelPipe();
3715
+ const ba = new KernelPipe();
3716
+ for (const pipe of [ab, ba]) {
3717
+ pipe.openRead();
3718
+ pipe.openWrite();
3719
+ }
3720
+ a.attach({ rx: ba, tx: ab });
3721
+ b.attach({ rx: ab, tx: ba });
3722
+ }
3723
+ var KernelSocket = class _KernelSocket {
3724
+ net;
3725
+ domain;
3726
+ state = "open";
3727
+ local;
3728
+ peer;
3729
+ link;
3730
+ readShut = false;
3731
+ writeShut = false;
3732
+ queue = [];
3733
+ backlog = 0;
3734
+ bound = false;
3735
+ changes = new Changes();
3736
+ options = /* @__PURE__ */ new Map();
3737
+ constructor(net, domain) {
3738
+ this.net = net;
3739
+ this.domain = domain;
3740
+ }
3741
+ attach(link2) {
3742
+ this.link = link2;
3743
+ this.state = "connected";
3744
+ this.changes.wake();
3745
+ }
3746
+ get listening() {
3747
+ return this.state === "listening";
3748
+ }
3749
+ connection() {
3750
+ if (!this.link || this.state !== "connected") throw new KernelError("ENOTCONN");
3751
+ return this.link;
3752
+ }
3753
+ async read(max, signal) {
3754
+ const { rx } = this.connection();
3755
+ if (this.readShut || max <= 0) return new Uint8Array(0);
3756
+ return pipeCall(() => rx.read(max, signal));
3757
+ }
3758
+ async peek(max, signal) {
3759
+ const { rx } = this.connection();
3760
+ if (this.readShut || max <= 0) return new Uint8Array(0);
3761
+ return pipeCall(() => rx.peek(max, signal));
3762
+ }
3763
+ async write(bytes, signal) {
3764
+ const { tx } = this.connection();
3765
+ if (this.writeShut) throw new KernelError("EPIPE");
3766
+ return pipeCall(() => tx.write(bytes, signal));
3767
+ }
3768
+ poll() {
3769
+ if (this.state === "listening") {
3770
+ return { readable: this.queue.length > 0, writable: false, hangup: false };
3771
+ }
3772
+ if (this.state !== "connected" || !this.link) {
3773
+ return { readable: false, writable: false, hangup: true };
3774
+ }
3775
+ const { rx, tx } = this.link;
3776
+ return {
3777
+ readable: this.readShut || rx.readReady,
3778
+ writable: this.writeShut || tx.writeReady,
3779
+ hangup: rx.writersGone && tx.readersGone
3780
+ };
3781
+ }
3782
+ changed(signal) {
3783
+ const own = this.changes.wait(signal);
3784
+ if (this.state !== "connected" || !this.link) return own;
3785
+ const waits = [own, this.link.rx.changed(signal), this.link.tx.changed(signal)];
3786
+ for (const wait of waits) wait.catch(() => void 0);
3787
+ return pipeCall(() => Promise.race(waits));
3788
+ }
3789
+ bind(addr) {
3790
+ if (this.state !== "open" || this.local) throw new KernelError("EINVAL");
3791
+ if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
3792
+ this.local = this.net.bind(this, addr);
3793
+ this.bound = true;
3794
+ }
3795
+ listen(backlog) {
3796
+ if (this.state === "connected") throw new KernelError("EISCONN");
3797
+ if (this.state !== "open" && this.state !== "listening") throw new KernelError("EINVAL");
3798
+ if (!this.local) {
3799
+ if (this.domain === "unix") throw new KernelError("EINVAL");
3800
+ this.bind({ family: "inet", host: "0.0.0.0", port: 0 });
3801
+ }
3802
+ this.backlog = Math.min(Math.max(backlog, 1), SOMAXCONN);
3803
+ this.state = "listening";
3804
+ }
3805
+ async accept(signal, nonblock = false) {
3806
+ for (; ; ) {
3807
+ if (this.state !== "listening") throw new KernelError("EINVAL");
3808
+ const next = this.queue.shift();
3809
+ if (next) return next;
3810
+ if (nonblock) throw new KernelError("EAGAIN");
3811
+ await this.changes.wait(signal);
3812
+ }
3813
+ }
3814
+ offer(server) {
3815
+ if (this.state !== "listening" || this.queue.length >= this.backlog) return false;
3816
+ this.queue.push(server);
3817
+ this.changes.wake();
3818
+ return true;
3819
+ }
3820
+ connect(addr) {
3821
+ if (this.state === "connected") throw new KernelError("EISCONN");
3822
+ if (this.state !== "open") throw new KernelError("EINVAL");
3823
+ if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
3824
+ const target = this.net.target(addr);
3825
+ if (!this.local) {
3826
+ if (addr.family === "inet") {
3827
+ this.local = this.net.bind(this, { family: "inet", host: "127.0.0.1", port: 0 });
3828
+ this.bound = true;
3829
+ } else this.local = { family: "unix", path: "" };
3830
+ }
3831
+ const server = new _KernelSocket(this.net, this.domain);
3832
+ server.local = addr.family === "inet" ? { ...addr, host: canonicalHost2(addr.host) } : addr;
3833
+ server.peer = this.local;
3834
+ this.peer = server.local;
3835
+ link(this, server);
3836
+ if (!target.offer(server)) {
3837
+ this.unlink();
3838
+ server.close();
3839
+ throw new KernelError("ECONNREFUSED");
3840
+ }
3841
+ }
3842
+ unlink() {
3843
+ const link2 = this.link;
3844
+ if (link2) {
3845
+ link2.rx.closeRead();
3846
+ link2.tx.closeWrite();
3847
+ }
3848
+ this.link = void 0;
3849
+ this.peer = void 0;
3850
+ this.state = "open";
3851
+ }
3852
+ shutdown(how) {
3853
+ const { rx, tx } = this.connection();
3854
+ if (how !== SHUT_RD && how !== SHUT_WR && how !== SHUT_RDWR) throw new KernelError("EINVAL");
3855
+ if (how !== SHUT_WR && !this.readShut) {
3856
+ this.readShut = true;
3857
+ rx.closeRead();
3858
+ }
3859
+ if (how !== SHUT_RD && !this.writeShut) {
3860
+ this.writeShut = true;
3861
+ tx.closeWrite();
3862
+ }
3863
+ this.changes.wake();
3864
+ }
3865
+ getOption(level, name2) {
3866
+ if (level === SOL_SOCKET) {
3867
+ switch (name2) {
3868
+ case SO_TYPE:
3869
+ return SOCK_STREAM;
3870
+ case SO_ERROR:
3871
+ return 0;
3872
+ case SO_ACCEPTCONN:
3873
+ return this.state === "listening" ? 1 : 0;
3874
+ case SO_DOMAIN:
3875
+ return this.domain === "inet" ? AF_INET : AF_UNIX;
3876
+ case SO_SNDBUF:
3877
+ case SO_RCVBUF:
3878
+ return this.options.get(`${level}:${name2}`) ?? PIPE_CAPACITY;
3879
+ }
3880
+ }
3881
+ return this.options.get(`${level}:${name2}`) ?? 0;
3882
+ }
3883
+ setOption(level, name2, value) {
3884
+ this.options.set(`${level}:${name2}`, value);
3885
+ }
3886
+ close() {
3887
+ const was = this.state;
3888
+ this.state = "closed";
3889
+ if (was === "listening") {
3890
+ for (const pending of this.queue.splice(0)) pending.close();
3891
+ }
3892
+ if (this.link) {
3893
+ if (!this.readShut) this.link.rx.closeRead();
3894
+ if (!this.writeShut) this.link.tx.closeWrite();
3895
+ this.readShut = true;
3896
+ this.writeShut = true;
3897
+ }
3898
+ if (this.bound && this.local) this.net.release(this.local, this);
3899
+ this.bound = false;
3900
+ this.changes.wake();
3901
+ }
3902
+ static pair(net, domain) {
3903
+ const a = new _KernelSocket(net, domain);
3904
+ const b = new _KernelSocket(net, domain);
3905
+ const name2 = domain === "inet" ? { family: "inet", host: "127.0.0.1", port: 0 } : { family: "unix", path: "" };
3906
+ a.local = b.local = a.peer = b.peer = name2;
3907
+ link(a, b);
3908
+ return [a, b];
3909
+ }
3910
+ };
3911
+ function canonicalHost2(host) {
3912
+ return host === "0.0.0.0" ? "127.0.0.1" : host;
3913
+ }
3914
+ function keyOf(addr) {
3915
+ return addr.family === "inet" ? `inet:${addr.port}` : `unix:${addr.path}`;
3916
+ }
3917
+ var LoopbackNet = class {
3918
+ bound = /* @__PURE__ */ new Map();
3919
+ activators = /* @__PURE__ */ new Map();
3920
+ nextEphemeral = EPHEMERAL_FIRST;
3921
+ activate(addr, start) {
3922
+ this.activators.set(keyOf(addr), start);
3923
+ }
3924
+ socket(domain) {
3925
+ return new KernelSocket(this, domain);
3926
+ }
3927
+ listen(addr, backlog = 128) {
3928
+ const socket = this.socket(addr.family);
3929
+ socket.bind(addr);
3930
+ socket.listen(backlog);
3931
+ return socket;
3932
+ }
3933
+ connect(addr) {
3934
+ const socket = this.socket(addr.family);
3935
+ socket.connect(addr);
3936
+ return socket;
3937
+ }
3938
+ bind(socket, addr) {
3939
+ if (addr.family === "unix") {
3940
+ if (addr.path === "") throw new KernelError("EINVAL");
3941
+ } else if (addr.host !== "0.0.0.0" && !isLoopback(addr.host)) {
3942
+ throw new KernelError("EADDRNOTAVAIL");
3943
+ }
3944
+ const named = addr.family === "inet" && addr.port === 0 ? { ...addr, port: this.ephemeral() } : addr;
3945
+ const key = keyOf(named);
3946
+ if (this.bound.has(key)) throw new KernelError("EADDRINUSE");
3947
+ this.bound.set(key, socket);
3948
+ return named;
3949
+ }
3950
+ release(addr, socket) {
3951
+ const key = keyOf(addr);
3952
+ if (this.bound.get(key) === socket) this.bound.delete(key);
3953
+ }
3954
+ target(addr) {
3955
+ if (addr.family === "inet" && !isLoopback(canonicalHost2(addr.host))) {
3956
+ throw new KernelError("ENETUNREACH");
3957
+ }
3958
+ const key = keyOf(addr);
3959
+ if (!this.bound.has(key)) this.activators.get(key)?.();
3960
+ const socket = this.bound.get(key);
3961
+ if (!socket?.listening) throw new KernelError("ECONNREFUSED");
3962
+ return socket;
3963
+ }
3964
+ ephemeral() {
3965
+ const span = EPHEMERAL_LAST - EPHEMERAL_FIRST + 1;
3966
+ for (let i = 0; i < span; i++) {
3967
+ const port = this.nextEphemeral;
3968
+ this.nextEphemeral = port === EPHEMERAL_LAST ? EPHEMERAL_FIRST : port + 1;
3969
+ if (!this.bound.has(`inet:${port}`)) return port;
3970
+ }
3971
+ throw new KernelError("EADDRINUSE");
3972
+ }
3973
+ };
3974
+
3975
+ // src/kernel/socket-syscalls.ts
3976
+ var SOCKET_OPS = [
3977
+ "sock-open",
3978
+ "sock-pair",
3979
+ "sock-bind",
3980
+ "sock-listen",
3981
+ "sock-accept",
3982
+ "sock-connect",
3983
+ "sock-shutdown",
3984
+ "sock-name",
3985
+ "sock-getopt",
3986
+ "sock-setopt"
3987
+ ];
3988
+ function socketAt(fds, fd) {
3989
+ const file = fds.get(fd).file;
3990
+ if (!(file instanceof KernelSocket)) throw new KernelError("ENOTSOCK");
3991
+ return file;
3992
+ }
3993
+ var ok = (json3) => json3 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json3 };
3994
+ async function socketSyscall(req, proc) {
3995
+ const { fds, net } = proc;
3996
+ switch (req.op) {
3997
+ case "sock-open":
3998
+ return ok(fds.install(new OpenFile(net.socket(req.domain)), 3));
3999
+ case "sock-pair": {
4000
+ const [a, b] = KernelSocket.pair(net, req.domain);
4001
+ const first = fds.install(new OpenFile(a), 3);
4002
+ try {
4003
+ return ok([first, fds.install(new OpenFile(b), 3)]);
4004
+ } catch (e) {
4005
+ await Promise.resolve(fds.close(first));
4006
+ throw e;
4007
+ }
4008
+ }
4009
+ case "sock-bind":
4010
+ socketAt(fds, req.fd).bind(req.addr);
4011
+ return ok();
4012
+ case "sock-listen":
4013
+ socketAt(fds, req.fd).listen(req.backlog);
4014
+ return ok();
4015
+ case "sock-accept":
4016
+ return ok(await accept(proc, req.fd, req.nonblock));
4017
+ case "sock-connect":
4018
+ socketAt(fds, req.fd).connect(req.addr);
4019
+ if (req.nonblock) throw new KernelError("EINPROGRESS");
4020
+ return ok();
4021
+ case "sock-shutdown":
4022
+ socketAt(fds, req.fd).shutdown(req.how);
4023
+ return ok();
4024
+ case "sock-name": {
4025
+ const socket = socketAt(fds, req.fd);
4026
+ const addr = req.peer ? socket.peer : socket.local;
4027
+ if (req.peer && !addr) throw new KernelError("ENOTCONN");
4028
+ return ok(addr ?? unnamed(socket.domain));
4029
+ }
4030
+ case "sock-getopt":
4031
+ return ok(socketAt(fds, req.fd).getOption(req.level, req.name));
4032
+ case "sock-setopt":
4033
+ socketAt(fds, req.fd).setOption(req.level, req.name, req.value);
4034
+ return ok();
4035
+ }
4036
+ }
4037
+ function unnamed(domain) {
4038
+ return domain === "inet" ? { family: "inet", host: "0.0.0.0", port: 0 } : { family: "unix", path: "" };
4039
+ }
4040
+ async function accept(proc, fd, nonblock) {
4041
+ const listener = socketAt(proc.fds, fd);
4042
+ const waits = !nonblock && !pollFile(listener).readable;
4043
+ const socket = await listener.accept(waits ? proc.blocking() : void 0, nonblock);
4044
+ return { fd: proc.fds.install(new OpenFile(socket), 3), peer: socket.peer };
4045
+ }
4046
+
1723
4047
  // src/kernel/vfs-file.ts
1724
4048
  var O_ACCMODE = 3;
1725
4049
  var O_WRONLY = 1;
@@ -1749,14 +4073,14 @@ var VfsNode = class {
1749
4073
  fs;
1750
4074
  path;
1751
4075
  orphaned;
1752
- constructor(fs, path, contents, orphaned = false, dirty = false) {
4076
+ constructor(fs, path, contents2, orphaned = false, dirty = false) {
1753
4077
  this.fs = fs;
1754
4078
  this.path = path;
1755
4079
  this.orphaned = orphaned;
1756
- if (contents !== void 0) this.data = new Uint8Array(contents);
4080
+ if (contents2 !== void 0) this.data = new Uint8Array(contents2);
1757
4081
  else if (orphaned) this.data = new Uint8Array(0);
1758
4082
  this.length = this.data?.length ?? 0;
1759
- if (dirty && contents !== void 0) this.markDirty();
4083
+ if (dirty && contents2 !== void 0) this.markDirty();
1760
4084
  }
1761
4085
  serial(op) {
1762
4086
  const next = this.queue.then(op);
@@ -1776,8 +4100,8 @@ var VfsNode = class {
1776
4100
  }
1777
4101
  return this.data;
1778
4102
  }
1779
- replace(contents) {
1780
- this.data = new Uint8Array(contents);
4103
+ replace(contents2) {
4104
+ this.data = new Uint8Array(contents2);
1781
4105
  this.length = this.data.length;
1782
4106
  this.missing = false;
1783
4107
  this.markDirty();
@@ -1858,13 +4182,13 @@ var VfsNodes = class {
1858
4182
  node.opens++;
1859
4183
  return node;
1860
4184
  }
1861
- adopt(path, contents, dirty) {
4185
+ adopt(path, contents2, dirty) {
1862
4186
  let node = this.byPath.get(path);
1863
4187
  if (!node) {
1864
- node = new VfsNode(this.fs, path, contents, false, dirty);
4188
+ node = new VfsNode(this.fs, path, contents2, false, dirty);
1865
4189
  this.byPath.set(path, node);
1866
4190
  } else if (dirty) {
1867
- node.replace(contents);
4191
+ node.replace(contents2);
1868
4192
  }
1869
4193
  node.opens++;
1870
4194
  return node;
@@ -1888,15 +4212,15 @@ var VfsNodes = class {
1888
4212
  }
1889
4213
  renamed(from, to) {
1890
4214
  if (from === to) return;
1891
- const moved = [];
4215
+ const moved2 = [];
1892
4216
  for (const [p, node] of this.byPath) {
1893
- if (within2(p, from)) moved.push(node);
4217
+ if (within2(p, from)) moved2.push(node);
1894
4218
  else if (within2(p, to)) {
1895
4219
  node.orphaned = true;
1896
4220
  this.byPath.delete(p);
1897
4221
  }
1898
4222
  }
1899
- for (const node of moved) {
4223
+ for (const node of moved2) {
1900
4224
  this.byPath.delete(node.path);
1901
4225
  node.path = to + node.path.slice(from.length);
1902
4226
  this.byPath.set(node.path, node);
@@ -1986,6 +4310,12 @@ var JOB_OPS = /* @__PURE__ */ new Set([
1986
4310
  function isJobSyscall(req) {
1987
4311
  return JOB_OPS.has(req.op);
1988
4312
  }
4313
+ function isHttpSyscall(req) {
4314
+ return req.op.startsWith("net-");
4315
+ }
4316
+ function isSocketSyscall(req) {
4317
+ return req.op.startsWith("sock-");
4318
+ }
1989
4319
  function isPtySyscall(req) {
1990
4320
  return req.op.startsWith("pty-");
1991
4321
  }
@@ -2044,6 +4374,8 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
2044
4374
  "tty-pgrp-set",
2045
4375
  "sig-mask",
2046
4376
  "sig-pause",
4377
+ ...SOCKET_OPS,
4378
+ ...HTTP_OPS,
2047
4379
  ...PTY_OPS
2048
4380
  ]);
2049
4381
  function isWasmSyscall(req) {
@@ -2064,6 +4396,7 @@ var WasmProcess = class {
2064
4396
  resumed = Promise.resolve();
2065
4397
  wake;
2066
4398
  stateListeners = [];
4399
+ net;
2067
4400
  nodes;
2068
4401
  pid;
2069
4402
  fds;
@@ -2133,6 +4466,8 @@ var WasmProcess = class {
2133
4466
  if (isFdSyscall(req)) return await this.fdSyscall(req);
2134
4467
  if (isTtySyscall(req)) return this.ttySyscall(req);
2135
4468
  if (isJobSyscall(req)) return this.jobSyscall(req);
4469
+ if (isSocketSyscall(req)) return await this.socketSyscall(req);
4470
+ if (isHttpSyscall(req)) return await this.httpSyscall(req);
2136
4471
  if (isPtySyscall(req)) {
2137
4472
  const { ptys, jobs } = this.options;
2138
4473
  return ptySyscall(req, { pid: this.pid, fds: this.fds, ptys, jobs });
@@ -2330,7 +4665,7 @@ var WasmProcess = class {
2330
4665
  const flags = this.fds.statusFlags(fd);
2331
4666
  return {
2332
4667
  tty: file.tty !== void 0,
2333
- kind: kernelFdKind(file),
4668
+ kind: file instanceof KernelSocket ? "socket" : kernelFdKind(file),
2334
4669
  ...file.heldMeta ? { meta: file.heldMeta } : {},
2335
4670
  ...flags !== void 0 ? { flags } : {},
2336
4671
  ...this.fds.closesOnExec(fd) ? { cloexec: true } : {},
@@ -2403,6 +4738,19 @@ var WasmProcess = class {
2403
4738
  return { ok: true, kind: "json", json: jobs.setsid(this.pid) };
2404
4739
  }
2405
4740
  }
4741
+ httpSyscall(req) {
4742
+ const http = this.options.http;
4743
+ if (!http) return Promise.resolve({ ok: false, errno: "ENETUNREACH", message: NO_TRANSPORT });
4744
+ return http.syscall(req);
4745
+ }
4746
+ socketSyscall(req) {
4747
+ this.net ??= this.options.net ?? new LoopbackNet();
4748
+ return socketSyscall(req, {
4749
+ fds: this.fds,
4750
+ net: this.net,
4751
+ blocking: () => this.blockingSignal()
4752
+ });
4753
+ }
2406
4754
  tty(fd) {
2407
4755
  const file = this.fds.get(fd).file;
2408
4756
  const tty = file.tty ?? file.pty?.slave;
@@ -2501,6 +4849,7 @@ var WasmProcess = class {
2501
4849
  this.clearAlarm();
2502
4850
  for (const pid of this.children.pids()) this.options.onReap?.(pid);
2503
4851
  await this.fds.closeAll();
4852
+ await this.options.http?.closeAll();
2504
4853
  }
2505
4854
  };
2506
4855
 
@@ -2522,7 +4871,7 @@ function inheritedFds(fds) {
2522
4871
  return fds.numbers().filter((fd) => fd > 2 && !fds.get(fd).file.held).map((fd) => {
2523
4872
  const open = fds.get(fd);
2524
4873
  const flags = fds.statusFlags(fd);
2525
- const kind = kernelFdKind(open.file);
4874
+ const kind = open.file instanceof KernelSocket ? "socket" : kernelFdKind(open.file);
2526
4875
  const meta = open.file.heldMeta;
2527
4876
  return {
2528
4877
  fd,
@@ -2536,7 +4885,7 @@ function inheritedFds(fds) {
2536
4885
  }
2537
4886
  function spawnWasmProcess(opts) {
2538
4887
  const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
2539
- const header = new Int32Array(sab, 0, SAB_HEADER_I32);
4888
+ const header2 = new Int32Array(sab, 0, SAB_HEADER_I32);
2540
4889
  const process = new WasmProcess(opts.pid, opts.fds, {
2541
4890
  spawner: opts.spawner,
2542
4891
  forker: opts.forker,
@@ -2544,10 +4893,12 @@ function spawnWasmProcess(opts) {
2544
4893
  kill: opts.kill,
2545
4894
  jobs: opts.jobs,
2546
4895
  ptys: opts.ptys,
4896
+ net: opts.net,
4897
+ http: opts.http,
2547
4898
  onReap: opts.onReap,
2548
- onPending: (sig) => void Atomics.or(header, SAB_I_SIGNALS, sigbit(sig)),
2549
- onTimer: (which) => void Atomics.or(header, SAB_I_TIMERS, 1 << which),
2550
- hasPending: () => Atomics.load(header, SAB_I_SIGNALS) !== 0 || Atomics.load(header, SAB_I_TIMERS) !== 0,
4899
+ onPending: (sig) => void Atomics.or(header2, SAB_I_SIGNALS, sigbit(sig)),
4900
+ onTimer: (which) => void Atomics.or(header2, SAB_I_TIMERS, 1 << which),
4901
+ hasPending: () => Atomics.load(header2, SAB_I_SIGNALS) !== 0 || Atomics.load(header2, SAB_I_TIMERS) !== 0,
2551
4902
  raise: (sig) => signal(sig)
2552
4903
  });
2553
4904
  const token = mintSyncFsToken({ fs: opts.fs, cwd: opts.cwd });
@@ -2557,8 +4908,8 @@ function spawnWasmProcess(opts) {
2557
4908
  return dispatchSyncFs(req);
2558
4909
  };
2559
4910
  const responder = attachSyncSabResponder(worker, sab, token, { dispatch });
2560
- let settle;
2561
- const exited = new Promise((resolve) => settle = resolve);
4911
+ let settle2;
4912
+ const exited = new Promise((resolve) => settle2 = resolve);
2562
4913
  let done3 = false;
2563
4914
  let endedBy;
2564
4915
  const finish = (code, sig) => {
@@ -2571,8 +4922,8 @@ function spawnWasmProcess(opts) {
2571
4922
  revokeSyncFsToken(token);
2572
4923
  worker.terminate();
2573
4924
  void process.exit().then(
2574
- () => settle(code),
2575
- () => settle(code)
4925
+ () => settle2(code),
4926
+ () => settle2(code)
2576
4927
  );
2577
4928
  };
2578
4929
  const onMessage = (event) => {
@@ -2682,9 +5033,9 @@ var JobTable = class {
2682
5033
  this.terminals.delete(pid);
2683
5034
  return pid;
2684
5035
  }
2685
- terminalNamed(name) {
5036
+ terminalNamed(name2) {
2686
5037
  const known = /* @__PURE__ */ new Set([...this.terminals.values(), ...this.foreground.keys()]);
2687
- return [...known].find((tty) => tty.name === name);
5038
+ return [...known].find((tty) => tty.name === name2);
2688
5039
  }
2689
5040
  controllingTerminal(pid) {
2690
5041
  const member = this.members.get(pid);
@@ -2754,12 +5105,12 @@ function childHandle(handle) {
2754
5105
  onState: (listener) => handle.onState(listener)
2755
5106
  };
2756
5107
  }
2757
- function concat2(chunks) {
2758
- const out = new Uint8Array(chunks.reduce((n, chunk) => n + chunk.length, 0));
5108
+ function concat4(chunks) {
5109
+ const out = new Uint8Array(chunks.reduce((n, chunk2) => n + chunk2.length, 0));
2759
5110
  let offset = 0;
2760
- for (const chunk of chunks) {
2761
- out.set(chunk, offset);
2762
- offset += chunk.length;
5111
+ for (const chunk2 of chunks) {
5112
+ out.set(chunk2, offset);
5113
+ offset += chunk2.length;
2763
5114
  }
2764
5115
  return out;
2765
5116
  }
@@ -2776,13 +5127,23 @@ var Launcher = class {
2776
5127
  orphans = /* @__PURE__ */ new Set();
2777
5128
  jobs = new JobTable();
2778
5129
  ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
5130
+ net = new LoopbackNet();
5131
+ ca;
5132
+ transport;
2779
5133
  nextPid = 1e3;
2780
5134
  terminals = 0;
2781
5135
  constructor(options) {
2782
5136
  this.base = options.fs;
2783
5137
  this.createWorker = options.createWorker;
2784
5138
  this.modulesDir = options.modules ?? "/node_modules";
2785
- this.env = options.env ?? {};
5139
+ this.env = { ...networkEnv(), ...options.env };
5140
+ this.ca = kernelCa(options.caStore ?? memoryCaStore());
5141
+ this.transport = options.transport ?? missingTransport();
5142
+ enableNetwork(this.net, {
5143
+ transport: this.transport,
5144
+ engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
5145
+ ca: this.ca
5146
+ });
2786
5147
  this.fs = withCommandDirs(options.fs, async () => new Set((await this.commands()).keys()));
2787
5148
  }
2788
5149
  commands() {
@@ -2790,12 +5151,14 @@ var Launcher = class {
2790
5151
  return this.catalog;
2791
5152
  }
2792
5153
  async resolve(file, argv0, cwd) {
2793
- const name = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
2794
- if (name !== void 0) {
2795
- const command = (await this.commands()).get(name);
5154
+ const name2 = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
5155
+ if (name2 !== void 0) {
5156
+ const command = (await this.commands()).get(name2);
2796
5157
  return command && targetOf(command);
2797
5158
  }
2798
- const glue = this.fs.resolvePath(cwd, file);
5159
+ const glue = await followLinks(this.base, this.fs.resolvePath(cwd, file));
5160
+ const linked = COMMAND2.exec(glue)?.[1];
5161
+ if (linked !== void 0) return this.resolve(`/bin/${linked}`, argv0, cwd);
2799
5162
  const wasm = modulePath(glue);
2800
5163
  if (!await this.base.exists(glue) || !await this.base.exists(wasm)) return void 0;
2801
5164
  return { glue, wasm, argv0: baseName(argv0 || file) };
@@ -2883,6 +5246,8 @@ var Launcher = class {
2883
5246
  kill: (target, sig) => this.kill(target, sig),
2884
5247
  jobs: this.jobs,
2885
5248
  ptys: this.ptys,
5249
+ net: this.net,
5250
+ http: new HttpHandles(this.transport),
2886
5251
  onReap: (child) => this.reaped(child),
2887
5252
  ...req.fork ? { fork: req.fork } : {},
2888
5253
  ...req.ppid !== void 0 ? { ppid: req.ppid } : {}
@@ -2923,6 +5288,7 @@ var Launcher = class {
2923
5288
  }
2924
5289
  async prepare() {
2925
5290
  for (const dir of SHARED_DIRS) await this.base.mkdir(dir, { recursive: true });
5291
+ await writeCaFile(this.base, this.ca).catch(() => void 0);
2926
5292
  }
2927
5293
  environment(cwd, extra) {
2928
5294
  return { PATH: "/usr/bin:/bin", HOME: "/home", ...this.env, PWD: cwd, ...extra };
@@ -2954,7 +5320,7 @@ var Launcher = class {
2954
5320
  COLORTERM: "truecolor",
2955
5321
  ...options.env
2956
5322
  });
2957
- const report = (message) => options.onData(encoder3.encode(`${message}\r
5323
+ const report = (message2) => options.onData(encoder3.encode(`${message2}\r
2958
5324
  `));
2959
5325
  const handle = await this.launch(planned, { env, cwd, fds, report });
2960
5326
  leader = handle.pid;
@@ -2982,11 +5348,11 @@ var Launcher = class {
2982
5348
  };
2983
5349
  const stdout = collect(out, options.onStdout);
2984
5350
  const stderr = collect(err, options.onStderr);
2985
- const report = (message) => stderr(encoder3.encode(`${message}
5351
+ const report = (message2) => stderr(encoder3.encode(`${message2}
2986
5352
  `));
2987
5353
  if (!planned) {
2988
5354
  report(`${file}: command not found`);
2989
- return { status: NOT_FOUND, stdout: concat2(out), stderr: concat2(err) };
5355
+ return { status: NOT_FOUND, stdout: concat4(out), stderr: concat4(err) };
2990
5356
  }
2991
5357
  const fds = new FdTable();
2992
5358
  fds.installAt(0, options.stdin ? bytesSource(options.stdin) : nullFile());
@@ -2994,15 +5360,15 @@ var Launcher = class {
2994
5360
  fds.installAt(2, sinkFile(stderr));
2995
5361
  const handle = await this.launch(planned, { env, cwd, fds, report });
2996
5362
  const status = await handle.exited;
2997
- return { status, stdout: concat2(out), stderr: concat2(err) };
5363
+ return { status, stdout: concat4(out), stderr: concat4(err) };
2998
5364
  }
2999
5365
  };
3000
5366
 
3001
5367
  // src/serve.ts
3002
- function signalNumber(name) {
3003
- const key = name.slice(3);
3004
- if (!name.startsWith("SIG") || !Object.hasOwn(SIG, key))
3005
- throw new Error(`unknown signal ${name}`);
5368
+ function signalNumber(name2) {
5369
+ const key = name2.slice(3);
5370
+ if (!name2.startsWith("SIG") || !Object.hasOwn(SIG, key))
5371
+ throw new Error(`unknown signal ${name2}`);
3006
5372
  return SIG[key];
3007
5373
  }
3008
5374
  function act(session, req) {
@@ -3013,6 +5379,7 @@ function act(session, req) {
3013
5379
  }
3014
5380
  function serveKernel(port, deps) {
3015
5381
  let launcher;
5382
+ let remote;
3016
5383
  const terminals = /* @__PURE__ */ new Map();
3017
5384
  const reply = (id, body) => port.postMessage({ id, ...body });
3018
5385
  async function ready2() {
@@ -3033,11 +5400,18 @@ function serveKernel(port, deps) {
3033
5400
  async function handle(req) {
3034
5401
  if (req.op === "init") {
3035
5402
  launcher = (req.root ? Promise.resolve(req.root) : deps.storage()).then(async (root) => {
5403
+ const name2 = req.metadata ?? META_DB;
5404
+ const meta = name2 === false ? void 0 : await deps.metadata?.(name2);
5405
+ remote = req.transport ? new RemoteTransport(port, req.transport) : void 0;
5406
+ const fs = new OpfsFs(root, meta);
5407
+ await fs.reconcile();
3036
5408
  const started = new Launcher({
3037
- fs: new OpfsFs(root),
5409
+ fs,
3038
5410
  createWorker: deps.createWorker,
3039
5411
  ...req.modules ? { modules: req.modules } : {},
3040
- ...req.env ? { env: req.env } : {}
5412
+ ...req.env ? { env: req.env } : {},
5413
+ ...remote ? { transport: remote } : {},
5414
+ caStore: caStore(deps.metadata ? name2 : false)
3041
5415
  });
3042
5416
  await started.prepare();
3043
5417
  return started;
@@ -3059,6 +5433,10 @@ function serveKernel(port, deps) {
3059
5433
  });
3060
5434
  }
3061
5435
  port.addEventListener("message", (event) => {
5436
+ if (event.data.net !== void 0) {
5437
+ remote?.receive(event.data);
5438
+ return;
5439
+ }
3062
5440
  const req = event.data;
3063
5441
  handle(req).then(
3064
5442
  (result) => reply(req.id, { result }),
@@ -3071,6 +5449,7 @@ function serveKernel(port, deps) {
3071
5449
  var processWorker = new URL("./process-worker.js", import.meta.url);
3072
5450
  serveKernel(globalThis, {
3073
5451
  storage: () => navigator.storage.getDirectory(),
3074
- createWorker: () => new Worker(processWorker, { type: "module" })
5452
+ createWorker: () => new Worker(processWorker, { type: "module" }),
5453
+ metadata: (name2) => openMeta(name2, indexedDB)
3075
5454
  });
3076
5455
  //# sourceMappingURL=kernel-worker.js.map