@ai-ecoverse/slicc-kernel 1.2.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.
@@ -71,18 +71,18 @@ var IndexedDbMeta = class {
71
71
  const [transaction, store] = this.transaction("readonly");
72
72
  const found = paths.map(() => void 0);
73
73
  paths.forEach((path, i) => {
74
- const request = store.get(path);
75
- request.onsuccess = () => {
76
- found[i] = request.result;
74
+ const request2 = store.get(path);
75
+ request2.onsuccess = () => {
76
+ found[i] = request2.result;
77
77
  };
78
78
  });
79
79
  return settle(transaction, () => found);
80
80
  }
81
81
  async update(path, change) {
82
82
  const [transaction, store] = this.transaction("readwrite");
83
- const request = store.get(path);
84
- request.onsuccess = () => {
85
- const next = change(request.result);
83
+ const request2 = store.get(path);
84
+ request2.onsuccess = () => {
85
+ const next = change(request2.result);
86
86
  if (next) store.put(next);
87
87
  else store.delete(path);
88
88
  };
@@ -114,8 +114,8 @@ var IndexedDbMeta = class {
114
114
  }
115
115
  async links(dir) {
116
116
  const [transaction, store] = this.transaction("readonly");
117
- const request = store.index("dir").getAll(dir);
118
- return settle(transaction, () => request.result);
117
+ const request2 = store.index("dir").getAll(dir);
118
+ return settle(transaction, () => request2.result);
119
119
  }
120
120
  async under(path) {
121
121
  const [transaction, store] = this.transaction("readonly");
@@ -130,20 +130,20 @@ var IndexedDbMeta = class {
130
130
  }
131
131
  async all() {
132
132
  const [transaction, store] = this.transaction("readonly");
133
- const request = store.getAll();
134
- return settle(transaction, () => request.result);
133
+ const request2 = store.getAll();
134
+ return settle(transaction, () => request2.result);
135
135
  }
136
136
  };
137
- function openMeta(name, factory) {
137
+ function openMeta(name2, factory) {
138
138
  return new Promise((resolve, reject) => {
139
- const request = factory.open(name, META_VERSION);
140
- request.onupgradeneeded = (event) => {
139
+ const request2 = factory.open(name2, META_VERSION);
140
+ request2.onupgradeneeded = (event) => {
141
141
  if (event.oldVersion < 1) {
142
- request.result.createObjectStore(META_STORE, { keyPath: "path" }).createIndex("dir", "dir");
142
+ request2.result.createObjectStore(META_STORE, { keyPath: "path" }).createIndex("dir", "dir");
143
143
  }
144
144
  };
145
- request.onsuccess = () => resolve(new IndexedDbMeta(request.result));
146
- request.onerror = () => reject(request.error);
145
+ request2.onsuccess = () => resolve(new IndexedDbMeta(request2.result));
146
+ request2.onerror = () => reject(request2.error);
147
147
  });
148
148
  }
149
149
 
@@ -193,8 +193,8 @@ var ERRNO_BY_DOM_ERROR = {
193
193
  };
194
194
  function translate(err, path) {
195
195
  if (typeof err?.code === "string") return err;
196
- const name = err?.name;
197
- 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
198
  }
199
199
  function missing(err) {
200
200
  const code = err?.code;
@@ -240,19 +240,19 @@ var OpfsFs = class {
240
240
  }
241
241
  async parent(path) {
242
242
  const parts = split(path);
243
- const name = parts.pop();
244
- if (name === void 0) throw fsError("EBUSY", path);
245
- 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];
246
246
  }
247
247
  async handle(path) {
248
248
  if (split(path).length === 0) return this.root;
249
- const [dir, name] = await this.parent(path);
249
+ const [dir, name2] = await this.parent(path);
250
250
  try {
251
- return await dir.getFileHandle(name);
251
+ return await dir.getFileHandle(name2);
252
252
  } catch (err) {
253
253
  if (err?.name !== "TypeMismatchError") throw translate(err, path);
254
254
  }
255
- return dir.getDirectoryHandle(name);
255
+ return dir.getDirectoryHandle(name2);
256
256
  }
257
257
  real(path) {
258
258
  return this.handle(path).then(
@@ -274,11 +274,11 @@ var OpfsFs = class {
274
274
  return { path: current, handle: await this.handle(current) };
275
275
  } catch (err) {
276
276
  if (!missing(err)) throw err;
277
- const link = await this.linkAlong(current);
278
- if (!link) throw err;
279
- if (link.path === current && !follow) return { path: current, link };
280
- const rest = current.slice(link.path.length);
281
- current = resolvePath(parentOf2(link.path), `${link.link}${rest}`);
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
282
  }
283
283
  }
284
284
  throw fsError("ELOOP", path);
@@ -305,15 +305,15 @@ var OpfsFs = class {
305
305
  };
306
306
  }
307
307
  linkStat(entry) {
308
- const time = new Date(entry.mtimeMs ?? 0);
308
+ const time2 = new Date(entry.mtimeMs ?? 0);
309
309
  return {
310
310
  isFile: false,
311
311
  isDirectory: false,
312
312
  isSymbolicLink: true,
313
313
  size: new TextEncoder().encode(entry.link).length,
314
314
  mode: S_IFLNK | 511,
315
- mtime: time,
316
- atime: time,
315
+ mtime: time2,
316
+ atime: time2,
317
317
  ctime: new Date(entry.ctimeMs ?? 0),
318
318
  ino: entry.ino ?? inodeOf(entry.path)
319
319
  };
@@ -364,10 +364,10 @@ var OpfsFs = class {
364
364
  }
365
365
  async writeFile(path, content) {
366
366
  const target = await this.writeTarget(path);
367
- const [dir, name] = await this.parent(target);
367
+ const [dir, name2] = await this.parent(target);
368
368
  let file;
369
369
  try {
370
- file = await dir.getFileHandle(name, { create: true });
370
+ file = await dir.getFileHandle(name2, { create: true });
371
371
  } catch (err) {
372
372
  const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
373
373
  throw code ? fsError(code, path) : translate(err, path);
@@ -380,26 +380,26 @@ var OpfsFs = class {
380
380
  const found = await this.locate(path, true);
381
381
  const handle = found.handle;
382
382
  if (handle.kind !== "directory") throw fsError("ENOTDIR", path);
383
- const children = /* @__PURE__ */ new Map();
384
- for await (const child of handle.values()) children.set(child.name, child);
385
- for (const link of await this.meta.links(found.path)) {
386
- const name = baseOf(link.path);
387
- if (!children.has(name)) children.set(name, link);
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
388
  }
389
- return [found.path, [...children].sort(([a], [b]) => a < b ? -1 : 1)];
389
+ return [found.path, [...children2].sort(([a], [b]) => a < b ? -1 : 1)];
390
390
  }
391
391
  async readdir(path) {
392
- return (await this.listing(path))[1].map(([name]) => name);
392
+ return (await this.listing(path))[1].map(([name2]) => name2);
393
393
  }
394
394
  async readdirStat(path) {
395
- const [dir, children] = await this.listing(path);
395
+ const [dir, children2] = await this.listing(path);
396
396
  const base = dir === "/" ? "" : dir;
397
- const metas = await this.meta.get(children.map(([name]) => `${base}/${name}`));
397
+ const metas = await this.meta.get(children2.map(([name2]) => `${base}/${name2}`));
398
398
  return Promise.all(
399
- children.map(async ([name, child], i) => {
400
- if (!isHandle(child)) return [name, this.linkStat(child)];
401
- const stat = await this.statOf(`${base}/${name}`, child, metas[i]).catch(() => null);
402
- return [name, stat];
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
403
  })
404
404
  );
405
405
  }
@@ -432,9 +432,9 @@ var OpfsFs = class {
432
432
  throw error;
433
433
  }
434
434
  if (found.handle) {
435
- const [dir, name] = await this.parent(found.path);
435
+ const [dir, name2] = await this.parent(found.path);
436
436
  try {
437
- await dir.removeEntry(name, { recursive: options.recursive === true });
437
+ await dir.removeEntry(name2, { recursive: options.recursive === true });
438
438
  } catch (err) {
439
439
  throw translate(err, path);
440
440
  }
@@ -473,9 +473,9 @@ var OpfsFs = class {
473
473
  return;
474
474
  }
475
475
  const dir = parent.handle;
476
- const name = baseOf(destination);
476
+ const name2 = baseOf(destination);
477
477
  const swap = targetStat?.isDirectory === true;
478
- const staging = swap ? `.${name}.${crypto.randomUUID()}` : name;
478
+ const staging = swap ? `.${name2}.${crypto.randomUUID()}` : name2;
479
479
  const timed = await this.explicitTimes(source.path);
480
480
  const moved2 = await this.place(source.handle, dir, staging);
481
481
  if (swap) await this.rm(destination, { recursive: true });
@@ -484,7 +484,7 @@ var OpfsFs = class {
484
484
  let copied = !moved2;
485
485
  if (swap) {
486
486
  const staged = await dir.getDirectoryHandle(staging);
487
- const placed = await this.place(staged, dir, name);
487
+ const placed = await this.place(staged, dir, name2);
488
488
  if (!placed) await dir.removeEntry(staging, { recursive: true });
489
489
  copied ||= !placed;
490
490
  }
@@ -509,28 +509,28 @@ var OpfsFs = class {
509
509
  await this.meta.update(path + rest, (entry) => entry && { ...entry, mtimeFor });
510
510
  }
511
511
  }
512
- async place(handle, dir, name) {
513
- const moved2 = await handle.move?.(dir, name).then(
512
+ async place(handle, dir, name2) {
513
+ const moved2 = await handle.move?.(dir, name2).then(
514
514
  () => true,
515
515
  () => false
516
516
  );
517
517
  if (moved2) return true;
518
518
  try {
519
- await this.copy(handle, dir, name);
519
+ await this.copy(handle, dir, name2);
520
520
  } catch (err) {
521
- if (handle.kind === "directory") await dir.removeEntry(name, { recursive: true });
522
- throw translate(err, name);
521
+ if (handle.kind === "directory") await dir.removeEntry(name2, { recursive: true });
522
+ throw translate(err, name2);
523
523
  }
524
524
  return false;
525
525
  }
526
- async copy(handle, dir, name) {
526
+ async copy(handle, dir, name2) {
527
527
  if (handle.kind === "file") {
528
- const writable = await (await dir.getFileHandle(name, { create: true })).createWritable();
528
+ const writable = await (await dir.getFileHandle(name2, { create: true })).createWritable();
529
529
  await writable.write(await handle.getFile());
530
530
  await writable.close();
531
531
  return;
532
532
  }
533
- const target = await dir.getDirectoryHandle(name, { create: true });
533
+ const target = await dir.getDirectoryHandle(name2, { create: true });
534
534
  for await (const child of handle.values()) await this.copy(child, target, child.name);
535
535
  }
536
536
  async symlink(target, path) {
@@ -595,259 +595,6 @@ var OpfsFs = class {
595
595
  }
596
596
  };
597
597
 
598
- // src/kernel/signals.ts
599
- var SIG = {
600
- HUP: 1,
601
- INT: 2,
602
- QUIT: 3,
603
- ILL: 4,
604
- TRAP: 5,
605
- ABRT: 6,
606
- BUS: 7,
607
- FPE: 8,
608
- KILL: 9,
609
- USR1: 10,
610
- SEGV: 11,
611
- USR2: 12,
612
- PIPE: 13,
613
- ALRM: 14,
614
- TERM: 15,
615
- STKFLT: 16,
616
- CHLD: 17,
617
- CONT: 18,
618
- STOP: 19,
619
- TSTP: 20,
620
- TTIN: 21,
621
- TTOU: 22,
622
- URG: 23,
623
- XCPU: 24,
624
- XFSZ: 25,
625
- VTALRM: 26,
626
- PROF: 27,
627
- WINCH: 28,
628
- POLL: 29,
629
- PWR: 30,
630
- SYS: 31
631
- };
632
- var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
633
- var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
634
- function defaultAction(sig) {
635
- if (sig === SIG.CONT) return "continue";
636
- if (STOPPING.has(sig)) return "stop";
637
- return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
638
- }
639
- function isSignal(sig) {
640
- return Number.isInteger(sig) && sig >= 1 && sig <= 31;
641
- }
642
- function sigbit(sig) {
643
- return 1 << sig;
644
- }
645
- var SIGNAL_BY_NAME = {
646
- SIGINT: SIG.INT,
647
- SIGTERM: SIG.TERM,
648
- SIGKILL: SIG.KILL,
649
- SIGSTOP: SIG.STOP,
650
- SIGCONT: SIG.CONT
651
- };
652
-
653
- // src/commands.ts
654
- var NAME = /^[A-Za-z0-9._+-]+$/;
655
- var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
656
- var PACKAGE = /\$\{package\}/g;
657
- function inside(pkg, rel) {
658
- if (typeof rel !== "string" || rel === "") return void 0;
659
- const clean = rel.replace(/^\.\//, "");
660
- if (clean.startsWith("/") || clean.split("/").includes("..")) return void 0;
661
- return `${pkg}/${clean}`;
662
- }
663
- function envOf(pkg, raw) {
664
- const env = {};
665
- if (!raw || typeof raw !== "object") return env;
666
- for (const [key, value] of Object.entries(raw)) {
667
- if (ENV_KEY.test(key) && typeof value === "string") env[key] = value.replace(PACKAGE, pkg);
668
- }
669
- return env;
670
- }
671
- function argsOf(pkg, raw) {
672
- if (!Array.isArray(raw) || raw.length === 0) return void 0;
673
- if (!raw.every((arg) => typeof arg === "string")) return void 0;
674
- return raw.map((arg) => arg.replace(PACKAGE, pkg));
675
- }
676
- function commandsOf(pkg, manifest) {
677
- const slicc = manifest.slicc;
678
- if (!slicc || typeof slicc !== "object" || (slicc.abi ?? "emscripten") !== "emscripten")
679
- return [];
680
- const entries = slicc.commands;
681
- if (!entries || typeof entries !== "object") return [];
682
- const shared = envOf(pkg, slicc.env);
683
- const out = [];
684
- for (const [name, raw] of Object.entries(entries)) {
685
- if (!NAME.test(name) || name === "." || name === ".." || !raw || typeof raw !== "object") {
686
- continue;
687
- }
688
- const glue = inside(pkg, raw.glue);
689
- const wasm = inside(pkg, raw.wasm);
690
- if ((raw.abi ?? "emscripten") !== "emscripten" || !glue || !wasm) continue;
691
- const argv0 = typeof raw.argv0 === "string" && raw.argv0 ? raw.argv0 : name;
692
- const args = argsOf(pkg, raw.args);
693
- const env = { ...shared, ...envOf(pkg, raw.env) };
694
- out.push({
695
- name,
696
- glue,
697
- wasm,
698
- argv0,
699
- ...args ? { args } : {},
700
- ...Object.keys(env).length > 0 ? { env } : {}
701
- });
702
- }
703
- return out;
704
- }
705
- async function packages(fs, modules) {
706
- const names = await fs.readdir(modules).catch(() => []);
707
- const dirs = [];
708
- for (const name of names) {
709
- if (name.startsWith(".")) continue;
710
- if (!name.startsWith("@")) {
711
- dirs.push(`${modules}/${name}`);
712
- continue;
713
- }
714
- for (const inner of await fs.readdir(`${modules}/${name}`).catch(() => [])) {
715
- dirs.push(`${modules}/${name}/${inner}`);
716
- }
717
- }
718
- return dirs;
719
- }
720
- async function scanCommands(fs, modules) {
721
- const found = /* @__PURE__ */ new Map();
722
- for (const pkg of await packages(fs, modules)) {
723
- let manifest;
724
- try {
725
- manifest = JSON.parse(await fs.readFile(`${pkg}/package.json`));
726
- } catch {
727
- continue;
728
- }
729
- for (const command of commandsOf(pkg, manifest)) {
730
- if (!found.has(command.name)) found.set(command.name, command);
731
- }
732
- }
733
- if (!found.has("sh") && found.has("bash")) {
734
- found.set("sh", { ...found.get("bash"), name: "sh", argv0: "sh" });
735
- }
736
- return found;
737
- }
738
-
739
- // src/fs/commands.ts
740
- var S_IFDIR2 = 16384;
741
- var S_IFREG2 = 32768;
742
- var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
743
- var MAX_LINKS2 = 40;
744
- function missing2(err) {
745
- return err?.code === "ENOENT";
746
- }
747
- function synthetic(directory, path) {
748
- const epoch = /* @__PURE__ */ new Date(0);
749
- return {
750
- isFile: !directory,
751
- isDirectory: directory,
752
- isSymbolicLink: false,
753
- size: 0,
754
- mode: (directory ? S_IFDIR2 : S_IFREG2) | 493,
755
- mtime: epoch,
756
- atime: epoch,
757
- ctime: epoch,
758
- ino: inodeOf(path)
759
- };
760
- }
761
- async function followLinks(fs, path) {
762
- let current = path;
763
- for (let hops = 0; hops < MAX_LINKS2; hops++) {
764
- const st = await fs.lstat(current).catch(() => void 0);
765
- if (!st?.isSymbolicLink) return current;
766
- const parent = current.slice(0, current.lastIndexOf("/")) || "/";
767
- current = fs.resolvePath(parent, await fs.readlink(current));
768
- }
769
- return current;
770
- }
771
- function withCommandDirs(fs, names) {
772
- async function children(path) {
773
- if (path === "/") return ["bin", "usr"];
774
- if (path === "/usr") return ["bin"];
775
- if (path === "/bin" || path === "/usr/bin") return [...await names()];
776
- return void 0;
777
- }
778
- async function virtual(path) {
779
- if (path === "/usr" || path === "/bin" || path === "/usr/bin") return synthetic(true, path);
780
- const name = COMMAND.exec(path)?.[1];
781
- return name !== void 0 && (await names()).has(name) ? synthetic(false, path) : void 0;
782
- }
783
- async function linked(path) {
784
- return await virtual(path) ?? virtual(await followLinks(fs, path));
785
- }
786
- async function stat(path, real) {
787
- try {
788
- return await real(path);
789
- } catch (err) {
790
- const found = missing2(err) ? await linked(path) : void 0;
791
- if (found) return found;
792
- throw err;
793
- }
794
- }
795
- return {
796
- resolvePath: (base, path) => fs.resolvePath(base, path),
797
- readFile: (path) => fs.readFile(path),
798
- async readFileBuffer(path) {
799
- try {
800
- return await fs.readFileBuffer(path);
801
- } catch (err) {
802
- if (missing2(err) && (await linked(path))?.isFile) return new Uint8Array(0);
803
- throw err;
804
- }
805
- },
806
- writeFile: (path, content) => fs.writeFile(path, content),
807
- async exists(path) {
808
- return await fs.exists(path) || await linked(path) !== void 0;
809
- },
810
- stat: (path) => stat(path, (p) => fs.stat(p)),
811
- lstat: (path) => stat(path, (p) => fs.lstat(p)),
812
- async readdir(path) {
813
- const extra = await children(path);
814
- if (!extra) return fs.readdir(path);
815
- const real = await fs.readdir(path).catch((err) => {
816
- if (missing2(err)) return [];
817
- throw err;
818
- });
819
- return [.../* @__PURE__ */ new Set([...real, ...extra])].sort();
820
- },
821
- async readdirStat(path) {
822
- const extra = await children(path);
823
- if (!extra) return fs.readdirStat(path);
824
- const real = await fs.readdirStat(path).catch((err) => {
825
- if (missing2(err)) return [];
826
- throw err;
827
- });
828
- const seen = new Set(real.map(([name]) => name));
829
- const base = path === "/" ? "" : path;
830
- const added = extra.filter((name) => !seen.has(name));
831
- const virtuals = await Promise.all(
832
- added.map(
833
- async (name) => [
834
- name,
835
- await virtual(`${base}/${name}`)
836
- ]
837
- )
838
- );
839
- return [...real, ...virtuals].sort(([a], [b]) => a < b ? -1 : 1);
840
- },
841
- mkdir: (path, options) => fs.mkdir(path, options),
842
- rm: (path, options) => fs.rm(path, options),
843
- rename: (from, to) => fs.rename(from, to),
844
- symlink: (target, path) => fs.symlink(target, path),
845
- readlink: (path) => fs.readlink(path),
846
- chmod: (path, mode) => fs.chmod(path, mode),
847
- utimes: (path, atime, mtime) => fs.utimes(path, atime, mtime)
848
- };
849
- }
850
-
851
598
  // src/kernel/pipe.ts
852
599
  var PIPE_CAPACITY = 64 * 1024;
853
600
  var PipeError = class extends Error {
@@ -916,10 +663,10 @@ var KernelPipe = class {
916
663
  }
917
664
  const out = new Uint8Array(Math.min(max, this.size));
918
665
  let filled = 0;
919
- for (const chunk of this.chunks) {
666
+ for (const chunk2 of this.chunks) {
920
667
  if (filled === out.length) break;
921
- const count = Math.min(chunk.length, out.length - filled);
922
- 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);
923
670
  filled += count;
924
671
  }
925
672
  return out;
@@ -1147,50 +894,1745 @@ var FdTable = class _FdTable {
1147
894
  return fd;
1148
895
  }
1149
896
  }
1150
- void Promise.resolve(file.release()).catch(() => void 0);
1151
- throw new KernelError("EMFILE");
1152
- }
1153
- installAt(fd, file) {
1154
- if (fd < 0 || fd >= _FdTable.MAX_FDS) {
1155
- void Promise.resolve(file.release()).catch(() => void 0);
1156
- throw new KernelError("EBADF");
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}`);
2502
+ }
2503
+ }
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);
1157
2517
  }
1158
- const previous = this.fds.get(fd);
1159
- this.fds.set(fd, file);
1160
- this.cloexec.delete(fd);
1161
- this.status.delete(fd);
1162
- void Promise.resolve(previous?.release()).catch(() => void 0);
1163
2518
  }
1164
- dup(fd, min = 0) {
1165
- 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" });
1166
2521
  }
1167
- dup2(oldFd, newFd) {
1168
- const file = this.get(oldFd);
1169
- if (oldFd !== newFd) this.installAt(newFd, file.retain());
1170
- 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));
1171
2554
  }
1172
- close(fd) {
1173
- const file = this.get(fd);
1174
- this.fds.delete(fd);
1175
- this.cloexec.delete(fd);
1176
- this.status.delete(fd);
1177
- return file.release();
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;
1178
2563
  }
1179
- fork() {
1180
- const child = new _FdTable();
1181
- for (const [fd, file] of this.fds) child.fds.set(fd, file.retain());
1182
- for (const fd of this.cloexec) child.cloexec.add(fd);
1183
- for (const [fd, flags] of this.status) child.status.set(fd, flags);
1184
- return child;
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;
1185
2568
  }
1186
- async closeAll() {
1187
- const files = [...this.fds.values()];
1188
- this.fds.clear();
1189
- this.cloexec.clear();
1190
- this.status.clear();
1191
- await Promise.allSettled(files.map((file) => Promise.resolve().then(() => file.release())));
2569
+ async function linked(path) {
2570
+ return await virtual(path) ?? virtual(await followLinks(fs, path));
1192
2571
  }
1193
- };
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
+ }
2580
+ }
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
+ }
1194
2636
 
1195
2637
  // src/kernel/children.ts
1196
2638
  var SpawnError = class extends Error {
@@ -1214,12 +2656,12 @@ var CONTINUED_STATUS = 65535;
1214
2656
  function waitStatus(code, termsig) {
1215
2657
  return termsig ? termsig & 127 : (code & 255) << 8;
1216
2658
  }
1217
- function concat(chunks) {
2659
+ function concat3(chunks) {
1218
2660
  const out = new Uint8Array(chunks.reduce((n, c) => n + c.length, 0));
1219
2661
  let offset = 0;
1220
- for (const chunk of chunks) {
1221
- out.set(chunk, offset);
1222
- offset += chunk.length;
2662
+ for (const chunk2 of chunks) {
2663
+ out.set(chunk2, offset);
2664
+ offset += chunk2.length;
1223
2665
  }
1224
2666
  return out;
1225
2667
  }
@@ -1371,7 +2813,7 @@ var ChildTable = class {
1371
2813
  if (!slots || !chunks) return new Uint8Array(0);
1372
2814
  slots.delete(slot);
1373
2815
  if (slots.size === 0) this.leftovers.delete(pid);
1374
- return concat(chunks);
2816
+ return concat3(chunks);
1375
2817
  }
1376
2818
  };
1377
2819
 
@@ -1397,12 +2839,12 @@ function syncError(code, label) {
1397
2839
 
1398
2840
  // src/realm/sync-fs-dispatch.ts
1399
2841
  function toErrno(err) {
1400
- const message = err instanceof Error ? err.message : String(err);
2842
+ const message2 = err instanceof Error ? err.message : String(err);
1401
2843
  const code = err?.code;
1402
2844
  if (typeof code === "string" && /^E[A-Z]+$/.test(code)) {
1403
- return { ok: false, errno: code, message };
2845
+ return { ok: false, errno: code, message: message2 };
1404
2846
  }
1405
- return { ok: false, errno: "EIO", message };
2847
+ return { ok: false, errno: "EIO", message: message2 };
1406
2848
  }
1407
2849
  function statJson(s) {
1408
2850
  return {
@@ -1418,7 +2860,7 @@ function statJson(s) {
1418
2860
  };
1419
2861
  }
1420
2862
  async function readdirStat(fs, dir) {
1421
- return (await fs.readdirStat(dir)).map(([name, stat]) => [name, stat && statJson(stat)]);
2863
+ return (await fs.readdirStat(dir)).map(([name2, stat]) => [name2, stat && statJson(stat)]);
1422
2864
  }
1423
2865
  var done = { ok: true, kind: "void" };
1424
2866
  function json(value) {
@@ -1537,7 +2979,7 @@ function sabViews(sab) {
1537
2979
  var PENDING_TTL_MS = SYNC_FS_REQUEST_TIMEOUT_MS + 5e3;
1538
2980
  function attachSyncSabResponder(port, sab, token, opts) {
1539
2981
  const views = sabViews(sab);
1540
- const { header, window } = views;
2982
+ const { header: header2, window } = views;
1541
2983
  const pending = /* @__PURE__ */ new Map();
1542
2984
  let disposed = false;
1543
2985
  const { dispatch } = opts;
@@ -1548,16 +2990,16 @@ function attachSyncSabResponder(port, sab, token, opts) {
1548
2990
  pending.delete(id);
1549
2991
  }
1550
2992
  function publish(id, entry, offset) {
1551
- const chunk = Math.min(window.byteLength, entry.payload.byteLength - offset);
1552
- if (chunk > 0) window.set(entry.payload.subarray(offset, offset + chunk));
1553
- Atomics.store(header, SAB_I_STATUS, entry.status);
1554
- Atomics.store(header, SAB_I_TOTAL, entry.payload.byteLength);
1555
- Atomics.store(header, SAB_I_CHUNK, chunk);
1556
- Atomics.store(header, SAB_I_OFFSET, offset);
1557
- Atomics.store(header, SAB_I_SEQ, id);
1558
- Atomics.store(header, SAB_I_STATE, SAB_STATE_READY);
1559
- Atomics.notify(header, SAB_I_STATE);
1560
- if (offset + chunk >= entry.payload.byteLength) drop(id);
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);
1561
3003
  }
1562
3004
  function settle2(id, result) {
1563
3005
  if (disposed) return;
@@ -1619,6 +3061,112 @@ function attachSyncSabResponder(port, sab, token, opts) {
1619
3061
  };
1620
3062
  }
1621
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
+
1622
3170
  // src/kernel/tty.ts
1623
3171
  var ICRNL = 256;
1624
3172
  var IUTF8 = 16384;
@@ -1787,9 +3335,9 @@ var KernelTty = class {
1787
3335
  return n;
1788
3336
  }
1789
3337
  rubOut(n) {
1790
- const dropped = this.line.splice(this.line.length - n, n);
1791
- const utf8 = (this.termios.c_iflag & IUTF8) !== 0;
1792
- 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;
1793
3341
  if (cells > 0 && this.termios.c_lflag & ECHO) {
1794
3342
  this.output(encoder2.encode("\b \b".repeat(cells)), false);
1795
3343
  }
@@ -1839,9 +3387,9 @@ var KernelTty = class {
1839
3387
  }
1840
3388
  const out = new Uint8Array(n);
1841
3389
  let at = 0;
1842
- for (const chunk of chunks) {
1843
- out.set(chunk, at);
1844
- at += chunk.length;
3390
+ for (const chunk2 of chunks) {
3391
+ out.set(chunk2, at);
3392
+ at += chunk2.length;
1845
3393
  }
1846
3394
  return out;
1847
3395
  }
@@ -1938,9 +3486,9 @@ var PtyPair = class {
1938
3486
  }
1939
3487
  const out = new Uint8Array(status + n);
1940
3488
  let at = status;
1941
- for (const chunk of chunks) {
1942
- out.set(chunk, at);
1943
- at += chunk.length;
3489
+ for (const chunk2 of chunks) {
3490
+ out.set(chunk2, at);
3491
+ at += chunk2.length;
1944
3492
  }
1945
3493
  return out;
1946
3494
  }
@@ -2111,6 +3659,391 @@ async function waitReady(fds, read, write, deadline, interrupted2) {
2111
3659
  }
2112
3660
  }
2113
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
+
2114
4047
  // src/kernel/vfs-file.ts
2115
4048
  var O_ACCMODE = 3;
2116
4049
  var O_WRONLY = 1;
@@ -2140,14 +4073,14 @@ var VfsNode = class {
2140
4073
  fs;
2141
4074
  path;
2142
4075
  orphaned;
2143
- constructor(fs, path, contents, orphaned = false, dirty = false) {
4076
+ constructor(fs, path, contents2, orphaned = false, dirty = false) {
2144
4077
  this.fs = fs;
2145
4078
  this.path = path;
2146
4079
  this.orphaned = orphaned;
2147
- if (contents !== void 0) this.data = new Uint8Array(contents);
4080
+ if (contents2 !== void 0) this.data = new Uint8Array(contents2);
2148
4081
  else if (orphaned) this.data = new Uint8Array(0);
2149
4082
  this.length = this.data?.length ?? 0;
2150
- if (dirty && contents !== void 0) this.markDirty();
4083
+ if (dirty && contents2 !== void 0) this.markDirty();
2151
4084
  }
2152
4085
  serial(op) {
2153
4086
  const next = this.queue.then(op);
@@ -2167,8 +4100,8 @@ var VfsNode = class {
2167
4100
  }
2168
4101
  return this.data;
2169
4102
  }
2170
- replace(contents) {
2171
- this.data = new Uint8Array(contents);
4103
+ replace(contents2) {
4104
+ this.data = new Uint8Array(contents2);
2172
4105
  this.length = this.data.length;
2173
4106
  this.missing = false;
2174
4107
  this.markDirty();
@@ -2249,13 +4182,13 @@ var VfsNodes = class {
2249
4182
  node.opens++;
2250
4183
  return node;
2251
4184
  }
2252
- adopt(path, contents, dirty) {
4185
+ adopt(path, contents2, dirty) {
2253
4186
  let node = this.byPath.get(path);
2254
4187
  if (!node) {
2255
- node = new VfsNode(this.fs, path, contents, false, dirty);
4188
+ node = new VfsNode(this.fs, path, contents2, false, dirty);
2256
4189
  this.byPath.set(path, node);
2257
4190
  } else if (dirty) {
2258
- node.replace(contents);
4191
+ node.replace(contents2);
2259
4192
  }
2260
4193
  node.opens++;
2261
4194
  return node;
@@ -2377,6 +4310,12 @@ var JOB_OPS = /* @__PURE__ */ new Set([
2377
4310
  function isJobSyscall(req) {
2378
4311
  return JOB_OPS.has(req.op);
2379
4312
  }
4313
+ function isHttpSyscall(req) {
4314
+ return req.op.startsWith("net-");
4315
+ }
4316
+ function isSocketSyscall(req) {
4317
+ return req.op.startsWith("sock-");
4318
+ }
2380
4319
  function isPtySyscall(req) {
2381
4320
  return req.op.startsWith("pty-");
2382
4321
  }
@@ -2435,6 +4374,8 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
2435
4374
  "tty-pgrp-set",
2436
4375
  "sig-mask",
2437
4376
  "sig-pause",
4377
+ ...SOCKET_OPS,
4378
+ ...HTTP_OPS,
2438
4379
  ...PTY_OPS
2439
4380
  ]);
2440
4381
  function isWasmSyscall(req) {
@@ -2455,6 +4396,7 @@ var WasmProcess = class {
2455
4396
  resumed = Promise.resolve();
2456
4397
  wake;
2457
4398
  stateListeners = [];
4399
+ net;
2458
4400
  nodes;
2459
4401
  pid;
2460
4402
  fds;
@@ -2524,6 +4466,8 @@ var WasmProcess = class {
2524
4466
  if (isFdSyscall(req)) return await this.fdSyscall(req);
2525
4467
  if (isTtySyscall(req)) return this.ttySyscall(req);
2526
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);
2527
4471
  if (isPtySyscall(req)) {
2528
4472
  const { ptys, jobs } = this.options;
2529
4473
  return ptySyscall(req, { pid: this.pid, fds: this.fds, ptys, jobs });
@@ -2721,7 +4665,7 @@ var WasmProcess = class {
2721
4665
  const flags = this.fds.statusFlags(fd);
2722
4666
  return {
2723
4667
  tty: file.tty !== void 0,
2724
- kind: kernelFdKind(file),
4668
+ kind: file instanceof KernelSocket ? "socket" : kernelFdKind(file),
2725
4669
  ...file.heldMeta ? { meta: file.heldMeta } : {},
2726
4670
  ...flags !== void 0 ? { flags } : {},
2727
4671
  ...this.fds.closesOnExec(fd) ? { cloexec: true } : {},
@@ -2794,6 +4738,19 @@ var WasmProcess = class {
2794
4738
  return { ok: true, kind: "json", json: jobs.setsid(this.pid) };
2795
4739
  }
2796
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
+ }
2797
4754
  tty(fd) {
2798
4755
  const file = this.fds.get(fd).file;
2799
4756
  const tty = file.tty ?? file.pty?.slave;
@@ -2892,6 +4849,7 @@ var WasmProcess = class {
2892
4849
  this.clearAlarm();
2893
4850
  for (const pid of this.children.pids()) this.options.onReap?.(pid);
2894
4851
  await this.fds.closeAll();
4852
+ await this.options.http?.closeAll();
2895
4853
  }
2896
4854
  };
2897
4855
 
@@ -2913,7 +4871,7 @@ function inheritedFds(fds) {
2913
4871
  return fds.numbers().filter((fd) => fd > 2 && !fds.get(fd).file.held).map((fd) => {
2914
4872
  const open = fds.get(fd);
2915
4873
  const flags = fds.statusFlags(fd);
2916
- const kind = kernelFdKind(open.file);
4874
+ const kind = open.file instanceof KernelSocket ? "socket" : kernelFdKind(open.file);
2917
4875
  const meta = open.file.heldMeta;
2918
4876
  return {
2919
4877
  fd,
@@ -2927,7 +4885,7 @@ function inheritedFds(fds) {
2927
4885
  }
2928
4886
  function spawnWasmProcess(opts) {
2929
4887
  const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
2930
- const header = new Int32Array(sab, 0, SAB_HEADER_I32);
4888
+ const header2 = new Int32Array(sab, 0, SAB_HEADER_I32);
2931
4889
  const process = new WasmProcess(opts.pid, opts.fds, {
2932
4890
  spawner: opts.spawner,
2933
4891
  forker: opts.forker,
@@ -2935,10 +4893,12 @@ function spawnWasmProcess(opts) {
2935
4893
  kill: opts.kill,
2936
4894
  jobs: opts.jobs,
2937
4895
  ptys: opts.ptys,
4896
+ net: opts.net,
4897
+ http: opts.http,
2938
4898
  onReap: opts.onReap,
2939
- onPending: (sig) => void Atomics.or(header, SAB_I_SIGNALS, sigbit(sig)),
2940
- onTimer: (which) => void Atomics.or(header, SAB_I_TIMERS, 1 << which),
2941
- 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,
2942
4902
  raise: (sig) => signal(sig)
2943
4903
  });
2944
4904
  const token = mintSyncFsToken({ fs: opts.fs, cwd: opts.cwd });
@@ -3073,9 +5033,9 @@ var JobTable = class {
3073
5033
  this.terminals.delete(pid);
3074
5034
  return pid;
3075
5035
  }
3076
- terminalNamed(name) {
5036
+ terminalNamed(name2) {
3077
5037
  const known = /* @__PURE__ */ new Set([...this.terminals.values(), ...this.foreground.keys()]);
3078
- return [...known].find((tty) => tty.name === name);
5038
+ return [...known].find((tty) => tty.name === name2);
3079
5039
  }
3080
5040
  controllingTerminal(pid) {
3081
5041
  const member = this.members.get(pid);
@@ -3145,12 +5105,12 @@ function childHandle(handle) {
3145
5105
  onState: (listener) => handle.onState(listener)
3146
5106
  };
3147
5107
  }
3148
- function concat2(chunks) {
3149
- 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));
3150
5110
  let offset = 0;
3151
- for (const chunk of chunks) {
3152
- out.set(chunk, offset);
3153
- offset += chunk.length;
5111
+ for (const chunk2 of chunks) {
5112
+ out.set(chunk2, offset);
5113
+ offset += chunk2.length;
3154
5114
  }
3155
5115
  return out;
3156
5116
  }
@@ -3167,13 +5127,23 @@ var Launcher = class {
3167
5127
  orphans = /* @__PURE__ */ new Set();
3168
5128
  jobs = new JobTable();
3169
5129
  ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
5130
+ net = new LoopbackNet();
5131
+ ca;
5132
+ transport;
3170
5133
  nextPid = 1e3;
3171
5134
  terminals = 0;
3172
5135
  constructor(options) {
3173
5136
  this.base = options.fs;
3174
5137
  this.createWorker = options.createWorker;
3175
5138
  this.modulesDir = options.modules ?? "/node_modules";
3176
- 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
+ });
3177
5147
  this.fs = withCommandDirs(options.fs, async () => new Set((await this.commands()).keys()));
3178
5148
  }
3179
5149
  commands() {
@@ -3181,9 +5151,9 @@ var Launcher = class {
3181
5151
  return this.catalog;
3182
5152
  }
3183
5153
  async resolve(file, argv0, cwd) {
3184
- const name = COMMAND2.exec(file)?.[1] ?? (file.includes("/") ? void 0 : file);
3185
- if (name !== void 0) {
3186
- 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);
3187
5157
  return command && targetOf(command);
3188
5158
  }
3189
5159
  const glue = await followLinks(this.base, this.fs.resolvePath(cwd, file));
@@ -3276,6 +5246,8 @@ var Launcher = class {
3276
5246
  kill: (target, sig) => this.kill(target, sig),
3277
5247
  jobs: this.jobs,
3278
5248
  ptys: this.ptys,
5249
+ net: this.net,
5250
+ http: new HttpHandles(this.transport),
3279
5251
  onReap: (child) => this.reaped(child),
3280
5252
  ...req.fork ? { fork: req.fork } : {},
3281
5253
  ...req.ppid !== void 0 ? { ppid: req.ppid } : {}
@@ -3316,6 +5288,7 @@ var Launcher = class {
3316
5288
  }
3317
5289
  async prepare() {
3318
5290
  for (const dir of SHARED_DIRS) await this.base.mkdir(dir, { recursive: true });
5291
+ await writeCaFile(this.base, this.ca).catch(() => void 0);
3319
5292
  }
3320
5293
  environment(cwd, extra) {
3321
5294
  return { PATH: "/usr/bin:/bin", HOME: "/home", ...this.env, PWD: cwd, ...extra };
@@ -3347,7 +5320,7 @@ var Launcher = class {
3347
5320
  COLORTERM: "truecolor",
3348
5321
  ...options.env
3349
5322
  });
3350
- const report = (message) => options.onData(encoder3.encode(`${message}\r
5323
+ const report = (message2) => options.onData(encoder3.encode(`${message2}\r
3351
5324
  `));
3352
5325
  const handle = await this.launch(planned, { env, cwd, fds, report });
3353
5326
  leader = handle.pid;
@@ -3375,11 +5348,11 @@ var Launcher = class {
3375
5348
  };
3376
5349
  const stdout = collect(out, options.onStdout);
3377
5350
  const stderr = collect(err, options.onStderr);
3378
- const report = (message) => stderr(encoder3.encode(`${message}
5351
+ const report = (message2) => stderr(encoder3.encode(`${message2}
3379
5352
  `));
3380
5353
  if (!planned) {
3381
5354
  report(`${file}: command not found`);
3382
- return { status: NOT_FOUND, stdout: concat2(out), stderr: concat2(err) };
5355
+ return { status: NOT_FOUND, stdout: concat4(out), stderr: concat4(err) };
3383
5356
  }
3384
5357
  const fds = new FdTable();
3385
5358
  fds.installAt(0, options.stdin ? bytesSource(options.stdin) : nullFile());
@@ -3387,15 +5360,15 @@ var Launcher = class {
3387
5360
  fds.installAt(2, sinkFile(stderr));
3388
5361
  const handle = await this.launch(planned, { env, cwd, fds, report });
3389
5362
  const status = await handle.exited;
3390
- return { status, stdout: concat2(out), stderr: concat2(err) };
5363
+ return { status, stdout: concat4(out), stderr: concat4(err) };
3391
5364
  }
3392
5365
  };
3393
5366
 
3394
5367
  // src/serve.ts
3395
- function signalNumber(name) {
3396
- const key = name.slice(3);
3397
- if (!name.startsWith("SIG") || !Object.hasOwn(SIG, key))
3398
- 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}`);
3399
5372
  return SIG[key];
3400
5373
  }
3401
5374
  function act(session, req) {
@@ -3406,6 +5379,7 @@ function act(session, req) {
3406
5379
  }
3407
5380
  function serveKernel(port, deps) {
3408
5381
  let launcher;
5382
+ let remote;
3409
5383
  const terminals = /* @__PURE__ */ new Map();
3410
5384
  const reply = (id, body) => port.postMessage({ id, ...body });
3411
5385
  async function ready2() {
@@ -3426,15 +5400,18 @@ function serveKernel(port, deps) {
3426
5400
  async function handle(req) {
3427
5401
  if (req.op === "init") {
3428
5402
  launcher = (req.root ? Promise.resolve(req.root) : deps.storage()).then(async (root) => {
3429
- const name = req.metadata ?? META_DB;
3430
- const meta = name === false ? void 0 : await deps.metadata?.(name);
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;
3431
5406
  const fs = new OpfsFs(root, meta);
3432
5407
  await fs.reconcile();
3433
5408
  const started = new Launcher({
3434
5409
  fs,
3435
5410
  createWorker: deps.createWorker,
3436
5411
  ...req.modules ? { modules: req.modules } : {},
3437
- ...req.env ? { env: req.env } : {}
5412
+ ...req.env ? { env: req.env } : {},
5413
+ ...remote ? { transport: remote } : {},
5414
+ caStore: caStore(deps.metadata ? name2 : false)
3438
5415
  });
3439
5416
  await started.prepare();
3440
5417
  return started;
@@ -3456,6 +5433,10 @@ function serveKernel(port, deps) {
3456
5433
  });
3457
5434
  }
3458
5435
  port.addEventListener("message", (event) => {
5436
+ if (event.data.net !== void 0) {
5437
+ remote?.receive(event.data);
5438
+ return;
5439
+ }
3459
5440
  const req = event.data;
3460
5441
  handle(req).then(
3461
5442
  (result) => reply(req.id, { result }),
@@ -3469,6 +5450,6 @@ var processWorker = new URL("./process-worker.js", import.meta.url);
3469
5450
  serveKernel(globalThis, {
3470
5451
  storage: () => navigator.storage.getDirectory(),
3471
5452
  createWorker: () => new Worker(processWorker, { type: "module" }),
3472
- metadata: (name) => openMeta(name, indexedDB)
5453
+ metadata: (name2) => openMeta(name2, indexedDB)
3473
5454
  });
3474
5455
  //# sourceMappingURL=kernel-worker.js.map