@amritk/nish-x86_64-linux 0.33.0 → 0.34.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 +10 -8
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/core.d.ts +83 -6
- package/runtime/core.mjs +90 -0
- package/runtime/nish.d.ts +83 -6
- package/runtime/nish.h +27 -4
- package/runtime/runtime-chacha20.c +366 -0
- package/scripts/build.sh +12 -9
- package/std/README.md +1 -1
- package/std/net/http1-codec.ts +81 -41
- package/std/net/http1-server.ts +62 -47
package/INSTALL.md
CHANGED
|
@@ -254,8 +254,8 @@ routes are
|
|
|
254
254
|
# on the musl host, with its own clang and the runtime from this repository
|
|
255
255
|
clang app.ll runtime/runtime.c runtime/runtime-os.c runtime/runtime-parallel.c \
|
|
256
256
|
runtime/runtime-cross.c runtime/runtime-host.c runtime/runtime-net.c \
|
|
257
|
-
runtime/runtime-simd.c runtime/runtime-aes.c runtime/runtime-sha256.c runtime/runtime-
|
|
258
|
-
runtime/runtime-bytes.c runtime/runtime-checksum.c runtime/runtime-sandbox.c \
|
|
257
|
+
runtime/runtime-simd.c runtime/runtime-aes.c runtime/runtime-sha256.c runtime/runtime-chacha20.c \
|
|
258
|
+
runtime/runtime-term.c runtime/runtime-bytes.c runtime/runtime-checksum.c runtime/runtime-sandbox.c \
|
|
259
259
|
runtime/runtime-shm.c runtime/runtime-region.c -lm -o app
|
|
260
260
|
```
|
|
261
261
|
|
|
@@ -392,7 +392,7 @@ it into `build/seed/`; `NISH_BOOTSTRAP=<path>` names one you already have and
|
|
|
392
392
|
skips the download. §2a has what the build does with it.
|
|
393
393
|
|
|
394
394
|
The published package ships `bin/` (the `nish` command, which is a launcher),
|
|
395
|
-
`runtime/` (the C runtime —
|
|
395
|
+
`runtime/` (the C runtime — sixteen translation units, the wasm subset and their
|
|
396
396
|
header — and `runtime/sqlite/`, SQLite's vendored source),
|
|
397
397
|
`scripts/build.sh` (the link pipeline), `std/` (the standard library) and
|
|
398
398
|
`docs/AI.md` (the rules card). The
|
|
@@ -405,14 +405,14 @@ working directory — and the binary runs from inside its own platform package
|
|
|
405
405
|
for exactly that reason.
|
|
406
406
|
|
|
407
407
|
Building the IR yourself rather than through `--link` means naming the runtime
|
|
408
|
-
on the `clang` line, and it is
|
|
408
|
+
on the `clang` line, and it is sixteen files:
|
|
409
409
|
|
|
410
410
|
```bash
|
|
411
411
|
clang app.ll runtime/runtime.c runtime/runtime-os.c runtime/runtime-parallel.c \
|
|
412
412
|
runtime/runtime-cross.c runtime/runtime-host.c runtime/runtime-net.c \
|
|
413
|
-
runtime/runtime-simd.c runtime/runtime-aes.c runtime/runtime-sha256.c runtime/runtime-
|
|
414
|
-
runtime/runtime-bytes.c runtime/runtime-checksum.c runtime/runtime-sandbox.c \
|
|
415
|
-
runtime/runtime-shm.c -lm -o app
|
|
413
|
+
runtime/runtime-simd.c runtime/runtime-aes.c runtime/runtime-sha256.c runtime/runtime-chacha20.c \
|
|
414
|
+
runtime/runtime-term.c runtime/runtime-bytes.c runtime/runtime-checksum.c runtime/runtime-sandbox.c \
|
|
415
|
+
runtime/runtime-shm.c runtime/runtime-region.c -lm -o app
|
|
416
416
|
```
|
|
417
417
|
|
|
418
418
|
`runtime.c` is the part every program touches — the arena, strings, arrays,
|
|
@@ -427,7 +427,9 @@ entropy, file times and signals, `runtime-net.c` the sockets of `nish:net`,
|
|
|
427
427
|
`runtime-simd.c` and `runtime-aes.c` the vector text search and AES-GCM,
|
|
428
428
|
`runtime-sha256.c` the SHA-256 block kernel `nish/crypto/sha256` hands its
|
|
429
429
|
blocks to on a CPU with the SHA instructions (`NISH_SHA256=soft` keeps the
|
|
430
|
-
portable path), `runtime-
|
|
430
|
+
portable path), `runtime-chacha20.c` the ChaCha20 keystream `nish/crypto/chacha20poly1305`
|
|
431
|
+
hands a message of 256 bytes or more to on a CPU with AVX2 or NEON (`NISH_CHACHA20=soft`
|
|
432
|
+
keeps the portable path), `runtime-term.c` the stdin reads and terminal calls of `nish:term`,
|
|
431
433
|
`runtime-bytes.c` the byte-class runs of `nish/bytes`, which `nish/json` steps
|
|
432
434
|
over, `runtime-checksum.c` the CRC-32 kernels of `nish/compress/checksum`,
|
|
433
435
|
`runtime-sandbox.c` the confinement a `--sandbox` build applies, and
|
package/bin/nish
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/runtime/core.d.ts
CHANGED
|
@@ -16,14 +16,30 @@ import type { f32, f64, i32, u16, u32, u8 } from "./core/numbers.js";
|
|
|
16
16
|
* `nish` does, but lets `===` and `<` through, which `nish` refuses. Under
|
|
17
17
|
* Node it is a typed array made once and never written after.
|
|
18
18
|
*/
|
|
19
|
-
export interface u8x16 { readonly __nishSimd: "u8x16"; readonly __lane: u8 }
|
|
20
|
-
export interface u16x8 { readonly __nishSimd: "u16x8"; readonly __lane: u16 }
|
|
21
|
-
export interface i32x4 { readonly __nishSimd: "i32x4"; readonly __lane: i32 }
|
|
22
|
-
export interface u32x4 { readonly __nishSimd: "u32x4"; readonly __lane: u32 }
|
|
23
|
-
export interface f32x4 { readonly __nishSimd: "f32x4"; readonly __lane: f32 }
|
|
24
|
-
export interface f64x2 { readonly __nishSimd: "f64x2"; readonly __lane: f64 }
|
|
19
|
+
export interface u8x16 { readonly __nishSimd: "u8x16"; readonly __lane: u8; readonly __mask: mask8x16 }
|
|
20
|
+
export interface u16x8 { readonly __nishSimd: "u16x8"; readonly __lane: u16; readonly __mask: mask16x8 }
|
|
21
|
+
export interface i32x4 { readonly __nishSimd: "i32x4"; readonly __lane: i32; readonly __mask: mask32x4 }
|
|
22
|
+
export interface u32x4 { readonly __nishSimd: "u32x4"; readonly __lane: u32; readonly __mask: mask32x4 }
|
|
23
|
+
export interface f32x4 { readonly __nishSimd: "f32x4"; readonly __lane: f32; readonly __mask: mask32x4 }
|
|
24
|
+
export interface f64x2 { readonly __nishSimd: "f64x2"; readonly __lane: f64; readonly __mask: mask64x2 }
|
|
25
25
|
/** Any of the six. */
|
|
26
26
|
export type SimdVector = u8x16 | u16x8 | i32x4 | u32x4 | f32x4 | f64x2;
|
|
27
|
+
/** The four with integer lanes. */
|
|
28
|
+
export type IntegerVector = u8x16 | u16x8 | i32x4 | u32x4;
|
|
29
|
+
/** The two with float lanes. */
|
|
30
|
+
export type FloatVector = f32x4 | f64x2;
|
|
31
|
+
/**
|
|
32
|
+
* One boolean per lane, which a lane-wise compare answers. Under Node an
|
|
33
|
+
* array of booleans. `nish` keeps a mask in a local, a parameter or a
|
|
34
|
+
* return of a function the module keeps, and nowhere else; it has no
|
|
35
|
+
* constructor.
|
|
36
|
+
*/
|
|
37
|
+
export interface mask8x16 { readonly __nishMask: "mask8x16" }
|
|
38
|
+
export interface mask16x8 { readonly __nishMask: "mask16x8" }
|
|
39
|
+
export interface mask32x4 { readonly __nishMask: "mask32x4" }
|
|
40
|
+
export interface mask64x2 { readonly __nishMask: "mask64x2" }
|
|
41
|
+
/** Any of the four. */
|
|
42
|
+
export type SimdMask = mask8x16 | mask16x8 | mask32x4 | mask64x2;
|
|
27
43
|
/** One value per lane, lane 0 first. */
|
|
28
44
|
export declare function u8x16(
|
|
29
45
|
a0: u8, a1: u8, a2: u8, a3: u8, a4: u8, a5: u8, a6: u8, a7: u8,
|
|
@@ -54,3 +70,64 @@ export declare function store<V extends SimdVector>(v: V, xs: V["__lane"][], i:
|
|
|
54
70
|
export declare function extract<V extends SimdVector>(v: V, k: i32): V["__lane"];
|
|
55
71
|
/** A new vector: `v` with lane `k` set to `x`. */
|
|
56
72
|
export declare function replace<V extends SimdVector>(v: V, k: i32, x: V["__lane"]): V;
|
|
73
|
+
/** Float lane arithmetic, lane by lane. An integer lane's form wraps. */
|
|
74
|
+
export declare function add<V extends FloatVector>(a: V, b: V): V;
|
|
75
|
+
export declare function sub<V extends FloatVector>(a: V, b: V): V;
|
|
76
|
+
export declare function mul<V extends FloatVector>(a: V, b: V): V;
|
|
77
|
+
export declare function div<V extends FloatVector>(a: V, b: V): V;
|
|
78
|
+
export declare function sqrt<V extends FloatVector>(a: V): V;
|
|
79
|
+
/** Integer lane arithmetic, wrapping at the lane width. */
|
|
80
|
+
export declare function addWrapping<V extends IntegerVector>(a: V, b: V): V;
|
|
81
|
+
export declare function subWrapping<V extends IntegerVector>(a: V, b: V): V;
|
|
82
|
+
export declare function mulWrapping<V extends IntegerVector>(a: V, b: V): V;
|
|
83
|
+
/** Negation and absolute value of float lanes: the sign bit. */
|
|
84
|
+
export declare function neg<V extends FloatVector>(a: V): V;
|
|
85
|
+
export declare function abs<V extends FloatVector>(a: V): V;
|
|
86
|
+
/** The lesser and the greater lane: a float NaN lane answers NaN. */
|
|
87
|
+
export declare function min<V extends SimdVector>(a: V, b: V): V;
|
|
88
|
+
export declare function max<V extends SimdVector>(a: V, b: V): V;
|
|
89
|
+
/** Bitwise, on integer lanes or lane by lane on two masks. */
|
|
90
|
+
export declare function and<V extends IntegerVector | SimdMask>(a: V, b: V): V;
|
|
91
|
+
export declare function or<V extends IntegerVector | SimdMask>(a: V, b: V): V;
|
|
92
|
+
export declare function xor<V extends IntegerVector | SimdMask>(a: V, b: V): V;
|
|
93
|
+
export declare function not<V extends IntegerVector | SimdMask>(a: V): V;
|
|
94
|
+
/** Lane-wise compares, answering the vector's mask. A float compare is ordered (`ne` unordered). */
|
|
95
|
+
export declare function eq<V extends SimdVector>(a: V, b: V): V["__mask"];
|
|
96
|
+
export declare function ne<V extends SimdVector>(a: V, b: V): V["__mask"];
|
|
97
|
+
export declare function lt<V extends SimdVector>(a: V, b: V): V["__mask"];
|
|
98
|
+
export declare function le<V extends SimdVector>(a: V, b: V): V["__mask"];
|
|
99
|
+
export declare function gt<V extends SimdVector>(a: V, b: V): V["__mask"];
|
|
100
|
+
export declare function ge<V extends SimdVector>(a: V, b: V): V["__mask"];
|
|
101
|
+
/** Lane `k` of `a` where lane `k` of `m` is set, else of `b`. */
|
|
102
|
+
export declare function select<V extends SimdVector>(m: V["__mask"], a: V, b: V): V;
|
|
103
|
+
/** Whether any lane, or every lane, is set. */
|
|
104
|
+
export declare function any(m: SimdMask): boolean;
|
|
105
|
+
export declare function all(m: SimdMask): boolean;
|
|
106
|
+
/** Lane `k`'s flag in bit `k`. */
|
|
107
|
+
export declare function bitmask(m: SimdMask): i32;
|
|
108
|
+
/**
|
|
109
|
+
* The lanes added: a `u8x16` or `u16x8` in an `i32`, a float vector in lane
|
|
110
|
+
* order. An `i32x4` or `u32x4` total is `sumWrapping`.
|
|
111
|
+
*/
|
|
112
|
+
export declare function sum(v: u8x16 | u16x8): i32;
|
|
113
|
+
export declare function sum(v: f32x4): f32;
|
|
114
|
+
export declare function sum(v: f64x2): f64;
|
|
115
|
+
export declare function sumWrapping(v: i32x4): i32;
|
|
116
|
+
export declare function sumWrapping(v: u32x4): u32;
|
|
117
|
+
/** Lanes picked from `a` then `b` by integer-literal indices, one per lane. */
|
|
118
|
+
export declare function shuffle<V extends SimdVector>(a: V, b: V, ...lanes: i32[]): V;
|
|
119
|
+
/**
|
|
120
|
+
* Shifts and rotates by an integer-literal count below the lane width:
|
|
121
|
+
* `shr` is logical on an unsigned lane and arithmetic on an `i32` lane.
|
|
122
|
+
*/
|
|
123
|
+
export declare function shl<V extends IntegerVector>(v: V, count: i32): V;
|
|
124
|
+
export declare function shr<V extends IntegerVector>(v: V, count: i32): V;
|
|
125
|
+
export declare function rotl<V extends IntegerVector>(v: V, count: i32): V;
|
|
126
|
+
export declare function rotr<V extends IntegerVector>(v: V, count: i32): V;
|
|
127
|
+
/** The same sixteen bytes read as another vector type. */
|
|
128
|
+
export declare function asU8x16(v: SimdVector): u8x16;
|
|
129
|
+
export declare function asU16x8(v: SimdVector): u16x8;
|
|
130
|
+
export declare function asI32x4(v: SimdVector): i32x4;
|
|
131
|
+
export declare function asU32x4(v: SimdVector): u32x4;
|
|
132
|
+
export declare function asF32x4(v: SimdVector): f32x4;
|
|
133
|
+
export declare function asF64x2(v: SimdVector): f64x2;
|
package/runtime/core.mjs
CHANGED
|
@@ -120,3 +120,93 @@ export function replace(v, k, x) {
|
|
|
120
120
|
out[k] = x;
|
|
121
121
|
return out;
|
|
122
122
|
}
|
|
123
|
+
|
|
124
|
+
// ---- `nish:simd` lane operations ---------------------------------------------
|
|
125
|
+
// Each answers a fresh vector of its operands' type, and the typed array does
|
|
126
|
+
// the wrapping: a `u8` sum stored into a `Uint8Array` keeps its low byte, as
|
|
127
|
+
// `add <16 x i8>` does. A mask is an array of booleans, one a lane. `f32`
|
|
128
|
+
// arithmetic rounds once, through the array, which for `+ - * /` and `sqrt` is
|
|
129
|
+
// the single rounding the native lane does: the double result of two floats
|
|
130
|
+
// rounds to the same float. `Math.min` and `Math.max` are `llvm.minimum` and
|
|
131
|
+
// `llvm.maximum`: a NaN lane answers NaN and -0 orders below +0.
|
|
132
|
+
|
|
133
|
+
const simdLanes = (a, f) => {
|
|
134
|
+
const out = new a.constructor(a.length);
|
|
135
|
+
for (let j = 0; j < a.length; j++) out[j] = f(a[j], j);
|
|
136
|
+
return out;
|
|
137
|
+
};
|
|
138
|
+
const simdMask = (a, f) => {
|
|
139
|
+
const out = new Array(a.length);
|
|
140
|
+
for (let j = 0; j < a.length; j++) out[j] = f(a[j], j);
|
|
141
|
+
return out;
|
|
142
|
+
};
|
|
143
|
+
const simdBits = (a) => a.BYTES_PER_ELEMENT * 8;
|
|
144
|
+
|
|
145
|
+
export const add = (a, b) => simdLanes(a, (x, j) => x + b[j]);
|
|
146
|
+
export const sub = (a, b) => simdLanes(a, (x, j) => x - b[j]);
|
|
147
|
+
export const mul = (a, b) => simdLanes(a, (x, j) => x * b[j]);
|
|
148
|
+
export const div = (a, b) => simdLanes(a, (x, j) => x / b[j]);
|
|
149
|
+
export const neg = (a) => simdLanes(a, (x) => -x);
|
|
150
|
+
export const abs = (a) => simdLanes(a, (x) => Math.abs(x));
|
|
151
|
+
export const sqrt = (a) => simdLanes(a, (x) => Math.sqrt(x));
|
|
152
|
+
// A typed array wraps on store, so the integer forms share the float bodies.
|
|
153
|
+
export const addWrapping = (a, b) => simdLanes(a, (x, j) => x + b[j]);
|
|
154
|
+
export const subWrapping = (a, b) => simdLanes(a, (x, j) => x - b[j]);
|
|
155
|
+
// A 32-bit product loses its low bits as a double; `Math.imul` keeps them.
|
|
156
|
+
export const mulWrapping = (a, b) => simdLanes(a, (x, j) => (simdBits(a) === 32 ? Math.imul(x, b[j]) : x * b[j]));
|
|
157
|
+
export const min = (a, b) => simdLanes(a, (x, j) => Math.min(x, b[j]));
|
|
158
|
+
export const max = (a, b) => simdLanes(a, (x, j) => Math.max(x, b[j]));
|
|
159
|
+
|
|
160
|
+
/** `and`, `or`, `xor` and `not` take an integer vector or a mask. */
|
|
161
|
+
export const and = (a, b) => (Array.isArray(a) ? simdMask(a, (x, j) => x && b[j]) : simdLanes(a, (x, j) => x & b[j]));
|
|
162
|
+
export const or = (a, b) => (Array.isArray(a) ? simdMask(a, (x, j) => x || b[j]) : simdLanes(a, (x, j) => x | b[j]));
|
|
163
|
+
export const xor = (a, b) => (Array.isArray(a) ? simdMask(a, (x, j) => x !== b[j]) : simdLanes(a, (x, j) => x ^ b[j]));
|
|
164
|
+
export const not = (a) => (Array.isArray(a) ? simdMask(a, (x) => !x) : simdLanes(a, (x) => ~x));
|
|
165
|
+
|
|
166
|
+
// `ne` is the unordered compare: a NaN lane is unequal to everything.
|
|
167
|
+
export const eq = (a, b) => simdMask(a, (x, j) => x === b[j]);
|
|
168
|
+
export const ne = (a, b) => simdMask(a, (x, j) => x !== b[j]);
|
|
169
|
+
export const lt = (a, b) => simdMask(a, (x, j) => x < b[j]);
|
|
170
|
+
export const le = (a, b) => simdMask(a, (x, j) => x <= b[j]);
|
|
171
|
+
export const gt = (a, b) => simdMask(a, (x, j) => x > b[j]);
|
|
172
|
+
export const ge = (a, b) => simdMask(a, (x, j) => x >= b[j]);
|
|
173
|
+
|
|
174
|
+
export const select = (m, a, b) => simdLanes(a, (x, j) => (m[j] ? x : b[j]));
|
|
175
|
+
export const any = (m) => m.some((x) => x);
|
|
176
|
+
export const all = (m) => m.every((x) => x);
|
|
177
|
+
export const bitmask = (m) => m.reduce((bits, x, j) => (x ? bits | (1 << j) : bits), 0);
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* `sum`: a float vector's lanes added first to last from -0, rounding each
|
|
181
|
+
* step as `llvm.vector.reduce.fadd` does; a `u8` or `u16` vector's, which
|
|
182
|
+
* cannot overflow an `i32`.
|
|
183
|
+
*/
|
|
184
|
+
export const sum = (a) => {
|
|
185
|
+
let acc = -0;
|
|
186
|
+
for (const x of a) acc = a instanceof Float32Array ? Math.fround(acc + x) : acc + x;
|
|
187
|
+
return acc;
|
|
188
|
+
};
|
|
189
|
+
/** `sumWrapping`: a 32-bit vector's lanes added modulo 2^32. */
|
|
190
|
+
export const sumWrapping = (a) => {
|
|
191
|
+
let acc = 0;
|
|
192
|
+
for (const x of a) acc = (acc + x) | 0;
|
|
193
|
+
return a instanceof Uint32Array ? acc >>> 0 : acc;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/** `shuffle(a, b, ...lanes)`: lane `k` of the 2N lanes `a` then `b`. */
|
|
197
|
+
export const shuffle = (a, b, ...lanes) => simdLanes(a, (_, j) => (lanes[j] < a.length ? a[lanes[j]] : b[lanes[j] - a.length]));
|
|
198
|
+
|
|
199
|
+
// A count is a constant below the lane width, so `x >>> (w - k)` is a real
|
|
200
|
+
// shift; at k = 0 and w = 32 JavaScript's count wraps to 0, and the `|` keeps x.
|
|
201
|
+
export const shl = (a, k) => simdLanes(a, (x) => x << k);
|
|
202
|
+
export const shr = (a, k) => simdLanes(a, (x) => (a instanceof Int32Array ? x >> k : x >>> k));
|
|
203
|
+
export const rotl = (a, k) => simdLanes(a, (x) => (x << k) | (x >>> (simdBits(a) - k)));
|
|
204
|
+
export const rotr = (a, k) => simdLanes(a, (x) => (x >>> k) | (x << (simdBits(a) - k)));
|
|
205
|
+
|
|
206
|
+
const simdAs = (Lanes) => (v) => new Lanes(v.buffer.slice(v.byteOffset, v.byteOffset + 16));
|
|
207
|
+
export const asU8x16 = simdAs(Uint8Array);
|
|
208
|
+
export const asU16x8 = simdAs(Uint16Array);
|
|
209
|
+
export const asI32x4 = simdAs(Int32Array);
|
|
210
|
+
export const asU32x4 = simdAs(Uint32Array);
|
|
211
|
+
export const asF32x4 = simdAs(Float32Array);
|
|
212
|
+
export const asF64x2 = simdAs(Float64Array);
|
package/runtime/nish.d.ts
CHANGED
|
@@ -856,14 +856,30 @@ declare module "nish:simd" {
|
|
|
856
856
|
* `nish` does, but lets `===` and `<` through, which `nish` refuses. Under
|
|
857
857
|
* Node it is a typed array made once and never written after.
|
|
858
858
|
*/
|
|
859
|
-
export interface u8x16 { readonly __nishSimd: "u8x16"; readonly __lane: u8 }
|
|
860
|
-
export interface u16x8 { readonly __nishSimd: "u16x8"; readonly __lane: u16 }
|
|
861
|
-
export interface i32x4 { readonly __nishSimd: "i32x4"; readonly __lane: i32 }
|
|
862
|
-
export interface u32x4 { readonly __nishSimd: "u32x4"; readonly __lane: u32 }
|
|
863
|
-
export interface f32x4 { readonly __nishSimd: "f32x4"; readonly __lane: f32 }
|
|
864
|
-
export interface f64x2 { readonly __nishSimd: "f64x2"; readonly __lane: f64 }
|
|
859
|
+
export interface u8x16 { readonly __nishSimd: "u8x16"; readonly __lane: u8; readonly __mask: mask8x16 }
|
|
860
|
+
export interface u16x8 { readonly __nishSimd: "u16x8"; readonly __lane: u16; readonly __mask: mask16x8 }
|
|
861
|
+
export interface i32x4 { readonly __nishSimd: "i32x4"; readonly __lane: i32; readonly __mask: mask32x4 }
|
|
862
|
+
export interface u32x4 { readonly __nishSimd: "u32x4"; readonly __lane: u32; readonly __mask: mask32x4 }
|
|
863
|
+
export interface f32x4 { readonly __nishSimd: "f32x4"; readonly __lane: f32; readonly __mask: mask32x4 }
|
|
864
|
+
export interface f64x2 { readonly __nishSimd: "f64x2"; readonly __lane: f64; readonly __mask: mask64x2 }
|
|
865
865
|
/** Any of the six. */
|
|
866
866
|
export type SimdVector = u8x16 | u16x8 | i32x4 | u32x4 | f32x4 | f64x2;
|
|
867
|
+
/** The four with integer lanes. */
|
|
868
|
+
export type IntegerVector = u8x16 | u16x8 | i32x4 | u32x4;
|
|
869
|
+
/** The two with float lanes. */
|
|
870
|
+
export type FloatVector = f32x4 | f64x2;
|
|
871
|
+
/**
|
|
872
|
+
* One boolean per lane, which a lane-wise compare answers. Under Node an
|
|
873
|
+
* array of booleans. `nish` keeps a mask in a local, a parameter or a
|
|
874
|
+
* return of a function the module keeps, and nowhere else; it has no
|
|
875
|
+
* constructor.
|
|
876
|
+
*/
|
|
877
|
+
export interface mask8x16 { readonly __nishMask: "mask8x16" }
|
|
878
|
+
export interface mask16x8 { readonly __nishMask: "mask16x8" }
|
|
879
|
+
export interface mask32x4 { readonly __nishMask: "mask32x4" }
|
|
880
|
+
export interface mask64x2 { readonly __nishMask: "mask64x2" }
|
|
881
|
+
/** Any of the four. */
|
|
882
|
+
export type SimdMask = mask8x16 | mask16x8 | mask32x4 | mask64x2;
|
|
867
883
|
/** One value per lane, lane 0 first. */
|
|
868
884
|
export function u8x16(
|
|
869
885
|
a0: u8, a1: u8, a2: u8, a3: u8, a4: u8, a5: u8, a6: u8, a7: u8,
|
|
@@ -894,6 +910,67 @@ declare module "nish:simd" {
|
|
|
894
910
|
export function extract<V extends SimdVector>(v: V, k: i32): V["__lane"];
|
|
895
911
|
/** A new vector: `v` with lane `k` set to `x`. */
|
|
896
912
|
export function replace<V extends SimdVector>(v: V, k: i32, x: V["__lane"]): V;
|
|
913
|
+
/** Float lane arithmetic, lane by lane. An integer lane's form wraps. */
|
|
914
|
+
export function add<V extends FloatVector>(a: V, b: V): V;
|
|
915
|
+
export function sub<V extends FloatVector>(a: V, b: V): V;
|
|
916
|
+
export function mul<V extends FloatVector>(a: V, b: V): V;
|
|
917
|
+
export function div<V extends FloatVector>(a: V, b: V): V;
|
|
918
|
+
export function sqrt<V extends FloatVector>(a: V): V;
|
|
919
|
+
/** Integer lane arithmetic, wrapping at the lane width. */
|
|
920
|
+
export function addWrapping<V extends IntegerVector>(a: V, b: V): V;
|
|
921
|
+
export function subWrapping<V extends IntegerVector>(a: V, b: V): V;
|
|
922
|
+
export function mulWrapping<V extends IntegerVector>(a: V, b: V): V;
|
|
923
|
+
/** Negation and absolute value of float lanes: the sign bit. */
|
|
924
|
+
export function neg<V extends FloatVector>(a: V): V;
|
|
925
|
+
export function abs<V extends FloatVector>(a: V): V;
|
|
926
|
+
/** The lesser and the greater lane: a float NaN lane answers NaN. */
|
|
927
|
+
export function min<V extends SimdVector>(a: V, b: V): V;
|
|
928
|
+
export function max<V extends SimdVector>(a: V, b: V): V;
|
|
929
|
+
/** Bitwise, on integer lanes or lane by lane on two masks. */
|
|
930
|
+
export function and<V extends IntegerVector | SimdMask>(a: V, b: V): V;
|
|
931
|
+
export function or<V extends IntegerVector | SimdMask>(a: V, b: V): V;
|
|
932
|
+
export function xor<V extends IntegerVector | SimdMask>(a: V, b: V): V;
|
|
933
|
+
export function not<V extends IntegerVector | SimdMask>(a: V): V;
|
|
934
|
+
/** Lane-wise compares, answering the vector's mask. A float compare is ordered (`ne` unordered). */
|
|
935
|
+
export function eq<V extends SimdVector>(a: V, b: V): V["__mask"];
|
|
936
|
+
export function ne<V extends SimdVector>(a: V, b: V): V["__mask"];
|
|
937
|
+
export function lt<V extends SimdVector>(a: V, b: V): V["__mask"];
|
|
938
|
+
export function le<V extends SimdVector>(a: V, b: V): V["__mask"];
|
|
939
|
+
export function gt<V extends SimdVector>(a: V, b: V): V["__mask"];
|
|
940
|
+
export function ge<V extends SimdVector>(a: V, b: V): V["__mask"];
|
|
941
|
+
/** Lane `k` of `a` where lane `k` of `m` is set, else of `b`. */
|
|
942
|
+
export function select<V extends SimdVector>(m: V["__mask"], a: V, b: V): V;
|
|
943
|
+
/** Whether any lane, or every lane, is set. */
|
|
944
|
+
export function any(m: SimdMask): boolean;
|
|
945
|
+
export function all(m: SimdMask): boolean;
|
|
946
|
+
/** Lane `k`'s flag in bit `k`. */
|
|
947
|
+
export function bitmask(m: SimdMask): i32;
|
|
948
|
+
/**
|
|
949
|
+
* The lanes added: a `u8x16` or `u16x8` in an `i32`, a float vector in lane
|
|
950
|
+
* order. An `i32x4` or `u32x4` total is `sumWrapping`.
|
|
951
|
+
*/
|
|
952
|
+
export function sum(v: u8x16 | u16x8): i32;
|
|
953
|
+
export function sum(v: f32x4): f32;
|
|
954
|
+
export function sum(v: f64x2): f64;
|
|
955
|
+
export function sumWrapping(v: i32x4): i32;
|
|
956
|
+
export function sumWrapping(v: u32x4): u32;
|
|
957
|
+
/** Lanes picked from `a` then `b` by integer-literal indices, one per lane. */
|
|
958
|
+
export function shuffle<V extends SimdVector>(a: V, b: V, ...lanes: i32[]): V;
|
|
959
|
+
/**
|
|
960
|
+
* Shifts and rotates by an integer-literal count below the lane width:
|
|
961
|
+
* `shr` is logical on an unsigned lane and arithmetic on an `i32` lane.
|
|
962
|
+
*/
|
|
963
|
+
export function shl<V extends IntegerVector>(v: V, count: i32): V;
|
|
964
|
+
export function shr<V extends IntegerVector>(v: V, count: i32): V;
|
|
965
|
+
export function rotl<V extends IntegerVector>(v: V, count: i32): V;
|
|
966
|
+
export function rotr<V extends IntegerVector>(v: V, count: i32): V;
|
|
967
|
+
/** The same sixteen bytes read as another vector type. */
|
|
968
|
+
export function asU8x16(v: SimdVector): u8x16;
|
|
969
|
+
export function asU16x8(v: SimdVector): u16x8;
|
|
970
|
+
export function asI32x4(v: SimdVector): i32x4;
|
|
971
|
+
export function asU32x4(v: SimdVector): u32x4;
|
|
972
|
+
export function asF32x4(v: SimdVector): f32x4;
|
|
973
|
+
export function asF64x2(v: SimdVector): f64x2;
|
|
897
974
|
}
|
|
898
975
|
|
|
899
976
|
declare module "nish:io" {
|
package/runtime/nish.h
CHANGED
|
@@ -10,15 +10,16 @@
|
|
|
10
10
|
* clock, `nish_platform` / `nish_arch` — and runtime-parallel.c, runtime-host.c,
|
|
11
11
|
* runtime-net.c, runtime-simd.c (the byte searches that read a vector at a
|
|
12
12
|
* time), runtime-aes.c (AES-GCM on the CPU's AES instructions),
|
|
13
|
-
* runtime-sha256.c (SHA-256 on the CPU's SHA instructions), runtime-
|
|
13
|
+
* runtime-sha256.c (SHA-256 on the CPU's SHA instructions), runtime-chacha20.c
|
|
14
|
+
* (ChaCha20's keystream on AVX2 or NEON vectors), runtime-checksum.c
|
|
14
15
|
* (CRC-32 on its carry-less multiply or CRC instructions) and runtime-term.c
|
|
15
16
|
* (stdin and the terminal, `nish:term`) split off
|
|
16
17
|
* the same way since. They are separate so that each carries its own measured
|
|
17
18
|
* code-size ceiling (docs/wp7-runtime.md, "Runtime additions and budget"); a
|
|
18
19
|
* host links all of them, which `scripts/build.sh` does for you when handed
|
|
19
|
-
* runtime.c, runtime-aes.c, runtime-sha256.c and runtime-checksum.c
|
|
20
|
-
* compiler calls their kernels from std/crypto/aes.ts,
|
|
21
|
-
* std/compress/checksum.ts, so `scripts/build.sh` pairs each of them with
|
|
20
|
+
* runtime.c, runtime-aes.c, runtime-sha256.c, runtime-chacha20.c and runtime-checksum.c
|
|
21
|
+
* included: the compiler calls their kernels from std/crypto/aes.ts,
|
|
22
|
+
* std/crypto/sha256.ts, std/crypto/chacha20poly1305.ts and std/compress/checksum.ts, so `scripts/build.sh` pairs each of them with
|
|
22
23
|
* runtime.c and `src/run-cache.ts` caches them like the others. `tests/run.js` checks that every runtime function
|
|
23
24
|
* declared in runtime.ts has a prototype in this file.
|
|
24
25
|
*
|
|
@@ -400,6 +401,28 @@ int32_t nish_sha256_hw(void);
|
|
|
400
401
|
guard, so the caller's fallback panics as the Nish function does. `w` (u32[],
|
|
401
402
|
the message schedule the Nish code fills) is neither read nor written. */
|
|
402
403
|
int32_t nish_sha256_compress(nish_array *h, const nish_array *w, const nish_array *src, int32_t at);
|
|
404
|
+
/* ---- ChaCha20 on vectors ----------------------------------------------------
|
|
405
|
+
* runtime/runtime-chacha20.c: eight blocks at once on AVX2 and four in 128-bit
|
|
406
|
+
* vectors on x86-64, four in NEON on AArch64. The compiler calls the kernel in
|
|
407
|
+
* place of std/crypto/chacha20poly1305.ts's private `chacha20XorInto`, behind
|
|
408
|
+
* `nish_chacha20_hw`, and calls the Nish function itself when either answers 0.
|
|
409
|
+
* Constant time in the key, the nonce and the data. */
|
|
410
|
+
/* 1 when this process runs the kernel, 0 when it runs the Nish code. Resolved
|
|
411
|
+
on the first call and fixed from then on: `NISH_CHACHA20=soft` pins 0; unset,
|
|
412
|
+
`hw` or any other value is the best this machine has: CPUID's AVX2 with the
|
|
413
|
+
OS saving the YMM registers (XGETBV) on x86-64, NEON on AArch64. Always 0 on
|
|
414
|
+
other targets, wasm32 among them. */
|
|
415
|
+
int32_t nish_chacha20_hw(void);
|
|
416
|
+
/* RFC 8439 §2.4's encryption of src[srcAt..srcAt + len) (u8[]) into
|
|
417
|
+
dst[dstAt..dstAt + len) (u8[]) under the ChaCha20 state state[0..16) (u32[]),
|
|
418
|
+
whose block counter state[12] advances one per 64-byte block, as
|
|
419
|
+
`chacha20XorInto` does; 1 when it ran. Answers 0 and writes nothing when
|
|
420
|
+
state.len < 16, len < 256, an offset is negative, a window is outside its
|
|
421
|
+
array, or the windows overlap without being the same bytes, so the Nish
|
|
422
|
+
function runs as it would have. The keystream it makes is wiped before it
|
|
423
|
+
returns; the state is the caller's. */
|
|
424
|
+
int32_t nish_chacha20_xor(nish_array *state, const nish_array *src, int32_t src_at, int32_t len, nish_array *dst,
|
|
425
|
+
int32_t dst_at);
|
|
403
426
|
/* ---- CRC-32 on the CPU's carry-less multiply or CRC instructions ---------
|
|
404
427
|
* runtime/runtime-checksum.c: PCLMULQDQ folding on x86-64, the ARMv8 CRC32
|
|
405
428
|
* instructions on AArch64. The compiler calls the kernel in
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/* Nish runtime, the ChaCha20 half: the keystream of RFC 8439 §2.3 and §2.4,
|
|
2
|
+
* several blocks at once in vector registers. std/crypto/chacha20poly1305.ts
|
|
3
|
+
* is the cipher and the AEAD; this is a faster body for its private
|
|
4
|
+
* `chacha20XorInto`, which the compiler calls in that function's place when
|
|
5
|
+
* `nish_chacha20_hw` says this machine runs it (src/kernels.ts). Poly1305
|
|
6
|
+
* stays in Nish: WP38 S10 timed it beside this kernel and found the Nish
|
|
7
|
+
* code within the noise of a C copy (docs/wp38-simd.md §3.1).
|
|
8
|
+
*
|
|
9
|
+
* A translation unit of its own for the reason runtime-aes.c is one: its own
|
|
10
|
+
* measured `.text*` ceiling in tests/runtime-budgets.js, and a program that
|
|
11
|
+
* never encrypts with ChaCha20 links none of it (section GC).
|
|
12
|
+
*
|
|
13
|
+
* The paths. x86-64 runs eight blocks at once in AVX2's 256-bit registers,
|
|
14
|
+
* then four in 128-bit ones, then one at a time for what is left, all
|
|
15
|
+
* compiled with `target("avx2")` alone so the binary stays a baseline binary.
|
|
16
|
+
* A block is a lane: word `i` of block `b` is lane `b` of vector `i`, so the
|
|
17
|
+
* rounds are the scalar quarter rounds on vectors, and nothing shuffles
|
|
18
|
+
* until the words are written out. AArch64 has the same four-block loop in
|
|
19
|
+
* NEON, which every AArch64 CPU has; `NISH_CHACHA20_ARM_ON` says whether the
|
|
20
|
+
* resolver takes it (see there). A CPU without AVX2, a process with
|
|
21
|
+
* `NISH_CHACHA20=soft`, and every other target, wasm32 included, run the Nish
|
|
22
|
+
* code: the compiler keeps it and branches on `nish_chacha20_hw` at each call,
|
|
23
|
+
* and here the resolver answers 0 and the kernel is a trap nothing reaches.
|
|
24
|
+
*
|
|
25
|
+
* The kernel takes a buffer of four blocks or more and answers 0 for anything
|
|
26
|
+
* shorter, so a short message (a TLS record header, the Poly1305 key's one
|
|
27
|
+
* block, a QUIC header mask) costs no call and no wipe: S10 measured the
|
|
28
|
+
* kernel losing at 64 bytes until it did. It answers 0 too, touching
|
|
29
|
+
* nothing, when a window is outside its array or the two windows overlap
|
|
30
|
+
* without being the same bytes, so the Nish function runs and fails, or
|
|
31
|
+
* writes in its own order, as it would have.
|
|
32
|
+
*
|
|
33
|
+
* Constant time: ChaCha20 is additions, xors and rotations by constants, with
|
|
34
|
+
* no table and no secret-dependent branch. The kernel's branches are on array
|
|
35
|
+
* headers, offsets and the length, which are public, and its loads and stores
|
|
36
|
+
* are at addresses fixed by them. tests/ct-asm-runtime.js reads the
|
|
37
|
+
* `clang -O2` assembly of this file for both targets and holds the kernel to
|
|
38
|
+
* that. The one branch on anything else is `nish_chacha20_hw`'s, on the CPU's
|
|
39
|
+
* feature bits and `NISH_CHACHA20` (docs/security/ct-verification.md).
|
|
40
|
+
*
|
|
41
|
+
* Wiping: the working words, the per-call input block and the keystream
|
|
42
|
+
* buffers on the stack are zeroed by volatile stores before the kernel
|
|
43
|
+
* returns, the mechanism runtime-aes.c and `nish:secret`'s wipe use. The
|
|
44
|
+
* caller's state is the caller's to wipe, as with the Nish function. What
|
|
45
|
+
* the vector registers hold, and what the register allocator spills, no
|
|
46
|
+
* store from C can reach (docs/security/crypto-chacha20-hw.md).
|
|
47
|
+
*
|
|
48
|
+
* Written from RFC 8439 in this file's own structure; the block-a-lane layout
|
|
49
|
+
* is the one §3 of the RFC points at ("SIMD"), and nothing is ported.
|
|
50
|
+
*/
|
|
51
|
+
#include <stddef.h>
|
|
52
|
+
#include <stdint.h>
|
|
53
|
+
|
|
54
|
+
#include "nish.h"
|
|
55
|
+
|
|
56
|
+
#if defined(__x86_64__)
|
|
57
|
+
#include <cpuid.h>
|
|
58
|
+
#define NISH_CHACHA20_X86 1
|
|
59
|
+
#elif defined(__aarch64__) && defined(__ARM_NEON) && defined(__ORDER_LITTLE_ENDIAN__) && \
|
|
60
|
+
__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
|
61
|
+
#include <arm_neon.h>
|
|
62
|
+
#define NISH_CHACHA20_ARM 1
|
|
63
|
+
#endif
|
|
64
|
+
|
|
65
|
+
#if defined(NISH_CHACHA20_X86) || defined(NISH_CHACHA20_ARM)
|
|
66
|
+
|
|
67
|
+
/* Named here rather than through <stdlib.h>, for runtime-aes.c's reason: the
|
|
68
|
+
constant-time check compiles this file for targets whose libc headers the
|
|
69
|
+
host does not have. */
|
|
70
|
+
char *getenv(const char *name);
|
|
71
|
+
|
|
72
|
+
#if defined(__has_attribute) && __has_attribute(no_builtin)
|
|
73
|
+
#define NISH_CHACHA20_NO_LIBC no_builtin
|
|
74
|
+
#else
|
|
75
|
+
#define NISH_CHACHA20_NO_LIBC
|
|
76
|
+
#endif
|
|
77
|
+
#if defined(NISH_CHACHA20_X86)
|
|
78
|
+
#define NISH_CHACHA20_TARGET __attribute__((target("avx2"), NISH_CHACHA20_NO_LIBC))
|
|
79
|
+
#else
|
|
80
|
+
#define NISH_CHACHA20_TARGET __attribute__((NISH_CHACHA20_NO_LIBC))
|
|
81
|
+
#endif
|
|
82
|
+
#define NISH_CHACHA20_INLINE static inline __attribute__((always_inline)) NISH_CHACHA20_TARGET
|
|
83
|
+
|
|
84
|
+
/* The buffer a vector loop needs before it runs: four blocks. */
|
|
85
|
+
#define NISH_CHACHA20_MIN 256
|
|
86
|
+
|
|
87
|
+
/* Zeroes `n` bytes, a multiple of 8, by volatile 8-byte stores. */
|
|
88
|
+
NISH_CHACHA20_INLINE void nish_chacha20_wipe(void *p, size_t n) {
|
|
89
|
+
volatile uint64_t *v = (volatile uint64_t *)p;
|
|
90
|
+
for (size_t i = 0; i < n / 8; i++) v[i] = 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
NISH_CHACHA20_INLINE uint32_t nish_chacha20_rotl(uint32_t x, int n) {
|
|
94
|
+
return (x << n) | (x >> (32 - n));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
#define NISH_CHACHA20_QR(a, b, c, d) \
|
|
98
|
+
a += b; \
|
|
99
|
+
d = nish_chacha20_rotl(d ^ a, 16); \
|
|
100
|
+
c += d; \
|
|
101
|
+
b = nish_chacha20_rotl(b ^ c, 12); \
|
|
102
|
+
a += b; \
|
|
103
|
+
d = nish_chacha20_rotl(d ^ a, 8); \
|
|
104
|
+
c += d; \
|
|
105
|
+
b = nish_chacha20_rotl(b ^ c, 7)
|
|
106
|
+
|
|
107
|
+
/* What is left after the vector loops, a block at a time as the Nish loop
|
|
108
|
+
does: `len` bytes under the keystream from `s`'s counter on, the counter
|
|
109
|
+
moved one a block, the last partial block taken byte by byte. */
|
|
110
|
+
NISH_CHACHA20_INLINE void nish_chacha20_tail(uint32_t s[16], const uint8_t *src, uint8_t *dst, size_t len) {
|
|
111
|
+
uint32_t x[16];
|
|
112
|
+
/* The block's words, which on these little-endian targets are its
|
|
113
|
+
keystream bytes in order: adding into words, not storing byte by byte,
|
|
114
|
+
keeps every store at a fixed offset of the frame. */
|
|
115
|
+
uint32_t ks[16];
|
|
116
|
+
const uint8_t *ks_bytes = (const uint8_t *)ks;
|
|
117
|
+
int ran = len > 0;
|
|
118
|
+
while (len > 0) {
|
|
119
|
+
for (int i = 0; i < 16; i++) x[i] = s[i];
|
|
120
|
+
for (int r = 0; r < 10; r++) {
|
|
121
|
+
NISH_CHACHA20_QR(x[0], x[4], x[8], x[12]);
|
|
122
|
+
NISH_CHACHA20_QR(x[1], x[5], x[9], x[13]);
|
|
123
|
+
NISH_CHACHA20_QR(x[2], x[6], x[10], x[14]);
|
|
124
|
+
NISH_CHACHA20_QR(x[3], x[7], x[11], x[15]);
|
|
125
|
+
NISH_CHACHA20_QR(x[0], x[5], x[10], x[15]);
|
|
126
|
+
NISH_CHACHA20_QR(x[1], x[6], x[11], x[12]);
|
|
127
|
+
NISH_CHACHA20_QR(x[2], x[7], x[8], x[13]);
|
|
128
|
+
NISH_CHACHA20_QR(x[3], x[4], x[9], x[14]);
|
|
129
|
+
}
|
|
130
|
+
for (int i = 0; i < 16; i++) ks[i] = x[i] + s[i];
|
|
131
|
+
s[12] += 1;
|
|
132
|
+
size_t take = len < 64 ? len : 64;
|
|
133
|
+
for (size_t i = 0; i < take; i++) dst[i] = src[i] ^ ks_bytes[i];
|
|
134
|
+
src += take;
|
|
135
|
+
dst += take;
|
|
136
|
+
len -= take;
|
|
137
|
+
}
|
|
138
|
+
if (ran) {
|
|
139
|
+
nish_chacha20_wipe(x, sizeof x);
|
|
140
|
+
nish_chacha20_wipe(ks, sizeof ks);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
#if defined(NISH_CHACHA20_X86)
|
|
145
|
+
|
|
146
|
+
typedef uint32_t nish_u32x4 __attribute__((vector_size(16)));
|
|
147
|
+
typedef uint32_t nish_u32x8 __attribute__((vector_size(32)));
|
|
148
|
+
|
|
149
|
+
/* The rotate lane by lane; LLVM turns 16 and 8 into byte shuffles. */
|
|
150
|
+
#define NISH_CHACHA20_VROTL(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
|
|
151
|
+
|
|
152
|
+
#define NISH_CHACHA20_VQR(a, b, c, d) \
|
|
153
|
+
a += b; \
|
|
154
|
+
d = NISH_CHACHA20_VROTL(d ^ a, 16); \
|
|
155
|
+
c += d; \
|
|
156
|
+
b = NISH_CHACHA20_VROTL(b ^ c, 12); \
|
|
157
|
+
a += b; \
|
|
158
|
+
d = NISH_CHACHA20_VROTL(d ^ a, 8); \
|
|
159
|
+
c += d; \
|
|
160
|
+
b = NISH_CHACHA20_VROTL(b ^ c, 7)
|
|
161
|
+
|
|
162
|
+
#define NISH_CHACHA20_VROUNDS(x) \
|
|
163
|
+
for (int r = 0; r < 10; r++) { \
|
|
164
|
+
NISH_CHACHA20_VQR(x[0], x[4], x[8], x[12]); \
|
|
165
|
+
NISH_CHACHA20_VQR(x[1], x[5], x[9], x[13]); \
|
|
166
|
+
NISH_CHACHA20_VQR(x[2], x[6], x[10], x[14]); \
|
|
167
|
+
NISH_CHACHA20_VQR(x[3], x[7], x[11], x[15]); \
|
|
168
|
+
NISH_CHACHA20_VQR(x[0], x[5], x[10], x[15]); \
|
|
169
|
+
NISH_CHACHA20_VQR(x[1], x[6], x[11], x[12]); \
|
|
170
|
+
NISH_CHACHA20_VQR(x[2], x[7], x[8], x[13]); \
|
|
171
|
+
NISH_CHACHA20_VQR(x[3], x[4], x[9], x[14]); \
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* `lanes` blocks at once, `vec` lanes of `lanes` words: the input block with
|
|
175
|
+
the counter stepped one a lane, the rounds, then the sum written out word
|
|
176
|
+
by word (lane `b` of word `i` to byte 64b + 4i of the keystream) and
|
|
177
|
+
xored into `dst` a vector at a time. Answers the bytes it covered. */
|
|
178
|
+
#define NISH_CHACHA20_LOOP(vec, lanes, s, src, dst, len, ...) \
|
|
179
|
+
{ \
|
|
180
|
+
vec x[16]; \
|
|
181
|
+
vec in[16]; \
|
|
182
|
+
uint8_t ks[64 * (lanes)] __attribute__((aligned(32))); \
|
|
183
|
+
const vec step = {__VA_ARGS__}; \
|
|
184
|
+
int ran = (len) >= 64 * (lanes); \
|
|
185
|
+
while ((len) >= 64 * (lanes)) { \
|
|
186
|
+
for (int i = 0; i < 16; i++) in[i] = (vec){0} + (s)[i]; \
|
|
187
|
+
in[12] += step; \
|
|
188
|
+
for (int i = 0; i < 16; i++) x[i] = in[i]; \
|
|
189
|
+
NISH_CHACHA20_VROUNDS(x) \
|
|
190
|
+
for (int i = 0; i < 16; i++) { \
|
|
191
|
+
vec w = x[i] + in[i]; \
|
|
192
|
+
for (int b = 0; b < (lanes); b++) __builtin_memcpy(ks + 64 * b + 4 * i, (const uint8_t *)&w + 4 * b, 4); \
|
|
193
|
+
} \
|
|
194
|
+
for (size_t i = 0; i < 64 * (lanes); i += sizeof(vec)) { \
|
|
195
|
+
vec p, k; \
|
|
196
|
+
__builtin_memcpy(&p, (src) + i, sizeof p); \
|
|
197
|
+
__builtin_memcpy(&k, ks + i, sizeof k); \
|
|
198
|
+
p ^= k; \
|
|
199
|
+
__builtin_memcpy((dst) + i, &p, sizeof p); \
|
|
200
|
+
} \
|
|
201
|
+
(s)[12] += (lanes); \
|
|
202
|
+
(src) += 64 * (lanes); \
|
|
203
|
+
(dst) += 64 * (lanes); \
|
|
204
|
+
(len) -= 64 * (lanes); \
|
|
205
|
+
} \
|
|
206
|
+
if (ran) { \
|
|
207
|
+
nish_chacha20_wipe(x, sizeof x); \
|
|
208
|
+
nish_chacha20_wipe(in, sizeof in); \
|
|
209
|
+
nish_chacha20_wipe(ks, sizeof ks); \
|
|
210
|
+
} \
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
NISH_CHACHA20_INLINE void nish_chacha20_blocks(uint32_t s[16], const uint8_t *src, uint8_t *dst, size_t len) {
|
|
214
|
+
NISH_CHACHA20_LOOP(nish_u32x8, 8, s, src, dst, len, 0, 1, 2, 3, 4, 5, 6, 7)
|
|
215
|
+
NISH_CHACHA20_LOOP(nish_u32x4, 4, s, src, dst, len, 0, 1, 2, 3)
|
|
216
|
+
nish_chacha20_tail(s, src, dst, len);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#else
|
|
220
|
+
|
|
221
|
+
#define NISH_CHACHA20_NROTL(x, n) vsriq_n_u32(vshlq_n_u32((x), (n)), (x), 32 - (n))
|
|
222
|
+
#define NISH_CHACHA20_NROT16(x) vreinterpretq_u32_u16(vrev32q_u16(vreinterpretq_u16_u32(x)))
|
|
223
|
+
#define NISH_CHACHA20_NQR(a, b, c, d) \
|
|
224
|
+
a = vaddq_u32(a, b); \
|
|
225
|
+
d = NISH_CHACHA20_NROT16(veorq_u32(d, a)); \
|
|
226
|
+
c = vaddq_u32(c, d); \
|
|
227
|
+
b = NISH_CHACHA20_NROTL(veorq_u32(b, c), 12); \
|
|
228
|
+
a = vaddq_u32(a, b); \
|
|
229
|
+
d = NISH_CHACHA20_NROTL(veorq_u32(d, a), 8); \
|
|
230
|
+
c = vaddq_u32(c, d); \
|
|
231
|
+
b = NISH_CHACHA20_NROTL(veorq_u32(b, c), 7)
|
|
232
|
+
|
|
233
|
+
/* Four blocks at once in NEON, a block a lane; `vst4q` interleaves four
|
|
234
|
+
words so each 16-byte row it stores is one row of one block. */
|
|
235
|
+
NISH_CHACHA20_INLINE void nish_chacha20_blocks(uint32_t s[16], const uint8_t *src, uint8_t *dst, size_t len) {
|
|
236
|
+
uint32x4_t x[16];
|
|
237
|
+
uint32x4_t in[16];
|
|
238
|
+
uint32_t rows[16] __attribute__((aligned(16)));
|
|
239
|
+
uint8_t ks[256] __attribute__((aligned(16)));
|
|
240
|
+
const uint32_t step_words[4] = {0, 1, 2, 3};
|
|
241
|
+
const uint32x4_t step = vld1q_u32(step_words);
|
|
242
|
+
int ran = len >= 256;
|
|
243
|
+
while (len >= 256) {
|
|
244
|
+
for (int i = 0; i < 16; i++) in[i] = vdupq_n_u32(s[i]);
|
|
245
|
+
in[12] = vaddq_u32(in[12], step);
|
|
246
|
+
for (int i = 0; i < 16; i++) x[i] = in[i];
|
|
247
|
+
for (int r = 0; r < 10; r++) {
|
|
248
|
+
NISH_CHACHA20_NQR(x[0], x[4], x[8], x[12]);
|
|
249
|
+
NISH_CHACHA20_NQR(x[1], x[5], x[9], x[13]);
|
|
250
|
+
NISH_CHACHA20_NQR(x[2], x[6], x[10], x[14]);
|
|
251
|
+
NISH_CHACHA20_NQR(x[3], x[7], x[11], x[15]);
|
|
252
|
+
NISH_CHACHA20_NQR(x[0], x[5], x[10], x[15]);
|
|
253
|
+
NISH_CHACHA20_NQR(x[1], x[6], x[11], x[12]);
|
|
254
|
+
NISH_CHACHA20_NQR(x[2], x[7], x[8], x[13]);
|
|
255
|
+
NISH_CHACHA20_NQR(x[3], x[4], x[9], x[14]);
|
|
256
|
+
}
|
|
257
|
+
for (int i = 0; i < 16; i += 4) {
|
|
258
|
+
uint32x4x4_t w;
|
|
259
|
+
w.val[0] = vaddq_u32(x[i], in[i]);
|
|
260
|
+
w.val[1] = vaddq_u32(x[i + 1], in[i + 1]);
|
|
261
|
+
w.val[2] = vaddq_u32(x[i + 2], in[i + 2]);
|
|
262
|
+
w.val[3] = vaddq_u32(x[i + 3], in[i + 3]);
|
|
263
|
+
vst4q_u32(rows, w);
|
|
264
|
+
for (int b = 0; b < 4; b++) __builtin_memcpy(ks + 64 * b + 4 * i, rows + 4 * b, 16);
|
|
265
|
+
}
|
|
266
|
+
for (size_t i = 0; i < 256; i += 16) vst1q_u8(dst + i, veorq_u8(vld1q_u8(src + i), vld1q_u8(ks + i)));
|
|
267
|
+
s[12] += 4;
|
|
268
|
+
src += 256;
|
|
269
|
+
dst += 256;
|
|
270
|
+
len -= 256;
|
|
271
|
+
}
|
|
272
|
+
if (ran) {
|
|
273
|
+
nish_chacha20_wipe(x, sizeof x);
|
|
274
|
+
nish_chacha20_wipe(in, sizeof in);
|
|
275
|
+
nish_chacha20_wipe(rows, sizeof rows);
|
|
276
|
+
nish_chacha20_wipe(ks, sizeof ks);
|
|
277
|
+
}
|
|
278
|
+
nish_chacha20_tail(s, src, dst, len);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
#endif
|
|
282
|
+
|
|
283
|
+
/* `chacha20XorInto`'s contract, checked rather than trusted: 1 when it ran,
|
|
284
|
+
0, writing nothing, unless `state` has its sixteen words, `len` is four
|
|
285
|
+
blocks or more, both windows are inside their arrays and end before
|
|
286
|
+
INT32_MAX - 64, and the windows are the same bytes or do not overlap.
|
|
287
|
+
The Nish function reads and writes byte
|
|
288
|
+
by byte in order, so an in-place call (the AEAD's and `chacha20Block`'s)
|
|
289
|
+
agrees with a kernel that loads a vector before it stores it; a shifted
|
|
290
|
+
overlap would not, and is left to the Nish function. */
|
|
291
|
+
NISH_CHACHA20_TARGET int32_t nish_chacha20_xor(nish_array *state, const nish_array *src, int32_t src_at,
|
|
292
|
+
int32_t len, nish_array *dst, int32_t dst_at) {
|
|
293
|
+
if (state->len < 16 || len < NISH_CHACHA20_MIN || src_at < 0 || dst_at < 0) return 0;
|
|
294
|
+
if ((uint64_t)src_at + (uint64_t)len > src->len || (uint64_t)dst_at + (uint64_t)len > dst->len) return 0;
|
|
295
|
+
/* The Nish function's i32 sums reach a window's end and its last block's
|
|
296
|
+
end, so past INT32_MAX - 64 it panics on an overflow; past that the
|
|
297
|
+
kernel refuses, and the Nish function panics as it would have. */
|
|
298
|
+
if ((uint64_t)src_at + (uint64_t)len > INT32_MAX - 64 || (uint64_t)dst_at + (uint64_t)len > INT32_MAX - 64) return 0;
|
|
299
|
+
const uint8_t *from = (const uint8_t *)src->data + src_at;
|
|
300
|
+
uint8_t *to = (uint8_t *)dst->data + dst_at;
|
|
301
|
+
uintptr_t f = (uintptr_t)from, t = (uintptr_t)to;
|
|
302
|
+
if (f != t && f < t + (uintptr_t)len && t < f + (uintptr_t)len) return 0;
|
|
303
|
+
nish_chacha20_blocks((uint32_t *)(void *)state->data, from, to, (size_t)len);
|
|
304
|
+
return 1;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
#if defined(NISH_CHACHA20_X86)
|
|
308
|
+
/* Whether this CPU and its OS give AVX2: CPUID leaf 1's OSXSAVE and AVX,
|
|
309
|
+
XCR0's SSE and AVX state, and leaf 7's AVX2. Asked through `cpuid.h`, for
|
|
310
|
+
runtime-aes.c's reason. */
|
|
311
|
+
static int32_t nish_chacha20_cpu(void) {
|
|
312
|
+
unsigned a, b, c, d;
|
|
313
|
+
if (!__get_cpuid(1, &a, &b, &c, &d)) return 0;
|
|
314
|
+
if ((c & bit_OSXSAVE) == 0 || (c & bit_AVX) == 0) return 0;
|
|
315
|
+
unsigned lo, hi;
|
|
316
|
+
__asm__("xgetbv" : "=a"(lo), "=d"(hi) : "c"(0));
|
|
317
|
+
(void)hi;
|
|
318
|
+
if ((lo & 6) != 6) return 0;
|
|
319
|
+
if (!__get_cpuid_count(7, 0, &a, &b, &c, &d)) return 0;
|
|
320
|
+
return (b & bit_AVX2) != 0;
|
|
321
|
+
}
|
|
322
|
+
#else
|
|
323
|
+
/* NEON is in every AArch64 CPU, so the question is only whether the kernel
|
|
324
|
+
pays here: `NISH_CHACHA20_ARM_ON` is S10's AArch64 verdict
|
|
325
|
+
(docs/wp38-simd.md §3.1). */
|
|
326
|
+
#ifndef NISH_CHACHA20_ARM_ON
|
|
327
|
+
#define NISH_CHACHA20_ARM_ON 1
|
|
328
|
+
#endif
|
|
329
|
+
static int32_t nish_chacha20_cpu(void) {
|
|
330
|
+
return NISH_CHACHA20_ARM_ON;
|
|
331
|
+
}
|
|
332
|
+
#endif
|
|
333
|
+
|
|
334
|
+
/* The answer: 0 until the first call, then 1 for the kernel or 2 for the Nish
|
|
335
|
+
path, with runtime-aes.c's relaxed atomics for the same reason. */
|
|
336
|
+
static int32_t nish_chacha20_answer;
|
|
337
|
+
|
|
338
|
+
int32_t nish_chacha20_hw(void) {
|
|
339
|
+
int32_t answer = __atomic_load_n(&nish_chacha20_answer, __ATOMIC_RELAXED);
|
|
340
|
+
if (answer == 0) {
|
|
341
|
+
/* `soft` pins the Nish path and never asks the CPU; anything else, or
|
|
342
|
+
nothing, means the best this machine has. */
|
|
343
|
+
const char *pin = getenv("NISH_CHACHA20");
|
|
344
|
+
int soft = pin != 0 && pin[0] == 's' && pin[1] == 'o' && pin[2] == 'f' && pin[3] == 't' && pin[4] == 0;
|
|
345
|
+
answer = !soft && nish_chacha20_cpu() ? 1 : 2;
|
|
346
|
+
__atomic_store_n(&nish_chacha20_answer, answer, __ATOMIC_RELAXED);
|
|
347
|
+
}
|
|
348
|
+
return answer == 1;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
#else
|
|
352
|
+
|
|
353
|
+
/* A target with no vector path here, wasm32 among them: the Nish path
|
|
354
|
+
always, and a kernel that a program whose every call is behind
|
|
355
|
+
`nish_chacha20_hw` never reaches. */
|
|
356
|
+
int32_t nish_chacha20_hw(void) {
|
|
357
|
+
return 0;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
int32_t nish_chacha20_xor(nish_array *state, const nish_array *src, int32_t src_at, int32_t len, nish_array *dst,
|
|
361
|
+
int32_t dst_at) {
|
|
362
|
+
(void)state, (void)src, (void)src_at, (void)len, (void)dst, (void)dst_at;
|
|
363
|
+
__builtin_trap();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
#endif
|
package/scripts/build.sh
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
#
|
|
4
4
|
# scripts/build.sh <module.ll> [more .ll/.c files...] -o <out> [--profile debug|speed|size|wasm|wasi|napi]
|
|
5
5
|
#
|
|
6
|
-
# The C runtime is
|
|
6
|
+
# The C runtime is sixteen translation units and is named as one: an input
|
|
7
7
|
# <dir>/runtime.c also compiles <dir>/runtime-os.c, the half that wraps the
|
|
8
8
|
# system calls (files, directories, subprocesses, the environment, the clock),
|
|
9
9
|
# <dir>/runtime-parallel.c, the half that divides a range of work across
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
# the SSE4.1 rounding of `Math.floor` and its kin,
|
|
15
15
|
# <dir>/runtime-aes.c, AES-GCM on the CPU's AES instructions,
|
|
16
16
|
# <dir>/runtime-sha256.c, SHA-256 on the CPU's SHA instructions,
|
|
17
|
+
# <dir>/runtime-chacha20.c, ChaCha20's keystream on AVX2 or NEON vectors,
|
|
17
18
|
# <dir>/runtime-checksum.c, CRC-32 on its carry-less multiply or CRC instructions,
|
|
18
19
|
# <dir>/runtime-term.c, stdin and the terminal of `nish:term`,
|
|
19
20
|
# <dir>/runtime-bytes.c, the byte-class runs of `nish/bytes`,
|
|
@@ -223,14 +224,14 @@ if [ -n "$sqlite_to" ] || [ -n "$sqlite_from" ]; then
|
|
|
223
224
|
fi
|
|
224
225
|
[ -n "$out" ] || [ -n "$objects_to" ] || [ -n "$sqlite_to" ] || { echo "error: -o <out> is required" >&2; exit 2; }
|
|
225
226
|
|
|
226
|
-
# The runtime is
|
|
227
|
+
# The runtime is sixteen translation units, and a caller names one: whoever passes
|
|
227
228
|
# <dir>/runtime.c gets <dir>/runtime-os.c, <dir>/runtime-parallel.c,
|
|
228
229
|
# <dir>/runtime-cross.c, <dir>/runtime-host.c, <dir>/runtime-net.c, <dir>/runtime-simd.c,
|
|
229
|
-
# <dir>/runtime-aes.c, <dir>/runtime-sha256.c, <dir>/runtime-checksum.c, <dir>/runtime-term.c,
|
|
230
|
+
# <dir>/runtime-aes.c, <dir>/runtime-sha256.c, <dir>/runtime-chacha20.c, <dir>/runtime-checksum.c, <dir>/runtime-term.c,
|
|
230
231
|
# <dir>/runtime-bytes.c, <dir>/runtime-sandbox.c, <dir>/runtime-shm.c and <dir>/runtime-region.c compiled beside it, on every profile,
|
|
231
232
|
# wasi included: runtime-simd.c and runtime-bytes.c take their scalar paths there
|
|
232
233
|
# unless the build adds -msimd128,
|
|
233
|
-
# runtime-aes.c, runtime-sha256.c and runtime-checksum.c compile to an answer of 0 and
|
|
234
|
+
# runtime-aes.c, runtime-sha256.c, runtime-chacha20.c and runtime-checksum.c compile to an answer of 0 and
|
|
234
235
|
# stubs that nothing calls, because the compiler emits no call to their kernels for wasm32, and
|
|
235
236
|
# runtime-term.c reads stdin and answers "not a terminal", and runtime-sandbox.c
|
|
236
237
|
# compiles to two functions that refuse to run, which no wasm build calls, and
|
|
@@ -244,7 +245,7 @@ fi
|
|
|
244
245
|
# here keeps every one of those correct, and keeps "the runtime" one thing to
|
|
245
246
|
# name from the outside. A caller that names one itself is left alone, because
|
|
246
247
|
# naming one object twice is a duplicate-symbol error.
|
|
247
|
-
halves="runtime-os.c runtime-parallel.c runtime-cross.c runtime-host.c runtime-net.c runtime-simd.c runtime-aes.c runtime-sha256.c runtime-checksum.c runtime-term.c runtime-bytes.c runtime-sandbox.c runtime-shm.c runtime-region.c"
|
|
248
|
+
halves="runtime-os.c runtime-parallel.c runtime-cross.c runtime-host.c runtime-net.c runtime-simd.c runtime-aes.c runtime-sha256.c runtime-chacha20.c runtime-checksum.c runtime-term.c runtime-bytes.c runtime-sandbox.c runtime-shm.c runtime-region.c"
|
|
248
249
|
for i in ${inputs[@]+"${inputs[@]}"}; do
|
|
249
250
|
case "$i" in
|
|
250
251
|
*/runtime.c|runtime.c)
|
|
@@ -693,14 +694,16 @@ case "$profile" in
|
|
|
693
694
|
fi
|
|
694
695
|
# IR compiled for the host rather than with `--target wasm32` calls std/crypto/aes.ts's
|
|
695
696
|
# AES kernels behind `nish_aes_hw`, std/crypto/sha256.ts's block function behind
|
|
696
|
-
# `nish_sha256_hw
|
|
697
|
-
# `
|
|
698
|
-
# runtime-
|
|
697
|
+
# `nish_sha256_hw`, std/crypto/chacha20poly1305.ts's keystream loop behind
|
|
698
|
+
# `nish_chacha20_hw` and std/compress/checksum.ts's CRC-32 kernel behind
|
|
699
|
+
# `nish_crc32_hw` (src/kernels.ts). runtime-aes.c's, runtime-sha256.c's,
|
|
700
|
+
# runtime-chacha20.c's and runtime-checksum.c's wasm32 builds answer 0 there,
|
|
701
|
+
# so the Nish code runs, and
|
|
699
702
|
# each is added only for such IR: every other module links the bytes it always did.
|
|
700
703
|
# Each pair is a unit and the kernel its probe names.
|
|
701
704
|
# A caller that names the unit itself is left alone, as `halves` leaves it:
|
|
702
705
|
# one object named twice is a duplicate-symbol error.
|
|
703
|
-
for pair in aes:aes sha256:sha256 checksum:crc32; do
|
|
706
|
+
for pair in aes:aes sha256:sha256 chacha20:chacha20 checksum:crc32; do
|
|
704
707
|
unit=${pair%%:*}
|
|
705
708
|
kernel=${pair#*:}
|
|
706
709
|
named=0
|
package/std/README.md
CHANGED
|
@@ -101,7 +101,7 @@ is listed in [`SECURITY.md`](../SECURITY.md#threat-model).
|
|
|
101
101
|
| [`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 |
|
|
102
102
|
| [`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` |
|
|
103
103
|
| [`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 |
|
|
104
|
-
| [`crypto/chacha20poly1305.ts`](./crypto/chacha20poly1305.ts) | `chacha20Poly1305Seal(key, nonce, aad, plaintext)`, which answers the ciphertext followed by the 16-byte tag, and `chacha20Poly1305Open`, which checks the whole tag before it decrypts anything and answers `null` for a message that does not authenticate. Beneath them `chacha20Block`, `chacha20`, `chacha20QuarterRound`, `poly1305` and `poly1305KeyGen`, and `chacha20HeaderMask`, QUIC's 5-byte header-protection mask. Every input of the wrong length answers `null`. What a call derives from the key — the ChaCha20 state, the keystream, the Poly1305 key, `r` and accumulator, and on open the tag it compared — is wiped with `wipe`'s volatile stores before the call returns, the refusal path included | RFC 8439 §2, RFC 9001 §5.4.4, Wycheproof `chacha20_poly1305` (all 325 cases) |
|
|
104
|
+
| [`crypto/chacha20poly1305.ts`](./crypto/chacha20poly1305.ts) | `chacha20Poly1305Seal(key, nonce, aad, plaintext)`, which answers the ciphertext followed by the 16-byte tag, and `chacha20Poly1305Open`, which checks the whole tag before it decrypts anything and answers `null` for a message that does not authenticate. Beneath them `chacha20Block`, `chacha20`, `chacha20QuarterRound`, `poly1305` and `poly1305KeyGen`, and `chacha20HeaderMask`, QUIC's 5-byte header-protection mask. Every input of the wrong length answers `null`. What a call derives from the key — the ChaCha20 state, the keystream, the Poly1305 key, `r` and accumulator, and on open the tag it compared — is wiped with `wipe`'s volatile stores before the call returns, the refusal path included. Natively, on x86-64 with AVX2 or on AArch64, the compiler sends the keystream of a message of 256 bytes or more to the runtime's kernel instead (`runtime/runtime-chacha20.c`, WP38 S10), and `NISH_CHACHA20=soft` pins the Nish code | RFC 8439 §2, RFC 9001 §5.4.4, Wycheproof `chacha20_poly1305` (all 325 cases) |
|
|
105
105
|
| [`crypto/aes.ts`](./crypto/aes.ts) | AES-128 and AES-256, bitsliced four blocks at a time with the Boyar–Peralta S-box circuit, so no table is read; natively, on a CPU with AES-NI and PCLMULQDQ or the ARMv8 AES and PMULL instructions, the compiler sends the key schedule, the block, GCTR and GHASH to the runtime's kernels instead (`runtime/runtime-aes.c`, WP38 S5), and `NISH_AES=soft` pins the bitsliced code: `aesKey(key)` expands a 16- or 32-byte key into an `AesKey` (or `aesKeyInto(key, out)` into one the caller holds, allocating nothing that outlives the call), then `aesEncryptBlock`, `aesGcmSeal` / `aesGcmOpen` (tag last, checked in full before anything is decrypted; any non-empty IV) and `aesHeaderMask`. AES-192 is out of scope, and a wrong length answers `null`, as does an `AesKey` not made by `aesKey`; one whose H is zero opens nothing. An `AesKey` holds its round keys in `roundKeys` and H in `h`, two `u64` words (high, low); `aesKeyWipe(key)` zeroes both with `wipe`'s volatile stores, the kernels' powers of H after the schedule included, and is how a holder drops a key. The bitsliced schedule's, block's and GCTR's working copies, the tag's E(K, J0) block, the tag open compared and J0 itself are wiped the same way before they are released | FIPS 197, SP 800-38D, RFC 9001 §5.4.3, Wycheproof `aes_gcm` (all 316 cases; the 103 AES-192 ones check that the key is refused) |
|
|
106
106
|
| [`crypto/p256.ts`](./crypto/p256.ts) | ECDSA over P-256 with RFC 6979's deterministic nonces: `p256PublicKey(priv)` (65-byte uncompressed SEC1) from a `Secret<u8[]>` private key, `p256Sign` / `p256Verify` over a digest and `p256SignSha256` / `p256VerifySha256` over a message, and `p256SignSha256Into(scratch, priv, msg, off, len, out, at)`, the same signature written into the caller's window in a `P256SignScratch` made once, keeping nothing in the arena; a signature is `r` and `s`, 32 big-endian bytes each, not DER (`crypto/x509.ts`'s `x509DerSignatureRS` converts one). A malformed key, a point off the curve or an `r` or `s` out of range answers `null` or `false`, never a panic. ECDSA is malleable, and `p256Verify` accepts a high `s`: when `(r, s)` verifies, so does `(r, n − s)` | RFC 6979 A.2.5, Wycheproof `ecdsa_secp256r1_sha256`, `_sha256_p1363`, `_sha512` and `_sha512_p1363` |
|
|
107
107
|
| [`crypto/p384.ts`](./crypto/p384.ts) | ECDSA verification over P-384: `p384Verify(pub, digest, sig)` over a digest from any hash (one longer than 48 bytes is truncated to its leftmost 384 bits) and `p384VerifySha384(pub, msg, sig)` over a message; the key is 97-byte uncompressed SEC1 and the signature `r` and `s`, 48 big-endian bytes each, not DER. It only verifies, so it holds no `Secret`. A malformed key, a point off the curve, an `r` or `s` out of range and a sum at the identity answer `false`, never a panic. ECDSA is malleable, and `p384Verify` accepts a high `s` | RFC 6979 A.2.6, Wycheproof `ecdsa_secp384r1_sha384`, `_sha384_p1363`, `_sha256`, `_sha512` and `_sha512_p1363` |
|
package/std/net/http1-codec.ts
CHANGED
|
@@ -506,17 +506,19 @@ const http1StartRequest = (p: Http1Parser): void => {
|
|
|
506
506
|
}
|
|
507
507
|
|
|
508
508
|
/**
|
|
509
|
-
*
|
|
510
|
-
*
|
|
511
|
-
*
|
|
512
|
-
*
|
|
513
|
-
* `
|
|
514
|
-
*
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
*
|
|
509
|
+
* The bytes of `x` below 32 or equal to 127 — a control byte or DEL, which
|
|
510
|
+
* `http1TakeLine` stops at — as their high bits: zero when there is none,
|
|
511
|
+
* and else the lowest set bit is the first such byte's. Two of the bit
|
|
512
|
+
* tricks of "Bit Twiddling Hacks" (S. E. Anderson, public domain): a byte
|
|
513
|
+
* below `n` leaves its high bit set in `(x - n * ones) & ~x`, for `n` up to
|
|
514
|
+
* 128, and `127` is the byte that is zero in `x ^ 127 * ones`. A byte from
|
|
515
|
+
* 128 up has its high bit set in `x`, so `~x` clears it: obs-text never
|
|
516
|
+
* matches. A borrow only runs up from a byte that matched, so the bits above
|
|
517
|
+
* the first match may be false and the lowest is exact, in each mask and so
|
|
518
|
+
* in their union. Each subtraction is masked to 64 bits because a `u64` is a
|
|
519
|
+
* BigInt under Node, which does not wrap; natively the mask is a no-op.
|
|
518
520
|
*/
|
|
519
|
-
const
|
|
521
|
+
const http1ControlBits = (x: u64): u64 => {
|
|
520
522
|
const ones32: u64 = toU64(16843009)
|
|
521
523
|
const ones: u64 = (ones32 << toU64(32)) | ones32
|
|
522
524
|
const low32: u64 = 0xffffffff
|
|
@@ -525,7 +527,18 @@ const http1WordHasControl = (x: u64): boolean => {
|
|
|
525
527
|
const below: u64 = (x - ones * toU64(32)) & all & ~x & high
|
|
526
528
|
const y: u64 = x ^ (ones * toU64(127))
|
|
527
529
|
const del: u64 = (y - ones) & all & ~y & high
|
|
528
|
-
return
|
|
530
|
+
return below | del
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/** Which byte of a word `bits`'s lowest set bit is in, 0 to 7; `bits` is not zero. */
|
|
534
|
+
const http1FirstByte = (bits: u64): i32 => {
|
|
535
|
+
const mask32: u64 = 0xffffffff
|
|
536
|
+
const lo: u32 = toU32(bits & mask32)
|
|
537
|
+
if (lo !== toU32(0)) {
|
|
538
|
+
return (toI32(31) - toI32(Math.clz32(lo & (toU32(0) - lo)))) >> toI32(3)
|
|
539
|
+
}
|
|
540
|
+
const hi: u32 = toU32(bits >> toU64(32))
|
|
541
|
+
return toI32(4) + ((toI32(31) - toI32(Math.clz32(hi & (toU32(0) - hi)))) >> toI32(3))
|
|
529
542
|
}
|
|
530
543
|
|
|
531
544
|
/**
|
|
@@ -585,22 +598,39 @@ const http1TakeLine = (p: Http1Parser, limit: i32, overStatus: i32): i32 => {
|
|
|
585
598
|
}
|
|
586
599
|
while (i < stop) {
|
|
587
600
|
// Most bytes are printable: sixteen at a time while a whole vector is,
|
|
588
|
-
//
|
|
589
|
-
//
|
|
590
|
-
|
|
601
|
+
// and the vector that is not answers where its first control byte is.
|
|
602
|
+
// The bytes after a control byte, and a tail shorter than a vector, go
|
|
603
|
+
// one at a time.
|
|
604
|
+
let found: boolean = false
|
|
605
|
+
while (!found && i >= 0 && i <= stop - 16 && i <= toI32(buf.length) - 16) {
|
|
591
606
|
const v: u8x16 = loadU8x16(buf, i)
|
|
592
|
-
|
|
593
|
-
|
|
607
|
+
const low: u64 = http1ControlBits(http1Lanes(v, 0))
|
|
608
|
+
if (low !== toU64(0)) {
|
|
609
|
+
i += http1FirstByte(low)
|
|
610
|
+
found = true
|
|
611
|
+
} else {
|
|
612
|
+
const high: u64 = http1ControlBits(http1Lanes(v, 8))
|
|
613
|
+
if (high !== toU64(0)) {
|
|
614
|
+
i += 8 + http1FirstByte(high)
|
|
615
|
+
found = true
|
|
616
|
+
} else {
|
|
617
|
+
i += 16
|
|
618
|
+
}
|
|
594
619
|
}
|
|
595
|
-
i += 16
|
|
596
620
|
}
|
|
597
|
-
while (i >= 0 && i < stop && i < toI32(buf.length) && buf[i] >= 32 && buf[i] !== 127) {
|
|
621
|
+
while (!found && i >= 0 && i < stop && i < toI32(buf.length) && buf[i] >= 32 && buf[i] !== 127) {
|
|
598
622
|
i += 1
|
|
599
623
|
}
|
|
600
624
|
if (i < 0 || i >= stop || i >= toI32(buf.length)) {
|
|
601
625
|
break
|
|
602
626
|
}
|
|
603
|
-
|
|
627
|
+
let c: i32 = toI32(buf[i])
|
|
628
|
+
if (c === 13 && i + 1 < stop && i + 1 < toI32(buf.length) && buf[i + 1] === 10) {
|
|
629
|
+
// CRLF, the end of almost every line: its LF is the next byte, so it
|
|
630
|
+
// is taken here rather than by another trip through the scan.
|
|
631
|
+
i += 1
|
|
632
|
+
c = 10
|
|
633
|
+
}
|
|
604
634
|
if (c === 10) {
|
|
605
635
|
if (i === p.start || buf[i - 1] !== 13) {
|
|
606
636
|
return http1Fail(p, 400, "a line ended in a bare LF")
|
|
@@ -636,9 +666,11 @@ const http1TakeLine = (p: Http1Parser, limit: i32, overStatus: i32): i32 => {
|
|
|
636
666
|
* `c - 64` and `c - 96` of each: `!` `#`-`'` `*` `+` `-` `.` and the digits
|
|
637
667
|
* in the first (0x03ff6cfa); the upper-case letters, `^` and `_` in the
|
|
638
668
|
* second (0xc7fffffe); `` ` ``, the lower-case letters, `|` and `~` in the
|
|
639
|
-
* third (0x57ffffff). No byte below 33 or above 126 is a tchar.
|
|
640
|
-
*
|
|
641
|
-
*
|
|
669
|
+
* third (0x57ffffff). No byte below 33 or above 126 is a tchar.
|
|
670
|
+
* `http1IsTokenByte` reads them as two 64-bit masks, one for the bytes 0 to
|
|
671
|
+
* 63 and one for 64 to 127, so the test is a select and a shift with no
|
|
672
|
+
* branch on the byte: a name or method scan is the parser's hottest loop
|
|
673
|
+
* after the line scan (`bench/http1-parse`).
|
|
642
674
|
*/
|
|
643
675
|
const HTTP1_TCHAR_32: i32 = 67071226
|
|
644
676
|
const HTTP1_TCHAR_64: i32 = -939524098
|
|
@@ -646,16 +678,10 @@ const HTTP1_TCHAR_96: i32 = 1476395007
|
|
|
646
678
|
|
|
647
679
|
/** Whether `c` is a tchar of RFC 9110 §5.6.2, the bytes a token is made of. */
|
|
648
680
|
export const http1IsTokenByte = (c: i32): boolean => {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
if (c < 64) {
|
|
654
|
-
mask = HTTP1_TCHAR_32
|
|
655
|
-
} else if (c < 96) {
|
|
656
|
-
mask = HTTP1_TCHAR_64
|
|
657
|
-
}
|
|
658
|
-
return ((mask >> (c & 31)) & 1) !== 0
|
|
681
|
+
const low: u64 = toU64(toU32(HTTP1_TCHAR_32)) << toU64(32)
|
|
682
|
+
const high: u64 = (toU64(toU32(HTTP1_TCHAR_96)) << toU64(32)) | toU64(toU32(HTTP1_TCHAR_64))
|
|
683
|
+
const word: u64 = c < 64 ? low : high
|
|
684
|
+
return c >= 0 && c < 128 && ((word >> toU64(c & 63)) & toU64(1)) !== toU64(0)
|
|
659
685
|
}
|
|
660
686
|
|
|
661
687
|
/** Whether `c` may appear in a field value (RFC 9110 §5.5): HTAB, SP, VCHAR or obs-text. */
|
|
@@ -978,9 +1004,30 @@ const http1FieldLine = (p: Http1Parser, keep: boolean): i32 => {
|
|
|
978
1004
|
if (a >= 0 && a < b && (buf[a] === 32 || buf[a] === 9)) {
|
|
979
1005
|
return http1Fail(p, 400, "a field line that starts with whitespace (obs-fold)")
|
|
980
1006
|
}
|
|
1007
|
+
// A kept line is copied into the head first, and its name is read there,
|
|
1008
|
+
// each upper-case letter lowered as the scan passes it: one walk over the
|
|
1009
|
+
// name, not a check of `buf` and then a second walk of the copy. The copy
|
|
1010
|
+
// of a line refused below is never read, since a refusal ends the parse.
|
|
1011
|
+
let at: i32 = 0
|
|
981
1012
|
let i: i32 = a
|
|
982
|
-
|
|
983
|
-
|
|
1013
|
+
if (keep) {
|
|
1014
|
+
at = http1HeadAppend(p, a, b)
|
|
1015
|
+
const head: u8[] = p.head
|
|
1016
|
+
let k: i32 = at
|
|
1017
|
+
const stop: i32 = at + (b - a)
|
|
1018
|
+
while (k >= 0 && k < stop && k < toI32(head.length)) {
|
|
1019
|
+
const c: i32 = toI32(head[k])
|
|
1020
|
+
if (!http1IsTokenByte(c)) {
|
|
1021
|
+
break
|
|
1022
|
+
}
|
|
1023
|
+
head[k] = toU8(c >= 65 && c <= 90 ? c + 32 : c)
|
|
1024
|
+
k += 1
|
|
1025
|
+
}
|
|
1026
|
+
i = a + (k - at)
|
|
1027
|
+
} else {
|
|
1028
|
+
while (i >= 0 && i < b && i < toI32(buf.length) && http1IsTokenByte(toI32(buf[i]))) {
|
|
1029
|
+
i += 1
|
|
1030
|
+
}
|
|
984
1031
|
}
|
|
985
1032
|
if (i === a || i >= b || buf[i] !== 58) {
|
|
986
1033
|
return http1Fail(p, 400, "a malformed field name")
|
|
@@ -1005,13 +1052,6 @@ const http1FieldLine = (p: Http1Parser, keep: boolean): i32 => {
|
|
|
1005
1052
|
while (valueEnd > valueStart && valueEnd - 1 >= 0 && http1IsOws(toI32(buf[valueEnd - 1]))) {
|
|
1006
1053
|
valueEnd -= 1
|
|
1007
1054
|
}
|
|
1008
|
-
const at: i32 = http1HeadAppend(p, a, b)
|
|
1009
|
-
const head: u8[] = p.head
|
|
1010
|
-
for (let k: i32 = at; k < at + (nameEnd - a) && k < toI32(head.length); k += 1) {
|
|
1011
|
-
if (k >= 0 && head[k] >= 65 && head[k] <= 90) {
|
|
1012
|
-
head[k] = head[k] + 32
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
1055
|
const base: i32 = 4 * p.headerCount
|
|
1016
1056
|
http1SpanSet(p, base + 3, at + (valueEnd - a))
|
|
1017
1057
|
// The last of the four is set first, so the array has room for them all.
|
package/std/net/http1-server.ts
CHANGED
|
@@ -525,28 +525,19 @@ export class Http1OutputPool {
|
|
|
525
525
|
* `output[outputStart .. outputEnd)` for its carrier to send.
|
|
526
526
|
*/
|
|
527
527
|
export class Http1Connection {
|
|
528
|
-
//
|
|
529
|
-
//
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
* put another, to share it or to pin its clock.
|
|
536
|
-
*/
|
|
537
|
-
date: HttpDate
|
|
528
|
+
// What a request reads and writes comes first, so that `inputRoom`, `feed`,
|
|
529
|
+
// `next`, `respond`, `write`, `end`, `consume`, `release` and `park` touch
|
|
530
|
+
// the front of the object: the parser, the output and its pool, the clocks,
|
|
531
|
+
// the state and the flags. What a WebSocket, an error or a configured cap
|
|
532
|
+
// needs follows. Within each part the layout packs as widest first does:
|
|
533
|
+
// the narrow fields of the front end on a 4-byte boundary and the cold
|
|
534
|
+
// 32-bit fields bring the references behind them to an 8-byte one.
|
|
538
535
|
/**
|
|
539
536
|
* The request parser: its head (`methodIs`, `targetIs`, `header`, spans) is
|
|
540
537
|
* the current request's from its `H1_REQUEST` until its response is done,
|
|
541
538
|
* `H1_END` included, however much of a pipelined request is fed behind it.
|
|
542
539
|
*/
|
|
543
540
|
parser: Http1Parser
|
|
544
|
-
/**
|
|
545
|
-
* The frame decoder: made when the slot first switches to WebSocket, so a
|
|
546
|
-
* slot that only ever speaks HTTP never holds its buffers, then kept for
|
|
547
|
-
* the slot's later peers.
|
|
548
|
-
*/
|
|
549
|
-
ws: WsDecoder | null
|
|
550
541
|
/**
|
|
551
542
|
* Bytes to send are `output[outputStart .. outputEnd)`. Empty until the
|
|
552
543
|
* connection first needs room to write: then its own buffer, made once, or
|
|
@@ -560,27 +551,17 @@ export class Http1Connection {
|
|
|
560
551
|
/** Where the parser's input and head buffers go while the slot rests, with `Http1Config.idleInput`; null otherwise. */
|
|
561
552
|
inputs: Http1OutputPool | null
|
|
562
553
|
heads: Http1OutputPool | null
|
|
563
|
-
/**
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
554
|
+
/**
|
|
555
|
+
* The `Date` every final response carries. The connection makes its own;
|
|
556
|
+
* `Http1Server` and `Http1TlsServer` put theirs here, so that every slot of a
|
|
557
|
+
* pool shares one and the pool formats at most once a second. A program may
|
|
558
|
+
* put another, to share it or to pin its clock.
|
|
559
|
+
*/
|
|
560
|
+
date: HttpDate
|
|
569
561
|
/** When the carrier last moved a byte for the connection, in `monotonicNanos`. */
|
|
570
562
|
lastActive: i64 = 0
|
|
571
563
|
/** When the head being read began to arrive, in `monotonicNanos`; meaningful while `headTimed`. */
|
|
572
564
|
headSince: i64 = 0
|
|
573
|
-
outputStart: i32 = 0
|
|
574
|
-
outputEnd: i32 = 0
|
|
575
|
-
dataStart: i32 = 0
|
|
576
|
-
dataLength: i32 = 0
|
|
577
|
-
/** `H1_WS_MESSAGE`'s opcode: text or binary. */
|
|
578
|
-
opcode: i32 = 0
|
|
579
|
-
/** `H1_ERROR`'s HTTP status or WebSocket close code, or `H1_WS_CLOSE`'s close code. */
|
|
580
|
-
status: i32 = 0
|
|
581
|
-
/** The most input the connection buffers, unread: the cap `inputRoom` holds it to. */
|
|
582
|
-
bufferLimit: i32 = 0
|
|
583
|
-
pongLength: i32 = 0
|
|
584
565
|
state: i32 = 0
|
|
585
566
|
response: i32 = 0
|
|
586
567
|
/**
|
|
@@ -590,12 +571,23 @@ export class Http1Connection {
|
|
|
590
571
|
responseFraming: i32 = 0
|
|
591
572
|
/** A response with a length: the bytes it still owes. */
|
|
592
573
|
responseLeft: i32 = 0
|
|
593
|
-
|
|
594
|
-
|
|
574
|
+
outputStart: i32 = 0
|
|
575
|
+
outputEnd: i32 = 0
|
|
576
|
+
/**
|
|
577
|
+
* `output.length`, kept beside the offsets so that `acquire` and `release`
|
|
578
|
+
* learn whether the connection holds a buffer without reading an array:
|
|
579
|
+
* the empty one is out of cache by the time a resting connection's next
|
|
580
|
+
* request comes. Only the connection assigns `output`, and with it this.
|
|
581
|
+
*/
|
|
582
|
+
outputCap: i32 = 0
|
|
583
|
+
/** The most input the connection buffers, unread: the cap `inputRoom` holds it to. */
|
|
584
|
+
bufferLimit: i32 = 0
|
|
595
585
|
/** A request's head has been read and its exchange is not over. */
|
|
596
586
|
requestOpen: boolean = false
|
|
597
587
|
/** The request's body has been read to its end. */
|
|
598
588
|
requestEnded: boolean = false
|
|
589
|
+
/** The response carries no body (HEAD, 204, 304): what is written is dropped. */
|
|
590
|
+
responseEmpty: boolean = false
|
|
599
591
|
/** The connection closes once this exchange is over. */
|
|
600
592
|
closeAfter: boolean = false
|
|
601
593
|
/** The program accepted the request's WebSocket handshake. */
|
|
@@ -607,6 +599,13 @@ export class Http1Connection {
|
|
|
607
599
|
inputEnded: boolean = false
|
|
608
600
|
/** A head has begun to arrive and is not read whole yet: `headSince` is its deadline's start. */
|
|
609
601
|
headTimed: boolean = false
|
|
602
|
+
dataStart: i32 = 0
|
|
603
|
+
dataLength: i32 = 0
|
|
604
|
+
/** `H1_WS_MESSAGE`'s opcode: text or binary. */
|
|
605
|
+
opcode: i32 = 0
|
|
606
|
+
/** `H1_ERROR`'s HTTP status or WebSocket close code, or `H1_WS_CLOSE`'s close code. */
|
|
607
|
+
status: i32 = 0
|
|
608
|
+
pongLength: i32 = 0
|
|
610
609
|
/**
|
|
611
610
|
* The TCP maximum segment size the program sets for this connection, for
|
|
612
611
|
* `respondEncoded` under `HttpCompressor.segmentMode`; 0, the default,
|
|
@@ -616,6 +615,19 @@ export class Http1Connection {
|
|
|
616
615
|
mss: i32 = 0
|
|
617
616
|
/** The MSS `Http1Server.accept` read from this connection's socket (`netMss`), or 0 when it reports none. */
|
|
618
617
|
socketMss: i32 = 0
|
|
618
|
+
config: Http1Config
|
|
619
|
+
/**
|
|
620
|
+
* The frame decoder: made when the slot first switches to WebSocket, so a
|
|
621
|
+
* slot that only ever speaks HTTP never holds its buffers, then kept for
|
|
622
|
+
* the slot's later peers.
|
|
623
|
+
*/
|
|
624
|
+
ws: WsDecoder | null
|
|
625
|
+
/** `H1_BODY`'s and `H1_WS_MESSAGE`'s bytes. */
|
|
626
|
+
data: u8[]
|
|
627
|
+
/** A ping's payload, kept until its pong fits: empty until the first ping. */
|
|
628
|
+
pong: u8[]
|
|
629
|
+
/** No fields, for the responses the connection writes itself: made once. */
|
|
630
|
+
noFields: u8[][]
|
|
619
631
|
|
|
620
632
|
/** A connection under `config`. A cap outside what the connection can work with panics. */
|
|
621
633
|
constructor(config: Http1Config) {
|
|
@@ -759,10 +771,15 @@ export class Http1Connection {
|
|
|
759
771
|
if (this.writeHeld || this.inputEnded || this.state === H1_S_CLOSING || this.state === H1_S_FAILED) {
|
|
760
772
|
return 0
|
|
761
773
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
774
|
+
let room: i32 = 0
|
|
775
|
+
if (this.state === H1_S_WEBSOCKET) {
|
|
776
|
+
// Only a WebSocket reads the decoder and the configured message cap,
|
|
777
|
+
// so a request's check stays on the connection's front fields.
|
|
778
|
+
const ws: WsDecoder | null = this.ws
|
|
779
|
+
room = this.inputLimit() - (ws !== null ? ws.end - ws.start : this.parser.buffered())
|
|
780
|
+
} else {
|
|
781
|
+
room = this.bufferLimit - this.parser.buffered()
|
|
782
|
+
}
|
|
766
783
|
return room > 0 ? room : 0
|
|
767
784
|
}
|
|
768
785
|
|
|
@@ -809,11 +826,13 @@ export class Http1Connection {
|
|
|
809
826
|
* the pool has no free buffer, or for a connection's own, once.
|
|
810
827
|
*/
|
|
811
828
|
acquire(): void {
|
|
812
|
-
if (
|
|
829
|
+
if (this.outputCap > 0) {
|
|
813
830
|
return
|
|
814
831
|
}
|
|
815
832
|
const pool: Http1OutputPool | null = this.pool
|
|
816
|
-
|
|
833
|
+
const output: u8[] = pool !== null ? pool.take() : new Array<u8>(this.config.outputSize)
|
|
834
|
+
this.output = output
|
|
835
|
+
this.outputCap = toI32(output.length)
|
|
817
836
|
this.outputStart = 0
|
|
818
837
|
this.outputEnd = 0
|
|
819
838
|
}
|
|
@@ -825,16 +844,12 @@ export class Http1Connection {
|
|
|
825
844
|
*/
|
|
826
845
|
release(): void {
|
|
827
846
|
const pool: Http1OutputPool | null = this.pool
|
|
828
|
-
if (
|
|
829
|
-
pool === null ||
|
|
830
|
-
toI32(this.output.length) === 0 ||
|
|
831
|
-
this.outputEnd > this.outputStart ||
|
|
832
|
-
this.writeHeld
|
|
833
|
-
) {
|
|
847
|
+
if (pool === null || this.outputCap === 0 || this.outputEnd > this.outputStart || this.writeHeld) {
|
|
834
848
|
return
|
|
835
849
|
}
|
|
836
850
|
pool.give(this.output)
|
|
837
851
|
this.output = this.noOutput
|
|
852
|
+
this.outputCap = 0
|
|
838
853
|
this.outputStart = 0
|
|
839
854
|
this.outputEnd = 0
|
|
840
855
|
}
|