@amritk/nish-x86_64-linux 0.16.0 → 0.18.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 (58) 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 +50 -13
  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-simd.c +203 -0
  11. package/runtime/runtime-wasm.c +12 -0
  12. package/runtime/runtime.c +66 -18
  13. package/runtime/shim.mjs +114 -5
  14. package/scripts/build.sh +14 -12
  15. package/std/README.md +168 -36
  16. package/std/crypto/aes.ts +51 -8
  17. package/std/crypto/base64url.ts +25 -12
  18. package/std/crypto/chacha20poly1305.ts +43 -29
  19. package/std/crypto/ct.ts +6 -1
  20. package/std/crypto/hkdf.ts +364 -3
  21. package/std/crypto/hmac.ts +307 -7
  22. package/std/crypto/p256.ts +618 -34
  23. package/std/crypto/sha1.ts +147 -0
  24. package/std/crypto/sha256.ts +3 -2
  25. package/std/crypto/x25519.ts +88 -36
  26. package/std/crypto/x509.ts +130 -47
  27. package/std/json.ts +297 -41
  28. package/std/net/hpack.ts +1094 -0
  29. package/std/net/http-fields.ts +604 -0
  30. package/std/net/http1-server.ts +1389 -0
  31. package/std/net/http1.ts +1492 -0
  32. package/std/net/http2-frame.ts +745 -0
  33. package/std/net/http2-tls.ts +216 -0
  34. package/std/net/http2.ts +1433 -0
  35. package/std/net/http3-frame.ts +457 -0
  36. package/std/net/http3-server.ts +911 -0
  37. package/std/net/http3.ts +2145 -0
  38. package/std/net/qpack.ts +1163 -0
  39. package/std/net/quic-conn-ack.ts +206 -0
  40. package/std/net/quic-conn-cid.ts +425 -0
  41. package/std/net/quic-conn-params.ts +462 -0
  42. package/std/net/quic-datagram.ts +130 -0
  43. package/std/net/quic-frame.ts +1206 -0
  44. package/std/net/quic-listener.ts +929 -0
  45. package/std/net/quic-packet.ts +1698 -0
  46. package/std/net/quic-recovery.ts +849 -0
  47. package/std/net/quic-stream.ts +1609 -0
  48. package/std/net/quic.ts +3578 -0
  49. package/std/net/tls/codec.ts +1089 -0
  50. package/std/net/tls/record-server.ts +1173 -0
  51. package/std/net/tls/record.ts +621 -0
  52. package/std/net/tls/schedule.ts +429 -0
  53. package/std/net/tls-tcp.ts +414 -0
  54. package/std/net/tls.ts +1437 -0
  55. package/std/net/websocket.ts +823 -0
  56. package/std/net/webtransport.ts +1334 -0
  57. package/std/secret.ts +62 -0
  58. package/std/text.ts +77 -0
