@amritk/nish-x86_64-linux 0.17.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.
@@ -7,19 +7,27 @@
7
7
  * (§10.3). Sans-IO, like the rest of `nish/net`, and stateless in the sense
8
8
  * the RFC means: nothing it answers depends on any connection.
9
9
  *
10
- * import { QuicListener, QuicListenerAnswer, QUIC_LISTEN_ACCEPT } from "nish/net/quic-listener";
10
+ * import { QuicListener, QuicListenerAnswer, QUIC_LISTEN_ACCEPT, QUIC_LISTENER_ANSWER_SIZE } from "nish/net/quic-listener";
11
11
  *
12
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) {
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
16
  * const conn = new QuicConnection(config, connectionEntropy);
17
17
  * if (answer.retried) { conn.acceptRetry(answer.originalDcid, answer.retryScid); }
18
- * conn.receive(datagram, now);
18
+ * conn.receiveWindow(buf, at, len, now);
19
19
  * } else if (toI32(answer.reply.length) > 0) {
20
20
  * … send answer.reply back to fromAddress …
21
21
  * }
22
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
+ *
23
31
  * **Routing is the caller's.** The listener sees only datagrams the caller
24
32
  * could not hand to a live connection, and it must see only those: a stateless
25
33
  * reset carries the token for the packet's connection ID, and handing the
@@ -39,7 +47,7 @@
39
47
  * server issued, is treated as no token at all (§8.1.3).
40
48
  *
41
49
  * **Stateless resets** (§10.3) end in the token `quicStatelessResetToken`
42
- * derives from the configuration's static key and the packet's connection
50
+ * derives (computed in place, by `quicConnResetTokenInto`) from the configuration's static key and the packet's connection
43
51
  * ID, so a server that restarted with the same key can end the connections
44
52
  * it lost. RFC 9000 §10.3 and §10.3.3 bound them, and each bound is held
45
53
  * here. A reset is sent only for a short header, and is always shorter than
@@ -95,8 +103,10 @@
95
103
  * ported from another implementation. Private names carry the
96
104
  * `quicListener` prefix (`docs/wp26-stdlib.md` §3e).
97
105
  */
98
- import { timingSafeEqual } from "nish/crypto/ct"
99
- import { hmacSha256 } from "nish/crypto/hmac"
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"
100
110
  import {
101
111
  QUIC_AEAD_AES_128_GCM,
102
112
  QUIC_ERR_VERSION,
@@ -104,26 +114,29 @@ import {
104
114
  QUIC_PACKET_INITIAL,
105
115
  QUIC_PACKET_OK,
106
116
  QUIC_PACKET_SHORT,
117
+ QUIC_RETRY_TAG_SIZE,
107
118
  QuicHeader,
108
- QuicInitialSecrets,
109
119
  QuicKeys,
120
+ QuicKeysSlot,
110
121
  QuicPacket,
111
- quicInitialSecrets,
112
- quicKeys,
113
- quicLongHeader,
114
- quicOpenPacket,
115
- quicParseHeader,
116
- quicRetryPacket,
117
- quicSealPacket,
122
+ quicDecryptPayload,
123
+ quicInitialSecretsInto,
124
+ quicKeysInto,
125
+ quicLongHeaderSize,
126
+ quicPacketCopy,
127
+ quicParseHeaderInto,
128
+ quicPutLongHeader,
129
+ quicSealInPlace,
130
+ quicUnprotectHeader,
118
131
  } from "nish/net/quic-packet"
119
- import { QUIC_ERROR_INVALID_TOKEN, QUIC_RESET_TOKEN_SIZE, quicPushConnectionClose } from "nish/net/quic-frame"
132
+ import { QUIC_ERROR_INVALID_TOKEN, QUIC_RESET_TOKEN_SIZE, quicPutConnectionClose } from "nish/net/quic-frame"
120
133
  import {
121
134
  QUIC_CONN_CID_LENGTH,
122
135
  QUIC_CONN_DATAGRAM_SIZE,
123
136
  QUIC_CONN_STATIC_KEY_SIZE,
124
137
  QuicConnection,
125
138
  QuicServerConfig,
126
- quicStatelessResetToken,
139
+ quicConnResetTokenInto,
127
140
  } from "nish/net/quic"
128
141
 
129
142
  // ---- What `handle` answers ----------------------------------------------------
@@ -165,12 +178,33 @@ const QUIC_LISTENER_TOKEN_MAC_SIZE: i32 = 16
165
178
  const QUIC_LISTENER_TOKEN_HEAD: i32 = 10
166
179
  /** Up to this datagram length a reset is exactly one byte shorter (RFC 9000 §10.3). */
167
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
168
195
 
169
196
  /**
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
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
173
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.
174
208
  */
175
209
  export class QuicListenerAnswer {
176
210
  reply: u8[]
@@ -179,59 +213,66 @@ export class QuicListenerAnswer {
179
213
  kind: i32 = 0
180
214
  retried: boolean = false
181
215
 
182
- constructor() {
183
- this.reply = []
184
- this.originalDcid = []
185
- this.retryScid = []
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)
186
220
  }
187
221
  }
188
222
 
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
- }
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()
197
228
  }
