@amritk/nish-aarch64-darwin 0.26.0 → 0.27.2

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/bin/nish CHANGED
Binary file
package/docs/AI.md CHANGED
@@ -2181,8 +2181,9 @@ also watches the signal fd. None of `spawn`, `kill`, `waitPid` may be called in
2181
2181
  a task or a parallel body; the two host facts may. `availableParallelism()` (`i32`, at least 1, no `--threads` needed) and
2182
2182
  `totalMemory()` (`i64` bytes, or `-1`) size a program to the machine. None of
2183
2183
  the five exists on a wasm target, and `spawn` and `waitPid` throw under Node.
2184
- `setNice(n)` sets the nice value (clamped to -20..19; lowering it unprivileged
2185
- is `-13`) and `setIdlePriority()` moves the program to Linux's `SCHED_IDLE`
2184
+ `setNice(n)` sets the nice value, and it is lower-only even as root: `n` takes
2185
+ 0..19 (`-22` outside it) and a value below the current one is `-13`, with no
2186
+ system call either way. `setIdlePriority()` moves the program to Linux's `SCHED_IDLE`
2186
2187
  (`-95` on Darwin); each answers 0 or a negative errno. Call them at the top of
2187
2188
  `main`, never in a task. An unprivileged process can never leave `SCHED_IDLE`
2188
2189
  and an idle one can starve on a saturated machine, so keep a normal-priority
@@ -2201,6 +2202,10 @@ wait for it to be writable, then `connectResult(fd)` is 0 or the failure. Buffer
2201
2202
  are `u8[]` only; an address is 18 bytes of one (IPv4 as `::ffff:a.b.c.d`, then
2202
2203
  the port big-endian); hosts are numeric literals; to reach a name, resolve it first with
2203
2204
  `nish/net/dns` (`DnsClient` on your loop, or the blocking `resolveHost`).
2205
+ To fetch a URL from a CLI, skip all of this: `nish/net/http-client`'s
2206
+ `httpFetch(new HttpRequest("GET", url), new HttpOptions())` blocks until the
2207
+ response's head is in and answers it with `body.readAll(limit)`, or null on a
2208
+ failure (an `HttpClient` keeps the connection and says why in `error`).
2204
2209
  `netRead` / `netWrite` panic on a range outside the buffer.
2205
2210
  `tcpListenFlags(host, port, backlog, flags)` is `tcpListen` with flag 1
