@ai-ecoverse/slicc-kernel 1.26.6 → 1.27.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.
@@ -883,7 +883,7 @@ function portCdpHook(ask, gone) {
883
883
  }
884
884
 
885
885
  // src/client/protocol.ts
886
- var PROTOCOL = [1, 5];
886
+ var PROTOCOL = [1, 6];
887
887
  var FS_METHODS = [
888
888
  "readFile",
889
889
  "writeFile",
@@ -2086,6 +2086,7 @@ function serveClient(port, host) {
2086
2086
 
2087
2087
  // src/kernel/net/loopback-names.ts
2088
2088
  var HOST_LOOPBACK = "host.slicc.internal";
2089
+ var HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2];
2089
2090
  var PAGE_LOOPBACK = "kernel.localhost";
2090
2091
  var PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;
2091
2092
  function toHostLoopback(href) {
@@ -3569,6 +3570,51 @@ var RemoteTransport = class {
3569
3570
  }
3570
3571
  };
3571
3572
 
3573
+ // src/kernel/net/uplink.ts
3574
+ function answerOf(result) {
3575
+ const answer = Array.isArray(result) ? { addresses: result } : result;
3576
+ const addresses = Array.isArray(answer?.addresses) ? answer.addresses.filter((a) => typeof a === "string") : [];
3577
+ return {
3578
+ addresses,
3579
+ ...typeof answer?.ttl === "number" && answer.ttl >= 0 ? { ttl: answer.ttl } : {}
3580
+ };
3581
+ }
3582
+ var RemoteUplink = class {
3583
+ traits;
3584
+ routes;
3585
+ port;
3586
+ waiting = /* @__PURE__ */ new Map();
3587
+ nextId = 0;
3588
+ constructor(port, traits, routes) {
3589
+ this.port = port;
3590
+ this.traits = traits;
3591
+ this.routes = routes;
3592
+ }
3593
+ receive(reply) {
3594
+ const waiter = this.waiting.get(reply.uid);
3595
+ if (!waiter) return;
3596
+ this.waiting.delete(reply.uid);
3597
+ if (reply.uplink === "error") {
3598
+ waiter.reject(Object.assign(new Error(reply.message), { code: reply.code }));
3599
+ } else waiter.resolve(answerOf(reply));
3600
+ }
3601
+ resolve(name2, family, signal) {
3602
+ const uid = ++this.nextId;
3603
+ const waiter = Promise.withResolvers();
3604
+ this.waiting.set(uid, waiter);
3605
+ const abort = () => {
3606
+ this.waiting.delete(uid);
3607
+ this.port.postMessage({ uplink: "cancel", uid });
3608
+ waiter.reject(signal?.reason);
3609
+ };
3610
+ signal?.addEventListener("abort", abort, { once: true });
3611
+ const settled = () => signal?.removeEventListener("abort", abort);
3612
+ waiter.promise.then(settled, settled);
3613
+ this.port.postMessage({ uplink: "resolve", uid, name: name2, family });
3614
+ return waiter.promise;
3615
+ }
3616
+ };
3617
+
3572
3618
  // src/cdp/types.ts
3573
3619
  var CDP_PORT = 9222;
3574
3620
  var NO_CDP_HOST = "slicc-kernel: no CDP host is attached (createKernel({ cdp }) or client.serveCdp)";
@@ -5897,6 +5943,204 @@ var HttpHandles = class {
5897
5943
  }
5898
5944
  };
5899
5945
 
