@amritk/nish-x86_64-linux 0.18.0 → 0.21.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/INSTALL.md +58 -28
- package/bin/nish +0 -0
- package/docs/AI.md +2759 -0
- package/package.json +3 -2
- package/runtime/browser.d.ts +14 -0
- package/runtime/browser.mjs +88 -0
- package/runtime/core/arrays.d.ts +18 -0
- package/runtime/core/arrays.mjs +125 -0
- package/runtime/core/checks.mjs +20 -0
- package/runtime/core/host.d.ts +70 -0
- package/runtime/core/host.mjs +311 -0
- package/runtime/core/numbers.d.ts +53 -0
- package/runtime/core/numbers.mjs +316 -0
- package/runtime/core/result.d.ts +41 -0
- package/runtime/core/result.mjs +127 -0
- package/runtime/core/strings.d.ts +14 -0
- package/runtime/core/strings.mjs +169 -0
- package/runtime/core.d.ts +9 -0
- package/runtime/core.mjs +55 -0
- package/runtime/host.d.ts +6 -0
- package/runtime/nish.d.ts +99 -5
- package/runtime/nish.h +289 -19
- package/runtime/nish.mjs +39 -3
- package/runtime/node.mjs +383 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-bytes.c +401 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +8 -7
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +214 -16
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +235 -11
- package/runtime/runtime-term.c +504 -0
- package/runtime/runtime-wasm.c +5 -14
- package/runtime/runtime.c +257 -17
- package/runtime/shim.mjs +6 -915
- package/runtime/sqlite/LICENSE.md +85 -0
- package/runtime/sqlite/README.md +45 -0
- package/runtime/sqlite/nish-sqlite.c +359 -0
- package/runtime/sqlite/sqlite3.c +271131 -0
- package/runtime/sqlite/sqlite3.h +14447 -0
- package/scripts/build.sh +437 -28
- package/std/LICENSE-fast-float +23 -0
- package/std/LICENSE-fdlibm +8 -0
- package/std/README.md +108 -58
- package/std/base64.ts +116 -0
- package/std/bytes.ts +419 -0
- package/std/cli.ts +328 -0
- package/std/collections.ts +6 -5
- package/std/crypto/LICENSE-crypt_blowfish +26 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/base64url.ts +125 -0
- package/std/crypto/bcrypt.ts +760 -0
- package/std/crypto/chacha20poly1305.ts +43 -9
- package/std/crypto/ed25519.ts +840 -0
- package/std/crypto/hkdf.ts +304 -12
- package/std/crypto/hmac.ts +382 -54
- package/std/crypto/p256.ts +932 -61
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +20 -131
- package/std/json-tokenizer.ts +4237 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1182 -0
- package/std/json.ts +1202 -34
- package/std/math.ts +608 -0
- package/std/net/buffer-pool.ts +151 -0
- package/std/net/hpack.ts +416 -112
- package/std/net/http-fields.ts +93 -0
- package/std/net/http1-server.ts +501 -62
- package/std/net/http1.ts +31 -6
- package/std/net/http2-tls.ts +112 -24
- package/std/net/http2.ts +333 -75
- package/std/net/http3-server.ts +261 -43
- package/std/net/http3.ts +187 -28
- package/std/net/postgres.ts +1875 -0
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-listener.ts +108 -11
- package/std/net/quic-packet.ts +243 -82
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +424 -24
- package/std/net/quic.ts +519 -167
- package/std/net/router.ts +676 -0
- package/std/net/tls/codec.ts +426 -27
- package/std/net/tls/record-server.ts +478 -132
- package/std/net/tls/record.ts +133 -42
- package/std/net/tls/schedule.ts +464 -55
- package/std/net/tls/ticket.ts +610 -0
- package/std/net/tls-tcp.ts +196 -53
- package/std/net/tls.ts +1018 -260
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/regex.ts +4274 -0
- package/std/sqlite.ts +391 -0
- package/std/term-io.ts +978 -0
- package/std/term-pixels.ts +874 -0
- package/std/term-probe.ts +459 -0
- package/std/term-rate.ts +993 -0
- package/std/term.ts +2453 -0
- package/std/text.ts +90 -7
- package/std/threads.ts +438 -14
- package/std/yaml-lines.ts +141 -0
- package/std/yaml.ts +4675 -0
package/std/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# `std/` — the standard library
|
|
2
2
|
|
|
3
3
|
Nish modules written in Nish, for Nish programs to import. There is no magic
|
|
4
|
-
here and — with
|
|
5
|
-
`secret.ts`, and one function, `text.ts`'s `indexOfAny` — nothing the compiler
|
|
4
|
+
here and — with five exceptions, `threads.ts`, `collections.ts`, `map.ts`,
|
|
5
|
+
`secret.ts` and `math.ts`, and one function, `text.ts`'s `indexOfAny` — nothing the compiler
|
|
6
6
|
knows about: a module in this directory is an ordinary Nish source file, compiled as part of
|
|
7
7
|
whatever program imports it, and subject to the same rules as `examples/` or
|
|
8
8
|
`src/` ([`docs/LANGUAGE.md`](../docs/LANGUAGE.md) is the style guide).
|
|
@@ -10,13 +10,31 @@ whatever program imports it, and subject to the same rules as `examples/` or
|
|
|
10
10
|
| Module | What it is |
|
|
11
11
|
| --- | --- |
|
|
12
12
|
| [`testing.ts`](./testing.ts) | a test runner: a `Suite` a program drives with straight-line assertions, printing the `PASS` / `FAIL` / `SKIP` lines the repository's own harness prints, and answering the exit code |
|
|
13
|
-
| [`text.ts`](./text.ts) | the string operations a program would otherwise write inline: `splitLines`, `splitWhitespace`, `trim` and its halves, `contains`, `replaceAll`, `firstDifference` over two arrays of lines, and `indexOfAny(text, bytes, from)`, the first index at or after `from` whose byte is one of 1 to 16 ASCII `bytes`. Its walker is the meaning and what runs under Node; natively the compiler replaces the one call to it with the runtime kernel `nish_str_index_of_any`, which reads 16 or 32 bytes at a time and is variable-time ([`docs/LANGUAGE.md`](../docs/LANGUAGE.md#byte-search-indexofany-in-nishtext)). `tests/link/std_text_index_of_any*` are its programs |
|
|
14
|
-
| [`
|
|
13
|
+
| [`text.ts`](./text.ts) | the string operations a program would otherwise write inline: `splitLines`, `splitWhitespace`, `trim` and its halves, `contains`, `replaceAll`, `firstDifference` over two arrays of lines, `codePointLength`, the number of Unicode code points (what JSON Schema's `minLength` counts, where `.length` counts UTF-8 bytes natively and UTF-16 units under Node; it answers the same on both by asking which it is reading), and `indexOfAny(text, bytes, from)`, the first index at or after `from` whose byte is one of 1 to 16 ASCII `bytes`. Its walker is the meaning and what runs under Node; natively the compiler replaces the one call to it with the runtime kernel `nish_str_index_of_any`, which reads 16 or 32 bytes at a time and is variable-time ([`docs/LANGUAGE.md`](../docs/LANGUAGE.md#byte-search-indexofany-in-nishtext)). `tests/link/std_text_index_of_any*` are its programs |
|
|
14
|
+
| [`math.ts`](./math.ts) | `Math.tan`, `Math.atan2` and `Math.hypot` on `f64`, ported from fdlibm (`LICENSE-fdlibm`) so that every target, wasm included, answers alike without libm. A program never imports it: a call to one of the three loads it, and the compiler copies the functions the call reaches into the calling module as `internal` ones. JavaScript's answer for every NaN, signed zero and infinity, and within one ulp elsewhere (0.83, 0.84 and 0.93 ulp measured against mpmath). `tests/cases/math_std_edges` pins the special values |
|
|
15
|
+
| [`regex.ts`](./regex.ts) | Regular expressions in JavaScript's syntax with the `u` flag's strictness, matched by a Pike VM in time linear in the input whatever the pattern; finding every match is one search per match, so `findAll` and its kin can be quadratic when a long branch is preferred to a short one that wins ([`docs/security/regex.md`](../docs/security/regex.md)). `regexCompile(pattern, flags)` answers a `Regex` or the error text; `test`, `exec` into a reused `RegexMatch`, `find`, `findAll`, `count`, `replace` and `replaceAll` with `$&`, `$1` and `$<name>`, and `split`. Flags `i` (ASCII), `m` and `s`; backreferences and lookaround are refused at compile time. Offsets are UTF-8 bytes. A search allocates nothing beyond its answer: the thread lists and capture tables are the `Regex`'s, so one `Regex` serves one thread at a time. A leading literal is found with `indexOf`, a small leading set with `indexOfAny`. `tests/link/std_regex` checks it against JavaScript's own answers |
|
|
16
|
+
| [`json.ts`](./json.ts) | `jsonField(object, name)`: the value of one field of one flat JSON object, which is the shape the compiler's own `--json` diagnostics have. A reader and not a parser — it answers text, answers `null` for a field that is not there, and does not validate. `JsonWriter` is the other direction: any JSON value written into a caller-owned `u8[]`, commas and colons included, byte for byte what `JSON.stringify` writes, and allocating nothing per call except an `f64`'s digits, a temporary string the call gives back before it returns (`text()` and `jsonStringifyString` build strings by design); misuse and a full buffer set a sticky negative `JSON_WRITE_*` code in `failed` rather than panicking. `setIndent(n)` and `setIndentTab()` write what `JSON.stringify(v, null, n)` or `(v, null, "\t")` writes, with no allocation for the indent, and `setStrict(true)` makes NaN and the infinities the sticky `JSON_WRITE_NON_FINITE` instead of `null`; both modes are off by default and kept by `reset`. `jsonStringifyString(s)` is the string rule answered as a string. `stringBytes(bytes, off, len)` and `keyBytes(bytes, off, len)` write a window of a byte array as `string` and `key` write a string of those bytes, allocating nothing, and refuse a window outside the array with `JSON_WRITE_BAD_WINDOW` |
|
|
17
|
+
| [`json-tokenizer.ts`](./json-tokenizer.ts) | `JsonTokenizer`: a validating, streaming tokenizer for RFC 8259 JSON held in a window of a `u8[]`. `next()` answers one `JSON_TOK_*` token at a time — object and array openers and closers, keys, strings, numbers and the three literals — then `JSON_TOK_END`, or a negative `JSON_TOK_ERR_*` code that sticks in `failed`, with the byte in `errorAt`. Strings are checked as UTF-8 and a lone surrogate escape is refused; `stringInto` decodes the current string into the caller's array and `keyEquals` compares it with a name, neither allocating. `numberF64` is correctly rounded; `isInteger` and `numberI64` read an integer that fits an `i64`, and `isU64` and `numberU64` one that fits a `u64`. `isIntegral` asks whether a number's value is whole however it is written (`1.0`, `1e2`, `1e400`), JSON Schema's "integer", from the text and at any exponent. `skipValue` steps over a value. `JsonLimits` caps nesting depth, string, number and total bytes, each with its own code; `reset` reuses the tokenizer, so a reused body keeps the arena flat. Duplicate keys are reported as tokens. No input panics and no byte outside the window is read; [`docs/security/json.md`](../docs/security/json.md) is its record, and `tests/link/std_json_tokenizer` runs JSONTestSuite and a seeded fuzz |
|
|
18
|
+
| [`json-typed.ts`](./json-typed.ts) | `jsonDecode(d, out)` and `jsonEncode(w, value)`: one JSON object read into a class and written back, with code the compiler writes at each call from the class's fields — scalars (`u64` exact to 2^64 - 1), strings, numeric enums, nested classes, arrays of those, `Map<string, V>` of those and `\| null` — through `JsonDecoder` (a `JsonTokenizer` and a sticky error) and `JsonWriter`. A class's one `static readonly json` literal renames a field's key and makes a key optional, a missing one setting its field to the initializer; otherwise every field is required, each key once, an unknown key refused unless `ignoreUnknown`, and every outcome a sticky `JSON_DECODE_*` or `JSON_TOK_ERR_*` code with its byte in `errorAt`; decoding into a reused object reuses its objects, arrays and equal strings, so the arena stays flat (a map's entries are made anew, at most `maxMapKeys` over all the maps of one decode, 4,096 by default); a key is sent to its field by its length, then one byte. A `JsonNumber` field keeps a number's text byte for byte. Under Node the types are erased and both calls panic ([`docs/LANGUAGE.md`](../docs/LANGUAGE.md#typed-json-nishjson-typed), [`docs/security/json.md`](../docs/security/json.md)) |
|
|
19
|
+
| [`json-value.ts`](./json-value.ts) | `JsonDocument`: any JSON document parsed into a tree of `JsonValue` handles (`i32`s into the document), read with `kind`, `boolean`, `string`, `stringEquals`, `length`, `at`, `keyAt` and `get`, built with the `new*` methods, `push` and `set`, and written back through `JsonWriter` with `write`, or as text with `stringify`. A number keeps its source text and is read on request as an `i64` or `u64` that fits (`isI64`, `isU64`), the nearest `f64`, or the text, and `isIntegral` asks whether its value is whole however it is written (`1.0`, `1e2`), so integers and decimals wider than any type round-trip byte for byte. Objects keep insertion order; a repeated key is `JSON_VALUE_ERR_DUPLICATE` unless `duplicates` is `JSON_VALUE_DUPLICATE_LAST`, `JSON.parse`'s rule. Every parse error and limit is the tokenizer's, with the depth held to the writer's 64 so that whatever parses writes back, nothing panics, and a value is in at most one container, so a tree built through `push` and `set` cannot hold itself. A handle carries the generation of the document it came from, so one kept across a `parse` or `clear` reads as nothing; `maxValues` (262,144 by default, `JSON_VALUE_ERR_VALUES` past it) bounds the memory a document keeps. Each `parse` reuses the document's arrays, so a reused document keeps the arena flat. `tests/link/std_json_value` runs JSONTestSuite through it ([`docs/security/json.md`](../docs/security/json.md)) |
|
|
20
|
+
| [`yaml.ts`](./yaml.ts) | `parseYaml(text)` and `parseYamlWith(text, options)`: a YAML 1.2 stream parsed into a `YamlStream` of documents, nodes and diagnostics, struct of arrays with `i32` handles and `-1` for none. Every node and diagnostic keeps its byte range; a scalar is decoded (`text`) and resolved against the core schema (`type`) only when read. `keyText`, `pathNode`, `pathPair` and `pathMatch` find a key by path through aliases and `<<` merge keys (the map's own keys first, then each merged map in order). A parse never fails or panics on its input: errors and warnings carry the `YAML_ERR_*` codes other YAML tooling reports (`yamlErrorName`), with `maxDepth`, `uniqueKeys`, `merge`, `keepComments` and `firstDocumentOnly` in `YamlOptions`. A merge key's value must name maps (`BAD_MERGE`); tabs are not indentation. |
|
|
21
|
+
| [`yaml-lines.ts`](./yaml-lines.ts) | `YamlLineIndex`: byte offsets turned into 1-based `line` and `column` (characters) or `byteColumn`, by binary search over line starts found once with `indexOf`. LF, CR LF and a lone CR each break a line; an offset is clamped to the text first. |
|
|
15
22
|
| [`pair.ts`](./pair.ts) | `Pair<A, B>`: an interface with `first` and `second`, for a function that answers two values from one call. A type and nothing else — the caller writes an object literal at the return — and for returning two values rather than storing them side by side |
|
|
16
23
|
| [`collections.ts`](./collections.ts) | the global `Map<K, V>` and `Set<T>`: insertion-ordered tables whose buckets carry a hash fingerprint beside the entry index and whose entries keep their full hash, so every `get`, `set`, `add`, `has` and `delete` is one probe. `get` is not a method here: its `V | undefined` never crosses a call, so the compiler lowers it to `probe` and, where the key was found, `valueAt`. Nor are `keys()` and `values()`: an iterator is not a value, so a `for...of` over one is lowered to `walkOpen`, `walkNext`, `keyAt` or `valueAt`, and `walkClose`, and a count of live walks defers compaction until no loop is walking the table. A program never imports it: naming `Map` or `Set` loads it, and the compiler emits what a module uses of it into that module ([`docs/wp32-map.md`](../docs/wp32-map.md), [`docs/LANGUAGE.md`](../docs/LANGUAGE.md#map-and-set)). Its `hashKey`, `sameKey` and `storedKey` are lowered by the compiler per key type |
|
|
17
24
|
| [`map.ts`](./map.ts) | `reserve(m, n)` and `getOrInsert(m, k, v)` for the global `Map`. Their bodies are the meaning, and what runs under Node: `reserve` does nothing, and `getOrInsert` is a `get`, and a `set` of `v` when the key was missing. Natively the compiler lowers every call in place — `reserve` to the table's `reserveSlots`, which grows the buckets once so that `n` entries fit without a rebuild, and `getOrInsert` to one `probe` and a `valueAt` or an `insertAt` through its answer — so, like `collections.ts`, it writes no `.ll` of its own ([`docs/wp32-map.md`](../docs/wp32-map.md) §9.2, [`docs/LANGUAGE.md`](../docs/LANGUAGE.md#map-and-set)) |
|
|
25
|
+
| [`bytes.ts`](./bytes.ts) | `getU16LE` / `getU16BE` to `getF64LE` / `getF64BE` and the `set` twins: `u16`, `u32`, `u64`, `i32`, `i64` and `f64` read from and written into a `u8[]` at an offset, little- or big-endian. Each access is an ordinary index, so out of range is an `index` panic naming the first missing byte. In range the answers are those of the private copies `std/` keeps (`sha256LoadWord`, `chacha20Word`, `poly1305Le64`, sha512's `loadWord` / `storeWord`, `http2FramePut32`, `tlsPutU16`), none of which it replaces. A guard in the caller does not yet remove the checks after inlining. Under Node only the `u16` and `u32` functions answer as natively. Beside them are the runs a parser walks — `skipBlank`, `skipDigits`, `skipJsonText` and `skipJsonPlain`, the first index in a clamped range whose byte is not a JSON blank, a digit, a byte a JSON string holds as itself or a byte a JSON writer copies without an escape — and `copyBytes`, a window copied as one move that panics on a range outside its array. Their bodies call private walkers, which are what runs under Node; natively the compiler replaces each walker's call with a kernel of `runtime/runtime-bytes.c`, or `llvm.memmove` for the copy ([`docs/LANGUAGE.md`](../docs/LANGUAGE.md#byte-runs-skipblank-skipdigits-skipjsontext-skipjsonplain-and-copybytes-in-nishbytes)). The runs are variable-time. `tests/link/std_bytes*` are its programs |
|
|
26
|
+
| [`base64.ts`](./base64.ts) | `nish/base64`: `base64Encode(bytes)`, `base64EncodeInto(src, off, len, dst, at)` and `base64EncodedLength(n)`, padded standard base64 (RFC 4648 §4) for kitty graphics frames. It only encodes, and it indexes its alphabet by the sextet, so it is not constant-time: it is for pixels, not secrets. A strict decoder, and an encoder for key material, are `crypto/base64url.ts`'s `base64StdPadDecode` and `base64StdPadEncode` (`tests/link/std_base64`; the two modules in one program: `tests/link/std_base64_two_modules`) |
|
|
27
|
+
| [`random.ts`](./random.ts) | `Rng`, a seeded generator of pseudo-random numbers: `new Rng(seed)`, `nextI32()` and `nextF64()`. A Weyl sequence through all 2^32 states with MurmurHash3's finaliser on each, written with `nish:unsafe`'s `wrappingAdd` and `wrappingMul` so that every answer is the same bits natively and under Node (`tests/link/std_random`; [`docs/LANGUAGE.md`](../docs/LANGUAGE.md#random-numbers-rng-in-nishrandom)). A parallel body or a task that makes its own steps it without a shared write. Not for cryptography |
|
|
28
|
+
| [`cli.ts`](./cli.ts) | a command-line tool's `--help`. `HelpMenu` builds it a line at a time — `header`, `usage`, `section`, `row(flag, placeholder, description)`, `note`, `value` — with descriptions lined up at the caller's columns, counted on the plain text, and answers it with `text()`. `Style` paints it in the eight basic ANSI colours, bold and faint, which the terminal's theme defines, or in bold and underline alone when `TERM` declares no colours. `styleFor(fd, agent)` is the policy `nish --help` follows: plain unless the descriptor is a terminal, `NO_COLOR`, `FORCE_COLOR`, `CLICOLOR_FORCE`, `CLICOLOR` and `TERM=dumb` honoured, plain for an agent (`agentDriven`), forced colour always the theme's; it reads the environment and, for the terminal check, forks `/bin/sh` once, which runs `tput` by absolute path. `HelpMenu` and `Style` reach no capability. The compiler's own help is built with it through a relative import, so the module may use only what the last release compiles (`tests/link/std_cli`; [guide](../docs/guide/using-nish/standard-library.md#nishcli)) |
|
|
29
|
+
| [`sqlite.ts`](./sqlite.ts) | SQLite, vendored in `runtime/sqlite/` and linked into a program that imports the module, through the C adapter `runtime/sqlite/nish-sqlite.c`. `openDatabase(path, flags)` answers a `Database`: `exec`, `begin` / `commit` / `rollback`, `prepare` into a `Statement`, `lastInsertRowid`, `changes` and `close`, which finalizes the connection's statements. A `Statement` binds (`bindI64`, `bindF64`, `bindNull`, `bindText`, `bindTextBytes`, `bindBlob`), steps, resets and reads columns (`columnType`, `columnI64`, `columnF64`, `columnBytes`, `columnText`, and `columnInto`, a copy into the caller's `u8[]`), so a row allocates nothing. Every failure is a `Result` of SQLite's extended code, and `error(code)` builds the `SqliteError` with its message. A handle is an `i32` into the adapter's thread-local tables, and one used after `close` or `finalize` answers `SQLITE_MISUSE`. Reaches `ffi`; links natively and under `--profile wasi` (`tests/link/std_sqlite*`; [`docs/LANGUAGE.md`](../docs/LANGUAGE.md#sqlite-nishsqlite)) |
|
|
30
|
+
| [`base64.ts`](./base64.ts) | standard base64 (RFC 4648 §4) with `=` padding, encode only: `base64Encode(bytes)` answers a string, `base64EncodeInto(src, off, len, dst, at)` writes into a byte buffer allocating nothing, `base64EncodedLength(n)` sizes it; a range outside its array panics before a byte is written. What kitty graphics frames are sent in; not the constant-time `crypto/base64url` (`tests/link/std_base64`, `std_base64_f64`, `std_base64_negative_*`; [guide](../docs/guide/using-nish/standard-library.md#nishbase64)) |
|
|
31
|
+
| [`term.ts`](./term.ts) | a terminal UI with no I/O of its own. `Screen` holds `width × height` cells (code point, 24-bit foreground and background, bold and inverse) for the frame being drawn and the frame last sent; `flush()` answers the string that turns one into the other — cursor moves and only the cells that changed, a style sent only where it differs — for the caller to `write`. A `Screen` made with `palette256` sends the nearest 256-colour index instead. `Canvas` draws pixels on a screen, two to a cell with the half block "▀" (`setPixel`, `fillRect`, `line`, `text`). The escapes (`enterAltScreen`, `hideCursor`, `clearScreen`, `beginSync`, …) are functions answering strings, and a `Screen` with `sync` set wraps each flush that changes anything in synchronized output (mode 2026). `FixedStep` is a fixed-step clock: `advance(nowMs)` answers the whole steps of `1000 / stepsPerSecond` ms since the last call, at most `maxSteps`, and `alpha()` the remainder, for logic at 240 Hz and drawing at 120. `KeyDecoder` turns raw-mode bytes into `KeyEvent`s (arrows as `ESC [` or `ESC O`, Enter, Space, Escape, Ctrl-C, characters), carries a sequence a read cut short into the next `feed`, and decides a lone ESC on a timeout the caller's clock measures. `feedEvents` and `pollEvents` read the same bytes as `TermEvent`s, whose `type` is an `EVENT_` constant: keys with `MOD_` bits and an `ACTION_` (legacy, modifyOtherKeys and the kitty keyboard protocol), SGR mouse reports in cells or pixels, bracketed paste, focus, and the terminal's replies to queries, which never come out as keys. `KeyState` tracks which keys are held, making up the releases a terminal without the kitty protocol never sends and turning Ctrl-C into `EVENT_QUIT`. The size, raw mode and the clock are the caller's, so the module reaches no capability and prints the same bytes under Node (`tests/link/std_term`, `std_term_f64`; [guide](../docs/guide/using-nish/standard-library.md#nishterm)) |
|
|
32
|
+
| [`term-probe.ts`](./term-probe.ts) | what the terminal can do, asked once at start. `probeQueries(env)` is every query as one string — XTVERSION, DECRQM for synchronized output and pixel mouse, kitty's keyboard flags and a kitty graphics query, XTSMGRAPHICS, the cell and text-area sizes, a true-colour read-back, and DA1 last, wrapped for tmux's or screen's passthrough where only the outer terminal can answer. `TermProbe` takes the replies a read at a time (`feed`), holds back any key typed meanwhile (`heldEvents`), is `done()` once DA1 is answered or `timedOut(now)` after 250 ms, and `result()` reads the replies and a `TermEnv` (the `TERM`, `COLORTERM`, `TMUX`, `SSH_*`, `NO_COLOR` and `NISH_TERM_*` variables) into a `TermCaps`: colours, graphics backend, sync, kitty keyboard, cell size, pixel mouse, multiplexer. Pure: the environment, the bytes and the clock are the caller's (`tests/link/std_term_probe`; [guide](../docs/guide/using-nish/terminal.md#what-it-detects-and-what-it-falls-back-to)) |
|
|
33
|
+
| [`term-rate.ts`](./term-rate.ts) | how fast the link to the terminal is, and the frame rate and detail a program keeps to under it. A `RateController` is told each frame sent (`frameSent(bytes, costNs, nowMs)`) and each acknowledgement — the answer to the DSR 5 query sent after a frame — (`ackReceived(nowMs)`), keeps running averages of the round trip, the bytes a second delivered and the cost of a frame, and answers `mayDraw(nowMs)` and a `RateDecision`: the frame interval, a resolution divisor, a backend cap and a palette, stepped down a tier when frames back up and up again when the link has room; `stats()` is the `TermStats` a program can show. Pure: no capability and no clock (`tests/link/std_term_rate`, `std_term_rate_f64`; [guide](../docs/guide/using-nish/terminal.md#how-the-frame-rate-adapts)) |
|
|
34
|
+
| [`term-pixels.ts`](./term-pixels.ts) | a `Framebuffer` of `0xRRGGBB` pixels and the encoders that draw it with the backend the terminal has: `kittyFrame` (kitty graphics, one image id retransmitted each frame), `sixelFrame` (a fixed palette, optionally dithered), `drawHalfBlocks` onto a `Canvas` and `drawAscii` onto a `Screen`, each into the `PixelRect` `fitFramebuffer` letterboxes in the cells; `scaleFramebuffer` shrinks a frame by the controller's divisor. `PixelBytes` keeps the encoders' output and working arrays, so frame after frame allocates nothing. Pure (`tests/link/std_term_pixels`, `std_term_pixels_f64`; [guide](../docs/guide/using-nish/standard-library.md#nishterm-pixels)) |
|
|
35
|
+
| [`term-io.ts`](./term-io.ts) | the term module that does the I/O. `runTerminal(options, state, update, render)` is a whole program loop: it opens the terminal, steps `update` at a fixed rate, calls `render` with a `Frame` (the `Screen`, the `Framebuffer`, the step `alpha`, `TermCaps` and `TermStats`) whenever the controller lets a frame go, and closes when `update` answers false or the user presses Ctrl-C. `openTerminal(options)` answers a `Terminal` for a program's own loop (`readEvents`, `present`, `stats`, `close`). Opening refuses a pipe, enters raw mode and the alternate screen, probes the terminal and turns on the kitty keyboard, mouse, paste and focus reports the options ask for, with the runtime's restore string set before each so a panic or signal leaves the terminal as it was; `present` sends the frame in the best backend the controller allows as one write and acknowledges it with DSR 5. Reaches `tty` (`tests/term-io/relay.mjs` drives it in a pseudo-terminal from `tests/run.js`'s `term-io` section; `tests/link/std_term_io_deny`; [guide](../docs/guide/using-nish/terminal.md)) |
|
|
18
36
|
| [`secret.ts`](./secret.ts) | The source behind the builtin module `nish:secret`, and imported only by that name: `Secret<T>`, `secret`, `expose`, `exposeWith` and `wipe`. The bodies are the meaning; what makes a `Secret` one is the checker's, keyed on this file ([`docs/LANGUAGE.md`](../docs/LANGUAGE.md#secrets-nishsecret)). `wipe`'s body is written by the emitter, one volatile `llvm.memset`, and like `collections.ts` the module writes no `.ll` of its own |
|
|
19
|
-
| [`threads.ts`](./threads.ts) | `parallelMapInto(src, dst, f)` and `parallelReduce(src, f, identity)`: a function over every element of an array, on as many threads as the length is worth. Its bodies are the sequential meaning, which is what runs under Node; the compiler recognises the two templates by module and name, lowers the one loop in each onto `nish_parallel_range`, holds the function to the rules that make that safe, and compiles an importing program with `--threads` ([`docs/LANGUAGE.md`](../docs/LANGUAGE.md#data-parallelism-nishthreads)). `tests/link/par_*` are its programs |
|
|
37
|
+
| [`threads.ts`](./threads.ts) | `parallelMapInto(src, dst, f)` and `parallelReduce(src, f, identity)`: a function over every element of an array, on as many threads as the length is worth. Its bodies are the sequential meaning, which is what runs under Node; the compiler recognises the two templates by module and name, lowers the one loop in each onto `nish_parallel_range`, holds the function to the rules that make that safe, and compiles an importing program with `--threads` ([`docs/LANGUAGE.md`](../docs/LANGUAGE.md#data-parallelism-nishthreads)). `tests/link/par_*` are its programs. `scope()`, bound by `using s = scope()`, runs unlike tasks, each on a thread of its own, and joins them at every exit of the block; `s.spawn(entry, arg, dst, at)` stores a task's answer, and `s.spawn(entry, arg)` and `s.spawn(() => ...)`, whose arrow reads the function's `const` locals, run one that answers `void` (`spawnWith` and `spawnThunk` are the methods the compiler reads them as, and under Node `spawn` itself tells them apart) ([`docs/LANGUAGE.md`](../docs/LANGUAGE.md#scoped-tasks-using-s--scope)). `Mutex<T>` is a lock that owns its data: the value is reachable only through the guard `using g = m.lock()` binds, which releases it at every exit of the block, so a scope's tasks can share and update one value without a data race or a deadlock ([`docs/LANGUAGE.md`](../docs/LANGUAGE.md#a-lock-that-owns-its-data-mutext); `tests/link/thread_mutex_*`, and `examples/histogram.ts` measured). `Channel<T>` carries scalars to one receiver, a task of the scope or the parent after the join, from the scope's sender tasks and from the parent before the block that spawns them, and closes when every sending task has returned ([`docs/LANGUAGE.md`](../docs/LANGUAGE.md#channels-channelt); [`docs/wp29-thread-surface.md`](../docs/wp29-thread-surface.md#44-stage-p3--channels-of-scalars) §4.4). Every body in the module is its meaning under Node, where a task runs at its `spawn` |
|
|
20
38
|
|
|
21
39
|
## `nish/crypto` — the primitives under TLS 1.3
|
|
22
40
|
|
|
@@ -26,7 +44,8 @@ K6's certificates, in
|
|
|
26
44
|
pure Nish (decision S1), each module imported by its own specifier. Every one
|
|
27
45
|
is written from its specification, with two exceptions that keep their
|
|
28
46
|
upstream notice: `crypto/aes.ts`'s `ghashMul32` is adapted from BearSSL, and
|
|
29
|
-
`crypto/p256.ts`'s field and scalar arithmetic is ported from fiat-crypto
|
|
47
|
+
`crypto/p256.ts`'s field and scalar arithmetic is ported from fiat-crypto and
|
|
48
|
+
its nonce's safegcd inversion from libsecp256k1
|
|
30
49
|
([`THIRD_PARTY_NOTICES.md`](../THIRD_PARTY_NOTICES.md)). Each reproduces its
|
|
31
50
|
specification's published vectors in its `tests/link/crypto_*` programs. The
|
|
32
51
|
performance gate compiles every module with no diagnostics under both
|
|
@@ -43,27 +62,35 @@ no array or string the runtime makes (a file read, a concatenation, a
|
|
|
43
62
|
template) and no string built by `join` can pass 2^31 − 1 elements or bytes
|
|
44
63
|
(K1-6, CG-3, closed), so `length` is right in both number modes, and the
|
|
45
64
|
one-shot functions below refuse a longer input as each row says. **Private
|
|
46
|
-
keys are `Secret`s.** `crypto/p256.ts`, `crypto/x25519.ts`
|
|
47
|
-
`crypto/x509.ts` take and give a private key as a `Secret<u8[]>` from
|
|
65
|
+
keys are `Secret`s.** `crypto/p256.ts`, `crypto/x25519.ts`, `crypto/ed25519.ts`,
|
|
66
|
+
`crypto/x509.ts` and `crypto/x509-random.ts` take and give a private key as a `Secret<u8[]>` from
|
|
48
67
|
`nish:secret`, compute on it only inside `expose`, and wipe every intermediate
|
|
49
68
|
the key reaches before they return, with a store the optimiser may not remove
|
|
50
|
-
(ECC-2, X509-7, closed).
|
|
51
|
-
|
|
69
|
+
(ECC-2, X509-7, closed); `crypto/hmac.ts` (`hmacSha256Secret` and its two
|
|
70
|
+
siblings, `pbkdf2HmacSha256`) and `crypto/hkdf.ts` take a key as a `Secret`
|
|
71
|
+
too. `crypto/aes.ts` and
|
|
72
|
+
`crypto/chacha20poly1305.ts` take their keys as plain arrays, the caller's to
|
|
73
|
+
wipe, and wipe everything they derive from one (`aesKeyWipe` drops an
|
|
74
|
+
`AesKey`). What no store reaches, registers and vector registers among them,
|
|
75
|
+
is listed in [`SECURITY.md`](../SECURITY.md#threat-model).
|
|
52
76
|
|
|
53
77
|
| Module | What it is | Reproduces |
|
|
54
78
|
| --- | --- | --- |
|
|
55
79
|
| [`crypto/sha256.ts`](./crypto/sha256.ts) | `sha256(data)`, and `Sha256`, a streaming hasher: `update(buf, off, len)` over a window of a `u8[]`, `copy()` for the hash of a prefix while the original keeps going, and `digest()`, a fresh 32-byte array. `SHA256_SIZE` and `SHA256_BLOCK`. `sha256` panics on an array longer than 2^31 − 1 bytes | FIPS 180-4 §6.2 |
|
|
56
80
|
| [`crypto/sha512.ts`](./crypto/sha512.ts) | SHA-512 and SHA-384 on one compression function: `sha512` and `sha384`, and the streaming `Sha512` and `Sha384` with `Sha256`'s three methods; digests of 64 and 48 bytes. `SHA512_SIZE`, `SHA384_SIZE` and `SHA512_BLOCK`. The one-shot `sha512` and `sha384` panic on an array longer than 2^31 − 1 bytes | FIPS 180-4 §6.4, §6.5 |
|
|
57
|
-
| [`crypto/hmac.ts`](./crypto/hmac.ts) | `hmacSha256` and `hmacSha384`, the streaming `HmacSha256` and `HmacSha384` (keyed in the constructor, then `update` and `digest`), and `hmacSha256Verify` / `hmacSha384Verify`, which compare a received tag with `timingSafeEqual`. The one-shot functions panic on a message longer than 2^31 − 1 bytes. `HmacSha256Scratch` / `HmacSha384Scratch` are the same tag over hashers the caller allocates once — `begin(key, off, len)`, `update`, `finishInto(out, at)`, `wipe` — so a loop of them inside `using a = arena()` leaves the arena where it was; `hmacCopySha256` / `hmacCopySha384` copy a hasher's state into one the caller holds | RFC 2104, RFC 4231 §4 |
|
|
58
|
-
| [`crypto/hkdf.ts`](./crypto/hkdf.ts) | `hkdfExtractSha256` / `hkdfExtractSha384` (an empty salt is HashLen zeros) and `hkdfExpandSha256` / `hkdfExpandSha384`, which answer `null` for a length below zero or above 255 × HashLen, for an `info` longer than 2^31 − 1 bytes, and for a PRK shorter than HashLen. `hkdfExpandLabelSha256` / `hkdfExpandLabelSha384` are TLS 1.3's HKDF-Expand-Label, which QUIC uses too; the label is given without its `"tls13 "` prefix, and a length outside 0 to 255, a label outside 1 to 249 bytes, a context past 255 bytes or a secret shorter than HashLen panics, since each is the program's choice rather than the peer's. `hkdfExtractInto`, `hkdfExpandInto` (`false` where the others answer `null`) and `hkdfExpandLabelInto` compute the same bytes in an `HkdfScratch` the caller owns, over the hash a length of 32 or 48 names, into the caller's array, and wipe the scratch before they return | RFC 5869 §2, Appendix A; RFC 8448 §3 and RFC 9001 A.1 for the label |
|
|
81
|
+
| [`crypto/hmac.ts`](./crypto/hmac.ts) | `hmacSha256` and `hmacSha384`, the streaming `HmacSha256` and `HmacSha384` (keyed in the constructor, then `update` and `digest`), and `hmacSha256Verify` / `hmacSha384Verify`, which compare a received tag with `timingSafeEqual`. The one-shot functions panic on a message longer than 2^31 − 1 bytes. `HmacSha256Scratch` / `HmacSha384Scratch` are the same tag over hashers the caller allocates once — `begin(key, off, len)`, `update`, `finishInto(out, at)`, `wipe` — so a loop of them inside `using a = arena()` leaves the arena where it was; `hmacCopySha256` / `hmacCopySha384` copy a hasher's state into one the caller holds. `pbkdf2HmacSha256(password, salt, iterations, dkLen)` is PBKDF2 with HMAC-SHA-256, for SCRAM-SHA-256: the password goes in and the key comes out as a `Secret<u8[]>`, every intermediate is wiped, and fewer than one iteration or a `dkLen` below one answers `null` | RFC 2104, RFC 4231 §4, RFC 8018 §5.2 |
|
|
82
|
+
| [`crypto/hkdf.ts`](./crypto/hkdf.ts) | `hkdfExtractSha256` / `hkdfExtractSha384` (an empty salt is HashLen zeros) and `hkdfExpandSha256` / `hkdfExpandSha384`, which answer `null` for a length below zero or above 255 × HashLen, for an `info` longer than 2^31 − 1 bytes, and for a PRK shorter than HashLen. `hkdfExpandLabelSha256` / `hkdfExpandLabelSha384` are TLS 1.3's HKDF-Expand-Label, which QUIC uses too; the label is given without its `"tls13 "` prefix, and a length outside 0 to 255, a label outside 1 to 249 bytes, a context past 255 bytes or a secret shorter than HashLen panics, since each is the program's choice rather than the peer's. `hkdfExtractInto`, `hkdfExpandInto` (`false` where the others answer `null`) and `hkdfExpandLabelInto` compute the same bytes in an `HkdfScratch` the caller owns, over the hash a length of 32 or 48 names, into the caller's array, and wipe the scratch before they return. A key held as a `Secret<u8[]>` (`nish:secret`) goes through the `*Secret` twins, which read it only inside `exposeWith` and answer a `Secret`: `hkdfSha256Secret` / `hkdfSha384Secret`, each step's, and `hkdfExpandLabelSha256Secret(prk, label, context, length)` / `hkdfExpandLabelSha384Secret`, which panic where HKDF-Expand-Label does. A destination already held as a `Secret` is derived into in place by `hkdfExpandLabelSha256Held(dst, prk, label, context)` / `hkdfExpandLabelSha384Held`, as many bytes as `dst` holds, the plain output copied in inside `exposeWith` and wiped; `dst` may be `prk`. `hkdfExpandLabelHeldInto(hashLength, prk, label, context, out, at, length)` writes a plain window from a held PRK, for output that is plain by design, a record key or IV | RFC 5869 §2, Appendix A; RFC 8448 §3 and RFC 9001 A.1 for the label |
|
|
59
83
|
| [`crypto/ct.ts`](./crypto/ct.ts) | `timingSafeEqual(a, b)`, which reads every byte whatever it holds, and `timingSafeEqualAt(a, aOff, b, bOff, len)` over two windows, which answers `false` for a window outside its array. Two lengths that differ answer `false` at once, because a length is public, and so does an array longer than 2^31 − 1 bytes | — |
|
|
60
|
-
| [`crypto/
|
|
84
|
+
| [`crypto/bcrypt.ts`](./crypto/bcrypt.ts) | `bcryptHash(password, salt, cost)`, the 60-character `$2b$` hash of a `Secret<u8[]>` password under a 16-byte salt and a cost of 4 to 31, and `bcryptVerify(password, hash)`, which reads `$2a$`, `$2b$` and `$2y$` hashes, refuses `$2x$`, and compares in constant time. The first 72 bytes of a password count, up to its first NUL, as under libxcrypt's `crypt_r` (where other implementations differ: [the interoperability table](../docs/security/crypto-bcrypt.md#interoperability)); the Blowfish state is wiped before either returns. `BCRYPT_MAX_PASSWORD`, `BCRYPT_HASH_LENGTH`, `BCRYPT_SALT_SIZE`, `BCRYPT_MIN_COST`, `BCRYPT_MAX_COST`. A `std/threads` task cannot call them yet, since a task may not wipe | OpenBSD `bcrypt(3)`; crypt_blowfish 1.3 for `$2a$` and `$2y$` |
|
|
85
|
+
| [`crypto/base64url.ts`](./crypto/base64url.ts) | `base64urlEncode(data)` and `base64urlDecode(text)`, unpadded; `base64StdPadEncode(data)` and `base64StdPadDecode(text)`, the padded standard alphabet that SCRAM and PEM use, constant-time in both directions. These are not `nish/base64`, which encodes only, is not constant-time and is for pixels; the names differ because std modules share the program's one namespace. Decoding is strict, so every byte string has one spelling. `base64urlDecode` answers `null` for any `=`, a character outside the alphabet, a length of 1 mod 4 or nonzero unused low bits; `base64StdPadDecode` requires the padding, answering `null` unless the text is whole four-character groups with `=` only as the last one or two characters, every other character in the alphabet and the unused bits zero. Both refuse a text longer than 2^31 − 1 bytes, and `base64urlEncode` panics on such an array | RFC 4648 §4, §5, §10 (`tests/link/std_base64_padded`) |
|
|
61
86
|
| [`crypto/sha1.ts`](./crypto/sha1.ts) | `sha1(data)`, a fresh 20-byte digest, and `SHA1_SIZE` and `SHA1_BLOCK`. **Not for security**: SHA-1 is broken for collisions, and it is here only because RFC 6455 §4.2.2's `Sec-WebSocket-Accept` is defined on it (`nish/net/websocket` is its importer). One-shot only; it panics on an array longer than 2^31 − 1 bytes. It came after the #363 audit and has no record in `docs/security/` | FIPS 180-4 §6.1 |
|
|
62
87
|
| [`crypto/x25519.ts`](./crypto/x25519.ts) | `x25519(scalar, u)`, the shared secret as a `Secret<u8[]>`, and `x25519Base(scalar)`, the public key, each from a `Secret<u8[]>` scalar, on ten 25.5-bit limbs in `i64`. Either answers `null` unless its arguments are `X25519_SIZE` (32) bytes; the scalar is clamped on a copy, which is wiped | RFC 7748 §5.2, §6.1, Wycheproof `x25519_test` |
|
|
63
|
-
| [`crypto/
|
|
64
|
-
| [`crypto/
|
|
88
|
+
| [`crypto/ed25519.ts`](./crypto/ed25519.ts) | `ed25519PublicKey(seed)`, the 32-byte public key, `ed25519Sign(seed, msg)`, the 64-byte signature `R \|\| S`, each from a `Secret<u8[]>` seed of `ED25519_SEED_SIZE` (32) bytes or `null`, and `ed25519Verify(pub, msg, sig)`. Signing is deterministic and constant time; the seed's hash, the nonce and every intermediate the scalar reaches are wiped. Verify is cofactorless, refuses an S of L or more and a key that does not decode, and is variable time, since its inputs are public. Sign and verify panic on a message longer than 2^31 − 1 bytes. It reuses `crypto/x25519.ts`'s field arithmetic | RFC 8032 §5.1, §7.1 |
|
|
89
|
+
| [`crypto/chacha20poly1305.ts`](./crypto/chacha20poly1305.ts) | `chacha20Poly1305Seal(key, nonce, aad, plaintext)`, which answers the ciphertext followed by the 16-byte tag, and `chacha20Poly1305Open`, which checks the whole tag before it decrypts anything and answers `null` for a message that does not authenticate. Beneath them `chacha20Block`, `chacha20`, `chacha20QuarterRound`, `poly1305` and `poly1305KeyGen`, and `chacha20HeaderMask`, QUIC's 5-byte header-protection mask. Every input of the wrong length answers `null`. What a call derives from the key — the ChaCha20 state, the keystream, the Poly1305 key, `r` and accumulator, and on open the tag it compared — is wiped with `wipe`'s volatile stores before the call returns, the refusal path included | RFC 8439 §2, RFC 9001 §5.4.4, Wycheproof `chacha20_poly1305` (all 325 cases) |
|
|
90
|
+
| [`crypto/aes.ts`](./crypto/aes.ts) | AES-128 and AES-256, bitsliced four blocks at a time with the Boyar–Peralta S-box circuit, so no table is read; natively, on a CPU with AES-NI and PCLMULQDQ or the ARMv8 AES and PMULL instructions, the compiler sends the key schedule, the block, GCTR and GHASH to the runtime's kernels instead (`runtime/runtime-aes.c`, WP38 S5), and `NISH_AES=soft` pins the bitsliced code: `aesKey(key)` expands a 16- or 32-byte key into an `AesKey` (or `aesKeyInto(key, out)` into one the caller holds, allocating nothing that outlives the call), then `aesEncryptBlock`, `aesGcmSeal` / `aesGcmOpen` (tag last, checked in full before anything is decrypted; any non-empty IV) and `aesHeaderMask`. AES-192 is out of scope, and a wrong length answers `null`, as does an `AesKey` not made by `aesKey`; one whose H is zero opens nothing. An `AesKey` holds its round keys in `roundKeys` and H in `h`, two `u64` words (high, low); `aesKeyWipe(key)` zeroes both with `wipe`'s volatile stores, the kernels' powers of H after the schedule included, and is how a holder drops a key. The bitsliced schedule's, block's and GCTR's working copies, the tag's E(K, J0) block, the tag open compared and J0 itself are wiped the same way before they are released | FIPS 197, SP 800-38D, RFC 9001 §5.4.3, Wycheproof `aes_gcm` (all 316 cases; the 103 AES-192 ones check that the key is refused) |
|
|
65
91
|
| [`crypto/p256.ts`](./crypto/p256.ts) | ECDSA over P-256 with RFC 6979's deterministic nonces: `p256PublicKey(priv)` (65-byte uncompressed SEC1) from a `Secret<u8[]>` private key, `p256Sign` / `p256Verify` over a digest and `p256SignSha256` / `p256VerifySha256` over a message, and `p256SignSha256Into(scratch, priv, msg, off, len, out, at)`, the same signature written into the caller's window in a `P256SignScratch` made once, keeping nothing in the arena; a signature is `r` and `s`, 32 big-endian bytes each, not DER (`crypto/x509.ts`'s `x509DerSignatureRS` converts one). A malformed key, a point off the curve or an `r` or `s` out of range answers `null` or `false`, never a panic. ECDSA is malleable, and `p256Verify` accepts a high `s`: when `(r, s)` verifies, so does `(r, n − s)` | RFC 6979 A.2.5, Wycheproof `ecdsa_secp256r1_sha256`, `_sha256_p1363`, `_sha512` and `_sha512_p1363` |
|
|
66
|
-
| [`crypto/x509.ts`](./crypto/x509.ts) | DER, PEM and X.509 over P-256: `pemToDer` / `derToPem` (RFC 7468, padded standard base64, strict labels), `x509ParseP256PrivateKey` (SEC1 or PKCS#8, answering a `Secret<u8[]>`), `x509ParseCertificate` / `x509ParseChain` into an `X509Certificate`, `x509VerifySignature` (ecdsa-with-SHA256 under an issuer's key), `x509DerSignatureRS` (a DER ECDSA-Sig-Value as the `r || s` `crypto/p256.ts` takes), `x509MintSelfSigned`, the 1-to-14-day self-signed P-256 certificate WebTransport's `serverCertificateHashes` accepts, and `x509CertificateHash`, its SHA-256. DER is read strictly — non-minimal lengths and integers, indefinite lengths and trailing bytes answer `null`. `x509ParseCertificate` and `x509ParseChain` parse and do not validate: there is no path validation, no extension is read or exposed, and no clock is consulted. `pemToDer` refuses a file in which any block, of any label, is malformed. `x509MintSelfSigned` refuses a common name that is not UTF-8 or holds a NUL, and its key and serial must come from `crypto.getRandomValues`, which the module cannot call itself without refusing wasm32 (X509-6) | X.690, RFC 5280, RFC 7468, RFC 5915, RFC 5208 / 5958; certificates checked against OpenSSL |
|
|
92
|
+
| [`crypto/x509.ts`](./crypto/x509.ts) | DER, PEM and X.509 over P-256: `pemToDer` / `derToPem` (RFC 7468, padded standard base64, strict labels), `x509ParseP256PrivateKey` (SEC1 or PKCS#8, answering a `Secret<u8[]>`), `x509ParseCertificate` / `x509ParseChain` into an `X509Certificate`, `x509VerifySignature` (ecdsa-with-SHA256 under an issuer's key), `x509DerSignatureRS` (a DER ECDSA-Sig-Value as the `r || s` `crypto/p256.ts` takes), `x509MintSelfSigned`, the 1-to-14-day self-signed P-256 certificate WebTransport's `serverCertificateHashes` accepts, and `x509CertificateHash`, its SHA-256. DER is read strictly — non-minimal lengths and integers, indefinite lengths and trailing bytes answer `null`. `x509ParseCertificate` and `x509ParseChain` parse and do not validate: there is no path validation, no extension is read or exposed, and no clock is consulted. `pemToDer` refuses a file in which any block, of any label, is malformed. `x509MintSelfSigned` refuses a common name that is not UTF-8 or holds a NUL, and its key and serial must come from `crypto.getRandomValues`, which the module cannot call itself without refusing wasm32: `crypto/x509-random.ts` draws them (X509-6) | X.690, RFC 5280, RFC 7468, RFC 5915, RFC 5208 / 5958; certificates checked against OpenSSL |
|
|
93
|
+
| [`crypto/x509-random.ts`](./crypto/x509-random.ts) | **Native only**: the key and serial `crypto/x509.ts`'s mint needs, drawn from `crypto.getRandomValues`, so a program that imports it does not compile for wasm32 and `crypto/x509.ts` imports nothing from it. `x509DrawP256Key()`, a `Secret<u8[]>` P-256 private key drawn again until it is a scalar in [1, n) (each refused draw wiped); `x509DrawSerial()`, twenty octets whose first is 0x40 to 0x7f, so a positive, non-zero, full-length serial; and `x509MintSelfSignedDrawn(priv, commonName, notBeforeMs, days)`, `x509MintSelfSigned` with a drawn serial | — |
|
|
67
94
|
|
|
68
95
|
Three rules hold across the modules:
|
|
69
96
|
|
|
@@ -81,8 +108,8 @@ Three rules hold across the modules:
|
|
|
81
108
|
- **Constant time by construction, and verified where the check reaches.** No
|
|
82
109
|
module branches on, or indexes by, a secret: comparisons OR the differences
|
|
83
110
|
into one word and test it once, the ladder swaps with a mask and always runs
|
|
84
|
-
255 steps, AES reads no table, P-256's window
|
|
85
|
-
|
|
111
|
+
255 steps, AES reads no table, P-256's comb and window read every entry of
|
|
112
|
+
a table and keep one by mask, and base64url maps characters by arithmetic on range masks
|
|
86
113
|
rather than a table. Every branch is on a length, a loop counter or a bit
|
|
87
114
|
position. WP34 N6 checks that the machine code kept that shape:
|
|
88
115
|
`tests/ct-asm.js` disassembles golden fixtures on x86-64 and aarch64 and
|
|
@@ -117,8 +144,13 @@ Three rules hold across the modules:
|
|
|
117
144
|
subtract, its scalar multiply and its conditional move; the sixteen-entry
|
|
118
145
|
table read by a secret digit; the complete doubling and addition; and one
|
|
119
146
|
window step of the scalar multiplication (four doublings, the table read
|
|
120
|
-
and an addition)
|
|
121
|
-
|
|
147
|
+
and an addition); and the fixed-base comb that multiplies G for a public
|
|
148
|
+
key or a signature: its digit, the fifteen-entry read of the precomputed
|
|
149
|
+
table, the mixed addition, the masked keep, and one whole comb step; and
|
|
150
|
+
the nonce's safegcd inversion: thirty divsteps, held to the module's loop
|
|
151
|
+
body, and both matrix updates. The check follows each call into the field
|
|
152
|
+
functions. The 64-window and 4-row loops, the window's table build, the
|
|
153
|
+
field inversion, safegcd's limb conversions, the encodings and
|
|
122
154
|
RFC 6979's nonce derivation remain discipline.
|
|
123
155
|
|
|
124
156
|
The SHA-2 hashes and HKDF are additions, rotations and xors that branch
|
|
@@ -137,35 +169,40 @@ reads one, and it still takes its randomness as an argument.
|
|
|
137
169
|
|
|
138
170
|
| Module | What it is | Reproduces |
|
|
139
171
|
| --- | --- | --- |
|
|
140
|
-
| [`net/tls.ts`](./net/tls.ts) | The server side of a TLS 1.3 handshake, ClientHello through the client's Finished. `new TlsServer(config, serverRandom, x25519Private)`, then `receive(level, buf, off, len)` with what the client sent, `takeOutput(level)` for what to send, and `signatureInput()` / `sign(signature)` for the CertificateVerify hand-off (`tlsSignEcdsaP256` signs for a P-256 key, DER-encoded; a `TlsP256Signer`, made once, gives the same DER from its `sign(priv, content)` and keeps nothing per signature). A `TlsServerConfig` names the certificate chain, the signature scheme, the ALPN protocols, whether the carrier is QUIC, the server's transport parameters, and extensions to pass through in EncryptedExtensions. The suites are `TLS_AES_128_GCM_SHA256`, `TLS_CHACHA20_POLY1305_SHA256` and `TLS_AES_256_GCM_SHA384` (in the client's order), the group x25519 (one HelloRetryRequest when the client offers it without a share), and `server_name`, ALPN and `quic_transport_parameters` are read; PSKs, 0-RTT, NewSessionTicket and client authentication are not. Every refusal is the alert to send — `receive` and `sign` answer 0 or a `TLS_ALERT_*` — never a panic, and a low-order x25519 share is refused. `restart(serverRandom, x25519Private)` hands a server's buffers, made once, to the slot's next connection, so a handshake allocates nothing that outlives it under any suite (TLS-3) | RFC 8446 §4, §7; RFC 8448 §3 byte for byte, and §5's transcript |
|
|
172
|
+
| [`net/tls.ts`](./net/tls.ts) | The server side of a TLS 1.3 handshake, ClientHello through the client's Finished. `new TlsServer(config, serverRandom, x25519Private)`, then `receive(level, buf, off, len)` with what the client sent, `takeOutput(level)` for what to send, and `signatureInput()` / `sign(signature)` for the CertificateVerify hand-off (`tlsSignEcdsaP256` signs for a P-256 key, DER-encoded; a `TlsP256Signer`, made once, gives the same DER from its `sign(priv, content)` and keeps nothing per signature). A `TlsServerConfig` names the certificate chain, the signature scheme, the ALPN protocols, whether the carrier is QUIC, the server's transport parameters, and extensions to pass through in EncryptedExtensions. The suites are `TLS_AES_128_GCM_SHA256`, `TLS_CHACHA20_POLY1305_SHA256` and `TLS_AES_256_GCM_SHA384` (in the client's order), the group x25519 (one HelloRetryRequest when the client offers it without a share), and `server_name`, ALPN and `quic_transport_parameters` are read; PSKs, 0-RTT, NewSessionTicket and client authentication are not. Every refusal is the alert to send — `receive` and `sign` answer 0 or a `TLS_ALERT_*` — never a panic, and a low-order x25519 share is refused. `restart(serverRandom, x25519Private)` hands a server's buffers, made once, to the slot's next connection, so a handshake allocates nothing that outlives it under any suite (TLS-3); it copies the key into the server's own `Secret` and wipes the caller's array, and `wipe()` zeroes every secret the server holds | RFC 8446 §4, §7; RFC 8448 §3 byte for byte, and §5's transcript |
|
|
141
173
|
| [`net/tls/codec.ts`](./net/tls/codec.ts) | The handshake messages as bytes: `tlsReadClientHello` into a reusable `TlsClientHelloView` of windows into the message, and `tlsParseClientHello` into a `TlsClientHello` of copies (structure checked, an alert on a malformed or duplicated field); each message's `tlsWrite*` writer into the caller's array at an offset, and `tlsEncodeServerHello`, `tlsEncodeHelloRetryRequest`, `tlsEncodeEncryptedExtensions`, `tlsEncodeCertificate`, `tlsEncodeCertificateVerify`, `tlsEncodeFinished` and `tlsCertificateVerifyContent`. The wire's constants: handshake and extension types, versions, the x25519 group, the two signature schemes, and the `TLS_ALERT_*` descriptions | RFC 8446 §4, RFC 6066 §3, RFC 7301 §3.1, RFC 9001 §8.2 |
|
|
142
|
-
| [`net/tls/schedule.ts`](./net/tls/schedule.ts) | The key schedule over SHA-256 or SHA-384: `tlsEarlySecret`, `tlsHandshakeSecret`, `tlsMasterSecret`, `tlsDeriveSecret`, `tlsExpandLabel`, `tlsFinishedVerifyData`, and `tlsTrafficKey` / `tlsTrafficIv` for a record layer, with in-place forms over an `HkdfScratch` (`tlsDeriveSecretInto`, `tlsMasterSecretInto`, `tlsFinishedVerifyDataInto`, `tlsTrafficKeysInto`). `TlsTranscript` is the running transcript hash, with HelloRetryRequest's `message_hash` rule, `reset` for the next connection and `hashInto` the caller's array. The suite constants and `tlsSuiteHashLength` / `tlsSuiteKeyLength` | RFC 8446 §4.4.1, §7.1, §7.3 |
|
|
143
|
-
| [`net/tls/record.ts`](./net/tls/record.ts) | The record layer (RFC 8446 §5): `TlsRecordReader` cuts a stream into records in a buffer allocated once, refusing a header of unknown type or a body past 2^14 + 256 bytes before the body is waited for; `TlsRecordProtection` is one direction, cleartext until `install(suite, secret)`, then `seal` and `open` a record into the caller's buffer — the nonce from the sequence number, the header as additional data, the inner content type and zero padding found without branching on the padding. `tlsNextTrafficSecret` is KeyUpdate's next secret
|
|
174
|
+
| [`net/tls/schedule.ts`](./net/tls/schedule.ts) | The key schedule over SHA-256 or SHA-384: `tlsEarlySecret`, `tlsHandshakeSecret`, `tlsMasterSecret`, `tlsDeriveSecret`, `tlsExpandLabel`, `tlsFinishedVerifyData`, and `tlsTrafficKey` / `tlsTrafficIv` for a record layer — these one-shots take every secret as a `Secret<u8[]>` and answer one (`tlsFinishedVerifyData` answers the public verify data as a plain array; transcript hashes, labels and contexts stay plain), and each wipes its derived salts and `finished_key` before it returns — with in-place forms over an `HkdfScratch` (`tlsDeriveSecretInto`, `tlsMasterSecretInto`, `tlsFinishedVerifyDataInto`, `tlsTrafficKeysInto`), and forms into a `Secret<u8[]>` the caller already holds (`tlsDeriveSecretHeld`, `tlsHandshakeSecretHeld`, `tlsMasterSecretHeld`; `dst` may be the source), `tlsFinishedVerifyDataHeld`, the plain verify data from a held base key, and `tlsTrafficKeysFromHeld`, a held secret's key and IV into plain arrays, which answers `false` for a suite or length it does not know. `TlsTranscript` is the running transcript hash, with HelloRetryRequest's `message_hash` rule, `reset` for the next connection and `hashInto` the caller's array. The suite constants and `tlsSuiteHashLength` / `tlsSuiteKeyLength` | RFC 8446 §4.4.1, §7.1, §7.3 |
|
|
175
|
+
| [`net/tls/record.ts`](./net/tls/record.ts) | The record layer (RFC 8446 §5): `TlsRecordReader` cuts a stream into records in a buffer allocated once, refusing a header of unknown type or a body past 2^14 + 256 bytes before the body is waited for; `TlsRecordProtection` is one direction, cleartext until `install(suite, secret)`, or `installHeld(suite, secret)` from a secret held as a `Secret<u8[]>`, ratcheted in place by `advanceHeld(secret)` — then `seal` and `open` a record into the caller's buffer — the nonce from the sequence number, the header as additional data, the inner content type and zero padding found without branching on the padding. `tlsNextTrafficSecret` is KeyUpdate's next secret, taken and answered as a `Secret<u8[]>`. Every refusal is the alert, negated | RFC 8448 §3's nine records byte for byte; ChaCha20-Poly1305, AES-256-GCM-SHA384 and padding against Python's `cryptography` |
|
|
144
176
|
| [`net/tls/record-server.ts`](./net/tls/record-server.ts) | `TlsRecordServer`: a TLS 1.3 server over a byte stream, sans-IO, wrapping a `TlsServer` in records. `receive` the client's bytes, send `output[outputStart .. outputEnd)` and `consume` what was sent, `read` and `write` application data, `sign` when asked, `keyUpdate`, `close`; `interest()` answers what it wants next as `TLS_RECORD_*` bits. It adds the compatibility `change_cipher_spec`, alerts both ways, KeyUpdate both ways and on a schedule, a limit of `TLS_RECORD_IDLE_LIMIT` (16) records in a row that carry nothing, a cap of `TLS_RECORD_MAX_KEY_UPDATES` (64) KeyUpdates from the client, and fixed buffers per connection; the handshake is sealed straight out of `TlsServer`'s buffers | RFC 8448 §3 replayed at every cut of the client's stream |
|
|
145
|
-
| [`net/tls-tcp.ts`](./net/tls-tcp.ts) | `TlsTcpServer`: TLS 1.3 over `nish:net` TCP for a program that owns its loop — a pool of `TlsRecordServer` slots
|
|
146
|
-
| [`net/quic-packet.ts`](./net/quic-packet.ts) | QUIC version 1's packets (WP34 Q1). The variable-length integer (`quicVarintPush`, `quicVarintPushSized`, `quicVarintRead`, `quicVarintLength`, `quicVarintSize`); packet-number length choice and recovery (`quicPacketNumberLength`, `quicPacketNumberDecode`); the long and short headers (`quicLongHeader`, `quicShortHeader`, and `quicParseHeader` into a `QuicHeader`, whose `end` is where the next coalesced packet starts); the Initial secrets from the client's first DCID (`quicInitialSecrets`); the packet keys from a traffic secret (`quicKeys` into a `
|
|
177
|
+
| [`net/tls-tcp.ts`](./net/tls-tcp.ts) | `TlsTcpServer`: TLS 1.3 over `nish:net` TCP for a program that owns its loop — a pool of `TlsRecordServer` slots, each built on the first accept into it, `accept(serverRandom, ephemeralPrivate)` into a free one, `readable` / `writable` when the loop says so, `signP256` with a `Secret` key, and `read`, `write`, `shutdown` and `close` answering as `nish:net`'s calls do. `accept` copies the server random and the ephemeral key into the slot, wipes the caller's key array, and refuses either with -22 when it is not 32 bytes. Every call answers the slot's interest, whose low two bits are `pollModify`'s events. Each slot keeps one `TlsServer`, restarted by `accept`, so a thousand connections through the pool leave the arena where the first did, under any suite (TLS-3). Native only, as `nish:net` is | openssl s_client under all three suites and Node's `https`, from `tests/run.js`; RFC 8448 §3 over loopback from a Nish client |
|
|
178
|
+
| [`net/quic-packet.ts`](./net/quic-packet.ts) | QUIC version 1's packets (WP34 Q1). The variable-length integer (`quicVarintPush`, `quicVarintPushSized`, `quicVarintRead`, `quicVarintLength`, `quicVarintSize`); packet-number length choice and recovery (`quicPacketNumberLength`, `quicPacketNumberDecode`); the long and short headers (`quicLongHeader`, `quicShortHeader`, and `quicParseHeader` into a `QuicHeader`, whose `end` is where the next coalesced packet starts); the Initial secrets from the client's first DCID (`quicInitialSecrets`); the packet keys from a traffic secret held as a `Secret` from `nish:secret` (`quicKeys(slot, aead, secret)` into a `QuicKeysSlot` the caller owns, for AES-128-GCM, AES-256-GCM and ChaCha20-Poly1305) and a key update, its secret, answered as a `Secret` (`quicKeyUpdateSecret`), and the next generation's keys with the header-protection key kept (`quicKeysUpdate(slot, keys, next)`, into a slot of its own), neither keeping anything in the arena, and `quicKeysWipe`, which zeroes a `QuicKeys`, AES schedules included; the same four in place for a connection slot, into a `QuicKeysSlot` it makes once and over its `HkdfScratch` (`quicInitialSecretsInto`, `quicKeysInto`, `quicKeyUpdateSecretInto`, `quicKeysUpdateInto`; `QuicKeysSlot.wipe` zeroes the keys and AES schedules), with `quicKeysHeldInto(slot, aead, secret)` and `quicKeyUpdateSecretHeldInto(dst, src)` for a secret held as a `Secret<u8[]>`, the update in place when `dst` is `src`; packet and header protection (`quicSealPacket`, and `quicOpenPacket` or its two halves `quicRemoveHeaderProtection` and `quicDecryptPacket` into a `QuicPacket`); and, for a connection that allocates nothing per packet (WP34 Q4), `quicParseHeaderInto` (windows, no copy), `quicPutLongHeader` / `quicPutShortHeader` / `quicVarintPut` in place, `quicSealInPlace`, and `quicUnprotectHeader` / `quicDecryptPayload`, which store only numbers into the `QuicPacket`; and the Retry integrity tag (`quicRetryIntegrityTag`, `quicRetryPacket`, `quicRetryVerify`). Every parse is bounds-checked against the datagram and answers a `QUIC_ERR_*` code rather than panicking: a truncated header, a clear fixed bit, another version (with its connection IDs read, for Version Negotiation), a connection ID over 20 bytes, a Length past the datagram, a packet too short to sample, a payload that does not authenticate, and reserved bits set in one that does; keys `quicKeys` did not make are `QUIC_ERR_KEYS`, a caller's error rather than a forgery. Long headers always carry a two-byte Length, so a header's size does not move with its payload, and `quicSealPacket` refuses a header whose Length or packet number disagrees with what it seals. The builders answer `null` for an argument out of range. The keys in a slot the caller owns are the caller's to wipe, with `QuicKeysSlot.wipe` or `quicKeysWipe` ([QUIC-1](../docs/security/quic.md)) | RFC 9000 §16, §17 and Appendix A.1–A.3; RFC 9001 §5 and Appendix A.1–A.5 (client and server Initial, Retry, ChaCha20 short header) |
|
|
147
179
|
| [`net/quic-frame.ts`](./net/quic-frame.ts) | QUIC version 1's frames (WP34 Q2). `quicParseFrame` reads any frame of RFC 9000 §19, 0x00 to 0x1e, into a reused `QuicFrame`, leaving a CRYPTO or STREAM frame's data in the packet (`dataStart`, `dataLength`) and answering 0 or the transport error the RFC names: FRAME_ENCODING_ERROR for a truncated field, a length past the packet, an ACK range below zero, an offset past 2^62 − 1, a stream count past 2^60, an empty NEW_TOKEN or a bad NEW_CONNECTION_ID, and PROTOCOL_VIOLATION for a frame type spelled in more bytes than it needs. The writers (`quicPushAck`, `quicPushCrypto`, `quicPushStream`, `quicPushValue`, `quicPushStreamValue`, `quicPushStreamError`, `quicPushNewConnectionId`, `quicPushPathData`, `quicPushConnectionClose`, `quicPushNewToken`, `quicPushTypeOnly`, `quicPushPadding`) answer `false` rather than write a frame that would not parse; `quicCryptoOverhead` and `quicStreamOverhead` size a frame's header before it is written. `quicFrameAllowed` is §12.4's table of which frames each packet type carries, `quicFrameAckEliciting` §13.2's rule, and `QUIC_ERROR_*` the transport error codes of §20.1. RFC 9221's DATAGRAM frames (0x30, 0x31) are read too (WP34 Q4); every writer has a `put` form (`quicPutAck`, `quicPutStream`, …, `quicPutDatagram`) that writes in place into a caller's buffer before an end and answers the offset past it, which the `push` form calls, and NEW_CONNECTION_ID's ID and token are left in the packet as windows | RFC 9000 §12.4, §19, §20; RFC 9221 §4; RFC 9001 A.2's and A.3's frames |
|
|
148
180
|
| [`net/quic-conn-params.ts`](./net/quic-conn-params.ts) | Transport parameters (RFC 9000 §18): a `QuicTransportParameters` that starts at every default, `quicEncodeTransportParameters` (a parameter only when it is not its default) and `quicParseTransportParameters`, with in-place forms that refill a slot's own array and object (`quicEncodeTransportParametersInto`, `quicParseTransportParametersInto`), which refuses with TRANSPORT_PARAMETER_ERROR, naming the parameter, a parameter that does not frame, a known one sent twice, a varint-valued one that is not exactly one varint, a server-only one from a client, and every value §18.2 bounds. Unknown parameters are skipped. RFC 9221's `max_datagram_frame_size` is read and written too (WP34 Q4) | RFC 9000 §7.4, §18; RFC 9221 §3; RFC 9001 A.2's client parameters |
|
|
149
181
|
| [`net/quic-conn-ack.ts`](./net/quic-conn-ack.ts) | `QuicAckRanges`: the packet numbers one space received, as at most 32 disjoint ranges, for duplicate detection (`record`, `contains`) and for the ACK frame written from them (`pushAck`, or `putAck` in place). Past 32 ranges the lowest is dropped and a floor raised over it, below which every packet number reads as already received | RFC 9000 §12.3, §13.2, §19.3 |
|
|
150
182
|
| [`net/quic-conn-cid.ts`](./net/quic-conn-cid.ts) | `QuicCidTable`: the connection IDs a connection issued (routed by `ownsLocal`, retired by the peer with `retireLocal`) and the ones its peer issued (`addPeer`, with Retire Prior To retiring older ones into `retirePending`, and `currentPeer` the one sent to). A reused sequence number, an ID under two sequence numbers and retiring the ID a packet was sent to are PROTOCOL_VIOLATION; more active IDs than advertised, or more owed retirements than twice that, CONNECTION_ID_LIMIT_ERROR. Its slots are fixed when it is made (WP34 Q4): an ID is copied into one, `addPeerAt` / `ownsLocalAt` / `retireLocal` read it from the packet in place, and `reset` empties the table for the next connection | RFC 9000 §5.1, §19.15, §19.16 |
|
|
151
|
-
| [`net/quic.ts`](./net/quic.ts) | The server side of a QUIC connection (WP34 Q2): `new QuicConnection(config, entropy)`, `receive(datagram, now)`, `signatureInput()` / `sign(signature)`, `takeDatagram(now)` until `null`, `deadline()` / `handleTimer(now)` for its timers, and `readStream()` / `writeStream(id, data, fin)` once connected; `updateKeys()`, `close(code)` and `release()`; `acceptRetry(originalDcid, retryScid)` for a connection a Retry token opened; `quicStatelessResetToken(key, cid)`. TLS 1.3 runs over CRYPTO frames at the Initial, Handshake and 1-RTT levels (`TlsServer` with `quic` set), the keys of each level come from `nish/net/quic-packet` as the secrets appear and are discarded and wiped as RFC 9001 §4.9 says, transport parameters go both ways and the client's are checked, every ack-eliciting packet is acknowledged in its space, the client gets new connection IDs once the handshake is confirmed, PATH_CHALLENGE is answered, and an ack-eliciting Initial is padded to 1200 bytes and held to three times what the unvalidated client sent. A protocol violation closes the connection with the RFC's transport error, a TLS alert with CRYPTO_ERROR; a packet that cannot be used is dropped and counted. The idle timeout is the smaller of both sides' `max_idle_timeout`, at least three probe timeouts, and closes silently; key update runs both ways, the client's followed (at most `QUIC_CONN_MAX_KEY_UPDATES`, QUIC-4) and the server's started by `updateKeys()`, with the next read keys derived in advance and the previous kept a probe timeout for reordered packets; every connection ID carries the stateless reset token the configuration's static key gives it. Loss recovery is `nish/net/quic-recovery`'s (WP34 Q3): what a lost packet carried (CRYPTO, STREAM, HANDSHAKE_DONE and the connection-ID frames) is sent again, a probe timeout sends a probe (two datagrams in the Initial and Handshake spaces, `QUIC_CONN_HANDSHAKE_PROBES`, each with the oldest CRYPTO data not acknowledged), a client that shows it lacks the handshake flight (an Initial repeating CRYPTO data, an ack-eliciting Handshake packet) gets it again at once, up to `QUIC_CONN_EARLY_RESENDS` times a connection (QUIC-8), and NewReno's window holds back everything but ACKs and probes. Streams (WP34 Q4) are `nish/net/quic-stream`'s, both ways and both kinds, through `nextStreamEvent()`, `openStream(bidirectional)`, `streamRead(id, buf, at, length)`, `streamWrite(id, buf, from, length, fin)`, `streamReset(id, code)` and `streamStopSending(id, code)`; RFC 9221's datagrams through `sendDatagram(buf, from, length)`, `readDatagram(buf, at, cap)` and `maxDatagramPayload()` when `config.maxDatagramFrameSize` offers them. Nothing is allocated per packet (QUIC-3): `receiveWindow(buf, off, len, now)` and `takeDatagramInto(out, at, now)` open and build packets in place inside an arena block, and `reset(entropy)` makes the connection a reusable slot for the next client.
|
|
152
|
-
| [`net/quic-listener.ts`](./net/quic-listener.ts) | What a QUIC server answers to a datagram no connection owns (WP34 Q2, second part): `new QuicListener(config, entropy)` and `handleWindow(buf, at, len, address, now, answer)`, which reads the datagram `buf[at .. at + len)` in place and fills `answer`, a `QuicListenerAnswer` made once with `QUIC_LISTENER_ANSWER_SIZE` of room and refilled in it, so a datagram costs no arena memory whatever it is answered with (H3-3); `handle(datagram, address, now)` does the same over a whole array into an answer of its own. An answer's `kind` is `QUIC_LISTEN_DROP`, `_ACCEPT` (make a `QuicConnection`; after a Retry, `acceptRetry` it with the answer's IDs), or a `reply` to send: `_VERSION_NEGOTIATION` for another version in a full-sized datagram, `_RETRY` with an address-validation token when `config.retry` is set, `_INVALID_TOKEN` for a Retry token that is forged, moved, expired or for another ID, and `_STATELESS_RESET` for a short header to an ID the server has no connection for. A token is 128 bits of HMAC-SHA256 under `config.retryTokenKey` over its issue time, the original DCID, the new ID and the client's address and port, good for `config.retryTokenLifetime`. A reset ends in `quicStatelessResetToken` of the packet's ID, is always shorter than the datagram it answers and never under 21 bytes, and is rate limited. Its own unpredictable bytes come from an HMAC generator seeded once by the caller. For the carrier's send loop, `quicListenerTakePaced(conn, now)` takes a connection's next datagram only when its pacer has the credit, and `quicListenerPaceTime(conn, now)` says when it will (WP34 Q3); `quicListenerTakeFlight(conn, now, buf, at, cap, flight)` writes a paced flight back to back for one GSO send, with the `UDP_SEGMENT` size in its `QuicFlight`, and `quicListenerReceiveSegments` hands a connection a GRO receive segment by segment, in place (WP34 Q4) | RFC 9002 §7.7; RFC 9000 §5.2.2, §6, §8.1, §10.3, §17.2.1, §17.2.5; Version Negotiation, Retry, a stateless reset and the idle timeout against aioquic, recorded and replayed byte for byte |
|
|
183
|
+
| [`net/quic.ts`](./net/quic.ts) | The server side of a QUIC connection (WP34 Q2): `new QuicConnection(config, entropy)`, `receive(datagram, now)`, `signatureInput()` / `sign(signature)`, `takeDatagram(now)` until `null`, `deadline()` / `handleTimer(now)` for its timers, and `readStream()` / `writeStream(id, data, fin)` once connected; `updateKeys()`, `close(code)` and `release()`; `acceptRetry(originalDcid, retryScid)` for a connection a Retry token opened; `quicStatelessResetToken(key, cid)`. TLS 1.3 runs over CRYPTO frames at the Initial, Handshake and 1-RTT levels (`TlsServer` with `quic` set), the keys of each level come from `nish/net/quic-packet` as the secrets appear and are discarded and wiped as RFC 9001 §4.9 says, transport parameters go both ways and the client's are checked, every ack-eliciting packet is acknowledged in its space, the client gets new connection IDs once the handshake is confirmed, PATH_CHALLENGE is answered, and an ack-eliciting Initial is padded to 1200 bytes and held to three times what the unvalidated client sent. A protocol violation closes the connection with the RFC's transport error, a TLS alert with CRYPTO_ERROR; a packet that cannot be used is dropped and counted. The idle timeout is the smaller of both sides' `max_idle_timeout`, at least three probe timeouts, and closes silently; key update runs both ways, the client's followed (at most `QUIC_CONN_MAX_KEY_UPDATES`, QUIC-4) and the server's started by `updateKeys()`, with the next read keys derived in advance and the previous kept a probe timeout for reordered packets; every connection ID carries the stateless reset token the configuration's static key gives it. Loss recovery is `nish/net/quic-recovery`'s (WP34 Q3): what a lost packet carried (CRYPTO, STREAM, HANDSHAKE_DONE and the connection-ID frames) is sent again, a probe timeout sends a probe (two datagrams in the Initial and Handshake spaces, `QUIC_CONN_HANDSHAKE_PROBES`, each with the oldest CRYPTO data not acknowledged), a client that shows it lacks the handshake flight (an Initial repeating CRYPTO data, an ack-eliciting Handshake packet) gets it again at once, up to `QUIC_CONN_EARLY_RESENDS` times a connection (QUIC-8), and NewReno's window holds back everything but ACKs and probes. Streams (WP34 Q4) are `nish/net/quic-stream`'s, both ways and both kinds, through `nextStreamEvent()`, `openStream(bidirectional)`, `streamRead(id, buf, at, length)`, `streamWrite(id, buf, from, length, fin)`, `streamReset(id, code)` and `streamStopSending(id, code)`; RFC 9221's datagrams through `sendDatagram(buf, from, length)`, `readDatagram(buf, at, cap)` and `maxDatagramPayload()` when `config.maxDatagramFrameSize` offers them. Nothing is allocated per packet (QUIC-3): `receiveWindow(buf, off, len, now)` and `takeDatagramInto(out, at, now)` open and build packets in place inside an arena block, and `reset(entropy)` makes the connection a reusable slot for the next client. The AEAD limits of RFC 9001 §6.6 are kept: a 1-RTT write key under AES-GCM seals at most 2^23 packets, the server starting a key update at three quarters of that and closing with AEAD_LIMIT_REACHED at it if none can be made, and packets that fail to authenticate past the integrity limit (2^52 for AES-GCM, 2^36 for ChaCha20-Poly1305) close the connection the same way (QUIC-6). One Application Data packet number in every 128 to 1024 is skipped, and an ACK of a skipped one is PROTOCOL_VIOLATION, an optimistic ACK (RFC 9000 §21.4, QUIC-7) | RFC 9000 §10.1, §10.3.2, §21.4; RFC 9001 §4, §5.7, §6, §8; RFC 9221; a handshake and stream echo, and an update each way, against aioquic, recorded and replayed byte for byte |
|
|
184
|
+
| [`net/quic-listener.ts`](./net/quic-listener.ts) | What a QUIC server answers to a datagram no connection owns (WP34 Q2, second part): `new QuicListener(config, entropy)` and `handleWindow(buf, at, len, address, now, answer)`, which reads the datagram `buf[at .. at + len)` in place and fills `answer`, a `QuicListenerAnswer` made once with `QUIC_LISTENER_ANSWER_SIZE` of room and refilled in it, so a datagram costs no arena memory whatever it is answered with (H3-3); `handle(datagram, address, now)` does the same over a whole array into an answer of its own. An answer's `kind` is `QUIC_LISTEN_DROP`, `_ACCEPT` (make a `QuicConnection`; after a Retry, `acceptRetry` it with the answer's IDs), or a `reply` to send: `_VERSION_NEGOTIATION` for another version in a full-sized datagram, `_RETRY` with an address-validation token when `config.retry` is set, `_INVALID_TOKEN` for a Retry token that is forged, moved, expired or for another ID, and `_STATELESS_RESET` for a short header to an ID the server has no connection for. A token is 128 bits of HMAC-SHA256 under `config.retryTokenKey` over its issue time, the original DCID, the new ID and the client's address and port, good for `config.retryTokenLifetime`. A reset ends in `quicStatelessResetToken` of the packet's ID, is always shorter than the datagram it answers and never under 21 bytes, and is rate limited. Its own unpredictable bytes come from an HMAC generator seeded once by the caller. `release()` wipes the seed, the generator, the scratch, the INVALID_TOKEN keys and the spare answer `handle` keeps, and `handle` and `handleWindow` answer nothing after it; its other methods still run, under the zeroed seed, and are the caller's not to call (QUIC-5). For the carrier's send loop, `quicListenerTakePaced(conn, now)` takes a connection's next datagram only when its pacer has the credit, and `quicListenerPaceTime(conn, now)` says when it will (WP34 Q3); `quicListenerTakeFlight(conn, now, buf, at, cap, flight)` writes a paced flight back to back for one GSO send, with the `UDP_SEGMENT` size in its `QuicFlight`, and `quicListenerReceiveSegments` hands a connection a GRO receive segment by segment, in place (WP34 Q4) | RFC 9002 §7.7; RFC 9000 §5.2.2, §6, §8.1, §10.3, §17.2.1, §17.2.5; Version Negotiation, Retry, a stateless reset and the idle timeout against aioquic, recorded and replayed byte for byte |
|
|
153
185
|
| [`net/quic-recovery.ts`](./net/quic-recovery.ts) | QUIC loss detection and congestion control (WP34 Q3), sans-IO, for the server side: `QuicRecovery` holds one `QuicSentPackets` ring per packet number space (16 packets for Initial and Handshake, 128 for Application Data, fixed when it is made); `onPacketSent(space, pn, size, now)` records an ack-eliciting packet and answers its slot, `onAck(space, ranges, count, ackDelay, now)` reads an ACK frame's ranges and lists the slots it acknowledged and showed lost (`acked`, `lost`), `deadline(amplificationBlocked)` and `onTimeout(now, amplificationBlocked)` run the time-threshold loss timer and the probe timeout with its backoff, `discardSpace` drops a space whose keys are gone, `evictOldest` frees a slot for a probe. The RTT estimate (`latestRtt`, `smoothedRtt`, `rttVar`, `minRtt`, `probeTimeout()`), loss by packet threshold 3 and time threshold 9/8, NewReno (slow start, one halving per recovery period, congestion avoidance, the 2400-byte floor, persistent congestion) and a pacer at 5/4 of the window per smoothed RTT (`pacerDelay`, `onPaced`), all in integer milliseconds and bytes | RFC 9002 §5–7, Appendices A and B, worked by hand in `net_quic_recovery` |
|
|
154
186
|
| [`net/quic-stream.ts`](./net/quic-stream.ts) | QUIC streams (WP34 Q4), sans-IO, which `nish/net/quic` drives: `QuicStreams` holds one `QuicStream` slot per stream that may be open at once (the client's bidirectional and unidirectional limits and the server's own), each with a fixed receive and send buffer, found by ID through a hash index. The send and receive state machines of §3 (`QUIC_SEND_*`, `QUIC_RECV_*`), reassembly into the receive buffer, flow control per stream and for the connection both ways (MAX_STREAM_DATA and MAX_DATA raised as the application reads, STREAM_DATA_BLOCKED and DATA_BLOCKED once per limit), the stream limits (MAX_STREAMS raised as the client's streams finish, STREAMS_BLOCKED at the client's), STOP_SENDING answered with RESET_STREAM, and the refusals STREAM_STATE_ERROR, STREAM_LIMIT_ERROR, FLOW_CONTROL_ERROR and FINAL_SIZE_ERROR. The application's answers are `QUIC_STREAM_OK`, `QUIC_STREAM_END` and `QUIC_STREAM_ERR_*` | RFC 9000 §2–§4, §19.4–§19.14 |
|
|
155
187
|
| [`net/quic-datagram.ts`](./net/quic-datagram.ts) | `QuicDatagramQueue` (WP34 Q4): a fixed ring of DATAGRAM payloads, one each way per connection, copied in (`push`) and out (`pop`, or `putFrame` as a DATAGRAM frame), refusing past a full ring and counting what it drops | RFC 9221 §5 |
|
|
156
|
-
| [`net/
|
|
188
|
+
| [`net/buffer-pool.ts`](./net/buffer-pool.ts) | `BufferPool(name, size, ceiling)`: a pool of fixed-size `u8[]` buffers that a server lends across connections. `take()` answers a free buffer, most recently given back first, or a new one while fewer than `ceiling` are lent; `give(buf, used)` zeroes the first `used` bytes and takes it back, and `giveSecret(buf, used)` wipes the whole buffer with `secureZero`; `lent()` counts the buffers out. A `size` not above 0 or a `ceiling` below 1, taking past the ceiling, giving back a buffer of the wrong length, a used extent outside 0 to `size`, and a give-back with none lent panic, naming the pool | Reuse, order, zeroing, the ceiling and a warmed loop that allocates nothing (`tests/link/net_buffer_pool`, and its `_bad_` cases) |
|
|
189
|
+
| [`net/hpack.ts`](./net/hpack.ts) | HPACK, HTTP/2's header compression: `HpackEncoder` and `HpackDecoder`, one per direction of a connection, each with its `HpackTable`; and beneath them the §5.1 integer (`hpackEncodeInteger` / `hpackDecodeInteger`), the §5.2 string (`hpackEncodeString` / `hpackDecodeString`) and Appendix B's Huffman code (`HpackHuffman`, `hpackHuffmanEncode`, `hpackHuffmanDecode`, `hpackHuffmanLength`). Names and values are `u8[]`. The encoder takes the representation and the Huffman choice per field, and sends a never-indexed field as a literal even when a table holds it. The decoder answers a negative `HPACK_ERR_*` code, and stays spent, for a truncated block, an integer past 2^31 - 1, an index of zero or out of range, bad Huffman padding, EOS, a size update above the SETTINGS limit, after a field or missing when a lowered limit requires one, and for a block that would copy more than `HPACK_COPY_FACTOR` times the larger of the table's size and the block's length into and out of the table (`HPACK_ERR_COPY_BUDGET`; `tableCopied` counts those octets); a header list past the caller's limit is decoded to the end, its excess dropped, and answered with `HPACK_LIST_TOO_LARGE`, which is not fatal. A window outside its buffer, a negative size or limit, and an integer prefix outside 1 to 8 bits are the program's mistakes and panic | RFC 7541 Appendix C (C.1–C.6, with the dynamic table after every step) and every code of Appendix B |
|
|
157
190
|
| [`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 |
|
|
158
|
-
| [`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` | 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`) |
|
|
191
|
+
| [`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`) |
|
|
192
|
+
| [`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 |
|
|
159
193
|
| [`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 |
|
|
160
|
-
| [`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). 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, and every refusal (`tests/link/net_http1_server`); `curl` against its `serve` mode |
|
|
161
|
-
| [`net/
|
|
194
|
+
| [`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 |
|
|
195
|
+
| [`net/url.ts`](./net/url.ts) | The request target's path and query, read in place: `urlQueryStart` finds the `?`, `UrlPathSegments` splits a path on the raw `/` and `UrlQuery` yields a query's pairs as offsets, both reusable by `reset`; `percentDecode` and `percentDecodePathSegment` decode a window into the caller's array and answer the length or a `URL_DECODE_ERR_*` code, `percentDecodeErrorAt` answers the offset an error is at, and `urlQueryFind` finds a key. Decoding is strict by default (a malformed escape and `%00` are refused); `URL_DECODE_FORM`, `URL_DECODE_LENIENT` and `URL_DECODE_ALLOW_NUL` together decode valid UTF-8 as `URLSearchParams` does. A query yields at most `UrlQueryLimits.maxPairs` pairs (100 by default) and then answers `URL_QUERY_ERR_PAIRS`. The byte API allocates nothing; `urlDecode` and `urlQueryGet` answer strings and refuse what is not UTF-8. Records: [`docs/security/url.md`](../docs/security/url.md) | RFC 3986 §2.1 and §3.3, the WHATWG URL Standard's percent-decode and `application/x-www-form-urlencoded` parser; `decodeURIComponent` and `URLSearchParams` on 3,000 seeded inputs |
|
|
196
|
+
| [`net/http-fields.ts`](./net/http-fields.ts) | The header model HTTP/2 and HTTP/3 share, version-neutral: `HttpFields` reads a decoded section with `readRequest(names, values, extendedConnect)`, `readResponse` or `readTrailers` into `method`, `scheme`, `authority`, `path`, `protocol`, `status`, the regular fields in order (`get(name)`) and `contentLength`, answering `HTTP_FIELDS_OK` or the first refusal: a name that is empty, not a token or not lowercase; a value with NUL, CR or LF or whitespace at an end; a connection-specific field (`te` but `trailers` included); a pseudo-header unknown, repeated, after a regular field, forbidden by the shape or missing; a pseudo-header value of the wrong syntax; a `host` that disagrees with `:authority`; a bad or disagreeing `content-length`. `:protocol` (RFC 8441) is read only where the caller enabled extended CONNECT. `httpFieldNameValid`, `httpFieldValueValid`, `httpFieldConnectionSpecific` and `httpFieldsCheckOutgoing` check one name, one value or a section a program sends. `httpDateWrite(out, at, seconds)` writes a second since 1970 as the IMF-fixdate a `Date` field holds (`HTTP_DATE_SIZE` bytes, to 9999), reading no clock, so it compiles for wasm32 | RFC 9110 §5, RFC 9113 §8.2–§8.5, RFC 9114 §4.2–§4.3, RFC 8441 §4; every refusal in `tests/link/net_http_fields`; RFC 9110 §5.6.7, every day from 1970 to 2399 (`tests/link/net_http_date`) |
|
|
162
197
|
| [`net/http2-frame.ts`](./net/http2-frame.ts) | HTTP/2's frames, sans-IO and allocation-free: `http2ReadHeader` and `http2ParseFrame` read any frame of RFC 9113 §6 into a reused `Http2Frame` (its content left in the buffer as a window), answering 0 or the connection error — FRAME_SIZE_ERROR past the reader's SETTINGS_MAX_FRAME_SIZE or for a frame of the wrong length, PROTOCOL_ERROR for a frame on the wrong stream, padding as long as its frame, or a WINDOW_UPDATE of zero on the connection — with a stream error (`streamError`) for a PRIORITY of the wrong length, a self-dependency or a WINDOW_UPDATE of zero on a stream. `http2SettingCount`, `http2SettingId`, `http2SettingValue` and `http2SettingError` read and check SETTINGS. The writers, one per type (`http2WriteData` … `http2WriteContinuation`, `http2WriteSettingsAck`), write into the caller's buffer at an offset and answer -1 when a frame does not fit | Every type against frames built by hand from §4.1 and §6, both directions, and every refusal (`tests/link/net_http2_frame`) |
|
|
163
|
-
| [`net/http2.ts`](./net/http2.ts) | `Http2Connection`, the server side of HTTP/2, sans-IO: `feed(buf, off, len)`, then `next()` answers `H2_REQUEST` (`stream`, `fields`), `H2_DATA` (a window onto the input), `H2_TRAILERS`, `H2_RESET`, `H2_WINDOW` (a send window opened), `H2_GOAWAY` or the final `H2_ERROR`; send `output[outputStart .. outputEnd)` and `consume`. `respond`, `writeData` (as far as both windows and the output allow), `writeTrailers`, `reset`, `goaway` and `restart`. The §5.1 stream state machine, connection and stream flow control with WINDOW_UPDATE both ways, SETTINGS and the acknowledgement, PING, header blocks across CONTINUATION decoded by `HpackDecoder`, a 431 past the header-list cap, extended CONNECT behind SETTINGS_ENABLE_CONNECT_PROTOCOL, and every connection and stream error of RFC 9113 as GOAWAY or RST_STREAM. Every cap is an `Http2Config` number fixed at start-up; DATA and every control frame allocate nothing ([H2-1](../docs/security/http2.md)) | RFC 9113 §3–§8, RFC 8441; requests, streamed responses, flow control stalling and resuming
|
|
164
|
-
| [`net/http2-tls.ts`](./net/http2-tls.ts) | `Http2TlsServer`: HTTP/2 over `nish/net/tls-tcp` for a program that owns its loop — a pool of `Http2Connection`s beside the TLS slots,
|
|
198
|
+
| [`net/http2.ts`](./net/http2.ts) | `Http2Connection`, the server side of HTTP/2, sans-IO: `feed(buf, off, len)`, then `next()` answers `H2_REQUEST` (`stream`, `fields`), `H2_DATA` (a window onto the input), `H2_TRAILERS`, `H2_RESET`, `H2_WINDOW` (a send window opened), `H2_GOAWAY` or the final `H2_ERROR`; send `output[outputStart .. outputEnd)` and `consume`. `respond`, `writeData` (as far as both windows and the output allow), `writeTrailers`, `reset`, `goaway` and `restart`. The §5.1 stream state machine, connection and stream flow control with WINDOW_UPDATE both ways, SETTINGS and the acknowledgement, PING, header blocks across CONTINUATION decoded by `HpackDecoder`, a 431 past the header-list cap, extended CONNECT behind SETTINGS_ENABLE_CONNECT_PROTOCOL, and every connection and stream error of RFC 9113 as GOAWAY or RST_STREAM. Every final head carries one `date` field from the connection's `date` bytes, when it has them, straight after `:status` and never on a 1xx or trailers, unless the program's fields hold one (matched ASCII case-insensitively). Every cap is an `Http2Config` number fixed at start-up; DATA and every control frame allocate nothing ([H2-1](../docs/security/http2.md)) | RFC 9113 §3–§8, RFC 8441; requests, streamed responses, flow control stalling and resuming, concurrent streams and the date on every final head from a scripted client (`tests/link/net_http2`), and a negative for each error class (`tests/link/net_http2_errors`) |
|
|
199
|
+
| [`net/http2-tls.ts`](./net/http2-tls.ts) | `Http2TlsServer`: HTTP/2 over `nish/net/tls-tcp` for a program that owns its loop — a pool of `Http2Connection`s beside the TLS slots, slot 0's built by the constructor and every other on the first accept into its slot, each reused with `restart`; `accept`, `readable` / `writable`, `signP256`, `next(slot)` for the slot's next event, `flush(slot)`, `close`. A slot whose ALPN did not choose `h2` is shut down with `close_notify` (RFC 9113 §3.2). Every slot's final responses carry the pool's one `HttpDate` (`date`, from `nish/net/http1-server`), refreshed at `connection(slot)` and `next(slot)` and formatted at most once a second; `date.pin(unixMs)` fixes it for a test. Native only, as `nish:net` is | A Nish TLS client over loopback (`tests/link/net_http2_tls`): ALPN, a GET, an echoed POST, a hundred DATA frames with the arena flat, the date with the arena flat while the pinned clock crosses seconds and shared by two slots, GOAWAY, and the pool; `curl --http2` against its `serve` mode |
|
|
165
200
|
| [`net/http3-frame.ts`](./net/http3-frame.ts) | HTTP/3's frames and stream types, sans-IO and allocation-free: `h3ReadFrameHeader` reads a type and a length (0 until both are in the window), `h3ReadSettings` a SETTINGS payload into a reused `Http3Settings` (the three identifiers it understands, and the first eight it does not, kept for an extension), `h3ReadIdPayload` a GOAWAY, MAX_PUSH_ID or CANCEL_PUSH; `h3PutFrameHeader`, `h3PutSettings`, `h3PutIdFrame` and `h3PutVarint` write in place. `h3ReservedHttp2Frame` names HTTP/2's reserved types, `h3Greased` the `0x1f * N + 0x21` ones, and the `H3_*` constants are RFC 9114's types, settings and error codes | RFC 9114 §6.2, §7, §8.1, against frames built by hand from §7's layouts (`tests/link/net_http3_frame`) |
|
|
166
|
-
| [`net/http3.ts`](./net/http3.ts) | `Http3Connection`, the server side of HTTP/3 over a `QuicConnection`'s streams: `next()` answers `H3_REQUEST` (`stream`, `fields`), `H3_DATA` (a window onto `data`, at most `bodyChunk`), `H3_TRAILERS`, `H3_END`, `H3_RESET`, `H3_WRITABLE`, `H3_GOAWAY` or the final `H3_ERROR`; `respond`, `writeData` (takes what the send buffer has room for), `writeTrailers`, `reset` and `goaway` write. It opens its control stream with SETTINGS and its QPACK streams (capacity 0, through `nish/net/qpack`), checks every field section with `nish/net/http-fields`, answers a section past `maxFieldSectionSize` with a 431, and closes the connection with RFC 9114's code for each connection error. No server push. A warm connection allocates nothing per request (`docs/security/http3.md`) | RFC 9114 §4–§8 and RFC 9204 §4.2: a Nish client over QUIC, requests with bodies both ways, trailers, GOAWAY, cancellation, every error code (`tests/link/net_http3`, `net_http3_errors`) |
|
|
167
|
-
| [`net/http3-server.ts`](./net/http3-server.ts) | `Http3Server`: HTTP/3 over QUIC on one UDP socket for a program that owns its loop — a pool of `QuicConnection` slots with an `Http3Connection` each, sized at start-up
|
|
201
|
+
| [`net/http3.ts`](./net/http3.ts) | `Http3Connection`, the server side of HTTP/3 over a `QuicConnection`'s streams: `next()` answers `H3_REQUEST` (`stream`, `fields`), `H3_DATA` (a window onto `data`, at most `bodyChunk`), `H3_TRAILERS`, `H3_END`, `H3_RESET`, `H3_WRITABLE`, `H3_GOAWAY` or the final `H3_ERROR`; `respond`, `writeData` (takes what the send buffer has room for), `writeTrailers`, `reset` and `goaway` write. It opens its control stream with SETTINGS and its QPACK streams (capacity 0, through `nish/net/qpack`), checks every field section with `nish/net/http-fields`, answers a section past `maxFieldSectionSize` with a 431, sends one `date` field from the connection's `date` bytes on every final head (straight after `:status`, counted against the client's field-section limit, never on trailers) unless the program's fields hold one, and closes the connection with RFC 9114's code for each connection error. No server push. A warm connection allocates nothing per request (`docs/security/http3.md`) | RFC 9114 §4–§8 and RFC 9204 §4.2: a Nish client over QUIC, requests with bodies both ways, trailers, the date and its limit, GOAWAY, cancellation, every error code (`tests/link/net_http3`, `net_http3_errors`) |
|
|
202
|
+
| [`net/http3-server.ts`](./net/http3-server.ts) | `Http3Server`: HTTP/3 over QUIC on one UDP socket for a program that owns its loop — a pool of `QuicConnection` slots with an `Http3Connection` each, a slot's built on the first Initial accepted into it, slots 0 up, and reused, while the connection-ID index, the timer wheel and the rings are sized from the cap at start-up; a `QuicListener` in front; datagrams routed by a hashed index of every slot's connection IDs; `receive(now, key)`, `tick(now)`, `ready()` for the next slot with news, `flush(now)` sending paced GSO flights, `timeout(now)` from a timer wheel. A handshake that did not choose ALPN `h3` is closed with no_application_protocol (RFC 9114 §3.1); a slot done after GOAWAY is closed and freed. Every slot's final responses carry the server's one `HttpDate` (`date`), refreshed at `connection(slot)` and `ready()` and formatted at most once a second. `stop()` releases every connection and the listener and wipes the configuration's stateless reset and Retry token keys, which are the server's once handed in, and its last answer; the keys are zeroed in place, so a `QuicServerConfig` serves one server (QUIC-5). Native only, as `nish:net` is | A Nish client across loopback (`tests/link/net_http3_server`): requests, a paced 300 KB response, GOAWAY and the slot freed, ALPN, a full pool, the arena over connections through one slot while the pinned date moves a second on for each, one date formatted for two slots; every key read back as zero after `stop()` (`tests/link/net_quic_server_wipes`); aioquic and `curl --http3` against its `serve` mode |
|
|
168
203
|
| [`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`) |
|
|
204
|
+
| [`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_*`) |
|
|
205
|
+
| [`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` / `paramNull`, `bindEnd`, `closeStatement`, `sync` — with up to `maxInFlight` groups pipelined. 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`) |
|
|
169
206
|
|
|
170
207
|
**How a carrier drives `TlsServer`.** The server speaks in handshake
|
|
171
208
|
*messages*, tagged by level: `TLS_LEVEL_INITIAL` (cleartext — ClientHello,
|
|
@@ -174,11 +211,14 @@ through both Finished messages) and `TLS_LEVEL_APPLICATION`. A carrier hands
|
|
|
174
211
|
`receive` the bytes it got at a level, in any split, and sends what
|
|
175
212
|
`takeOutput(level)` answers under that level's keys: TLS over TCP (T2) in
|
|
176
213
|
records, QUIC (Q2) in CRYPTO frames of the matching packet number space.
|
|
177
|
-
`
|
|
178
|
-
client's traffic secret for the Handshake and Application levels
|
|
179
|
-
|
|
180
|
-
`
|
|
181
|
-
|
|
214
|
+
`writeKnown(level)` and `readKnown(level)` say when the server's and the
|
|
215
|
+
client's traffic secret for the Handshake and Application levels is known
|
|
216
|
+
(the Initial level has none); the secrets themselves are `Secret` fields of
|
|
217
|
+
`secrets()`, the `TlsSecretPool` of the negotiated hash — `serverHandshake`,
|
|
218
|
+
`clientHandshake`, `serverApplication`, `clientApplication` and `exporter` —
|
|
219
|
+
which a carrier reads only through `expose` (`installHeld` turns one into a
|
|
220
|
+
record key), and `wipeSecret(TLS_SECRET_*)` zeroes one once the carrier has
|
|
221
|
+
what it needs. The state says what is due: `TLS_STATE_WAIT_SIGNATURE` after the
|
|
182
222
|
first flight is written, `TLS_STATE_WAIT_FINISHED` once it is signed,
|
|
183
223
|
`TLS_STATE_CONNECTED` when the client's Finished verified, and
|
|
184
224
|
`TLS_STATE_FAILED` with `alert` set after a refusal. Over QUIC, set `quic` in
|
|
@@ -187,9 +227,16 @@ ways (`clientTransportParameters` holds the client's).
|
|
|
187
227
|
|
|
188
228
|
**Secrets.** The ECDHE secret, and the copy of the ephemeral key it is
|
|
189
229
|
computed with, are `Secret`s wiped on every path, and `tlsSignEcdsaP256` takes
|
|
190
|
-
its key as a `Secret`.
|
|
191
|
-
|
|
192
|
-
|
|
230
|
+
its key as a `Secret`. The one-shot key-schedule functions of
|
|
231
|
+
`nish/net/tls/schedule` and `tlsNextTrafficSecret` take and answer their
|
|
232
|
+
secrets as `Secret<u8[]>`s, so a plain array there is a compile error. What `TlsServer` keeps for an open connection —
|
|
233
|
+
the caller's ephemeral key, copied in by `restart`, and the handshake,
|
|
234
|
+
traffic, exporter and resumption secrets — and the four traffic secrets
|
|
235
|
+
`TlsRecordServer` keeps for KeyUpdate are `Secret` fields, readonly and read
|
|
236
|
+
only through `expose`, so the checker refuses binding one to a local, logging
|
|
237
|
+
it or replacing it, and refuses a holder whose `wipe()` misses one; each is
|
|
238
|
+
zeroed once the carrier has what it needs from it and at every end of the
|
|
239
|
+
connection (TLS-1 in
|
|
193
240
|
[`docs/security/tls.md`](../docs/security/tls.md), the record that also lists
|
|
194
241
|
what each refusal is and the test that pins it).
|
|
195
242
|
`tests/link/net_tls_*` are the module's programs, each also run under
|
|
@@ -202,7 +249,7 @@ so each call answers the slot's interest and the program re-arms the
|
|
|
202
249
|
descriptor with `pollModify(loop, tls.fd(slot), wants & 3, slot)`, signs on
|
|
203
250
|
`TLS_RECORD_SIGN`, reads on `TLS_RECORD_DATA` and calls `close(slot)` on
|
|
204
251
|
`TLS_RECORD_DONE`. A slot's buffers — about 136 KB of resident memory, measured over a 400-slot pool, with the handshake's own state in them — are
|
|
205
|
-
allocated once, and after its handshake a connection allocates nothing that
|
|
252
|
+
allocated once, on the first accept into the slot, and after its handshake a connection allocates nothing that
|
|
206
253
|
outlives a record. The handshake keeps its state in the slot, which `accept`
|
|
207
254
|
restarts, and a KeyUpdate installs its keys in place, so neither leaves
|
|
208
255
|
anything in the arena (TLS-3); a connection still takes at most
|
|
@@ -212,13 +259,15 @@ closing quiet slots is the program's (TLS-4). On this path the record layer
|
|
|
212
259
|
wipes `TlsServer`'s traffic and handshake secrets and the ephemeral key once
|
|
213
260
|
it has what it needs from them, which narrows TLS-1.
|
|
214
261
|
`tests/link/net_tls_record_*` are its programs, and the `net_tls_tcp` block
|
|
215
|
-
of `tests/run.js` drives the carrier with openssl and
|
|
262
|
+
of `tests/run.js` drives the carrier with openssl and Node's `https`.
|
|
216
263
|
|
|
217
264
|
**How a server drives `QuicConnection`.** Keep one `QuicListener` for the
|
|
218
265
|
server, with the same `QuicServerConfig` its connections use: two static keys
|
|
219
266
|
of `QUIC_CONN_STATIC_KEY_SIZE` bytes (the stateless reset key, which has to
|
|
220
|
-
survive a restart to be any use, and the Retry token key
|
|
221
|
-
|
|
267
|
+
survive a restart to be any use, and the Retry token key) and whether to
|
|
268
|
+
ask for Retry. `listener.release()` wipes the listener's seed and scratch
|
|
269
|
+
when the server stops; the two keys are the caller's to wipe then, unless
|
|
270
|
+
`Http3Server.stop` does it (QUIC-5). Route each datagram by
|
|
222
271
|
its first packet's DCID: to the connection whose `ownsConnectionId` says it
|
|
223
272
|
is its, and anything else to `listener.handle(datagram, address, now)`, which
|
|
224
273
|
answers what to send back or says to make a connection — and must see nothing
|
|
@@ -249,8 +298,9 @@ Retry, key update both ways, the idle timeout and a stateless reset.
|
|
|
249
298
|
|
|
250
299
|
`net/http1.ts`, `net/http1-server.ts` and `net/websocket.ts` run their checks
|
|
251
300
|
again under `--number-mode f64` (`net_http1_f64`, `net_http1_server_f64`,
|
|
252
|
-
`net_websocket_f64`).
|
|
253
|
-
|
|
301
|
+
`net_websocket_f64`). So does the HTTP date (`net_http_date_f64`). None
|
|
302
|
+
handles a secret but through `nish/net/tls-tcp`, so none is in the
|
|
303
|
+
constant-time check; their record is
|
|
254
304
|
[`docs/security/http1.md`](../docs/security/http1.md), written with the code
|
|
255
305
|
rather than after an audit.
|
|
256
306
|
|
|
@@ -281,7 +331,7 @@ A `std/` module is its own package (`nish`), so its symbols are scoped and a
|
|
|
281
331
|
program may declare a function one of these modules also exports
|
|
282
332
|
(`tests/link/std_package_scope`, `docs/wp21-packages.md` §5a). The package is
|
|
283
333
|
decided by the `nish/` specifier rather than by the directory the file is found
|
|
284
|
-
in — read off the path it would be `nish` from `node_modules
|
|
334
|
+
in — read off the path it would be `nish` from `node_modules/nish/std/` and the
|
|
285
335
|
root package from a checkout, and the same program would compile against an
|
|
286
336
|
installed compiler and be refused by a checkout of it.
|
|
287
337
|
|
|
@@ -304,14 +354,14 @@ reasons that are only true of it:
|
|
|
304
354
|
guess a resolver makes — it is the only `std/` that can be correct for the
|
|
305
355
|
compiler reading it. No version can be skewed against it.
|
|
306
356
|
- It **is** what Node and `tsc` resolve. The package is published as
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
357
|
+
`nish` and declares `"./*": "./std/*.ts"` in `exports`, so with it installed
|
|
358
|
+
a bare `nish/text` is an ordinary package specifier that reaches
|
|
359
|
+
`std/text.ts`. Without it, `runtime/nish.mjs`, the prelude a program runs
|
|
360
|
+
under Node with (`node --experimental-strip-types --import
|
|
361
|
+
./runtime/nish.mjs`), resolves `nish/<module>` to `std/<module>.ts` beside
|
|
362
|
+
itself, and the repository's `tsconfig.json` maps `nish/*` to `./std/*`,
|
|
363
|
+
which is what gives `tsc` and an editor go-to-definition into the real
|
|
364
|
+
source. The compiler short-circuits to that answer rather than walking
|
|
315
365
|
`node_modules` to reach it.
|
|
316
366
|
|
|
317
367
|
So this is WP21's first slice rather than a detour around it: the spelling is
|