2206
2211
  `SO_REUSEPORT`, so several listeners, in one process or many, share a port (Linux
@@ -2654,7 +2659,7 @@ a `PgBatcher` built from a `PgBatchConfig` (`name`, `select` reading the key as
2654
2659
  queue is full, or for a text key too long for `maxBytes`, with no room left in
2655
2660
  `queueBytes`, with a NUL or not UTF-8 — and `take(conn)`
2656
2661
  binds the waiting ones, each key once, as one execution of its `sql` over
2657
- `unnest($1) with ordinality`, answering a run handle or -1. Hand that
2662
+ `unnest($1) with ordinality`, answering a run handle or -1. Set `binary` in the `PgBatchConfig` and each batch comes in binary: `take` opens its bind with `bindStartBinary`, and `row` reads the routing ordinal with `binaryInt` (`binary.failed` is saved and restored, so the caller's own decoding is untouched). Hand that
2658
2663
  execution's `PG_ROW` to `row(run, conn)`, `PG_COMPLETE` to `end(run)`, an error
2659
2664
  or `PG_LOST` to `fail(run)` (on a spent handle these do nothing and count in
2660
2665
  `stray`), and after each call `next(run)` until
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/nish-aarch64-darwin",
3
- "version": "0.26.0",
3
+ "version": "0.27.2",
4
4
  "description": "The nish native compiler for macOS on ARM64",
5
5
  "license": "MIT",
6
6
  "os": [
package/runtime/nish.d.ts CHANGED
@@ -356,10 +356,10 @@ declare function availableParallelism(): i32;
356
356
  /** Bytes of physical memory, or -1 when the host does not say. */
357
357
  declare function totalMemory(): i64;
358
358
  /**
359
- * Set this process's nice value to `n`: 0, or a negative errno. The kernel
360
- * clamps `n` to -20..19, and lowering it below the current value needs the
361
- * privilege to (-13 without it on Linux). Children started afterwards inherit
362
- * it. Throws under Node.
359
+ * Set this process's nice value to `n`, which must be 0..19: 0, or a negative
360
+ * errno. Lower-only, even as root: `n` outside 0..19 is -22 (EINVAL), and `n`
361
+ * below the current value is -13 (EACCES), both with no system call. Children
362
+ * started afterwards inherit it. Throws under Node.
363
363
  */
364
364
  declare function setNice(n: i32): i32;
365
365
  /**
@@ -588,7 +588,7 @@ declare module "nish:process" {
588
588
  export function availableParallelism(): i32;
589
589
  /** Bytes of physical memory, or -1. */
590
590
  export function totalMemory(): i64;
591
- /** Set this process's nice value (clamped to -20..19): 0, or a negative errno. */
591
+ /** Set this process's nice value, 0..19 only: 0, -22 outside it, or -13 below the current value (lower-only, even as root). */
592
592
  export function setNice(n: i32): i32;
593
593
  /**
594
594
  * Move this process to `SCHED_IDLE`: 0, a negative errno, or -95 off Linux.
package/runtime/nish.h CHANGED
@@ -282,6 +282,20 @@ nish_array *nish_alloc_array(uint64_t elem_size, uint64_t len);
282
282
  Variable-time: never call it on a secret. */
283
283
  int64_t nish_regex_scan(const nish_str *s, const nish_array *masks, int64_t depth, int64_t off, int64_t width,
284
284
  int64_t from);
285
+ /* `Math.floor`, `Math.ceil` and `Math.trunc`, and the floor `Math.round`
286
+ takes (#943): each answers as C's `floor`, `ceil` and `trunc` do, -0, the
287
+ infinities and NaN included. A module for x86-64 calls them and registers a
288
+ constructor in `@llvm.global_ctors` that passes `nish_round_init` libm's
289
+ `floor`, `ceil` and `trunc`; it asks the CPU for SSE4.1 once before `main`
290
+ and keeps them on a machine without it, or under `NISH_SIMD=scalar`. Each
291
+ is otherwise SSE4.1's `roundsd`, so the constructor must run first. The
292
+ runtime references no libm symbol: the module that rounds does. They live
293
+ in runtime/runtime-simd.c; runtime-wasm.c has the wasm32 ones for host IR
294
+ that `scripts/build.sh --profile wasm` links. */
295
+ void nish_round_init(double (*floor_fn)(double), double (*ceil_fn)(double), double (*trunc_fn)(double));
296
+ double nish_floor(double x);
297
+ double nish_ceil(double x);
298
+ double nish_trunc(double x);
285
299
  /* No array grows past 2^31 - 1 elements and no string past 2^31 - 1 bytes:
286
300
  * `nish_array_grow`, `nish_alloc_array` and `nish_str_concat` fail as an
287
301
  * allocation does (`nish: out of memory`, exit 1; a trap on wasm) rather than
@@ -480,9 +494,9 @@ nish_array *nish_wait_pid(int32_t pid, int32_t flags);
480
494
  * page size; Darwin: `hw.memsize`), or -1 where the host does not say. */
481
495
  int64_t nish_total_memory(void);
482
496
  /* `setNice(n)`: this process's nice value set to `n` (`setpriority`), 0 or a
483
- * negative errno. The kernel clamps `n` to -20..19; lowering it below the
484
- * current value without the privilege is -EACCES (or -EPERM). -ENOSYS under
485
- * WASI. */
497
+ * negative errno. Lower-only, as root too: `n` takes 0..19, and anything else
498
+ * is -22 (-EINVAL) with no system call; `n` below the current value is -13
499
+ * (-EACCES) with none either. -ENOSYS under WASI. */
486
500
  int32_t nish_set_nice(int32_t n);
487
501
  /* `setIdlePriority()`: this process moved to `SCHED_IDLE` (Linux), 0 or a
488
502
  * negative errno; -95 (Linux's EOPNOTSUPP) elsewhere, -ENOSYS under WASI. An
@@ -775,15 +775,20 @@ int64_t nish_total_memory(void) {
775
775
  }
776
776
 
777
777
  /* `setNice(n)`: this process's nice value set to `n`, 0 or a negative errno.
778
- The kernel clamps `n` to its range (-20 to 19), so only lowering the value
779
- below what the process has can fail: -EACCES (Linux, Darwin) or -EPERM
780
- without the privilege to raise a priority. Linux keeps the value per thread,
781
- so it is the calling thread's and every thread started after it. */
778
+ Lower-only, even as root: `n` takes 0..19 (-EINVAL outside it, with no system
779
+ call), and a value below the current one is -EACCES (Linux, Darwin) without
780
+ a `setpriority` call, so the priority never rises. Linux keeps the value per
781
+ thread, so it is the calling thread's and every thread started after it. */
782
782
  int32_t nish_set_nice(int32_t n) {
783
783
  #ifdef __wasi__
784
784
  (void)n;
785
785
  return -ENOSYS;
786
786
  #else
787
+ if (n < 0 || n > 19) return -EINVAL;
788
+ errno = 0;
789
+ int current = getpriority(PRIO_PROCESS, 0);
790
+ if (current == -1 && errno != 0) return -errno;
791
+ if (n < current) return -EACCES;
787
792
  return setpriority(PRIO_PROCESS, 0, n) == 0 ? 0 : -errno;
788
793
  #endif
789
794
  }
@@ -1,5 +1,6 @@
1
1
  /* Nish runtime, the vector half: byte searches that read sixteen or thirty-two
2
- * bytes at a time (WP38 S1, docs/wp38-simd.md §3.1).
2
+ * bytes at a time (WP38 S1, docs/wp38-simd.md §3.1), and the SSE4.1 rounding
3
+ * behind `Math.floor`, `Math.ceil` and `Math.trunc` (#943, at the end).
3
4
  *
4
5
  * A translation unit of its own for the reason runtime-host.c is one: each file
5
6
  * carries its own measured `.text*` ceiling in tests/runtime-budgets.js, and
@@ -176,10 +177,17 @@ static int nish_cpu_has_avx2(void) {
176
177
  `base` never reads the CPU's feature bits at all. */
177
178
  enum { NISH_PATH_SCALAR, NISH_PATH_BASE, NISH_PATH_AVX2 };
178
179
 
179
- static int nish_simd_choose(void) {
180
+ /* The path `NISH_SIMD` pins, or -1 when it pins none. */
181
+ static int nish_simd_pin(void) {
180
182
  const char *pin = getenv("NISH_SIMD");
181
183
  if (pin != NULL && strcmp(pin, "scalar") == 0) return NISH_PATH_SCALAR;
182
184
  if (pin != NULL && strcmp(pin, "base") == 0) return NISH_PATH_BASE;
185
+ return -1;
186
+ }
187
+
188
+ static int nish_simd_choose(void) {
189
+ int pin = nish_simd_pin();
190
+ if (pin >= 0) return pin;
183
191
  #if defined(NISH_SIMD_SSE2)
184
192
  if (nish_cpu_has_avx2()) return NISH_PATH_AVX2;
185
193
  #endif
@@ -425,3 +433,56 @@ int64_t nish_regex_scan(const nish_str *s, const nish_array *masks, int64_t dept
425
433
  int64_t from) {
426
434
  return __atomic_load_n(&nish_scan, __ATOMIC_RELAXED)(s, masks, depth, off, width, from);
427
435
  }
436
+
437
+ /* `Math.floor`, `Math.ceil` and `Math.trunc`, and the floor `Math.round` takes
438
+ (#943), the contract in nish.h. SSE4.1's `roundsd` answers each in one
439
+ instruction, but the x86-64 baseline is SSE2, where LLVM lowers
440
+ `llvm.floor.f64` to a call of libm's `floor` through the GOT. So a module
441
+ for x86-64 calls these instead (`roundingCallee`, src/emit-builtins.ts) and
442
+ registers a constructor that hands `nish_round_init` libm's three routines;
443
+ it asks the CPU once, before `main`, and keeps them only when the answer is
444
+ no. The runtime names no libm symbol itself, so a link without `-lm` (a C
445
+ host, a program that never rounds) still resolves: the module that rounds
446
+ brings the reference, as it did before. A null slot is the instruction and
447
+ is never written after the constructor, so on a CPU without SSE4.1 each
448
+ call is an indirect jump to libm, the path a module took before.
449
+ `NISH_SIMD=scalar` keeps libm, the forced fallback a test runs. `base` does
450
+ not: the instruction gate counts under it, and glibc's own `floor` takes
451
+ `roundsd` there as well, so the count is of the instruction both call.
452
+ Built for a machine that has SSE4.1 already (`--cpu x86-64-v3`), or for any
453
+ other, each is the builtin, which is the one instruction there, and the
454
+ constructor keeps nothing. The immediate is the rounding mode, 1 down, 2 up,
455
+ 3 toward zero, plus 8, which keeps the precision exception quiet as libm's
456
+ `floor` does. */
457
+ #if defined(NISH_SIMD_SSE2) && !defined(__SSE4_1__)
458
+ static double (*nish_round_libm[3])(double);
459
+
460
+ void nish_round_init(double (*floor_fn)(double), double (*ceil_fn)(double), double (*trunc_fn)(double)) {
461
+ unsigned a, b, c, d;
462
+ if (nish_simd_pin() != NISH_PATH_SCALAR && __get_cpuid(1, &a, &b, &c, &d) && (c & bit_SSE4_1)) return;
463
+ nish_round_libm[0] = floor_fn;
464
+ nish_round_libm[1] = ceil_fn;
465
+ nish_round_libm[2] = trunc_fn;
466
+ }
467
+
468
+ #define NISH_ROUNDING(name, base, slot, mode) \
469
+ double name(double x) { \
470
+ double (*libm)(double) = nish_round_libm[slot]; \
471
+ if (libm) return libm(x); \
472
+ __asm__("roundsd $" #mode ", %0, %0" : "+x"(x)); \
473
+ return x; \
474
+ }
475
+ #else
476
+ void nish_round_init(double (*floor_fn)(double), double (*ceil_fn)(double), double (*trunc_fn)(double)) {
477
+ (void)floor_fn;
478
+ (void)ceil_fn;
479
+ (void)trunc_fn;
480
+ }
481
+
482
+ #define NISH_ROUNDING(name, base, slot, mode) \
483
+ double name(double x) { return __builtin_##base(x); }
484
+ #endif
485
+
486
+ NISH_ROUNDING(nish_floor, floor, 0, 9)
487
+ NISH_ROUNDING(nish_ceil, ceil, 1, 10)
488
+ NISH_ROUNDING(nish_trunc, trunc, 2, 11)
@@ -128,3 +128,21 @@ void nish_exit(int32_t code) { (void)code; __builtin_trap(); }
128
128
  void nish_panic(const void *s) { (void)s; __builtin_trap(); }
129
129
  /* Output has nowhere to go either. */
130
130
  void nish_write(const void *s, int32_t fd, _Bool newline) { (void)s; (void)fd; (void)newline; }
131
+
132
+ /* ---- Rounding (#943): IR compiled for the host calls these and registers a
133
+ * constructor that passes `nish_round_init` libm's three routines
134
+ * (runtime-simd.c has the native ones). On wasm32 each is the one
135
+ * instruction, f64.floor, f64.ceil or f64.trunc, and there is no CPU to ask.
136
+ * The link is freestanding, so the libm names the constructor takes are
137
+ * defined here too, as the same instructions. */
138
+ void nish_round_init(double (*floor_fn)(double), double (*ceil_fn)(double), double (*trunc_fn)(double)) {
139
+ (void)floor_fn;
140
+ (void)ceil_fn;
141
+ (void)trunc_fn;
142
+ }
143
+ double nish_floor(double x) { return __builtin_floor(x); }
144
+ double nish_ceil(double x) { return __builtin_ceil(x); }
145
+ double nish_trunc(double x) { return __builtin_trunc(x); }
146
+ double floor(double x) { return __builtin_floor(x); }
147
+ double ceil(double x) { return __builtin_ceil(x); }
148
+ double trunc(double x) { return __builtin_trunc(x); }
package/scripts/build.sh CHANGED
@@ -10,7 +10,8 @@
10
10
  # threads, <dir>/runtime-cross.c, the copies that carry a string or an array
11
11
  # from one thread's arena to another's, <dir>/runtime-host.c, the wall clock, entropy, file times and
12
12
  # signals, <dir>/runtime-net.c, the sockets of `nish:net`,
13
- # <dir>/runtime-simd.c, the byte searches that read a vector at a time,
13
+ # <dir>/runtime-simd.c, the byte searches that read a vector at a time and
14
+ # the SSE4.1 rounding of `Math.floor` and its kin,
14
15
  # <dir>/runtime-aes.c, AES-GCM on the CPU's AES instructions,
15
16
  # <dir>/runtime-term.c, stdin and the terminal of `nish:term`, and
16
17
  # <dir>/runtime-bytes.c, the byte-class runs of `nish/bytes`. Each of those
@@ -665,6 +666,20 @@ case "$profile" in
665
666
  if [ "$bytes_wanted" = 1 ] && [ "$bytes_named" = 0 ]; then
666
667
  inputs+=("$(cd "$(dirname "$0")/../runtime" && pwd)/runtime-bytes.c")
667
668
  fi
669
+ # IR compiled for the host rounds through runtime-simd.c's `nish_floor` and its
670
+ # kin behind the constructor `nish_round_init` (#943). runtime-wasm.c has their
671
+ # wasm32 bodies, and it is added the same way when such IR does not name it.
672
+ wasm_named=0
673
+ round_wanted=0
674
+ for i in ${inputs[@]+"${inputs[@]}"}; do
675
+ case "$i" in
676
+ */runtime-wasm.c|runtime-wasm.c) wasm_named=1 ;;
677
+ *.ll) if grep -q '@nish_round_init' "$i"; then round_wanted=1; fi ;;
678
+ esac
679
+ done
680
+ if [ "$round_wanted" = 1 ] && [ "$wasm_named" = 0 ]; then
681
+ inputs+=("$(cd "$(dirname "$0")/../runtime" && pwd)/runtime-wasm.c")
682
+ fi
668
683
  # -mbulk-memory lowers llvm.memset/memcpy (`new Array<T>(n)`, `push` growth) to the
669
684
  # memory.fill/memory.copy instructions instead of libc calls the freestanding link lacks.
670
685
  "$CC" -Wno-override-module --target=wasm32-unknown-unknown -Oz -nostdlib -mbulk-memory \
package/std/README.md CHANGED
@@ -225,6 +225,7 @@ reads one, and it still takes its randomness as an argument.
225
225
  | [`net/qpack.ts`](./net/qpack.ts) | QPACK, HTTP/3's field compression, with a dynamic table of capacity 0: `QpackEncoder` and `QpackDecoder`, one per connection. The encoder writes a field section that starts `00 00` (Required Insert Count 0, Base 0) with `beginSection`, then one field line per `encodeField(out, name, off, len, value, off, len, neverIndexed, huffman)`: an Indexed Field Line when the 99-entry static table holds the field, else a literal with the name by static index when it can, each string Huffman-coded when that is shorter and `huffman` is set, and a never-indexed field always a literal with the N bit. The decoder answers a section's fields as windows into its own `bytes` (`nameStart`, `nameLength`, `valueStart`, `valueLength`, `neverIndexed`, `count`), its arrays reused from section to section. It answers QPACK_DECOMPRESSION_FAILED, and stays spent, for a truncated section, an integer past 2^62 − 1, a non-zero Required Insert Count, a negative Base, any dynamic-table reference, a static index past 98 and bad Huffman; a section past `maxFieldSectionSize` answers `QPACK_SECTION_TOO_LARGE`, which is not fatal. `receiveEncoderStream` accepts Set Dynamic Table Capacity 0 and answers QPACK_ENCODER_STREAM_ERROR for any other capacity, Insert or Duplicate; the encoder's `receiveDecoderStream` accepts Stream Cancellations, split anywhere, and answers QPACK_DECODER_STREAM_ERROR for a Section Acknowledgment or Insert Count Increment; `reason` names the rule. `qpackPushStreamCancellation` is the one instruction it may send. The Huffman code is `nish/net/hpack`'s. A window outside its buffer, a negative limit and a stream ID outside 0 to 2^62 − 1 panic | RFC 9204 Appendix B.1, every Appendix A entry by its index, B.2 to B.5's instructions refused, and a request matched byte for byte with pylsqpack's encoding |
226
226
  | [`net/http1.ts`](./net/http1.ts) | `Http1Parser`, an incremental HTTP/1.1 request parser: `feed(buf, off, len)` any slice, then `next()` answers `HTTP1_NEED_MORE`, `HTTP1_HEAD` (`method`, `target`, `minor`, lowercased `names` beside `values`, `header(name)`, `contentLength` or `chunked`, `keepAlive`, `upgrade`), `HTTP1_BODY` (a window onto its buffer, de-chunked), `HTTP1_END`, or one of three final events: `HTTP1_UPGRADE` (`upgradeBytes()` and `declineUpgrade()`), `HTTP1_CLOSED` and `HTTP1_ERROR` with the `status` to answer. It refuses with 400 every request-smuggling shape — `Content-Length` with `Transfer-Encoding`, a repeated or non-digit `Content-Length`, an obs-fold, a bare LF, whitespace before a colon, a malformed chunk size or terminator — and with 413, 414, 431, 501 and 505 what those mean. The head is also kept as spans in a buffer reused from request to request (`head`, `spans`, `methodIs`, `targetIs`, `headerIndex`, `headerIs`, `upgradeIs`), so a parser with `keepText` off allocates nothing per request; `maxChunk` caps each `HTTP1_BODY`, and `restart()` readies it for the next connection. The writer is `http1ResponseHead(status, reason, names, values, bodyLength)`, which adds the framing field itself and refuses (`null`) a CR, LF or other control character anywhere a caller's string lands, and `http1Chunk` and `http1LastChunk` for a chunked body; `http1WriteResponseHead` and `http1WriteChunk` write the same bytes into a caller's buffer at an offset, answering `HTTP1_NO_ROOM` or `HTTP1_REFUSED`, and the first takes a date's bytes to send as a `Date` field on a final response, unless the caller's fields hold one | RFC 9112 §2–§7 and §9.3, RFC 9110 §5, §6.2, §7.2 and §7.8; a corpus fed at every split point (`tests/link/net_http1`) |
227
227
  | [`net/http1-client.ts`](./net/http1-client.ts) | The client side of HTTP/1.1, sans-IO. The writer is `http1RequestHead(method, target, names, values, bodyLength)`, `Host` first and the framing field added for `bodyLength` (`Content-Length`, `HTTP1_CHUNKED` or `HTTP1_NO_BODY`), and `http1WriteRequestHead` into a caller's buffer, answering `HTTP1_NO_ROOM` or `HTTP1_REFUSED` with nothing written; it refuses a method that is not a token, a target that is not a form the method may send, a CR, LF or control byte in a value, not exactly one `Host`, the caller's own framing fields and a `Connection: upgrade`. `Http1ResponseParser(maxStatusLine, maxHeaderBytes, maxHeaders, maxBody, maxTrailerBytes)` is told what was asked (`expect(head, connect)`), fed any slice, and `next()` answers `HTTP1_INFORMATIONAL`, `HTTP1_HEAD` (`status`, `reason`, `minor`, lowercased `names` beside `values`, `header(name)`, `contentLength`), `HTTP1_BODY` (a window, de-chunked), `HTTP1_END` with `keepAlive()`, or the final `HTTP1_UPGRADE` (a 2xx to CONNECT, `tunnelBytes()`), `HTTP1_CLOSED` and `HTTP1_ERROR`; `finish()` reports the close that ends a body read until close, and anywhere else is `HTTP1_E_TRUNCATED`. Every smuggling shape is refused with its own `HTTP1_E_*` code, as is a 101, more than `HTTP1_MAX_INFORMATIONAL` interim responses and a chunk-size line over `HTTP1_MAX_CHUNK_LINE`. Record: [`docs/security/http1-client.md`](../docs/security/http1-client.md) | RFC 9112 §4–§7 and §9.3, RFC 9110 §9.3.6 and §15.2; a corpus fed whole, cut at every byte and a byte at a time, every refusal and limit at its edge, and the server parser reading what the writer writes (`tests/link/net_http1_client`, `_f64`, `_feed_window`) |
228
+ | [`net/http-client.ts`](./net/http-client.ts) | A blocking HTTP/1.1 client for command-line programs: `httpFetch(request, options)` looks the host up, connects, sends an `HttpRequest` (`method`, `url`, `header(name, value)`, `body`) and answers an `HttpResponse` (`status`, `reason`, `url`, `header(name)`) once its head is in, its body read through `body.read` or `body.readAll(limit)`; null on a failure. `HttpClient(options)` does the same and keeps one connection alive between fetches, sending an idempotent request once more when a kept connection turns out to be gone, and says why a fetch failed in `error` (an `HTTP_E_*` code, `httpErrorName`) and `errorText`. `HttpOptions` bounds every wait (`connectTimeoutMs`, `readTimeoutMs`, `totalTimeoutMs`) and every body (`maxBodyBytes`), follows redirects only when asked (`followRedirects`, `maxRedirects`, `crossOriginRedirects`, never https to http, credentials dropped across origins), and reads `http_proxy`, `https_proxy` and `no_proxy` when `useEnvProxy` is set (`HTTP_PROXY` ignored under CGI; https through `CONNECT`). Every byte goes through `nish/net/http1-client`. Native only. Record: [`docs/security/http-client.md`](../docs/security/http-client.md) | RFC 9110 §9.2.2 and §15.4, RFC 9112; a loopback server and proxy in the test itself: keep-alive, retries, redirects, proxies, timeouts and body caps (`tests/link/net_http_fetch`) |
228
229
  | [`net/tls/ticket.ts`](./net/tls/ticket.ts) | Session tickets a server seals for itself (RFC 8446 §4.6.1): `tlsTicketSeal` and `tlsTicketOpen` seal a `TlsTicketState` (suite, issue time, lifetime, chain start, server name, resumption PSK) under ChaCha20-Poly1305 with a borrowed `Secret<u8[]>` key, as key id ‖ nonce ‖ ciphertext ‖ tag, and wipe the plaintext on every path. `TlsTicketKeyInfo`, `tlsTicketKeyDraw` and `tlsTicketKeyDue` are a key's public record, its draw and when to replace it: a key seals for 12 h of awake time and 2^30 tickets, and opens for 14 h. `TlsTicketStrikes` is the bounded strike register (§8.1) that makes a ticket single-use, failing closed when full | `tests/link/net_tls_ticket`: every bound at its instant and 1 ns past it, a flipped bit in each region, three key rotations, replay and a full register |
229
230
  | [`net/websocket.ts`](./net/websocket.ts) | `WsDecoder(expectMasked, maxMessage)`, the same `feed` and `next()` over WebSocket frames: `WS_MESSAGE` (fragments joined, text checked as UTF-8 fragment by fragment), `WS_PING`, `WS_PONG`, and the final `WS_CLOSE` and `WS_ERROR`, whose `closeCode` is the 1002, 1007 or 1009 to close with. `websocketFrame` encodes a frame with the mask key the caller supplies (`websocketWriteFrame` into a caller's buffer, `websocketFrameSize` its length), `reset()` readies a decoder for the next connection, `websocketClosePayload` a close frame's code and reason, and `websocketIsUtf8` checks a window. The handshake is `websocketRequestKey(parser)` over an `Http1Parser`'s head, `websocketAcceptKey`, `websocketKeyIsValid` and `websocketUpgradeResponse`, the 101 | RFC 6455 §1.3's accept key, the §5.7 example frames byte for byte, §5.5's control-frame rules, §7.4's close codes |
230
231
  | [`net/http1-server.ts`](./net/http1-server.ts) | The server side of HTTP/1.1 (WP34 H1): `Http1Connection`, sans-IO, drives an `Http1Parser` — `feed`, then `next()` answers `H1_REQUEST` (the parser's head), `H1_BODY` (at most `chunkSize` bytes, either framing), `H1_END`, `H1_WRITE` (a held-back write may go again), `H1_WS_MESSAGE`, `H1_WS_CLOSE` or the final `H1_ERROR`; the program answers with `respond(status, names, values, bodyLength)`, `write` and `end`, or `acceptWebSocket(protocol)` and then `sendFrame` and `closeWebSocket`. Keep-alive and pipelining one exchange at a time, a response with a length or chunked (unframed to an HTTP/1.0 client), `Connection` and the framing decided by the server, every parser refusal answered with its status and a close, pings and closes answered on a WebSocket. Two slot pools for a program that owns its loop: `Http1Server` on plain TCP and `Http1TlsServer` on `nish/net/tls-tcp` with ALPN `http/1.1` or none (`accept`, `readable` / `writable`, `next(slot)`, `flush(slot)`, `close`, `expire(now)` for the idle timeout and the deadline for a whole head, `headTimeout`, against a client that trickles its head). Both build a slot's connection on the first accept into it rather than at start-up. `Http1Server` reads a socket until a short read rather than until it would block, takes a free slot off a stack of released slots or the next never-used one rather than scanning for one, and answers `rearm(slot, wants)`, whether the interest changed and needs a `pollModify` at all: a keep-alive request is one `recv` and one `send`. The slots borrow their output from one `Http1OutputPool` while they write and give it back once sent, so a pool's memory follows the connections the socket is holding back, not its size: an idle slot holds no output buffer. Every final response carries a `Date` from `HttpDate`, which reads `Date.now()` and formats at most once a second, shared by a pool's slots; `pin(unixMs)` fixes it for a test, and a program's own `date` field replaces it. Every cap is an `Http1Config` number fixed at start-up, and a warmed slot allocates nothing per request (`docs/security/http1.md`). Native only, as `nish:net` is | RFC 9112 §6, §7, §9; RFC 9110 §9.3.2, §15; RFC 6455 §4.2, §5.5. Nish clients over loopback against both carriers, requests cut at every byte, 2 MiB bodies each way, pipelining, a WebSocket echo, the arena flat over two hundred requests, the date formatted once a second, and every refusal (`tests/link/net_http1_server`); `curl` against its `serve` mode |
@@ -239,7 +240,7 @@ reads one, and it still takes its randomness as an argument.
239
240
  | [`net/webtransport.ts`](./net/webtransport.ts) | `WebTransport`, the server side of WebTransport over HTTP/3 as draft-ietf-webtrans-http3-02 has it and Chrome and `wtransport` 0.7 negotiate it, a layer over one `Http3Connection` whose `Http3Config.webtransportSessions` turns it on (extended CONNECT, SETTINGS_H3_DATAGRAM and the draft's two WebTransport settings): `next()` answers `WT_SESSION` (the program `accept`s or `refuse`s it), `WT_DATAGRAM`, `WT_STREAM`, `WT_STREAM_DATA`, `WT_STREAM_END`, `WT_STREAM_RESET`, `WT_STREAM_STOPPED`, `WT_WRITABLE`, `WT_DRAIN` and `WT_CLOSED`, and passes a plain request's `H3_*` events through; `sendDatagram` (the quarter stream ID, then the payload), `openStream`, `write`, `resetStream`, `stopSending`, `close` (CLOSE_WEBTRANSPORT_SESSION) and `drain`. Streams that arrive before their session wait, bounded; every cap is fixed at start-up, and a warm session allocates nothing per datagram or stream (`docs/security/webtransport.md`) | RFC 9220, RFC 9297 and draft-02: a Nish client over QUIC — sessions, datagrams at the size limits, streams both ways, close and drain from either side, every refusal — and one exchange recorded from a `wtransport` 0.7 client replayed byte for byte (`tests/link/net_webtransport`) |
240
241
  | [`net/http-compress.ts`](./net/http-compress.ts) | Content coding for HTTP responses: `httpAcceptEncoding(buf, start, end, offered)` reads an `Accept-Encoding` value (q-values, `*`, `identity`, `x-gzip`) into `HTTP_CODING_GZIP`, `HTTP_CODING_DEFLATE`, `HTTP_CODING_IDENTITY` or `HTTP_CODING_NONE`, allocating nothing; `HttpCompressor(level, minLength, maxLength)` gzips a body into its own buffer when it is worth it (-1 to send it as it is) and builds the response fields (`addFields`: `Content-Encoding: gzip` and, while `vary`, `Vary: Accept-Encoding`). On HTTP/1.1, `Http1Connection.acceptEncoding()` and `respondEncoded(status, names, values, body, off, len, enc, coding)` answer a request in one call with the compressed `Content-Length`, or nothing at all (`H1_AGAIN`, `H1_INVALID`). Opt-in; BREACH is the program's to avoid (`docs/security/compress.md`) | RFC 9110 §8.4 and §12.5.3: the negotiation table, and sans-IO responses gunzipped back (`tests/link/std_http_compress`) |
241
242
  | [`net/router.ts`](./net/router.ts) | `Router`, a method and path router that knows no carrier and allocates nothing per match: `add(method, pattern)` answers the route's index (0, 1, 2 in the order added) or a refusal — `ROUTER_BAD_PATTERN`, `ROUTER_BAD_METHOD`, `ROUTER_TOO_LONG`, `ROUTER_AMBIGUOUS` (`*` not last), `ROUTER_DUPLICATE` (the same shape and method, whatever the parameter names) or `ROUTER_FULL`, against `RouterLimits`; `match(method, mStart, mEnd, target, tStart, tEnd, params)` answers the index, `ROUTER_NOT_FOUND` or `ROUTER_METHOD_NOT_ALLOWED`, writing each `:name` and a final `*` as a `[start, end)` pair into the caller's `params` (`ROUTER_TOO_MANY_PARAMS` when it is short). Literal before parameter before `*`, segment by segment, backtracking; a lookup enters each node at most once. The query is left unparsed and nothing is decoded: `routerDecode` decodes one parameter (`ROUTER_BAD_ESCAPE`, `ROUTER_NO_ROOM`) (`docs/security/router.md`) | Every refusal code, precedence and backtracking, 405 against 404, `%2F` kept in one segment, the query, `*` and authority-form targets, a target deeper than any route, the lookup bound, params past storage, and the arena flat over matches (`tests/link/net_router`, `net_router_f64`, five `net_router_negative_*`) |
242
- | [`net/postgres.ts`](./net/postgres.ts) | `PgConnection`, a PostgreSQL client that reads no socket: the program fills `inputBuffer()` and sends `outputBuffer()`, and `next()` answers `PG_NEED_MORE`, `PG_READY`, `PG_DESCRIBED`, `PG_ROW`, `PG_COMPLETE`, `PG_ERROR`, `PG_NOTICE`, `PG_GROUP_DONE` or the final `PG_FAILED` (`failure`, one of the `PG_ERR_*` codes). Protocol 3.0: startup, `trust` and SCRAM-SHA-256 with the server's signature checked (cleartext and MD5 refused), the extended query protocol — `prepare`, `bindStart`, `paramInt` / `paramText` / `paramBytes` / `paramTextArray` / `paramIntArray` / `paramNull`, an array built element by element into the Bind (`arrayStart`, `arrayElement`, `arrayInt`, `arrayNull`, `arrayEnd`), `bindEnd`, `bindStartBinary` for a Bind whose result columns come in binary (read in place by `binaryInt`, `binaryBool`, `binaryFloat` and `PgBinary`'s `timestamp` and `iso`), `bindCancel` to drop a Bind not yet ended, `closeStatement`, `sync` — with up to `maxInFlight` groups pipelined. `PgBatcher` queues keyed reads of one statement shape (`addInt`, `addText`) and `take(conn)` binds up to `maxRequests` of them, each key once, as one execution over `unnest($1) with ordinality`; `row`, `end` and `fail` route its rows by their server-supplied ordinal, checked first, to `next(run)`'s per-request `PG_BATCH_ROW` and `PG_BATCH_DONE` / `PG_BATCH_MISSING` / `PG_BATCH_FAILED`; `take` answers a fresh handle each time, so a server's late or repeated event on a spent one is counted in `stray`, never routed or panicked on. It reads no clock and allocates nothing per request or row. A row's columns are windows onto the receive buffer (`colStart`, `colLength`), read in place by `int`, `bool`, `isNull` and `PgText`'s decoders (`timestamptz`, `PgArrayWalker` for `text[]`); no per-row or per-column allocation. The password is a `Secret<u8[]>`, wiped once the proof is made. Caps on message size, columns, groups in flight and SCRAM iterations (`docs/security/postgres.md`) | PostgreSQL 18's frontend/backend protocol chapter, RFC 5802 and RFC 7677: every builder, event and refusal (`tests/link/std_postgres`), and exchanges recorded from PostgreSQL 18 by `tests/postgres/record.mjs` replayed byte for byte (`tests/link/std_postgres_replay`); the batcher's queueing, caps and ordinal refusals (`tests/link/std_postgres_batch`) |
243
+ | [`net/postgres.ts`](./net/postgres.ts) | `PgConnection`, a PostgreSQL client that reads no socket: the program fills `inputBuffer()` and sends `outputBuffer()`, and `next()` answers `PG_NEED_MORE`, `PG_READY`, `PG_DESCRIBED`, `PG_ROW`, `PG_COMPLETE`, `PG_ERROR`, `PG_NOTICE`, `PG_GROUP_DONE` or the final `PG_FAILED` (`failure`, one of the `PG_ERR_*` codes). Protocol 3.0: startup, `trust` and SCRAM-SHA-256 with the server's signature checked (cleartext and MD5 refused), the extended query protocol — `prepare`, `bindStart`, `paramInt` / `paramText` / `paramBytes` / `paramTextArray` / `paramIntArray` / `paramNull`, an array built element by element into the Bind (`arrayStart`, `arrayElement`, `arrayInt`, `arrayNull`, `arrayEnd`), `bindEnd`, `bindStartBinary` for a Bind whose result columns come in binary (read in place by `binaryInt`, `binaryBool`, `binaryFloat` and `PgBinary`'s `timestamp` and `iso`, and a one-dimensional `int2[]`, `int4[]` or `int8[]` by `PgBinaryArray`), `bindCancel` to drop a Bind not yet ended, `closeStatement`, `sync` — with up to `maxInFlight` groups pipelined. `PgBatcher` queues keyed reads of one statement shape (`addInt`, `addText`) and `take(conn)` binds up to `maxRequests` of them, each key once, as one execution over `unnest($1) with ordinality`; `row`, `end` and `fail` route its rows by their server-supplied ordinal, checked first (read with `binaryInt` when `PgBatchConfig.binary` is set, so the batch's rows come in binary too), to `next(run)`'s per-request `PG_BATCH_ROW` and `PG_BATCH_DONE` / `PG_BATCH_MISSING` / `PG_BATCH_FAILED`; `take` answers a fresh handle each time, so a server's late or repeated event on a spent one is counted in `stray`, never routed or panicked on. It reads no clock and allocates nothing per request or row. A row's columns are windows onto the receive buffer (`colStart`, `colLength`), read in place by `int`, `bool`, `isNull` and `PgText`'s decoders (`timestamptz`, `PgArrayWalker` for `text[]`); no per-row or per-column allocation. The password is a `Secret<u8[]>`, wiped once the proof is made. Caps on message size, columns, groups in flight and SCRAM iterations (`docs/security/postgres.md`) | PostgreSQL 18's frontend/backend protocol chapter, RFC 5802 and RFC 7677: every builder, event and refusal (`tests/link/std_postgres`), and exchanges recorded from PostgreSQL 18 by `tests/postgres/record.mjs` replayed byte for byte (`tests/link/std_postgres_replay`); the batcher's queueing, caps and ordinal refusals (`tests/link/std_postgres_batch`) |
243
244
 
244
245
  **How a carrier drives `TlsServer`.** The server speaks in handshake
245
246
  *messages*, tagged by level: `TLS_LEVEL_INITIAL` (cleartext — ClientHello,