@ai-ecoverse/slicc-kernel 1.27.0 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -282,6 +282,101 @@ async function loopbackFetch(dial, input, options) {
282
282
  }
283
283
  }
284
284
 
285
+ // src/kernel/net/loopback-names.ts
286
+ var HOST_LOOPBACK = "host.slicc.internal";
287
+ var HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2];
288
+ var PAGE_LOOPBACK = "kernel.localhost";
289
+ var PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;
290
+
291
+ // src/kernel/net/routes.ts
292
+ var HOST_ADDRESS = HOST_LOOPBACK_ADDRESS.join(".");
293
+ function ipv4Bytes(text) {
294
+ const parts = text.split(".");
295
+ if (parts.length !== 4 || !parts.every((p) => /^\d{1,3}$/.test(p))) return void 0;
296
+ const bytes = parts.map(Number);
297
+ return bytes.every((b) => b <= 255) ? bytes : void 0;
298
+ }
299
+ function ipv6Groups(text) {
300
+ if (!text.includes(":")) return void 0;
301
+ let canonical;
302
+ try {
303
+ canonical = new URL(`http://[${text}]/`).hostname.slice(1, -1);
304
+ } catch {
305
+ return void 0;
306
+ }
307
+ const [head, tail] = canonical.split("::");
308
+ const part = (s) => s ? s.split(":").map((h) => Number.parseInt(h, 16)) : [];
309
+ const front = part(head);
310
+ const back = part(tail);
311
+ return tail === void 0 ? front : [...front, ...new Array(8 - front.length - back.length).fill(0), ...back];
312
+ }
313
+ function toBits(values, width) {
314
+ return values.reduce((acc, v) => acc << BigInt(width) | BigInt(v), 0n);
315
+ }
316
+ function addressBits(host) {
317
+ const v4 = ipv4Bytes(host);
318
+ if (v4) return { v6: false, bits: toBits(v4, 8) };
319
+ const v6 = ipv6Groups(host);
320
+ return v6 ? { v6: true, bits: toBits(v6, 16) } : void 0;
321
+ }
322
+ function parsePrefix(text) {
323
+ const parts = text.trim().split("/");
324
+ if (parts.length > 2) return void 0;
325
+ const [address = "", size] = parts;
326
+ const parsed = addressBits(address);
327
+ if (!parsed) return void 0;
328
+ const max = parsed.v6 ? 128 : 32;
329
+ const length = size === void 0 ? max : /^\d{1,3}$/.test(size) ? Number(size) : -1;
330
+ if (length < 0 || length > max) return void 0;
331
+ return { ...parsed, length };
332
+ }
333
+ function within(prefix, addr) {
334
+ if (prefix.v6 !== addr.v6) return false;
335
+ const shift = BigInt((prefix.v6 ? 128 : 32) - prefix.length);
336
+ return addr.bits >> shift === prefix.bits >> shift;
337
+ }
338
+ var RESERVED = [
339
+ "0.0.0.0/8",
340
+ "127.0.0.0/8",
341
+ "169.254.0.0/16",
342
+ "224.0.0.0/4",
343
+ "240.0.0.0/4",
344
+ "::/96",
345
+ "::ffff:0:0/96",
346
+ "fe80::/10",
347
+ "ff00::/8"
348
+ ].map((p) => parsePrefix(p));
349
+ var Routes = class {
350
+ table = [];
351
+ exit = false;
352
+ ipv6;
353
+ constructor(ipv6 = false) {
354
+ this.ipv6 = ipv6;
355
+ }
356
+ set(table) {
357
+ const parsed = (table.prefixes ?? []).map(parsePrefix);
358
+ const bad = (table.prefixes ?? []).find((_, i) => !parsed[i]);
359
+ if (bad !== void 0) throw new Error(`not an address prefix: ${bad}`);
360
+ this.table = parsed;
361
+ this.exit = table.exit === true;
362
+ }
363
+ classify(host) {
364
+ const name = host.toLowerCase().replace(/\.$/, "");
365
+ if (name === "localhost" || name.endsWith(".localhost") || name === "0.0.0.0") {
366
+ return "loopback";
367
+ }
368
+ if (name === HOST_LOOPBACK || name === HOST_ADDRESS) return "host";
369
+ const addr = addressBits(name);
370
+ if (!addr) return "unreachable";
371
+ if (addr.v6 && addr.bits === 1n) return "loopback";
372
+ if (!addr.v6 && addr.bits >> 24n === 127n) return "loopback";
373
+ if (RESERVED.some((p) => within(p, addr))) return "reserved";
374
+ if (addr.v6 && !this.ipv6) return "unreachable";
375
+ if (this.exit || this.table.some((p) => within(p, addr))) return "uplink";
376
+ return "unreachable";
377
+ }
378
+ };
379
+
285
380
  // src/kernel/net/uplink.ts
