@amritk/nish-x86_64-linux 0.18.0 → 0.20.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 +43 -25
- package/bin/nish +0 -0
- package/package.json +2 -2
- package/runtime/nish.d.ts +55 -5
- package/runtime/nish.h +215 -18
- package/runtime/nish.mjs +16 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +7 -6
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +213 -15
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +4 -4
- package/runtime/runtime-wasm.c +5 -3
- package/runtime/runtime.c +69 -13
- package/runtime/shim.mjs +71 -1
- package/scripts/build.sh +223 -22
- package/std/README.md +81 -51
- package/std/bytes.ts +163 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/chacha20poly1305.ts +43 -9
- package/std/crypto/ed25519.ts +840 -0
- package/std/crypto/hkdf.ts +304 -12
- package/std/crypto/hmac.ts +245 -53
- package/std/crypto/p256.ts +932 -61
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +9 -9
- package/std/json-tokenizer.ts +1296 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1163 -0
- package/std/json.ts +1120 -34
- package/std/net/buffer-pool.ts +151 -0
- package/std/net/hpack.ts +416 -112
- package/std/net/http-fields.ts +93 -0
- package/std/net/http1-server.ts +417 -55
- package/std/net/http1.ts +31 -6
- package/std/net/http2-tls.ts +112 -24
- package/std/net/http2.ts +333 -75
- package/std/net/http3-server.ts +211 -31
- package/std/net/http3.ts +187 -28
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-listener.ts +88 -7
- package/std/net/quic-packet.ts +243 -82
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +424 -24
- package/std/net/quic.ts +519 -167
- package/std/net/router.ts +676 -0
- package/std/net/tls/codec.ts +426 -27
- package/std/net/tls/record-server.ts +478 -132
- package/std/net/tls/record.ts +133 -42
- package/std/net/tls/schedule.ts +464 -55
- package/std/net/tls/ticket.ts +610 -0
- package/std/net/tls-tcp.ts +196 -53
- package/std/net/tls.ts +1018 -260
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/threads.ts +438 -14
package/std/net/http1.ts
CHANGED
|
@@ -67,6 +67,9 @@
|
|
|
67
67
|
* header section with the framing field the body needs, and `http1Chunk` and
|
|
68
68
|
* `http1LastChunk` for a chunked body. It refuses (`null`) anything that would
|
|
69
69
|
* let a caller's string end a line early — the response-splitting shape.
|
|
70
|
+
* `http1WriteResponseHead` writes the same head into a caller's buffer with
|
|
71
|
+
* nothing allocated, and adds a `Date` field from the bytes it is given
|
|
72
|
+
* (`httpDateWrite` in `nish/net/http-fields` makes them) to a final response.
|
|
70
73
|
*
|
|
71
74
|
* Field names are kept in lowercase, the form RFC 9110 §5.1 says they compare
|
|
72
75
|
* in and the form HTTP/2 sends them in. Strings in the language are bytes, so a
|
|
@@ -1410,11 +1413,16 @@ const http1PutDecimal = (out: u8[], at: i32, n: i32): i32 => {
|
|
|
1410
1413
|
* `http1ResponseHead`'s status line and header section, written into `out`
|
|
1411
1414
|
* at `at` with nothing allocated: field names and values are octets (as
|
|
1412
1415
|
* `nish/net/http-fields` holds them), and `connection`, when not empty, is
|
|
1413
|
-
* sent as a `Connection` field after the caller's.
|
|
1414
|
-
*
|
|
1415
|
-
*
|
|
1416
|
-
* `
|
|
1417
|
-
*
|
|
1416
|
+
* sent as a `Connection` field after the caller's. `date`, when not empty, is
|
|
1417
|
+
* sent as a `Date` field between the caller's fields and the framing ones
|
|
1418
|
+
* (RFC 9110 §6.6.1), on a final status only, and not when the caller's fields
|
|
1419
|
+
* already hold a `date` in any case: the caller's wins, so a response carries
|
|
1420
|
+
* one. Answers where the head ends; `HTTP1_REFUSED` for everything
|
|
1421
|
+
* `http1ResponseHead` refuses, a `Connection` among the caller's fields when
|
|
1422
|
+
* `connection` is given, a `connection` that is not field text, and a `date`
|
|
1423
|
+
* that is not field text on a final status, the only one it is sent on;
|
|
1424
|
+
* `HTTP1_NO_ROOM` when it is well formed but does not fit, the `Date` line
|
|
1425
|
+
* counted when it is sent. Either way `out` is untouched.
|
|
1418
1426
|
*/
|
|
1419
1427
|
export const http1WriteResponseHead = (
|
|
1420
1428
|
out: u8[],
|
|
@@ -1424,11 +1432,15 @@ export const http1WriteResponseHead = (
|
|
|
1424
1432
|
names: u8[][],
|
|
1425
1433
|
values: u8[][],
|
|
1426
1434
|
bodyLength: i32,
|
|
1427
|
-
connection: string
|
|
1435
|
+
connection: string,
|
|
1436
|
+
date: u8[]
|
|
1428
1437
|
): i32 => {
|
|
1429
1438
|
if (status < 100 || status > 599 || !http1IsFieldText(reason) || !http1IsFieldText(connection)) {
|
|
1430
1439
|
return HTTP1_REFUSED
|
|
1431
1440
|
}
|
|
1441
|
+
if (status >= 200 && !http1BytesAreFieldText(date)) {
|
|
1442
|
+
return HTTP1_REFUSED
|
|
1443
|
+
}
|
|
1432
1444
|
if (bodyLength < HTTP1_NO_BODY || ((status < 200 || status === 204) && bodyLength !== HTTP1_NO_BODY)) {
|
|
1433
1445
|
return HTTP1_REFUSED
|
|
1434
1446
|
}
|
|
@@ -1440,6 +1452,7 @@ export const http1WriteResponseHead = (
|
|
|
1440
1452
|
// field, the connection field and the empty line: counted before anything
|
|
1441
1453
|
// is written, in 64 bits so that no caller's lengths can wrap the sum.
|
|
1442
1454
|
let need: i64 = toI64(17) + toI64(reason.length)
|
|
1455
|
+
let sendDate: boolean = status >= 200 && toI32(date.length) > 0
|
|
1443
1456
|
for (let i: i32 = 0; i < count && i < toI32(values.length); i += 1) {
|
|
1444
1457
|
const name: u8[] = names[i]
|
|
1445
1458
|
const value: u8[] = values[i]
|
|
@@ -1452,8 +1465,15 @@ export const http1WriteResponseHead = (
|
|
|
1452
1465
|
if (toI32(connection.length) > 0 && http1BytesAre(name, "connection")) {
|
|
1453
1466
|
return HTTP1_REFUSED
|
|
1454
1467
|
}
|
|
1468
|
+
if (http1BytesAre(name, "date")) {
|
|
1469
|
+
sendDate = false
|
|
1470
|
+
}
|
|
1455
1471
|
need = need + toI64(name.length) + toI64(value.length) + toI64(4)
|
|
1456
1472
|
}
|
|
1473
|
+
if (sendDate) {
|
|
1474
|
+
// "Date: " value CRLF: 37 bytes for an IMF-fixdate.
|
|
1475
|
+
need = need + toI64(8) + toI64(date.length)
|
|
1476
|
+
}
|
|
1457
1477
|
if (bodyLength >= 0) {
|
|
1458
1478
|
need = need + toI64(18 + http1DecimalDigits(bodyLength))
|
|
1459
1479
|
} else if (bodyLength === HTTP1_CHUNKED) {
|
|
@@ -1476,6 +1496,11 @@ export const http1WriteResponseHead = (
|
|
|
1476
1496
|
end = http1PutBytes(out, end, values[i])
|
|
1477
1497
|
end = http1PutText(out, end, "\r\n")
|
|
1478
1498
|
}
|
|
1499
|
+
if (sendDate) {
|
|
1500
|
+
end = http1PutText(out, end, "Date: ")
|
|
1501
|
+
end = http1PutBytes(out, end, date)
|
|
1502
|
+
end = http1PutText(out, end, "\r\n")
|
|
1503
|
+
}
|
|
1479
1504
|
if (bodyLength >= 0) {
|
|
1480
1505
|
end = http1PutText(out, end, "Content-Length: ")
|
|
1481
1506
|
end = http1PutDecimal(out, end, bodyLength)
|
package/std/net/http2-tls.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `nish/net/http2-tls` — HTTP/2 over TLS 1.3 on TCP: a pool of
|
|
3
3
|
* `Http2Connection`s, one beside each slot of a `TlsTcpServer`, for a
|
|
4
|
-
* program that owns its readiness loop.
|
|
4
|
+
* program that owns its readiness loop. Slot 0's connection is built with the
|
|
5
|
+
* server, so a bad `Http2Config` is refused there; every other slot's is built
|
|
6
|
+
* on the first accept into it.
|
|
5
7
|
*
|
|
6
8
|
* const listener = tcpListen("0.0.0.0", 8443, 128);
|
|
7
9
|
* const server = new Http2TlsServer(tlsConfig, new Http2Config(), listener, 64);
|
|
@@ -36,16 +38,35 @@
|
|
|
36
38
|
* configuration offers `h2` (and may offer `http/1.1` beside it, for a
|
|
37
39
|
* program that serves both and calls `alpn(slot)` itself).
|
|
38
40
|
*
|
|
39
|
-
* **Slots,
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* `
|
|
41
|
+
* **Slots, a cap; buffers, borrowed.** The constructor makes the TLS pool
|
|
42
|
+
* and slot 0's connection, but no other slot's and no frame buffer: a slot's
|
|
43
|
+
* connection is made by the first `accept` into it and kept, so a pool sized
|
|
44
|
+
* for its peak costs at start-up only what does not depend on the size, and
|
|
45
|
+
* holds at most `size` connections once the peak has come. A connection
|
|
46
|
+
* borrows its input, output and header-block buffers from the
|
|
47
|
+
* server's `Http2Pools` when it first needs each, and gives each back once it
|
|
48
|
+
* is empty and when the slot closes, so a free or idle slot holds none. A
|
|
49
|
+
* slot whose handshake is not done holds one output buffer, its SETTINGS
|
|
50
|
+
* written and waiting on TLS, until the handshake ends or the slot closes:
|
|
51
|
+
* one client that stalls its handshake keeps one buffer, as it kept a whole
|
|
52
|
+
* slot before (`docs/security/tls.md`, TLS-4). Each pool's ceiling is the
|
|
53
|
+
* slot count, so a borrow never fails. `accept` resets
|
|
54
|
+
* the slot's connection, and once the pools are warm nothing a connection
|
|
55
|
+
* does allocates but a header block's HPACK decoding (`docs/security/http2.md`,
|
|
56
|
+
* H2-1). The caps are the `Http2Config`'s, one for every slot, so a program
|
|
57
|
+
* sets them once.
|
|
58
|
+
*
|
|
59
|
+
* **The Date.** Every final response carries one `date` field (RFC 9110
|
|
60
|
+
* §6.6.1) from the pool's `HttpDate`, which formats the IMF-fixdate at most
|
|
61
|
+
* once a second for every slot; a 1xx and trailers carry none, and a program
|
|
62
|
+
* that sends its own `date` sends that one instead. `server.date.pin(unixMs)`
|
|
63
|
+
* fixes it, for a test that compares bytes.
|
|
44
64
|
*
|
|
45
65
|
* Written from RFC 9113 §3 and §9.2, not ported from another implementation.
|
|
46
66
|
*/
|
|
47
67
|
import { Secret } from "nish:secret"
|
|
48
|
-
import {
|
|
68
|
+
import { HttpDate } from "nish/net/http1-server"
|
|
69
|
+
import { H2_ERROR, H2_NEED_MORE, Http2Config, Http2Connection, Http2Pools } from "nish/net/http2"
|
|
49
70
|
import { TlsServerConfig } from "nish/net/tls"
|
|
50
71
|
import { TLS_RECORD_DONE, TLS_RECORD_STATE_OPEN, TLS_RECORD_WANT_WRITE } from "nish/net/tls/record-server"
|
|
51
72
|
import { TlsTcpServer } from "nish/net/tls-tcp"
|
|
@@ -62,22 +83,57 @@ export const H2_ALPN: string = "h2"
|
|
|
62
83
|
*/
|
|
63
84
|
export class Http2TlsServer {
|
|
64
85
|
tls: TlsTcpServer
|
|
86
|
+
/** The caps every slot's connection is made under. */
|
|
87
|
+
config: Http2Config
|
|
88
|
+
/**
|
|
89
|
+
* The connections made so far, slot `k`'s at `k`: slot 0's, made by the
|
|
90
|
+
* constructor, and one for each slot accepted into since.
|
|
91
|
+
*/
|
|
65
92
|
connections: Http2Connection[]
|
|
93
|
+
/**
|
|
94
|
+
* The `date` every slot's responses carry, formatted once a second for all
|
|
95
|
+
* of them: each slot's `Http2Connection.date` is its bytes, refreshed by
|
|
96
|
+
* `connection` and `next`. `date.pin` fixes its clock.
|
|
97
|
+
*/
|
|
98
|
+
date: HttpDate
|
|
99
|
+
/** The frame buffers every slot's connection borrows: one of each per slot at most, none made up front. */
|
|
100
|
+
readonly pools: Http2Pools
|
|
66
101
|
|
|
67
102
|
/**
|
|
68
103
|
* A pool of `size` slots, at least one, serving HTTP/2 under `config` over
|
|
69
|
-
* TLS under `tlsConfig` on `listener`.
|
|
70
|
-
* here
|
|
104
|
+
* TLS under `tlsConfig` on `listener`. Only slot 0's connection is made
|
|
105
|
+
* here, so a config it refuses panics now, with its words; every other
|
|
106
|
+
* slot's is made by the first `accept` into it and kept. Frame buffers are
|
|
107
|
+
* borrowed from `pools` as a connection needs them.
|
|
71
108
|
*/
|
|
72
109
|
constructor(tlsConfig: TlsServerConfig, config: Http2Config, listener: i32, size: i32) {
|
|
73
110
|
this.tls = new TlsTcpServer(tlsConfig, listener, size)
|
|
111
|
+
this.config = config
|
|
74
112
|
this.connections = []
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
113
|
+
this.date = new HttpDate()
|
|
114
|
+
// Formatted once now, so no slot ever sends the bytes before a first format.
|
|
115
|
+
const first: Http2Connection = new Http2Connection(config)
|
|
116
|
+
first.date = this.date.current()
|
|
117
|
+
this.connections.push(first)
|
|
118
|
+
// After slot 0's connection, so a config it refuses panics with its words.
|
|
119
|
+
this.pools = new Http2Pools("Http2TlsServer", config, this.tls.size())
|
|
120
|
+
first.pools = this.pools
|
|
78
121
|
}
|
|
79
122
|
|
|
80
|
-
/**
|
|
123
|
+
/** Makes the next slot's connection, on this pool's buffers and `date`. */
|
|
124
|
+
build(): void {
|
|
125
|
+
const conn: Http2Connection = new Http2Connection(this.config)
|
|
126
|
+
conn.date = this.date.current()
|
|
127
|
+
conn.pools = this.pools
|
|
128
|
+
this.connections.push(conn)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Whether `slot` holds a connection and its HTTP/2 side has been made. */
|
|
132
|
+
live(slot: i32): boolean {
|
|
133
|
+
return this.tls.holds(slot) && slot < toI32(this.connections.length)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** How many slots the pool has, made or not. */
|
|
81
137
|
size(): i32 {
|
|
82
138
|
return this.tls.size()
|
|
83
139
|
}
|
|
@@ -97,8 +153,12 @@ export class Http2TlsServer {
|
|
|
97
153
|
return this.tls.fd(slot)
|
|
98
154
|
}
|
|
99
155
|
|
|
100
|
-
/**
|
|
156
|
+
/**
|
|
157
|
+
* The HTTP/2 connection of `slot`, its `date` brought up to now. A slot
|
|
158
|
+
* never accepted into, or out of range, names slot 0's, which may be live.
|
|
159
|
+
*/
|
|
101
160
|
connection(slot: i32): Http2Connection {
|
|
161
|
+
this.date.current()
|
|
102
162
|
const at: i32 = slot >= 0 && slot < toI32(this.connections.length) ? slot : H2_TLS_ZERO
|
|
103
163
|
return this.connections[at]
|
|
104
164
|
}
|
|
@@ -111,33 +171,46 @@ export class Http2TlsServer {
|
|
|
111
171
|
/**
|
|
112
172
|
* Accepts the next waiting connection into a free slot, as
|
|
113
173
|
* `TlsTcpServer.accept` does, and gives it a fresh HTTP/2 connection whose
|
|
114
|
-
* SETTINGS go out once the handshake is done
|
|
115
|
-
*
|
|
174
|
+
* SETTINGS go out once the handshake is done: the slot's own, restarted,
|
|
175
|
+
* or one made now if this is the first accept into it. Answers the slot,
|
|
176
|
+
* or what `TlsTcpServer.accept` answers.
|
|
116
177
|
*/
|
|
117
178
|
accept(serverRandom: u8[], ephemeralPrivate: u8[]): i32 {
|
|
118
179
|
const slot: i32 = this.tls.accept(serverRandom, ephemeralPrivate)
|
|
119
|
-
if (slot
|
|
120
|
-
|
|
180
|
+
if (slot < 0 || slot >= this.tls.size()) {
|
|
181
|
+
return slot
|
|
182
|
+
}
|
|
183
|
+
// `TlsTcpServer` hands out its lowest slots first, so this makes one
|
|
184
|
+
// connection, unless a program accepted on `tls` itself.
|
|
185
|
+
while (toI32(this.connections.length) <= slot) {
|
|
186
|
+
this.build()
|
|
121
187
|
}
|
|
188
|
+
this.connections[slot].restart()
|
|
122
189
|
return slot
|
|
123
190
|
}
|
|
124
191
|
|
|
125
192
|
/** The socket of `slot` is readable: TLS reads it, and what HTTP/2 has to send goes after. Answers the interest. */
|
|
126
193
|
readable(slot: i32): i32 {
|
|
127
194
|
this.tls.readable(slot)
|
|
128
|
-
|
|
195
|
+
const wants: i32 = this.flush(slot)
|
|
196
|
+
this.rest(slot)
|
|
197
|
+
return wants
|
|
129
198
|
}
|
|
130
199
|
|
|
131
200
|
/** The socket of `slot` is writable. Answers the interest. */
|
|
132
201
|
writable(slot: i32): i32 {
|
|
133
202
|
this.tls.writable(slot)
|
|
134
|
-
|
|
203
|
+
const wants: i32 = this.flush(slot)
|
|
204
|
+
this.rest(slot)
|
|
205
|
+
return wants
|
|
135
206
|
}
|
|
136
207
|
|
|
137
208
|
/** Signs `slot`'s CertificateVerify with `key`, as `TlsTcpServer.signP256` does. Answers the interest. */
|
|
138
209
|
signP256(slot: i32, key: Secret<u8[]>): i32 {
|
|
139
210
|
this.tls.signP256(slot, key)
|
|
140
|
-
|
|
211
|
+
const wants: i32 = this.flush(slot)
|
|
212
|
+
this.rest(slot)
|
|
213
|
+
return wants
|
|
141
214
|
}
|
|
142
215
|
|
|
143
216
|
/** Whether `slot`'s handshake is done and chose anything but `h2`. */
|
|
@@ -151,9 +224,10 @@ export class Http2TlsServer {
|
|
|
151
224
|
* slot, or one whose ALPN was not `h2`, answers `H2_ERROR`.
|
|
152
225
|
*/
|
|
153
226
|
next(slot: i32): i32 {
|
|
154
|
-
if (!this.
|
|
227
|
+
if (!this.live(slot) || this.refused(slot)) {
|
|
155
228
|
return H2_ERROR
|
|
156
229
|
}
|
|
230
|
+
this.date.current()
|
|
157
231
|
const conn: Http2Connection = this.connections[slot]
|
|
158
232
|
// TLS decrypts straight into the connection's input, so a byte is
|
|
159
233
|
// copied once on its way in.
|
|
@@ -177,6 +251,7 @@ export class Http2TlsServer {
|
|
|
177
251
|
// the program's own `flush` after the loop.
|
|
178
252
|
if (event === H2_NEED_MORE || event === H2_ERROR) {
|
|
179
253
|
this.flush(slot)
|
|
254
|
+
this.rest(slot)
|
|
180
255
|
}
|
|
181
256
|
return event
|
|
182
257
|
}
|
|
@@ -188,7 +263,7 @@ export class Http2TlsServer {
|
|
|
188
263
|
* with the write bit while the connection still holds bytes.
|
|
189
264
|
*/
|
|
190
265
|
flush(slot: i32): i32 {
|
|
191
|
-
if (!this.
|
|
266
|
+
if (!this.live(slot)) {
|
|
192
267
|
return TLS_RECORD_DONE
|
|
193
268
|
}
|
|
194
269
|
if (this.refused(slot)) {
|
|
@@ -209,8 +284,21 @@ export class Http2TlsServer {
|
|
|
209
284
|
return conn.wantsWrite() && (wants & TLS_RECORD_DONE) === 0 ? wants | TLS_RECORD_WANT_WRITE : wants
|
|
210
285
|
}
|
|
211
286
|
|
|
212
|
-
/**
|
|
287
|
+
/**
|
|
288
|
+
* Ends `slot`'s connection and frees the slot at once, as
|
|
289
|
+
* `TlsTcpServer.close` does; the connection gives back every buffer it holds.
|
|
290
|
+
*/
|
|
213
291
|
close(slot: i32): void {
|
|
292
|
+
if (slot >= 0 && slot < toI32(this.connections.length)) {
|
|
293
|
+
this.connections[slot].drop()
|
|
294
|
+
}
|
|
214
295
|
this.tls.close(slot)
|
|
215
296
|
}
|
|
297
|
+
|
|
298
|
+
/** Gives back the buffers `slot`'s connection holds and has emptied, so an idle slot holds none. */
|
|
299
|
+
rest(slot: i32): void {
|
|
300
|
+
if (slot >= 0 && slot < toI32(this.connections.length)) {
|
|
301
|
+
this.connections[slot].rest()
|
|
302
|
+
}
|
|
303
|
+
}
|
|
216
304
|
}
|