@amritk/nish-x86_64-linux 0.16.0 → 0.17.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.
Files changed (55) hide show
  1. package/INSTALL.md +43 -34
  2. package/bin/nish +0 -0
  3. package/package.json +1 -1
  4. package/runtime/nish.d.ts +117 -8
  5. package/runtime/nish.h +30 -7
  6. package/runtime/nish.mjs +71 -7
  7. package/runtime/runtime-host.c +43 -24
  8. package/runtime/runtime-net.c +57 -10
  9. package/runtime/runtime-os.c +62 -20
  10. package/runtime/runtime-wasm.c +1 -0
  11. package/runtime/runtime.c +46 -8
  12. package/runtime/shim.mjs +114 -5
  13. package/scripts/build.sh +1 -3
  14. package/std/README.md +166 -35
  15. package/std/crypto/aes.ts +51 -8
  16. package/std/crypto/base64url.ts +25 -12
  17. package/std/crypto/chacha20poly1305.ts +43 -29
  18. package/std/crypto/ct.ts +6 -1
  19. package/std/crypto/hkdf.ts +364 -3
  20. package/std/crypto/hmac.ts +307 -7
  21. package/std/crypto/p256.ts +148 -27
  22. package/std/crypto/sha1.ts +147 -0
  23. package/std/crypto/sha256.ts +3 -2
  24. package/std/crypto/x25519.ts +88 -36
  25. package/std/crypto/x509.ts +130 -47
  26. package/std/net/hpack.ts +1094 -0
  27. package/std/net/http-fields.ts +604 -0
  28. package/std/net/http1-server.ts +1389 -0
  29. package/std/net/http1.ts +1492 -0
  30. package/std/net/http2-frame.ts +745 -0
  31. package/std/net/http2-tls.ts +216 -0
  32. package/std/net/http2.ts +1433 -0
  33. package/std/net/http3-frame.ts +457 -0
  34. package/std/net/http3-server.ts +905 -0
  35. package/std/net/http3.ts +2145 -0
  36. package/std/net/qpack.ts +1163 -0
  37. package/std/net/quic-conn-ack.ts +206 -0
  38. package/std/net/quic-conn-cid.ts +425 -0
  39. package/std/net/quic-conn-params.ts +392 -0
  40. package/std/net/quic-datagram.ts +130 -0
  41. package/std/net/quic-frame.ts +1206 -0
  42. package/std/net/quic-listener.ts +653 -0
  43. package/std/net/quic-packet.ts +1474 -0
  44. package/std/net/quic-recovery.ts +849 -0
  45. package/std/net/quic-stream.ts +1603 -0
  46. package/std/net/quic.ts +3214 -0
  47. package/std/net/tls/codec.ts +1089 -0
  48. package/std/net/tls/record-server.ts +1173 -0
  49. package/std/net/tls/record.ts +621 -0
  50. package/std/net/tls/schedule.ts +429 -0
  51. package/std/net/tls-tcp.ts +410 -0
  52. package/std/net/tls.ts +1368 -0
  53. package/std/net/websocket.ts +823 -0
  54. package/std/net/webtransport.ts +1334 -0
  55. package/std/secret.ts +62 -0
