@amritk/nish-x86_64-linux 0.17.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/INSTALL.md +43 -25
  2. package/bin/nish +0 -0
  3. package/package.json +2 -2
  4. package/runtime/nish.d.ts +55 -5
  5. package/runtime/nish.h +231 -20
  6. package/runtime/nish.mjs +16 -0
  7. package/runtime/runtime-aes.c +490 -0
  8. package/runtime/runtime-cross.c +366 -0
  9. package/runtime/runtime-host.c +7 -6
  10. package/runtime/runtime-net.c +93 -34
  11. package/runtime/runtime-os.c +213 -15
  12. package/runtime/runtime-parallel.c +590 -107
  13. package/runtime/runtime-simd.c +203 -0
  14. package/runtime/runtime-wasm.c +16 -3
  15. package/runtime/runtime.c +89 -23
  16. package/runtime/shim.mjs +71 -1
  17. package/scripts/build.sh +225 -20
  18. package/std/README.md +86 -55
  19. package/std/bytes.ts +163 -0
  20. package/std/crypto/LICENSE-libsecp256k1 +19 -0
  21. package/std/crypto/aes.ts +238 -76
  22. package/std/crypto/chacha20poly1305.ts +43 -9
  23. package/std/crypto/ed25519.ts +840 -0
  24. package/std/crypto/hkdf.ts +304 -12
  25. package/std/crypto/hmac.ts +245 -53
  26. package/std/crypto/p256.ts +1376 -42
  27. package/std/crypto/x25519.ts +55 -37
  28. package/std/crypto/x509-random.ts +71 -0
  29. package/std/crypto/x509.ts +9 -9
  30. package/std/json-tokenizer.ts +1296 -0
  31. package/std/json-typed.ts +1014 -0
  32. package/std/json-value.ts +1163 -0
  33. package/std/json.ts +1412 -70
  34. package/std/net/buffer-pool.ts +151 -0
  35. package/std/net/hpack.ts +416 -112
  36. package/std/net/http-fields.ts +93 -0
  37. package/std/net/http1-server.ts +417 -55
  38. package/std/net/http1.ts +31 -6
  39. package/std/net/http2-tls.ts +112 -24
  40. package/std/net/http2.ts +333 -75
  41. package/std/net/http3-server.ts +231 -45
  42. package/std/net/http3.ts +187 -28
  43. package/std/net/prefork.ts +412 -0
  44. package/std/net/quic-conn-params.ts +86 -16
  45. package/std/net/quic-listener.ts +576 -219
  46. package/std/net/quic-packet.ts +443 -58
  47. package/std/net/quic-recovery.ts +361 -30
  48. package/std/net/quic-stream.ts +434 -28
  49. package/std/net/quic.ts +922 -206
  50. package/std/net/router.ts +676 -0
  51. package/std/net/tls/codec.ts +426 -27
  52. package/std/net/tls/record-server.ts +478 -132
  53. package/std/net/tls/record.ts +133 -42
  54. package/std/net/tls/schedule.ts +464 -55
  55. package/std/net/tls/ticket.ts +610 -0
  56. package/std/net/tls-tcp.ts +204 -57
  57. package/std/net/tls.ts +1132 -305
  58. package/std/net/url.ts +571 -0
  59. package/std/random.ts +94 -0
  60. package/std/text.ts +77 -0
  61. package/std/threads.ts +438 -14
package/std/net/quic.ts CHANGED
@@ -58,27 +58,46 @@
58
58
  * its current value. When nothing is acknowledged for a probe timeout, with
59
59
  * its backoff, everything in flight is queued again and a probe goes out in
60
60
  * each space that has packets in flight, a PING if there is nothing to
61
- * resend (§6.2.4). NewReno's window holds back everything ack-eliciting but
62
- * a probe; an ACK always goes. PATH_RESPONSE and DATAGRAM are never sent
63
- * again (RFC 9000 §13.3, RFC 9221 §5.2). Pacing, and GSO batching, are the
64
- * carrier's: `nish/net/quic-listener`.
61
+ * resend (§6.2.4) — two datagrams of them during the handshake, each with
62
+ * the oldest CRYPTO data not acknowledged. A client that shows it lacks the
63
+ * server's handshake data (an Initial repeating CRYPTO data, an
64
+ * ack-eliciting Handshake packet) gets it again at once, a datagram each
65
+ * time, up to `QUIC_CONN_EARLY_RESENDS` times (§6.2.3). NewReno's window
66
+ * holds back everything ack-eliciting but a probe; an ACK always goes.
67
+ * PATH_RESPONSE and DATAGRAM are never sent again (RFC 9000 §13.3, RFC 9221
68
+ * §5.2). Pacing, and GSO batching, are the carrier's:
69
+ * `nish/net/quic-listener`. Once the 1-RTT keys are in, one Application
70
+ * Data packet number in every 128 to 1024 is skipped, at a distance drawn
71
+ * from the ID seed, and the last `QUIC_CONN_SKIP_RING` are remembered: an
72
+ * ACK of one acknowledges a packet never sent, an optimistic ACK meant to
73
+ * widen the congestion window, and is PROTOCOL_VIOLATION (RFC 9000 §21.4).
65
74
  *
66
75
  * **Nothing allocated per packet** (QUIC-3). A connection is a slot: every
67
- * buffer it uses — the packet record, the stream buffers, the CRYPTO
68
- * reassembly, the connection-ID table, the datagram rings — is made by the
69
- * constructor and reused, and `reset` makes the slot a new connection for
70
- * the next peer. A packet is opened (`receiveWindow`) and a datagram built
76
+ * buffer it uses — the packet record, the CRYPTO reassembly, the
77
+ * connection-ID table, the datagram rings — is made by the constructor and
78
+ * reused, and `reset` makes the slot a new connection for the next peer. The
79
+ * stream buffers are borrowed instead (`QuicStreamPools` in
80
+ * `nish/net/quic-stream`): a receive side takes one on its first byte and a
81
+ * send side on its first write, each goes back zeroed once the stream is done
82
+ * with it, and a server's connections share one set of pools, so idle slots
83
+ * hold none. A standalone connection makes private pools as large as all its
84
+ * slots at its first packet. The pools are primed before a packet's arena
85
+ * block and never touched inside it. A packet is opened (`receiveWindow`) and a datagram built
71
86
  * and sealed (`takeDatagramInto`) inside a `using a = arena()` block, so the
72
87
  * AEAD's and the header protection's temporaries go when the block ends, and
73
- * the compiler refuses the block if anything in it could store one. What
74
- * still allocates is the handshake's, once a connection: `TlsServer` and its
75
- * key schedule (TLS-3), the packet keys of each level, the stateless reset
76
- * tokens and connection IDs (HMAC), and a key update (QUIC-4) — recorded in
77
- * `docs/security/quic.md`.
88
+ * the compiler refuses the block if anything in it could store one. The
89
+ * handshake keeps its state in the slot too: one `TlsServer`, made by the
90
+ * slot's first handshake and `restart`ed by each after it; every level's
91
+ * packet keys, and a key update's, derived in place into `QuicKeysSlot`s
92
+ * (`nish/net/quic-packet`) over the slot's HKDF scratch; the Initial
93
+ * secrets, the stateless reset tokens and the connection IDs computed in
94
+ * arena blocks; the transport parameters encoded and parsed into the
95
+ * slot's own objects. After the first connection in a slot, a connection
96
+ * keeps nothing at all (QUIC-3 in `docs/security/quic.md`).
78
97
  *
79
98
  * **Time.** The connection has no clock: `receive`, `takeDatagram` and
80
99
  * `handleTimer` take the caller's monotonic time in milliseconds, and
81
- * `deadline()` says when `handleTimer` is next due. Three things run on it.
100
+ * `deadline()` says when `handleTimer` is next due. Four things run on it.
82
101
  * The idle timeout (RFC 9000 §10.1) is the smaller of the two sides'
83
102
  * `max_idle_timeout`, never under `QUIC_CONN_IDLE_FLOOR` or three probe
84
103
  * timeouts; once it passes with nothing received, the connection closes
@@ -86,7 +105,13 @@
86
105
  * timer declares packets lost by the time threshold or fires the probe
87
106
  * timeout. And after a key update the previous read keys are kept for a
88
107
  * probe timeout (`recovery.probeTimeout()`), for packets the network
89
- * reordered, before the next generation's are derived (RFC 9001 §6.5).
108
+ * reordered, before the next generation's are derived (RFC 9001 §6.5). Once
109
+ * the connection has closed, by either side, its keys are kept through the
110
+ * closing or draining period, three probe timeouts (RFC 9000 §10.2), and
111
+ * wiped at its end; `deadline()` is that end, then -1. A close this side
112
+ * owes starts the period when `takeDatagram` sends it, not when `close` or
113
+ * `fail` is called, so that however long the caller waits, the close still
114
+ * goes out.
90
115
  *
91
116
  * **Key update** (RFC 9001 §6), both ways. A 1-RTT packet whose Key Phase
92
117
  * bit differs from the current one is opened with the next generation's read
@@ -94,11 +119,17 @@
94
119
  * derivation and shows no timing difference (§6.3); if it opens, the client
95
120
  * has updated, and the server's write keys follow before anything is
96
121
  * acknowledged (§6.2). `updateKeys()` starts an update from the server once
97
- * the client has acknowledged a packet of the current phase (§6.1). Each
98
- * update the client starts costs two key derivations, which stay in the arena
99
- * (QUIC-4 in `docs/security/quic.md`), so a connection takes at most
100
- * `QUIC_CONN_MAX_KEY_UPDATES` of them and closes on the next with
101
- * KEY_UPDATE_ERROR.
122
+ * the client has acknowledged a packet of the current phase (§6.1). The
123
+ * generations take turns in two key slots and two secrets a direction, so an
124
+ * update keeps nothing in the arena; each still costs two key derivations of
125
+ * work, so a connection takes at most `QUIC_CONN_MAX_KEY_UPDATES` updates the
126
+ * client starts and closes on the next with KEY_UPDATE_ERROR (QUIC-4).
127
+ * The AEAD limits are kept (RFC 9001 §6.6): from three quarters of AES-GCM's
128
+ * confidentiality limit, 2^23 packets under one write key, the server starts
129
+ * an update itself, and closes with AEAD_LIMIT_REACHED, sealing nothing more,
130
+ * if it reaches the limit with none possible; past the integrity limit of
131
+ * packets that fail to authenticate over the connection, 2^52 for AES-GCM
132
+ * and 2^36 for ChaCha20-Poly1305, it closes with AEAD_LIMIT_REACHED too.
102
133
  *
103
134
  * **Sans-IO and deterministic.** No socket, no clock and no random device:
104
135
  * every random choice (the TLS server random and ephemeral key, the server's
@@ -122,25 +153,31 @@
122
153
  * `maxData`, the connection-ID table by the limit advertised, the received
123
154
  * packet numbers by `QUIC_ACK_MAX_RANGES`, and datagrams by their ring.
124
155
  *
125
- * **Secrets.** The packet keys of each level live in this connection's
126
- * fields as long as the level does, and in `TlsServer`'s (TLS-1); so do the
127
- * 1-RTT secrets the next key generation is derived from, and the next
128
- * generation's read keys. A `Secret` may not be a field (NL2430), so they are
129
- * plain bytes; `secureZero` wipes each level's key, IV and header-protection
130
- * key when the level is discarded, each generation's key, IV and secret when
131
- * a key update replaces it, and `release()` (and `reset`) the rest — the
132
- * 1-RTT keys, the traffic secrets `TlsServer` holds, its ephemeral key and
133
- * the connection-ID seed. What no wipe reaches (the expanded AES key
134
- * schedules, the HKDF and HMAC intermediates in arena memory) is recorded as
135
- * QUIC-2 in `docs/security/quic.md`, with #430, the follow-up that moves
136
- * these structs onto `nish:secret`.
156
+ * **Secrets.** The packet keys of each level live in this connection's key
157
+ * slots as long as the level does; so do the 1-RTT secrets the next key
158
+ * generation is derived from, and the next generation's read keys. The
159
+ * traffic secrets and the ephemeral key `TlsServer` holds are `Secret`
160
+ * fields its `wipe()` reaches (TLS-1): `afterTls` takes each level's secrets
161
+ * out of them once, into this connection's plain slots, and zeroes them
162
+ * through `wipeSecret`. The slots are plain bytes, wiped with stores no
163
+ * optimiser removes (`secureZero`, and `aesKeyWipe` for an AES schedule):
164
+ * each level's key, IV, header-protection key and schedules when the level
165
+ * is discarded; each generation's key, IV, schedule and secret when a key
166
+ * update replaces it; and the rest — the 1-RTT keys, the key-update secrets,
167
+ * whatever `TlsServer` still holds and the connection-ID seed — by
168
+ * `release()`, `reset`, the idle timeout, and the end of the closing or
169
+ * draining period (RFC 9000 §10.2), three probe timeouts after this side's
170
+ * CONNECTION_CLOSE went out or the client's arrived.
171
+ * `docs/security/quic.md` records what is wiped where as QUIC-2.
137
172
  *
138
173
  * Written from RFC 9000, RFC 9001 and RFC 9221, in this module's own
139
174
  * structure; nothing here is ported from another implementation. Private
140
175
  * names carry the `quicConn` prefix (`docs/wp26-stdlib.md` §3e).
141
176
  */
142
177
  import { timingSafeEqualAt } from "nish/crypto/ct"
143
- import { hmacSha256 } from "nish/crypto/hmac"
178
+ import { AesKey, aesKeyWipe } from "nish/crypto/aes"
179
+ import { HkdfScratch } from "nish/crypto/hkdf"
180
+ import { HmacSha256Scratch, hmacSha256 } from "nish/crypto/hmac"
144
181
  import {
145
182
  QUIC_AEAD_AES_128_GCM,
146
183
  QUIC_AEAD_AES_256_GCM,
@@ -153,15 +190,16 @@ import {
153
190
  QUIC_PACKET_INITIAL,
154
191
  QUIC_PACKET_OK,
155
192
  QUIC_PACKET_SHORT,
193
+ QUIC_PACKET_ZERO_RTT,
156
194
  QuicHeader,
157
- QuicInitialSecrets,
158
195
  QuicKeys,
196
+ QuicKeysSlot,
159
197
  QuicPacket,
160
198
  quicDecryptPayload,
161
- quicInitialSecrets,
162
- quicKeyUpdateSecret,
163
- quicKeys,
164
- quicKeysUpdate,
199
+ quicInitialSecretsInto,
200
+ quicKeyUpdateSecretInto,
201
+ quicKeysInto,
202
+ quicKeysUpdateInto,
165
203
  quicPacketNumberLength,
166
204
  quicParseHeaderInto,
167
205
  quicPutLongHeader,
@@ -170,6 +208,7 @@ import {
170
208
  quicUnprotectHeader,
171
209
  } from "nish/net/quic-packet"
172
210
  import {
211
+ QUIC_ERROR_AEAD_LIMIT_REACHED,
173
212
  QUIC_ERROR_APPLICATION,
174
213
  QUIC_ERROR_CRYPTO,
175
214
  QUIC_ERROR_CRYPTO_BUFFER_EXCEEDED,
@@ -218,8 +257,8 @@ import {
218
257
  } from "nish/net/quic-frame"
219
258
  import {
220
259
  QuicTransportParameters,
221
- quicEncodeTransportParameters,
222
- quicParseTransportParameters,
260
+ quicEncodeTransportParametersInto,
261
+ quicParseTransportParametersInto,
223
262
  } from "nish/net/quic-conn-params"
224
263
  import { QuicAckRanges } from "nish/net/quic-conn-ack"
225
264
  import {
@@ -249,13 +288,22 @@ import {
249
288
  TLS_LEVEL_APPLICATION,
250
289
  TLS_LEVEL_HANDSHAKE,
251
290
  TLS_LEVEL_INITIAL,
291
+ TLS_SECRET_CLIENT_APPLICATION,
292
+ TLS_SECRET_CLIENT_HANDSHAKE,
293
+ TLS_SECRET_SERVER_APPLICATION,
294
+ TLS_SECRET_SERVER_HANDSHAKE,
252
295
  TLS_STATE_CONNECTED,
253
296
  TLS_STATE_WAIT_CLIENT_HELLO,
254
297
  TLS_STATE_WAIT_SIGNATURE,
298
+ TlsSecretPool,
255
299
  TlsServer,
256
300
  TlsServerConfig,
301
+ tlsHeldClone,
302
+ tlsHeldCopyIn,
303
+ tlsHeldCopyOver,
257
304
  } from "nish/net/tls"
258
305
  import { TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256 } from "nish/net/tls/schedule"
306
+ import { Secret, expose, exposeWith, secret, wipe } from "nish:secret"
259
307
 
260
308
  // ---- Sizes ----------------------------------------------------------------------
261
309
 
@@ -288,10 +336,11 @@ export const QUIC_CONN_MAX_STREAM_DATA: i64 = 1048576
288
336
  /** The most streams of one kind a configuration may let the client open at once, or open itself. */
289
337
  export const QUIC_CONN_MAX_STREAMS: i64 = 1024
290
338
  /**
291
- * The most bytes of stream buffer a configuration may make each connection
339
+ * The most bytes of stream buffer a configuration may let each connection
292
340
  * hold: every stream slot (`maxStreamsBidi + maxStreamsUni + localStreams`)
293
- * has `maxStreamData` bytes each way, made when the connection is, so a
294
- * configuration past 64 MiB of them is refused rather than allocated.
341
+ * may borrow `maxStreamData` bytes each way, and the pools are sized so all
342
+ * of them can at once, so a configuration past 64 MiB of them is refused
343
+ * rather than allowed to allocate it.
295
344
  */
296
345
  export const QUIC_CONN_MAX_STREAM_BUFFERS: i64 = 67108864
297
346
  /** The length of the configuration's static keys: the stateless reset key and the Retry token key. */
@@ -320,12 +369,62 @@ export const QUIC_CONN_PACKET_STREAMS: i32 = 4
320
369
  export const QUIC_CONN_PACKET_CONTROL: i32 = 4
321
370
  /**
322
371
  * How many key updates a connection takes from its client (RFC 9001 §6).
323
- * Each costs two key derivations whose temporaries stay in the arena, so the
324
- * cap bounds what a client can make the server derive (QUIC-4); the next one
372
+ * Each costs two key derivations of work, though none of their memory, so
373
+ * the cap bounds what a client can make the server derive (QUIC-4); the next one
325
374
  * closes the connection with KEY_UPDATE_ERROR. A client also has to wait
326
375
  * a probe timeout between two of them.
327
376
  */
328
377
  export const QUIC_CONN_MAX_KEY_UPDATES: i32 = 64
378
+ /**
379
+ * AES-GCM's confidentiality limit (RFC 9001 §6.6): how many packets one
380
+ * 1-RTT write key seals. The server starts a key update at three quarters of
381
+ * it, and closes the connection with AEAD_LIMIT_REACHED, sending nothing more
382
+ * under the key, if it reaches it with no update possible. ChaCha20-Poly1305
383
+ * has no such limit.
384
+ */
385
+ export const QUIC_CONN_CONFIDENTIALITY_LIMIT: i64 = 8388608
386
+ /**
387
+ * The integrity limits (RFC 9001 §6.6): how many packets may fail to
388
+ * authenticate over a connection's life, across every key, before it closes
389
+ * with AEAD_LIMIT_REACHED. AES-GCM's is 2^52, ChaCha20-Poly1305's 2^36.
390
+ */
391
+ export const QUIC_CONN_INTEGRITY_LIMIT_GCM: i64 = 4503599627370496
392
+ export const QUIC_CONN_INTEGRITY_LIMIT_CHACHA: i64 = 68719476736
393
+ /**
394
+ * How many skipped packet numbers the server remembers (RFC 9000 §21.4): an
395
+ * ACK of any of the last this many closes the connection as an optimistic
396
+ * acknowledgement. One is skipped every `QUIC_CONN_SKIP_MIN` to
397
+ * `QUIC_CONN_SKIP_MIN + QUIC_CONN_SKIP_SPREAD - 1` packets, so the ring
398
+ * reaches back at least 7 × 129 = 903 packets.
399
+ */
400
+ export const QUIC_CONN_SKIP_RING: i32 = 8
401
+ export const QUIC_CONN_SKIP_MIN: i64 = 128
402
+ export const QUIC_CONN_SKIP_SPREAD: i64 = 897
403
+ /**
404
+ * The HMAC counter the skip distances are drawn under, past any connection
405
+ * ID's sequence number (`QUIC_CONN_MAX_ISSUED_CIDS`), so the same seed gives
406
+ * both without either telling anything about the other.
407
+ */
408
+ const QUIC_CONN_SKIP_SEQUENCE: i64 = QUIC_MAX_VARINT + 1
409
+ /**
410
+ * How many ack-eliciting datagrams a probe timeout sends in the Initial and
411
+ * Handshake spaces (RFC 9002 §6.2.4 allows up to two). The server's first
412
+ * flight is usually one datagram, so with one probe a lossy path loses every
413
+ * copy of it about as often as it loses one datagram per probe timeout; two
414
+ * square that, and stay inside the amplification limit, which a one-datagram
415
+ * flight and two probes fill exactly. The Application Data space sends one.
416
+ */
417
+ export const QUIC_CONN_HANDSHAKE_PROBES: i32 = 2
418
+ /**
419
+ * How many times a connection resends its handshake data before the probe
420
+ * timeout because the client showed it lacks it (RFC 9002 §6.2.3, "for a
421
+ * limited number of times per connection"): a client Initial carrying
422
+ * CRYPTO data the server already read, or an ack-eliciting Handshake packet,
423
+ * while the server's own CRYPTO data at that level is in flight. Each
424
+ * resend is one datagram, so a client cannot make the server answer more
425
+ * than this many packets that way, whatever it sends.
426
+ */
427
+ export const QUIC_CONN_EARLY_RESENDS: i32 = 2
329
428
  /**
330
429
  * How many connection IDs a connection issues in all, its first included.
331
430
  * The client retiring one asks for a replacement (§5.1.1, a SHOULD), and
@@ -381,6 +480,10 @@ export const QUIC_DATAGRAM_ERR_FULL: i32 = -5
381
480
 
382
481
  /** A typed zero for the offsets below: a bare literal is an `f64` under `--number-mode f64`. */
383
482
  const QUIC_CONN_FROM: i32 = 0
483
+ /** A space's key slots (`QuicConnSpace.keySlot`): the read keys', the write keys', and where Application Data's write keys start. */
484
+ const QUIC_CONN_READ_SLOT: i32 = 0
485
+ const QUIC_CONN_WRITE_SLOT: i32 = 1
486
+ const QUIC_CONN_APP_WRITE_SLOTS: i32 = 2
384
487
  /** The same for the `i64` arguments of the methods below, where a literal is not given its parameter's type. */
385
488
  const QUIC_CONN_NONE: i64 = 0
386
489
  /** TLS's unexpected_message alert, which CRYPTO data at a level TLS is not reading is (RFC 8446 §6). */
@@ -577,6 +680,12 @@ class QuicConnSpace {
577
680
  */
578
681
  cryptoOut: u8[]
579
682
  cryptoOutLength: i32 = 0
683
+ /**
684
+ * How many ack-eliciting probe packets this space still owes, which the
685
+ * window does not hold back: what a probe timeout asked for (RFC 9002
686
+ * §6.2.4), or an early resend (§6.2.3).
687
+ */
688
+ probes: i32 = 0
580
689
  cryptoOutOffset: i64 = 0
581
690
  /** How many CRYPTO bytes the client sent here that were handed to TLS, for a duplicate arriving after the next level started. */
582
691
  cryptoInDelivered: i64 = 0
@@ -597,6 +706,12 @@ class QuicConnSpace {
597
706
  sentControlCount: i32[]
598
707
  sentControlKind: u8[]
599
708
  sentControlValue: i64[]
709
+ /**
710
+ * Where this space's packet keys live, made once for the slot (QUIC-3):
711
+ * the read keys' and the write keys', and for the Application Data space a
712
+ * second of each, which a key update derives the next generation into.
713
+ */
714
+ keySlots: QuicKeysSlot[]
600
715
  /** `TLS_LEVEL_INITIAL`, `_HANDSHAKE` or `_APPLICATION`, which is also the space's index. */
601
716
  level: i32 = 0
602
717
  cryptoOutHead: i32 = 0
@@ -609,13 +724,16 @@ class QuicConnSpace {
609
724
  discarded: boolean = false
610
725
  /** Whether the packet being built elicits an acknowledgement, and so is recorded. */
611
726
  stagedEliciting: boolean = false
612
- /** Whether a probe timeout asked this space for an ack-eliciting packet, which the window does not hold back (RFC 9002 §6.2.4). */
613
- probe: boolean = false
614
727
 
615
728
  constructor(level: i32) {
616
729
  this.level = level
617
730
  this.received = new QuicAckRanges()
618
731
  this.cryptoOut = []
732
+ this.keySlots = []
733
+ const slots: i32 = level === TLS_LEVEL_APPLICATION ? 4 : 2
734
+ for (let k: i32 = 0; k < slots; k += 1) {
735
+ this.keySlots.push(new QuicKeysSlot())
736
+ }
619
737
  const capacity: i32 =
620
738
  level === TLS_LEVEL_APPLICATION ? QUIC_RECOVERY_APPLICATION_CAPACITY : QUIC_RECOVERY_HANDSHAKE_CAPACITY
621
739
  const rows: i32 = capacity + 1
@@ -649,23 +767,44 @@ class QuicConnSpace {
649
767
  this.cryptoResendLow = -1
650
768
  this.cryptoResendHigh = -1
651
769
  this.discarded = false
652
- this.probe = false
770
+ this.probes = 0
653
771
  this.clearStaged()
654
772
  }
655
773
 
656
- /** Appends `bytes` to the CRYPTO stream kept, writing into the array's old room before growing it. */
657
- appendCrypto(bytes: u8[]): void {
658
- for (const b of bytes) {
774
+ /**
775
+ * Appends `bytes[0 .. length)` to the CRYPTO stream kept, writing into the
776
+ * array's old room before growing it: TLS's output buffer is read in
777
+ * place, so its flight is not copied on the way.
778
+ */
779
+ appendCrypto(bytes: u8[], length: i32): void {
780
+ for (let k: i32 = 0; k < length && k < toI32(bytes.length); k += 1) {
659
781
  const at: i32 = this.cryptoOutLength
660
782
  if (at >= 0 && at < toI32(this.cryptoOut.length)) {
661
- this.cryptoOut[at] = b
783
+ this.cryptoOut[at] = bytes[k]
662
784
  } else {
663
- this.cryptoOut.push(b)
785
+ this.cryptoOut.push(bytes[k])
664
786
  }
665
787
  this.cryptoOutLength = at + 1
666
788
  }
667
789
  }
668
790
 
791
+ /**
792
+ * Key slot `k` of this space: in the Initial and Handshake spaces 0 holds
793
+ * the read keys and 1 the write keys; in Application Data 0 and 1 hold
794
+ * the read keys' generations and 2 and 3 the write keys'.
795
+ */
796
+ keySlot(k: i32): QuicKeysSlot {
797
+ const at: i32 = k >= 0 && k < toI32(this.keySlots.length) ? k : 0
798
+ return this.keySlots[at]
799
+ }
800
+
801
+ /** Zeroes every key this space's slots hold, the AES schedules included. */
802
+ wipeKeySlots(): void {
803
+ for (const slot of this.keySlots) {
804
+ slot.wipe()
805
+ }
806
+ }
807
+
669
808
  /** The number of STREAM chunks row `row` holds, or 0. */
670
809
  streamCount(row: i32): i32 {
671
810
  return row >= 0 && row < toI32(this.sentStreamCount.length) ? this.sentStreamCount[row] : 0
@@ -853,7 +992,14 @@ const quicConnAead = (suite: i32): i32 => {
853
992
  return QUIC_AEAD_AES_128_GCM
854
993
  }
855
994
 
856
- /** Wipes a level's key, IV and header-protection key; the expanded AES schedules are out of reach (QUIC-2). */
995
+ /** Empties a ring of skipped packet numbers. */
996
+ const quicConnForgetSkips = (skipped: i64[]): void => {
997
+ for (let k: i32 = 0; k < toI32(skipped.length); k += 1) {
998
+ skipped[k] = -1
999
+ }
1000
+ }
1001
+
1002
+ /** Wipes a level's key, IV and header-protection key; `QuicKeysSlot.wipe` takes the header-protection schedule. */
857
1003
  const quicConnWipeKeys = (keys: QuicKeys | null): void => {
858
1004
  quicConnWipePacketKey(keys)
859
1005
  if (keys !== null) {
@@ -862,14 +1008,19 @@ const quicConnWipeKeys = (keys: QuicKeys | null): void => {
862
1008
  }
863
1009
 
864
1010
  /**
865
- * Wipes a key generation's packet key and IV that a key update replaced. The
866
- * header-protection key is left alone: every generation shares it (RFC 9001
867
- * §6), so it goes only when the level does.
1011
+ * Wipes a key generation's packet key, IV and the packet key's AES schedule,
1012
+ * which a key update replaced. The header-protection key and its schedule are
1013
+ * left alone: every generation shares them (RFC 9001 §6), so they go only
1014
+ * when the level does.
868
1015
  */
869
1016
  const quicConnWipePacketKey = (keys: QuicKeys | null): void => {
870
1017
  if (keys !== null) {
871
1018
  secureZero(keys.key)
872
1019
  secureZero(keys.iv)
1020
+ const packetAes: AesKey | null = keys.packetAes
1021
+ if (packetAes !== null) {
1022
+ aesKeyWipe(packetAes)
1023
+ }
873
1024
  }
874
1025
  }
875
1026
 
@@ -888,6 +1039,59 @@ export const quicStatelessResetToken = (key: u8[], cid: u8[]): u8[] => {
888
1039
  return token
889
1040
  }
890
1041
 
1042
+ /**
1043
+ * `quicStatelessResetToken` of the ID `cid[at .. at + length)` into
1044
+ * `out[outAt ..]`, computed in a connection's own HMAC scratch inside an
1045
+ * arena block, so issuing an ID keeps nothing (QUIC-3). The scratch and the
1046
+ * full MAC are wiped before it returns.
1047
+ */
1048
+ export const quicConnResetTokenInto = (
1049
+ mac: HmacSha256Scratch,
1050
+ key: u8[],
1051
+ cid: u8[],
1052
+ at: i32,
1053
+ length: i32,
1054
+ out: u8[],
1055
+ outAt: i32
1056
+ ): void => {
1057
+ using _scope = arena()
1058
+ const tag: u8[] = new Array<u8>(32)
1059
+ mac.begin(key, QUIC_CONN_FROM, toI32(key.length))
1060
+ mac.update(cid, at, length)
1061
+ mac.finishInto(tag, QUIC_CONN_FROM)
1062
+ for (
1063
+ let k: i32 = 0;
1064
+ k < QUIC_RESET_TOKEN_SIZE && k < toI32(tag.length) && outAt + k < toI32(out.length);
1065
+ k += 1
1066
+ ) {
1067
+ out[outAt + k] = tag[k]
1068
+ }
1069
+ secureZero(tag)
1070
+ mac.wipe()
1071
+ }
1072
+
1073
+ /** Puts `from[at .. at + length)` in `to`, emptied first, in the room `to` already has. */
1074
+ const quicConnRefill = (to: u8[], from: u8[], at: i32, length: i32): void => {
1075
+ while (to.length > 0) {
1076
+ to.pop()
1077
+ }
1078
+ for (let k: i32 = 0; k < length && at + k >= 0 && at + k < toI32(from.length); k += 1) {
1079
+ to.push(from[at + k])
1080
+ }
1081
+ }
1082
+
1083
+ /** Zeroes the bytes a `Secret` field holds, for `exposeWith` to run outside the connection's `wipe()`; answers `answer`. */
1084
+ const quicConnZeroHeld = (held: u8[], answer: i32): i32 => {
1085
+ wipe(held)
1086
+ return answer
1087
+ }
1088
+
1089
+ /** Copies the x25519 private key, `entropy[32 .. 64)`, into the held key, for `exposeWith`; answers 0. */
1090
+ const quicConnEphemeralIn = (held: u8[], entropy: u8[]): i32 => {
1091
+ quicCidCopy(held, entropy, 32, 32)
1092
+ return 0
1093
+ }
1094
+
891
1095
  /** Whether the configuration's limits are ones this module can honour. */
892
1096
  const quicConnConfigFits = (config: QuicServerConfig): boolean =>
893
1097
  config.maxStreamData >= 1 &&
@@ -931,7 +1135,8 @@ export class QuicConnection {
931
1135
  /** For a transport error: the type of the frame that caused it, or 0. */
932
1136
  errorFrameType: i64 = 0
933
1137
  serverRandom: u8[]
934
- ephemeralPrivate: u8[]
1138
+ /** The x25519 private key `begin` takes from the entropy, until the handshake hands it to `TlsServer`. */
1139
+ readonly ephemeral: Secret<u8[]>
935
1140
  cidSeed: u8[]
936
1141
  /** The DCID of the client's first Initial, which the Initial keys come from: its first `originalDcidLength` bytes. */
937
1142
  originalDcid: u8[]
@@ -940,6 +1145,37 @@ export class QuicConnection {
940
1145
  /** The server's first connection ID, the SCID of every long-header packet it sends. */
941
1146
  localScid: u8[]
942
1147
  tls: TlsServer | null = null
1148
+ /** The slot's `TlsServer`, made by its first handshake and `restart`ed by every one after (QUIC-3). */
1149
+ tlsSlot: TlsServer | null = null
1150
+ /** Its configuration, made once: the parameters it carries are `localEncoded`, rewritten for each connection. */
1151
+ tlsConfig: TlsServerConfig
1152
+ /** The server's transport parameters, set for each connection, and their encoding. */
1153
+ localParameters: QuicTransportParameters
1154
+ localEncoded: u8[]
1155
+ /** The client's transport parameters, parsed into the slot's own object. */
1156
+ parsedParameters: QuicTransportParameters
1157
+ /** Where every key derivation and HMAC of the connection runs. */
1158
+ kdf: HkdfScratch
1159
+ /** The Initial secrets of RFC 9001 §5.2, the client's and the server's. */
1160
+ initialClient: u8[]
1161
+ initialServer: u8[]
1162
+ /**
1163
+ * The 1-RTT traffic secrets (RFC 9001 §6.1), one set of SHA-256's length
1164
+ * and one of SHA-384's, the one the suite's hash picks in use: the
1165
+ * current read secret, the next generation's, and the current write
1166
+ * secret, which a key update ratchets in place. The write side keeps no
1167
+ * next secret, because the server derives its next write keys only when
1168
+ * it moves to them.
1169
+ */
1170
+ readonly readSecret256: Secret<u8[]>
1171
+ readonly nextReadSecret256: Secret<u8[]>
1172
+ readonly writeSecret256: Secret<u8[]>
1173
+ readonly readSecret384: Secret<u8[]>
1174
+ readonly nextReadSecret384: Secret<u8[]>
1175
+ readonly writeSecret384: Secret<u8[]>
1176
+ /** A connection ID being issued and its stateless reset token, before the table copies them. */
1177
+ cidScratch: u8[]
1178
+ tokenScratch: u8[]
943
1179
  initial: QuicConnSpace
944
1180
  handshake: QuicConnSpace
945
1181
  application: QuicConnSpace
@@ -980,14 +1216,10 @@ export class QuicConnection {
980
1216
  /** When the idle timer last restarted (RFC 9000 §10.1), or -1 before the first packet. */
981
1217
  idleSince: i64 = -1
982
1218
  /**
983
- * The 1-RTT secrets of the current generation, the next generation's read
984
- * secret, and its read keys (RFC 9001 §6.1, §6.3). `otherReadKeys` holds
985
- * the next generation's while `otherIsNext`, and for a probe timeout after
986
- * an update the previous one's, for reordered packets (§6.5).
1219
+ * The other read keys of the Application Data space (RFC 9001 §6.3):
1220
+ * the next generation's while `otherIsNext`, and for a probe timeout
1221
+ * after an update the previous one's, for reordered packets (§6.5).
987
1222
  */
988
- appReadSecret: u8[]
989
- appWriteSecret: u8[]
990
- nextReadSecret: u8[]
991
1223
  otherReadKeys: QuicKeys | null = null
992
1224
  /** The lowest and highest packet numbers opened with the current read keys, or -1. */
993
1225
  readPhaseLowest: i64 = -1
@@ -996,10 +1228,49 @@ export class QuicConnection {
996
1228
  writePhaseFirst: i64 = 0
997
1229
  /** When the previous read keys go and the next are derived, or -1. */
998
1230
  keyRetainUntil: i64 = -1
1231
+ /**
1232
+ * When a closed connection's closing or draining period ends (RFC 9000
1233
+ * §10.2), and `runClocks` wipes every key it kept for it, or -1: before
1234
+ * the close, while a close this side owes has not gone out, and once the
1235
+ * keys are wiped.
1236
+ */
1237
+ closingUntil: i64 = -1
999
1238
  /** The packet number of a client key update read but not applied yet, or -1. */
1000
1239
  phasePending: i64 = -1
1001
1240
  /** The largest DATAGRAM frame the client takes (its `max_datagram_frame_size`), 0 for none. */
1002
1241
  peerMaxDatagramFrame: i64 = 0
1242
+ /**
1243
+ * The AEAD limits this connection keeps (RFC 9001 §6.6): packets one 1-RTT
1244
+ * write key seals, and packets that fail to authenticate over the
1245
+ * connection. `afterTls` sets them from the suite when the 1-RTT keys
1246
+ * arrive; until then they are AES-GCM's. They are fields so that a test can
1247
+ * lower them after the handshake; nothing else should set them.
1248
+ */
1249
+ confidentialityLimit: i64 = 8388608
1250
+ integrityLimit: i64 = 4503599627370496
1251
+ /** Packets sealed under the current 1-RTT write key, and packets that failed to authenticate, against those limits. */
1252
+ sealedUnderKey: i64 = 0
1253
+ authFailures: i64 = 0
1254
+ /**
1255
+ * ECN (RFC 9000 §13.4): `ecnMarking` is the carrier's to set when it sends
1256
+ * this connection's datagrams with the ECN bits of `datagramEcn`, which
1257
+ * each `takeDatagramInto` sets for the datagram it wrote: ECT(0) while
1258
+ * loss recovery's `ecnCodepoint()` asks for it, 0 otherwise and always
1259
+ * while `ecnMarking` is false. Every packet of a marked datagram is counted
1260
+ * as marked, and an ACK_ECN's counts are validated against them. `reset`
1261
+ * keeps `ecnMarking`, which is the carrier's, and clears the rest.
1262
+ */
1263
+ ecnMarking: boolean = false
1264
+ datagramEcn: i32 = 0
1265
+ /**
1266
+ * The next Application Data packet number skipped (RFC 9000 §21.4), or -1
1267
+ * before the 1-RTT keys; a test may set it to force one. `skipped` holds
1268
+ * the last `QUIC_CONN_SKIP_RING` skipped, -1 where none, `skipHead` where
1269
+ * the next goes, and `skipsDrawn` how many distances were drawn.
1270
+ */
1271
+ nextSkip: i64 = -1
1272
+ skipped: i64[]
1273
+ skipsDrawn: i64 = 0
1003
1274
  originalDcidLength: i32 = 0
1004
1275
  peerScidLength: i32 = 0
1005
1276
  pathHead: i32 = 0
@@ -1009,6 +1280,15 @@ export class QuicConnection {
1009
1280
  dropped: i32 = 0
1010
1281
  /** Key updates the client started, against `QUIC_CONN_MAX_KEY_UPDATES`. */
1011
1282
  keyUpdates: i32 = 0
1283
+ /** Where the next skipped packet number goes in `skipped`. */
1284
+ skipHead: i32 = 0
1285
+ /** Which of the Application Data space's two read slots, and two write slots, hold the current keys. */
1286
+ appReadSlot: i32 = 0
1287
+ appWriteSlot: i32 = 0
1288
+ /** Handshake data sent again early, against `QUIC_CONN_EARLY_RESENDS` (RFC 9002 §6.2.3). */
1289
+ earlyResends: i32 = 0
1290
+ /** The level at which the packet just read showed the client lacks the server's handshake data, or -1. */
1291
+ resendLevel: i32 = -1
1012
1292
  /** Whether `error` is an application code (CONNECTION_CLOSE 0x1d) rather than a transport error. */
1013
1293
  errorIsApplication: boolean = false
1014
1294
  /** Whether the one CONNECTION_CLOSE this side owes has gone out. */
@@ -1018,6 +1298,12 @@ export class QuicConnection {
1018
1298
  /** Whether the handshake is complete, which for a server is also confirmed (RFC 9001 §4.1.2). */
1019
1299
  handshakeComplete: boolean = false
1020
1300
  handshakeDonePending: boolean = false
1301
+ /**
1302
+ * Whether `afterTls` has taken the application traffic secrets out of
1303
+ * `TlsServer`. It takes them once: `wipeSecret` leaves them known, so a
1304
+ * second take would derive the 1-RTT keys from the zeros it left.
1305
+ */
1306
+ applicationTaken: boolean = false
1021
1307
  /** Whether the connection was set up by `acceptRetry`. */
1022
1308
  retried: boolean = false
1023
1309
  /** Whether an ack-eliciting packet went out since the last packet was received. */
@@ -1042,7 +1328,7 @@ export class QuicConnection {
1042
1328
  constructor(config: QuicServerConfig, entropy: u8[]) {
1043
1329
  this.config = config
1044
1330
  this.serverRandom = new Array<u8>(32)
1045
- this.ephemeralPrivate = new Array<u8>(32)
1331
+ this.ephemeral = secret(new Array<u8>(32))
1046
1332
  this.localScid = new Array<u8>(QUIC_CONN_CID_LENGTH)
1047
1333
  this.cidSeed = new Array<u8>(32)
1048
1334
  this.originalDcid = new Array<u8>(QUIC_MAX_CID_LENGTH)
@@ -1080,11 +1366,33 @@ export class QuicConnection {
1080
1366
  this.header = new QuicHeader()
1081
1367
  this.packet = new QuicPacket()
1082
1368
  this.none = []
1083
- this.retryOriginalDcid = this.none
1084
- this.retryScid = this.none
1085
- this.appReadSecret = this.none
1086
- this.appWriteSecret = this.none
1087
- this.nextReadSecret = this.none
1369
+ this.retryOriginalDcid = []
1370
+ this.retryScid = []
1371
+ this.localParameters = new QuicTransportParameters()
1372
+ this.localEncoded = []
1373
+ this.parsedParameters = new QuicTransportParameters()
1374
+ const extra: u8[] = []
1375
+ this.tlsConfig = {
1376
+ certificateChain: config.certificateChain,
1377
+ alpn: config.alpn,
1378
+ quicTransportParameters: this.localEncoded,
1379
+ extraExtensions: extra,
1380
+ signatureScheme: config.signatureScheme,
1381
+ quic: true,
1382
+ }
1383
+ this.kdf = new HkdfScratch()
1384
+ this.initialClient = new Array<u8>(32)
1385
+ this.initialServer = new Array<u8>(32)
1386
+ this.readSecret256 = secret(new Array<u8>(32))
1387
+ this.nextReadSecret256 = secret(new Array<u8>(32))
1388
+ this.writeSecret256 = secret(new Array<u8>(32))
1389
+ this.readSecret384 = secret(new Array<u8>(48))
1390
+ this.nextReadSecret384 = secret(new Array<u8>(48))
1391
+ this.writeSecret384 = secret(new Array<u8>(48))
1392
+ this.cidScratch = new Array<u8>(QUIC_CONN_CID_LENGTH)
1393
+ this.tokenScratch = new Array<u8>(QUIC_RESET_TOKEN_SIZE)
1394
+ this.skipped = new Array<i64>(QUIC_CONN_SKIP_RING)
1395
+ quicConnForgetSkips(this.skipped)
1088
1396
  this.begin(entropy)
1089
1397
  }
1090
1398
 
@@ -1094,8 +1402,8 @@ export class QuicConnection {
1094
1402
  * array): every secret of the last connection is wiped first, as
1095
1403
  * `release()` does, and every buffer is kept and emptied. The limits are
1096
1404
  * the constructor's configuration's. A slot reset this way allocates
1097
- * nothing for itself; the next handshake allocates what any handshake does
1098
- * (TLS-3 in `docs/security/tls.md`).
1405
+ * nothing, and neither does the next handshake (QUIC-3 in
1406
+ * `docs/security/quic.md`).
1099
1407
  */
1100
1408
  reset(entropy: u8[]): void {
1101
1409
  this.wipeAll()
@@ -1109,6 +1417,7 @@ export class QuicConnection {
1109
1417
  this.datagramsIn.reset()
1110
1418
  this.cryptoIn.reset(TLS_LEVEL_INITIAL)
1111
1419
  this.peerParameters = this.defaultParameters
1420
+ // The slot keeps its `TlsServer`, wiped by `wipeAll`; the next handshake restarts it.
1112
1421
  this.tls = null
1113
1422
  this.error = 0
1114
1423
  this.errorFrameType = 0
@@ -1116,18 +1425,20 @@ export class QuicConnection {
1116
1425
  this.bytesSent = 0
1117
1426
  this.alpn = ""
1118
1427
  this.serverName = ""
1119
- this.retryOriginalDcid = this.none
1120
- this.retryScid = this.none
1121
- this.appReadSecret = this.none
1122
- this.appWriteSecret = this.none
1123
- this.nextReadSecret = this.none
1428
+ quicConnRefill(this.retryOriginalDcid, this.none, QUIC_CONN_FROM, QUIC_CONN_FROM)
1429
+ quicConnRefill(this.retryScid, this.none, QUIC_CONN_FROM, QUIC_CONN_FROM)
1124
1430
  this.otherReadKeys = null
1431
+ this.appReadSlot = 0
1432
+ this.appWriteSlot = 0
1433
+ this.earlyResends = 0
1434
+ this.resendLevel = -1
1125
1435
  this.now = 0
1126
1436
  this.idleSince = -1
1127
1437
  this.readPhaseLowest = -1
1128
1438
  this.readPhaseHighest = -1
1129
1439
  this.writePhaseFirst = 0
1130
1440
  this.keyRetainUntil = -1
1441
+ this.closingUntil = -1
1131
1442
  this.phasePending = -1
1132
1443
  this.peerMaxDatagramFrame = 0
1133
1444
  this.originalDcidLength = 0
@@ -1137,10 +1448,20 @@ export class QuicConnection {
1137
1448
  this.state = QUIC_STATE_WAIT_INITIAL
1138
1449
  this.dropped = 0
1139
1450
  this.keyUpdates = 0
1451
+ this.confidentialityLimit = QUIC_CONN_CONFIDENTIALITY_LIMIT
1452
+ this.integrityLimit = QUIC_CONN_INTEGRITY_LIMIT_GCM
1453
+ this.sealedUnderKey = 0
1454
+ this.authFailures = 0
1455
+ this.datagramEcn = 0
1456
+ this.nextSkip = -1
1457
+ quicConnForgetSkips(this.skipped)
1458
+ this.skipsDrawn = 0
1459
+ this.skipHead = 0
1140
1460
  this.errorIsApplication = false
1141
1461
  this.closeSent = false
1142
1462
  this.addressValidated = false
1143
1463
  this.handshakeComplete = false
1464
+ this.applicationTaken = false
1144
1465
  this.handshakeDonePending = false
1145
1466
  this.retried = false
1146
1467
  this.elicitingSent = false
@@ -1155,7 +1476,7 @@ export class QuicConnection {
1155
1476
  /** Takes `entropy` (and wipes it), or closes with INTERNAL_ERROR for entropy or a configuration that does not fit. */
1156
1477
  begin(entropy: u8[]): void {
1157
1478
  quicCidCopy(this.serverRandom, entropy, 0, 32)
1158
- quicCidCopy(this.ephemeralPrivate, entropy, 32, 32)
1479
+ exposeWith(this.ephemeral, entropy, quicConnEphemeralIn)
1159
1480
  quicCidCopy(this.localScid, entropy, 64, QUIC_CONN_CID_LENGTH)
1160
1481
  quicCidCopy(this.cidSeed, entropy, 72, 32)
1161
1482
  const fits: boolean = toI32(entropy.length) === QUIC_CONN_ENTROPY_SIZE && quicConnConfigFits(this.config)
@@ -1164,6 +1485,7 @@ export class QuicConnection {
1164
1485
  this.state = QUIC_STATE_CLOSING
1165
1486
  this.error = QUIC_ERROR_INTERNAL
1166
1487
  this.closeSent = true
1488
+ this.startClosingPeriod()
1167
1489
  }
1168
1490
  }
1169
1491
 
@@ -1201,8 +1523,8 @@ export class QuicConnection {
1201
1523
  ) {
1202
1524
  return false
1203
1525
  }
1204
- this.retryOriginalDcid = quicConnSlice(originalDcid, 0, toI32(originalDcid.length))
1205
- this.retryScid = quicConnSlice(retryScid, 0, toI32(retryScid.length))
1526
+ quicConnRefill(this.retryOriginalDcid, originalDcid, QUIC_CONN_FROM, toI32(originalDcid.length))
1527
+ quicConnRefill(this.retryScid, retryScid, QUIC_CONN_FROM, toI32(retryScid.length))
1206
1528
  this.retried = true
1207
1529
  return true
1208
1530
  }
@@ -1234,6 +1556,19 @@ export class QuicConnection {
1234
1556
  this.errorIsApplication = true
1235
1557
  }
1236
1558
 
1559
+ /**
1560
+ * Starts the closing or draining period, three probe timeouts long (RFC
1561
+ * 9000 §10.2), once nothing more is owed: when the client's
1562
+ * CONNECTION_CLOSE arrives, when `takeCloseInto` sends this side's, or when
1563
+ * `begin` closes before anything could be sent. The keys stay through it,
1564
+ * as the period's state, and `runClocks` wipes them all at its end, which
1565
+ * `deadline()` names, so that a connection nobody `release()`s does not
1566
+ * keep them.
1567
+ */
1568
+ startClosingPeriod(): void {
1569
+ this.closingUntil = this.now + this.recovery.probeTimeout() * 3
1570
+ }
1571
+
1237
1572
  /** The space of packets of `type`, or `null` for a 0-RTT or Retry packet. */
1238
1573
  spaceOf(type: i32): QuicConnSpace | null {
1239
1574
  if (type === QUIC_PACKET_INITIAL) {
@@ -1352,13 +1687,29 @@ export class QuicConnection {
1352
1687
  ) {
1353
1688
  return false
1354
1689
  }
1355
- const dcid: u8[] = quicConnSlice(buf, header.dcidStart, header.dcidLength)
1356
- const secrets: QuicInitialSecrets | null = quicInitialSecrets(dcid)
1357
- if (secrets === null) {
1690
+ const secrets: boolean = quicInitialSecretsInto(
1691
+ this.kdf,
1692
+ buf,
1693
+ header.dcidStart,
1694
+ header.dcidLength,
1695
+ this.initialClient,
1696
+ this.initialServer
1697
+ )
1698
+ if (!secrets) {
1358
1699
  return false
1359
1700
  }
1360
- this.initial.readKeys = quicKeys(QUIC_AEAD_AES_128_GCM, secrets.client)
1361
- this.initial.writeKeys = quicKeys(QUIC_AEAD_AES_128_GCM, secrets.server)
1701
+ this.initial.readKeys = quicKeysInto(
1702
+ this.kdf,
1703
+ this.initial.keySlot(QUIC_CONN_READ_SLOT),
1704
+ QUIC_AEAD_AES_128_GCM,
1705
+ this.initialClient
1706
+ )
1707
+ this.initial.writeKeys = quicKeysInto(
1708
+ this.kdf,
1709
+ this.initial.keySlot(QUIC_CONN_WRITE_SLOT),
1710
+ QUIC_AEAD_AES_128_GCM,
1711
+ this.initialServer
1712
+ )
1362
1713
  quicCidCopy(this.originalDcid, buf, header.dcidStart, header.dcidLength)
1363
1714
  this.originalDcidLength = header.dcidLength
1364
1715
  quicCidCopy(this.peerScid, buf, header.scidStart, header.scidLength)
@@ -1369,14 +1720,24 @@ export class QuicConnection {
1369
1720
 
1370
1721
  /**
1371
1722
  * The rest of `start`, once the first Initial has opened: the first local
1372
- * connection ID, and a `TlsServer` whose transport parameters name both
1373
- * IDs (RFC 9000 §7.3), the Retry's when `acceptRetry` set one up, and the
1374
- * first ID's stateless reset token (§18.2).
1723
+ * connection ID, and the slot's `TlsServer` started with transport
1724
+ * parameters that name both IDs (RFC 9000 §7.3), the Retry's when
1725
+ * `acceptRetry` set one up, and the first ID's stateless reset token
1726
+ * (§18.2).
1375
1727
  */
1376
1728
  establish(): void {
1377
1729
  const none: u8[] = this.none
1378
- const resetToken: u8[] = quicStatelessResetToken(this.config.statelessResetKey, this.localScid)
1379
- this.cids.addLocal(this.localScid, resetToken)
1730
+ const token: u8[] = this.tokenScratch
1731
+ quicConnResetTokenInto(
1732
+ this.kdf.sha256,
1733
+ this.config.statelessResetKey,
1734
+ this.localScid,
1735
+ QUIC_CONN_FROM,
1736
+ QUIC_CONN_CID_LENGTH,
1737
+ token,
1738
+ QUIC_CONN_FROM
1739
+ )
1740
+ this.cids.addLocal(this.localScid, token)
1380
1741
  this.cids.addPeerAt(
1381
1742
  QUIC_CONN_NONE,
1382
1743
  QUIC_CONN_NONE,
@@ -1390,17 +1751,27 @@ export class QuicConnection {
1390
1751
  // §8.1.2: a Retry token the listener checked has validated the address.
1391
1752
  this.addressValidated = this.retried
1392
1753
 
1393
- const params: QuicTransportParameters = new QuicTransportParameters()
1394
- params.originalDcid = this.retried
1395
- ? this.retryOriginalDcid
1396
- : quicConnSlice(this.originalDcid, 0, this.originalDcidLength)
1754
+ // The parameters, and their encoding, are rewritten in the slot's own
1755
+ // arrays, which grow only when a connection needs more than any before.
1756
+ const params: QuicTransportParameters = this.localParameters
1757
+ if (this.retried) {
1758
+ quicConnRefill(
1759
+ params.originalDcid,
1760
+ this.retryOriginalDcid,
1761
+ QUIC_CONN_FROM,
1762
+ toI32(this.retryOriginalDcid.length)
1763
+ )
1764
+ } else {
1765
+ quicConnRefill(params.originalDcid, this.originalDcid, QUIC_CONN_FROM, this.originalDcidLength)
1766
+ }
1397
1767
  params.hasOriginalDcid = true
1398
1768
  params.initialScid = this.localScid
1399
1769
  params.hasInitialScid = true
1400
- params.retryScid = this.retryScid
1770
+ quicConnRefill(params.retryScid, this.retryScid, QUIC_CONN_FROM, toI32(this.retryScid.length))
1401
1771
  params.hasRetryScid = this.retried
1402
- params.statelessResetToken = resetToken
1772
+ quicConnRefill(params.statelessResetToken, token, QUIC_CONN_FROM, QUIC_RESET_TOKEN_SIZE)
1403
1773
  params.hasStatelessResetToken = true
1774
+ secureZero(token)
1404
1775
  params.maxIdleTimeout = this.config.maxIdleTimeout
1405
1776
  params.initialMaxData = this.config.maxData
1406
1777
  params.initialMaxStreamDataBidiRemote = this.config.maxStreamData
@@ -1415,16 +1786,27 @@ export class QuicConnection {
1415
1786
  // The server never migrates and asks the client not to (§9): path
1416
1787
  // validation beyond answering PATH_CHALLENGE is not here.
1417
1788
  params.disableActiveMigration = true
1418
- const extra: u8[] = []
1419
- const tlsConfig: TlsServerConfig = {
1420
- certificateChain: this.config.certificateChain,
1421
- alpn: this.config.alpn,
1422
- quicTransportParameters: quicEncodeTransportParameters(params),
1423
- extraExtensions: extra,
1424
- signatureScheme: this.config.signatureScheme,
1425
- quic: true,
1789
+ quicEncodeTransportParametersInto(params, this.localEncoded)
1790
+ // One `TlsServer` a slot, made by its first handshake (so its flight is
1791
+ // sized for these parameters) and restarted, wiped, by each after it.
1792
+ // The ephemeral key goes from the connection's `Secret` to the
1793
+ // server's: a restart takes a plain copy, which it zeroes, made in an
1794
+ // arena block; a new server takes a zero key that ours is copied over.
1795
+ // Either way ours is zeroed once it is handed on.
1796
+ const tls: TlsServer | null = this.tlsSlot
1797
+ if (tls !== null) {
1798
+ {
1799
+ using _scope = arena()
1800
+ tls.restart(this.serverRandom, expose(this.ephemeral, tlsHeldClone))
1801
+ }
1802
+ this.tls = tls
1803
+ } else {
1804
+ const made: TlsServer = new TlsServer(this.tlsConfig, this.serverRandom, new Array<u8>(32))
1805
+ exposeWith(this.ephemeral, made.ephemeral, tlsHeldCopyOver)
1806
+ this.tlsSlot = made
1807
+ this.tls = made
1426
1808
  }
1427
- this.tls = new TlsServer(tlsConfig, this.serverRandom, this.ephemeralPrivate)
1809
+ exposeWith(this.ephemeral, QUIC_CONN_FROM, quicConnZeroHeld)
1428
1810
  }
1429
1811
 
1430
1812
  /**
@@ -1460,10 +1842,10 @@ export class QuicConnection {
1460
1842
  /**
1461
1843
  * Opens one packet of a connection already started, and acts on its frames.
1462
1844
  * The opening and the frames run inside an arena block (`openScoped`), so
1463
- * their temporaries go when it ends; what has to keep memory — a first
1464
- * Initial's `TlsServer`, a key update's new keys, TLS reading the CRYPTO
1465
- * bytes and what it answers — runs after it. Answers whether the packet
1466
- * was used.
1845
+ * their temporaries go when it ends; what may grow a buffer of the slot —
1846
+ * a first Initial's `TlsServer` and parameters, a key update, TLS reading
1847
+ * the CRYPTO bytes and what it answers — runs after it. Answers whether
1848
+ * the packet was used.
1467
1849
  */
1468
1850
  openPacket(buf: u8[], header: QuicHeader, first: boolean): boolean {
1469
1851
  const type: i32 = header.type
@@ -1481,10 +1863,22 @@ export class QuicConnection {
1481
1863
  this.cryptoArrived = false
1482
1864
  this.topUpOwed = false
1483
1865
  this.phasePending = -1
1866
+ this.resendLevel = -1
1484
1867
  let used: boolean = false
1868
+ // A STREAM frame's first byte borrows a receive buffer, and no pool may
1869
+ // take one inside the scope, so the reserve is primed before it with what
1870
+ // this packet could want. Only 0-RTT and 1-RTT packets carry STREAM
1871
+ // frames (RFC 9000 §12.4); any other still makes a standalone
1872
+ // connection's private pools, so the handshake, not the first 1-RTT
1873
+ // packet, pays for them.
1874
+ if (type === QUIC_PACKET_SHORT || type === QUIC_PACKET_ZERO_RTT) {
1875
+ this.streams.prime(header.end - header.pnOffset)
1876
+ } else {
1877
+ this.streams.ownPools()
1878
+ }
1485
1879
  {
1486
1880
  using _scope = arena()
1487
- used = this.openScoped(buf, header, space, keys)
1881
+ used = this.openScoped(buf, header, space, keys, first)
1488
1882
  }
1489
1883
  if (!used) {
1490
1884
  return false
@@ -1511,6 +1905,9 @@ export class QuicConnection {
1511
1905
  if (this.topUpOwed) {
1512
1906
  this.topUpConnectionIds()
1513
1907
  }
1908
+ if (this.resendLevel >= 0 && !this.closed()) {
1909
+ this.resendEarly(this.resendLevel)
1910
+ }
1514
1911
  return true
1515
1912
  }
1516
1913
 
@@ -1521,7 +1918,7 @@ export class QuicConnection {
1521
1918
  * an allocation, which is what lets the block release the packet's
1522
1919
  * temporaries. Answers whether the packet was used rather than dropped.
1523
1920
  */
1524
- openScoped(buf: u8[], header: QuicHeader, space: QuicConnSpace, keys: QuicKeys): boolean {
1921
+ openScoped(buf: u8[], header: QuicHeader, space: QuicConnSpace, keys: QuicKeys, first: boolean): boolean {
1525
1922
  const packet: QuicPacket = this.packet
1526
1923
  // Every key generation shares the header-protection key (RFC 9001 §6),
1527
1924
  // so the current keys take it off whatever the Key Phase bit says.
@@ -1536,6 +1933,15 @@ export class QuicConnection {
1536
1933
  const payload: u8[] | null = quicDecryptPayload(open, buf, header, clear, packet)
1537
1934
  if (payload === null) {
1538
1935
  this.drop()
1936
+ // RFC 9001 §6.6: past the integrity limit, the connection closes and
1937
+ // reads nothing more. A first Initial that fails is not counted: the
1938
+ // connection is not started, and the next one starts afresh.
1939
+ if (!first) {
1940
+ this.authFailures = this.authFailures + 1
1941
+ if (this.authFailures > this.integrityLimit) {
1942
+ this.fail(QUIC_ERROR_AEAD_LIMIT_REACHED, QUIC_CONN_NONE)
1943
+ }
1944
+ }
1539
1945
  return false
1540
1946
  }
1541
1947
  // §17.2, §17.3.1: reserved bits set under a valid tag close the connection.
@@ -1555,6 +1961,11 @@ export class QuicConnection {
1555
1961
  if (this.closed()) {
1556
1962
  return true
1557
1963
  }
1964
+ // RFC 9002 §6.2.3: a client that sends an ack-eliciting Handshake packet
1965
+ // while the server's Handshake data is in flight may not have it all.
1966
+ if (eliciting && type === QUIC_PACKET_HANDSHAKE) {
1967
+ this.resendLevel = TLS_LEVEL_HANDSHAKE
1968
+ }
1558
1969
  space.received.record(pn, eliciting)
1559
1970
  // §10.1: a packet received and processed restarts the idle timer.
1560
1971
  this.idleSince = this.now
@@ -1619,9 +2030,14 @@ export class QuicConnection {
1619
2030
  this.application.readKeys = this.otherReadKeys
1620
2031
  this.otherReadKeys = previous
1621
2032
  this.otherIsNext = false
1622
- secureZero(this.appReadSecret)
1623
- this.appReadSecret = this.nextReadSecret
1624
- this.nextReadSecret = this.none
2033
+ this.appReadSlot = 1 - this.appReadSlot
2034
+ // The next read secret becomes the current one, over the secret it
2035
+ // replaces, and is zeroed where it was. Both widths move: the one the
2036
+ // suite does not use holds zeros.
2037
+ exposeWith(this.nextReadSecret256, this.readSecret256, tlsHeldCopyOver)
2038
+ exposeWith(this.nextReadSecret384, this.readSecret384, tlsHeldCopyOver)
2039
+ exposeWith(this.nextReadSecret256, QUIC_CONN_FROM, quicConnZeroHeld)
2040
+ exposeWith(this.nextReadSecret384, QUIC_CONN_FROM, quicConnZeroHeld)
1625
2041
  this.readPhase = !this.readPhase
1626
2042
  this.readPhaseLowest = pn
1627
2043
  this.readPhaseHighest = pn
@@ -1639,24 +2055,68 @@ export class QuicConnection {
1639
2055
  if (keys === null) {
1640
2056
  return false
1641
2057
  }
1642
- const secret: u8[] | null = quicKeyUpdateSecret(keys.aead, this.appWriteSecret)
1643
- if (secret === null) {
1644
- return false
1645
- }
1646
- const next: QuicKeys | null = quicKeysUpdate(keys, secret)
2058
+ const slot: QuicKeysSlot = this.application.keySlot(QUIC_CONN_APP_WRITE_SLOTS + 1 - this.appWriteSlot)
2059
+ const next: QuicKeys | null =
2060
+ keys.aead === QUIC_AEAD_AES_256_GCM
2061
+ ? this.nextKeys(slot, keys, this.writeSecret384, this.writeSecret384)
2062
+ : this.nextKeys(slot, keys, this.writeSecret256, this.writeSecret256)
1647
2063
  if (next === null) {
1648
- secureZero(secret)
1649
2064
  return false
1650
2065
  }
1651
2066
  quicConnWipePacketKey(keys)
1652
- secureZero(this.appWriteSecret)
1653
2067
  this.application.writeKeys = next
1654
- this.appWriteSecret = secret
2068
+ this.appWriteSlot = 1 - this.appWriteSlot
1655
2069
  this.writePhase = !this.writePhase
1656
2070
  this.writePhaseFirst = this.application.nextPn
2071
+ this.sealedUnderKey = 0
1657
2072
  return true
1658
2073
  }
1659
2074
 
2075
+ /**
2076
+ * A level's keys from a held traffic secret, derived into `slot` through
2077
+ * the connection's own HKDF scratch, as `quicKeys` derives them: the
2078
+ * derivation reads a plain copy made in an arena block, which is zeroed
2079
+ * before it ends, so the call keeps nothing in the arena. Answers the
2080
+ * slot's keys, or `null` for a secret of the wrong length.
2081
+ */
2082
+ keysFrom(slot: QuicKeysSlot, aead: i32, held: Secret<u8[]>): QuicKeys | null {
2083
+ let made: boolean = false
2084
+ {
2085
+ using _scope = arena()
2086
+ const plain: u8[] = expose(held, tlsHeldClone)
2087
+ made = quicKeysInto(this.kdf, slot, aead, plain) !== null
2088
+ secureZero(plain)
2089
+ }
2090
+ return made ? slot.keys : null
2091
+ }
2092
+
2093
+ /**
2094
+ * The next generation of `keys` into `slot` (RFC 9001 §6.1): the next
2095
+ * traffic secret, `quic ku` of `current`, and its packet key and IV, with
2096
+ * the header-protection key and its schedule shared, as every generation
2097
+ * shares them. The secret is written into `next`, which may be `current`
2098
+ * and then ratchets it. The derivation reads plain copies made in an arena
2099
+ * block, which are zeroed before it ends. Answers the slot's keys, or
2100
+ * `null`, leaving `next` as it was, for keys `quicKeys` did not make.
2101
+ */
2102
+ nextKeys(slot: QuicKeysSlot, keys: QuicKeys, current: Secret<u8[]>, next: Secret<u8[]>): QuicKeys | null {
2103
+ let made: boolean = false
2104
+ {
2105
+ using _scope = arena()
2106
+ const plain: u8[] = expose(current, tlsHeldClone)
2107
+ const out: u8[] = new Array<u8>(plain.length)
2108
+ made =
2109
+ quicKeyUpdateSecretInto(this.kdf, keys.aead, plain, out) &&
2110
+ quicKeysUpdateInto(this.kdf, slot, keys, out) !== null
2111
+ if (made) {
2112
+ exposeWith(next, out, tlsHeldCopyIn)
2113
+ }
2114
+ secureZero(plain)
2115
+ secureZero(out)
2116
+ }
2117
+ return made ? slot.keys : null
2118
+ }
2119
+
1660
2120
  /**
1661
2121
  * Derives the next generation's read secret and keys from the current
1662
2122
  * ones, into `otherReadKeys`, wiping the previous generation's key and IV
@@ -1673,12 +2133,11 @@ export class QuicConnection {
1673
2133
  if (keys === null) {
1674
2134
  return
1675
2135
  }
1676
- const secret: u8[] | null = quicKeyUpdateSecret(keys.aead, this.appReadSecret)
1677
- if (secret === null) {
1678
- return
1679
- }
1680
- this.nextReadSecret = secret
1681
- this.otherReadKeys = quicKeysUpdate(keys, secret)
2136
+ const slot: QuicKeysSlot = this.application.keySlot(1 - this.appReadSlot)
2137
+ this.otherReadKeys =
2138
+ keys.aead === QUIC_AEAD_AES_256_GCM
2139
+ ? this.nextKeys(slot, keys, this.readSecret384, this.nextReadSecret384)
2140
+ : this.nextKeys(slot, keys, this.readSecret256, this.nextReadSecret256)
1682
2141
  this.otherIsNext = this.otherReadKeys !== null
1683
2142
  }
1684
2143
 
@@ -1873,6 +2332,7 @@ export class QuicConnection {
1873
2332
  this.error = code
1874
2333
  this.errorIsApplication = application
1875
2334
  this.closeSent = true
2335
+ this.startClosingPeriod()
1876
2336
  }
1877
2337
 
1878
2338
  /**
@@ -1882,7 +2342,7 @@ export class QuicConnection {
1882
2342
  * carried, and each it shows lost has its frames queued again.
1883
2343
  */
1884
2344
  receiveAck(space: QuicConnSpace, frame: QuicFrame): void {
1885
- if (frame.largest >= space.nextPn) {
2345
+ if (frame.largest >= space.nextPn || (space.level === TLS_LEVEL_APPLICATION && this.acksSkipped(frame))) {
1886
2346
  this.fail(QUIC_ERROR_PROTOCOL_VIOLATION, toI64(frame.type))
1887
2347
  return
1888
2348
  }
@@ -1891,9 +2351,24 @@ export class QuicConnection {
1891
2351
  }
1892
2352
  const sent: QuicSentPackets | null = this.recovery.space(space.level)
1893
2353
  const delay: i64 = this.ackDelayOf(frame.ackDelay)
2354
+ // ACK_ECN's counts (RFC 9002 §7.1), or -1 each for a plain ACK.
2355
+ const ecn: boolean = frame.type === QUIC_FRAME_ACK_ECN
2356
+ const none: i64 = -1
2357
+ const ect0: i64 = ecn ? frame.ect0 : none
2358
+ const ect1: i64 = ecn ? frame.ect1 : none
2359
+ const ce: i64 = ecn ? frame.ce : none
1894
2360
  if (
1895
2361
  sent === null ||
1896
- this.recovery.onAck(space.level, frame.ackRanges, frame.ackRangeCount, delay, this.now) <= 0
2362
+ this.recovery.onAckEcn(
2363
+ space.level,
2364
+ frame.ackRanges,
2365
+ frame.ackRangeCount,
2366
+ delay,
2367
+ this.now,
2368
+ ect0,
2369
+ ect1,
2370
+ ce
2371
+ ) <= 0
1897
2372
  ) {
1898
2373
  return
1899
2374
  }
@@ -1903,6 +2378,26 @@ export class QuicConnection {
1903
2378
  this.packetsLost(space, sent)
1904
2379
  }
1905
2380
 
2381
+ /**
2382
+ * Whether `frame` acknowledges a packet number the server skipped and
2383
+ * still remembers (RFC 9000 §21.4): a client that does is acknowledging
2384
+ * what it never received. Every range the frame stored is read; one past
2385
+ * `QUIC_FRAME_MAX_ACK_RANGES` is not, and loss recovery does not act on
2386
+ * it either.
2387
+ */
2388
+ acksSkipped(frame: QuicFrame): boolean {
2389
+ const ranges: i64[] = frame.ackRanges
2390
+ for (let k: i32 = 0; k < toI32(this.skipped.length); k += 1) {
2391
+ const pn: i64 = this.skipped[k]
2392
+ for (let r: i32 = 0; pn >= 0 && r < frame.ackRangeCount && r * 2 + 1 < toI32(ranges.length); r += 1) {
2393
+ if (pn >= ranges[r * 2] && pn <= ranges[r * 2 + 1]) {
2394
+ return true
2395
+ }
2396
+ }
2397
+ }
2398
+ return false
2399
+ }
2400
+
1906
2401
  /**
1907
2402
  * An ACK frame's ACK Delay in milliseconds: the field scaled by the
1908
2403
  * client's `ack_delay_exponent` (RFC 9000 §19.3, §18.2). A value too large
@@ -1984,10 +2479,12 @@ export class QuicConnection {
1984
2479
 
1985
2480
  /**
1986
2481
  * A probe timeout fired (RFC 9002 §6.2.4): in every space with packets in
1987
- * flight, everything they carry is queued again and the next packet is a
1988
- * probe, sent whatever the window says, with a PING if nothing is left to
2482
+ * flight, everything they carry is queued again and the next packets are
2483
+ * probes, sent whatever the window says, with a PING if nothing is left to
1989
2484
  * carry. During the handshake that resends the Initial and the Handshake
1990
- * flight together, which is what a client that lost both needs.
2485
+ * flight together, which is what a client that lost both needs, and does
2486
+ * it twice (`QUIC_CONN_HANDSHAKE_PROBES`), so that one more lost datagram
2487
+ * does not cost another, doubled, probe timeout.
1991
2488
  */
1992
2489
  probe(): void {
1993
2490
  for (let level: i32 = 0; level < 3; level += 1) {
@@ -2002,7 +2499,56 @@ export class QuicConnection {
2002
2499
  this.requeue(space, slot)
2003
2500
  }
2004
2501
  }
2005
- space.probe = true
2502
+ space.probes = QUIC_CONN_HANDSHAKE_PROBES
2503
+ if (level === TLS_LEVEL_APPLICATION) {
2504
+ space.probes = 1
2505
+ }
2506
+ }
2507
+ }
2508
+
2509
+ /**
2510
+ * Speeds up the handshake (RFC 9002 §6.2.3): the client showed at `level`
2511
+ * that it lacks the server's handshake data there — an Initial with CRYPTO
2512
+ * data the server already read, or an ack-eliciting Handshake packet —
2513
+ * so, while the server's own packets at that level are in flight, the
2514
+ * oldest one's CRYPTO data at that level and at Handshake goes again now,
2515
+ * as one probe datagram, rather than at the probe timeout. It does so at
2516
+ * most `QUIC_CONN_EARLY_RESENDS` times a connection, and not while an
2517
+ * earlier probe is still owed, so each packet the client sends asks for
2518
+ * one datagram at most. The anti-amplification limit holds it like any
2519
+ * datagram (`takeDatagramInto`).
2520
+ */
2521
+ resendEarly(level: i32): void {
2522
+ if (
2523
+ this.earlyResends >= QUIC_CONN_EARLY_RESENDS ||
2524
+ this.initial.probes > 0 ||
2525
+ this.handshake.probes > 0
2526
+ ) {
2527
+ return
2528
+ }
2529
+ const asked: QuicSentPackets | null = this.recovery.space(level)
2530
+ if (asked === null || asked.inFlight === 0) {
2531
+ return
2532
+ }
2533
+ let queued: boolean = false
2534
+ for (let at: i32 = level; at <= TLS_LEVEL_HANDSHAKE; at += 1) {
2535
+ const space: QuicConnSpace = this.spaceAt(at)
2536
+ const sent: QuicSentPackets | null = this.recovery.space(at)
2537
+ if (sent === null || space.discarded || sent.inFlight === 0) {
2538
+ continue
2539
+ }
2540
+ // Compacted, the ring's oldest slot is the oldest packet in flight.
2541
+ sent.compact()
2542
+ const oldest: i32 = sent.slot(QUIC_CONN_FROM)
2543
+ const length: i32 = space.cryptoLength(oldest)
2544
+ if (length > 0) {
2545
+ space.resendCrypto(space.cryptoOffset(oldest), length)
2546
+ space.probes = 1
2547
+ queued = true
2548
+ }
2549
+ }
2550
+ if (queued) {
2551
+ this.earlyResends += 1
2006
2552
  }
2007
2553
  }
2008
2554
 
@@ -2026,6 +2572,11 @@ export class QuicConnection {
2026
2572
  this.spaceAt(reassembly.level).cryptoInDelivered = reassembly.delivered
2027
2573
  reassembly.reset(space.level)
2028
2574
  }
2575
+ // RFC 9002 §6.2.3: an Initial with CRYPTO data the server already read
2576
+ // says the client did not get the server's Initial flight.
2577
+ if (space.level === TLS_LEVEL_INITIAL && frame.offset < reassembly.delivered) {
2578
+ this.resendLevel = TLS_LEVEL_INITIAL
2579
+ }
2029
2580
  if (!reassembly.insert(frame.offset, payload, frame.dataStart, frame.dataLength)) {
2030
2581
  this.fail(QUIC_ERROR_CRYPTO_BUFFER_EXCEEDED, toI64(QUIC_FRAME_CRYPTO))
2031
2582
  return
@@ -2116,10 +2667,12 @@ export class QuicConnection {
2116
2667
  return
2117
2668
  }
2118
2669
  if (!this.initial.discarded) {
2119
- this.initial.appendCrypto(tls.takeOutput(TLS_LEVEL_INITIAL))
2670
+ this.initial.appendCrypto(tls.outputInitial, tls.outputInitialLength)
2671
+ tls.clearOutput(TLS_LEVEL_INITIAL)
2120
2672
  }
2121
2673
  if (!this.handshake.discarded) {
2122
- this.handshake.appendCrypto(tls.takeOutput(TLS_LEVEL_HANDSHAKE))
2674
+ this.handshake.appendCrypto(tls.outputHandshake, tls.outputHandshakeLength)
2675
+ tls.clearOutput(TLS_LEVEL_HANDSHAKE)
2123
2676
  }
2124
2677
  const aead: i32 = quicConnAead(tls.suite)
2125
2678
  if (
@@ -2132,23 +2685,59 @@ export class QuicConnection {
2132
2685
  }
2133
2686
  this.alpn = tls.alpn
2134
2687
  this.serverName = tls.serverName
2135
- this.handshake.readKeys = this.keysFor(aead, tls.readSecret(TLS_LEVEL_HANDSHAKE))
2136
- this.handshake.writeKeys = this.keysFor(aead, tls.writeSecret(TLS_LEVEL_HANDSHAKE))
2688
+ if (tls.readKnown(TLS_LEVEL_HANDSHAKE)) {
2689
+ this.handshake.readKeys = this.keysFrom(
2690
+ this.handshake.keySlot(QUIC_CONN_READ_SLOT),
2691
+ aead,
2692
+ tls.secrets().clientHandshake
2693
+ )
2694
+ }
2695
+ if (tls.writeKnown(TLS_LEVEL_HANDSHAKE)) {
2696
+ this.handshake.writeKeys = this.keysFrom(
2697
+ this.handshake.keySlot(QUIC_CONN_WRITE_SLOT),
2698
+ aead,
2699
+ tls.secrets().serverHandshake
2700
+ )
2701
+ }
2137
2702
  }
2138
2703
  if (
2704
+ !this.applicationTaken &&
2139
2705
  !this.application.discarded &&
2140
2706
  this.application.writeKeys === null &&
2141
2707
  tls.state !== TLS_STATE_WAIT_SIGNATURE
2142
2708
  ) {
2143
- const write: u8[] | null = tls.writeSecret(TLS_LEVEL_APPLICATION)
2144
- const read: u8[] | null = tls.readSecret(TLS_LEVEL_APPLICATION)
2145
- if (write !== null && read !== null) {
2146
- this.application.writeKeys = this.keysFor(aead, write)
2147
- this.application.readKeys = this.keysFor(aead, read)
2148
- // The key update secrets start from `TlsServer`'s own arrays, so the
2149
- // first update wipes those too, rather than leaving a copy behind.
2150
- this.appWriteSecret = write
2151
- this.appReadSecret = read
2709
+ if (tls.writeKnown(TLS_LEVEL_APPLICATION) && tls.readKnown(TLS_LEVEL_APPLICATION)) {
2710
+ // The key updates derive from the connection's own `Secret`
2711
+ // fields, so the application secrets are copied into those of the
2712
+ // suite's width and wiped in `TlsServer`, leaving one copy of each.
2713
+ const pool: TlsSecretPool = tls.secrets()
2714
+ const writeSlot: QuicKeysSlot = this.application.keySlot(
2715
+ QUIC_CONN_APP_WRITE_SLOTS + this.appWriteSlot
2716
+ )
2717
+ const readSlot: QuicKeysSlot = this.application.keySlot(this.appReadSlot)
2718
+ if (aead === QUIC_AEAD_AES_256_GCM) {
2719
+ exposeWith(pool.serverApplication, this.writeSecret384, tlsHeldCopyOver)
2720
+ exposeWith(pool.clientApplication, this.readSecret384, tlsHeldCopyOver)
2721
+ } else {
2722
+ exposeWith(pool.serverApplication, this.writeSecret256, tlsHeldCopyOver)
2723
+ exposeWith(pool.clientApplication, this.readSecret256, tlsHeldCopyOver)
2724
+ }
2725
+ this.applicationTaken = true
2726
+ // ChaCha20-Poly1305 has no confidentiality limit, and no packet
2727
+ // number, so no count of packets, reaches `QUIC_MAX_VARINT`.
2728
+ const chacha: boolean = aead === QUIC_AEAD_CHACHA20_POLY1305
2729
+ this.integrityLimit = chacha ? QUIC_CONN_INTEGRITY_LIMIT_CHACHA : QUIC_CONN_INTEGRITY_LIMIT_GCM
2730
+ this.confidentialityLimit = chacha ? QUIC_MAX_VARINT : QUIC_CONN_CONFIDENTIALITY_LIMIT
2731
+ this.scheduleSkip(this.application.nextPn)
2732
+ tls.wipeSecret(TLS_SECRET_SERVER_APPLICATION)
2733
+ tls.wipeSecret(TLS_SECRET_CLIENT_APPLICATION)
2734
+ if (aead === QUIC_AEAD_AES_256_GCM) {
2735
+ this.application.writeKeys = this.keysFrom(writeSlot, aead, this.writeSecret384)
2736
+ this.application.readKeys = this.keysFrom(readSlot, aead, this.readSecret384)
2737
+ } else {
2738
+ this.application.writeKeys = this.keysFrom(writeSlot, aead, this.writeSecret256)
2739
+ this.application.readKeys = this.keysFrom(readSlot, aead, this.readSecret256)
2740
+ }
2152
2741
  this.prepareNextReadKeys()
2153
2742
  }
2154
2743
  }
@@ -2158,18 +2747,14 @@ export class QuicConnection {
2158
2747
  this.handshakeDonePending = true
2159
2748
  this.state = QUIC_STATE_CONNECTED
2160
2749
  this.discard(this.handshake)
2750
+ // The Handshake level's traffic secrets have nothing left to derive
2751
+ // once it is discarded (TLS wipes the handshake secret itself).
2752
+ tls.wipeSecret(TLS_SECRET_CLIENT_HANDSHAKE)
2753
+ tls.wipeSecret(TLS_SECRET_SERVER_HANDSHAKE)
2161
2754
  this.topUpConnectionIds()
2162
2755
  }
2163
2756
  }
2164
2757
 
2165
- /** `quicKeys` of a secret TLS has, or `null` when it has none yet. */
2166
- keysFor(aead: i32, secret: u8[] | null): QuicKeys | null {
2167
- if (secret === null) {
2168
- return null
2169
- }
2170
- return quicKeys(aead, secret)
2171
- }
2172
-
2173
2758
  /**
2174
2759
  * The client's transport parameters (RFC 9000 §7.3, §7.4, §18.2): they must
2175
2760
  * parse, and must name as `initial_source_connection_id` the SCID its first
@@ -2179,7 +2764,11 @@ export class QuicConnection {
2179
2764
  * the datagrams its `max_datagram_frame_size`.
2180
2765
  */
2181
2766
  checkPeerParameters(tls: TlsServer): boolean {
2182
- const p: QuicTransportParameters = quicParseTransportParameters(tls.clientTransportParameters, false)
2767
+ const p: QuicTransportParameters = quicParseTransportParametersInto(
2768
+ this.parsedParameters,
2769
+ tls.clientTransportParameters,
2770
+ false
2771
+ )
2183
2772
  if (p.error !== QUIC_ERROR_NO_ERROR || !p.hasInitialScid) {
2184
2773
  this.fail(QUIC_ERROR_TRANSPORT_PARAMETER, toI64(QUIC_FRAME_CRYPTO))
2185
2774
  return false
@@ -2215,9 +2804,10 @@ export class QuicConnection {
2215
2804
  this.recovery.discardSpace(space.level)
2216
2805
  space.cryptoResendLow = -1
2217
2806
  space.cryptoResendHigh = -1
2218
- space.probe = false
2807
+ space.probes = 0
2219
2808
  quicConnWipeKeys(space.readKeys)
2220
2809
  quicConnWipeKeys(space.writeKeys)
2810
+ space.wipeKeySlots()
2221
2811
  space.readKeys = null
2222
2812
  space.writeKeys = null
2223
2813
  space.cryptoOutLength = 0
@@ -2227,15 +2817,30 @@ export class QuicConnection {
2227
2817
  /**
2228
2818
  * The HMAC-SHA256 under the connection's seed of the sequence number
2229
2819
  * `sequence`, whose first 8 bytes are the local connection ID of that
2230
- * number: so the IDs are unlinkable to anyone without the seed and need no
2231
- * more entropy.
2820
+ * number, into `cidScratch`: so the IDs are unlinkable to anyone without
2821
+ * the seed and need no more entropy. It runs in the connection's HMAC
2822
+ * scratch inside an arena block, and wipes the MAC and the scratch.
2232
2823
  */
2233
- deriveConnectionId(sequence: i64): u8[] {
2824
+ deriveConnectionId(sequence: i64): void {
2825
+ const mac: HmacSha256Scratch = this.kdf.sha256
2826
+ using _scope = arena()
2234
2827
  const counter: u8[] = new Array<u8>(8)
2235
2828
  for (let k: i32 = 0; k < 8 && k < toI32(counter.length); k += 1) {
2236
2829
  counter[k] = toU8(toI32((sequence >> (toI64(7 - k) * 8)) & 255))
2237
2830
  }
2238
- return hmacSha256(this.cidSeed, counter)
2831
+ const tag: u8[] = new Array<u8>(32)
2832
+ mac.begin(this.cidSeed, QUIC_CONN_FROM, toI32(this.cidSeed.length))
2833
+ mac.update(counter, QUIC_CONN_FROM, toI32(counter.length))
2834
+ mac.finishInto(tag, QUIC_CONN_FROM)
2835
+ for (
2836
+ let k: i32 = 0;
2837
+ k < QUIC_CONN_CID_LENGTH && k < toI32(tag.length) && k < toI32(this.cidScratch.length);
2838
+ k += 1
2839
+ ) {
2840
+ this.cidScratch[k] = tag[k]
2841
+ }
2842
+ secureZero(tag)
2843
+ mac.wipe()
2239
2844
  }
2240
2845
 
2241
2846
  /**
@@ -2243,27 +2848,34 @@ export class QuicConnection {
2243
2848
  * would take (its `active_connection_id_limit`), up to
2244
2849
  * `QUIC_CONN_LOCAL_CIDS` at once and `QUIC_CONN_MAX_ISSUED_CIDS` in all;
2245
2850
  * each goes out in a NEW_CONNECTION_ID frame with the stateless reset
2246
- * token the configuration's static key gives it. One ID at a time, each
2247
- * call issuing one and then topping up the rest, since each leaves its
2248
- * HMAC's temporaries in the arena (QUIC-3) and a loop would hold them
2249
- * across its passes for no reason.
2851
+ * token the configuration's static key gives it. Each ID and token is made
2852
+ * in the slot's scratch and copied into the table, so issuing keeps
2853
+ * nothing (QUIC-3).
2250
2854
  */
2251
2855
  topUpConnectionIds(): void {
2252
- if (!this.handshakeComplete || this.cids.nextLocal >= QUIC_CONN_MAX_ISSUED_CIDS) {
2856
+ if (!this.handshakeComplete) {
2253
2857
  return
2254
2858
  }
2255
2859
  let want: i64 = this.peerParameters.activeConnectionIdLimit
2256
2860
  if (want > toI64(QUIC_CONN_LOCAL_CIDS)) {
2257
2861
  want = toI64(QUIC_CONN_LOCAL_CIDS)
2258
2862
  }
2259
- if (toI64(this.cids.activeLocal()) >= want) {
2260
- return
2261
- }
2262
- const mac: u8[] = this.deriveConnectionId(this.cids.nextLocal)
2263
- const cid: u8[] = quicConnSlice(mac, 0, QUIC_CONN_CID_LENGTH)
2264
- secureZero(mac)
2265
- if (this.cids.addLocal(cid, quicStatelessResetToken(this.config.statelessResetKey, cid)) >= 0) {
2266
- this.topUpConnectionIds()
2863
+ while (this.cids.nextLocal < QUIC_CONN_MAX_ISSUED_CIDS && toI64(this.cids.activeLocal()) < want) {
2864
+ this.deriveConnectionId(this.cids.nextLocal)
2865
+ quicConnResetTokenInto(
2866
+ this.kdf.sha256,
2867
+ this.config.statelessResetKey,
2868
+ this.cidScratch,
2869
+ QUIC_CONN_FROM,
2870
+ QUIC_CONN_CID_LENGTH,
2871
+ this.tokenScratch,
2872
+ QUIC_CONN_FROM
2873
+ )
2874
+ const added: i64 = this.cids.addLocal(this.cidScratch, this.tokenScratch)
2875
+ secureZero(this.tokenScratch)
2876
+ if (added < 0) {
2877
+ return
2878
+ }
2267
2879
  }
2268
2880
  }
2269
2881
 
@@ -2330,12 +2942,17 @@ export class QuicConnection {
2330
2942
  * When `handleTimer` is next due, in the caller's milliseconds: the idle
2331
2943
  * timeout, loss recovery's timer (a time-threshold loss or the probe
2332
2944
  * timeout), or the end of a key update's probe timeout, whichever comes
2333
- * first. -1 when nothing is timed: before the first packet, once the
2334
- * connection has closed, and with no idle timeout, nothing in flight and
2335
- * no update pending.
2945
+ * first; once the connection has closed, the end of its closing or
2946
+ * draining period, when its keys are wiped. -1 when nothing is timed:
2947
+ * before the first packet, while a closed connection's own close has not
2948
+ * gone out (`takeDatagram` sends it), once a closed connection's keys are
2949
+ * wiped, and with no idle timeout, nothing in flight and no update pending.
2336
2950
  */
2337
2951
  deadline(): i64 {
2338
- if (this.closed() || this.state === QUIC_STATE_WAIT_INITIAL) {
2952
+ if (this.closed()) {
2953
+ return this.closingUntil
2954
+ }
2955
+ if (this.state === QUIC_STATE_WAIT_INITIAL) {
2339
2956
  return -1
2340
2957
  }
2341
2958
  let due: i64 = this.idleDeadline()
@@ -2362,9 +2979,10 @@ export class QuicConnection {
2362
2979
  * Runs whatever is due by `now`, the caller's monotonic time in
2363
2980
  * milliseconds; call it at `deadline()`, or any time. Past the idle timeout
2364
2981
  * the connection closes silently (`QUIC_STATE_TIMED_OUT`, RFC 9000 §10.1):
2365
- * no CONNECTION_CLOSE, every key wiped. Past a key update's probe timeout
2366
- * the previous read keys are wiped and the next generation's derived (RFC
2367
- * 9001 §6.5). Past loss recovery's timer, packets lost by the time
2982
+ * no CONNECTION_CLOSE, every key wiped. Past the closing or draining
2983
+ * period of a connection that closed, every key is wiped (§10.2). Past a
2984
+ * key update's probe timeout the previous read keys are wiped and the next
2985
+ * generation's derived (RFC 9001 §6.5). Past loss recovery's timer, packets lost by the time
2368
2986
  * threshold have their frames queued again, or a probe timeout queues
2369
2987
  * every space's packets in flight and asks for a probe (RFC 9002 §6.1.2,
2370
2988
  * §6.2.4): `takeDatagram` then sends it. `receive` and `takeDatagram` run
@@ -2376,12 +2994,24 @@ export class QuicConnection {
2376
2994
  this.runRecoveryTimer()
2377
2995
  }
2378
2996
 
2379
- /** `handleTimer`'s idle timeout and key retention, which `receive` runs before it reads a datagram. */
2997
+ /**
2998
+ * `handleTimer`'s idle timeout, key retention and the end of the closing
2999
+ * or draining period, which `receive` runs before it reads a datagram.
3000
+ */
2380
3001
  runClocks(now: i64): void {
2381
3002
  if (now > this.now) {
2382
3003
  this.now = now
2383
3004
  }
2384
- if (this.closed() || this.state === QUIC_STATE_WAIT_INITIAL) {
3005
+ if (this.closed()) {
3006
+ // RFC 9000 §10.2: past the period, nothing more goes out, so the keys
3007
+ // kept for the CONNECTION_CLOSE are wiped (QUIC-2).
3008
+ if (this.closingUntil >= 0 && this.now >= this.closingUntil) {
3009
+ this.closeSent = true
3010
+ this.wipeAll()
3011
+ }
3012
+ return
3013
+ }
3014
+ if (this.state === QUIC_STATE_WAIT_INITIAL) {
2385
3015
  return
2386
3016
  }
2387
3017
  const idle: i64 = this.idleDeadline()
@@ -2436,6 +3066,24 @@ export class QuicConnection {
2436
3066
  return this.updateWriteKeys()
2437
3067
  }
2438
3068
 
3069
+ /**
3070
+ * Draws where the next Application Data packet number is skipped (RFC
3071
+ * 9000 §21.4): `QUIC_CONN_SKIP_MIN` plus up to `QUIC_CONN_SKIP_SPREAD - 1`
3072
+ * packets past `from`, the distance taken from the ID seed's HMAC under
3073
+ * `QUIC_CONN_SKIP_SEQUENCE`, so a client that saw where the last gaps fell
3074
+ * cannot tell where the next will.
3075
+ */
3076
+ scheduleSkip(from: i64): void {
3077
+ this.deriveConnectionId(QUIC_CONN_SKIP_SEQUENCE + this.skipsDrawn)
3078
+ this.skipsDrawn = this.skipsDrawn + 1
3079
+ let draw: i64 = 0
3080
+ for (let k: i32 = 0; k < 4 && k < toI32(this.cidScratch.length); k += 1) {
3081
+ draw = (draw << 8) | toI64(this.cidScratch[k])
3082
+ }
3083
+ secureZero(this.cidScratch)
3084
+ this.nextSkip = from + QUIC_CONN_SKIP_MIN + (draw % QUIC_CONN_SKIP_SPREAD)
3085
+ }
3086
+
2439
3087
  // ---- Streams ------------------------------------------------------------------
2440
3088
 
2441
3089
  /**
@@ -2681,6 +3329,7 @@ export class QuicConnection {
2681
3329
  return 0
2682
3330
  }
2683
3331
  let n: i32 = 0
3332
+ this.datagramEcn = 0
2684
3333
  if (this.state === QUIC_STATE_CLOSING) {
2685
3334
  using _scope = arena()
2686
3335
  n = this.takeCloseInto(out, at)
@@ -2694,6 +3343,8 @@ export class QuicConnection {
2694
3343
  if (this.amplificationBlocked()) {
2695
3344
  return 0
2696
3345
  }
3346
+ this.beforeDatagram()
3347
+ this.datagramEcn = this.ecnMarking ? this.recovery.ecnCodepoint() : 0
2697
3348
  {
2698
3349
  using _scope = arena()
2699
3350
  n = this.buildDatagram(out, at)
@@ -2701,6 +3352,32 @@ export class QuicConnection {
2701
3352
  return n
2702
3353
  }
2703
3354
 
3355
+ /**
3356
+ * What the next datagram's Application Data packet needs before it is
3357
+ * built, outside the arena block because a key update stores the keys it
3358
+ * derives. A datagram carries at most one such packet, so this runs once
3359
+ * a packet. From three quarters of the confidentiality limit, each tries a
3360
+ * key update, which goes when `updateKeys` allows (RFC 9001 §6.6). And
3361
+ * the packet number due to be skipped is (RFC 9000 §21.4): remembered,
3362
+ * never sealed nor recorded, and the next skip drawn.
3363
+ */
3364
+ beforeDatagram(): void {
3365
+ const limit: i64 = this.confidentialityLimit
3366
+ if (this.sealedUnderKey >= limit - limit / 4) {
3367
+ this.updateKeys()
3368
+ }
3369
+ const pn: i64 = this.application.nextPn
3370
+ if (pn !== this.nextSkip) {
3371
+ return
3372
+ }
3373
+ if (this.skipHead >= 0 && this.skipHead < toI32(this.skipped.length)) {
3374
+ this.skipped[this.skipHead] = pn
3375
+ }
3376
+ this.skipHead = (this.skipHead + 1) % QUIC_CONN_SKIP_RING
3377
+ this.application.nextPn = pn + 1
3378
+ this.scheduleSkip(pn + 1)
3379
+ }
3380
+
2704
3381
  /**
2705
3382
  * `takeDatagramInto`'s datagram, built and sealed into `out` at `at`: one
2706
3383
  * packet per space with something to carry, each written in place, the
@@ -2722,7 +3399,7 @@ export class QuicConnection {
2722
3399
  if (sent === null || overhead === 0 || remaining <= overhead + 8) {
2723
3400
  continue
2724
3401
  }
2725
- if (space.probe && sent.full()) {
3402
+ if (space.probes > 0 && sent.full()) {
2726
3403
  // A probe has to be recorded: the oldest packet gives up its slot,
2727
3404
  // and what it carried, already queued again by the probe, stays queued.
2728
3405
  const slot: i32 = this.recovery.evictOldest(level)
@@ -2730,7 +3407,7 @@ export class QuicConnection {
2730
3407
  this.requeue(space, slot)
2731
3408
  }
2732
3409
  }
2733
- const elicit: boolean = space.probe || (open && !sent.full())
3410
+ const elicit: boolean = space.probes > 0 || (open && !sent.full())
2734
3411
  const headerLength: i32 = overhead - QUIC_AEAD_TAG_SIZE
2735
3412
  const payloadStart: i32 = position + headerLength
2736
3413
  let payloadEnd: i32 = this.buildPayloadInto(
@@ -2842,7 +3519,7 @@ export class QuicConnection {
2842
3519
  if (space.level === TLS_LEVEL_APPLICATION) {
2843
3520
  at = this.buildApplication(space, out, at, end)
2844
3521
  }
2845
- if (space.probe && !space.stagedEliciting && at < end) {
3522
+ if (space.probes > 0 && !space.stagedEliciting && at < end) {
2846
3523
  at = quicPutTypeOnly(out, at, end, QUIC_FRAME_PING)
2847
3524
  space.stagedEliciting = true
2848
3525
  }
@@ -3001,6 +3678,13 @@ export class QuicConnection {
3001
3678
  * `quicKeys` did not make cause.
3002
3679
  */
3003
3680
  sealAt(space: QuicConnSpace, out: u8[], start: i32, payloadEnd: i32): i32 {
3681
+ // RFC 9001 §6.6: at the confidentiality limit, with no key update made
3682
+ // (`beforeDatagram`), the connection closes, and no packet, its
3683
+ // CONNECTION_CLOSE included, is sealed under the key again.
3684
+ if (space.level === TLS_LEVEL_APPLICATION && this.sealedUnderKey >= this.confidentialityLimit) {
3685
+ this.fail(QUIC_ERROR_AEAD_LIMIT_REACHED, QUIC_CONN_NONE)
3686
+ return 0
3687
+ }
3004
3688
  const keys: QuicKeys | null = space.writeKeys
3005
3689
  const pn: i64 = space.nextPn
3006
3690
  const pnLength: i32 = quicPacketNumberLength(pn, space.largestAcked)
@@ -3040,7 +3724,13 @@ export class QuicConnection {
3040
3724
  return 0
3041
3725
  }
3042
3726
  space.nextPn = pn + 1
3043
- this.recordSent(space, pn, end - start)
3727
+ if (space.level === TLS_LEVEL_APPLICATION) {
3728
+ this.sealedUnderKey = this.sealedUnderKey + 1
3729
+ }
3730
+ const slot: i32 = this.recordSent(space, pn, end - start)
3731
+ if (this.datagramEcn !== 0) {
3732
+ this.recovery.onEctSent(space.level, slot)
3733
+ }
3044
3734
  return end - start
3045
3735
  }
3046
3736
 
@@ -3051,19 +3741,29 @@ export class QuicConnection {
3051
3741
  * recorded. `buildDatagram` builds nothing ack-eliciting into a space
3052
3742
  * whose record is full, so recovery does not refuse one; if it ever did,
3053
3743
  * what the packet carried is queued again at once, as though lost.
3744
+ * Answers the slot recovery recorded it in, or -1.
3054
3745
  */
3055
- recordSent(space: QuicConnSpace, pn: i64, size: i32): void {
3746
+ recordSent(space: QuicConnSpace, pn: i64, size: i32): i32 {
3056
3747
  if (!space.stagedEliciting) {
3057
- return
3748
+ return -1
3058
3749
  }
3059
3750
  const slot: i32 = this.recovery.onPacketSent(space.level, pn, size, this.now)
3060
3751
  if (slot >= 0) {
3061
3752
  space.commitStaged(slot)
3062
- space.probe = false
3753
+ if (space.probes > 0) {
3754
+ space.probes -= 1
3755
+ // §6.2.4: a second probe carries unacknowledged data too. When the
3756
+ // first took everything queued, the oldest — what it just carried —
3757
+ // goes again.
3758
+ if (space.probes > 0 && space.cryptoResendLow < 0) {
3759
+ this.requeue(space, slot)
3760
+ }
3761
+ }
3063
3762
  } else {
3064
3763
  this.requeue(space, space.staging)
3065
3764
  }
3066
3765
  space.clearStaged()
3766
+ return slot
3067
3767
  }
3068
3768
 
3069
3769
  /** An acknowledged packet in row `row`: each stream chunk and stream control frame it carried is settled. */
@@ -3109,6 +3809,7 @@ export class QuicConnection {
3109
3809
  return 0
3110
3810
  }
3111
3811
  this.closeSent = true
3812
+ this.startClosingPeriod()
3112
3813
  let position: i32 = at
3113
3814
  const limit: i32 = at + QUIC_CONN_DATAGRAM_SIZE
3114
3815
  for (let level: i32 = 0; level < 3; level += 1) {
@@ -3159,10 +3860,13 @@ export class QuicConnection {
3159
3860
  * secrets and expected client Finished, the ephemeral key, the
3160
3861
  * connection-ID seed and the stateless reset tokens (QUIC-2). Call it when
3161
3862
  * the connection is done with (or `reset` the slot, which does the same
3162
- * first); the connection is closed after it and sends nothing more.
3863
+ * first); the connection is closed after it and sends nothing more. Every
3864
+ * stream buffer it borrowed goes back to its pool, zeroed: nothing is sent
3865
+ * again from a closed connection.
3163
3866
  */
3164
3867
  release(): void {
3165
3868
  this.wipeAll()
3869
+ this.streams.giveBack()
3166
3870
  if (!this.closed()) {
3167
3871
  this.state = QUIC_STATE_CLOSING
3168
3872
  this.error = QUIC_ERROR_NO_ERROR
@@ -3172,9 +3876,10 @@ export class QuicConnection {
3172
3876
 
3173
3877
  /**
3174
3878
  * Wipes every key and secret the connection holds and discards every
3175
- * level: `release()`'s work, the idle timeout's and `reset`'s. That takes
3176
- * in both sides' stateless reset tokens and `TlsServer`'s expected client
3177
- * Finished.
3879
+ * level: `release()`'s work, the idle timeout's, the end of the closing or
3880
+ * draining period's and `reset`'s. That takes in both sides' stateless
3881
+ * reset tokens and `TlsServer`'s expected client Finished and resumption
3882
+ * secret.
3178
3883
  */
3179
3884
  wipeAll(): void {
3180
3885
  this.discard(this.initial)
@@ -3184,11 +3889,11 @@ export class QuicConnection {
3184
3889
  this.otherReadKeys = null
3185
3890
  this.otherIsNext = false
3186
3891
  this.keyRetainUntil = -1
3187
- secureZero(this.appReadSecret)
3188
- secureZero(this.appWriteSecret)
3189
- secureZero(this.nextReadSecret)
3892
+ this.closingUntil = -1
3893
+ this.wipe()
3894
+ secureZero(this.initialClient)
3895
+ secureZero(this.initialServer)
3190
3896
  secureZero(this.cidSeed)
3191
- secureZero(this.ephemeralPrivate)
3192
3897
  // The stateless reset tokens of both sides' IDs: either one ends the
3193
3898
  // connection for whoever learns it (RFC 9000 §10.3).
3194
3899
  for (const entry of this.cids.local) {
@@ -3198,17 +3903,28 @@ export class QuicConnection {
3198
3903
  secureZero(entry.resetToken)
3199
3904
  }
3200
3905
  secureZero(this.peerParameters.statelessResetToken)
3906
+ secureZero(this.localParameters.statelessResetToken)
3201
3907
  const tls: TlsServer | null = this.tls
3202
3908
  if (tls !== null) {
3203
3909
  // The server's encoded transport parameters carry its first ID's token.
3204
3910
  secureZero(tls.config.quicTransportParameters)
3205
- secureZero(tls.expectedClientFinished)
3206
- secureZero(tls.handshakeSecret)
3207
- secureZero(tls.clientHandshakeSecret)
3208
- secureZero(tls.serverHandshakeSecret)
3209
- secureZero(tls.clientApplicationSecret)
3210
- secureZero(tls.serverApplicationSecret)
3211
- secureZero(tls.exporterSecret)
3911
+ tls.wipe()
3212
3912
  }
3213
3913
  }
3914
+
3915
+ /**
3916
+ * Wipes the connection's `Secret` fields: the ephemeral key and both
3917
+ * widths' 1-RTT read, next read and write secrets. `wipeAll` calls it,
3918
+ * and so `release()`, `reset`, the idle timeout and the end of the
3919
+ * closing or draining period.
3920
+ */
3921
+ wipe(): void {
3922
+ wipe(this.ephemeral)
3923
+ wipe(this.readSecret256)
3924
+ wipe(this.nextReadSecret256)
3925
+ wipe(this.writeSecret256)
3926
+ wipe(this.readSecret384)
3927
+ wipe(this.nextReadSecret384)
3928
+ wipe(this.writeSecret384)
3929
+ }
3214
3930
  }