198
229
  return out
199
230
  }
200
231
 
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)
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()
205
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)
206
246
  }
207
247
 
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)))
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
+ }
212
254
  }
213
255
  }
214
256
 
215
257
  /**
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.
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.
220
262
  *
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.
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.
223
265
  */
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
- }
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
235
276
 
236
277
  /**
237
278
  * The server's answers to datagrams no connection owns, under one
@@ -243,12 +284,42 @@ const quicListenerVersionNegotiation = (dcid: u8[], scid: u8[], unused: i32): u8
243
284
  * `QUIC_CONN_STATIC_KEY_SIZE` bytes, or whose `retryTokenLifetime` is outside
244
285
  * 1 to `QUIC_LISTENER_MAX_TOKEN_LIFETIME`, or entropy of another length, make
245
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.
246
292
  */
247
293
  export class QuicListener {
248
294
  config: QuicServerConfig
249
295
  seed: u8[]
250
296
  /** Unpredictable bytes not yet used, from `poolHead`, and the counter the next block is made from. */
251
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
252
323
  counter: i64 = 0
253
324
  /** The latest time the caller gave, in milliseconds; time never runs backwards here. */
254
325
  now: i64 = 0
@@ -265,8 +336,26 @@ export class QuicListener {
265
336
  /** A listener under `config`; `entropy`'s `QUIC_LISTENER_ENTROPY_SIZE` bytes are copied, then wiped in the caller's array. */
266
337
  constructor(config: QuicServerConfig, entropy: u8[]) {
267
338
  this.config = config
268
- this.seed = quicListenerSlice(entropy, 0, QUIC_LISTENER_ENTROPY_SIZE)
269
- this.pool = []
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)
270
359
  this.usable =
271
360
  toI32(entropy.length) === QUIC_LISTENER_ENTROPY_SIZE &&
272
361
  toI32(config.statelessResetKey.length) === QUIC_CONN_STATIC_KEY_SIZE &&
@@ -276,218 +365,397 @@ export class QuicListener {
276
365
  secureZero(entropy)
277
366
  }
278
367
 
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)
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)
294
391
  }
295
- this.poolHead = this.poolHead + 1
296
392
  }
297
- return out
298
393
  }
299
394
 
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
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
305
408
  }
306
409
 
307
410
  /**
308
411
  * 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.
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.
313
419
  */
314
- handle(datagram: u8[], address: u8[], now: i64): QuicListenerAnswer {
315
- const answer: QuicListenerAnswer = new QuicListenerAnswer()
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)
316
431
  if (now > this.now) {
317
432
  this.now = now
318
433
  }
319
434
  if (!this.usable) {
320
- return answer
435
+ return answer.kind
321
436
  }
