@amritk/nish-x86_64-linux 0.21.0 → 0.22.1
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 +30 -0
- package/bin/nish +0 -0
- package/docs/AI.md +18 -0
- package/package.json +1 -1
- package/runtime/browser.d.ts +1 -9
- package/runtime/browser.mjs +2 -13
- package/runtime/core/arrays.d.ts +26 -8
- package/runtime/core/arrays.mjs +31 -3
- package/runtime/core/host.d.ts +10 -0
- package/runtime/core/host.mjs +14 -0
- package/runtime/core/numbers.d.ts +14 -1
- package/runtime/core/numbers.mjs +30 -3
- package/runtime/core/result.d.ts +4 -0
- package/runtime/core/result.mjs +13 -0
- package/runtime/core/strings.d.ts +2 -2
- package/runtime/nish.d.ts +2 -2
- package/runtime/node.mjs +3 -0
- package/runtime/runtime-net.c +12 -1
- package/scripts/build.sh +58 -14
- package/std/README.md +2 -2
- package/std/cli.ts +17 -1
- package/std/crypto/bcrypt.ts +869 -72
- package/std/json.ts +4 -7
- package/std/net/http1-server.ts +199 -34
- package/std/net/http1.ts +40 -21
- package/std/net/postgres.ts +471 -52
- package/std/net/prefork.ts +23 -3
- package/std/random.ts +66 -4
package/INSTALL.md
CHANGED
|
@@ -143,6 +143,36 @@ for — which catches a truncated download, a tarball built for another
|
|
|
143
143
|
architecture, and an asset whose name does not match what is inside it — and
|
|
144
144
|
only then swapped in. If any of that fails the install you had is untouched.
|
|
145
145
|
|
|
146
|
+
**Every download is checked before it is unpacked.** install.sh compares the
|
|
147
|
+
tarball's SHA-256 with the release's `SHA256SUMS` (or with the digest pinned
|
|
148
|
+
for releases that predate it) and refuses a mismatch. For releases after
|
|
149
|
+
0.21.0 it also checks the release's **build provenance attestation** when a
|
|
150
|
+
signed-in GitHub CLI is present whose `gh attestation verify` takes the
|
|
151
|
+
options below. The attestation
|
|
152
|
+
is a Sigstore-signed statement, made by `actions/attest-build-provenance` in
|
|
153
|
+
the release workflow, that every file in `SHA256SUMS` was built by
|
|
154
|
+
`.github/workflows/release.yml` at the release's tag on a GitHub-hosted
|
|
155
|
+
runner. A failed verification refuses the install; with no `gh`, a signed-out
|
|
156
|
+
one or one too old for those options, the check is skipped with a note saying so, and the
|
|
157
|
+
SHA-256 still applies. The same command by hand, on a tarball you downloaded:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
gh attestation verify nish-<version>-<asset>.tar.gz \
|
|
161
|
+
--repo amritk/nish \
|
|
162
|
+
--signer-workflow amritk/nish/.github/workflows/release.yml \
|
|
163
|
+
--source-ref refs/tags/v<version> \
|
|
164
|
+
--deny-self-hosted-runners
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
It works on any file the release attaches, the npm tarballs included. Each
|
|
168
|
+
release also attaches `nish-<version>.spdx.json`, an SPDX 2.3 software bill of
|
|
169
|
+
materials: every attached file with its SHA-256, the clang, linker, Node.js
|
|
170
|
+
and seed compiler that built each tarball (recorded by the build row itself),
|
|
171
|
+
and the vendored code from
|
|
172
|
+
[THIRD_PARTY_NOTICES.md](../THIRD_PARTY_NOTICES.md) that each of them contains.
|
|
173
|
+
How these fit the rest of the release's defences is
|
|
174
|
+
[docs/security/supply-chain.md](security/supply-chain.md).
|
|
175
|
+
|
|
146
176
|
The npm route installs the **native** compiler, and it is a download rather than a
|
|
147
177
|
build: nothing is compiled on your machine. The package declares one
|
|
148
178
|
`nish-<os>-<arch>` package per supported platform as an `optionalDependencies`
|
package/bin/nish
CHANGED
|
Binary file
|
package/docs/AI.md
CHANGED
|
@@ -93,6 +93,24 @@ compiles and the warning would be printed: `NL9007`'s inserts the guard that
|
|
|
93
93
|
proves an index in range (not under `--no-warn-performance`), and `NL7002`'s
|
|
94
94
|
migrates a site off `--unchecked-indexing` (below).
|
|
95
95
|
|
|
96
|
+
When it compiles, **check the native build against the program's TypeScript
|
|
97
|
+
reading**. Run the project's tests natively; where it has none, write the
|
|
98
|
+
program back out and run both:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
nish program.ts --emit-ts out/ # the program as TypeScript, plus out/entry.ts
|
|
102
|
+
node out/entry.ts # Node 22.18 or newer; compare with the binary
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The two must print the same and exit the same, a panic included: `--emit-ts`
|
|
106
|
+
keeps the overflow and bounds checks the native build makes, but not a ranged
|
|
107
|
+
integer's range (`integer<0, 255>` is a plain `number`). Where they differ,
|
|
108
|
+
the difference is a row of [the user guide's table](guide/using-nish/to-typescript.md),
|
|
109
|
+
or a bug in `nish` worth reporting. A program on `nish:net`, the signal
|
|
110
|
+
descriptor or a C declaration (NL4010, NL4011), a compound assignment whose
|
|
111
|
+
index would run twice (NL4012) or a generic whose element type decides its
|
|
112
|
+
code (NL4013, NL4014) is refused and has no TypeScript to run.
|
|
113
|
+
|
|
96
114
|
Two more surfaces worth knowing: `nish --emit-ast f.ts` prints what was parsed
|
|
97
115
|
and `nish --emit-checked f.ts` prints the side tables the emitter reads. Both
|
|
98
116
|
are one node per line, which is to say diffable.
|
package/package.json
CHANGED
package/runtime/browser.d.ts
CHANGED
|
@@ -3,12 +3,4 @@
|
|
|
3
3
|
* in `nish/runtime` (docs/guide/using-nish/to-typescript.md).
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
* What the program's `exit(code)`, and every panic, throws: a page cannot end
|
|
8
|
-
* itself. Tell it apart with `instanceof`, not by its `name`.
|
|
9
|
-
*/
|
|
10
|
-
export class NishExit extends Error {
|
|
11
|
-
/** The 8-bit status a native exit would leave: `code & 0xff`. */
|
|
12
|
-
readonly code: number;
|
|
13
|
-
constructor(code: number);
|
|
14
|
-
}
|
|
6
|
+
export { NishExit } from "./core/host.js";
|
package/runtime/browser.mjs
CHANGED
|
@@ -16,20 +16,9 @@
|
|
|
16
16
|
* - Every file-system, process and machine call panics, naming itself and
|
|
17
17
|
* the browser.
|
|
18
18
|
*/
|
|
19
|
-
import { installHost, panic } from "./core/host.mjs";
|
|
19
|
+
import { installHost, NishExit, panic } from "./core/host.mjs";
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
* What `exit(code)` throws: `code` is the 8-bit status a native exit would
|
|
23
|
-
* leave. Exported so a caller tells it apart with `instanceof`, which a thrown
|
|
24
|
-
* object that only carries the name does not pass.
|
|
25
|
-
*/
|
|
26
|
-
export class NishExit extends Error {
|
|
27
|
-
constructor(code) {
|
|
28
|
-
super(`the program exited with ${code}`);
|
|
29
|
-
this.name = "NishExit";
|
|
30
|
-
this.code = code & 0xff;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
21
|
+
export { NishExit };
|
|
33
22
|
|
|
34
23
|
/**
|
|
35
24
|
* One console stream, buffered to the next newline. The `"%s"` keeps a `%d`
|
package/runtime/core/arrays.d.ts
CHANGED
|
@@ -1,18 +1,36 @@
|
|
|
1
1
|
/** The array half of the core: bounds-checked access, and the arena stubs. */
|
|
2
|
-
import type { i32 } from "./numbers.js";
|
|
2
|
+
import type { i32, i64 } from "./numbers.js";
|
|
3
3
|
export declare function idx<T>(a: readonly T[], i: number | bigint): T;
|
|
4
4
|
export declare function pop<T>(a: T[]): T;
|
|
5
5
|
export declare function setIdx<T>(a: T[], i: number | bigint, v: T): T;
|
|
6
6
|
export declare function updIdx<T>(a: T[], i: number | bigint, f: (old: T) => T): T;
|
|
7
|
-
export declare function uncheckedGet<T extends number>(xs: readonly T[], i: i32): T;
|
|
8
|
-
export declare function uncheckedSet<T extends number>(xs: T[], i: i32, v: T): void;
|
|
7
|
+
export declare function uncheckedGet<T extends number | bigint>(xs: readonly T[], i: i32): T;
|
|
8
|
+
export declare function uncheckedSet<T extends number | bigint>(xs: T[], i: i32, v: T): void;
|
|
9
9
|
export declare function copyIn<T>(v: T): T;
|
|
10
10
|
export declare function storeIdx<T>(a: T[], i: number | bigint, v: T): T;
|
|
11
11
|
export declare function arraySet<T>(dst: T[], src: readonly T[], offset?: number): void;
|
|
12
|
-
|
|
13
|
-
export declare function
|
|
14
|
-
|
|
12
|
+
/** `dst.set(src, offset)` as `--emit-ts` writes it: a 64-bit offset is a bigint, checked exactly. */
|
|
13
|
+
export declare function arraySetAt<T>(dst: T[], src: readonly T[], offset?: number | bigint): void;
|
|
14
|
+
/** `new Array<T>(n)`: `--emit-ts` writes `T` out, so the zero it fills with is not what `T` is read from. */
|
|
15
|
+
export declare function newArray<T>(n: number | bigint, zero: unknown): T[];
|
|
16
|
+
export declare function arenaUsed(): i64;
|
|
17
|
+
export declare function arenaMark(): i64;
|
|
15
18
|
export declare function arenaRelease(m?: unknown): void;
|
|
16
19
|
export declare function arenaReset(): void;
|
|
17
|
-
/**
|
|
18
|
-
|
|
20
|
+
/**
|
|
21
|
+
* The protocol `using` reads, declared here as `nish.d.ts` does, so `using a =
|
|
22
|
+
* arena()` type-checks with no `"esnext.disposable"` in a program's `lib`.
|
|
23
|
+
*/
|
|
24
|
+
declare global {
|
|
25
|
+
interface SymbolConstructor {
|
|
26
|
+
readonly dispose: unique symbol;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** What `arena()` answers: its `Symbol.dispose` does nothing. */
|
|
31
|
+
export interface ArenaHandle {
|
|
32
|
+
[Symbol.dispose](): void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** `using a = arena()`: the arena is the collector's, so leaving it frees nothing. */
|
|
36
|
+
export declare function arena(): ArenaHandle;
|
package/runtime/core/arrays.mjs
CHANGED
|
@@ -79,6 +79,22 @@ export function storeIdx(a, i, v) {
|
|
|
79
79
|
return v;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* `dst.set(src, offset)` as `nish --emit-ts` writes it: an `i64` or `u64`
|
|
84
|
+
* offset, a bigint here, is compared as one, so a value past 2^53 fails with
|
|
85
|
+
* the native panic rather than being rounded into range, and one in range is
|
|
86
|
+
* the number `arraySet` takes. `runtime/nish.mjs` keeps `arraySet`, whose
|
|
87
|
+
* bigint offset throws as `docs/LANGUAGE.md` says.
|
|
88
|
+
*/
|
|
89
|
+
export function arraySetAt(dst, src, offset) {
|
|
90
|
+
if (typeof offset === "bigint") {
|
|
91
|
+
const end = offset + BigInt(src.length);
|
|
92
|
+
if (offset < 0n || end > BigInt(dst.length)) panicSlice(offset, end, dst.length);
|
|
93
|
+
offset = Number(offset);
|
|
94
|
+
}
|
|
95
|
+
arraySet(dst, src, offset);
|
|
96
|
+
}
|
|
97
|
+
|
|
82
98
|
/**
|
|
83
99
|
* `dst.set(src, offset)` (WP34 N2): `TypedArray.prototype.set`'s copy on the
|
|
84
100
|
* plain array a `u8[]` is here. The source is copied first, so a self-copy or
|
|
@@ -94,13 +110,25 @@ export function arraySet(dst, src, offset) {
|
|
|
94
110
|
// to the nearest double past 2^53 (docs/RUN_UNDER_NODE.md).
|
|
95
111
|
const at = offset === undefined ? 0 : Math.trunc(offset) || 0;
|
|
96
112
|
const end = at + src.length;
|
|
97
|
-
if (!(at >= 0 && end <= dst.length)) panicSlice(at,
|
|
113
|
+
if (!(at >= 0 && end <= dst.length)) panicSlice(...saturatedRange(at, src.length), dst.length);
|
|
98
114
|
// Two plain arrays overlap only when they are one array, which is the one
|
|
99
115
|
// case that must read the source before writing it.
|
|
100
116
|
const from = src === dst ? src.slice() : src;
|
|
101
117
|
for (let i = 0; i < from.length; i++) dst[at + i] = from[i];
|
|
102
118
|
}
|
|
103
119
|
|
|
120
|
+
/**
|
|
121
|
+
* `[at, at + n)` as the native panic spells it: the offset saturated into an
|
|
122
|
+
* `i64`, as `llvm.fptosi.sat` leaves it, and the end wrapped, so `1e300` is
|
|
123
|
+
* `9223372036854775807` and its end `-9223372036854775808`.
|
|
124
|
+
*/
|
|
125
|
+
function saturatedRange(at, n) {
|
|
126
|
+
if (Number.isSafeInteger(at + n)) return [at, at + n];
|
|
127
|
+
const max = 2n ** 63n - 1n;
|
|
128
|
+
const start = at === Infinity || at >= 2 ** 63 ? max : at <= -(2 ** 63) ? -max - 1n : BigInt(at);
|
|
129
|
+
return [start, BigInt.asIntN(64, start + BigInt(n))];
|
|
130
|
+
}
|
|
131
|
+
|
|
104
132
|
/** `new Array<T>(n)`: `n` zero-filled elements (`0`, `0n`, or `false`). */
|
|
105
133
|
export function newArray(n, zero) {
|
|
106
134
|
return new Array(toIndex(n)).fill(zero);
|
|
@@ -115,8 +143,8 @@ export function newArray(n, zero) {
|
|
|
115
143
|
// so "the arena of the calling thread" and "the arena" are the same object, and
|
|
116
144
|
// these stubs answer for both. If T1 ever lands a spawn the rewrite can reach,
|
|
117
145
|
// that is when this file grows a second arena to keep count of.
|
|
118
|
-
export function arenaUsed() { return
|
|
119
|
-
export function arenaMark() { return
|
|
146
|
+
export function arenaUsed() { return 0n; }
|
|
147
|
+
export function arenaMark() { return 0n; }
|
|
120
148
|
export function arenaRelease() {}
|
|
121
149
|
export function arenaReset() {}
|
|
122
150
|
/** `using a = arena()`: a disposable whose disposal does nothing, because there is no arena to release. */
|
package/runtime/core/host.d.ts
CHANGED
|
@@ -20,6 +20,16 @@ export declare class NishNoHost extends Error {
|
|
|
20
20
|
readonly requested: string;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* What the browser host's `exit(code)`, and every panic there, throws: a page
|
|
25
|
+
* cannot end itself. Tell it apart with `instanceof`, not by its `name`.
|
|
26
|
+
*/
|
|
27
|
+
export declare class NishExit extends Error {
|
|
28
|
+
/** The 8-bit status a native exit would leave: `code & 0xff`. */
|
|
29
|
+
readonly code: number;
|
|
30
|
+
constructor(code: number);
|
|
31
|
+
}
|
|
32
|
+
|
|
23
33
|
export declare function installHost(next: Host): void;
|
|
24
34
|
export declare function log(x: unknown): void;
|
|
25
35
|
export declare function error(x: unknown): void;
|
package/runtime/core/host.mjs
CHANGED
|
@@ -48,6 +48,20 @@ const NO_HOST = {
|
|
|
48
48
|
|
|
49
49
|
let host = NO_HOST;
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* What `exit(code)` throws: `code` is the 8-bit status a native exit would
|
|
53
|
+
* leave. The browser host throws it, since a page cannot end itself; it is
|
|
54
|
+
* exported here and from `nish/runtime/browser` so a caller tells it apart with `instanceof`, which a thrown
|
|
55
|
+
* object that only carries the name does not pass.
|
|
56
|
+
*/
|
|
57
|
+
export class NishExit extends Error {
|
|
58
|
+
constructor(code) {
|
|
59
|
+
super(`the program exited with ${code}`);
|
|
60
|
+
this.name = "NishExit";
|
|
61
|
+
this.code = code & 0xff;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
51
65
|
/** Make `next` the host every later call goes through. */
|
|
52
66
|
export function installHost(next) {
|
|
53
67
|
host = next;
|
|
@@ -12,6 +12,16 @@ export type u32 = number;
|
|
|
12
12
|
export type u64 = bigint;
|
|
13
13
|
export type f32 = number;
|
|
14
14
|
export type f64 = number;
|
|
15
|
+
/**
|
|
16
|
+
* A ranged integer (docs/wp31-ranged-integers.md) is a `number`; its range is
|
|
17
|
+
* the native build's check, which Node does not make (wp33 §3.3, the range row).
|
|
18
|
+
*/
|
|
19
|
+
export type integer<Lo extends number, Hi extends number> = number;
|
|
20
|
+
/** The typed-array aliases (docs/LANGUAGE.md): in Nish each is an ordinary array of its width. */
|
|
21
|
+
export type Int32Array = i32[];
|
|
22
|
+
export type Float32Array = f32[];
|
|
23
|
+
export type Float64Array = f64[];
|
|
24
|
+
export type BigInt64Array = i64[];
|
|
15
25
|
|
|
16
26
|
/** A width as the rewriter names it, for `convert`. */
|
|
17
27
|
export type NumericKind = "i32" | "i64" | "u8" | "u16" | "u32" | "u64" | "f32" | "f64";
|
|
@@ -46,7 +56,10 @@ export declare function shlU64(a: u64, b: u64): u64;
|
|
|
46
56
|
export declare function lshrU64(a: u64, b: u64): u64;
|
|
47
57
|
export declare function minU64(a: u64, b: u64): u64;
|
|
48
58
|
export declare function maxU64(a: u64, b: u64): u64;
|
|
49
|
-
|
|
59
|
+
/** What `convert` answers for the width `K`: a `bigint` for the 64-bit integers. */
|
|
60
|
+
export type Width<K extends NumericKind> = K extends "i64" | "u64" ? bigint : number;
|
|
61
|
+
|
|
62
|
+
export declare function convert<K extends NumericKind>(x: number | bigint, from: NumericKind, to: K): Width<K>;
|
|
50
63
|
export declare function parseInt(s: string): i32;
|
|
51
64
|
export declare function parseFloat(s: string): f64;
|
|
52
65
|
export declare function number(x: string | number | bigint | boolean): f64;
|
package/runtime/core/numbers.mjs
CHANGED
|
@@ -248,9 +248,36 @@ export function maxU64(a, b) {
|
|
|
248
248
|
* what `sext`/`zext`/`trunc` do natively.
|
|
249
249
|
*/
|
|
250
250
|
export function convert(x, from, to) {
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
|
|
251
|
+
// The hot paths of u64 arithmetic written over i64 (`toU64(a) * toU64(b)`)
|
|
252
|
+
// stay in a function small enough for V8 to inline at the call: the two
|
|
253
|
+
// 64-bit kinds reinterpret each other's bits, and a zext of an unsigned
|
|
254
|
+
// 32-bit-or-narrower value to u64 keeps the value as it is.
|
|
255
|
+
if (typeof x === "bigint") {
|
|
256
|
+
if (to === "u64") return BigInt.asUintN(64, x);
|
|
257
|
+
if (to === "i64") return BigInt.asIntN(64, x);
|
|
258
|
+
} else if (to === "u64" && (from === "u32" || from === "u16" || from === "u8")) {
|
|
259
|
+
return BigInt(x);
|
|
260
|
+
}
|
|
261
|
+
return convertOther(x, from, to);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Every conversion `convert` does not answer inline. */
|
|
265
|
+
function convertOther(x, from, to) {
|
|
266
|
+
if (typeof x === "bigint") {
|
|
267
|
+
// A BigInt source narrowed to 32 bits or less skips the `number` round trip.
|
|
268
|
+
const narrow = INT_KINDS[to];
|
|
269
|
+
if (narrow !== undefined) {
|
|
270
|
+
return Number(narrow.signed ? BigInt.asIntN(narrow.bits, x) : BigInt.asUintN(narrow.bits, x));
|
|
271
|
+
}
|
|
272
|
+
} else if (from === "i32" || from === "u32" || from === "u16" || from === "u8") {
|
|
273
|
+
// An integer `number` source narrowed to a `number` kind: the 32-bit wrap
|
|
274
|
+
// `|0` / `>>>0` and a mask do what trunc/sext/zext do, without a BigInt.
|
|
275
|
+
if (to === "i32") return x | 0;
|
|
276
|
+
if (to === "u32") return x >>> 0;
|
|
277
|
+
if (to === "u16") return x & 0xffff;
|
|
278
|
+
if (to === "u8") return x & 0xff;
|
|
279
|
+
if (to === "i64") return BigInt(x);
|
|
280
|
+
}
|
|
254
281
|
const num = typeof x === "bigint" ? Number(x) : x;
|
|
255
282
|
if (to === "f64") return num; // sitofp / uitofp / fpext
|
|
256
283
|
if (to === "f32") return Math.fround(num); // ... / fptrunc, then rounded to a float
|
package/runtime/core/result.d.ts
CHANGED
|
@@ -25,6 +25,10 @@ export declare const Result: { prototype: ResultMethods<unknown, unknown> };
|
|
|
25
25
|
export declare function Ok<T, E>(value: T): Result<T, E>;
|
|
26
26
|
export declare function Ok<E>(): Result<void, E>;
|
|
27
27
|
export declare function Err<T, E>(error: E): Result<T, E>;
|
|
28
|
+
/** `r.expect(message())`, with `message` called on the `Err` path only. */
|
|
29
|
+
export declare function expectLazy<T, E>(r: Result<T, E>, message: () => string): T;
|
|
30
|
+
/** `r.unwrapOr(fallback())`, with `fallback` called on the `Err` path only. */
|
|
31
|
+
export declare function unwrapOrLazy<T, E>(r: Result<T, E>, fallback: () => T): T;
|
|
28
32
|
/** The `catch` of a body that calls `orReturn`: the `Err` to return, or `thrown` thrown again. */
|
|
29
33
|
export declare function caught<T, E>(thrown: unknown): Result<T, E>;
|
|
30
34
|
|
package/runtime/core/result.mjs
CHANGED
|
@@ -27,6 +27,19 @@ export function exposeWith(s, arg, f) {
|
|
|
27
27
|
return f(s.value, arg);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* `r.expect(message)` and `r.unwrapOr(fallback)` as `--emit-ts` writes them
|
|
32
|
+
* when the argument is more than a name or a literal: natively it is
|
|
33
|
+
* evaluated on the `Err` path only (#182), so here it is a function of none.
|
|
34
|
+
*/
|
|
35
|
+
export function expectLazy(r, message) {
|
|
36
|
+
return r.ok ? r.value : r.expect(message());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function unwrapOrLazy(r, fallback) {
|
|
40
|
+
return r.ok ? r.value : fallback();
|
|
41
|
+
}
|
|
42
|
+
|
|
30
43
|
/** Zero an array of integers, or every integer field of a record: `0` for a number, `0n` for an `i64` or `u64`. */
|
|
31
44
|
function zeroFill(target) {
|
|
32
45
|
if (Array.isArray(target) || ArrayBuffer.isView(target)) {
|
|
@@ -10,5 +10,5 @@ export declare function indexOf(s: string, sub: string, from?: number | bigint):
|
|
|
10
10
|
export declare function startsWith(s: string, sub: string): boolean;
|
|
11
11
|
export declare function endsWith(s: string, sub: string): boolean;
|
|
12
12
|
export declare function fromCharCode(code: number | bigint): string;
|
|
13
|
-
export declare function fromBytes(bytes: readonly u8[], start: number, end: number): string;
|
|
14
|
-
export declare function utf8FromBytes(bytes: readonly u8[], start: number, end: number): string;
|
|
13
|
+
export declare function fromBytes(bytes: readonly u8[], start: number | bigint, end: number | bigint): string;
|
|
14
|
+
export declare function utf8FromBytes(bytes: readonly u8[], start: number | bigint, end: number | bigint): string;
|
package/runtime/nish.d.ts
CHANGED
|
@@ -353,7 +353,7 @@ declare function netWrite(fd: i32, buf: readonly u8[], off: i32, len: i32): i32;
|
|
|
353
353
|
declare function netShutdown(fd: i32, how: i32): i32;
|
|
354
354
|
/** Close the descriptor. */
|
|
355
355
|
declare function netClose(fd: i32): i32;
|
|
356
|
-
/** A TCP socket connecting to `addr`, answered while it connects: writable once it has, or has failed. */
|
|
356
|
+
/** A TCP socket connecting to `addr`, answered while it connects: writable once it has, or has failed. It holds `TCP_NODELAY`, so a small write is sent at once. */
|
|
357
357
|
declare function tcpConnect(addr: readonly u8[]): i32;
|
|
358
358
|
/** Once `fd` is writable: 0 when the connection is made, else its failure (-111 refused). */
|
|
359
359
|
declare function connectResult(fd: i32): i32;
|
|
@@ -510,7 +510,7 @@ declare module "nish:net" {
|
|
|
510
510
|
/** Shut the read side (0), the write side (1) or both (2). */
|
|
511
511
|
export function netShutdown(fd: i32, how: i32): i32;
|
|
512
512
|
export function netClose(fd: i32): i32;
|
|
513
|
-
/** A TCP socket connecting to `addr`, answered while it connects: writable once it has, or has failed. */
|
|
513
|
+
/** A TCP socket connecting to `addr`, answered while it connects: writable once it has, or has failed. It holds `TCP_NODELAY`, so a small write is sent at once. */
|
|
514
514
|
export function tcpConnect(addr: readonly u8[]): i32;
|
|
515
515
|
/** Once `fd` is writable: 0 when the connection is made, else its failure (-111 refused). */
|
|
516
516
|
export function connectResult(fd: i32): i32;
|
package/runtime/node.mjs
CHANGED
|
@@ -378,6 +378,9 @@ installHost({
|
|
|
378
378
|
writeOut: (s) => fs.writeSync(1, s),
|
|
379
379
|
writeErr: (s) => fs.writeSync(2, s),
|
|
380
380
|
exit: (code) => process.exit(code),
|
|
381
|
+
// Index 0 is the program on both sides: the executable natively, the entry
|
|
382
|
+
// script here, which is one index later than Node's own `argv`. The global
|
|
383
|
+
// is left as Node set it; the output reads `argv()` (`src/emit-ts-records.ts`).
|
|
381
384
|
argv: () => process.argv.slice(1),
|
|
382
385
|
call: (name, args) => CALLS[name](...args),
|
|
383
386
|
});
|
package/runtime/runtime-net.c
CHANGED
|
@@ -318,13 +318,24 @@ int32_t nish_tcp_accept(int32_t fd, nish_array *peer) {
|
|
|
318
318
|
descriptor is answered all the same, and the connection is made once the
|
|
319
319
|
loop reports it writable, when `connectResult` says how it went. Any other
|
|
320
320
|
failure closes the socket and is answered instead of it. An `addr` shorter
|
|
321
|
-
than 18 bytes is -22 before any socket exists.
|
|
321
|
+
than 18 bytes is -22 before any socket exists. The socket holds
|
|
322
|
+
`TCP_NODELAY` before it connects, as libpq and Go's net package set it on
|
|
323
|
+
every client socket: a client writes whole requests and waits on the
|
|
324
|
+
answer, and Nagle would hold a small write behind an unacknowledged one
|
|
325
|
+
for a delayed ACK (#942). A refused option closes the socket and is
|
|
326
|
+
answered instead of it. */
|
|
322
327
|
int32_t nish_tcp_connect(const nish_array *addr) {
|
|
323
328
|
if (addr->len < NISH_ADDR_BYTES) return -22;
|
|
324
329
|
const unsigned char *p = (const unsigned char *)addr->data;
|
|
325
330
|
int v4 = memcmp(p, nish_mapped_prefix, 12) == 0;
|
|
326
331
|
int fd = nish_net_socket(v4 ? AF_INET : AF_INET6, SOCK_STREAM);
|
|
327
332
|
if (fd < 0) return nish_net_fail();
|
|
333
|
+
int one = 1;
|
|
334
|
+
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof one) != 0) {
|
|
335
|
+
int e = errno;
|
|
336
|
+
close(fd);
|
|
337
|
+
return nish_net_err(e);
|
|
338
|
+
}
|
|
328
339
|
nish_sockaddr s;
|
|
329
340
|
socklen_t n = nish_net_sockaddr(&s, p, (p[16] << 8) | p[17], v4);
|
|
330
341
|
if (connect(fd, &s.sa, n) != 0 && errno != EINPROGRESS) {
|
package/scripts/build.sh
CHANGED
|
@@ -334,6 +334,39 @@ if [ "$debug" = 1 ]; then common+=(-g); strip_flag=(); fi
|
|
|
334
334
|
# float than the baseline build (docs/LANGUAGE.md, "The CPU level").
|
|
335
335
|
if [ -n "$cpu" ]; then common+=(-march=x86-64-v3 -ffp-contract=off); fi
|
|
336
336
|
|
|
337
|
+
# Hardening, on every native profile (the wasm and wasi ones build their own
|
|
338
|
+
# command line and have none of these to give). docs/security/runtime.md
|
|
339
|
+
# records what each one closes and what it costs.
|
|
340
|
+
#
|
|
341
|
+
# The control-flow flag names the target's own scheme: -fcf-protection is an
|
|
342
|
+
# error on arm64 and -mbranch-protection one on x86, so the triple decides.
|
|
343
|
+
# Nish's IR carries both targets' module flags, and each backend reads its own;
|
|
344
|
+
# these make clang's C units agree, because the linker marks the binary only
|
|
345
|
+
# when every object asks for it. -fstack-clash-protection is ELF's alone: on
|
|
346
|
+
# Mach-O clang ignores it with a warning, and the kernel's guard is the stack
|
|
347
|
+
# itself. -z relro -z now leaves the GOT read-only after relocation, and
|
|
348
|
+
# -z noexecstack says so even when an object without the note slips in.
|
|
349
|
+
#
|
|
350
|
+
# -fstack-protector-strong and _FORTIFY_SOURCE reach the C runtime alone: on a
|
|
351
|
+
# .ll input clang adds no ssp attribute and the macro has no source to change,
|
|
352
|
+
# so one flag list serves the one-command link too. FORTIFY wants an optimiser
|
|
353
|
+
# and is left out of debug, where it would only warn. -U first, because a
|
|
354
|
+
# distribution clang may already define it at another level.
|
|
355
|
+
case "$("$CC" -dumpmachine 2>/dev/null)" in
|
|
356
|
+
x86_64*|amd64*) common+=(-fcf-protection=full); landing=1 ;;
|
|
357
|
+
aarch64*|arm64*) common+=(-mbranch-protection=standard); landing=1 ;;
|
|
358
|
+
*) landing=0 ;;
|
|
359
|
+
esac
|
|
360
|
+
common+=(-fstack-protector-strong)
|
|
361
|
+
if [ "$profile" != debug ]; then common+=(-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3); fi
|
|
362
|
+
case "$(uname -s)" in
|
|
363
|
+
Darwin) ;;
|
|
364
|
+
*)
|
|
365
|
+
common+=(-fstack-clash-protection)
|
|
366
|
+
# shellcheck disable=SC2054 # -Wl,<flag> is one argument: the comma is the linker's
|
|
367
|
+
common+=(-Wl,-z,relro,-z,now -Wl,-z,noexecstack) ;;
|
|
368
|
+
esac
|
|
369
|
+
|
|
337
370
|
tls=()
|
|
338
371
|
if [ "$threads" = 1 ]; then
|
|
339
372
|
common+=(-DNISH_THREADS=1 -ftls-model=initial-exec -pthread)
|
|
@@ -353,7 +386,7 @@ case "$profile" in
|
|
|
353
386
|
optimise=(-Oz -flto -DNDEBUG ${pgo[@]+"${pgo[@]}"}
|
|
354
387
|
-ffunction-sections -fdata-sections -fomit-frame-pointer
|
|
355
388
|
-fno-asynchronous-unwind-tables -fno-unwind-tables ${elf[@]+"${elf[@]}"}
|
|
356
|
-
-
|
|
389
|
+
-fvisibility=hidden) ;;
|
|
357
390
|
napi)
|
|
358
391
|
optimise=(-O3 -flto -DNDEBUG ${pgo[@]+"${pgo[@]}"}
|
|
359
392
|
-ffunction-sections -fdata-sections -fomit-frame-pointer
|
|
@@ -362,14 +395,18 @@ esac
|
|
|
362
395
|
|
|
363
396
|
# The speed link splits full LTO's code generation into one partition per core,
|
|
364
397
|
# run in parallel: lld's --lto-partitions, after the whole program is optimised
|
|
365
|
-
# as one module, so inlining
|
|
366
|
-
#
|
|
367
|
-
#
|
|
368
|
-
#
|
|
369
|
-
#
|
|
370
|
-
#
|
|
398
|
+
# as one module, so inlining does not change. But a partition boundary promotes
|
|
399
|
+
# the internal functions called across it to external ones, and under the
|
|
400
|
+
# control-flow hardening above every external function opens with a landing pad
|
|
401
|
+
# (endbr64, or bti c), which the hot calls then run: fib runs 6.5% more
|
|
402
|
+
# instructions with two partitions than with one. So a target with landing pads
|
|
403
|
+
# links in one partition, and the split is left to the rest. The bytes depend
|
|
404
|
+
# on the count, so one machine links one input to one output, as
|
|
405
|
+
# scripts/bootstrap.sh needs. ld64 and GNU ld have no such flag and link as
|
|
406
|
+
# before, and so does every other profile. The count comes from nproc (Linux),
|
|
407
|
+
# else getconf (both), else sysctl (macOS).
|
|
371
408
|
lto_parts=()
|
|
372
|
-
if [ "$profile" = speed ] && [ "$lld" = 1 ]; then
|
|
409
|
+
if [ "$profile" = speed ] && [ "$lld" = 1 ] && [ "$landing" = 0 ]; then
|
|
373
410
|
cores=$(nproc 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null ||
|
|
374
411
|
sysctl -n hw.ncpu 2>/dev/null || echo 1)
|
|
375
412
|
# shellcheck disable=SC2054 # -Wl,<flag> is one argument: the comma is the linker's
|
|
@@ -379,15 +416,22 @@ fi
|
|
|
379
416
|
# --runtime-objects and --runtime-from: the one-command link split in two, with
|
|
380
417
|
# every compile it held run at once. A `$CC -c` takes `common` and `optimise`,
|
|
381
418
|
# which is what the one command compiles each input with -- less
|
|
382
|
-
# -fuse-ld=lld, which only
|
|
383
|
-
# same object either way: native code for debug,
|
|
384
|
-
# size, which the link still optimises together
|
|
385
|
-
# takes the same line the profile always uses, with
|
|
419
|
+
# -fuse-ld=lld and the -Wl, flags, which only speak to the linker -- so a
|
|
420
|
+
# runtime unit compiles to the same object either way: native code for debug,
|
|
421
|
+
# -flto bitcode for speed and size, which the link still optimises together
|
|
422
|
+
# with the program. The link takes the same line the profile always uses, with
|
|
423
|
+
# objects for sources.
|
|
386
424
|
compile=()
|
|
387
425
|
for flag in "${common[@]}"; do
|
|
388
|
-
case "$flag" in -fuse-ld
|
|
426
|
+
case "$flag" in -fuse-ld=*|-Wl,*) ;; *) compile+=("$flag") ;; esac
|
|
389
427
|
done
|
|
390
428
|
compile+=(${optimise[@]+"${optimise[@]}"})
|
|
429
|
+
# A program's modules are .ll alone, and clang warns that a -U it cannot
|
|
430
|
+
# apply to one went unused, so their compile leaves the FORTIFY pair out.
|
|
431
|
+
module_compile=()
|
|
432
|
+
for flag in "${compile[@]}"; do
|
|
433
|
+
case "$flag" in -U_FORTIFY_SOURCE|-D_FORTIFY_SOURCE=*) ;; *) module_compile+=("$flag") ;; esac
|
|
434
|
+
done
|
|
391
435
|
# Waits for every job, not just the last, and fails when any of them did.
|
|
392
436
|
# bash 3.2 has no `wait -n`, so the jobs are waited for in order.
|
|
393
437
|
pids=()
|
|
@@ -542,7 +586,7 @@ if [ -n "$objects_from" ]; then
|
|
|
542
586
|
for i in "${inputs[@]}"; do
|
|
543
587
|
case "$i" in */runtime.c|runtime.c) echo "error: --runtime-from links the runtime in $objects_from, so $i is not an input" >&2; exit 2 ;; esac
|
|
544
588
|
n=$((n + 1))
|
|
545
|
-
"$CC" "${
|
|
589
|
+
"$CC" "${module_compile[@]}" -c "$i" -o "$work/$n.o" &
|
|
546
590
|
pids+=("$!")
|
|
547
591
|
objects+=("$work/$n.o")
|
|
548
592
|
done
|
package/std/README.md
CHANGED
|
@@ -24,7 +24,7 @@ whatever program imports it, and subject to the same rules as `examples/` or
|
|
|
24
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
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
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 `
|
|
27
|
+
| [`random.ts`](./random.ts) | `Rng`, a seeded generator of pseudo-random numbers: `new Rng(seed)`, `nextI32()`, `nextF64()` and `nextInt(lo, hi)`, an unbiased integer in [lo, hi] by rejection sampling. A Weyl sequence through all 2^32 states with MurmurHash3's finaliser on each, written with `nish:unsafe`'s `wrappingAdd`, `wrappingSub` 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
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
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
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)) |
|
|
@@ -81,7 +81,7 @@ is listed in [`SECURITY.md`](../SECURITY.md#threat-model).
|
|
|
81
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
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 |
|
|
83
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 | — |
|
|
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
|
|
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. `bcryptHash4(p0, p1, p2, p3, salts, cost)` and `bcryptVerify4(p0, p1, p2, p3, hashes)` do up to four at once, interleaved (four in the time of about 1.2 single hashes on one core), each lane the single path's answer; verify groups lanes by cost, and a malformed lane makes the whole call an `Err` naming it. `BCRYPT_MAX_PASSWORD`, `BCRYPT_HASH_LENGTH`, `BCRYPT_SALT_SIZE`, `BCRYPT_MIN_COST`, `BCRYPT_MAX_COST`. A `std/threads` task may call all four ([BCRYPT-9](../docs/security/crypto-bcrypt.md#findings)) | OpenBSD `bcrypt(3)`; crypt_blowfish 1.3 for `$2a$` and `$2y$` |
|
|
85
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`) |
|
|
86
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 |
|
|
87
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` |
|
package/std/cli.ts
CHANGED
|
@@ -273,9 +273,25 @@ export class HelpMenu {
|
|
|
273
273
|
|
|
274
274
|
/** The opening line, the program's name, `version` and a one-line `summary`, and a blank after it. */
|
|
275
275
|
header(version: string, summary: string): void {
|
|
276
|
+
this.title(version, summary)
|
|
277
|
+
this.lines.push("")
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* The opening line alone, without `header`'s blank after it, for a menu that
|
|
282
|
+
* puts something under its title (`line`) before the blank.
|
|
283
|
+
*/
|
|
284
|
+
title(version: string, summary: string): void {
|
|
276
285
|
const style = this.style
|
|
277
286
|
this.lines.push(`${style.name(this.program)} ${style.dim(version)} ${style.dim(summary)}`)
|
|
278
|
-
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* A line exactly as given, painted (or not) by the caller with the menu's
|
|
291
|
+
* `style`: a rule, a banner, a blank. Its width is the caller's to keep.
|
|
292
|
+
*/
|
|
293
|
+
line(text: string): void {
|
|
294
|
+
this.lines.push(text)
|
|
279
295
|
}
|
|
280
296
|
|
|
281
297
|
/**
|