5946
+ // src/kernel/net/routes.ts
5947
+ var HOST_ADDRESS = HOST_LOOPBACK_ADDRESS.join(".");
5948
+ function ipv4Bytes(text) {
5949
+ const parts = text.split(".");
5950
+ if (parts.length !== 4 || !parts.every((p) => /^\d{1,3}$/.test(p))) return void 0;
5951
+ const bytes = parts.map(Number);
5952
+ return bytes.every((b) => b <= 255) ? bytes : void 0;
5953
+ }
5954
+ function ipv6Groups(text) {
5955
+ if (!text.includes(":")) return void 0;
5956
+ let canonical;
5957
+ try {
5958
+ canonical = new URL(`http://[${text}]/`).hostname.slice(1, -1);
5959
+ } catch {
5960
+ return void 0;
5961
+ }
5962
+ const [head, tail] = canonical.split("::");
5963
+ const part = (s) => s ? s.split(":").map((h) => Number.parseInt(h, 16)) : [];
5964
+ const front = part(head);
5965
+ const back = part(tail);
5966
+ return tail === void 0 ? front : [...front, ...new Array(8 - front.length - back.length).fill(0), ...back];
5967
+ }
5968
+ function toBits(values, width) {
5969
+ return values.reduce((acc, v) => acc << BigInt(width) | BigInt(v), 0n);
5970
+ }
5971
+ function addressBits(host) {
5972
+ const v4 = ipv4Bytes(host);
5973
+ if (v4) return { v6: false, bits: toBits(v4, 8) };
5974
+ const v6 = ipv6Groups(host);
5975
+ return v6 ? { v6: true, bits: toBits(v6, 16) } : void 0;
5976
+ }
5977
+ function parsePrefix(text) {
5978
+ const parts = text.trim().split("/");
5979
+ if (parts.length > 2) return void 0;
5980
+ const [address = "", size] = parts;
5981
+ const parsed = addressBits(address);
5982
+ if (!parsed) return void 0;
5983
+ const max = parsed.v6 ? 128 : 32;
5984
+ const length2 = size === void 0 ? max : /^\d{1,3}$/.test(size) ? Number(size) : -1;
5985
+ if (length2 < 0 || length2 > max) return void 0;
5986
+ return { ...parsed, length: length2 };
5987
+ }
5988
+ function within3(prefix, addr) {
5989
+ if (prefix.v6 !== addr.v6) return false;
5990
+ const shift = BigInt((prefix.v6 ? 128 : 32) - prefix.length);
5991
+ return addr.bits >> shift === prefix.bits >> shift;
5992
+ }
5993
+ var RESERVED = [
5994
+ "0.0.0.0/8",
5995
+ "127.0.0.0/8",
5996
+ "169.254.0.0/16",
5997
+ "224.0.0.0/4",
5998
+ "240.0.0.0/4",
5999
+ "::/96",
6000
+ "::ffff:0:0/96",
6001
+ "fe80::/10",
6002
+ "ff00::/8"
6003
+ ].map((p) => parsePrefix(p));
6004
+ var Routes = class {
6005
+ table = [];
6006
+ exit = false;
6007
+ ipv6;
6008
+ constructor(ipv62 = false) {
6009
+ this.ipv6 = ipv62;
6010
+ }
6011
+ set(table) {
6012
+ const parsed = (table.prefixes ?? []).map(parsePrefix);
6013
+ const bad = (table.prefixes ?? []).find((_, i) => !parsed[i]);
6014
+ if (bad !== void 0) throw new Error(`not an address prefix: ${bad}`);
6015
+ this.table = parsed;
6016
+ this.exit = table.exit === true;
6017
+ }
6018
+ classify(host) {
6019
+ const name2 = host.toLowerCase().replace(/\.$/, "");
6020
+ if (name2 === "localhost" || name2.endsWith(".localhost") || name2 === "0.0.0.0") {
6021
+ return "loopback";
6022
+ }
6023
+ if (name2 === HOST_LOOPBACK || name2 === HOST_ADDRESS) return "host";
6024
+ const addr = addressBits(name2);
6025
+ if (!addr) return "unreachable";
6026
+ if (addr.v6 && addr.bits === 1n) return "loopback";
6027
+ if (!addr.v6 && addr.bits >> 24n === 127n) return "loopback";
6028
+ if (RESERVED.some((p) => within3(p, addr))) return "reserved";
6029
+ if (addr.v6 && !this.ipv6) return "unreachable";
6030
+ if (this.exit || this.table.some((p) => within3(p, addr))) return "uplink";
6031
+ return "unreachable";
6032
+ }
6033
+ };
6034
+
6035
+ // src/kernel/net/resolver.ts
6036
+ var RESOLVE_TTL_MAX = 60;
6037
+ var RESOLVE_TIMEOUT_MS = 1e4;
6038
+ var RESOLVE_CACHE_MAX = 256;
6039
+ var LOOPBACK_NAMES2 = /* @__PURE__ */ new Set(["localhost", "localhost.localdomain", "ip6-localhost", "::1"]);
6040
+ function hostsEntry(name2) {
6041
+ if (LOOPBACK_NAMES2.has(name2) || name2.endsWith(".localhost")) return "127.0.0.1";
6042
+ if (name2 === HOST_LOOPBACK) return HOST_LOOPBACK_ADDRESS.join(".");
6043
+ return ipv4Bytes(name2) ? name2 : void 0;
6044
+ }
6045
+ var Resolver = class {
6046
+ uplink;
6047
+ routes;
6048
+ now;
6049
+ timeoutMs;
6050
+ log;
6051
+ cache = /* @__PURE__ */ new Map();
6052
+ pending = /* @__PURE__ */ new Map();
6053
+ constructor(options) {
6054
+ this.uplink = options.uplink;
6055
+ this.routes = options.routes;
6056
+ this.now = options.now ?? Date.now;
6057
+ this.timeoutMs = options.timeoutMs ?? RESOLVE_TIMEOUT_MS;
6058
+ this.log = options.log ?? ((message2) => console.warn(message2));
6059
+ }
6060
+ async resolve(name2, family, blocking) {
6061
+ const key = name2.toLowerCase().replace(/\.$/, "");
6062
+ const local = hostsEntry(key);
6063
+ if (ipv6Groups(key)) {
6064
+ const v4 = family !== 6 && local ? [local] : [];
6065
+ return family === 4 ? v4 : [...v4, key];
6066
+ }
6067
+ if (local) {
6068
+ const six = LOOPBACK_NAMES2.has(key) || key.endsWith(".localhost") ? ["::1"] : [];
6069
+ return family === 6 ? six : family === 4 ? [local] : [local, ...six];
6070
+ }
6071
+ if (!this.uplink || key === "") return [];
6072
+ const asked = this.uplink.traits.ipv6 ? family : 4;
6073
+ if (family === 6 && asked !== 6) return [];
6074
+ const id = `${asked}:${key}`;
6075
+ const hit = this.cache.get(id);
6076
+ if (hit && hit.expires > this.now()) return hit.addresses;
6077
+ const signal = blocking?.();
6078
+ if (signal?.aborted) throw new KernelError("EINTR");
6079
+ let lookup = this.pending.get(id);
6080
+ if (!lookup) {
6081
+ const abort = new AbortController();
6082
+ lookup = { work: this.lookup(key, asked, id, abort.signal), abort, waiters: 0 };
6083
+ this.pending.set(id, lookup);
6084
+ void lookup.work.then(() => this.pending.delete(id));
6085
+ }
6086
+ return waitFor(lookup, signal);
6087
+ }
6088
+ async lookup(name2, family, id, abandoned) {
6089
+ const uplink = this.uplink;
6090
+ const timeout = AbortSignal.any([AbortSignal.timeout(this.timeoutMs), abandoned]);
6091
+ let answer;
6092
+ try {
6093
+ answer = answerOf(
6094
+ await Promise.race([
6095
+ uplink.resolve(name2, family, timeout),
6096
+ new Promise(
6097
+ (_, reject) => timeout.addEventListener("abort", () => reject(timeout.reason), { once: true })
6098
+ )
6099
+ ])
6100
+ );
6101
+ } catch {
6102
+ return [];
6103
+ }
6104
+ const addresses = answer.addresses.filter((a) => this.usable(name2, a, family));
6105
+ if (addresses.length > 0) this.remember(id, addresses, answer.ttl);
6106
+ return addresses;
6107
+ }
6108
+ usable(name2, address, family) {
6109
+ const v4 = ipv4Bytes(address) !== void 0;
6110
+ if (!v4 && (family === 4 || !ipv6Groups(address))) return false;
6111
+ if (v4 && family === 6) return false;
6112
+ const kind = this.routes.classify(address);
6113
+ if (kind !== "loopback" && kind !== "host" && kind !== "reserved") return true;
6114
+ this.log(`slicc-kernel: dropped the uplink's answer ${address} for ${name2}: it is ${kind}`);
6115
+ return false;
6116
+ }
6117
+ remember(id, addresses, ttl) {
6118
+ const seconds = Math.min(ttl ?? RESOLVE_TTL_MAX, RESOLVE_TTL_MAX);
6119
+ if (seconds <= 0) return;
6120
+ this.cache.delete(id);
6121
+ this.cache.set(id, { addresses, expires: this.now() + seconds * 1e3 });
6122
+ if (this.cache.size > RESOLVE_CACHE_MAX) {
6123
+ this.cache.delete(this.cache.keys().next().value);
6124
+ }
6125
+ }
6126
+ };
6127
+ function waitFor(lookup, signal) {
6128
+ lookup.waiters++;
6129
+ return new Promise((resolve, reject) => {
6130
+ const abort = () => {
6131
+ if (--lookup.waiters === 0) lookup.abort.abort(new KernelError("EINTR"));
6132
+ reject(new KernelError("EINTR"));
6133
+ };
6134
+ signal?.addEventListener("abort", abort, { once: true });
6135
+ void lookup.work.then((value) => {
6136
+ if (signal?.aborted) return;
6137
+ lookup.waiters--;
6138
+ signal?.removeEventListener("abort", abort);
6139
+ resolve(value);
6140
+ });
6141
+ });
6142
+ }
6143
+
5900
6144
  // src/kernel/tty.ts