@@ -0,0 +1,653 @@
1
+ /**
2
+ * `nish/net/quic-listener` — what a QUIC version 1 server answers to a
3
+ * datagram no connection owns: Version Negotiation for a version it does not
4
+ * speak (RFC 9000 §6, §17.2.1), a Retry that asks a client to prove its
5
+ * address before any connection state exists (§8.1.2, §17.2.5), and a
6
+ * stateless reset for a packet sent to a connection the server no longer has
7
+ * (§10.3). Sans-IO, like the rest of `nish/net`, and stateless in the sense
8
+ * the RFC means: nothing it answers depends on any connection.
9
+ *
10
+ * import { QuicListener, QuicListenerAnswer, QUIC_LISTEN_ACCEPT } from "nish/net/quic-listener";
11
+ *
12
+ * const listener = new QuicListener(config, entropy); // entropy: QUIC_LISTENER_ENTROPY_SIZE random bytes
13
+ * // For a datagram whose first packet no connection owns (`ownsConnectionId`):
14
+ * const answer: QuicListenerAnswer = listener.handle(datagram, fromAddress, now);
15
+ * if (answer.kind === QUIC_LISTEN_ACCEPT) {
16
+ * const conn = new QuicConnection(config, connectionEntropy);
17
+ * if (answer.retried) { conn.acceptRetry(answer.originalDcid, answer.retryScid); }
18
+ * conn.receive(datagram, now);
19
+ * } else if (toI32(answer.reply.length) > 0) {
20
+ * … send answer.reply back to fromAddress …
21
+ * }
22
+ *
23
+ * **Routing is the caller's.** The listener sees only datagrams the caller
24
+ * could not hand to a live connection, and it must see only those: a stateless
25
+ * reset carries the token for the packet's connection ID, and handing the
26
+ * listener a packet for a connection that is still alive would give that
27
+ * token, which ends the connection, to whoever sent it (RFC 9000 §21.11).
28
+ *
29
+ * **Retry tokens** (§8.1.4) are this server's own format: a marker byte, the
30
+ * time the token was issued, and the client's original Destination
31
+ * Connection ID, then 16 bytes of HMAC-SHA256 under the configuration's
32
+ * `retryTokenKey` over all of that, the Retry's Source Connection ID (which
33
+ * the client's next Initial must be sent to) and the client's address as the
34
+ * caller gives it, port included. So a token is good only from the address
35
+ * it was sent to, only for the connection it was issued for, and only for
36
+ * `retryTokenLifetime` milliseconds. A token that carries the marker and fails
37
+ * any of that is an invalid Retry token, which closes the attempt with
38
+ * INVALID_TOKEN (§8.1.2); a token without the marker, which some other
39
+ * server issued, is treated as no token at all (§8.1.3).
40
+ *
41
+ * **Stateless resets** (§10.3) end in the token `quicStatelessResetToken`
42
+ * derives from the configuration's static key and the packet's connection
43
+ * ID, so a server that restarted with the same key can end the connections
44
+ * it lost. RFC 9000 §10.3 and §10.3.3 bound them, and each bound is held
45
+ * here. A reset is sent only for a short header, and is always shorter than
46
+ * the datagram that caused it, so two endpoints resetting each other shrink
47
+ * to nothing; one byte shorter for a datagram of 43 bytes or fewer, and
48
+ * never under 21 bytes, so a datagram of 21 or fewer gets none. That also
49
+ * bounds amplification at less than one to one. They are rate limited:
50
+ * `QUIC_LISTENER_RESET_BURST` at once, then one per
51
+ * `QUIC_LISTENER_RESET_INTERVAL` milliseconds. Each reset's other bytes are
52
+ * unpredictable, from the listener's own generator.
53
+ *
54
+ * **Pacing** (RFC 9002 §7.7) is the carrier's send loop, so it is here
55
+ * beside the rest of what the carrier calls: `quicListenerTakePaced` takes a
56
+ * connection's next datagram only when its pacer has the credit for it, and
57
+ * `quicListenerPaceTime` says when it will. The pacer earns 5/4 of the
58
+ * congestion window per smoothed RTT and lets a burst of at most the initial
59
+ * window go at once, so a flight leaves spread over the round trip instead
60
+ * of in one burst that overruns a queue on the path.
61
+ *
62
+ * let out: u8[] | null = quicListenerTakePaced(conn, now);
63
+ * while (out !== null) { … send it … ; out = quicListenerTakePaced(conn, now); }
64
+ * … and wake at the earlier of conn.deadline() and quicListenerPaceTime(conn, now)
65
+ *
66
+ * **GSO and GRO** (`nish:net`'s `UDP_SEGMENT` and `UDP_GRO`). A carrier that
67
+ * sends with segmentation offload takes a whole flight at once:
68
+ * `quicListenerTakeFlight` writes as many datagrams as the pacer has credit
69
+ * for back to back into one buffer, every one `QUIC_CONN_DATAGRAM_SIZE`
70
+ * bytes but the last, which is what one `udpSendTo` with that segment size
71
+ * cuts back into datagrams; each is written in place, so a flight allocates
72
+ * nothing. The flight is paced as a whole: it is as long as the pacer's
73
+ * credit, and charged to it datagram by datagram, so a burst is never more
74
+ * than the initial window however the carrier sends it. On the receiving
75
+ * side, `quicListenerReceiveSegments` hands a connection the datagrams of
76
+ * one GRO receive, segment by segment, in place.
77
+ *
78
+ * const flight = new QuicFlight();
79
+ * const n: i32 = quicListenerTakeFlight(conn, now, buf, 0, toI32(buf.length), flight);
80
+ * if (n > 0) { udpSendTo(fd, buf, 0, n, peer, flight.count > 1 ? flight.segment : 0, 0); }
81
+ *
82
+ * **Sans-IO and deterministic.** The caller gives the time, the client's
83
+ * address and, once, `QUIC_LISTENER_ENTROPY_SIZE` random bytes: every
84
+ * unpredictable byte the listener sends (a Retry's connection ID and unused
85
+ * bits, Version Negotiation's unused bits, a reset's padding and length) is
86
+ * HMAC-SHA256 under that seed of a counter. So a recorded exchange replays
87
+ * byte for byte.
88
+ *
89
+ * **Secrets.** The seed lives in the listener and the static keys in the
90
+ * caller's `QuicServerConfig`, for the server's lifetime, as plain bytes,
91
+ * since a `Secret` may not be a field (NL2430); QUIC-5 in
92
+ * `docs/security/quic.md` records it, with #430.
93
+ *
94
+ * Written from RFC 9000, in this module's own structure; nothing here is
95
+ * ported from another implementation. Private names carry the
96
+ * `quicListener` prefix (`docs/wp26-stdlib.md` §3e).
97
+ */
98
+ import { timingSafeEqual } from "nish/crypto/ct"
99
+ import { hmacSha256 } from "nish/crypto/hmac"
100
+ import {
101
+ QUIC_AEAD_AES_128_GCM,
102
+ QUIC_ERR_VERSION,
103
+ QUIC_MAX_CID_LENGTH,
104
+ QUIC_PACKET_INITIAL,
105
+ QUIC_PACKET_OK,
106
+ QUIC_PACKET_SHORT,
107
+ QuicHeader,
108
+ QuicInitialSecrets,
109
+ QuicKeys,
110
+ QuicPacket,
111
+ quicInitialSecrets,
112
+ quicKeys,
113
+ quicLongHeader,
114
+ quicOpenPacket,
115
+ quicParseHeader,
116
+ quicRetryPacket,
117
+ quicSealPacket,
118
+ } from "nish/net/quic-packet"
119
+ import { QUIC_ERROR_INVALID_TOKEN, QUIC_RESET_TOKEN_SIZE, quicPushConnectionClose } from "nish/net/quic-frame"
120
+ import {
121
+ QUIC_CONN_CID_LENGTH,
122
+ QUIC_CONN_DATAGRAM_SIZE,
123
+ QUIC_CONN_STATIC_KEY_SIZE,
124
+ QuicConnection,
125
+ QuicServerConfig,
126
+ quicStatelessResetToken,
127
+ } from "nish/net/quic"
128
+
129
+ // ---- What `handle` answers ----------------------------------------------------
130
+
131
+ /** Nothing to send and no connection to make: the datagram is dropped. */
132
+ export const QUIC_LISTEN_DROP: i32 = 0
133
+ /** Make a connection and hand it the datagram; after a Retry, `acceptRetry` it first. */
134
+ export const QUIC_LISTEN_ACCEPT: i32 = 1
135
+ /** Send `reply`, a Version Negotiation packet (RFC 9000 §17.2.1). */
136
+ export const QUIC_LISTEN_VERSION_NEGOTIATION: i32 = 2
137
+ /** Send `reply`, a Retry packet with an address-validation token (§17.2.5). */
138
+ export const QUIC_LISTEN_RETRY: i32 = 3
139
+ /** Send `reply`, an Initial carrying CONNECTION_CLOSE with INVALID_TOKEN (§8.1.2). */
140
+ export const QUIC_LISTEN_INVALID_TOKEN: i32 = 4
141
+ /** Send `reply`, a stateless reset (§10.3). */
142
+ export const QUIC_LISTEN_STATELESS_RESET: i32 = 5
143
+
144
+ // ---- Sizes and limits -----------------------------------------------------------
145
+
146
+ /** How many random bytes the constructor takes: the seed of every unpredictable byte the listener sends. */
147
+ export const QUIC_LISTENER_ENTROPY_SIZE: i32 = 32
148
+ /** The most stateless resets the listener sends at once, before the rate limit applies. */
149
+ export const QUIC_LISTENER_RESET_BURST: i32 = 16
150
+ /** After the burst, one stateless reset per this many milliseconds. */
151
+ export const QUIC_LISTENER_RESET_INTERVAL: i64 = 100
152
+ /**
153
+ * The smallest stateless reset (RFC 9000 §10.3): a first byte, at least 38
154
+ * unpredictable bits after the fixed ones, and the 16-byte token.
155
+ */
156
+ export const QUIC_LISTENER_MIN_RESET: i32 = 21
157
+ /** The longest a Retry token may be accepted for, in milliseconds: a client returns one within a round trip. */
158
+ export const QUIC_LISTENER_MAX_TOKEN_LIFETIME: i64 = 60000
159
+
160
+ /** The first byte of every Retry token this listener issues. */
161
+ const QUIC_LISTENER_TOKEN_MARKER: i32 = 0x52
162
+ /** A Retry token's MAC length: HMAC-SHA256 cut to 128 bits. */
163
+ const QUIC_LISTENER_TOKEN_MAC_SIZE: i32 = 16
164
+ /** A Retry token's fixed part before the original DCID: the marker, eight bytes of time, the DCID's length. */
165
+ const QUIC_LISTENER_TOKEN_HEAD: i32 = 10
166
+ /** Up to this datagram length a reset is exactly one byte shorter (RFC 9000 §10.3). */
167
+ const QUIC_LISTENER_SHORT_TRIGGER: i32 = 43
168
+
169
+ /**
170
+ * What `QuicListener.handle` decided about one datagram: `kind` is a
171
+ * `QUIC_LISTEN_*`, and `reply` the datagram to send back, empty when there is
172
+ * none. For `QUIC_LISTEN_ACCEPT` after a Retry, `retried` is set and
173
+ * `originalDcid` and `retryScid` are what `QuicConnection.acceptRetry` takes.
174
+ */
175
+ export class QuicListenerAnswer {
176
+ reply: u8[]
177
+ originalDcid: u8[]
178
+ retryScid: u8[]
179
+ kind: i32 = 0
180
+ retried: boolean = false
181
+
182
+ constructor() {
183
+ this.reply = []
184
+ this.originalDcid = []
185
+ this.retryScid = []
186
+ }
187
+ }
188
+
189
+ /** The bytes `bytes[from .. from + length)`, copied into an array of exactly that size. */
190
+ const quicListenerSlice = (bytes: u8[], from: i32, length: i32): u8[] => {
191
+ const out: u8[] = new Array<u8>(length)
192
+ const n: i32 = toI32(out.length)
193
+ for (let k: i32 = 0; k < n; k += 1) {
194
+ if (from + k >= 0 && from + k < toI32(bytes.length)) {
195
+ out[k] = bytes[from + k]
196
+ }
197
+ }
198
+ return out
199
+ }
200
+
201
+ /** Appends every byte of `bytes` to `out`. */
202
+ const quicListenerAppend = (out: u8[], bytes: u8[]): void => {
203
+ for (const b of bytes) {
204
+ out.push(b)
205
+ }
206
+ }
207
+
208
+ /** Appends `value`'s low `size` bytes to `out`, big-endian. */
209
+ const quicListenerPushNumber = (out: u8[], value: i64, size: i32): void => {
210
+ for (let k: i32 = size - 1; k >= 0; k -= 1) {
211
+ out.push(toU8(toI32((value >> (toI64(k) * 8)) & 255)))
212
+ }
213
+ }
214
+
215
+ /**
216
+ * A Version Negotiation packet (RFC 9000 §17.2.1) answering a packet from
217
+ * `scid` to `dcid`: the IDs swapped, version 0, and version 1 as the only one
218
+ * supported. `unused` fills the first byte's low six bits, under the 0x40 the
219
+ * RFC asks for so the packet looks like QUIC to a demultiplexer.
220
+ *
221
+ * TODO(WP34 Q1): every other packet format is `nish/net/quic-packet`'s; this
222
+ * belongs beside `quicRetryPacket` there, which this stage may not edit.
223
+ */
224
+ const quicListenerVersionNegotiation = (dcid: u8[], scid: u8[], unused: i32): u8[] => {
225
+ const out: u8[] = []
226
+ out.push(toU8((unused & 0x3f) | 0xc0))
227
+ quicListenerPushNumber(out, 0, 4)
228
+ out.push(toU8(toI32(scid.length)))
229
+ quicListenerAppend(out, scid)
230
+ out.push(toU8(toI32(dcid.length)))
231
+ quicListenerAppend(out, dcid)
232
+ quicListenerPushNumber(out, 1, 4)
233
+ return out
234
+ }
235
+
236
+ /**
237
+ * The server's answers to datagrams no connection owns, under one
238
+ * `QuicServerConfig`. Make one when the server starts, with the same
239
+ * configuration its connections use, and hand it every such datagram with
240
+ * the address it came from and the time.
241
+ *
242
+ * A configuration whose `statelessResetKey` or `retryTokenKey` is not
243
+ * `QUIC_CONN_STATIC_KEY_SIZE` bytes, or whose `retryTokenLifetime` is outside
244
+ * 1 to `QUIC_LISTENER_MAX_TOKEN_LIFETIME`, or entropy of another length, make
245
+ * a listener that drops everything.
246
+ */
247
+ export class QuicListener {
248
+ config: QuicServerConfig
249
+ seed: u8[]
250
+ /** Unpredictable bytes not yet used, from `poolHead`, and the counter the next block is made from. */
251
+ pool: u8[]
252
+ counter: i64 = 0
253
+ /** The latest time the caller gave, in milliseconds; time never runs backwards here. */
254
+ now: i64 = 0
255
+ /** When the reset budget was last topped up (-1: never yet), and the stateless resets that may go now. */
256
+ resetRefilled: i64 = -1
257
+ resetBudget: i32 = 0
258
+ poolHead: i32 = 0
259
+ /** Stateless resets sent, and ones the rate limit held back. */
260
+ resetsSent: i32 = 0
261
+ resetsLimited: i32 = 0
262
+ /** Whether the configuration and entropy were usable. */
263
+ usable: boolean = false
264
+
265
+ /** A listener under `config`; `entropy`'s `QUIC_LISTENER_ENTROPY_SIZE` bytes are copied, then wiped in the caller's array. */
266
+ constructor(config: QuicServerConfig, entropy: u8[]) {
267
+ this.config = config
268
+ this.seed = quicListenerSlice(entropy, 0, QUIC_LISTENER_ENTROPY_SIZE)
269
+ this.pool = []
270
+ this.usable =
271
+ toI32(entropy.length) === QUIC_LISTENER_ENTROPY_SIZE &&
272
+ toI32(config.statelessResetKey.length) === QUIC_CONN_STATIC_KEY_SIZE &&
273
+ toI32(config.retryTokenKey.length) === QUIC_CONN_STATIC_KEY_SIZE &&
274
+ config.retryTokenLifetime >= 1 &&
275
+ config.retryTokenLifetime <= QUIC_LISTENER_MAX_TOKEN_LIFETIME
276
+ secureZero(entropy)
277
+ }
278
+
279
+ /** `count` unpredictable bytes: HMAC-SHA256 under the seed of a counter, a block at a time. */
280
+ random(count: i32): u8[] {
281
+ const out: u8[] = []
282
+ while (toI32(out.length) < count) {
283
+ if (this.poolHead >= toI32(this.pool.length)) {
284
+ secureZero(this.pool)
285
+ const block: u8[] = []
286
+ quicListenerPushNumber(block, this.counter, 8)
287
+ this.counter = this.counter + 1
288
+ this.pool = hmacSha256(this.seed, block)
289
+ this.poolHead = 0
290
+ }
291
+ if (this.poolHead >= 0 && this.poolHead < toI32(this.pool.length)) {
292
+ out.push(this.pool[this.poolHead])
293
+ this.pool[this.poolHead] = toU8(0)
294
+ }
295
+ this.poolHead = this.poolHead + 1
296
+ }
297
+ return out
298
+ }
299
+
300
+ /** One unpredictable value in 0 to 255. */
301
+ randomByte(): i32 {
302
+ const one: i32 = 1
303
+ const b: u8[] = this.random(one)
304
+ return toI32(b.length) > 0 ? toI32(b[0]) : 0
305
+ }
306
+
307
+ /**
308
+ * Decides what to answer a datagram whose first packet no connection
309
+ * owns, from `address` (the client's address as the caller's socket
310
+ * reports it, port included) at `now` (the caller's monotonic time in
311
+ * milliseconds). See the module header for the rules; nothing a peer sends
312
+ * makes it panic.
313
+ */
314
+ handle(datagram: u8[], address: u8[], now: i64): QuicListenerAnswer {
315
+ const answer: QuicListenerAnswer = new QuicListenerAnswer()
316
+ if (now > this.now) {
317
+ this.now = now
318
+ }
319
+ if (!this.usable) {
320
+ return answer
321
+ }
322
+ const n: i32 = toI32(datagram.length)
323
+ const header: QuicHeader = quicParseHeader(datagram, 0, QUIC_CONN_CID_LENGTH)
324
+ if (header.error === QUIC_ERR_VERSION) {
325
+ // §5.2.2: answer an unsupported version only in a datagram that could
326
+ // start a connection, and §6.1: never a Version Negotiation packet.
327
+ if (header.version !== 0 && n >= QUIC_CONN_DATAGRAM_SIZE) {
328
+ answer.kind = QUIC_LISTEN_VERSION_NEGOTIATION
329
+ answer.reply = quicListenerVersionNegotiation(header.dcid, header.scid, this.randomByte())
330
+ }
331
+ return answer
332
+ }
333
+ if (header.error !== QUIC_PACKET_OK) {
334
+ return answer
335
+ }
336
+ if (header.type === QUIC_PACKET_SHORT) {
337
+ return this.statelessReset(answer, n, header.dcid)
338
+ }
339
+ // §14.1, §7.2, §5.2.2: only an Initial in a full-sized datagram, sent to
340
+ // a DCID of at least 8 bytes, can start a connection; the rest is dropped.
341
+ if (header.type !== QUIC_PACKET_INITIAL || n < QUIC_CONN_DATAGRAM_SIZE || toI32(header.dcid.length) < 8) {
342
+ return answer
343
+ }
344
+ const token: u8[] = header.token
345
+ if (toI32(token.length) === 0 || toI32(token[0]) !== QUIC_LISTENER_TOKEN_MARKER) {
346
+ if (!this.config.retry) {
347
+ answer.kind = QUIC_LISTEN_ACCEPT
348
+ return answer
349
+ }
350
+ return this.retry(answer, header, address)
351
+ }
352
+ if (this.tokenValid(token, header.dcid, address)) {
353
+ answer.kind = QUIC_LISTEN_ACCEPT
354
+ answer.retried = true
355
+ answer.originalDcid = quicListenerSlice(
356
+ token,
357
+ QUIC_LISTENER_TOKEN_HEAD,
358
+ toI32(token[QUIC_LISTENER_TOKEN_HEAD - 1])
359
+ )
360
+ answer.retryScid = header.dcid
361
+ return answer
362
+ }
363
+ return this.invalidToken(answer, datagram, header)
364
+ }
365
+
366
+ /**
367
+ * The token's MAC over its own bytes before the MAC (`head`), the ID the
368
+ * client is to send to, and its address: what makes a token good for one
369
+ * address and one connection only.
370
+ */
371
+ tokenMac(head: u8[], retryScid: u8[], address: u8[]): u8[] {
372
+ const message: u8[] = []
373
+ quicListenerAppend(message, head)
374
+ message.push(toU8(toI32(retryScid.length)))
375
+ quicListenerAppend(message, retryScid)
376
+ quicListenerPushNumber(message, toI64(toI32(address.length)), 4)
377
+ quicListenerAppend(message, address)
378
+ const mac: u8[] = hmacSha256(this.config.retryTokenKey, message)
379
+ const out: u8[] = quicListenerSlice(mac, 0, QUIC_LISTENER_TOKEN_MAC_SIZE)
380
+ secureZero(mac)
381
+ return out
382
+ }
383
+
384
+ /** A Retry token for the client at `address`, whose first Initial went to `odcid`, now told to send to `retryScid`. */
385
+ issueToken(odcid: u8[], retryScid: u8[], address: u8[]): u8[] {
386
+ const token: u8[] = []
387
+ token.push(toU8(QUIC_LISTENER_TOKEN_MARKER))
388
+ quicListenerPushNumber(token, this.now, 8)
389
+ token.push(toU8(toI32(odcid.length)))
390
+ quicListenerAppend(token, odcid)
391
+ quicListenerAppend(token, this.tokenMac(token, retryScid, address))
392
+ return token
393
+ }
394
+
395
+ /**
396
+ * Whether `token` is one this listener issued for the client at `address`
397
+ * that was told to send to `dcid`, and still within its lifetime: the
398
+ * lengths add up, the MAC matches (compared in constant time), and it was
399
+ * issued no later than now and less than `retryTokenLifetime` ago.
400
+ */
401
+ tokenValid(token: u8[], dcid: u8[], address: u8[]): boolean {
402
+ const length: i32 = toI32(token.length)
403
+ if (length < QUIC_LISTENER_TOKEN_HEAD + QUIC_LISTENER_TOKEN_MAC_SIZE) {
404
+ return false
405
+ }
406
+ const odcidLength: i32 = toI32(token[QUIC_LISTENER_TOKEN_HEAD - 1])
407
+ const head: i32 = QUIC_LISTENER_TOKEN_HEAD + odcidLength
408
+ if (odcidLength > QUIC_MAX_CID_LENGTH || head + QUIC_LISTENER_TOKEN_MAC_SIZE !== length) {
409
+ return false
410
+ }
411
+ const mac: u8[] = this.tokenMac(quicListenerSlice(token, 0, head), dcid, address)
412
+ if (!timingSafeEqual(mac, quicListenerSlice(token, head, QUIC_LISTENER_TOKEN_MAC_SIZE))) {
413
+ return false
414
+ }
415
+ let issued: i64 = 0
416
+ for (let k: i32 = 1; k < QUIC_LISTENER_TOKEN_HEAD - 1; k += 1) {
417
+ if (k < toI32(token.length)) {
418
+ issued = (issued << 8) | toI64(token[k])
419
+ }
420
+ }
421
+ return issued <= this.now && this.now - issued < this.config.retryTokenLifetime
422
+ }
423
+
424
+ /**
425
+ * A Retry for the client's first Initial (RFC 9000 §8.1.2): a new
426
+ * connection ID for the client to send to, and a token binding it, the
427
+ * original DCID and the client's address to now.
428
+ */
429
+ retry(answer: QuicListenerAnswer, header: QuicHeader, address: u8[]): QuicListenerAnswer {
430
+ const retryScid: u8[] = this.random(QUIC_CONN_CID_LENGTH)
431
+ const token: u8[] = this.issueToken(header.dcid, retryScid, address)
432
+ const packet: u8[] | null = quicRetryPacket(
433
+ header.scid,
434
+ retryScid,
435
+ token,
436
+ header.dcid,
437
+ this.randomByte() & 15
438
+ )
439
+ if (packet !== null) {
440
+ answer.kind = QUIC_LISTEN_RETRY
441
+ answer.reply = packet
442
+ }
443
+ return answer
444
+ }
445
+
446
+ /**
447
+ * The answer to an Initial carrying an invalid Retry token (RFC 9000
448
+ * §8.1.2): an Initial with CONNECTION_CLOSE and INVALID_TOKEN, under the
449
+ * Initial keys of the DCID the client sent to, from that DCID. It is sent
450
+ * only when the client's Initial authenticates, so it answers a client and
451
+ * not noise; it is far smaller than the datagram it answers.
452
+ */
453
+ invalidToken(answer: QuicListenerAnswer, datagram: u8[], header: QuicHeader): QuicListenerAnswer {
454
+ const secrets: QuicInitialSecrets | null = quicInitialSecrets(header.dcid)
455
+ if (secrets === null) {
456
+ return answer
457
+ }
458
+ const read: QuicKeys | null = quicKeys(QUIC_AEAD_AES_128_GCM, secrets.client)
459
+ if (read === null) {
460
+ return answer
461
+ }
462
+ const opened: QuicPacket = quicOpenPacket(read, datagram, header, -1)
463
+ // The write keys are derived only for an Initial that opened, so a forged
464
+ // token on noise costs one derivation, not two.
465
+ const write: QuicKeys | null =
466
+ opened.error === QUIC_PACKET_OK ? quicKeys(QUIC_AEAD_AES_128_GCM, secrets.server) : null
467
+ if (write === null) {
468
+ return answer
469
+ }
470
+ const payload: u8[] = []
471
+ const none: u8[] = []
472
+ quicPushConnectionClose(payload, false, QUIC_ERROR_INVALID_TOKEN, 0, none)
473
+ const first: u8[] | null = quicLongHeader(
474
+ QUIC_PACKET_INITIAL,
475
+ header.scid,
476
+ header.dcid,
477
+ none,
478
+ 0,
479
+ 1,
480
+ toI32(payload.length)
481
+ )
482
+ if (first === null) {
483
+ return answer
484
+ }
485
+ const packet: u8[] | null = quicSealPacket(write, first, 0, payload)
486
+ if (packet !== null) {
487
+ answer.kind = QUIC_LISTEN_INVALID_TOKEN
488
+ answer.reply = packet
489
+ }
490
+ return answer
491
+ }
492
+
493
+ /** Takes one stateless reset from the rate limit's budget, topping it up first. Answers whether one was there. */
494
+ takeReset(): boolean {
495
+ if (this.resetRefilled < 0) {
496
+ this.resetRefilled = this.now
497
+ this.resetBudget = QUIC_LISTENER_RESET_BURST
498
+ }
499
+ const earned: i64 = (this.now - this.resetRefilled) / QUIC_LISTENER_RESET_INTERVAL
500
+ if (earned > 0) {
501
+ const room: i64 = toI64(QUIC_LISTENER_RESET_BURST - this.resetBudget)
502
+ this.resetBudget = this.resetBudget + toI32(earned < room ? earned : room)
503
+ this.resetRefilled = this.resetRefilled + earned * QUIC_LISTENER_RESET_INTERVAL
504
+ }
505
+ if (this.resetBudget <= 0) {
506
+ this.resetsLimited = this.resetsLimited + 1
507
+ return false
508
+ }
509
+ this.resetBudget = this.resetBudget - 1
510
+ this.resetsSent = this.resetsSent + 1
511
+ return true
512
+ }
513
+
514
+ /**
515
+ * A stateless reset answering an `n`-byte datagram sent to `dcid` (RFC
516
+ * 9000 §10.3): a short-header first byte, unpredictable bytes, and the
517
+ * token for `dcid`. It is one byte shorter than a datagram of up to 43
518
+ * bytes and between 43 bytes and one byte short of the datagram (at most
519
+ * 1200) otherwise, so it is always shorter; a datagram too short for that
520
+ * to leave 21 bytes, or one past the rate limit, gets nothing.
521
+ */
522
+ statelessReset(answer: QuicListenerAnswer, n: i32, dcid: u8[]): QuicListenerAnswer {
523
+ if (n <= QUIC_LISTENER_MIN_RESET || !this.takeReset()) {
524
+ return answer
525
+ }
526
+ let size: i32 = n - 1
527
+ if (n > QUIC_LISTENER_SHORT_TRIGGER) {
528
+ const most: i32 = n - 1 < QUIC_CONN_DATAGRAM_SIZE ? n - 1 : QUIC_CONN_DATAGRAM_SIZE
529
+ const span: i32 = most - QUIC_LISTENER_SHORT_TRIGGER + 1
530
+ const two: i32 = 2
531
+ const pick: u8[] = this.random(two)
532
+ const r: i32 = toI32(pick.length) > 1 ? (toI32(pick[0]) << 8) | toI32(pick[1]) : 0
533
+ size = QUIC_LISTENER_SHORT_TRIGGER + (span > 0 ? r % span : 0)
534
+ }
535
+ const reply: u8[] = this.random(size - QUIC_RESET_TOKEN_SIZE)
536
+ if (toI32(reply.length) > 0) {
537
+ reply[0] = toU8((toI32(reply[0]) & 0x3f) | 0x40)
538
+ }
539
+ quicListenerAppend(reply, quicStatelessResetToken(this.config.statelessResetKey, dcid))
540
+ answer.kind = QUIC_LISTEN_STATELESS_RESET
541
+ answer.reply = reply
542
+ return answer
543
+ }
544
+ }
545
+
546
+ /**
547
+ * The next datagram `conn` sends at `now`, as `takeDatagram` answers it,
548
+ * but only when the connection's pacer has the credit for a full datagram
549
+ * (RFC 9002 §7.7); `null` when it has not, or when nothing is due. The
550
+ * datagram is charged to the pacer at its real size.
551
+ */
552
+ export const quicListenerTakePaced = (conn: QuicConnection, now: i64): u8[] | null => {
553
+ if (conn.recovery.pacerDelay(now, QUIC_CONN_DATAGRAM_SIZE) > 0) {
554
+ return null
555
+ }
556
+ const out: u8[] | null = conn.takeDatagram(now)
557
+ if (out !== null) {
558
+ conn.recovery.onPaced(now, toI32(out.length))
559
+ }
560
+ return out
561
+ }
562
+
563
+ /**
564
+ * When `quicListenerTakePaced` next lets `conn` send a full datagram, in the
565
+ * caller's milliseconds: `now` when it may already.
566
+ */
567
+ export const quicListenerPaceTime = (conn: QuicConnection, now: i64): i64 =>
568
+ now + conn.recovery.pacerDelay(now, QUIC_CONN_DATAGRAM_SIZE)
569
+
570
+ /** The most datagrams one GSO send carries: Linux's `UDP_MAX_SEGMENTS`. */
571
+ export const QUIC_LISTENER_FLIGHT_MAX: i32 = 64
572
+
573
+ /** What `quicListenerTakeFlight` wrote: how many datagrams, and the segment size to send them with. */
574
+ export class QuicFlight {
575
+ /** The datagrams in the flight. */
576
+ count: i32 = 0
577
+ /** Every datagram's size but the last's, which may be shorter: the `UDP_SEGMENT` to send with. */
578
+ segment: i32 = 0
579
+ }
580
+
581
+ /**
582
+ * Writes the next flight `conn` sends at `now` into `buf[at .. at + cap)`,
583
+ * datagram after datagram, for one GSO send, and answers its length in
584
+ * bytes (0 for none); `flight` says how many datagrams it holds and the
585
+ * segment size. Each datagram goes only when the pacer has credit for a full
586
+ * one (RFC 9002 §7.7) and is charged at its real size, as
587
+ * `quicListenerTakePaced` does, so the flight is the pacer's whole credit
588
+ * and no more. It stops at the first datagram shorter than
589
+ * `QUIC_CONN_DATAGRAM_SIZE`, which can only be the last of a segmented send,
590
+ * at `QUIC_LISTENER_FLIGHT_MAX` datagrams, and when `cap` has no room for
591
+ * another.
592
+ */
593
+ export const quicListenerTakeFlight = (
594
+ conn: QuicConnection,
595
+ now: i64,
596
+ buf: u8[],
597
+ at: i32,
598
+ cap: i32,
599
+ flight: QuicFlight
600
+ ): i32 => {
601
+ flight.count = 0
602
+ flight.segment = 0
603
+ let length: i32 = 0
604
+ while (
605
+ flight.count < QUIC_LISTENER_FLIGHT_MAX &&
606
+ length + QUIC_CONN_DATAGRAM_SIZE <= cap &&
607
+ conn.recovery.pacerDelay(now, QUIC_CONN_DATAGRAM_SIZE) <= 0
608
+ ) {
609
+ const n: i32 = conn.takeDatagramInto(buf, at + length, now)
610
+ if (n <= 0) {
611
+ return length
612
+ }
613
+ conn.recovery.onPaced(now, n)
614
+ if (flight.count === 0) {
615
+ flight.segment = n
616
+ }
617
+ flight.count = flight.count + 1
618
+ length = length + n
619
+ if (n < QUIC_CONN_DATAGRAM_SIZE) {
620
+ return length
621
+ }
622
+ }
623
+ return length
624
+ }
625
+
626
+ /**
627
+ * Hands `conn` the datagrams of one receive, `buf[off .. off + len)`, at
628
+ * `now`: one datagram when `segment` is 0, else a GRO receive cut every
629
+ * `segment` bytes (the last may be shorter). Each is read in place. Answers
630
+ * how many it handed over.
631
+ */
632
+ export const quicListenerReceiveSegments = (
633
+ conn: QuicConnection,
634
+ buf: u8[],
635
+ off: i32,
636
+ len: i32,
637
+ segment: i32,
638
+ now: i64
639
+ ): i32 => {
640
+ if (segment <= 0) {
641
+ conn.receiveWindow(buf, off, len, now)
642
+ return 1
643
+ }
644
+ let count: i32 = 0
645
+ let at: i32 = 0
646
+ while (at < len) {
647
+ const n: i32 = len - at < segment ? len - at : segment
648
+ conn.receiveWindow(buf, off + at, n, now)
649
+ at = at + n
650
+ count += 1
651
+ }
652
+ return count
653
+ }