286
381
  function answerOf(result) {
287
382
  const answer = Array.isArray(result) ? { addresses: result } : result;
@@ -300,30 +395,90 @@ function failure(uid, e) {
300
395
  ...typeof code === "string" ? { code } : {}
301
396
  };
302
397
  }
398
+ var NEVER = new Routes(true);
303
399
  function serveUplink(peer, uplink) {
400
+ const conns = /* @__PURE__ */ new Map();
304
401
  const pending = /* @__PURE__ */ new Map();
305
- const answer = (call) => {
306
- if (call.uplink === "cancel") return pending.get(call.uid)?.abort();
402
+ let nextCid = 0;
403
+ let closed = false;
404
+ const run = (uid, work) => {
307
405
  const abort = new AbortController();
308
- pending.set(call.uid, abort);
309
- void (async () => {
310
- try {
311
- const answer2 = answerOf(await uplink.resolve(call.name, call.family, abort.signal));
312
- peer.postMessage({ uplink: "resolved", uid: call.uid, ...answer2 });
313
- } catch (e) {
314
- peer.postMessage(failure(call.uid, e));
315
- } finally {
316
- pending.delete(call.uid);
317
- }
318
- })();
406
+ pending.set(uid, abort);
407
+ void work(abort.signal).then(
408
+ (reply) => reply && peer.postMessage(reply, transferOf(reply)),
409
+ (e) => peer.postMessage(failure(uid, e))
410
+ ).finally(() => pending.delete(uid));
411
+ };
412
+ const conn = (cid) => {
413
+ const found = conns.get(cid);
414
+ if (!found) throw Object.assign(new Error("no such uplink connection"), { code: "EBADF" });
415
+ return found;
416
+ };
417
+ const dial = async (call, signal) => {
418
+ const kind = NEVER.classify(call.host);
419
+ if (kind === "loopback" || kind === "host" || kind === "reserved") {
420
+ throw Object.assign(new Error(`the uplink does not carry ${call.host}`), {
421
+ code: "ENETUNREACH"
422
+ });
423
+ }
424
+ const { host, port } = call;
425
+ const opened = await uplink.dial({ network: "tcp", host, port, signal });
426
+ if (signal.aborted || closed) {
427
+ opened.close();
428
+ return void 0;
429
+ }
430
+ const cid = ++nextCid;
431
+ conns.set(cid, opened);
432
+ const { localAddr, remoteAddr } = opened;
433
+ return { uplink: "dialled", uid: call.uid, cid, localAddr, remoteAddr };
434
+ };
435
+ const drop = (cid) => {
436
+ const found = conns.get(cid);
437
+ conns.delete(cid);
438
+ found?.close();
319
439
  };
320
440
  return {
321
- answer,
322
- close: () => {
441
+ answer(call) {
442
+ switch (call.uplink) {
443
+ case "resolve":
444
+ return run(call.uid, async (signal) => ({
445
+ uplink: "resolved",
446
+ uid: call.uid,
447
+ ...answerOf(await uplink.resolve(call.name, call.family, signal))
448
+ }));
449
+ case "dial":
450
+ return run(call.uid, (signal) => dial(call, signal));
451
+ case "cancel":
452
+ return pending.get(call.uid)?.abort();
453
+ case "read":
454
+ return run(call.uid, async () => ({
455
+ uplink: "data",
456
+ uid: call.uid,
457
+ bytes: (await conn(call.cid).read())?.slice() ?? null
458
+ }));
459
+ case "write":
460
+ return run(call.uid, async () => ({
461
+ uplink: "written",
462
+ uid: call.uid,
463
+ n: await conn(call.cid).write(call.bytes)
464
+ }));
465
+ case "close-write":
466
+ return conns.get(call.cid)?.closeWrite();
467
+ default:
468
+ return drop(call.cid);
469
+ }
470
+ },
471
+ close() {
472
+ closed = true;
323
473
  for (const abort of pending.values()) abort.abort();
474
+ for (const cid of [...conns.keys()]) drop(cid);
324
475
  }
325
476
  };
326
477
  }
478
+ function transferOf(reply) {
479
+ if (reply.uplink !== "data" || !reply.bytes) return void 0;
480
+ return [reply.bytes.buffer];
481
+ }
327
482
 
328
483
  // src/body-idle.ts
329
484
  var BODY_IDLE_MS = 5 * 60 * 1e3;
@@ -604,7 +759,7 @@ var RemoteTransport = class {
604
759
  };
605
760
 
606
761
  // src/client/protocol.ts
607
- var PROTOCOL = [1, 6];
762
+ var PROTOCOL = [1, 7];
608
763
  function locksOf(scope = globalThis) {
609
764
  const locks = scope.navigator?.locks;
610
765
  return typeof locks?.request === "function" ? locks : void 0;