@ai-ecoverse/slicc-kernel 1.154.3 → 1.155.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/README.md CHANGED
@@ -453,7 +453,7 @@ The module's default export (or its `main` export) is called with a context and
453
453
  - `wait(pid, { untraced })` resolves with `{ status }` once a child ends, or `{ status: 128 + signal, signal }` when a signal ended it, without blocking the program; with `untraced: true` it also resolves when the child stops, with `{ status: 128 + signal, signal, stopped: true }`, as `waitpid` with `WUNTRACED` (a child's `wait({ untraced })` is the same), and options that aren't an object are `EINVAL`; `kill(pid, signal)` signals any process, by number or as `'SIGTERM'` (the default);
454
454
  - `sudo(args)`, what the [`sudo`](#sudo) command calls: `args` are its arguments after `sudo`, and it resolves with `{ pid }`, the command the kernel started as the program's child, to `wait` for; `{ run }`, an argv to run as the caller (with `users: 'off'`); `{ list }`, the text of `sudo -l`; or `{ error, errno }`, the message to print;
455
455
  - `crontab({ action, user?, text? })`, what the [`crontab`](#cron) command calls: `action` is `'list'`, `'remove'` or `'install'` (with the table as `text`), on the caller's own table or, for an effective uid of 0, on `user`'s. It resolves with `{ text }` (the table for `list`, `''` otherwise) or `{ error }`, the message to print. A malformed call is `EINVAL`;
456
- - `net`: `listen({ port, host, backlog })` and `connect({ host, port, signal })` (`host` defaults to `127.0.0.1`) on the kernel's loopback, the same sockets WASIX programs use, so a JS server is reached by other processes, by the page as `<port>.kernel.localhost` (`loopbackFetch`, `dial`) and by attached clients. A listener has its `port` (as bound, so port `0` picks a free one), `accept({ signal })`, `close()` and `for await` over its connections. A connection has `local` and `remote` addresses, `readable` and `writable` web streams (closing the writable shuts down the sending side, so the peer reads the end), and `close()`. The loopback is IPv4 (`127.0.0.1` by default, or `0.0.0.0`), so an IPv6 host such as `::1` fails with `EADDRNOTAVAIL` for both `listen` and `connect`, and binding and connecting fail as they would for a WASIX program (`EADDRINUSE`, `ECONNREFUSED`, `EADDRNOTAVAIL`). `sync.net.listen` takes the same options and gives the same listener, bound and listening when it returns, so its `port` is known at once, as Node's `listen()` needs ([#1398](https://github.com/ai-ecoverse/slicc-kernel/issues/1398)); it needs the sync lane. A blocked accept, read or write waits without holding anything in the kernel, so aborting it or closing the socket leaves nothing behind. A write to a connection the peer closed fails with `EPIPE` and no `SIGPIPE`;
456
+ - `net`: `listen({ port, host, backlog })` and `connect({ host, port, signal })` (`host` defaults to `127.0.0.1`) on the kernel's loopback, the same sockets WASIX programs use, so a JS server is reached by other processes, by the page as `<port>.kernel.localhost` (`loopbackFetch`, `dial`) and by attached clients. A listener has its `port` (as bound, so port `0` picks a free one), `accept({ signal })`, `close()` and `for await` over its connections. A connection has `local` and `remote` addresses, `readable` and `writable` web streams (closing the writable shuts down the sending side, so the peer reads the end), `close()`, and `reset()`, which closes it with an RST as Node's `resetAndDestroy()` does ([#1530](https://github.com/ai-ecoverse/slicc-kernel/issues/1530)): on the kernel's loopback, the peer's pending and later reads and its writes fail with `ECONNRESET` instead of seeing the end, and bytes still in flight to it are dropped. A TLS connection (`secure()`, a `tls` connect on the loopback) resets the same way. On an uplink connection `reset()` closes at once with a FIN, as the transport can't send an RST: bytes still buffered for the peer are discarded instead of sent. The loopback is IPv4 (`127.0.0.1` by default, or `0.0.0.0`), so an IPv6 host such as `::1` fails with `EADDRNOTAVAIL` for both `listen` and `connect`, and binding and connecting fail as they would for a WASIX program (`EADDRINUSE`, `ECONNREFUSED`, `EADDRNOTAVAIL`). `sync.net.listen` takes the same options and gives the same listener, bound and listening when it returns, so its `port` is known at once, as Node's `listen()` needs ([#1398](https://github.com/ai-ecoverse/slicc-kernel/issues/1398)); it needs the sync lane. A blocked accept, read or write waits without holding anything in the kernel, so aborting it or closing the socket leaves nothing behind. A write to a connection the peer closed fails with `EPIPE` and no `SIGPIPE`;
457
457
  - `net.connect({ host, port, tls: { servername, alpn, rejectUnauthorized, ca, path, cert, key, passphrase, sni, checkHostname, keylog } })` takes one of two paths. By default it's **A1, TLS through the transport** ([#914](https://github.com/ai-ecoverse/slicc-kernel/issues/914)), described first below. For a destination the uplink routes it can also be **A2, a TLS handshake in the kernel** ([#922](https://github.com/ai-ecoverse/slicc-kernel/issues/922)), described after it. `conn.tls.path` (`'transport'` or `'kernel'`) and `conn.tls.substitutes` say which one was taken.
458
458
  - **What it does:** there's no handshake in the kernel. The connection carries plain HTTP/1.1 (and WebSocket upgrades) to the realm proxy's HTTP path for the origin `https://<servername or host>[:port]`, exactly as a `CONNECT` through `127.0.0.1:3128` does after the kernel terminates its TLS. The transport (the page's `fetch` or WebSocket, or the uplink's) makes the real TLS connection and verifies it. So `https.request` over a socket, keep-alive agents, `agent-base` and `wss://` clients work with the transport's semantics. That includes sealed placeholders in headers, which reach the transport exactly as from `ctx.fetch`.
459
459
  - **The `tls` property:** the connection's `tls` is `{ path: 'transport', substitutes: true, protocol: 'transport', alpnProtocol: 'http/1.1', authorized: true, servername }`. `authorized` means the transport verified the server. There's no peer certificate chain and no TLS version of the kernel's own, so don't read it as a real handshake's result.
@@ -6097,8 +6097,8 @@ function link(a, b) {
6097
6097
  pipe.openRead();
6098
6098
  pipe.openWrite();
6099
6099
  }
6100
- a.attach({ rx: ba, tx: ab });
6101
- b.attach({ rx: ab, tx: ba });
6100
+ a.attach({ rx: ba, tx: ab, peer: b });
6101
+ b.attach({ rx: ab, tx: ba, peer: a });
6102
6102
  }
6103
6103
  var SERVE_CHUNK = 64 * 1024;
6104
6104
  var NEVER_ABORTED = new AbortController().signal;
@@ -6110,6 +6110,8 @@ var KernelSocket = class _KernelSocket {
6110
6110
  dialing;
6111
6111
  failed;
6112
6112
  reset;
6113
+ abortive = false;
6114
+ refused;
6113
6115
  local;
6114
6116
  peer;
6115
6117
  peerCred;
@@ -6146,20 +6148,25 @@ var KernelSocket = class _KernelSocket {
6146
6148
  }
6147
6149
  async read(max, signal) {
6148
6150
  const { rx } = this.connection();
6151
+ if (this.refused) throw this.refused;
6149
6152
  if (this.readShut || max <= 0) return new Uint8Array(0);
6150
6153
  const bytes = await pipeCall(() => rx.read(max, signal));
6154
+ if (this.refused) throw this.refused;
6151
6155
  if (bytes.length === 0 && this.reset) throw this.reset;
6152
6156
  return bytes;
6153
6157
  }
6154
6158
  async peek(max, signal) {
6155
6159
  const { rx } = this.connection();
6160
+ if (this.refused) throw this.refused;
6156
6161
  if (this.readShut || max <= 0) return new Uint8Array(0);
6157
6162
  const bytes = await pipeCall(() => rx.peek(max, signal));
6163
+ if (this.refused) throw this.refused;
6158
6164
  if (bytes.length === 0 && this.reset) throw this.reset;
6159
6165
  return bytes;
6160
6166
  }
6161
6167
  async write(bytes, signal) {
6162
6168
  const { tx } = this.connection();
6169
+ if (this.refused) throw this.refused;
6163
6170
  if (this.writeShut) throw new KernelError("EPIPE");
6164
6171
  if (this.reset?.code === "EPROTO") throw this.reset;
6165
6172
  return pipeCall(() => tx.write(bytes, signal));
@@ -6346,10 +6353,11 @@ var KernelSocket = class _KernelSocket {
6346
6353
  handshaking = false;
6347
6354
  signal.removeEventListener("abort", abandon);
6348
6355
  }
6349
- this.link = uplinkLink(made2.conn, (err) => {
6356
+ const plain3 = uplinkLink(made2.conn, (err) => {
6350
6357
  this.reset = err instanceof KernelError && err.code === "EPROTO" ? err : new KernelError("ECONNRESET");
6351
6358
  this.changes.wake();
6352
6359
  });
6360
+ this.link = { ...plain3, ...raw2.peer ? { peer: raw2.peer } : {} };
6353
6361
  this.changes.wake();
6354
6362
  return made2.info;
6355
6363
  }
@@ -6467,8 +6475,22 @@ var KernelSocket = class _KernelSocket {
6467
6475
  setOption(level, name2, value2) {
6468
6476
  this.options.set(`${level}:${name2}`, value2);
6469
6477
  }
6478
+ resetOnClose() {
6479
+ this.connection();
6480
+ this.abortive = true;
6481
+ }
6482
+ abort() {
6483
+ const link2 = this.link;
6484
+ if (link2.peer) link2.peer.resetBy();
6485
+ else link2.tx.closeRead();
6486
+ }
6487
+ resetBy() {
6488
+ this.refused ??= new KernelError("ECONNRESET");
6489
+ this.changes.wake();
6490
+ }
6470
6491
  close() {
6471
6492
  const was = this.state;
6493
+ if (this.abortive && was === "connected") this.abort();
6472
6494
  this.state = "closed";
6473
6495
  this.dialing?.abort(new KernelError("ECONNABORTED"));
6474
6496
  this.dialing = void 0;
@@ -8090,7 +8112,7 @@ function satisfies(version, range2) {
8090
8112
  }
8091
8113
 
8092
8114
  // src/version.ts
8093
- var KERNEL_VERSION = true ? "1.154.3" : "0.0.0-dev";
8115
+ var KERNEL_VERSION = true ? "1.155.0" : "0.0.0-dev";
8094
8116
  var OS_RELEASE = "6.0.0-slicc";
8095
8117
  function procVersion() {
8096
8118
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -23272,6 +23294,7 @@ var SOCKET_OPS = [
23272
23294
  "sock-accept",
23273
23295
  "sock-connect",
23274
23296
  "sock-shutdown",
23297
+ "sock-reset",
23275
23298
  "sock-name",
23276
23299
  "sock-getopt",
23277
23300
  "sock-setopt",
@@ -23339,6 +23362,9 @@ async function socketSyscall(req, proc) {
23339
23362
  case "sock-shutdown":
23340
23363
  anySocketAt(fds, req.fd).shutdown(req.how);
23341
23364
  return ok3();
23365
+ case "sock-reset":
23366
+ socketAt(fds, req.fd).resetOnClose();
23367
+ return ok3();
23342
23368
  case "sock-name": {
23343
23369
  const socket = anySocketAt(fds, req.fd);
23344
23370
  const addr = req.peer ? socket.peer : socket.local;