@amritk/nish-x86_64-linux 0.20.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 +22 -10
- package/bin/nish +0 -0
- package/docs/AI.md +2759 -0
- package/package.json +2 -1
- 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 +44 -0
- package/runtime/nish.h +75 -2
- package/runtime/nish.mjs +23 -3
- package/runtime/node.mjs +383 -0
- package/runtime/runtime-bytes.c +401 -0
- package/runtime/runtime-cross.c +1 -1
- package/runtime/runtime-host.c +1 -1
- package/runtime/runtime-os.c +1 -1
- package/runtime/runtime-parallel.c +1 -1
- package/runtime/runtime-simd.c +231 -7
- package/runtime/runtime-term.c +504 -0
- package/runtime/runtime-wasm.c +0 -11
- package/runtime/runtime.c +190 -6
- package/runtime/shim.mjs +6 -985
- 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 +261 -53
- package/std/LICENSE-fast-float +23 -0
- package/std/LICENSE-fdlibm +8 -0
- package/std/README.md +31 -11
- package/std/base64.ts +116 -0
- package/std/bytes.ts +256 -0
- package/std/cli.ts +328 -0
- package/std/collections.ts +6 -5
- package/std/crypto/LICENSE-crypt_blowfish +26 -0
- package/std/crypto/base64url.ts +125 -0
- package/std/crypto/bcrypt.ts +760 -0
- package/std/crypto/hmac.ts +138 -2
- package/std/crypto/x509.ts +11 -122
- package/std/json-tokenizer.ts +2998 -57
- package/std/json-value.ts +22 -3
- package/std/json.ts +83 -1
- package/std/math.ts +608 -0
- package/std/net/http1-server.ts +85 -8
- package/std/net/http3-server.ts +50 -12
- package/std/net/postgres.ts +1875 -0
- package/std/net/quic-listener.ts +20 -4
- 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/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,16 +10,29 @@ 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 |
|
|
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 |
|
|
14
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` |
|
|
15
|
-
| [`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`. `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 |
|
|
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 |
|
|
16
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)) |
|
|
17
|
-
| [`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, 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)) |
|
|
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. |
|
|
18
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 |
|
|
19
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 |
|
|
20
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)) |
|
|
21
|
-
| [`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. `tests/link/std_bytes*` are its programs |
|
|
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`) |
|
|
22
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)) |
|
|
23
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 |
|
|
24
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` |
|
|
25
38
|
|
|
@@ -53,17 +66,23 @@ keys are `Secret`s.** `crypto/p256.ts`, `crypto/x25519.ts`, `crypto/ed25519.ts`,
|
|
|
53
66
|
`crypto/x509.ts` and `crypto/x509-random.ts` take and give a private key as a `Secret<u8[]>` from
|
|
54
67
|
`nish:secret`, compute on it only inside `expose`, and wipe every intermediate
|
|
55
68
|
the key reaches before they return, with a store the optimiser may not remove
|
|
56
|
-
(ECC-2, X509-7, closed).
|
|
57
|
-
|
|
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).
|
|
58
76
|
|
|
59
77
|
| Module | What it is | Reproduces |
|
|
60
78
|
| --- | --- | --- |
|
|
61
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 |
|
|
62
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 |
|
|
63
|
-
| [`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 |
|
|
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 |
|
|
64
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 |
|
|
65
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 | — |
|
|
66
|
-
| [`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`) |
|
|
67
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 |
|
|
68
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` |
|
|
69
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 |
|
|
@@ -172,7 +191,7 @@ reads one, and it still takes its randomness as an argument.
|
|
|
172
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`) |
|
|
173
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 |
|
|
174
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 |
|
|
175
|
-
| [`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). 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 |
|
|
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 |
|
|
176
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 |
|
|
177
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`) |
|
|
178
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`) |
|
|
@@ -183,6 +202,7 @@ reads one, and it still takes its randomness as an argument.
|
|
|
183
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 |
|
|
184
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`) |
|
|
185
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`) |
|
|
186
206
|
|
|
187
207
|
**How a carrier drives `TlsServer`.** The server speaks in handshake
|
|
188
208
|
*messages*, tagged by level: `TLS_LEVEL_INITIAL` (cleartext — ClientHello,
|
package/std/base64.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/base64` — standard base64 (RFC 4648 §4) with `=` padding, the encoding
|
|
3
|
+
* kitty graphics frames are sent in.
|
|
4
|
+
*
|
|
5
|
+
* The alphabet is `A-Z a-z 0-9 + /`. Every three bytes become four characters,
|
|
6
|
+
* and a final group of one or two bytes becomes two or three characters filled
|
|
7
|
+
* out to four with `=` (RFC 4648 §4, the padding rules of §3.2), so the text is
|
|
8
|
+
* always a multiple of four long.
|
|
9
|
+
*
|
|
10
|
+
* - `base64EncodedLength(n)` is the characters `n` bytes encode to.
|
|
11
|
+
* - `base64EncodeInto(src, off, len, dst, at)` writes the encoding of
|
|
12
|
+
* `src[off..off+len)` into `dst` from `at` and allocates nothing, so a frame
|
|
13
|
+
* can be encoded chunk by chunk straight into its output buffer.
|
|
14
|
+
* - `base64Encode(bytes)` is the whole array as a string.
|
|
15
|
+
*
|
|
16
|
+
* This module only encodes: what it is for writes base64 and never reads it
|
|
17
|
+
* back. It is not the strict decoder of `nish/crypto/base64url`, which is a
|
|
18
|
+
* different alphabet without padding and is written not to branch on the data;
|
|
19
|
+
* what goes through here is pixels, not secrets, so a sextet becomes its
|
|
20
|
+
* character by indexing the alphabet. The output is ASCII, so the string is
|
|
21
|
+
* the same bytes natively and under Node.
|
|
22
|
+
*
|
|
23
|
+
* Private helpers share the importing program's flat symbol namespace
|
|
24
|
+
* (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** The 64 characters of RFC 4648 §4's Table 1, in value order. */
|
|
28
|
+
const BASE64_ALPHABET: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
|
29
|
+
|
|
30
|
+
/** `=`, which fills a short final group out to four characters (RFC 4648 §3.2). */
|
|
31
|
+
const BASE64_PAD: i32 = 61
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The characters `n` bytes encode to: four for every three, a short final
|
|
35
|
+
* group counted as a whole one. Panics when `n` is negative or the length
|
|
36
|
+
* would pass 2^31 - 1, which `n` above 1,610,612,733 does.
|
|
37
|
+
*/
|
|
38
|
+
export const base64EncodedLength = (n: i32): i32 => {
|
|
39
|
+
if (n < 0) {
|
|
40
|
+
panic("base64EncodedLength: a negative byte count")
|
|
41
|
+
}
|
|
42
|
+
// (2^31 - 1) / 4 * 3 groups' worth of bytes is the most whose encoding fits.
|
|
43
|
+
if (n > 1610612733) {
|
|
44
|
+
panic("base64EncodedLength: the encoding is longer than 2^31 - 1 characters")
|
|
45
|
+
}
|
|
46
|
+
return (((n + 2) / 3) | 0) * 4
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The byte of the character for the sextet `v` (`0 <= v <= 63`). */
|
|
50
|
+
const base64CharOf = (v: i32): u8 => toU8(BASE64_ALPHABET.charCodeAt(v))
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Write the base64 of `src[off..off+len)` into `dst` from index `at`, and
|
|
54
|
+
* answer the index just past the last character written, which is
|
|
55
|
+
* `at + base64EncodedLength(len)`. Nothing is allocated. Panics when the source
|
|
56
|
+
* range is not inside `src` or the encoding does not fit in `dst` from `at`;
|
|
57
|
+
* both are checked before anything is written.
|
|
58
|
+
*/
|
|
59
|
+
export const base64EncodeInto = (src: u8[], off: i32, len: i32, dst: u8[], at: i32): i32 => {
|
|
60
|
+
// Refused before `toI32`, which saturates a longer length under
|
|
61
|
+
// `--number-mode f64` and would let a range past the true end through.
|
|
62
|
+
if (src.length > 2147483647 || dst.length > 2147483647) {
|
|
63
|
+
panic("base64EncodeInto: an array longer than 2^31 - 1 bytes")
|
|
64
|
+
}
|
|
65
|
+
const srcLength: i32 = toI32(src.length)
|
|
66
|
+
const dstLength: i32 = toI32(dst.length)
|
|
67
|
+
if (off < 0 || len < 0 || off > srcLength - len) {
|
|
68
|
+
panic("base64EncodeInto: the source range is outside the array")
|
|
69
|
+
}
|
|
70
|
+
const need: i32 = base64EncodedLength(len)
|
|
71
|
+
if (at < 0 || at > dstLength - need) {
|
|
72
|
+
panic("base64EncodeInto: the encoding does not fit in the destination")
|
|
73
|
+
}
|
|
74
|
+
let i: i32 = off
|
|
75
|
+
let o: i32 = at
|
|
76
|
+
const whole: i32 = off + len - (len % 3)
|
|
77
|
+
// The checks above make every term after the first true; they are written out
|
|
78
|
+
// because they are what proves the loop's accesses in range.
|
|
79
|
+
while (i < whole && i >= 0 && i + 2 < srcLength && o >= 0 && o + 3 < dstLength) {
|
|
80
|
+
const group: i32 = (toI32(src[i]) << 16) | (toI32(src[i + 1]) << 8) | toI32(src[i + 2])
|
|
81
|
+
dst[o] = base64CharOf(group >> 18)
|
|
82
|
+
dst[o + 1] = base64CharOf((group >> 12) & 63)
|
|
83
|
+
dst[o + 2] = base64CharOf((group >> 6) & 63)
|
|
84
|
+
dst[o + 3] = base64CharOf(group & 63)
|
|
85
|
+
i = i + 3
|
|
86
|
+
o = o + 4
|
|
87
|
+
}
|
|
88
|
+
const tail: i32 = len % 3
|
|
89
|
+
if (tail === 1) {
|
|
90
|
+
const group: i32 = toI32(src[i]) << 16
|
|
91
|
+
dst[o] = base64CharOf(group >> 18)
|
|
92
|
+
dst[o + 1] = base64CharOf((group >> 12) & 63)
|
|
93
|
+
dst[o + 2] = toU8(BASE64_PAD)
|
|
94
|
+
dst[o + 3] = toU8(BASE64_PAD)
|
|
95
|
+
o = o + 4
|
|
96
|
+
} else if (tail === 2) {
|
|
97
|
+
const group: i32 = (toI32(src[i]) << 16) | (toI32(src[i + 1]) << 8)
|
|
98
|
+
dst[o] = base64CharOf(group >> 18)
|
|
99
|
+
dst[o + 1] = base64CharOf((group >> 12) & 63)
|
|
100
|
+
dst[o + 2] = base64CharOf((group >> 6) & 63)
|
|
101
|
+
dst[o + 3] = toU8(BASE64_PAD)
|
|
102
|
+
o = o + 4
|
|
103
|
+
}
|
|
104
|
+
return o
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** `bytes` as padded base64. Panics past 1,610,612,733 bytes, whose encoding would not fit a string. */
|
|
108
|
+
export const base64Encode = (bytes: u8[]): string => {
|
|
109
|
+
if (bytes.length > 2147483647) {
|
|
110
|
+
panic("base64Encode: more than 2^31 - 1 bytes")
|
|
111
|
+
}
|
|
112
|
+
const n: i32 = toI32(bytes.length)
|
|
113
|
+
const out: u8[] = new Array<u8>(base64EncodedLength(n))
|
|
114
|
+
const end: i32 = base64EncodeInto(bytes, 0, n, out, 0)
|
|
115
|
+
return String.fromBytes(out, 0, end)
|
|
116
|
+
}
|
package/std/bytes.ts
CHANGED
|
@@ -48,6 +48,22 @@
|
|
|
48
48
|
* the prelude saturates too (`setI32LE(b, 0, -2)` writes zeros), and the
|
|
49
49
|
* 64-bit functions and `f64` meet its BigInt `u64` and `i64`, which `toU8`
|
|
50
50
|
* refuses.
|
|
51
|
+
*
|
|
52
|
+
* The module also has the runs a parser walks and the copy it makes of them.
|
|
53
|
+
* `skipBlank`, `skipDigits`, `skipJsonText` and `skipJsonPlain` each answer
|
|
54
|
+
* the first index in a range whose byte is not in their class — JSON's blanks,
|
|
55
|
+
* the ASCII digits, the bytes a JSON string holds as themselves, the bytes a
|
|
56
|
+
* JSON writer copies without an escape — and `copyBytes` copies a
|
|
57
|
+
* window of one array into another, or into itself, as one move:
|
|
58
|
+
*
|
|
59
|
+
* const text: u8[] = [32, 32, 52, 50, 44]; // " 42,"
|
|
60
|
+
* const start = skipBlank(text, 0, 5); // 2
|
|
61
|
+
* const end = skipDigits(text, start, 5); // 4
|
|
62
|
+
*
|
|
63
|
+
* The four runs clamp their range and never panic; `copyBytes` panics on a
|
|
64
|
+
* range outside its array. Natively each is a runtime kernel
|
|
65
|
+
* (docs/LANGUAGE.md, "Byte runs"), and the runs are variable-time: they are
|
|
66
|
+
* for text, never for secret data.
|
|
51
67
|
*/
|
|
52
68
|
|
|
53
69
|
/** The `u16` whose low byte is `b[off]` and high byte `b[off + 1]`. */
|
|
@@ -161,3 +177,243 @@ export const setF64LE = (b: u8[], off: i32, v: f64): void => setU64LE(b, off, to
|
|
|
161
177
|
|
|
162
178
|
/** Writes `v`'s IEEE 754 bits as `setU64BE` does. */
|
|
163
179
|
export const setF64BE = (b: u8[], off: i32, v: f64): void => setU64BE(b, off, toU64(f64ToBits(v)))
|
|
180
|
+
|
|
181
|
+
// ---- Byte-class runs and the byte copy (WP38 S6a) ----------------------------------------
|
|
182
|
+
|
|
183
|
+
/** JSON's four blanks (RFC 8259 §2): space, tab, newline and carriage return. */
|
|
184
|
+
const BYTES_SPACE: i32 = 32
|
|
185
|
+
const BYTES_TAB: i32 = 9
|
|
186
|
+
const BYTES_NEWLINE: i32 = 10
|
|
187
|
+
const BYTES_CARRIAGE_RETURN: i32 = 13
|
|
188
|
+
/** `0` and `9`. */
|
|
189
|
+
const BYTES_DIGIT_ZERO: i32 = 48
|
|
190
|
+
const BYTES_DIGIT_NINE: i32 = 57
|
|
191
|
+
/** The two printable ASCII bytes a JSON string escapes, `"` and `\`. */
|
|
192
|
+
const BYTES_QUOTE: i32 = 34
|
|
193
|
+
const BYTES_BACKSLASH: i32 = 92
|
|
194
|
+
/** The first byte that is not ASCII, and the continuation bytes' range. */
|
|
195
|
+
const BYTES_NON_ASCII: i32 = 128
|
|
196
|
+
const BYTES_CONTINUATION_TOP: i32 = 191
|
|
197
|
+
|
|
198
|
+
/** `to` clamped into `[0, b.length]`: where a run's scan stops. */
|
|
199
|
+
const bytesRunEnd = (b: u8[], to: i32): i32 => {
|
|
200
|
+
const n: i32 = toI32(b.length)
|
|
201
|
+
if (to < 0) {
|
|
202
|
+
return 0
|
|
203
|
+
}
|
|
204
|
+
return to < n ? to : n
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** `from` clamped into `[0, end]`: where a run's scan starts. */
|
|
208
|
+
const bytesRunStart = (from: i32, end: i32): i32 => {
|
|
209
|
+
if (from < 0) {
|
|
210
|
+
return 0
|
|
211
|
+
}
|
|
212
|
+
return from < end ? from : end
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* The first index in `[from, to)` whose byte is not one of JSON's blanks —
|
|
217
|
+
* space, tab, newline, carriage return — or `to` when every byte there is
|
|
218
|
+
* one. `to` is clamped to `[0, b.length]` and `from` to `[0, to]`, as
|
|
219
|
+
* `indexOfAny` clamps, so no range panics: one past the end answers the end.
|
|
220
|
+
*
|
|
221
|
+
* Natively the scan is the runtime's `nish_bytes_skip_blank`, which reads 16
|
|
222
|
+
* or 32 bytes at a time (docs/LANGUAGE.md, "Byte runs"). It is
|
|
223
|
+
* **variable-time**: how long it takes says how long the run is, so it is
|
|
224
|
+
* never for secret data.
|
|
225
|
+
*/
|
|
226
|
+
export const skipBlank = (b: u8[], from: i32, to: i32): i32 => {
|
|
227
|
+
const end: i32 = bytesRunEnd(b, to)
|
|
228
|
+
return skipBlankWalk(b, bytesRunStart(from, end), end)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** `skipBlank` for the ASCII digits `0` to `9`: the same clamps, the same kernel family, `nish_bytes_skip_digits`. */
|
|
232
|
+
export const skipDigits = (b: u8[], from: i32, to: i32): i32 => {
|
|
233
|
+
const end: i32 = bytesRunEnd(b, to)
|
|
234
|
+
return skipDigitsWalk(b, bytesRunStart(from, end), end)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* `skipBlank` for the bytes a JSON string holds as themselves: printable ASCII
|
|
239
|
+
* other than `"` and `\`, and every well-formed UTF-8 sequence (RFC 3629 §4)
|
|
240
|
+
* that ends before `to`. The run stops at the first quote, backslash or
|
|
241
|
+
* control byte, and at the lead byte of a sequence that is malformed — an
|
|
242
|
+
* overlong form, a surrogate, a code point past U+10FFFF, a stray continuation
|
|
243
|
+
* byte — or cut short by `to`. So a whole JSON string body with no escape is
|
|
244
|
+
* one call, and where it stops is the byte a decoder has to look at. Natively
|
|
245
|
+
* the scan is `nish_bytes_skip_json_text`: blocks of ASCII 16 or 32 bytes at a
|
|
246
|
+
* time, each multi-byte sequence a byte at a time. Variable-time, as
|
|
247
|
+
* `skipBlank` is.
|
|
248
|
+
*/
|
|
249
|
+
export const skipJsonText = (b: u8[], from: i32, to: i32): i32 => {
|
|
250
|
+
const end: i32 = bytesRunEnd(b, to)
|
|
251
|
+
return skipJsonTextWalk(b, bytesRunStart(from, end), end)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* The first index in `[from, to)` whose byte a JSON writer cannot copy as
|
|
256
|
+
* itself — a control byte below `0x20`, a quote or a backslash — or `to`, with
|
|
257
|
+
* the range clamped as `skipBlank`'s is. Every byte from `0x20` up is in the
|
|
258
|
+
* run, `0x7f` and the bytes from `0x80` up included, and no UTF-8 is checked:
|
|
259
|
+
* where `skipJsonText` is the reader's run, this is the writer's, which passes
|
|
260
|
+
* the bytes of a string through as they are. Natively the scan is
|
|
261
|
+
* `nish_bytes_skip_json_plain`, 16 bytes at a time. Variable-time, as
|
|
262
|
+
* `skipBlank` is.
|
|
263
|
+
*/
|
|
264
|
+
export const skipJsonPlain = (b: u8[], from: i32, to: i32): i32 => {
|
|
265
|
+
const end: i32 = bytesRunEnd(b, to)
|
|
266
|
+
return skipJsonPlainWalk(b, bytesRunStart(from, end), end)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* `count` bytes of `src` from `from` written over `dst` from `at`, as one
|
|
271
|
+
* copy: where `dst` and `src` are one array and the two ranges overlap, the
|
|
272
|
+
* bytes land as if every one were read before any was written, as
|
|
273
|
+
* `copyWithin` promises. Where `dst.set(src, at)` copies all of `src`, this
|
|
274
|
+
* copies a window of it, and allocates nothing.
|
|
275
|
+
*
|
|
276
|
+
* A negative `count`, or a range that does not lie inside its array —
|
|
277
|
+
* `0 <= from <= from + count <= src.length` and the same for `at` in `dst` —
|
|
278
|
+
* panics `copyBytes` before any byte is written. Natively the copy is one
|
|
279
|
+
* `llvm.memmove`.
|
|
280
|
+
*/
|
|
281
|
+
export const copyBytes = (dst: u8[], at: i32, src: u8[], from: i32, count: i32): void => {
|
|
282
|
+
if (count < 0 || at < 0 || from < 0 || at > toI32(dst.length) - count || from > toI32(src.length) - count) {
|
|
283
|
+
panic("copyBytes: the range lies outside an array")
|
|
284
|
+
}
|
|
285
|
+
copyBytesWalk(dst, at, src, from, count)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* `skipBlank`'s walk, once its range is clamped. This loop and the four
|
|
290
|
+
* below are what run under Node; natively the compiler replaces the one call
|
|
291
|
+
* each public function makes with the runtime kernel (`src/kernels.ts`), as it
|
|
292
|
+
* does `indexOfAny`'s walk, so each loop is its kernel's meaning written in
|
|
293
|
+
* the language. The `i < b.length` test never fails after the clamp; it is
|
|
294
|
+
* written because it is what proves the index.
|
|
295
|
+
*/
|
|
296
|
+
const skipBlankWalk = (b: u8[], from: i32, to: i32): i32 => {
|
|
297
|
+
let i: i32 = from
|
|
298
|
+
while (i >= 0 && i < to && i < toI32(b.length)) {
|
|
299
|
+
const c: i32 = toI32(b[i])
|
|
300
|
+
if (c !== BYTES_SPACE && c !== BYTES_TAB && c !== BYTES_NEWLINE && c !== BYTES_CARRIAGE_RETURN) {
|
|
301
|
+
return i
|
|
302
|
+
}
|
|
303
|
+
i += 1
|
|
304
|
+
}
|
|
305
|
+
return i
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** `skipDigits`'s walk, once its range is clamped. */
|
|
309
|
+
const skipDigitsWalk = (b: u8[], from: i32, to: i32): i32 => {
|
|
310
|
+
let i: i32 = from
|
|
311
|
+
while (i >= 0 && i < to && i < toI32(b.length)) {
|
|
312
|
+
const c: i32 = toI32(b[i])
|
|
313
|
+
if (c < BYTES_DIGIT_ZERO || c > BYTES_DIGIT_NINE) {
|
|
314
|
+
return i
|
|
315
|
+
}
|
|
316
|
+
i += 1
|
|
317
|
+
}
|
|
318
|
+
return i
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* The length of the well-formed UTF-8 sequence whose lead byte is `b[i]` and
|
|
323
|
+
* that ends at or before `to`, or 0: RFC 3629's table, with the narrowed
|
|
324
|
+
* second byte after `E0`, `ED`, `F0` and `F4`.
|
|
325
|
+
*/
|
|
326
|
+
const bytesUtf8Length = (b: u8[], i: i32, to: i32): i32 => {
|
|
327
|
+
const lead: i32 = toI32(b[i])
|
|
328
|
+
let low: i32 = BYTES_NON_ASCII
|
|
329
|
+
let high: i32 = BYTES_CONTINUATION_TOP
|
|
330
|
+
let n: i32 = 0
|
|
331
|
+
if (lead >= 0xc2 && lead <= 0xdf) {
|
|
332
|
+
n = 2
|
|
333
|
+
} else if (lead >= 0xe0 && lead <= 0xef) {
|
|
334
|
+
n = 3
|
|
335
|
+
if (lead === 0xe0) {
|
|
336
|
+
low = 0xa0
|
|
337
|
+
}
|
|
338
|
+
if (lead === 0xed) {
|
|
339
|
+
high = 0x9f
|
|
340
|
+
}
|
|
341
|
+
} else if (lead >= 0xf0 && lead <= 0xf4) {
|
|
342
|
+
n = 4
|
|
343
|
+
if (lead === 0xf0) {
|
|
344
|
+
low = 0x90
|
|
345
|
+
}
|
|
346
|
+
if (lead === 0xf4) {
|
|
347
|
+
high = 0x8f
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
return 0
|
|
351
|
+
}
|
|
352
|
+
if (to - i < n) {
|
|
353
|
+
return 0
|
|
354
|
+
}
|
|
355
|
+
const second: i32 = toI32(b[i + 1])
|
|
356
|
+
if (second < low || second > high) {
|
|
357
|
+
return 0
|
|
358
|
+
}
|
|
359
|
+
let k: i32 = 2
|
|
360
|
+
while (k < n) {
|
|
361
|
+
const c: i32 = toI32(b[i + k])
|
|
362
|
+
if (c < BYTES_NON_ASCII || c > BYTES_CONTINUATION_TOP) {
|
|
363
|
+
return 0
|
|
364
|
+
}
|
|
365
|
+
k += 1
|
|
366
|
+
}
|
|
367
|
+
return n
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** `skipJsonText`'s walk, once its range is clamped. */
|
|
371
|
+
const skipJsonTextWalk = (b: u8[], from: i32, to: i32): i32 => {
|
|
372
|
+
let i: i32 = from
|
|
373
|
+
while (i >= 0 && i < to && i < toI32(b.length)) {
|
|
374
|
+
const c: i32 = toI32(b[i])
|
|
375
|
+
if (c >= BYTES_NON_ASCII) {
|
|
376
|
+
const n: i32 = bytesUtf8Length(b, i, to)
|
|
377
|
+
if (n === 0) {
|
|
378
|
+
return i
|
|
379
|
+
}
|
|
380
|
+
i += n
|
|
381
|
+
continue
|
|
382
|
+
}
|
|
383
|
+
if (c < BYTES_SPACE || c === BYTES_QUOTE || c === BYTES_BACKSLASH) {
|
|
384
|
+
return i
|
|
385
|
+
}
|
|
386
|
+
i += 1
|
|
387
|
+
}
|
|
388
|
+
return i
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** `skipJsonPlain`'s walk, once its range is clamped. */
|
|
392
|
+
const skipJsonPlainWalk = (b: u8[], from: i32, to: i32): i32 => {
|
|
393
|
+
let i: i32 = from
|
|
394
|
+
while (i >= 0 && i < to && i < toI32(b.length)) {
|
|
395
|
+
const c: i32 = toI32(b[i])
|
|
396
|
+
if (c < BYTES_SPACE || c === BYTES_QUOTE || c === BYTES_BACKSLASH) {
|
|
397
|
+
return i
|
|
398
|
+
}
|
|
399
|
+
i += 1
|
|
400
|
+
}
|
|
401
|
+
return i
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** `copyBytes`'s walk, once its ranges are checked: forward, or backward when `dst` is `src` and the bytes move up. */
|
|
405
|
+
const copyBytesWalk = (dst: u8[], at: i32, src: u8[], from: i32, count: i32): void => {
|
|
406
|
+
if (at <= from) {
|
|
407
|
+
let k: i32 = 0
|
|
408
|
+
while (k < count) {
|
|
409
|
+
dst[at + k] = src[from + k]
|
|
410
|
+
k += 1
|
|
411
|
+
}
|
|
412
|
+
return
|
|
413
|
+
}
|
|
414
|
+
let k: i32 = count - 1
|
|
415
|
+
while (k >= 0) {
|
|
416
|
+
dst[at + k] = src[from + k]
|
|
417
|
+
k -= 1
|
|
418
|
+
}
|
|
419
|
+
}
|