@ai-ecoverse/slicc-kernel 1.126.0 → 1.127.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.
@@ -2655,7 +2655,7 @@ var KERNEL_VERSION, OS_RELEASE;
2655
2655
  var init_version = __esm({
2656
2656
  "src/version.ts"() {
2657
2657
  "use strict";
2658
- KERNEL_VERSION = true ? "1.126.0" : "0.0.0-dev";
2658
+ KERNEL_VERSION = true ? "1.127.0" : "0.0.0-dev";
2659
2659
  OS_RELEASE = "6.0.0-slicc";
2660
2660
  }
2661
2661
  });
package/dist/testing.js CHANGED
@@ -308,6 +308,7 @@ function fakeUplink(options = {}) {
308
308
  return {
309
309
  localAddr: named(options.address ?? "100.100.100.100", nextPort++),
310
310
  remoteAddr: named(host, port),
311
+ ...options.exit === void 0 ? {} : { exit: options.exit },
311
312
  read: () => down.read(),
312
313
  write: async (bytes) => {
313
314
  await up.push(bytes);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/kernel/net/loopback-names.ts", "../src/kernel/net/routes.ts", "../src/kernel/net/uplink.ts", "../src/cron/clock.ts", "../src/testing.ts"],
4
- "sourcesContent": ["export const HOST_LOOPBACK = 'host.slicc.internal';\n\nexport const HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2] as const;\n\nexport const PAGE_LOOPBACK = 'kernel.localhost';\n\nexport const PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;\n\nexport function toHostLoopback(href: string): string {\n const url = new URL(href);\n if (url.hostname.replace(/\\.$/, '').toLowerCase() !== HOST_LOOPBACK) return href;\n url.hostname = '127.0.0.1';\n return url.href;\n}\n\nconst LOOPBACK_V4 = /^127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/;\nconst LOOPBACK_V6 = new Set(['::1', '::', '0:0:0:0:0:0:0:1']);\nconst MAPPED_V4 = /^::ffff:7f[0-9a-f]{2}:[0-9a-f]{1,4}$/;\nconst KERNEL_PORT = /^(\\d{1,5})\\.kernel\\.localhost$/;\nconst SCHEMES: Readonly<Record<string, number>> = {\n 'http:': 80,\n 'ws:': 80,\n 'https:': 443,\n 'wss:': 443,\n};\n\nexport function isKernelLoopbackHost(hostname: string): boolean {\n const host = hostname\n .replace(/^\\[(.*)\\]$/, '$1')\n .replace(/\\.$/, '')\n .toLowerCase();\n return (\n host === 'localhost' ||\n host.endsWith('.localhost') ||\n host === '0.0.0.0' ||\n LOOPBACK_V4.test(host) ||\n LOOPBACK_V6.has(host) ||\n MAPPED_V4.test(host)\n );\n}\n\nexport function kernelLoopbackPort(href: string): number | undefined {\n let url: URL;\n try {\n url = new URL(href);\n } catch {\n return undefined;\n }\n const fallback = SCHEMES[url.protocol];\n if (fallback === undefined || !isKernelLoopbackHost(url.hostname)) return undefined;\n const named = KERNEL_PORT.exec(url.hostname.replace(/\\.$/, '').toLowerCase())?.[1];\n const port = Number(named ?? (url.port || fallback));\n return port >= 1 && port <= 65535 ? port : undefined;\n}\n\nexport const DEFAULT_HOSTNAME = 'slicc';\n\nexport const HOSTNAME_ADDRESS = '127.0.1.1';\n\nexport const LIBC_HOSTNAMES: readonly string[] = ['emscripten', 'wasmer.sh'];\n\nconst LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;\n\nfunction reserved(name: string): boolean {\n const lower = name.toLowerCase();\n return (\n lower === 'localhost' ||\n lower.endsWith('.localhost') ||\n lower === HOST_LOOPBACK ||\n lower === 'wasmer.sh' ||\n /^\\d+(\\.\\d+){3}$/.test(lower)\n );\n}\n\nexport function isHostname(name: unknown): name is string {\n if (typeof name !== 'string' || name.length > 253 || reserved(name)) return false;\n return name.split('.').every((l) => LABEL.test(l));\n}\n\nexport function hostnameOf(env: Readonly<Record<string, string>>): string {\n return isHostname(env.HOSTNAME) ? env.HOSTNAME : DEFAULT_HOSTNAME;\n}\n\nexport function ownNames(hostname: string): string[] {\n return [...new Set([hostname.toLowerCase(), ...LIBC_HOSTNAMES])];\n}\n\nexport function hostsFile(hostname: string = DEFAULT_HOSTNAME): string {\n return [\n '127.0.0.1 localhost localhost.localdomain',\n '::1 localhost ip6-localhost',\n `${HOSTNAME_ADDRESS} ${ownNames(hostname).join(' ')}`,\n `${HOST_LOOPBACK_ADDRESS.join('.')} ${HOST_LOOPBACK}`,\n '',\n ].join('\\n');\n}\n", "import { HOST_LOOPBACK, HOST_LOOPBACK_ADDRESS } from './loopback-names.ts';\n\nexport interface RouteTable {\n prefixes: readonly string[];\n exit: boolean;\n}\n\nexport type Destination = 'loopback' | 'host' | 'reserved' | 'uplink' | 'unreachable';\n\ninterface Prefix {\n v6: boolean;\n bits: bigint;\n length: number;\n}\n\nconst HOST_ADDRESS = HOST_LOOPBACK_ADDRESS.join('.');\n\nexport function ipv4Bytes(text: string): number[] | undefined {\n const parts = text.split('.');\n if (parts.length !== 4 || !parts.every((p) => /^\\d{1,3}$/.test(p))) return undefined;\n const bytes = parts.map(Number);\n return bytes.every((b) => b <= 255) ? bytes : undefined;\n}\n\nexport function ipv6Groups(text: string): number[] | undefined {\n if (!text.includes(':')) return undefined;\n let canonical: string;\n try {\n canonical = new URL(`http://[${text}]/`).hostname.slice(1, -1);\n } catch {\n return undefined;\n }\n const [head, tail] = canonical.split('::');\n const part = (s: string | undefined) =>\n s ? s.split(':').map((h) => Number.parseInt(h, 16)) : [];\n const front = part(head);\n const back = part(tail);\n return tail === undefined\n ? front\n : [...front, ...new Array<number>(8 - front.length - back.length).fill(0), ...back];\n}\n\nfunction toBits(values: number[], width: number): bigint {\n return values.reduce((acc, v) => (acc << BigInt(width)) | BigInt(v), 0n);\n}\n\nfunction addressBits(host: string): { v6: boolean; bits: bigint } | undefined {\n const v4 = ipv4Bytes(host);\n if (v4) return { v6: false, bits: toBits(v4, 8) };\n const v6 = ipv6Groups(host);\n return v6 ? { v6: true, bits: toBits(v6, 16) } : undefined;\n}\n\nfunction parsePrefix(text: string): Prefix | undefined {\n const parts = text.trim().split('/');\n if (parts.length > 2) return undefined;\n const [address = '', size] = parts;\n const parsed = addressBits(address);\n if (!parsed) return undefined;\n const max = parsed.v6 ? 128 : 32;\n const length = size === undefined ? max : /^\\d{1,3}$/.test(size) ? Number(size) : -1;\n if (length < 0 || length > max) return undefined;\n return { ...parsed, length };\n}\n\nfunction within(prefix: Prefix, addr: { v6: boolean; bits: bigint }): boolean {\n if (prefix.v6 !== addr.v6) return false;\n const shift = BigInt((prefix.v6 ? 128 : 32) - prefix.length);\n return addr.bits >> shift === prefix.bits >> shift;\n}\n\nconst RESERVED = [\n '0.0.0.0/8',\n '127.0.0.0/8',\n '169.254.0.0/16',\n '224.0.0.0/4',\n '240.0.0.0/4',\n '::/96',\n '::ffff:0:0/96',\n 'fe80::/10',\n 'ff00::/8',\n].map((p) => parsePrefix(p) as Prefix);\n\nexport class Routes {\n private table: Prefix[] = [];\n private exit = false;\n readonly ipv6: boolean;\n\n constructor(ipv6 = false) {\n this.ipv6 = ipv6;\n }\n\n set(table: RouteTable): void {\n const parsed = (table.prefixes ?? []).map(parsePrefix);\n const bad = (table.prefixes ?? []).find((_, i) => !parsed[i]);\n if (bad !== undefined) throw new Error(`not an address prefix: ${bad}`);\n this.table = parsed as Prefix[];\n this.exit = table.exit === true;\n }\n\n classify(host: string): Destination {\n const name = host.toLowerCase().replace(/\\.$/, '');\n if (name === 'localhost' || name.endsWith('.localhost') || name === '0.0.0.0') {\n return 'loopback';\n }\n if (name === HOST_LOOPBACK || name === HOST_ADDRESS) return 'host';\n const addr = addressBits(name);\n if (!addr) return 'unreachable';\n if (addr.v6 && addr.bits === 1n) return 'loopback';\n if (!addr.v6 && addr.bits >> 24n === 127n) return 'loopback';\n if (RESERVED.some((p) => within(p, addr))) return 'reserved';\n if (addr.v6 && !this.ipv6) return 'unreachable';\n if (this.exit || this.table.some((p) => within(p, addr))) return 'uplink';\n return 'unreachable';\n }\n}\n", "import { Routes, type RouteTable } from './routes.ts';\n\nexport interface UplinkTraits {\n tcp: true;\n udp: boolean;\n ipv6: boolean;\n}\n\nexport type ResolveFamily = 0 | 4 | 6;\n\nexport interface ResolveAnswer {\n addresses: string[];\n ttl?: number;\n}\n\nexport interface UplinkConn {\n readonly localAddr: string;\n readonly remoteAddr: string;\n read(): Promise<Uint8Array | null>;\n write(bytes: Uint8Array): Promise<number>;\n closeWrite(): void;\n close(): void;\n}\n\nexport interface UplinkDial {\n network: 'tcp';\n host: string;\n port: number;\n signal: AbortSignal;\n}\n\nexport interface UplinkDatagram {\n bytes: Uint8Array;\n host: string;\n port: number;\n}\n\nexport interface UplinkUnreachable {\n error: string;\n host: string;\n port: number;\n}\n\nexport type UplinkMessage = UplinkDatagram | UplinkUnreachable;\n\nexport interface UplinkUdpOpen {\n family: 4 | 6;\n signal: AbortSignal;\n}\n\nexport interface UplinkUdp {\n readonly localAddr: string;\n send(bytes: Uint8Array, host: string, port: number): Promise<void>;\n readonly messages: AsyncIterable<UplinkMessage>;\n close(): void;\n}\n\nexport interface NetworkUplink {\n readonly traits: UplinkTraits;\n readonly routes?: RouteTable;\n resolve(\n name: string,\n family: ResolveFamily,\n signal?: AbortSignal\n ): Promise<string[] | ResolveAnswer>;\n dial(req: UplinkDial): Promise<UplinkConn>;\n udp?(req: UplinkUdpOpen): Promise<UplinkUdp>;\n}\n\nexport function carriesUdp(uplink: NetworkUplink): boolean {\n return uplink.traits.udp === true && typeof uplink.udp === 'function';\n}\n\nexport type UplinkCall =\n | { uplink: 'resolve'; uid: number; name: string; family: ResolveFamily }\n | { uplink: 'dial'; uid: number; host: string; port: number }\n | { uplink: 'cancel'; uid: number }\n | { uplink: 'read'; uid: number; cid: number }\n | { uplink: 'write'; uid: number; cid: number; bytes: Uint8Array }\n | { uplink: 'close-write'; cid: number }\n | { uplink: 'close'; cid: number }\n | { uplink: 'udp-open'; uid: number; family: 4 | 6 }\n | { uplink: 'udp-send'; uid: number; cid: number; bytes: Uint8Array; host: string; port: number }\n | { uplink: 'udp-recv'; uid: number; cid: number }\n | { uplink: 'udp-close'; cid: number };\n\nexport type UplinkReply =\n | { uplink: 'resolved'; uid: number; addresses: string[]; ttl?: number }\n | { uplink: 'dialled'; uid: number; cid: number; localAddr: string; remoteAddr: string }\n | { uplink: 'data'; uid: number; bytes: Uint8Array | null }\n | { uplink: 'written'; uid: number; n: number }\n | { uplink: 'udp-opened'; uid: number; cid: number; localAddr: string }\n | { uplink: 'udp-sent'; uid: number }\n | { uplink: 'udp-message'; uid: number; message: UplinkMessage | null }\n | { uplink: 'error'; uid: number; message: string; code?: string };\n\nexport interface UplinkPeer {\n postMessage(message: UplinkReply, transfer?: Transferable[]): void;\n}\n\nexport interface UplinkPort {\n postMessage(message: UplinkCall, transfer?: Transferable[]): void;\n}\n\nexport interface UplinkServer {\n answer(call: UplinkCall): void;\n close(): void;\n}\n\nexport function answerOf(result: string[] | ResolveAnswer): ResolveAnswer {\n const answer = Array.isArray(result) ? { addresses: result } : result;\n const addresses = Array.isArray(answer?.addresses)\n ? answer.addresses.filter((a): a is string => typeof a === 'string')\n : [];\n return {\n addresses,\n ...(typeof answer?.ttl === 'number' && answer.ttl >= 0 ? { ttl: answer.ttl } : {}),\n };\n}\n\nfunction failure(uid: number, e: unknown): UplinkReply {\n const code = (e as { code?: unknown } | null)?.code;\n return {\n uplink: 'error',\n uid,\n message: e instanceof Error ? e.message : String(e),\n ...(typeof code === 'string' ? { code } : {}),\n };\n}\n\nconst NEVER = new Routes(true);\n\nexport function serveUplink(peer: UplinkPeer, uplink: NetworkUplink): UplinkServer {\n const conns = new Map<number, UplinkConn>();\n const pending = new Map<number, AbortController>();\n let nextCid = 0;\n let closed = false;\n const run = (uid: number, work: (signal: AbortSignal) => Promise<UplinkReply | undefined>) => {\n const abort = new AbortController();\n pending.set(uid, abort);\n void work(abort.signal)\n .then(\n (reply) => reply && peer.postMessage(reply, transferOf(reply)),\n (e: unknown) => peer.postMessage(failure(uid, e))\n )\n .finally(() => pending.delete(uid));\n };\n const conn = (cid: number): UplinkConn => {\n const found = conns.get(cid);\n if (!found) throw Object.assign(new Error('no such uplink connection'), { code: 'EBADF' });\n return found;\n };\n const dial = async (call: Extract<UplinkCall, { uplink: 'dial' }>, signal: AbortSignal) => {\n refuseLocal(call.host);\n const { host, port } = call;\n const opened = await uplink.dial({ network: 'tcp', host, port, signal });\n if (signal.aborted || closed) {\n opened.close();\n return undefined;\n }\n const cid = ++nextCid;\n conns.set(cid, opened);\n const { localAddr, remoteAddr } = opened;\n return { uplink: 'dialled' as const, uid: call.uid, cid, localAddr, remoteAddr };\n };\n const drop = (cid: number) => {\n const found = conns.get(cid);\n conns.delete(cid);\n found?.close();\n };\n const udp = new UdpServer(uplink, () => closed);\n return {\n answer(call) {\n switch (call.uplink) {\n case 'resolve':\n return run(call.uid, async (signal) => ({\n uplink: 'resolved',\n uid: call.uid,\n ...answerOf(await uplink.resolve(call.name, call.family, signal)),\n }));\n case 'dial':\n return run(call.uid, (signal) => dial(call, signal));\n case 'cancel':\n return pending.get(call.uid)?.abort();\n case 'read':\n return run(call.uid, async () => ({\n uplink: 'data',\n uid: call.uid,\n bytes: (await conn(call.cid).read())?.slice() ?? null,\n }));\n case 'write':\n return run(call.uid, async () => ({\n uplink: 'written',\n uid: call.uid,\n n: await conn(call.cid).write(call.bytes),\n }));\n case 'close-write':\n return conns.get(call.cid)?.closeWrite();\n case 'close':\n return drop(call.cid);\n case 'udp-close':\n return udp.drop(call.cid);\n default:\n return run(call.uid, (signal) => udp.answer(call, signal));\n }\n },\n close() {\n closed = true;\n for (const abort of pending.values()) abort.abort();\n for (const cid of [...conns.keys()]) drop(cid);\n udp.close();\n },\n };\n}\n\nfunction copied(message: UplinkMessage): UplinkMessage {\n if ('bytes' in message) return { ...message, bytes: message.bytes.slice() };\n return { error: String(message.error), host: message.host, port: message.port };\n}\n\nfunction refuseLocal(host: string): void {\n const kind = NEVER.classify(host);\n if (kind === 'loopback' || kind === 'host' || kind === 'reserved') {\n throw Object.assign(new Error(`the uplink does not carry ${host}`), { code: 'ENETUNREACH' });\n }\n}\n\ntype UdpCall = Extract<UplinkCall, { uplink: 'udp-open' | 'udp-send' | 'udp-recv' }>;\n\nclass UdpServer {\n private readonly sockets = new Map<\n number,\n { udp: UplinkUdp; messages: AsyncIterator<UplinkMessage> }\n >();\n private next = 0;\n private readonly uplink: NetworkUplink;\n private readonly closed: () => boolean;\n\n constructor(uplink: NetworkUplink, closed: () => boolean) {\n this.uplink = uplink;\n this.closed = closed;\n }\n\n private socket(cid: number) {\n const found = this.sockets.get(cid);\n if (!found) throw Object.assign(new Error('no such uplink socket'), { code: 'EBADF' });\n return found;\n }\n\n async answer(call: UdpCall, signal: AbortSignal): Promise<UplinkReply | undefined> {\n if (call.uplink === 'udp-open') {\n if (!this.uplink.udp || this.uplink.traits.udp !== true) {\n throw Object.assign(new Error('the uplink carries no datagrams'), { code: 'ENETUNREACH' });\n }\n const opened = await this.uplink.udp({ family: call.family, signal });\n if (signal.aborted || this.closed()) {\n opened.close();\n return undefined;\n }\n const cid = ++this.next;\n this.sockets.set(cid, { udp: opened, messages: opened.messages[Symbol.asyncIterator]() });\n return { uplink: 'udp-opened', uid: call.uid, cid, localAddr: opened.localAddr };\n }\n if (call.uplink === 'udp-send') {\n refuseLocal(call.host);\n await this.socket(call.cid).udp.send(call.bytes, call.host, call.port);\n return { uplink: 'udp-sent', uid: call.uid };\n }\n const next = await this.socket(call.cid).messages.next();\n const message = next.done ? null : copied(next.value);\n return { uplink: 'udp-message', uid: call.uid, message };\n }\n\n drop(cid: number): void {\n const found = this.sockets.get(cid);\n this.sockets.delete(cid);\n found?.udp.close();\n }\n\n close(): void {\n for (const cid of [...this.sockets.keys()]) this.drop(cid);\n }\n}\n\nfunction transferOf(reply: UplinkReply): Transferable[] | undefined {\n if (reply.uplink === 'udp-message' && reply.message && 'bytes' in reply.message) {\n return [reply.message.bytes.buffer as ArrayBuffer];\n }\n if (reply.uplink !== 'data' || !reply.bytes) return undefined;\n return [reply.bytes.buffer as ArrayBuffer];\n}\n\ntype Answer = Exclude<UplinkReply, { uplink: 'error' }>;\n\nexport class RemoteUplink implements NetworkUplink {\n readonly traits: UplinkTraits;\n readonly routes: RouteTable | undefined;\n private readonly port: UplinkPort;\n private readonly waiting = new Map<number, PromiseWithResolvers<Answer>>();\n private nextId = 0;\n\n constructor(port: UplinkPort, traits: UplinkTraits, routes?: RouteTable) {\n this.port = port;\n this.traits = traits;\n this.routes = routes;\n }\n\n receive(reply: UplinkReply): void {\n const waiter = this.waiting.get(reply.uid);\n if (!waiter) return;\n this.waiting.delete(reply.uid);\n if (reply.uplink === 'error') {\n waiter.reject(Object.assign(new Error(reply.message), { code: reply.code }));\n } else waiter.resolve(reply);\n }\n\n private ask<T extends Answer>(\n call: Extract<UplinkCall, { uid: number }>,\n signal?: AbortSignal,\n transfer?: Transferable[]\n ): Promise<T> {\n const waiter = Promise.withResolvers<Answer>();\n this.waiting.set(call.uid, waiter);\n const abort = () => {\n this.waiting.delete(call.uid);\n this.port.postMessage({ uplink: 'cancel', uid: call.uid });\n waiter.reject(signal?.reason);\n };\n signal?.addEventListener('abort', abort, { once: true });\n const settled = () => signal?.removeEventListener('abort', abort);\n waiter.promise.then(settled, settled);\n this.port.postMessage(call, transfer);\n return waiter.promise as Promise<T>;\n }\n\n async resolve(name: string, family: ResolveFamily, signal?: AbortSignal) {\n const reply = await this.ask<Extract<Answer, { uplink: 'resolved' }>>(\n { uplink: 'resolve', uid: ++this.nextId, name, family },\n signal\n );\n return answerOf(reply);\n }\n\n async dial(req: UplinkDial): Promise<UplinkConn> {\n req.signal.throwIfAborted();\n const { host, port } = req;\n const opened = await this.ask<Extract<Answer, { uplink: 'dialled' }>>(\n { uplink: 'dial', uid: ++this.nextId, host, port },\n req.signal\n );\n const { cid, localAddr, remoteAddr } = opened;\n return {\n localAddr,\n remoteAddr,\n read: async () => {\n const got = await this.ask<Extract<Answer, { uplink: 'data' }>>({\n uplink: 'read',\n uid: ++this.nextId,\n cid,\n });\n return got.bytes;\n },\n write: async (bytes) => {\n const copy = bytes.slice();\n const call = { uplink: 'write' as const, uid: ++this.nextId, cid, bytes: copy };\n const done = await this.ask<Extract<Answer, { uplink: 'written' }>>(call, undefined, [\n copy.buffer,\n ]);\n return done.n;\n },\n closeWrite: () => this.port.postMessage({ uplink: 'close-write', cid }),\n close: () => this.port.postMessage({ uplink: 'close', cid }),\n };\n }\n\n async udp(req: UplinkUdpOpen): Promise<UplinkUdp> {\n req.signal.throwIfAborted();\n const opened = await this.ask<Extract<Answer, { uplink: 'udp-opened' }>>(\n { uplink: 'udp-open', uid: ++this.nextId, family: req.family },\n req.signal\n );\n const { cid, localAddr } = opened;\n const recv = () =>\n this.ask<Extract<Answer, { uplink: 'udp-message' }>>({\n uplink: 'udp-recv',\n uid: ++this.nextId,\n cid,\n });\n return {\n localAddr,\n send: async (bytes, host, port) => {\n const copy = bytes.slice();\n const call = {\n uplink: 'udp-send' as const,\n uid: ++this.nextId,\n cid,\n bytes: copy,\n host,\n port,\n };\n await this.ask(call, undefined, [copy.buffer]);\n },\n messages: {\n [Symbol.asyncIterator]: async function* () {\n for (;;) {\n const got = await recv();\n if (!got.message) return;\n yield got.message;\n }\n },\n },\n close: () => this.port.postMessage({ uplink: 'udp-close', cid }),\n };\n }\n}\n", "export interface CronClock {\n now(): number;\n readonly utc?: boolean;\n sleep(until: number, signal: AbortSignal): Promise<void>;\n}\n\nexport const systemCronClock: CronClock = {\n now: () => Date.now(),\n sleep(until, signal) {\n if (signal.aborted) return Promise.resolve();\n return new Promise((resolve) => {\n const done = () => {\n clearTimeout(timer);\n signal.removeEventListener('abort', done);\n resolve();\n };\n const timer = setTimeout(done, Math.max(0, until - Date.now()));\n (timer as { unref?: () => void }).unref?.();\n signal.addEventListener('abort', done, { once: true });\n });\n },\n};\n\nexport interface FakeCronClock extends CronClock {\n set(ms: number): void;\n advance(ms: number): void;\n flush(): Promise<void>;\n readonly sleepers: number;\n}\n\ninterface Sleeper {\n until: number;\n wake: () => void;\n ring: () => void;\n}\n\nexport function fakeCronClock(start = Date.UTC(2026, 0, 1), utc = true): FakeCronClock {\n let now = start;\n const sleepers = new Set<Sleeper>();\n let idle: Array<() => void> = [];\n let awake = 0;\n const settled = () =>\n awake === 0 && sleepers.size > 0 && [...sleepers].every((s) => s.until > now);\n const check = () => {\n if (!settled()) return;\n const waiting = idle;\n idle = [];\n for (const resolve of waiting) resolve();\n };\n const wakeDue = () => {\n for (const sleeper of [...sleepers]) if (sleeper.until <= now) sleeper.ring();\n };\n return {\n utc,\n now: () => now,\n get sleepers() {\n return sleepers.size;\n },\n sleep(until, signal) {\n if (signal.aborted) return Promise.resolve();\n return new Promise((resolve) => {\n const sleeper: Sleeper = {\n until,\n wake: () => {\n sleepers.delete(sleeper);\n signal.removeEventListener('abort', sleeper.wake);\n resolve();\n },\n ring: () => {\n if (!sleepers.has(sleeper)) return;\n awake++;\n sleeper.wake();\n },\n };\n awake = Math.max(0, awake - 1);\n signal.addEventListener('abort', sleeper.wake, { once: true });\n sleepers.add(sleeper);\n if (until <= now) queueMicrotask(sleeper.ring);\n else check();\n });\n },\n set(ms) {\n now = ms;\n for (const sleeper of [...sleepers]) sleeper.ring();\n },\n advance(ms) {\n now += ms;\n wakeDue();\n },\n flush() {\n return new Promise((resolve) => {\n idle.push(resolve);\n queueMicrotask(check);\n });\n },\n };\n}\n", "import type { RouteTable } from './kernel/net/routes.ts';\nimport {\n answerOf,\n type NetworkUplink,\n type ResolveAnswer,\n type ResolveFamily,\n type UplinkConn,\n type UplinkDatagram,\n type UplinkDial,\n type UplinkMessage,\n type UplinkUdp,\n type UplinkUdpOpen,\n type UplinkUnreachable,\n} from './kernel/net/uplink.ts';\n\nexport { type CronClock, type FakeCronClock, fakeCronClock } from './cron/clock.ts';\nexport type {\n NetworkUplink,\n ResolveAnswer,\n ResolveFamily,\n RouteTable,\n UplinkConn,\n UplinkDatagram,\n UplinkDial,\n UplinkMessage,\n UplinkUdp,\n UplinkUdpOpen,\n UplinkUnreachable,\n};\n\nexport type FakeDatagramPeer = (\n bytes: Uint8Array\n) => Uint8Array | readonly Uint8Array[] | { error: string } | undefined;\n\nexport interface FakePeerSocket {\n read(): Promise<Uint8Array | null>;\n write(bytes: Uint8Array): void;\n end(): void;\n reset(code?: string): void;\n readonly buffered: number;\n drained(): Promise<void>;\n readonly closed: Promise<void>;\n}\n\nexport type FakePeer = ((socket: FakePeerSocket) => void) | { error: string } | { hang: true };\n\nexport interface FakeUplinkOptions {\n names?: Readonly<Record<string, string[] | ResolveAnswer>>;\n routes?: RouteTable;\n ipv6?: boolean;\n peers?: Readonly<Record<string, FakePeer>>;\n datagrams?: Readonly<Record<string, FakeDatagramPeer>>;\n address?: string;\n}\n\nexport interface FakeUplink extends NetworkUplink {\n readonly asked: Array<{ name: string; family: ResolveFamily }>;\n readonly dialled: Array<{ host: string; port: number }>;\n readonly sent: Array<{ host: string; port: number; bytes: Uint8Array }>;\n udp(req: UplinkUdpOpen): Promise<UplinkUdp>;\n}\n\nconst failure = (code: string, message: string) => Object.assign(new Error(message), { code });\n\nclass Stream {\n private readonly queue: Array<{\n bytes: Uint8Array;\n taken: () => void;\n lost: (error: Error) => void;\n }> = [];\n private ended = false;\n private broken: Error | undefined;\n private waiters: Array<() => void> = [];\n size = 0;\n\n private wake(): void {\n const waiters = this.waiters;\n this.waiters = [];\n for (const wake of waiters) wake();\n }\n\n push(bytes: Uint8Array): Promise<void> {\n if (this.broken) return Promise.reject(this.broken);\n if (this.ended) return Promise.reject(failure('EPIPE', 'the stream is closed'));\n return new Promise((taken, lost) => {\n this.queue.push({ bytes: bytes.slice(), taken, lost });\n this.size += bytes.length;\n this.wake();\n });\n }\n\n end(): void {\n this.ended = true;\n this.wake();\n }\n\n fail(error: Error): void {\n this.broken = error;\n for (const pending of this.queue.splice(0)) pending.lost(error);\n this.size = 0;\n this.wake();\n }\n\n async read(): Promise<Uint8Array | null> {\n while (this.queue.length === 0 && !this.ended && !this.broken) {\n await new Promise<void>((resolve) => this.waiters.push(resolve));\n }\n if (this.broken) throw this.broken;\n const next = this.queue.shift();\n if (!next) return null;\n this.size -= next.bytes.length;\n next.taken();\n this.wake();\n return next.bytes;\n }\n\n async drained(): Promise<void> {\n while (this.size > 0) await new Promise<void>((resolve) => this.waiters.push(resolve));\n }\n}\n\nfunction named(host: string, port: number): string {\n return `${host.includes(':') ? `[${host}]` : host}:${port}`;\n}\n\nclass Inbox {\n private readonly queue: UplinkMessage[] = [];\n private ended = false;\n private wake: (() => void) | undefined;\n\n push(message: UplinkMessage): void {\n if (this.ended) return;\n this.queue.push(message);\n this.wake?.();\n }\n\n end(): void {\n this.ended = true;\n this.wake?.();\n }\n\n async *[Symbol.asyncIterator](): AsyncGenerator<UplinkMessage> {\n for (;;) {\n const next = this.queue.shift();\n if (next) yield next;\n else if (this.ended) return;\n else await new Promise<void>((resolve) => (this.wake = resolve));\n }\n }\n}\n\nfunction fakeUdp(\n options: FakeUplinkOptions,\n sent: FakeUplink['sent'],\n local: string\n): Promise<UplinkUdp> {\n const inbox = new Inbox();\n return Promise.resolve({\n localAddr: local,\n messages: inbox,\n send: async (bytes, host, port) => {\n sent.push({ host, port, bytes: bytes.slice() });\n const answer = options.datagrams?.[named(host, port)]?.(bytes.slice());\n if (!answer) return;\n if ('error' in answer) {\n inbox.push({ error: answer.error, host, port });\n return;\n }\n const replies = answer instanceof Uint8Array ? [answer] : answer;\n for (const reply of replies) inbox.push({ bytes: reply.slice(), host, port });\n },\n close: () => inbox.end(),\n });\n}\n\nfunction hang(signal: AbortSignal): Promise<never> {\n return new Promise((_, reject) => {\n signal.addEventListener('abort', () => reject(failure('ECONNABORTED', 'dial aborted')), {\n once: true,\n });\n });\n}\n\nexport function fakeUplink(options: FakeUplinkOptions = {}): FakeUplink {\n const asked: FakeUplink['asked'] = [];\n const dialled: FakeUplink['dialled'] = [];\n const sent: FakeUplink['sent'] = [];\n let nextPort = 40000;\n return {\n traits: { tcp: true, udp: options.datagrams !== undefined, ipv6: options.ipv6 === true },\n ...(options.routes ? { routes: options.routes } : {}),\n asked,\n dialled,\n sent,\n udp: () => fakeUdp(options, sent, named(options.address ?? '100.100.100.100', nextPort++)),\n resolve: async (name, family) => {\n asked.push({ name, family });\n return answerOf(options.names?.[name] ?? []);\n },\n dial: async ({ host, port, signal }) => {\n dialled.push({ host, port });\n const peer = options.peers?.[named(host, port)];\n if (!peer) throw failure('ECONNREFUSED', `nothing listens on ${named(host, port)}`);\n if ('error' in peer) throw failure(peer.error, `${named(host, port)}: ${peer.error}`);\n if ('hang' in peer) return hang(signal);\n const up = new Stream();\n const down = new Stream();\n const closed = Promise.withResolvers<void>();\n peer({\n read: () => up.read(),\n write: (bytes) => void down.push(bytes).catch(() => undefined),\n end: () => down.end(),\n reset: (code = 'ECONNRESET') => {\n const error = failure(code, `the peer reset the connection (${code})`);\n up.fail(error);\n down.fail(error);\n },\n get buffered() {\n return down.size;\n },\n drained: () => down.drained(),\n closed: closed.promise,\n });\n return {\n localAddr: named(options.address ?? '100.100.100.100', nextPort++),\n remoteAddr: named(host, port),\n read: () => down.read(),\n write: async (bytes) => {\n await up.push(bytes);\n return bytes.length;\n },\n closeWrite: () => up.end(),\n close: () => {\n up.end();\n down.end();\n closed.resolve();\n },\n };\n },\n };\n}\n"],
5
- "mappings": ";AAAO,IAAM,gBAAgB;AAEtB,IAAM,wBAAwB,CAAC,IAAI,GAAG,GAAG,CAAC;AAE1C,IAAM,gBAAgB;AAEtB,IAAM,oBAAoB,iBAAiB,aAAa;;;ACS/D,IAAM,eAAe,sBAAsB,KAAK,GAAG;AAE5C,SAAS,UAAU,MAAoC;AAC5D,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,MAAI,MAAM,WAAW,KAAK,CAAC,MAAM,MAAM,CAAC,MAAM,YAAY,KAAK,CAAC,CAAC,EAAG,QAAO;AAC3E,QAAM,QAAQ,MAAM,IAAI,MAAM;AAC9B,SAAO,MAAM,MAAM,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ;AAChD;AAEO,SAAS,WAAW,MAAoC;AAC7D,MAAI,CAAC,KAAK,SAAS,GAAG,EAAG,QAAO;AAChC,MAAI;AACJ,MAAI;AACF,gBAAY,IAAI,IAAI,WAAW,IAAI,IAAI,EAAE,SAAS,MAAM,GAAG,EAAE;AAAA,EAC/D,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,CAAC,MAAM,IAAI,IAAI,UAAU,MAAM,IAAI;AACzC,QAAM,OAAO,CAAC,MACZ,IAAI,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,OAAO,SAAS,GAAG,EAAE,CAAC,IAAI,CAAC;AACzD,QAAM,QAAQ,KAAK,IAAI;AACvB,QAAM,OAAO,KAAK,IAAI;AACtB,SAAO,SAAS,SACZ,QACA,CAAC,GAAG,OAAO,GAAG,IAAI,MAAc,IAAI,MAAM,SAAS,KAAK,MAAM,EAAE,KAAK,CAAC,GAAG,GAAG,IAAI;AACtF;AAEA,SAAS,OAAO,QAAkB,OAAuB;AACvD,SAAO,OAAO,OAAO,CAAC,KAAK,MAAO,OAAO,OAAO,KAAK,IAAK,OAAO,CAAC,GAAG,EAAE;AACzE;AAEA,SAAS,YAAY,MAAyD;AAC5E,QAAM,KAAK,UAAU,IAAI;AACzB,MAAI,GAAI,QAAO,EAAE,IAAI,OAAO,MAAM,OAAO,IAAI,CAAC,EAAE;AAChD,QAAM,KAAK,WAAW,IAAI;AAC1B,SAAO,KAAK,EAAE,IAAI,MAAM,MAAM,OAAO,IAAI,EAAE,EAAE,IAAI;AACnD;AAEA,SAAS,YAAY,MAAkC;AACrD,QAAM,QAAQ,KAAK,KAAK,EAAE,MAAM,GAAG;AACnC,MAAI,MAAM,SAAS,EAAG,QAAO;AAC7B,QAAM,CAAC,UAAU,IAAI,IAAI,IAAI;AAC7B,QAAM,SAAS,YAAY,OAAO;AAClC,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,MAAM,OAAO,KAAK,MAAM;AAC9B,QAAM,SAAS,SAAS,SAAY,MAAM,YAAY,KAAK,IAAI,IAAI,OAAO,IAAI,IAAI;AAClF,MAAI,SAAS,KAAK,SAAS,IAAK,QAAO;AACvC,SAAO,EAAE,GAAG,QAAQ,OAAO;AAC7B;AAEA,SAAS,OAAO,QAAgB,MAA8C;AAC5E,MAAI,OAAO,OAAO,KAAK,GAAI,QAAO;AAClC,QAAM,QAAQ,QAAQ,OAAO,KAAK,MAAM,MAAM,OAAO,MAAM;AAC3D,SAAO,KAAK,QAAQ,UAAU,OAAO,QAAQ;AAC/C;AAEA,IAAM,WAAW;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,IAAI,CAAC,MAAM,YAAY,CAAC,CAAW;AAE9B,IAAM,SAAN,MAAa;AAAA,EACV,QAAkB,CAAC;AAAA,EACnB,OAAO;AAAA,EACN;AAAA,EAET,YAAY,OAAO,OAAO;AACxB,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,IAAI,OAAyB;AAC3B,UAAM,UAAU,MAAM,YAAY,CAAC,GAAG,IAAI,WAAW;AACrD,UAAM,OAAO,MAAM,YAAY,CAAC,GAAG,KAAK,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;AAC5D,QAAI,QAAQ,OAAW,OAAM,IAAI,MAAM,0BAA0B,GAAG,EAAE;AACtE,SAAK,QAAQ;AACb,SAAK,OAAO,MAAM,SAAS;AAAA,EAC7B;AAAA,EAEA,SAAS,MAA2B;AAClC,UAAM,OAAO,KAAK,YAAY,EAAE,QAAQ,OAAO,EAAE;AACjD,QAAI,SAAS,eAAe,KAAK,SAAS,YAAY,KAAK,SAAS,WAAW;AAC7E,aAAO;AAAA,IACT;AACA,QAAI,SAAS,iBAAiB,SAAS,aAAc,QAAO;AAC5D,UAAM,OAAO,YAAY,IAAI;AAC7B,QAAI,CAAC,KAAM,QAAO;AAClB,QAAI,KAAK,MAAM,KAAK,SAAS,GAAI,QAAO;AACxC,QAAI,CAAC,KAAK,MAAM,KAAK,QAAQ,QAAQ,KAAM,QAAO;AAClD,QAAI,SAAS,KAAK,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,EAAG,QAAO;AAClD,QAAI,KAAK,MAAM,CAAC,KAAK,KAAM,QAAO;AAClC,QAAI,KAAK,QAAQ,KAAK,MAAM,KAAK,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,EAAG,QAAO;AACjE,WAAO;AAAA,EACT;AACF;;;ACNO,SAAS,SAAS,QAAiD;AACxE,QAAM,SAAS,MAAM,QAAQ,MAAM,IAAI,EAAE,WAAW,OAAO,IAAI;AAC/D,QAAM,YAAY,MAAM,QAAQ,QAAQ,SAAS,IAC7C,OAAO,UAAU,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ,IACjE,CAAC;AACL,SAAO;AAAA,IACL;AAAA,IACA,GAAI,OAAO,QAAQ,QAAQ,YAAY,OAAO,OAAO,IAAI,EAAE,KAAK,OAAO,IAAI,IAAI,CAAC;AAAA,EAClF;AACF;AAYA,IAAM,QAAQ,IAAI,OAAO,IAAI;;;AC9FtB,SAAS,cAAc,QAAQ,KAAK,IAAI,MAAM,GAAG,CAAC,GAAG,MAAM,MAAqB;AACrF,MAAI,MAAM;AACV,QAAM,WAAW,oBAAI,IAAa;AAClC,MAAI,OAA0B,CAAC;AAC/B,MAAI,QAAQ;AACZ,QAAM,UAAU,MACd,UAAU,KAAK,SAAS,OAAO,KAAK,CAAC,GAAG,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG;AAC9E,QAAM,QAAQ,MAAM;AAClB,QAAI,CAAC,QAAQ,EAAG;AAChB,UAAM,UAAU;AAChB,WAAO,CAAC;AACR,eAAW,WAAW,QAAS,SAAQ;AAAA,EACzC;AACA,QAAM,UAAU,MAAM;AACpB,eAAW,WAAW,CAAC,GAAG,QAAQ,EAAG,KAAI,QAAQ,SAAS,IAAK,SAAQ,KAAK;AAAA,EAC9E;AACA,SAAO;AAAA,IACL;AAAA,IACA,KAAK,MAAM;AAAA,IACX,IAAI,WAAW;AACb,aAAO,SAAS;AAAA,IAClB;AAAA,IACA,MAAM,OAAO,QAAQ;AACnB,UAAI,OAAO,QAAS,QAAO,QAAQ,QAAQ;AAC3C,aAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,cAAM,UAAmB;AAAA,UACvB;AAAA,UACA,MAAM,MAAM;AACV,qBAAS,OAAO,OAAO;AACvB,mBAAO,oBAAoB,SAAS,QAAQ,IAAI;AAChD,oBAAQ;AAAA,UACV;AAAA,UACA,MAAM,MAAM;AACV,gBAAI,CAAC,SAAS,IAAI,OAAO,EAAG;AAC5B;AACA,oBAAQ,KAAK;AAAA,UACf;AAAA,QACF;AACA,gBAAQ,KAAK,IAAI,GAAG,QAAQ,CAAC;AAC7B,eAAO,iBAAiB,SAAS,QAAQ,MAAM,EAAE,MAAM,KAAK,CAAC;AAC7D,iBAAS,IAAI,OAAO;AACpB,YAAI,SAAS,IAAK,gBAAe,QAAQ,IAAI;AAAA,YACxC,OAAM;AAAA,MACb,CAAC;AAAA,IACH;AAAA,IACA,IAAI,IAAI;AACN,YAAM;AACN,iBAAW,WAAW,CAAC,GAAG,QAAQ,EAAG,SAAQ,KAAK;AAAA,IACpD;AAAA,IACA,QAAQ,IAAI;AACV,aAAO;AACP,cAAQ;AAAA,IACV;AAAA,IACA,QAAQ;AACN,aAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,aAAK,KAAK,OAAO;AACjB,uBAAe,KAAK;AAAA,MACtB,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;AClCA,IAAM,UAAU,CAAC,MAAc,YAAoB,OAAO,OAAO,IAAI,MAAM,OAAO,GAAG,EAAE,KAAK,CAAC;AAE7F,IAAM,SAAN,MAAa;AAAA,EACM,QAIZ,CAAC;AAAA,EACE,QAAQ;AAAA,EACR;AAAA,EACA,UAA6B,CAAC;AAAA,EACtC,OAAO;AAAA,EAEC,OAAa;AACnB,UAAM,UAAU,KAAK;AACrB,SAAK,UAAU,CAAC;AAChB,eAAW,QAAQ,QAAS,MAAK;AAAA,EACnC;AAAA,EAEA,KAAK,OAAkC;AACrC,QAAI,KAAK,OAAQ,QAAO,QAAQ,OAAO,KAAK,MAAM;AAClD,QAAI,KAAK,MAAO,QAAO,QAAQ,OAAO,QAAQ,SAAS,sBAAsB,CAAC;AAC9E,WAAO,IAAI,QAAQ,CAAC,OAAO,SAAS;AAClC,WAAK,MAAM,KAAK,EAAE,OAAO,MAAM,MAAM,GAAG,OAAO,KAAK,CAAC;AACrD,WAAK,QAAQ,MAAM;AACnB,WAAK,KAAK;AAAA,IACZ,CAAC;AAAA,EACH;AAAA,EAEA,MAAY;AACV,SAAK,QAAQ;AACb,SAAK,KAAK;AAAA,EACZ;AAAA,EAEA,KAAK,OAAoB;AACvB,SAAK,SAAS;AACd,eAAW,WAAW,KAAK,MAAM,OAAO,CAAC,EAAG,SAAQ,KAAK,KAAK;AAC9D,SAAK,OAAO;AACZ,SAAK,KAAK;AAAA,EACZ;AAAA,EAEA,MAAM,OAAmC;AACvC,WAAO,KAAK,MAAM,WAAW,KAAK,CAAC,KAAK,SAAS,CAAC,KAAK,QAAQ;AAC7D,YAAM,IAAI,QAAc,CAAC,YAAY,KAAK,QAAQ,KAAK,OAAO,CAAC;AAAA,IACjE;AACA,QAAI,KAAK,OAAQ,OAAM,KAAK;AAC5B,UAAM,OAAO,KAAK,MAAM,MAAM;AAC9B,QAAI,CAAC,KAAM,QAAO;AAClB,SAAK,QAAQ,KAAK,MAAM;AACxB,SAAK,MAAM;AACX,SAAK,KAAK;AACV,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,UAAyB;AAC7B,WAAO,KAAK,OAAO,EAAG,OAAM,IAAI,QAAc,CAAC,YAAY,KAAK,QAAQ,KAAK,OAAO,CAAC;AAAA,EACvF;AACF;AAEA,SAAS,MAAM,MAAc,MAAsB;AACjD,SAAO,GAAG,KAAK,SAAS,GAAG,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI;AAC3D;AAEA,IAAM,QAAN,MAAY;AAAA,EACO,QAAyB,CAAC;AAAA,EACnC,QAAQ;AAAA,EACR;AAAA,EAER,KAAK,SAA8B;AACjC,QAAI,KAAK,MAAO;AAChB,SAAK,MAAM,KAAK,OAAO;AACvB,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,MAAY;AACV,SAAK,QAAQ;AACb,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,QAAQ,OAAO,aAAa,IAAmC;AAC7D,eAAS;AACP,YAAM,OAAO,KAAK,MAAM,MAAM;AAC9B,UAAI,KAAM,OAAM;AAAA,eACP,KAAK,MAAO;AAAA,UAChB,OAAM,IAAI,QAAc,CAAC,YAAa,KAAK,OAAO,OAAQ;AAAA,IACjE;AAAA,EACF;AACF;AAEA,SAAS,QACP,SACA,MACA,OACoB;AACpB,QAAM,QAAQ,IAAI,MAAM;AACxB,SAAO,QAAQ,QAAQ;AAAA,IACrB,WAAW;AAAA,IACX,UAAU;AAAA,IACV,MAAM,OAAO,OAAO,MAAM,SAAS;AACjC,WAAK,KAAK,EAAE,MAAM,MAAM,OAAO,MAAM,MAAM,EAAE,CAAC;AAC9C,YAAM,SAAS,QAAQ,YAAY,MAAM,MAAM,IAAI,CAAC,IAAI,MAAM,MAAM,CAAC;AACrE,UAAI,CAAC,OAAQ;AACb,UAAI,WAAW,QAAQ;AACrB,cAAM,KAAK,EAAE,OAAO,OAAO,OAAO,MAAM,KAAK,CAAC;AAC9C;AAAA,MACF;AACA,YAAM,UAAU,kBAAkB,aAAa,CAAC,MAAM,IAAI;AAC1D,iBAAW,SAAS,QAAS,OAAM,KAAK,EAAE,OAAO,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC;AAAA,IAC9E;AAAA,IACA,OAAO,MAAM,MAAM,IAAI;AAAA,EACzB,CAAC;AACH;AAEA,SAAS,KAAK,QAAqC;AACjD,SAAO,IAAI,QAAQ,CAAC,GAAG,WAAW;AAChC,WAAO,iBAAiB,SAAS,MAAM,OAAO,QAAQ,gBAAgB,cAAc,CAAC,GAAG;AAAA,MACtF,MAAM;AAAA,IACR,CAAC;AAAA,EACH,CAAC;AACH;AAEO,SAAS,WAAW,UAA6B,CAAC,GAAe;AACtE,QAAM,QAA6B,CAAC;AACpC,QAAM,UAAiC,CAAC;AACxC,QAAM,OAA2B,CAAC;AAClC,MAAI,WAAW;AACf,SAAO;AAAA,IACL,QAAQ,EAAE,KAAK,MAAM,KAAK,QAAQ,cAAc,QAAW,MAAM,QAAQ,SAAS,KAAK;AAAA,IACvF,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAAA,IACnD;AAAA,IACA;AAAA,IACA;AAAA,IACA,KAAK,MAAM,QAAQ,SAAS,MAAM,MAAM,QAAQ,WAAW,mBAAmB,UAAU,CAAC;AAAA,IACzF,SAAS,OAAO,MAAM,WAAW;AAC/B,YAAM,KAAK,EAAE,MAAM,OAAO,CAAC;AAC3B,aAAO,SAAS,QAAQ,QAAQ,IAAI,KAAK,CAAC,CAAC;AAAA,IAC7C;AAAA,IACA,MAAM,OAAO,EAAE,MAAM,MAAM,OAAO,MAAM;AACtC,cAAQ,KAAK,EAAE,MAAM,KAAK,CAAC;AAC3B,YAAM,OAAO,QAAQ,QAAQ,MAAM,MAAM,IAAI,CAAC;AAC9C,UAAI,CAAC,KAAM,OAAM,QAAQ,gBAAgB,sBAAsB,MAAM,MAAM,IAAI,CAAC,EAAE;AAClF,UAAI,WAAW,KAAM,OAAM,QAAQ,KAAK,OAAO,GAAG,MAAM,MAAM,IAAI,CAAC,KAAK,KAAK,KAAK,EAAE;AACpF,UAAI,UAAU,KAAM,QAAO,KAAK,MAAM;AACtC,YAAM,KAAK,IAAI,OAAO;AACtB,YAAM,OAAO,IAAI,OAAO;AACxB,YAAM,SAAS,QAAQ,cAAoB;AAC3C,WAAK;AAAA,QACH,MAAM,MAAM,GAAG,KAAK;AAAA,QACpB,OAAO,CAAC,UAAU,KAAK,KAAK,KAAK,KAAK,EAAE,MAAM,MAAM,MAAS;AAAA,QAC7D,KAAK,MAAM,KAAK,IAAI;AAAA,QACpB,OAAO,CAAC,OAAO,iBAAiB;AAC9B,gBAAM,QAAQ,QAAQ,MAAM,kCAAkC,IAAI,GAAG;AACrE,aAAG,KAAK,KAAK;AACb,eAAK,KAAK,KAAK;AAAA,QACjB;AAAA,QACA,IAAI,WAAW;AACb,iBAAO,KAAK;AAAA,QACd;AAAA,QACA,SAAS,MAAM,KAAK,QAAQ;AAAA,QAC5B,QAAQ,OAAO;AAAA,MACjB,CAAC;AACD,aAAO;AAAA,QACL,WAAW,MAAM,QAAQ,WAAW,mBAAmB,UAAU;AAAA,QACjE,YAAY,MAAM,MAAM,IAAI;AAAA,QAC5B,MAAM,MAAM,KAAK,KAAK;AAAA,QACtB,OAAO,OAAO,UAAU;AACtB,gBAAM,GAAG,KAAK,KAAK;AACnB,iBAAO,MAAM;AAAA,QACf;AAAA,QACA,YAAY,MAAM,GAAG,IAAI;AAAA,QACzB,OAAO,MAAM;AACX,aAAG,IAAI;AACP,eAAK,IAAI;AACT,iBAAO,QAAQ;AAAA,QACjB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["export const HOST_LOOPBACK = 'host.slicc.internal';\n\nexport const HOST_LOOPBACK_ADDRESS = [10, 0, 2, 2] as const;\n\nexport const PAGE_LOOPBACK = 'kernel.localhost';\n\nexport const PAGE_LOOPBACK_URL = `http://{port}.${PAGE_LOOPBACK}`;\n\nexport function toHostLoopback(href: string): string {\n const url = new URL(href);\n if (url.hostname.replace(/\\.$/, '').toLowerCase() !== HOST_LOOPBACK) return href;\n url.hostname = '127.0.0.1';\n return url.href;\n}\n\nconst LOOPBACK_V4 = /^127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/;\nconst LOOPBACK_V6 = new Set(['::1', '::', '0:0:0:0:0:0:0:1']);\nconst MAPPED_V4 = /^::ffff:7f[0-9a-f]{2}:[0-9a-f]{1,4}$/;\nconst KERNEL_PORT = /^(\\d{1,5})\\.kernel\\.localhost$/;\nconst SCHEMES: Readonly<Record<string, number>> = {\n 'http:': 80,\n 'ws:': 80,\n 'https:': 443,\n 'wss:': 443,\n};\n\nexport function isKernelLoopbackHost(hostname: string): boolean {\n const host = hostname\n .replace(/^\\[(.*)\\]$/, '$1')\n .replace(/\\.$/, '')\n .toLowerCase();\n return (\n host === 'localhost' ||\n host.endsWith('.localhost') ||\n host === '0.0.0.0' ||\n LOOPBACK_V4.test(host) ||\n LOOPBACK_V6.has(host) ||\n MAPPED_V4.test(host)\n );\n}\n\nexport function kernelLoopbackPort(href: string): number | undefined {\n let url: URL;\n try {\n url = new URL(href);\n } catch {\n return undefined;\n }\n const fallback = SCHEMES[url.protocol];\n if (fallback === undefined || !isKernelLoopbackHost(url.hostname)) return undefined;\n const named = KERNEL_PORT.exec(url.hostname.replace(/\\.$/, '').toLowerCase())?.[1];\n const port = Number(named ?? (url.port || fallback));\n return port >= 1 && port <= 65535 ? port : undefined;\n}\n\nexport const DEFAULT_HOSTNAME = 'slicc';\n\nexport const HOSTNAME_ADDRESS = '127.0.1.1';\n\nexport const LIBC_HOSTNAMES: readonly string[] = ['emscripten', 'wasmer.sh'];\n\nconst LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;\n\nfunction reserved(name: string): boolean {\n const lower = name.toLowerCase();\n return (\n lower === 'localhost' ||\n lower.endsWith('.localhost') ||\n lower === HOST_LOOPBACK ||\n lower === 'wasmer.sh' ||\n /^\\d+(\\.\\d+){3}$/.test(lower)\n );\n}\n\nexport function isHostname(name: unknown): name is string {\n if (typeof name !== 'string' || name.length > 253 || reserved(name)) return false;\n return name.split('.').every((l) => LABEL.test(l));\n}\n\nexport function hostnameOf(env: Readonly<Record<string, string>>): string {\n return isHostname(env.HOSTNAME) ? env.HOSTNAME : DEFAULT_HOSTNAME;\n}\n\nexport function ownNames(hostname: string): string[] {\n return [...new Set([hostname.toLowerCase(), ...LIBC_HOSTNAMES])];\n}\n\nexport function hostsFile(hostname: string = DEFAULT_HOSTNAME): string {\n return [\n '127.0.0.1 localhost localhost.localdomain',\n '::1 localhost ip6-localhost',\n `${HOSTNAME_ADDRESS} ${ownNames(hostname).join(' ')}`,\n `${HOST_LOOPBACK_ADDRESS.join('.')} ${HOST_LOOPBACK}`,\n '',\n ].join('\\n');\n}\n", "import { HOST_LOOPBACK, HOST_LOOPBACK_ADDRESS } from './loopback-names.ts';\n\nexport interface RouteTable {\n prefixes: readonly string[];\n exit: boolean;\n}\n\nexport type Destination = 'loopback' | 'host' | 'reserved' | 'uplink' | 'unreachable';\n\ninterface Prefix {\n v6: boolean;\n bits: bigint;\n length: number;\n}\n\nconst HOST_ADDRESS = HOST_LOOPBACK_ADDRESS.join('.');\n\nexport function ipv4Bytes(text: string): number[] | undefined {\n const parts = text.split('.');\n if (parts.length !== 4 || !parts.every((p) => /^\\d{1,3}$/.test(p))) return undefined;\n const bytes = parts.map(Number);\n return bytes.every((b) => b <= 255) ? bytes : undefined;\n}\n\nexport function ipv6Groups(text: string): number[] | undefined {\n if (!text.includes(':')) return undefined;\n let canonical: string;\n try {\n canonical = new URL(`http://[${text}]/`).hostname.slice(1, -1);\n } catch {\n return undefined;\n }\n const [head, tail] = canonical.split('::');\n const part = (s: string | undefined) =>\n s ? s.split(':').map((h) => Number.parseInt(h, 16)) : [];\n const front = part(head);\n const back = part(tail);\n return tail === undefined\n ? front\n : [...front, ...new Array<number>(8 - front.length - back.length).fill(0), ...back];\n}\n\nfunction toBits(values: number[], width: number): bigint {\n return values.reduce((acc, v) => (acc << BigInt(width)) | BigInt(v), 0n);\n}\n\nfunction addressBits(host: string): { v6: boolean; bits: bigint } | undefined {\n const v4 = ipv4Bytes(host);\n if (v4) return { v6: false, bits: toBits(v4, 8) };\n const v6 = ipv6Groups(host);\n return v6 ? { v6: true, bits: toBits(v6, 16) } : undefined;\n}\n\nfunction parsePrefix(text: string): Prefix | undefined {\n const parts = text.trim().split('/');\n if (parts.length > 2) return undefined;\n const [address = '', size] = parts;\n const parsed = addressBits(address);\n if (!parsed) return undefined;\n const max = parsed.v6 ? 128 : 32;\n const length = size === undefined ? max : /^\\d{1,3}$/.test(size) ? Number(size) : -1;\n if (length < 0 || length > max) return undefined;\n return { ...parsed, length };\n}\n\nfunction within(prefix: Prefix, addr: { v6: boolean; bits: bigint }): boolean {\n if (prefix.v6 !== addr.v6) return false;\n const shift = BigInt((prefix.v6 ? 128 : 32) - prefix.length);\n return addr.bits >> shift === prefix.bits >> shift;\n}\n\nconst RESERVED = [\n '0.0.0.0/8',\n '127.0.0.0/8',\n '169.254.0.0/16',\n '224.0.0.0/4',\n '240.0.0.0/4',\n '::/96',\n '::ffff:0:0/96',\n 'fe80::/10',\n 'ff00::/8',\n].map((p) => parsePrefix(p) as Prefix);\n\nexport class Routes {\n private table: Prefix[] = [];\n private exit = false;\n readonly ipv6: boolean;\n\n constructor(ipv6 = false) {\n this.ipv6 = ipv6;\n }\n\n set(table: RouteTable): void {\n const parsed = (table.prefixes ?? []).map(parsePrefix);\n const bad = (table.prefixes ?? []).find((_, i) => !parsed[i]);\n if (bad !== undefined) throw new Error(`not an address prefix: ${bad}`);\n this.table = parsed as Prefix[];\n this.exit = table.exit === true;\n }\n\n classify(host: string): Destination {\n const name = host.toLowerCase().replace(/\\.$/, '');\n if (name === 'localhost' || name.endsWith('.localhost') || name === '0.0.0.0') {\n return 'loopback';\n }\n if (name === HOST_LOOPBACK || name === HOST_ADDRESS) return 'host';\n const addr = addressBits(name);\n if (!addr) return 'unreachable';\n if (addr.v6 && addr.bits === 1n) return 'loopback';\n if (!addr.v6 && addr.bits >> 24n === 127n) return 'loopback';\n if (RESERVED.some((p) => within(p, addr))) return 'reserved';\n if (addr.v6 && !this.ipv6) return 'unreachable';\n if (this.exit || this.table.some((p) => within(p, addr))) return 'uplink';\n return 'unreachable';\n }\n}\n", "import { Routes, type RouteTable } from './routes.ts';\n\nexport interface UplinkTraits {\n tcp: true;\n udp: boolean;\n ipv6: boolean;\n}\n\nexport type ResolveFamily = 0 | 4 | 6;\n\nexport interface ResolveAnswer {\n addresses: string[];\n ttl?: number;\n}\n\nexport interface UplinkConn {\n readonly localAddr: string;\n readonly remoteAddr: string;\n readonly exit?: string;\n read(): Promise<Uint8Array | null>;\n write(bytes: Uint8Array): Promise<number>;\n closeWrite(): void;\n close(): void;\n}\n\nexport interface UplinkDial {\n network: 'tcp';\n host: string;\n port: number;\n signal: AbortSignal;\n}\n\nexport interface UplinkDatagram {\n bytes: Uint8Array;\n host: string;\n port: number;\n}\n\nexport interface UplinkUnreachable {\n error: string;\n host: string;\n port: number;\n}\n\nexport type UplinkMessage = UplinkDatagram | UplinkUnreachable;\n\nexport interface UplinkUdpOpen {\n family: 4 | 6;\n signal: AbortSignal;\n}\n\nexport interface UplinkUdp {\n readonly localAddr: string;\n send(bytes: Uint8Array, host: string, port: number): Promise<void>;\n readonly messages: AsyncIterable<UplinkMessage>;\n close(): void;\n}\n\nexport interface NetworkUplink {\n readonly traits: UplinkTraits;\n readonly routes?: RouteTable;\n resolve(\n name: string,\n family: ResolveFamily,\n signal?: AbortSignal\n ): Promise<string[] | ResolveAnswer>;\n dial(req: UplinkDial): Promise<UplinkConn>;\n udp?(req: UplinkUdpOpen): Promise<UplinkUdp>;\n}\n\nexport function carriesUdp(uplink: NetworkUplink): boolean {\n return uplink.traits.udp === true && typeof uplink.udp === 'function';\n}\n\nexport type UplinkCall =\n | { uplink: 'resolve'; uid: number; name: string; family: ResolveFamily }\n | { uplink: 'dial'; uid: number; host: string; port: number }\n | { uplink: 'cancel'; uid: number }\n | { uplink: 'read'; uid: number; cid: number }\n | { uplink: 'write'; uid: number; cid: number; bytes: Uint8Array }\n | { uplink: 'close-write'; cid: number }\n | { uplink: 'close'; cid: number }\n | { uplink: 'udp-open'; uid: number; family: 4 | 6 }\n | { uplink: 'udp-send'; uid: number; cid: number; bytes: Uint8Array; host: string; port: number }\n | { uplink: 'udp-recv'; uid: number; cid: number }\n | { uplink: 'udp-close'; cid: number };\n\nexport type UplinkReply =\n | { uplink: 'resolved'; uid: number; addresses: string[]; ttl?: number }\n | { uplink: 'dialled'; uid: number; cid: number; localAddr: string; remoteAddr: string }\n | { uplink: 'data'; uid: number; bytes: Uint8Array | null }\n | { uplink: 'written'; uid: number; n: number }\n | { uplink: 'udp-opened'; uid: number; cid: number; localAddr: string }\n | { uplink: 'udp-sent'; uid: number }\n | { uplink: 'udp-message'; uid: number; message: UplinkMessage | null }\n | { uplink: 'error'; uid: number; message: string; code?: string };\n\nexport interface UplinkPeer {\n postMessage(message: UplinkReply, transfer?: Transferable[]): void;\n}\n\nexport interface UplinkPort {\n postMessage(message: UplinkCall, transfer?: Transferable[]): void;\n}\n\nexport interface UplinkServer {\n answer(call: UplinkCall): void;\n close(): void;\n}\n\nexport function answerOf(result: string[] | ResolveAnswer): ResolveAnswer {\n const answer = Array.isArray(result) ? { addresses: result } : result;\n const addresses = Array.isArray(answer?.addresses)\n ? answer.addresses.filter((a): a is string => typeof a === 'string')\n : [];\n return {\n addresses,\n ...(typeof answer?.ttl === 'number' && answer.ttl >= 0 ? { ttl: answer.ttl } : {}),\n };\n}\n\nfunction failure(uid: number, e: unknown): UplinkReply {\n const code = (e as { code?: unknown } | null)?.code;\n return {\n uplink: 'error',\n uid,\n message: e instanceof Error ? e.message : String(e),\n ...(typeof code === 'string' ? { code } : {}),\n };\n}\n\nconst NEVER = new Routes(true);\n\nexport function serveUplink(peer: UplinkPeer, uplink: NetworkUplink): UplinkServer {\n const conns = new Map<number, UplinkConn>();\n const pending = new Map<number, AbortController>();\n let nextCid = 0;\n let closed = false;\n const run = (uid: number, work: (signal: AbortSignal) => Promise<UplinkReply | undefined>) => {\n const abort = new AbortController();\n pending.set(uid, abort);\n void work(abort.signal)\n .then(\n (reply) => reply && peer.postMessage(reply, transferOf(reply)),\n (e: unknown) => peer.postMessage(failure(uid, e))\n )\n .finally(() => pending.delete(uid));\n };\n const conn = (cid: number): UplinkConn => {\n const found = conns.get(cid);\n if (!found) throw Object.assign(new Error('no such uplink connection'), { code: 'EBADF' });\n return found;\n };\n const dial = async (call: Extract<UplinkCall, { uplink: 'dial' }>, signal: AbortSignal) => {\n refuseLocal(call.host);\n const { host, port } = call;\n const opened = await uplink.dial({ network: 'tcp', host, port, signal });\n if (signal.aborted || closed) {\n opened.close();\n return undefined;\n }\n const cid = ++nextCid;\n conns.set(cid, opened);\n const { localAddr, remoteAddr } = opened;\n return { uplink: 'dialled' as const, uid: call.uid, cid, localAddr, remoteAddr };\n };\n const drop = (cid: number) => {\n const found = conns.get(cid);\n conns.delete(cid);\n found?.close();\n };\n const udp = new UdpServer(uplink, () => closed);\n return {\n answer(call) {\n switch (call.uplink) {\n case 'resolve':\n return run(call.uid, async (signal) => ({\n uplink: 'resolved',\n uid: call.uid,\n ...answerOf(await uplink.resolve(call.name, call.family, signal)),\n }));\n case 'dial':\n return run(call.uid, (signal) => dial(call, signal));\n case 'cancel':\n return pending.get(call.uid)?.abort();\n case 'read':\n return run(call.uid, async () => ({\n uplink: 'data',\n uid: call.uid,\n bytes: (await conn(call.cid).read())?.slice() ?? null,\n }));\n case 'write':\n return run(call.uid, async () => ({\n uplink: 'written',\n uid: call.uid,\n n: await conn(call.cid).write(call.bytes),\n }));\n case 'close-write':\n return conns.get(call.cid)?.closeWrite();\n case 'close':\n return drop(call.cid);\n case 'udp-close':\n return udp.drop(call.cid);\n default:\n return run(call.uid, (signal) => udp.answer(call, signal));\n }\n },\n close() {\n closed = true;\n for (const abort of pending.values()) abort.abort();\n for (const cid of [...conns.keys()]) drop(cid);\n udp.close();\n },\n };\n}\n\nfunction copied(message: UplinkMessage): UplinkMessage {\n if ('bytes' in message) return { ...message, bytes: message.bytes.slice() };\n return { error: String(message.error), host: message.host, port: message.port };\n}\n\nfunction refuseLocal(host: string): void {\n const kind = NEVER.classify(host);\n if (kind === 'loopback' || kind === 'host' || kind === 'reserved') {\n throw Object.assign(new Error(`the uplink does not carry ${host}`), { code: 'ENETUNREACH' });\n }\n}\n\ntype UdpCall = Extract<UplinkCall, { uplink: 'udp-open' | 'udp-send' | 'udp-recv' }>;\n\nclass UdpServer {\n private readonly sockets = new Map<\n number,\n { udp: UplinkUdp; messages: AsyncIterator<UplinkMessage> }\n >();\n private next = 0;\n private readonly uplink: NetworkUplink;\n private readonly closed: () => boolean;\n\n constructor(uplink: NetworkUplink, closed: () => boolean) {\n this.uplink = uplink;\n this.closed = closed;\n }\n\n private socket(cid: number) {\n const found = this.sockets.get(cid);\n if (!found) throw Object.assign(new Error('no such uplink socket'), { code: 'EBADF' });\n return found;\n }\n\n async answer(call: UdpCall, signal: AbortSignal): Promise<UplinkReply | undefined> {\n if (call.uplink === 'udp-open') {\n if (!this.uplink.udp || this.uplink.traits.udp !== true) {\n throw Object.assign(new Error('the uplink carries no datagrams'), { code: 'ENETUNREACH' });\n }\n const opened = await this.uplink.udp({ family: call.family, signal });\n if (signal.aborted || this.closed()) {\n opened.close();\n return undefined;\n }\n const cid = ++this.next;\n this.sockets.set(cid, { udp: opened, messages: opened.messages[Symbol.asyncIterator]() });\n return { uplink: 'udp-opened', uid: call.uid, cid, localAddr: opened.localAddr };\n }\n if (call.uplink === 'udp-send') {\n refuseLocal(call.host);\n await this.socket(call.cid).udp.send(call.bytes, call.host, call.port);\n return { uplink: 'udp-sent', uid: call.uid };\n }\n const next = await this.socket(call.cid).messages.next();\n const message = next.done ? null : copied(next.value);\n return { uplink: 'udp-message', uid: call.uid, message };\n }\n\n drop(cid: number): void {\n const found = this.sockets.get(cid);\n this.sockets.delete(cid);\n found?.udp.close();\n }\n\n close(): void {\n for (const cid of [...this.sockets.keys()]) this.drop(cid);\n }\n}\n\nfunction transferOf(reply: UplinkReply): Transferable[] | undefined {\n if (reply.uplink === 'udp-message' && reply.message && 'bytes' in reply.message) {\n return [reply.message.bytes.buffer as ArrayBuffer];\n }\n if (reply.uplink !== 'data' || !reply.bytes) return undefined;\n return [reply.bytes.buffer as ArrayBuffer];\n}\n\ntype Answer = Exclude<UplinkReply, { uplink: 'error' }>;\n\nexport class RemoteUplink implements NetworkUplink {\n readonly traits: UplinkTraits;\n readonly routes: RouteTable | undefined;\n private readonly port: UplinkPort;\n private readonly waiting = new Map<number, PromiseWithResolvers<Answer>>();\n private nextId = 0;\n\n constructor(port: UplinkPort, traits: UplinkTraits, routes?: RouteTable) {\n this.port = port;\n this.traits = traits;\n this.routes = routes;\n }\n\n receive(reply: UplinkReply): void {\n const waiter = this.waiting.get(reply.uid);\n if (!waiter) return;\n this.waiting.delete(reply.uid);\n if (reply.uplink === 'error') {\n waiter.reject(Object.assign(new Error(reply.message), { code: reply.code }));\n } else waiter.resolve(reply);\n }\n\n private ask<T extends Answer>(\n call: Extract<UplinkCall, { uid: number }>,\n signal?: AbortSignal,\n transfer?: Transferable[]\n ): Promise<T> {\n const waiter = Promise.withResolvers<Answer>();\n this.waiting.set(call.uid, waiter);\n const abort = () => {\n this.waiting.delete(call.uid);\n this.port.postMessage({ uplink: 'cancel', uid: call.uid });\n waiter.reject(signal?.reason);\n };\n signal?.addEventListener('abort', abort, { once: true });\n const settled = () => signal?.removeEventListener('abort', abort);\n waiter.promise.then(settled, settled);\n this.port.postMessage(call, transfer);\n return waiter.promise as Promise<T>;\n }\n\n async resolve(name: string, family: ResolveFamily, signal?: AbortSignal) {\n const reply = await this.ask<Extract<Answer, { uplink: 'resolved' }>>(\n { uplink: 'resolve', uid: ++this.nextId, name, family },\n signal\n );\n return answerOf(reply);\n }\n\n async dial(req: UplinkDial): Promise<UplinkConn> {\n req.signal.throwIfAborted();\n const { host, port } = req;\n const opened = await this.ask<Extract<Answer, { uplink: 'dialled' }>>(\n { uplink: 'dial', uid: ++this.nextId, host, port },\n req.signal\n );\n const { cid, localAddr, remoteAddr } = opened;\n return {\n localAddr,\n remoteAddr,\n read: async () => {\n const got = await this.ask<Extract<Answer, { uplink: 'data' }>>({\n uplink: 'read',\n uid: ++this.nextId,\n cid,\n });\n return got.bytes;\n },\n write: async (bytes) => {\n const copy = bytes.slice();\n const call = { uplink: 'write' as const, uid: ++this.nextId, cid, bytes: copy };\n const done = await this.ask<Extract<Answer, { uplink: 'written' }>>(call, undefined, [\n copy.buffer,\n ]);\n return done.n;\n },\n closeWrite: () => this.port.postMessage({ uplink: 'close-write', cid }),\n close: () => this.port.postMessage({ uplink: 'close', cid }),\n };\n }\n\n async udp(req: UplinkUdpOpen): Promise<UplinkUdp> {\n req.signal.throwIfAborted();\n const opened = await this.ask<Extract<Answer, { uplink: 'udp-opened' }>>(\n { uplink: 'udp-open', uid: ++this.nextId, family: req.family },\n req.signal\n );\n const { cid, localAddr } = opened;\n const recv = () =>\n this.ask<Extract<Answer, { uplink: 'udp-message' }>>({\n uplink: 'udp-recv',\n uid: ++this.nextId,\n cid,\n });\n return {\n localAddr,\n send: async (bytes, host, port) => {\n const copy = bytes.slice();\n const call = {\n uplink: 'udp-send' as const,\n uid: ++this.nextId,\n cid,\n bytes: copy,\n host,\n port,\n };\n await this.ask(call, undefined, [copy.buffer]);\n },\n messages: {\n [Symbol.asyncIterator]: async function* () {\n for (;;) {\n const got = await recv();\n if (!got.message) return;\n yield got.message;\n }\n },\n },\n close: () => this.port.postMessage({ uplink: 'udp-close', cid }),\n };\n }\n}\n", "export interface CronClock {\n now(): number;\n readonly utc?: boolean;\n sleep(until: number, signal: AbortSignal): Promise<void>;\n}\n\nexport const systemCronClock: CronClock = {\n now: () => Date.now(),\n sleep(until, signal) {\n if (signal.aborted) return Promise.resolve();\n return new Promise((resolve) => {\n const done = () => {\n clearTimeout(timer);\n signal.removeEventListener('abort', done);\n resolve();\n };\n const timer = setTimeout(done, Math.max(0, until - Date.now()));\n (timer as { unref?: () => void }).unref?.();\n signal.addEventListener('abort', done, { once: true });\n });\n },\n};\n\nexport interface FakeCronClock extends CronClock {\n set(ms: number): void;\n advance(ms: number): void;\n flush(): Promise<void>;\n readonly sleepers: number;\n}\n\ninterface Sleeper {\n until: number;\n wake: () => void;\n ring: () => void;\n}\n\nexport function fakeCronClock(start = Date.UTC(2026, 0, 1), utc = true): FakeCronClock {\n let now = start;\n const sleepers = new Set<Sleeper>();\n let idle: Array<() => void> = [];\n let awake = 0;\n const settled = () =>\n awake === 0 && sleepers.size > 0 && [...sleepers].every((s) => s.until > now);\n const check = () => {\n if (!settled()) return;\n const waiting = idle;\n idle = [];\n for (const resolve of waiting) resolve();\n };\n const wakeDue = () => {\n for (const sleeper of [...sleepers]) if (sleeper.until <= now) sleeper.ring();\n };\n return {\n utc,\n now: () => now,\n get sleepers() {\n return sleepers.size;\n },\n sleep(until, signal) {\n if (signal.aborted) return Promise.resolve();\n return new Promise((resolve) => {\n const sleeper: Sleeper = {\n until,\n wake: () => {\n sleepers.delete(sleeper);\n signal.removeEventListener('abort', sleeper.wake);\n resolve();\n },\n ring: () => {\n if (!sleepers.has(sleeper)) return;\n awake++;\n sleeper.wake();\n },\n };\n awake = Math.max(0, awake - 1);\n signal.addEventListener('abort', sleeper.wake, { once: true });\n sleepers.add(sleeper);\n if (until <= now) queueMicrotask(sleeper.ring);\n else check();\n });\n },\n set(ms) {\n now = ms;\n for (const sleeper of [...sleepers]) sleeper.ring();\n },\n advance(ms) {\n now += ms;\n wakeDue();\n },\n flush() {\n return new Promise((resolve) => {\n idle.push(resolve);\n queueMicrotask(check);\n });\n },\n };\n}\n", "import type { RouteTable } from './kernel/net/routes.ts';\nimport {\n answerOf,\n type NetworkUplink,\n type ResolveAnswer,\n type ResolveFamily,\n type UplinkConn,\n type UplinkDatagram,\n type UplinkDial,\n type UplinkMessage,\n type UplinkUdp,\n type UplinkUdpOpen,\n type UplinkUnreachable,\n} from './kernel/net/uplink.ts';\n\nexport { type CronClock, type FakeCronClock, fakeCronClock } from './cron/clock.ts';\nexport type {\n NetworkUplink,\n ResolveAnswer,\n ResolveFamily,\n RouteTable,\n UplinkConn,\n UplinkDatagram,\n UplinkDial,\n UplinkMessage,\n UplinkUdp,\n UplinkUdpOpen,\n UplinkUnreachable,\n};\n\nexport type FakeDatagramPeer = (\n bytes: Uint8Array\n) => Uint8Array | readonly Uint8Array[] | { error: string } | undefined;\n\nexport interface FakePeerSocket {\n read(): Promise<Uint8Array | null>;\n write(bytes: Uint8Array): void;\n end(): void;\n reset(code?: string): void;\n readonly buffered: number;\n drained(): Promise<void>;\n readonly closed: Promise<void>;\n}\n\nexport type FakePeer = ((socket: FakePeerSocket) => void) | { error: string } | { hang: true };\n\nexport interface FakeUplinkOptions {\n names?: Readonly<Record<string, string[] | ResolveAnswer>>;\n routes?: RouteTable;\n ipv6?: boolean;\n exit?: string;\n peers?: Readonly<Record<string, FakePeer>>;\n datagrams?: Readonly<Record<string, FakeDatagramPeer>>;\n address?: string;\n}\n\nexport interface FakeUplink extends NetworkUplink {\n readonly asked: Array<{ name: string; family: ResolveFamily }>;\n readonly dialled: Array<{ host: string; port: number }>;\n readonly sent: Array<{ host: string; port: number; bytes: Uint8Array }>;\n udp(req: UplinkUdpOpen): Promise<UplinkUdp>;\n}\n\nconst failure = (code: string, message: string) => Object.assign(new Error(message), { code });\n\nclass Stream {\n private readonly queue: Array<{\n bytes: Uint8Array;\n taken: () => void;\n lost: (error: Error) => void;\n }> = [];\n private ended = false;\n private broken: Error | undefined;\n private waiters: Array<() => void> = [];\n size = 0;\n\n private wake(): void {\n const waiters = this.waiters;\n this.waiters = [];\n for (const wake of waiters) wake();\n }\n\n push(bytes: Uint8Array): Promise<void> {\n if (this.broken) return Promise.reject(this.broken);\n if (this.ended) return Promise.reject(failure('EPIPE', 'the stream is closed'));\n return new Promise((taken, lost) => {\n this.queue.push({ bytes: bytes.slice(), taken, lost });\n this.size += bytes.length;\n this.wake();\n });\n }\n\n end(): void {\n this.ended = true;\n this.wake();\n }\n\n fail(error: Error): void {\n this.broken = error;\n for (const pending of this.queue.splice(0)) pending.lost(error);\n this.size = 0;\n this.wake();\n }\n\n async read(): Promise<Uint8Array | null> {\n while (this.queue.length === 0 && !this.ended && !this.broken) {\n await new Promise<void>((resolve) => this.waiters.push(resolve));\n }\n if (this.broken) throw this.broken;\n const next = this.queue.shift();\n if (!next) return null;\n this.size -= next.bytes.length;\n next.taken();\n this.wake();\n return next.bytes;\n }\n\n async drained(): Promise<void> {\n while (this.size > 0) await new Promise<void>((resolve) => this.waiters.push(resolve));\n }\n}\n\nfunction named(host: string, port: number): string {\n return `${host.includes(':') ? `[${host}]` : host}:${port}`;\n}\n\nclass Inbox {\n private readonly queue: UplinkMessage[] = [];\n private ended = false;\n private wake: (() => void) | undefined;\n\n push(message: UplinkMessage): void {\n if (this.ended) return;\n this.queue.push(message);\n this.wake?.();\n }\n\n end(): void {\n this.ended = true;\n this.wake?.();\n }\n\n async *[Symbol.asyncIterator](): AsyncGenerator<UplinkMessage> {\n for (;;) {\n const next = this.queue.shift();\n if (next) yield next;\n else if (this.ended) return;\n else await new Promise<void>((resolve) => (this.wake = resolve));\n }\n }\n}\n\nfunction fakeUdp(\n options: FakeUplinkOptions,\n sent: FakeUplink['sent'],\n local: string\n): Promise<UplinkUdp> {\n const inbox = new Inbox();\n return Promise.resolve({\n localAddr: local,\n messages: inbox,\n send: async (bytes, host, port) => {\n sent.push({ host, port, bytes: bytes.slice() });\n const answer = options.datagrams?.[named(host, port)]?.(bytes.slice());\n if (!answer) return;\n if ('error' in answer) {\n inbox.push({ error: answer.error, host, port });\n return;\n }\n const replies = answer instanceof Uint8Array ? [answer] : answer;\n for (const reply of replies) inbox.push({ bytes: reply.slice(), host, port });\n },\n close: () => inbox.end(),\n });\n}\n\nfunction hang(signal: AbortSignal): Promise<never> {\n return new Promise((_, reject) => {\n signal.addEventListener('abort', () => reject(failure('ECONNABORTED', 'dial aborted')), {\n once: true,\n });\n });\n}\n\nexport function fakeUplink(options: FakeUplinkOptions = {}): FakeUplink {\n const asked: FakeUplink['asked'] = [];\n const dialled: FakeUplink['dialled'] = [];\n const sent: FakeUplink['sent'] = [];\n let nextPort = 40000;\n return {\n traits: { tcp: true, udp: options.datagrams !== undefined, ipv6: options.ipv6 === true },\n ...(options.routes ? { routes: options.routes } : {}),\n asked,\n dialled,\n sent,\n udp: () => fakeUdp(options, sent, named(options.address ?? '100.100.100.100', nextPort++)),\n resolve: async (name, family) => {\n asked.push({ name, family });\n return answerOf(options.names?.[name] ?? []);\n },\n dial: async ({ host, port, signal }) => {\n dialled.push({ host, port });\n const peer = options.peers?.[named(host, port)];\n if (!peer) throw failure('ECONNREFUSED', `nothing listens on ${named(host, port)}`);\n if ('error' in peer) throw failure(peer.error, `${named(host, port)}: ${peer.error}`);\n if ('hang' in peer) return hang(signal);\n const up = new Stream();\n const down = new Stream();\n const closed = Promise.withResolvers<void>();\n peer({\n read: () => up.read(),\n write: (bytes) => void down.push(bytes).catch(() => undefined),\n end: () => down.end(),\n reset: (code = 'ECONNRESET') => {\n const error = failure(code, `the peer reset the connection (${code})`);\n up.fail(error);\n down.fail(error);\n },\n get buffered() {\n return down.size;\n },\n drained: () => down.drained(),\n closed: closed.promise,\n });\n return {\n localAddr: named(options.address ?? '100.100.100.100', nextPort++),\n remoteAddr: named(host, port),\n ...(options.exit === undefined ? {} : { exit: options.exit }),\n read: () => down.read(),\n write: async (bytes) => {\n await up.push(bytes);\n return bytes.length;\n },\n closeWrite: () => up.end(),\n close: () => {\n up.end();\n down.end();\n closed.resolve();\n },\n };\n },\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -18,6 +18,12 @@ export interface MetaEntry {
18
18
  writes?: number;
19
19
  epoch?: number;
20
20
  vacated?: true;
21
+ rename?: {
22
+ from: string;
23
+ to: string;
24
+ at: number;
25
+ replace?: true;
26
+ };
21
27
  }
22
28
  export type MetaChange = (entry: MetaEntry | undefined) => MetaEntry | undefined;
23
29
  export interface MetaUpdate {
@@ -157,11 +157,19 @@ export declare class OpfsFs implements KernelFs {
157
157
  link(from: string, to: string, follow?: boolean): Promise<void>;
158
158
  rename(from: string, to: string): Promise<void>;
159
159
  private moveTo;
160
+ private readonly renamingDirs;
161
+ private intents;
162
+ private intend;
163
+ private rollBack;
164
+ private finishRenames;
165
+ settledRename(path: unknown): Promise<void>;
166
+ private renameAt;
160
167
  private replacingTarget;
161
168
  private modified;
162
169
  private explicitTimes;
163
170
  private restamp;
164
171
  private place;
172
+ private fileMoves;
165
173
  private copy;
166
174
  symlink(target: string, path: string, owner?: NewOwner): Promise<void>;
167
175
  realpath(path: string, follow: boolean): Promise<string>;
@@ -0,0 +1,16 @@
1
+ import type { SealedBroker, SealedCaller } from '../sealed.js';
2
+ import type { UplinkConn } from './uplink.js';
3
+ export declare const HEAD_IDLE_MS = 2000;
4
+ export interface SealedSession {
5
+ broker: SealedBroker;
6
+ stamp: () => Promise<SealedCaller>;
7
+ pid: number;
8
+ host: string;
9
+ port: number;
10
+ exit: string | undefined;
11
+ verified: boolean;
12
+ handshakeAt: number;
13
+ alpn: string | false;
14
+ }
15
+ export declare function substitutes(s: SealedSession): boolean;
16
+ export declare function sealedConn(conn: UplinkConn, s: SealedSession): UplinkConn;
@@ -0,0 +1,10 @@
1
+ import { type SessionHead } from '../sealed-session.js';
2
+ export declare const HTTP1_METHODS: readonly string[];
3
+ export declare const HEAD_MAX: number;
4
+ export type HeadAsk = (position: string, placeholders: string[], head: SessionHead) => Promise<Record<string, string> | undefined>;
5
+ export interface Substituted {
6
+ head: string;
7
+ values: Record<string, string>;
8
+ }
9
+ export declare function sniffHttp1(start: string): 'yes' | 'no' | 'more';
10
+ export declare function substituteHead(head: string, ask: HeadAsk): Promise<Substituted | undefined>;
@@ -0,0 +1,30 @@
1
+ export interface ScrubSecret {
2
+ name: string;
3
+ value: string;
4
+ placeholder: string;
5
+ }
6
+ export interface ScrubStream {
7
+ push(chunk: Uint8Array): Uint8Array;
8
+ idle(): Uint8Array;
9
+ end(): Uint8Array;
10
+ }
11
+ export interface Scrubber {
12
+ stream(): ScrubStream;
13
+ }
14
+ export interface SessionScrub {
15
+ version: number;
16
+ scrubber: Scrubber;
17
+ }
18
+ export interface ScrubScope {
19
+ add(secrets: readonly ScrubSecret[]): void;
20
+ of(): SessionScrub | undefined;
21
+ }
22
+ export declare class SessionScrubs {
23
+ private readonly trees;
24
+ private treeOf;
25
+ inherit(parent: number, child: number): void;
26
+ scope(pid: number, host: string): ScrubScope;
27
+ add(pid: number, host: string, secrets: readonly ScrubSecret[]): void;
28
+ of(pid: number, host: string): SessionScrub | undefined;
29
+ drop(pid: number): void;
30
+ }
@@ -10,6 +10,7 @@ export declare class SwitchConn implements UplinkConn {
10
10
  constructor(raw: UplinkConn);
11
11
  get localAddr(): string;
12
12
  get remoteAddr(): string;
13
+ get exit(): string | undefined;
13
14
  get upgraded(): boolean;
14
15
  private wake;
15
16
  private changed;
@@ -17,6 +17,7 @@ export interface TlsClientInfo {
17
17
  chainError?: string;
18
18
  verified: boolean;
19
19
  handshakeAt: number;
20
+ substitutes?: boolean;
20
21
  }
21
22
  export interface TlsClientOptions {
22
23
  engine: TlsEngine;
@@ -1,3 +1,4 @@
1
+ import type { SealedBroker, SealedCaller } from '../sealed.js';
1
2
  import type { KernelSocket, LoopbackNet } from '../socket.js';
2
3
  import { type TlsClientInfo } from './tls-client.js';
3
4
  import { type TlsEngine } from './tls-engine.js';
@@ -22,6 +23,11 @@ export interface ClientCertRequest {
22
23
  }
23
24
  export interface SecureProcess {
24
25
  net: LoopbackNet;
26
+ sealed?: SealedBroker | undefined;
27
+ stamp?(): Promise<SealedCaller>;
28
+ cred?(): {
29
+ pid: number;
30
+ };
25
31
  blocking(): AbortSignal;
26
32
  resolve(name: string, family: ResolveFamily, blocking: () => AbortSignal): Promise<string[]>;
27
33
  }
@@ -12,6 +12,7 @@ export interface ResolveAnswer {
12
12
  export interface UplinkConn {
13
13
  readonly localAddr: string;
14
14
  readonly remoteAddr: string;
15
+ readonly exit?: string;
15
16
  read(): Promise<Uint8Array | null>;
16
17
  write(bytes: Uint8Array): Promise<number>;
17
18
  closeWrite(): void;
@@ -2,6 +2,7 @@ import type { SealedCaller } from './sealed.js';
2
2
  export declare const SESSION_WINDOW_MS = 60000;
3
3
  export declare const SESSION_PLACEHOLDERS = 16;
4
4
  export declare const SESSION_PLACEHOLDER: RegExp;
5
+ export declare const SESSION_EXIT: RegExp;
5
6
  export type SessionProtocol = 'http1' | 'smtp' | 'pg';
6
7
  export interface SessionHead {
7
8
  method: string;
@@ -2,6 +2,7 @@ import type { UsersMode } from '../fs/types.js';
2
2
  import type { Cred } from './cred.js';
3
3
  import { TicketBook } from './exec-ticket.js';
4
4
  import { type OpenAccess, OpenFile, type PollState } from './fd-table.js';
5
+ import { SessionScrubs } from './net/sealed-scrubs.js';
5
6
  import { type SessionFields } from './sealed-session.js';
6
7
  export declare const SEALED_PATH = "/dev/sealed";
7
8
  export declare const SEALED_MAX_BYTES: number;
@@ -63,6 +64,7 @@ export declare class SealedBroker {
63
64
  readonly timeoutMs: number;
64
65
  readonly tagOf: (pid: number) => string | undefined;
65
66
  readonly tickets: TicketBook;
67
+ readonly sessions: SessionScrubs;
66
68
  userOf: (uid: number) => Promise<string | undefined>;
67
69
  constructor(hook: SealedHook | undefined, users: UsersMode, timeoutMs?: number, tagOf?: (pid: number) => string | undefined);
68
70
  stamp(ids: {
@@ -2,6 +2,7 @@ import type { SyncFsResult } from '../realm/sync-fs-wire.js';
2
2
  import { type FdTable } from './fd-table.js';
3
3
  import { type ClientCertRequest } from './net/tls-uplink.js';
4
4
  import type { ResolveFamily } from './net/uplink.js';
5
+ import type { SealedBroker } from './sealed.js';
5
6
  import { type ConnectorStamp, type LoopbackNet, type PeerCred, type SockAddr, type SocketDomain } from './socket.js';
6
7
  import { type SocketNames } from './socket-nodes.js';
7
8
  export type SocketSyscall = {
@@ -96,5 +97,6 @@ export interface SocketProcess {
96
97
  cred?(): PeerCred;
97
98
  stamp?(): Promise<ConnectorStamp>;
98
99
  names?: SocketNames;
100
+ sealed?: SealedBroker | undefined;
99
101
  }
100
102
  export declare function socketSyscall(req: SocketSyscall, proc: SocketProcess): Promise<SyncFsResult>;
@@ -23,6 +23,7 @@ export interface FakeUplinkOptions {
23
23
  names?: Readonly<Record<string, string[] | ResolveAnswer>>;
24
24
  routes?: RouteTable;
25
25
  ipv6?: boolean;
26
+ exit?: string;
26
27
  peers?: Readonly<Record<string, FakePeer>>;
27
28
  datagrams?: Readonly<Record<string, FakeDatagramPeer>>;
28
29
  address?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/slicc-kernel",
3
- "version": "1.126.0",
3
+ "version": "1.127.0",
4
4
  "description": "SLICC's wasm-realm kernel: run Emscripten programs in the browser against OPFS, with fork, spawn, pipes and waitpid",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -68,7 +68,7 @@
68
68
  "@ai-ecoverse/slicc-scrub": "1.0.0",
69
69
  "@ai-ecoverse/slicc-shared-web": "1.24.2",
70
70
  "@ai-ecoverse/wasi-cargo": "0.99.0-3",
71
- "@ai-ecoverse/wasi-pnpm": "12.9.1-10",
71
+ "@ai-ecoverse/wasi-pnpm": "12.9.1-11",
72
72
  "@ai-ecoverse/wasi-ripgrep": "15.2.0-5",
73
73
  "@ai-ecoverse/wasi-rustc": "1.98.1-5",
74
74
  "@ai-ecoverse/wasi-zig": "0.16.0-16",