322
- const n: i32 = toI32(datagram.length)
323
- const header: QuicHeader = quicParseHeader(datagram, 0, QUIC_CONN_CID_LENGTH)
437
+ const header: QuicHeader = this.header
438
+ quicParseHeaderInto(header, datagram, at, at + len, QUIC_CONN_CID_LENGTH)
324
439
  if (header.error === QUIC_ERR_VERSION) {
325
440
  // §5.2.2: answer an unsupported version only in a datagram that could
326
441
  // 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())
442
+ if (header.version !== 0 && len >= QUIC_CONN_DATAGRAM_SIZE) {
443
+ this.versionNegotiation(answer, datagram, header)
330
444
  }
331
- return answer
445
+ return answer.kind
332
446
  }
333
447
  if (header.error !== QUIC_PACKET_OK) {
334
- return answer
448
+ return answer.kind
335
449
  }
336
450
  if (header.type === QUIC_PACKET_SHORT) {
337
- return this.statelessReset(answer, n, header.dcid)
451
+ this.statelessReset(answer, len, datagram, header.dcidStart, header.dcidLength)
452
+ return answer.kind
338
453
  }
339
454
  // §14.1, §7.2, §5.2.2: only an Initial in a full-sized datagram, sent to
340
455
  // 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
456
+ if (header.type !== QUIC_PACKET_INITIAL || len < QUIC_CONN_DATAGRAM_SIZE || header.dcidLength < 8) {
457
+ return answer.kind
343
458
  }
344
- const token: u8[] = header.token
345
- if (toI32(token.length) === 0 || toI32(token[0]) !== QUIC_LISTENER_TOKEN_MARKER) {
459
+ const tokenAt: i32 = header.tokenStart
460
+ if (header.tokenLength === 0 || toI32(datagram[tokenAt]) !== QUIC_LISTENER_TOKEN_MARKER) {
346
461
  if (!this.config.retry) {
347
462
  answer.kind = QUIC_LISTEN_ACCEPT
348
- return answer
463
+ return answer.kind
349
464
  }
350
- return this.retry(answer, header, address)
465
+ this.retry(answer, datagram, header, address)
466
+ return answer.kind
351
467
  }
352
- if (this.tokenValid(token, header.dcid, address)) {
468
+ if (this.tokenValid(datagram, header, address)) {
353
469
  answer.kind = QUIC_LISTEN_ACCEPT
354
470
  answer.retried = true
355
- answer.originalDcid = quicListenerSlice(
356
- token,
357
- QUIC_LISTENER_TOKEN_HEAD,
358
- toI32(token[QUIC_LISTENER_TOKEN_HEAD - 1])
471
+ quicListenerRefill(
472
+ answer.originalDcid,
473
+ datagram,
474
+ tokenAt + QUIC_LISTENER_TOKEN_HEAD,
475
+ toI32(datagram[tokenAt + QUIC_LISTENER_TOKEN_HEAD - 1])
359
476
  )
360
- answer.retryScid = header.dcid
361
- return answer
477
+ quicListenerRefill(answer.retryScid, datagram, header.dcidStart, header.dcidLength)
478
+ return answer.kind
362
479
  }
363
- return this.invalidToken(answer, datagram, header)
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
364
505
  }
365
506
 
366
507
  /**
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.
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.
370
517
  */
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
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
382
533
  }
383
534
 
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
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()
393
569
  }
394
570
 
395
571
  /**
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.
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`.
400
577
  */
401
- tokenValid(token: u8[], dcid: u8[], address: u8[]): boolean {
402
- const length: i32 = toI32(token.length)
578
+ tokenValid(datagram: u8[], header: QuicHeader, address: u8[]): boolean {
579
+ const tokenAt: i32 = header.tokenStart
580
+ const length: i32 = header.tokenLength
403
581
  if (length < QUIC_LISTENER_TOKEN_HEAD + QUIC_LISTENER_TOKEN_MAC_SIZE) {
404
582
  return false
405
583
  }
406
- const odcidLength: i32 = toI32(token[QUIC_LISTENER_TOKEN_HEAD - 1])
584
+ const odcidLength: i32 = toI32(datagram[tokenAt + QUIC_LISTENER_TOKEN_HEAD - 1])
407
585
  const head: i32 = QUIC_LISTENER_TOKEN_HEAD + odcidLength
408
586
  if (odcidLength > QUIC_MAX_CID_LENGTH || head + QUIC_LISTENER_TOKEN_MAC_SIZE !== length) {
409
587
  return false
410
588
  }
411
- const mac: u8[] = this.tokenMac(quicListenerSlice(token, 0, head), dcid, address)
412
- if (!timingSafeEqual(mac, quicListenerSlice(token, head, QUIC_LISTENER_TOKEN_MAC_SIZE))) {
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) {
413
599
  return false
414
600
  }
415
601
  let issued: i64 = 0
416
602
  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
- }
603
+ issued = (issued << 8) | toI64(datagram[tokenAt + k])
420
604
  }