@@ -0,0 +1,929 @@
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, QUIC_LISTENER_ANSWER_SIZE } from "nish/net/quic-listener";
11
+ *
12
+ * const listener = new QuicListener(config, entropy); // entropy: QUIC_LISTENER_ENTROPY_SIZE random bytes
13
+ * const answer = new QuicListenerAnswer(QUIC_LISTENER_ANSWER_SIZE); // once, beside the listener
14
+ * // For a datagram buf[at .. at + len) whose first packet no connection owns:
15
+ * if (listener.handleWindow(buf, at, len, fromAddress, now, answer) === QUIC_LISTEN_ACCEPT) {
16
+ * const conn = new QuicConnection(config, connectionEntropy);
17
+ * if (answer.retried) { conn.acceptRetry(answer.originalDcid, answer.retryScid); }
18
+ * conn.receiveWindow(buf, at, len, now);
19
+ * } else if (toI32(answer.reply.length) > 0) {
20
+ * … send answer.reply back to fromAddress …
21
+ * }
22
+ *
23
+ * **Nothing is kept per datagram** (H3-3 in `docs/security/http3.md`).
24
+ * `handleWindow` reads the datagram where it lies, its header parsed in
25
+ * place, and writes every answer into the caller's `QuicListenerAnswer`,
26
+ * whose arrays were given their room once; every MAC, key and seal is
27
+ * computed in scratch the listener made at start-up. `handle` is the same
28
+ * over a whole array, into an answer of its own each time, for a caller
29
+ * that keeps them.
30
+ *
31
+ * **Routing is the caller's.** The listener sees only datagrams the caller
32
+ * could not hand to a live connection, and it must see only those: a stateless
33
+ * reset carries the token for the packet's connection ID, and handing the
34
+ * listener a packet for a connection that is still alive would give that
35
+ * token, which ends the connection, to whoever sent it (RFC 9000 §21.11).
36
+ *
37
+ * **Retry tokens** (§8.1.4) are this server's own format: a marker byte, the
38
+ * time the token was issued, and the client's original Destination
39
+ * Connection ID, then 16 bytes of HMAC-SHA256 under the configuration's
40
+ * `retryTokenKey` over all of that, the Retry's Source Connection ID (which
41
+ * the client's next Initial must be sent to) and the client's address as the
42
+ * caller gives it, port included. So a token is good only from the address
43
+ * it was sent to, only for the connection it was issued for, and only for
44
+ * `retryTokenLifetime` milliseconds. A token that carries the marker and fails
45
+ * any of that is an invalid Retry token, which closes the attempt with
46
+ * INVALID_TOKEN (§8.1.2); a token without the marker, which some other
47
+ * server issued, is treated as no token at all (§8.1.3).
48
+ *
49
+ * **Stateless resets** (§10.3) end in the token `quicStatelessResetToken`
50
+ * derives (computed in place, by `quicConnResetTokenInto`) from the configuration's static key and the packet's connection
51
+ * ID, so a server that restarted with the same key can end the connections
52
+ * it lost. RFC 9000 §10.3 and §10.3.3 bound them, and each bound is held
53
+ * here. A reset is sent only for a short header, and is always shorter than
54
+ * the datagram that caused it, so two endpoints resetting each other shrink
55
+ * to nothing; one byte shorter for a datagram of 43 bytes or fewer, and
56
+ * never under 21 bytes, so a datagram of 21 or fewer gets none. That also
57
+ * bounds amplification at less than one to one. They are rate limited:
58
+ * `QUIC_LISTENER_RESET_BURST` at once, then one per
59
+ * `QUIC_LISTENER_RESET_INTERVAL` milliseconds. Each reset's other bytes are
60
+ * unpredictable, from the listener's own generator.
61
+ *
62
+ * **Pacing** (RFC 9002 §7.7) is the carrier's send loop, so it is here
63
+ * beside the rest of what the carrier calls: `quicListenerTakePaced` takes a
64
+ * connection's next datagram only when its pacer has the credit for it, and
65
+ * `quicListenerPaceTime` says when it will. The pacer earns 5/4 of the
66
+ * congestion window per smoothed RTT and lets a burst of at most the initial
67
+ * window go at once, so a flight leaves spread over the round trip instead
68
+ * of in one burst that overruns a queue on the path.
69
+ *
70
+ * let out: u8[] | null = quicListenerTakePaced(conn, now);
71
+ * while (out !== null) { … send it … ; out = quicListenerTakePaced(conn, now); }
72
+ * … and wake at the earlier of conn.deadline() and quicListenerPaceTime(conn, now)
73
+ *
74
+ * **GSO and GRO** (`nish:net`'s `UDP_SEGMENT` and `UDP_GRO`). A carrier that
75
+ * sends with segmentation offload takes a whole flight at once:
76
+ * `quicListenerTakeFlight` writes as many datagrams as the pacer has credit
77
+ * for back to back into one buffer, every one `QUIC_CONN_DATAGRAM_SIZE`
78
+ * bytes but the last, which is what one `udpSendTo` with that segment size
79
+ * cuts back into datagrams; each is written in place, so a flight allocates
80
+ * nothing. The flight is paced as a whole: it is as long as the pacer's
81
+ * credit, and charged to it datagram by datagram, so a burst is never more
82
+ * than the initial window however the carrier sends it. On the receiving
83
+ * side, `quicListenerReceiveSegments` hands a connection the datagrams of
84
+ * one GRO receive, segment by segment, in place.
85
+ *
86
+ * const flight = new QuicFlight();
87
+ * const n: i32 = quicListenerTakeFlight(conn, now, buf, 0, toI32(buf.length), flight);
88
+ * if (n > 0) { udpSendTo(fd, buf, 0, n, peer, flight.count > 1 ? flight.segment : 0, 0); }
89
+ *
90
+ * **Sans-IO and deterministic.** The caller gives the time, the client's
91
+ * address and, once, `QUIC_LISTENER_ENTROPY_SIZE` random bytes: every
92
+ * unpredictable byte the listener sends (a Retry's connection ID and unused
93
+ * bits, Version Negotiation's unused bits, a reset's padding and length) is
94
+ * HMAC-SHA256 under that seed of a counter. So a recorded exchange replays
95
+ * byte for byte.
96
+ *
97
+ * **Secrets.** The seed lives in the listener and the static keys in the
98
+ * caller's `QuicServerConfig`, for the server's lifetime, as plain bytes,
99
+ * since a `Secret` may not be a field (NL2430); QUIC-5 in
100
+ * `docs/security/quic.md` records it, with #430.
101
+ *
102
+ * Written from RFC 9000, in this module's own structure; nothing here is
103
+ * ported from another implementation. Private names carry the
104
+ * `quicListener` prefix (`docs/wp26-stdlib.md` §3e).
105
+ */
106
+ import { AesKey, aesGcmSeal, aesKeyInto } from "nish/crypto/aes"
107
+ import { timingSafeEqualAt } from "nish/crypto/ct"
108
+ import { HkdfScratch } from "nish/crypto/hkdf"
109
+ import { HmacSha256Scratch } from "nish/crypto/hmac"
110
+ import {
111
+ QUIC_AEAD_AES_128_GCM,
112
+ QUIC_ERR_VERSION,
113
+ QUIC_MAX_CID_LENGTH,
114
+ QUIC_PACKET_INITIAL,
115
+ QUIC_PACKET_OK,
116
+ QUIC_PACKET_SHORT,
117
+ QUIC_RETRY_TAG_SIZE,
118
+ QuicHeader,
119
+ QuicKeys,
120
+ QuicKeysSlot,
121
+ QuicPacket,
122
+ quicDecryptPayload,
123
+ quicInitialSecretsInto,
124
+ quicKeysInto,
125
+ quicLongHeaderSize,
126
+ quicPacketCopy,
127
+ quicParseHeaderInto,
128
+ quicPutLongHeader,
129
+ quicSealInPlace,
130
+ quicUnprotectHeader,
131
+ } from "nish/net/quic-packet"
132
+ import { QUIC_ERROR_INVALID_TOKEN, QUIC_RESET_TOKEN_SIZE, quicPutConnectionClose } from "nish/net/quic-frame"
133
+ import {
134
+ QUIC_CONN_CID_LENGTH,
135
+ QUIC_CONN_DATAGRAM_SIZE,
136
+ QUIC_CONN_STATIC_KEY_SIZE,
137
+ QuicConnection,
138
+ QuicServerConfig,
139
+ quicConnResetTokenInto,
140
+ } from "nish/net/quic"
141
+
142
+ // ---- What `handle` answers ----------------------------------------------------
143
+
144
+ /** Nothing to send and no connection to make: the datagram is dropped. */
145
+ export const QUIC_LISTEN_DROP: i32 = 0
146
+ /** Make a connection and hand it the datagram; after a Retry, `acceptRetry` it first. */
147
+ export const QUIC_LISTEN_ACCEPT: i32 = 1
148
+ /** Send `reply`, a Version Negotiation packet (RFC 9000 §17.2.1). */
149
+ export const QUIC_LISTEN_VERSION_NEGOTIATION: i32 = 2
150
+ /** Send `reply`, a Retry packet with an address-validation token (§17.2.5). */
151
+ export const QUIC_LISTEN_RETRY: i32 = 3
152
+ /** Send `reply`, an Initial carrying CONNECTION_CLOSE with INVALID_TOKEN (§8.1.2). */
153
+ export const QUIC_LISTEN_INVALID_TOKEN: i32 = 4
154
+ /** Send `reply`, a stateless reset (§10.3). */
155
+ export const QUIC_LISTEN_STATELESS_RESET: i32 = 5
156
+
157
+ // ---- Sizes and limits -----------------------------------------------------------
158
+
159
+ /** How many random bytes the constructor takes: the seed of every unpredictable byte the listener sends. */
160
+ export const QUIC_LISTENER_ENTROPY_SIZE: i32 = 32
161
+ /** The most stateless resets the listener sends at once, before the rate limit applies. */
162
+ export const QUIC_LISTENER_RESET_BURST: i32 = 16
163
+ /** After the burst, one stateless reset per this many milliseconds. */
164
+ export const QUIC_LISTENER_RESET_INTERVAL: i64 = 100
165
+ /**
166
+ * The smallest stateless reset (RFC 9000 §10.3): a first byte, at least 38
167
+ * unpredictable bits after the fixed ones, and the 16-byte token.
168
+ */
169
+ export const QUIC_LISTENER_MIN_RESET: i32 = 21
170
+ /** The longest a Retry token may be accepted for, in milliseconds: a client returns one within a round trip. */
171
+ export const QUIC_LISTENER_MAX_TOKEN_LIFETIME: i64 = 60000
172
+
173
+ /** The first byte of every Retry token this listener issues. */
174
+ const QUIC_LISTENER_TOKEN_MARKER: i32 = 0x52
175
+ /** A Retry token's MAC length: HMAC-SHA256 cut to 128 bits. */
176
+ const QUIC_LISTENER_TOKEN_MAC_SIZE: i32 = 16
177
+ /** A Retry token's fixed part before the original DCID: the marker, eight bytes of time, the DCID's length. */
178
+ const QUIC_LISTENER_TOKEN_HEAD: i32 = 10
179
+ /** Up to this datagram length a reset is exactly one byte shorter (RFC 9000 §10.3). */
180
+ const QUIC_LISTENER_SHORT_TRIGGER: i32 = 43
181
+ /** The generator's block, one HMAC-SHA256 tag: the bytes `refill` makes at a time. */
182
+ const QUIC_LISTENER_POOL: i32 = 32
183
+ /** The generator's message: its counter, eight bytes. */
184
+ const QUIC_LISTENER_BLOCK: i32 = 8
185
+ /** A typed 0, the start of a whole array, since a bare literal is an `f64` under `--number-mode f64`. */
186
+ const QUIC_LISTENER_FROM: i32 = 0
187
+
188
+ /**
189
+ * The largest answer the listener sends, and so the room `QuicListenerAnswer`
190
+ * makes for `reply`: a stateless reset is at most a full datagram (RFC 9000
191
+ * §10.3); a Version Negotiation packet, which echoes two IDs of up to 255
192
+ * bytes, a Retry and the INVALID_TOKEN close are all shorter.
193
+ */
194
+ export const QUIC_LISTENER_ANSWER_SIZE: i32 = QUIC_CONN_DATAGRAM_SIZE
195
+
196
+ /**
197
+ * What `QuicListener.handleWindow` decided about one datagram: `kind` is a
198
+ * `QUIC_LISTEN_*`, and `reply` the datagram to send back, empty when there
199
+ * is none. For `QUIC_LISTEN_ACCEPT` after a Retry, `retried` is set and
200
+ * `originalDcid` and `retryScid` are what `QuicConnection.acceptRetry` takes.
201
+ *
202
+ * Make one once, with `QUIC_LISTENER_ANSWER_SIZE` as its `room`, and hand it
203
+ * to every `handleWindow`: the constructor gives `reply` that room and each
204
+ * ID 20 bytes, and each answer refills them in it, its length the answer's,
205
+ * so answering allocates nothing (H3-3). A smaller room still works, and
206
+ * grows the first time an answer needs more; `handle` makes each answer
207
+ * with exactly the room its reply takes.
208
+ */
209
+ export class QuicListenerAnswer {
210
+ reply: u8[]
211
+ originalDcid: u8[]
212
+ retryScid: u8[]
213
+ kind: i32 = 0
214
+ retried: boolean = false
215
+
216
+ constructor(room: i32) {
217
+ this.reply = quicListenerRoom(room > 0 ? room : 0)
218
+ this.originalDcid = quicListenerRoom(QUIC_MAX_CID_LENGTH)
219
+ this.retryScid = quicListenerRoom(QUIC_MAX_CID_LENGTH)
220
+ }
221
+ }
222
+
223
+ /** An empty array with room for `size` bytes, so pushing up to that many allocates nothing. */
224
+ const quicListenerRoom = (size: i32): u8[] => {
225
+ const out: u8[] = new Array<u8>(size)
226
+ while (out.length > 0) {
227
+ out.pop()
228
+ }
229
+ return out
230
+ }
231
+
232
+ /** Sets `bytes`' length to `length` in the room it already has: popped, or pushed with zeros. */
233
+ const quicListenerResize = (bytes: u8[], length: i32): void => {
234
+ while (toI32(bytes.length) > length) {
235
+ bytes.pop()
236
+ }
237
+ while (toI32(bytes.length) < length) {
238
+ bytes.push(toU8(0))
239
+ }
240
+ }
241
+
242
+ /** Puts `from[at .. at + length)` in `to`, resized to `length` in its own room. */
243
+ const quicListenerRefill = (to: u8[], from: u8[], at: i32, length: i32): void => {
244
+ quicListenerResize(to, length)
245
+ quicPacketCopy(to, 0, from, at, length)
246
+ }
247
+
248
+ /** Writes `value`'s low `size` bytes into `buf` at `at`, big-endian. */
249
+ const quicListenerPutNumber = (buf: u8[], at: i32, value: i64, size: i32): void => {
250
+ for (let k: i32 = 0; k < size; k += 1) {
251
+ if (at + k >= 0 && at + k < toI32(buf.length)) {
252
+ buf[at + k] = toU8(toI32((value >> (toI64(size - 1 - k) * 8)) & 255))
253
+ }
254
+ }
255
+ }
256
+
257
+ /**
258
+ * RFC 9001 §5.8's fixed AES-128-GCM key and nonce for version 1's Retry
259
+ * integrity tag: the listener keeps the key expanded, so a Retry's tag is
260
+ * computed without `quicRetryIntegrityTag`'s expansion, which stores what it
261
+ * allocates and so cannot run in an arena block.
262
+ *
263
+ * TODO(WP34 Q1): `nish/net/quic-packet` holds the same two constants and
264
+ * should offer the tag over a kept key; this stage may not edit it.
265
+ */
266
+ const quicListenerRetryKey = (): u8[] => [
267
+ 0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a, 0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68, 0xc8, 0x4e,
268
+ ]
269
+ const quicListenerRetryNonce = (): u8[] => [
270
+ 0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb,
271
+ ]
272
+
273
+ /** Whether `[at, at + len)` is a window inside an array of `length` elements: `p256WindowFits`' test. */
274
+ const quicListenerWindowFits = (length: i32, at: i32, len: i32): boolean =>
275
+ at >= 0 && len >= 0 && at <= length - len
276
+
277
+ /**
278
+ * The server's answers to datagrams no connection owns, under one
279
+ * `QuicServerConfig`. Make one when the server starts, with the same
280
+ * configuration its connections use, and hand it every such datagram with
281
+ * the address it came from and the time.
282
+ *
283
+ * A configuration whose `statelessResetKey` or `retryTokenKey` is not
284
+ * `QUIC_CONN_STATIC_KEY_SIZE` bytes, or whose `retryTokenLifetime` is outside
285
+ * 1 to `QUIC_LISTENER_MAX_TOKEN_LIFETIME`, or entropy of another length, make
286
+ * a listener that drops everything.
287
+ *
288
+ * Everything an answer is computed in is made here, once: the header a
289
+ * datagram is parsed into, the HMAC and HKDF scratch, the Initial secrets
290
+ * and key slots of the INVALID_TOKEN close, and the generator's block. So
291
+ * `handleWindow` reads the datagram where it lies and keeps nothing.
292
+ */
293
+ export class QuicListener {
294
+ config: QuicServerConfig
295
+ seed: u8[]
296
+ /** Unpredictable bytes not yet used, from `poolHead`, and the counter the next block is made from. */
297
+ pool: u8[]
298
+ /** The counter's eight bytes, the generator's HMAC message. */
299
+ block: u8[]
300
+ /** What `handleWindow` parses a datagram's header into, in place. */
301
+ header: QuicHeader
302
+ /** Every HMAC and HKDF the listener computes runs here; its `sha256` makes tokens, resets and the generator's blocks. */
303
+ kdf: HkdfScratch
304
+ /** A full HMAC-SHA256 tag, wiped once its 16 bytes are used. */
305
+ tag: u8[]
306
+ /** A Retry token MAC's length fields: the Retry SCID's one byte, the address's four. */
307
+ lengths: u8[]
308
+ /** The Initial secrets of an INVALID_TOKEN close, and its keys, both directions. */
309
+ initialClient: u8[]
310
+ initialServer: u8[]
311
+ readSlot: QuicKeysSlot
312
+ writeSlot: QuicKeysSlot
313
+ packet: QuicPacket
314
+ /** The client's SCID and the DCID it sent to, copied out for `quicPutLongHeader`, which reads IDs from 0. */
315
+ peerCid: u8[]
316
+ ownCid: u8[]
317
+ /** An empty array: the INVALID_TOKEN close's token and reason. */
318
+ none: u8[]
319
+ /** Version 1's fixed Retry key (RFC 9001 §5.8), expanded once. */
320
+ retryKey: AesKey
321
+ /** What `handle` answers into before it copies the answer out. */
322
+ spare: QuicListenerAnswer
323
+ counter: i64 = 0
324
+ /** The latest time the caller gave, in milliseconds; time never runs backwards here. */
325
+ now: i64 = 0
326
+ /** When the reset budget was last topped up (-1: never yet), and the stateless resets that may go now. */
327
+ resetRefilled: i64 = -1
328
+ resetBudget: i32 = 0
329
+ poolHead: i32 = 0
330
+ /** Stateless resets sent, and ones the rate limit held back. */
331
+ resetsSent: i32 = 0
332
+ resetsLimited: i32 = 0
333
+ /** Whether the configuration and entropy were usable. */
334
+ usable: boolean = false
335
+
336
+ /** A listener under `config`; `entropy`'s `QUIC_LISTENER_ENTROPY_SIZE` bytes are copied, then wiped in the caller's array. */
337
+ constructor(config: QuicServerConfig, entropy: u8[]) {
338
+ this.config = config
339
+ this.seed = new Array<u8>(QUIC_LISTENER_ENTROPY_SIZE)
340
+ quicPacketCopy(this.seed, 0, entropy, 0, QUIC_LISTENER_ENTROPY_SIZE)
341
+ this.pool = new Array<u8>(QUIC_LISTENER_POOL)
342
+ this.poolHead = QUIC_LISTENER_POOL
343
+ this.block = new Array<u8>(QUIC_LISTENER_BLOCK)
344
+ this.header = new QuicHeader()
345
+ this.kdf = new HkdfScratch()
346
+ this.tag = new Array<u8>(QUIC_LISTENER_POOL)
347
+ this.lengths = new Array<u8>(5)
348
+ this.initialClient = new Array<u8>(32)
349
+ this.initialServer = new Array<u8>(32)
350
+ this.readSlot = new QuicKeysSlot()
351
+ this.writeSlot = new QuicKeysSlot()
352
+ this.packet = new QuicPacket()
353
+ this.peerCid = new Array<u8>(QUIC_MAX_CID_LENGTH)
354
+ this.ownCid = new Array<u8>(QUIC_MAX_CID_LENGTH)
355
+ this.none = []
356
+ this.spare = new QuicListenerAnswer(QUIC_LISTENER_ANSWER_SIZE)
357
+ this.retryKey = new AesKey(10, new Array<u64>(88))
358
+ aesKeyInto(quicListenerRetryKey(), this.retryKey)
359
+ this.usable =
360
+ toI32(entropy.length) === QUIC_LISTENER_ENTROPY_SIZE &&
361
+ toI32(config.statelessResetKey.length) === QUIC_CONN_STATIC_KEY_SIZE &&
362
+ toI32(config.retryTokenKey.length) === QUIC_CONN_STATIC_KEY_SIZE &&
363
+ config.retryTokenLifetime >= 1 &&
364
+ config.retryTokenLifetime <= QUIC_LISTENER_MAX_TOKEN_LIFETIME
365
+ secureZero(entropy)
366
+ }
367
+
368
+ /**
369
+ * One unpredictable value in 0 to 255: the next byte of HMAC-SHA256 under
370
+ * the seed of a counter, a block at a time, each byte wiped once taken.
371
+ */
372
+ randomByte(): i32 {
373
+ if (this.poolHead >= QUIC_LISTENER_POOL) {
374
+ this.refill()
375
+ }
376
+ let b: i32 = 0
377
+ if (this.poolHead >= 0 && this.poolHead < toI32(this.pool.length)) {
378
+ b = toI32(this.pool[this.poolHead])
379
+ this.pool[this.poolHead] = toU8(0)
380
+ }
381
+ this.poolHead = this.poolHead + 1
382
+ return b
383
+ }
384
+
385
+ /** Writes `count` unpredictable bytes into `out` at `at`. */
386
+ randomInto(out: u8[], at: i32, count: i32): void {
387
+ for (let k: i32 = 0; k < count; k += 1) {
388
+ const b: i32 = this.randomByte()
389
+ if (at + k >= 0 && at + k < toI32(out.length)) {
390
+ out[at + k] = toU8(b)
391
+ }
392
+ }
393
+ }
394
+
395
+ /** The generator's next block, HMAC-SHA256 under the seed of the counter, into `pool`. */
396
+ refill(): void {
397
+ quicListenerPutNumber(this.block, 0, this.counter, QUIC_LISTENER_BLOCK)
398
+ this.counter = this.counter + 1
399
+ const mac: HmacSha256Scratch = this.kdf.sha256
400
+ {
401
+ using _scope = arena()
402
+ mac.begin(this.seed, QUIC_LISTENER_FROM, QUIC_LISTENER_ENTROPY_SIZE)
403
+ mac.update(this.block, QUIC_LISTENER_FROM, QUIC_LISTENER_BLOCK)
404
+ mac.finishInto(this.pool, QUIC_LISTENER_FROM)
405
+ }
406
+ mac.wipe()
407
+ this.poolHead = 0
408
+ }
409
+
410
+ /**
411
+ * Decides what to answer a datagram whose first packet no connection
412
+ * owns, `datagram[at .. at + len)`, read where it lies, from `address`
413
+ * (the client's address as the caller's socket reports it, port included)
414
+ * at `now` (the caller's monotonic time in milliseconds), into `answer`,
415
+ * whose arrays are refilled in their own room. Answers `answer.kind`. See
416
+ * the module header for the rules; nothing a peer sends makes it panic,
417
+ * and nothing it does keeps arena memory. A window outside `datagram` is
418
+ * the caller's mistake, and panics.
419
+ */
420
+ handleWindow(datagram: u8[], at: i32, len: i32, address: u8[], now: i64, answer: QuicListenerAnswer): i32 {
421
+ if (!quicListenerWindowFits(toI32(datagram.length), at, len)) {
422
+ panic(
423
+ `QuicListener.handleWindow: the window [${at}, ${at} + ${len}) is outside a datagram of ${toI32(datagram.length)} bytes`
424
+ )
425
+ }
426
+ answer.kind = QUIC_LISTEN_DROP
427
+ answer.retried = false
428
+ quicListenerResize(answer.reply, 0)
429
+ quicListenerResize(answer.originalDcid, 0)
430
+ quicListenerResize(answer.retryScid, 0)
431
+ if (now > this.now) {
432
+ this.now = now
433
+ }
434
+ if (!this.usable) {
435
+ return answer.kind
436
+ }
437
+ const header: QuicHeader = this.header
438
+ quicParseHeaderInto(header, datagram, at, at + len, QUIC_CONN_CID_LENGTH)
439
+ if (header.error === QUIC_ERR_VERSION) {
440
+ // §5.2.2: answer an unsupported version only in a datagram that could
441
+ // start a connection, and §6.1: never a Version Negotiation packet.
442
+ if (header.version !== 0 && len >= QUIC_CONN_DATAGRAM_SIZE) {
443
+ this.versionNegotiation(answer, datagram, header)
444
+ }
445
+ return answer.kind
446
+ }
447
+ if (header.error !== QUIC_PACKET_OK) {
448
+ return answer.kind
449
+ }
450
+ if (header.type === QUIC_PACKET_SHORT) {
451
+ this.statelessReset(answer, len, datagram, header.dcidStart, header.dcidLength)
452
+ return answer.kind
453
+ }
454
+ // §14.1, §7.2, §5.2.2: only an Initial in a full-sized datagram, sent to
455
+ // a DCID of at least 8 bytes, can start a connection; the rest is dropped.
456
+ if (header.type !== QUIC_PACKET_INITIAL || len < QUIC_CONN_DATAGRAM_SIZE || header.dcidLength < 8) {
457
+ return answer.kind
458
+ }
459
+ const tokenAt: i32 = header.tokenStart
460
+ if (header.tokenLength === 0 || toI32(datagram[tokenAt]) !== QUIC_LISTENER_TOKEN_MARKER) {
461
+ if (!this.config.retry) {
462
+ answer.kind = QUIC_LISTEN_ACCEPT
463
+ return answer.kind
464
+ }
465
+ this.retry(answer, datagram, header, address)
466
+ return answer.kind
467
+ }
468
+ if (this.tokenValid(datagram, header, address)) {
469
+ answer.kind = QUIC_LISTEN_ACCEPT
470
+ answer.retried = true
471
+ quicListenerRefill(
472
+ answer.originalDcid,
473
+ datagram,
474
+ tokenAt + QUIC_LISTENER_TOKEN_HEAD,
475
+ toI32(datagram[tokenAt + QUIC_LISTENER_TOKEN_HEAD - 1])
476
+ )
477
+ quicListenerRefill(answer.retryScid, datagram, header.dcidStart, header.dcidLength)
478
+ return answer.kind
479
+ }
480
+ this.invalidToken(answer, datagram, header)
481
+ return answer.kind
482
+ }
483
+
484
+ /**
485
+ * `handleWindow` over the whole of `datagram`, into an answer of its own,
486
+ * made with exactly the room its reply takes: for a caller that keeps each
487
+ * answer, at the cost of one `QuicListenerAnswer` a datagram. A carrier
488
+ * makes one answer and calls `handleWindow`.
489
+ */
490
+ handle(datagram: u8[], address: u8[], now: i64): QuicListenerAnswer {
491
+ const spare: QuicListenerAnswer = this.spare
492
+ this.handleWindow(datagram, QUIC_LISTENER_FROM, toI32(datagram.length), address, now, spare)
493
+ const answer: QuicListenerAnswer = new QuicListenerAnswer(toI32(spare.reply.length))
494
+ answer.kind = spare.kind
495
+ answer.retried = spare.retried
496
+ quicListenerRefill(answer.reply, spare.reply, QUIC_LISTENER_FROM, toI32(spare.reply.length))
497
+ quicListenerRefill(
498
+ answer.originalDcid,
499
+ spare.originalDcid,
500
+ QUIC_LISTENER_FROM,
501
+ toI32(spare.originalDcid.length)
502
+ )
503
+ quicListenerRefill(answer.retryScid, spare.retryScid, QUIC_LISTENER_FROM, toI32(spare.retryScid.length))
504
+ return answer
505
+ }
506
+
507
+ /**
508
+ * A Version Negotiation packet (RFC 9000 §17.2.1) into `answer`, answering
509
+ * a packet from the SCID `header` read to its DCID: the IDs swapped,
510
+ * version 0, and version 1 as the only one supported. A random byte fills
511
+ * the first byte's low six bits, under the 0x40 the RFC asks for so the
512
+ * packet looks like QUIC to a demultiplexer. Each ID is at most 255 bytes,
513
+ * so the packet is at most 521, inside `reply`'s room.
514
+ *
515
+ * TODO(WP34 Q1): every other packet format is `nish/net/quic-packet`'s; this
516
+ * belongs beside `quicRetryPacket` there, which this stage may not edit.
517
+ */
518
+ versionNegotiation(answer: QuicListenerAnswer, datagram: u8[], header: QuicHeader): void {
519
+ const unused: i32 = this.randomByte()
520
+ const dcidLength: i32 = header.dcidLength
521
+ const scidLength: i32 = header.scidLength
522
+ const reply: u8[] = answer.reply
523
+ quicListenerResize(reply, 11 + dcidLength + scidLength)
524
+ reply[0] = toU8((unused & 0x3f) | 0xc0)
525
+ quicListenerPutNumber(reply, 1, 0, 4)
526
+ reply[5] = toU8(scidLength)
527
+ quicPacketCopy(reply, 6, datagram, header.scidStart, scidLength)
528
+ const dcidAt: i32 = 6 + scidLength
529
+ quicListenerPutNumber(reply, dcidAt, toI64(dcidLength), 1)
530
+ quicPacketCopy(reply, dcidAt + 1, datagram, header.dcidStart, dcidLength)
531
+ quicListenerPutNumber(reply, dcidAt + 1 + dcidLength, 1, 4)
532
+ answer.kind = QUIC_LISTEN_VERSION_NEGOTIATION
533
+ }
534
+
535
+ /**
536
+ * The token MAC over `head[headAt .. headAt + headLength)`, the token's
537
+ * own bytes before its MAC, the ID the client is to send to
538
+ * (`scid[scidAt .. scidAt + scidLength)`) and its address, into `tag`:
539
+ * what makes a token good for one address and one connection only. Its
540
+ * first `QUIC_LISTENER_TOKEN_MAC_SIZE` bytes are the token's MAC; the
541
+ * caller wipes `tag` once it has used them.
542
+ */
543
+ tokenMac(
544
+ head: u8[],
545
+ headAt: i32,
546
+ headLength: i32,
547
+ scid: u8[],
548
+ scidAt: i32,
549
+ scidLength: i32,
550
+ address: u8[]
551
+ ): void {
552
+ const mac: HmacSha256Scratch = this.kdf.sha256
553
+ const lengths: u8[] = this.lengths
554
+ const one: i32 = 1
555
+ const four: i32 = 4
556
+ quicListenerPutNumber(lengths, QUIC_LISTENER_FROM, toI64(scidLength), one)
557
+ quicListenerPutNumber(lengths, one, toI64(toI32(address.length)), four)
558
+ {
559
+ using _scope = arena()
560
+ mac.begin(this.config.retryTokenKey, QUIC_LISTENER_FROM, toI32(this.config.retryTokenKey.length))
561
+ mac.update(head, headAt, headLength)
562
+ mac.update(lengths, QUIC_LISTENER_FROM, one)
563
+ mac.update(scid, scidAt, scidLength)
564
+ mac.update(lengths, one, four)
565
+ mac.update(address, QUIC_LISTENER_FROM, toI32(address.length))
566
+ mac.finishInto(this.tag, QUIC_LISTENER_FROM)
567
+ }
568
+ mac.wipe()
569
+ }
570
+
571
+ /**
572
+ * Whether the token `header` read is one this listener issued for the
573
+ * client at `address` that was told to send to the DCID `header` read,
574
+ * and still within its lifetime: the lengths add up, the MAC matches
575
+ * (compared in constant time), and it was issued no later than now and
576
+ * less than `retryTokenLifetime` ago. The token is read in `datagram`.
577
+ */
578
+ tokenValid(datagram: u8[], header: QuicHeader, address: u8[]): boolean {
579
+ const tokenAt: i32 = header.tokenStart
580
+ const length: i32 = header.tokenLength
581
+ if (length < QUIC_LISTENER_TOKEN_HEAD + QUIC_LISTENER_TOKEN_MAC_SIZE) {
582
+ return false
583
+ }
584
+ const odcidLength: i32 = toI32(datagram[tokenAt + QUIC_LISTENER_TOKEN_HEAD - 1])
585
+ const head: i32 = QUIC_LISTENER_TOKEN_HEAD + odcidLength
586
+ if (odcidLength > QUIC_MAX_CID_LENGTH || head + QUIC_LISTENER_TOKEN_MAC_SIZE !== length) {
587
+ return false
588
+ }
589
+ this.tokenMac(datagram, tokenAt, head, datagram, header.dcidStart, header.dcidLength, address)
590
+ const same: boolean = timingSafeEqualAt(
591
+ this.tag,
592
+ 0,
593
+ datagram,
594
+ tokenAt + head,
595
+ QUIC_LISTENER_TOKEN_MAC_SIZE
596
+ )
597
+ secureZero(this.tag)
598
+ if (!same) {
599
+ return false
600
+ }
601
+ let issued: i64 = 0
602
+ for (let k: i32 = 1; k < QUIC_LISTENER_TOKEN_HEAD - 1; k += 1) {
603
+ issued = (issued << 8) | toI64(datagram[tokenAt + k])
604
+ }
605
+ return issued <= this.now && this.now - issued < this.config.retryTokenLifetime
606
+ }
607
+
608
+ /**
609
+ * A Retry for the client's first Initial (RFC 9000 §8.1.2, §17.2.5) into
610
+ * `answer`: a new connection ID for the client to send to, and a token
611
+ * binding it, the original DCID and the client's address to now — a
612
+ * marker byte, the time, the DCID, then the MAC — under the integrity tag
613
+ * of RFC 9001 §5.8. Each part is written where it goes in `reply`.
614
+ */
615
+ retry(answer: QuicListenerAnswer, datagram: u8[], header: QuicHeader, address: u8[]): void {
616
+ const scidLength: i32 = header.scidLength
617
+ const odcidLength: i32 = header.dcidLength
618
+ const retryScidAt: i32 = 7 + scidLength
619
+ const tokenAt: i32 = retryScidAt + QUIC_CONN_CID_LENGTH
620
+ const macAt: i32 = tokenAt + QUIC_LISTENER_TOKEN_HEAD + odcidLength
621
+ const tagAt: i32 = macAt + QUIC_LISTENER_TOKEN_MAC_SIZE
622
+ const reply: u8[] = answer.reply
623
+ quicListenerResize(reply, tagAt + QUIC_RETRY_TAG_SIZE)
624
+ this.randomInto(reply, retryScidAt, QUIC_CONN_CID_LENGTH)
625
+ reply[tokenAt] = toU8(QUIC_LISTENER_TOKEN_MARKER)
626
+ quicListenerPutNumber(reply, tokenAt + 1, this.now, 8)
627
+ reply[tokenAt + QUIC_LISTENER_TOKEN_HEAD - 1] = toU8(odcidLength)
628
+ quicPacketCopy(reply, tokenAt + QUIC_LISTENER_TOKEN_HEAD, datagram, header.dcidStart, odcidLength)
629
+ this.tokenMac(reply, tokenAt, macAt - tokenAt, reply, retryScidAt, QUIC_CONN_CID_LENGTH, address)
630
+ quicPacketCopy(reply, macAt, this.tag, 0, QUIC_LISTENER_TOKEN_MAC_SIZE)
631
+ secureZero(this.tag)
632
+ reply[0] = toU8((this.randomByte() & 15) | 0xf0)
633
+ quicListenerPutNumber(reply, 1, 1, 4)
634
+ reply[5] = toU8(scidLength)
635
+ quicPacketCopy(reply, 6, datagram, header.scidStart, scidLength)
636
+ reply[retryScidAt - 1] = toU8(QUIC_CONN_CID_LENGTH)
637
+ if (this.retryTag(reply, tagAt, datagram, header.dcidStart, odcidLength)) {
638
+ answer.kind = QUIC_LISTEN_RETRY
639
+ } else {
640
+ quicListenerResize(reply, 0)
641
+ }
642
+ }
643
+
644
+ /**
645
+ * The Retry integrity tag (RFC 9001 §5.8) over the pseudo-packet — the
646
+ * original DCID `datagram[odcidAt .. odcidAt + odcidLength)`, its length
647
+ * first, then `reply[0 .. tagAt)` — written at `reply[tagAt]`: AES-128-GCM
648
+ * under the listener's copy of version 1's fixed key, over nothing. The
649
+ * pseudo-packet and the AEAD's temporaries go with the arena block.
650
+ * Answers whether it was made.
651
+ */
652
+ retryTag(reply: u8[], tagAt: i32, datagram: u8[], odcidAt: i32, odcidLength: i32): boolean {
653
+ using _scope = arena()
654
+ const pseudo: u8[] = new Array<u8>(1 + odcidLength + tagAt)
655
+ quicListenerPutNumber(pseudo, 0, toI64(odcidLength), 1)
656
+ quicPacketCopy(pseudo, 1, datagram, odcidAt, odcidLength)
657
+ quicPacketCopy(pseudo, 1 + odcidLength, reply, 0, tagAt)
658
+ const plain: u8[] = []
659
+ const tag: u8[] | null = aesGcmSeal(this.retryKey, quicListenerRetryNonce(), pseudo, plain)
660
+ if (tag === null || toI32(tag.length) !== QUIC_RETRY_TAG_SIZE) {
661
+ return false
662
+ }
663
+ quicPacketCopy(reply, tagAt, tag, 0, QUIC_RETRY_TAG_SIZE)
664
+ return true
665
+ }
666
+
667
+ /**
668
+ * The answer to an Initial carrying an invalid Retry token (RFC 9000
669
+ * §8.1.2), into `answer`: an Initial with CONNECTION_CLOSE and
670
+ * INVALID_TOKEN, under the Initial keys of the DCID the client sent to,
671
+ * from that DCID. It is sent only when the client's Initial
672
+ * authenticates, so it answers a client and not noise; it is far smaller
673
+ * than the datagram it answers. The keys are derived into the listener's
674
+ * own slots, the Initial is opened in an arena block, and the close is
675
+ * written and sealed in `reply`.
676
+ */
677
+ invalidToken(answer: QuicListenerAnswer, datagram: u8[], header: QuicHeader): void {
678
+ const secrets: boolean = quicInitialSecretsInto(
679
+ this.kdf,
680
+ datagram,
681
+ header.dcidStart,
682
+ header.dcidLength,
683
+ this.initialClient,
684
+ this.initialServer
685
+ )
686
+ if (!secrets) {
687
+ return
688
+ }
689
+ const read: QuicKeys | null = quicKeysInto(
690
+ this.kdf,
691
+ this.readSlot,
692
+ QUIC_AEAD_AES_128_GCM,
693
+ this.initialClient
694
+ )
695
+ if (read === null || !this.opens(read, datagram, header)) {
696
+ return
697
+ }
698
+ // The write keys are derived only for an Initial that opened, so a forged
699
+ // token on noise costs one derivation, not two.
700
+ const write: QuicKeys | null = quicKeysInto(
701
+ this.kdf,
702
+ this.writeSlot,
703
+ QUIC_AEAD_AES_128_GCM,
704
+ this.initialServer
705
+ )
706
+ if (write === null) {
707
+ return
708
+ }
709
+ const peer: i32 = header.scidLength
710
+ const own: i32 = header.dcidLength
711
+ quicPacketCopy(this.peerCid, 0, datagram, header.scidStart, peer)
712
+ quicPacketCopy(this.ownCid, 0, datagram, header.dcidStart, own)
713
+ const reply: u8[] = answer.reply
714
+ quicListenerResize(reply, QUIC_LISTENER_ANSWER_SIZE)
715
+ const headerLength: i32 = quicLongHeaderSize(QUIC_PACKET_INITIAL, peer, own, 0, 0, 1, 0)
716
+ const frameEnd: i32 = quicPutConnectionClose(
717
+ reply,
718
+ headerLength,
719
+ QUIC_LISTENER_ANSWER_SIZE,
720
+ false,
721
+ QUIC_ERROR_INVALID_TOKEN,
722
+ 0,
723
+ this.none
724
+ )
725
+ const payloadLength: i32 = frameEnd - headerLength
726
+ const put: i32 =
727
+ headerLength > 0 && frameEnd > 0
728
+ ? quicPutLongHeader(
729
+ reply,
730
+ 0,
731
+ QUIC_PACKET_INITIAL,
732
+ this.peerCid,
733
+ peer,
734
+ this.ownCid,
735
+ own,
736
+ this.none,
737
+ 0,
738
+ 1,
739
+ payloadLength
740
+ )
741
+ : -1
742
+ const end: i32 =
743
+ put === headerLength ? quicSealInPlace(write, reply, 0, headerLength, payloadLength, 0) : -1
744
+ if (end > 0) {
745
+ quicListenerResize(reply, end)
746
+ answer.kind = QUIC_LISTEN_INVALID_TOKEN
747
+ } else {
748
+ quicListenerResize(reply, 0)
749
+ }
750
+ }
751
+
752
+ /** Whether the Initial `header` read in `datagram` opens under `read`; what opening it allocates goes with the arena block. */
753
+ opens(read: QuicKeys, datagram: u8[], header: QuicHeader): boolean {
754
+ using _scope = arena()
755
+ const packet: QuicPacket = this.packet
756
+ const clear: u8[] = quicUnprotectHeader(read, datagram, header, -1, packet)
757
+ const payload: u8[] | null = quicDecryptPayload(read, datagram, header, clear, packet)
758
+ return payload !== null && packet.error === QUIC_PACKET_OK
759
+ }
760
+
761
+ /** Takes one stateless reset from the rate limit's budget, topping it up first. Answers whether one was there. */
762
+ takeReset(): boolean {
763
+ if (this.resetRefilled < 0) {
764
+ this.resetRefilled = this.now
765
+ this.resetBudget = QUIC_LISTENER_RESET_BURST
766
+ }
767
+ const earned: i64 = (this.now - this.resetRefilled) / QUIC_LISTENER_RESET_INTERVAL
768
+ if (earned > 0) {
769
+ const room: i64 = toI64(QUIC_LISTENER_RESET_BURST - this.resetBudget)
770
+ this.resetBudget = this.resetBudget + toI32(earned < room ? earned : room)
771
+ this.resetRefilled = this.resetRefilled + earned * QUIC_LISTENER_RESET_INTERVAL
772
+ }
773
+ if (this.resetBudget <= 0) {
774
+ this.resetsLimited = this.resetsLimited + 1
775
+ return false
776
+ }
777
+ this.resetBudget = this.resetBudget - 1
778
+ this.resetsSent = this.resetsSent + 1
779
+ return true
780
+ }
781
+
782
+ /**
783
+ * A stateless reset into `answer`, answering an `n`-byte datagram sent to
784
+ * the ID `datagram[dcidAt .. dcidAt + dcidLength)` (RFC 9000 §10.3): a
785
+ * short-header first byte, unpredictable bytes, and the token for that
786
+ * ID, computed in place by `quicConnResetTokenInto`. It is one byte
787
+ * shorter than a datagram of up to 43 bytes and between 43 bytes and one
788
+ * byte short of the datagram (at most 1200) otherwise, so it is always
789
+ * shorter; a datagram too short for that to leave 21 bytes, or one past
790
+ * the rate limit, gets nothing.
791
+ */
792
+ statelessReset(answer: QuicListenerAnswer, n: i32, datagram: u8[], dcidAt: i32, dcidLength: i32): void {
793
+ if (n <= QUIC_LISTENER_MIN_RESET || !this.takeReset()) {
794
+ return
795
+ }
796
+ let size: i32 = n - 1
797
+ if (n > QUIC_LISTENER_SHORT_TRIGGER) {
798
+ const most: i32 = n - 1 < QUIC_CONN_DATAGRAM_SIZE ? n - 1 : QUIC_CONN_DATAGRAM_SIZE
799
+ const span: i32 = most - QUIC_LISTENER_SHORT_TRIGGER + 1
800
+ const high: i32 = this.randomByte()
801
+ const r: i32 = (high << 8) | this.randomByte()
802
+ size = QUIC_LISTENER_SHORT_TRIGGER + (span > 0 ? r % span : 0)
803
+ }
804
+ const reply: u8[] = answer.reply
805
+ const tokenAt: i32 = size - QUIC_RESET_TOKEN_SIZE
806
+ quicListenerResize(reply, size)
807
+ this.randomInto(reply, QUIC_LISTENER_FROM, tokenAt)
808
+ reply[0] = toU8((toI32(reply[0]) & 0x3f) | 0x40)
809
+ quicConnResetTokenInto(
810
+ this.kdf.sha256,
811
+ this.config.statelessResetKey,
812
+ datagram,
813
+ dcidAt,
814
+ dcidLength,
815
+ reply,
816
+ tokenAt
817
+ )
818
+ answer.kind = QUIC_LISTEN_STATELESS_RESET
819
+ }
820
+ }
821
+
822
+ /**
823
+ * The next datagram `conn` sends at `now`, as `takeDatagram` answers it,
824
+ * but only when the connection's pacer has the credit for a full datagram
825
+ * (RFC 9002 §7.7); `null` when it has not, or when nothing is due. The
826
+ * datagram is charged to the pacer at its real size.
827
+ */
828
+ export const quicListenerTakePaced = (conn: QuicConnection, now: i64): u8[] | null => {
829
+ if (conn.recovery.pacerDelay(now, QUIC_CONN_DATAGRAM_SIZE) > 0) {
830
+ return null
831
+ }
832
+ const out: u8[] | null = conn.takeDatagram(now)
833
+ if (out !== null) {
834
+ conn.recovery.onPaced(now, toI32(out.length))
835
+ }
836
+ return out
837
+ }
838
+
839
+ /**
840
+ * When `quicListenerTakePaced` next lets `conn` send a full datagram, in the
841
+ * caller's milliseconds: `now` when it may already.
842
+ */
843
+ export const quicListenerPaceTime = (conn: QuicConnection, now: i64): i64 =>
844
+ now + conn.recovery.pacerDelay(now, QUIC_CONN_DATAGRAM_SIZE)
845
+
846
+ /** The most datagrams one GSO send carries: Linux's `UDP_MAX_SEGMENTS`. */
847
+ export const QUIC_LISTENER_FLIGHT_MAX: i32 = 64
848
+
849
+ /** What `quicListenerTakeFlight` wrote: how many datagrams, and the segment size to send them with. */
850
+ export class QuicFlight {
851
+ /** The datagrams in the flight. */
852
+ count: i32 = 0
853
+ /** Every datagram's size but the last's, which may be shorter: the `UDP_SEGMENT` to send with. */
854
+ segment: i32 = 0
855
+ }
856
+
857
+ /**
858
+ * Writes the next flight `conn` sends at `now` into `buf[at .. at + cap)`,
859
+ * datagram after datagram, for one GSO send, and answers its length in
860
+ * bytes (0 for none); `flight` says how many datagrams it holds and the
861
+ * segment size. Each datagram goes only when the pacer has credit for a full
862
+ * one (RFC 9002 §7.7) and is charged at its real size, as
863
+ * `quicListenerTakePaced` does, so the flight is the pacer's whole credit
864
+ * and no more. It stops at the first datagram shorter than
865
+ * `QUIC_CONN_DATAGRAM_SIZE`, which can only be the last of a segmented send,
866
+ * at `QUIC_LISTENER_FLIGHT_MAX` datagrams, and when `cap` has no room for
867
+ * another.
868
+ */
869
+ export const quicListenerTakeFlight = (
870
+ conn: QuicConnection,
871
+ now: i64,
872
+ buf: u8[],
873
+ at: i32,
874
+ cap: i32,
875
+ flight: QuicFlight
876
+ ): i32 => {
877
+ flight.count = 0
878
+ flight.segment = 0
879
+ let length: i32 = 0
880
+ while (
881
+ flight.count < QUIC_LISTENER_FLIGHT_MAX &&
882
+ length + QUIC_CONN_DATAGRAM_SIZE <= cap &&
883
+ conn.recovery.pacerDelay(now, QUIC_CONN_DATAGRAM_SIZE) <= 0
884
+ ) {
885
+ const n: i32 = conn.takeDatagramInto(buf, at + length, now)
886
+ if (n <= 0) {
887
+ return length
888
+ }
889
+ conn.recovery.onPaced(now, n)
890
+ if (flight.count === 0) {
891
+ flight.segment = n
892
+ }
893
+ flight.count = flight.count + 1
894
+ length = length + n
895
+ if (n < QUIC_CONN_DATAGRAM_SIZE) {
896
+ return length
897
+ }
898
+ }
899
+ return length
900
+ }
901
+
902
+ /**
903
+ * Hands `conn` the datagrams of one receive, `buf[off .. off + len)`, at
904
+ * `now`: one datagram when `segment` is 0, else a GRO receive cut every
905
+ * `segment` bytes (the last may be shorter). Each is read in place. Answers
906
+ * how many it handed over.
907
+ */
908
+ export const quicListenerReceiveSegments = (
909
+ conn: QuicConnection,
910
+ buf: u8[],
911
+ off: i32,
912
+ len: i32,
913
+ segment: i32,
914
+ now: i64
915
+ ): i32 => {
916
+ if (segment <= 0) {
917
+ conn.receiveWindow(buf, off, len, now)
918
+ return 1
919
+ }
920
+ let count: i32 = 0
921
+ let at: i32 = 0
922
+ while (at < len) {
923
+ const n: i32 = len - at < segment ? len - at : segment
924
+ conn.receiveWindow(buf, off + at, n, now)
925
+ at = at + n
926
+ count += 1
927
+ }
928
+ return count
929
+ }