5901
6145
  var ICRNL = 256;
5902
6146
  var IUTF8 = 16384;
@@ -6400,7 +6644,8 @@ var SOCKET_OPS = [
6400
6644
  "sock-shutdown",
6401
6645
  "sock-name",
6402
6646
  "sock-getopt",
6403
- "sock-setopt"
6647
+ "sock-setopt",
6648
+ "sock-resolve"
6404
6649
  ];
6405
6650
  function socketAt(fds, fd) {
6406
6651
  const file = fds.get(fd).file;
@@ -6449,6 +6694,11 @@ async function socketSyscall(req, proc) {
6449
6694
  case "sock-setopt":
6450
6695
  socketAt(fds, req.fd).setOption(req.level, req.name, req.value);
6451
6696
  return ok();
6697
+ case "sock-resolve": {
6698
+ const addresses = await proc.resolve(req.name, req.family, () => proc.blocking());
6699
+ if (addresses.length === 0) throw new KernelError("ENOENT");
6700
+ return ok(addresses);
6701
+ }
6452
6702
  }
6453
6703
  }
6454
6704
  function unnamed(domain) {
@@ -6700,7 +6950,7 @@ function isMissing(err) {
6700
6950
  if (err?.code === "ENOENT") return true;
6701
6951
  return err instanceof Error && err.message.startsWith("ENOENT");
6702
6952
  }
6703
- function within3(path, root) {
6953
+ function within4(path, root) {
6704
6954
  return path === root || path.startsWith(root.endsWith("/") ? root : `${root}/`);
6705
6955
  }
6706
6956
  var VfsNode = class {
@@ -6908,7 +7158,7 @@ var VfsNodes = class {
6908
7158
  return node;
6909
7159
  }
6910
7160
  holds(prefix) {
6911
- for (const path of this.byPath.keys()) if (within3(path, prefix)) return true;
7161
+ for (const path of this.byPath.keys()) if (within4(path, prefix)) return true;
6912
7162
  return false;
6913
7163
  }
6914
7164
  closed(node) {
@@ -6916,24 +7166,24 @@ var VfsNodes = class {
6916
7166
  if (node.opens === 0 && this.byPath.get(node.path) === node) this.byPath.delete(node.path);
6917
7167
  }
6918
7168
  async flush(path) {
6919
- for (const [p, node] of this.byPath) if (within3(p, path)) await node.serial(() => node.flush());
7169
+ for (const [p, node] of this.byPath) if (within4(p, path)) await node.serial(() => node.flush());
6920
7170
  }
6921
7171
  async unlinking(path) {
6922
7172
  for (const [held, node] of [...this.byPath]) {
6923
- if (!within3(held, path)) continue;
7173
+ if (!within4(held, path)) continue;
6924
7174
  await node.serial(async () => void await Promise.all([node.keep(), node.limit()]));
6925
7175
  }
6926
7176
  }
6927
7177
  revoke(prefix) {
6928
7178
  for (const [path, node] of this.byPath) {
6929
- if (!within3(path, prefix)) continue;
7179
+ if (!within4(path, prefix)) continue;
6930
7180
  node.revoke();
6931
7181
  this.byPath.delete(path);
6932
7182
  }
6933
7183
  }
6934
7184
  unlinked(path) {
6935
7185
  for (const [held, node] of [...this.byPath]) {
6936
- if (!within3(held, path)) continue;
7186
+ if (!within4(held, path)) continue;
6937
7187
  node.orphaned = true;
6938
7188
  this.byPath.delete(held);
6939
7189
  }
@@ -6942,8 +7192,8 @@ var VfsNodes = class {
6942
7192
  if (from === to) return;
6943
7193
  const moved2 = [];
6944
7194
  for (const [p, node] of this.byPath) {
6945
- if (within3(p, from)) moved2.push(node);
6946
- else if (within3(p, to)) {
7195
+ if (within4(p, from)) moved2.push(node);
7196
+ else if (within4(p, to)) {
6947
7197
  node.orphaned = true;
6948
7198
  this.byPath.delete(p);
6949
7199
  }
@@ -7197,6 +7447,7 @@ function isWasmSyscall(req) {
7197
7447
  return typeof op === "string" && SYSCALL_OPS.has(op);
7198
7448
  }
7199
7449
  var MAX_READ = 1024 * 1024;
7450
+ var HOSTS_ONLY = new Resolver({ routes: new Routes() });
7200
7451
  var WasmProcess = class {
7201
7452
  exited = false;
7202
7453
  children;
@@ -7588,10 +7839,12 @@ var WasmProcess = class {
7588
7839
  }
7589
7840
  socketSyscall(req) {
7590
7841
  this.net ??= this.options.net ?? new LoopbackNet();
7842
+ const resolver = this.options.resolver ?? HOSTS_ONLY;
7591
7843
  return socketSyscall(req, {
7592
7844
  fds: this.fds,
7593
7845
  net: this.net,
7594
- blocking: () => this.blockingSignal()
7846
+ blocking: () => this.blockingSignal(),
7847
+ resolve: (name2, family, blocking) => resolver.resolve(name2, family, blocking)
7595
7848
  });
7596
7849
  }
7597
7850
  tty(fd) {
@@ -7786,6 +8039,7 @@ function spawnWasmProcess(opts) {
7786
8039
  http: opts.http,
7787
8040
  locks: opts.locks,
7788
8041
  onReap: opts.onReap,
8042
+ resolver: opts.resolver,
7789
8043
  onPending: (sig) => void Atomics.or(header2, SAB_I_SIGNALS, sigbit(sig)),
7790
8044
  onTimer: (which) => void Atomics.or(header2, SAB_I_TIMERS, 1 << which),
7791
8045
  hasPending: () => Atomics.load(header2, SAB_I_SIGNALS) !== 0 || Atomics.load(header2, SAB_I_TIMERS) !== 0,
@@ -9325,6 +9579,9 @@ var Launcher = class {
9325
9579
  cdp;
9326
9580
  ca;
9327
9581
  transport;
9582
+ uplink;
9583
+ routes;
9584
+ resolver;
9328
9585
  watchers = new FsWatchers();
9329
9586
  pnpmHome;
9330
9587
  mounts;
@@ -9364,6 +9621,10 @@ var Launcher = class {
9364
9621
  this.env = { ...networkEnv(), SLICC_CDP_URL: this.cdp.url, ...options.env };
9365
9622
  this.ca = kernelCa(options.caStore ?? memoryCaStore());
9366
9623
  this.transport = options.transport ?? missingTransport();
9624
+ this.uplink = options.uplink;
9625
+ this.routes = new Routes(options.uplink?.traits.ipv6 === true);
9626
+ if (options.uplink?.routes) this.routes.set(options.uplink.routes);
9627
+ this.resolver = new Resolver({ uplink: options.uplink, routes: this.routes });
9367
9628
  enableNetwork(this.net, {
9368
9629
  transport: this.transport,
9369
9630
  engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
@@ -9378,6 +9639,9 @@ var Launcher = class {
9378
9639
  this.catalog = void 0;
9379
9640
  });
9380
9641
  }
9642
+ setRoutes(table) {
9643
+ this.routes.set(table);
9644
+ }
9381
9645
  commands() {
9382
9646
  this.catalog ??= this.roots().then((roots) => scanCommands(this.base, roots));
9383
9647
  return this.catalog;
@@ -9557,6 +9821,7 @@ var Launcher = class {
9557
9821
  net: this.net,
9558
9822
  http: new HttpHandles(this.transport),
9559
9823
  locks: this.locks,
9824
+ resolver: this.resolver,
9560
9825
  onReap: (child) => this.reaped(child),
9561
9826
  ...req.fork ? { fork: req.fork } : {},
9562
9827
  ...this.shownIds(req)
@@ -9959,9 +10224,22 @@ function cdpOption(req, port) {
9959
10224
  )
9960
10225
  };
9961
10226
  }
10227
+ function remotes(port, req) {
10228
+ return {
10229
+ ...req.transport ? { transport: new RemoteTransport(port, req.transport) } : {},
10230
+ ...req.uplink ? { uplink: new RemoteUplink(port, req.uplink.traits, req.uplink.routes) } : {}
10231
+ };
10232
+ }
10233
+ function answered(data, remote, uplink) {
10234
+ if (data.net !== void 0) remote?.receive(data);
10235
+ else if (typeof data.uplink === "string") uplink?.receive(data);
10236
+ else return false;
10237
+ return true;
10238
+ }
9962
10239
  function serveKernel(port, deps) {
9963
10240
  let launcher;
9964
10241
  let remote;
10242
+ let uplink;
9965
10243
  const terminals = /* @__PURE__ */ new Map();
9966
10244
  const reply = (id, body, transfer) => port.postMessage({ id, ...body }, transfer);
9967
10245
  const clients = /* @__PURE__ */ new Set();
@@ -10017,7 +10295,8 @@ function serveKernel(port, deps) {
10017
10295
  launcher = (req.root ? Promise.resolve(req.root) : deps.storage()).then(async (root) => {
10018
10296
  const name2 = req.metadata ?? META_DB;
10019
10297
  const { meta, media } = await stores(deps, name2, req.media);
10020
- remote = req.transport ? new RemoteTransport(port, req.transport) : void 0;
10298
+ const net = remotes(port, req);
10299
+ ({ transport: remote, uplink } = net);
10021
10300
  const ranged2 = await inOpfs(deps, root);
10022
10301
  const fs = new OpfsFs(root, meta, dirsChannel(name2), { ranged: ranged2 });
10023
10302
  await fs.reconcile();
@@ -10027,7 +10306,7 @@ function serveKernel(port, deps) {
10027
10306
  ...deps.createDriverWorker ? { createDriverWorker: deps.createDriverWorker } : {},
10028
10307
  ...req.modules ? { modules: req.modules } : {},
10029
10308
  ...req.env ? { env: req.env } : {},
10030
- ...remote ? { transport: remote } : {},
10309
+ ...net,
10031
10310
  ...media ? { media } : {},
10032
10311
  onMountPending: (medium) => port.postMessage({ medium }),
10033
10312
  ...req.hostfs ? { hostfs: askGrant } : {},
@@ -10052,6 +10331,10 @@ function serveKernel(port, deps) {
10052
10331
  if (req.op === "init") return init(req);
10053
10332
  if (req.op === "open-terminal") return terminal(req);
10054
10333
  if (req.op === "connect") return connect();
10334
+ if (req.op === "routes") {
10335
+ (await ready2()).setRoutes(req.routes);
10336
+ return true;
10337
+ }
10055
10338
  if (req.op === "dial") return dial(req);
10056
10339
  if (req.op === "mount" || req.op === "umount" || req.op === "mounts" || req.op === "insert")
10057
10340
  return mountOp(req);
@@ -10100,10 +10383,7 @@ function serveKernel(port, deps) {
10100
10383
  policies.delete(answer.id);
10101
10384
  return;
10102
10385
  }
10103
- if (event.data.net !== void 0) {
10104
- remote?.receive(event.data);
10105
- return;
10106
- }
10386
+ if (answered(event.data, remote, uplink)) return;
10107
10387
  const req = event.data;
10108
10388
  handle(req).then(
10109
10389
  (result) => reply(req.id, { result }, result instanceof MessagePort ? [result] : void 0),