421
605
  return issued <= this.now && this.now - issued < this.config.retryTokenLifetime
422
606
  }
423
607
 
424
608
  /**
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.
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`.
428
614
  */
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) {
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)) {
440
638
  answer.kind = QUIC_LISTEN_RETRY
441
- answer.reply = packet
639
+ } else {
640
+ quicListenerResize(reply, 0)
442
641
  }
443
- return answer
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
444
665
  }
445
666
 
446
667
  /**
447
668
  * 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.
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`.
452
676
  */
453
- invalidToken(answer: QuicListenerAnswer, datagram: u8[], header: QuicHeader): QuicListenerAnswer {
454
- const secrets: QuicInitialSecrets | null = quicInitialSecrets(header.dcid)
455
- if (secrets === null) {
456
- return answer
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
457
688
  }
458
- const read: QuicKeys | null = quicKeys(QUIC_AEAD_AES_128_GCM, secrets.client)
459
- if (read === null) {
460
- return answer
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
461
697
  }
462
- const opened: QuicPacket = quicOpenPacket(read, datagram, header, -1)
463
698
  // The write keys are derived only for an Initial that opened, so a forged
464
699
  // 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
700
+ const write: QuicKeys | null = quicKeysInto(
701
+ this.kdf,
702
+ this.writeSlot,
703
+ QUIC_AEAD_AES_128_GCM,
704
+ this.initialServer
705
+ )
467
706
  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,
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,
478
722
  0,
479
- 1,
480
- toI32(payload.length)
723
+ this.none
481
724
  )
482
- if (first === null) {
483
- return answer
484
- }
485
- const packet: u8[] | null = quicSealPacket(write, first, 0, payload)
486
- if (packet !== null) {
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)
487
746
  answer.kind = QUIC_LISTEN_INVALID_TOKEN
488
- answer.reply = packet
747
+ } else {
748
+ quicListenerResize(reply, 0)
489
749
  }
490
- return answer
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
491
759
  }
492
760
 
493
761
  /** Takes one stateless reset from the rate limit's budget, topping it up first. Answers whether one was there. */
@@ -512,34 +780,42 @@ export class QuicListener {
512
780
  }
513
781
 
514
782
  /**
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.
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.
521
791
  */
522
- statelessReset(answer: QuicListenerAnswer, n: i32, dcid: u8[]): QuicListenerAnswer {
792
+ statelessReset(answer: QuicListenerAnswer, n: i32, datagram: u8[], dcidAt: i32, dcidLength: i32): void {
523
793
  if (n <= QUIC_LISTENER_MIN_RESET || !this.takeReset()) {
524
- return answer
794
+ return
525
795
  }
526
796
  let size: i32 = n - 1
527
797
  if (n > QUIC_LISTENER_SHORT_TRIGGER) {
528
798
  const most: i32 = n - 1 < QUIC_CONN_DATAGRAM_SIZE ? n - 1 : QUIC_CONN_DATAGRAM_SIZE
529
799
  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
800
+ const high: i32 = this.randomByte()
801
+ const r: i32 = (high << 8) | this.randomByte()
533
802
  size = QUIC_LISTENER_SHORT_TRIGGER + (span > 0 ? r % span : 0)
534
803
  }
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))
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
+ )
540
818
  answer.kind = QUIC_LISTEN_STATELESS_RESET
541
- answer.reply = reply
542
- return answer
543
819
  }
544
820
  }
545
821