@amritk/nish-x86_64-linux 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +43 -34
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +117 -8
- package/runtime/nish.h +50 -13
- package/runtime/nish.mjs +71 -7
- package/runtime/runtime-host.c +43 -24
- package/runtime/runtime-net.c +57 -10
- package/runtime/runtime-os.c +62 -20
- package/runtime/runtime-simd.c +203 -0
- package/runtime/runtime-wasm.c +12 -0
- package/runtime/runtime.c +66 -18
- package/runtime/shim.mjs +114 -5
- package/scripts/build.sh +14 -12
- package/std/README.md +168 -36
- package/std/crypto/aes.ts +51 -8
- package/std/crypto/base64url.ts +25 -12
- package/std/crypto/chacha20poly1305.ts +43 -29
- package/std/crypto/ct.ts +6 -1
- package/std/crypto/hkdf.ts +364 -3
- package/std/crypto/hmac.ts +307 -7
- package/std/crypto/p256.ts +618 -34
- package/std/crypto/sha1.ts +147 -0
- package/std/crypto/sha256.ts +3 -2
- package/std/crypto/x25519.ts +88 -36
- package/std/crypto/x509.ts +130 -47
- package/std/json.ts +297 -41
- package/std/net/hpack.ts +1094 -0
- package/std/net/http-fields.ts +604 -0
- package/std/net/http1-server.ts +1389 -0
- package/std/net/http1.ts +1492 -0
- package/std/net/http2-frame.ts +745 -0
- package/std/net/http2-tls.ts +216 -0
- package/std/net/http2.ts +1433 -0
- package/std/net/http3-frame.ts +457 -0
- package/std/net/http3-server.ts +911 -0
- package/std/net/http3.ts +2145 -0
- package/std/net/qpack.ts +1163 -0
- package/std/net/quic-conn-ack.ts +206 -0
- package/std/net/quic-conn-cid.ts +425 -0
- package/std/net/quic-conn-params.ts +462 -0
- package/std/net/quic-datagram.ts +130 -0
- package/std/net/quic-frame.ts +1206 -0
- package/std/net/quic-listener.ts +929 -0
- package/std/net/quic-packet.ts +1698 -0
- package/std/net/quic-recovery.ts +849 -0
- package/std/net/quic-stream.ts +1609 -0
- package/std/net/quic.ts +3578 -0
- package/std/net/tls/codec.ts +1089 -0
- package/std/net/tls/record-server.ts +1173 -0
- package/std/net/tls/record.ts +621 -0
- package/std/net/tls/schedule.ts +429 -0
- package/std/net/tls-tcp.ts +414 -0
- package/std/net/tls.ts +1437 -0
- package/std/net/websocket.ts +823 -0
- package/std/net/webtransport.ts +1334 -0
- package/std/secret.ts +62 -0
- package/std/text.ts +77 -0
package/std/net/quic.ts
ADDED
|
@@ -0,0 +1,3578 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/net/quic` — the server side of a QUIC version 1 connection (RFC 9000,
|
|
3
|
+
* RFC 9001, RFC 9221): the TLS 1.3 handshake carried in CRYPTO frames at
|
|
4
|
+
* three encryption levels, transport parameters both ways, acknowledgements
|
|
5
|
+
* per packet number space, the connection-ID table, streams both ways
|
|
6
|
+
* (`nish/net/quic-stream`) and DATAGRAM frames once the handshake is done.
|
|
7
|
+
* Sans-IO, like the rest of `nish/net`.
|
|
8
|
+
*
|
|
9
|
+
* import { QuicConnection, QuicServerConfig } from "nish/net/quic";
|
|
10
|
+
*
|
|
11
|
+
* const conn = new QuicConnection(config, entropy); // entropy: QUIC_CONN_ENTROPY_SIZE random bytes
|
|
12
|
+
* conn.receiveWindow(buf, off, len, now); // every datagram the client sends; now in ms
|
|
13
|
+
* const input: u8[] | null = conn.signatureInput();
|
|
14
|
+
* if (input !== null) { conn.sign(tlsSignEcdsaP256(key, input)); }
|
|
15
|
+
* let n: i32 = conn.takeDatagramInto(out, 0, now); // out: at least QUIC_CONN_DATAGRAM_SIZE bytes
|
|
16
|
+
* while (n > 0) { … send out[0 .. n) to the client … ; n = conn.takeDatagramInto(out, 0, now); }
|
|
17
|
+
* let id: i64 = conn.nextStreamEvent(); // a stream with news: read it, write it
|
|
18
|
+
* … conn.streamRead(id, buf, 0, size) / conn.streamWrite(id, data, 0, n, fin) …
|
|
19
|
+
* … and at conn.deadline(), conn.handleTimer(now); once done, conn.reset(entropy) for the next peer
|
|
20
|
+
*
|
|
21
|
+
* **What it is.** One connection, from the client's first Initial to a
|
|
22
|
+
* CONNECTION_CLOSE either way. The handshake is `nish/net/tls`'s `TlsServer`,
|
|
23
|
+
* driven over QUIC as RFC 9001 §4 describes: the bytes it writes at each
|
|
24
|
+
* level go out in CRYPTO frames of the matching packet number space, the
|
|
25
|
+
* bytes the client sends in CRYPTO frames are put back in order and handed to
|
|
26
|
+
* it, and each level's packet keys are made with `nish/net/quic-packet`'s
|
|
27
|
+
* `quicKeys` as soon as `TlsServer` knows the secret. The server's transport
|
|
28
|
+
* parameters, `original_destination_connection_id` and
|
|
29
|
+
* `initial_source_connection_id` among them, go into EncryptedExtensions; the
|
|
30
|
+
* client's are parsed and checked (RFC 9000 §7.3, §7.4) before the server's
|
|
31
|
+
* flight leaves.
|
|
32
|
+
*
|
|
33
|
+
* **Streams** are `nish/net/quic-stream`'s: both kinds, opened by either
|
|
34
|
+
* side, with flow control per stream and for the connection raised as the
|
|
35
|
+
* application reads, and the stream limits raised as streams finish. The
|
|
36
|
+
* application sees them through a small surface: `nextStreamEvent` names a
|
|
37
|
+
* stream with something new (opened, readable, finished, reset, stopped, or
|
|
38
|
+
* writable again), `streamRead` and `streamWrite` move bytes a chunk at a
|
|
39
|
+
* time with back-pressure, `openStream` opens one of the server's,
|
|
40
|
+
* `streamReset` and `streamStopSending` abandon a side. `readStream` and
|
|
41
|
+
* `writeStream` are the all-at-once forms, which allocate.
|
|
42
|
+
*
|
|
43
|
+
* **Datagrams** (RFC 9221). With `maxDatagramFrameSize` above 0 the server
|
|
44
|
+
* advertises `max_datagram_frame_size` and takes DATAGRAM frames up to it; a
|
|
45
|
+
* DATAGRAM frame it did not offer to take, or one larger, is
|
|
46
|
+
* PROTOCOL_VIOLATION. `sendDatagram` queues a payload when the client
|
|
47
|
+
* offered to take one that size and it fits a packet; it goes out within the
|
|
48
|
+
* congestion window, and is never sent again. `readDatagram` hands out what
|
|
49
|
+
* arrived. Each way is a fixed ring (`nish/net/quic-datagram`).
|
|
50
|
+
*
|
|
51
|
+
* **Loss recovery** (RFC 9002) is `nish/net/quic-recovery`'s: every
|
|
52
|
+
* ack-eliciting packet is recorded in its space's ring with what it carried
|
|
53
|
+
* (its CRYPTO range, its STREAM chunks, HANDSHAKE_DONE and the control
|
|
54
|
+
* frames), the client's ACKs give the RTT estimate and show what was lost by
|
|
55
|
+
* packet or time threshold, and what a lost packet carried is sent again,
|
|
56
|
+
* from the CRYPTO bytes a level keeps until it is discarded and the bytes a
|
|
57
|
+
* stream keeps until they are acknowledged; a control frame goes again with
|
|
58
|
+
* its current value. When nothing is acknowledged for a probe timeout, with
|
|
59
|
+
* its backoff, everything in flight is queued again and a probe goes out in
|
|
60
|
+
* each space that has packets in flight, a PING if there is nothing to
|
|
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`.
|
|
70
|
+
*
|
|
71
|
+
* **Nothing allocated per packet** (QUIC-3). A connection is a slot: every
|
|
72
|
+
* buffer it uses — the packet record, the stream buffers, the CRYPTO
|
|
73
|
+
* reassembly, the connection-ID table, the datagram rings — is made by the
|
|
74
|
+
* constructor and reused, and `reset` makes the slot a new connection for
|
|
75
|
+
* the next peer. A packet is opened (`receiveWindow`) and a datagram built
|
|
76
|
+
* and sealed (`takeDatagramInto`) inside a `using a = arena()` block, so the
|
|
77
|
+
* AEAD's and the header protection's temporaries go when the block ends, and
|
|
78
|
+
* the compiler refuses the block if anything in it could store one. The
|
|
79
|
+
* handshake keeps its state in the slot too: one `TlsServer`, made by the
|
|
80
|
+
* slot's first handshake and `restart`ed by each after it; every level's
|
|
81
|
+
* packet keys, and a key update's, derived in place into `QuicKeysSlot`s
|
|
82
|
+
* (`nish/net/quic-packet`) over the slot's HKDF scratch; the Initial
|
|
83
|
+
* secrets, the stateless reset tokens and the connection IDs computed in
|
|
84
|
+
* arena blocks; the transport parameters encoded and parsed into the
|
|
85
|
+
* slot's own objects. After the first connection in a slot, a connection
|
|
86
|
+
* keeps nothing at all (QUIC-3 in `docs/security/quic.md`).
|
|
87
|
+
*
|
|
88
|
+
* **Time.** The connection has no clock: `receive`, `takeDatagram` and
|
|
89
|
+
* `handleTimer` take the caller's monotonic time in milliseconds, and
|
|
90
|
+
* `deadline()` says when `handleTimer` is next due. Three things run on it.
|
|
91
|
+
* The idle timeout (RFC 9000 §10.1) is the smaller of the two sides'
|
|
92
|
+
* `max_idle_timeout`, never under `QUIC_CONN_IDLE_FLOOR` or three probe
|
|
93
|
+
* timeouts; once it passes with nothing received, the connection closes
|
|
94
|
+
* silently (`QUIC_STATE_TIMED_OUT`) and wipes its keys. Loss recovery's
|
|
95
|
+
* timer declares packets lost by the time threshold or fires the probe
|
|
96
|
+
* timeout. And after a key update the previous read keys are kept for a
|
|
97
|
+
* probe timeout (`recovery.probeTimeout()`), for packets the network
|
|
98
|
+
* reordered, before the next generation's are derived (RFC 9001 §6.5).
|
|
99
|
+
*
|
|
100
|
+
* **Key update** (RFC 9001 §6), both ways. A 1-RTT packet whose Key Phase
|
|
101
|
+
* bit differs from the current one is opened with the next generation's read
|
|
102
|
+
* keys, which are derived ahead of time so that a forged bit costs no
|
|
103
|
+
* derivation and shows no timing difference (§6.3); if it opens, the client
|
|
104
|
+
* has updated, and the server's write keys follow before anything is
|
|
105
|
+
* acknowledged (§6.2). `updateKeys()` starts an update from the server once
|
|
106
|
+
* the client has acknowledged a packet of the current phase (§6.1). The
|
|
107
|
+
* generations take turns in two key slots and two secrets a direction, so an
|
|
108
|
+
* update keeps nothing in the arena; each still costs two key derivations of
|
|
109
|
+
* work, so a connection takes at most `QUIC_CONN_MAX_KEY_UPDATES` updates the
|
|
110
|
+
* client starts and closes on the next with KEY_UPDATE_ERROR (QUIC-4).
|
|
111
|
+
*
|
|
112
|
+
* **Sans-IO and deterministic.** No socket, no clock and no random device:
|
|
113
|
+
* every random choice (the TLS server random and ephemeral key, the server's
|
|
114
|
+
* first connection ID, and the seed later IDs are derived from) comes from
|
|
115
|
+
* the `entropy` the constructor and `reset` take, every stateless reset
|
|
116
|
+
* token from the configuration's static key (RFC 9000 §10.3.2), and every
|
|
117
|
+
* ACK says a delay of zero. So a recorded exchange replays byte for byte.
|
|
118
|
+
*
|
|
119
|
+
* **What a peer cannot do.** Nothing it sends makes this module panic. A
|
|
120
|
+
* datagram that does not parse, a packet that does not authenticate, a
|
|
121
|
+
* duplicate packet number, a packet for keys already discarded and a 1-RTT
|
|
122
|
+
* packet before the handshake is done are dropped and counted in `dropped`.
|
|
123
|
+
* A frame that breaks the protocol closes the connection with the transport
|
|
124
|
+
* error RFC 9000 names (`QUIC_ERROR_*` in `nish/net/quic-frame`), a TLS alert
|
|
125
|
+
* closes it with CRYPTO_ERROR (0x100 + the alert), and the CONNECTION_CLOSE
|
|
126
|
+
* is the next datagram out. Before the client's address is validated — by a
|
|
127
|
+
* Handshake packet, or by a Retry token `nish/net/quic-listener` checked —
|
|
128
|
+
* the server sends at most three times what it received (§8.1). Every buffer
|
|
129
|
+
* a peer can fill is fixed: CRYPTO reassembly by `QUIC_CONN_CRYPTO_WINDOW`,
|
|
130
|
+
* each stream by the credit advertised (its buffer), the connection by
|
|
131
|
+
* `maxData`, the connection-ID table by the limit advertised, the received
|
|
132
|
+
* packet numbers by `QUIC_ACK_MAX_RANGES`, and datagrams by their ring.
|
|
133
|
+
*
|
|
134
|
+
* **Secrets.** The packet keys of each level live in this connection's key
|
|
135
|
+
* slots as long as the level does, and in `TlsServer`'s (TLS-1); so do the
|
|
136
|
+
* 1-RTT secrets the next key generation is derived from, and the next
|
|
137
|
+
* generation's read keys. A `Secret` may not be a field (NL2430), so they are
|
|
138
|
+
* plain bytes; `secureZero` wipes each level's key, IV and header-protection
|
|
139
|
+
* key, and its AES schedules with ordinary stores, when the level is
|
|
140
|
+
* discarded, each generation's key, IV and secret when a key update
|
|
141
|
+
* replaces it, and `release()` (and `reset`) the rest — the 1-RTT keys, the
|
|
142
|
+
* key-update secrets, the traffic secrets `TlsServer` holds, its ephemeral
|
|
143
|
+
* key and the connection-ID seed. What no wipe reaches (`aesKeyInto`'s
|
|
144
|
+
* working copy of a schedule) is recorded as QUIC-2 in
|
|
145
|
+
* `docs/security/quic.md`, with #430, the follow-up that moves these structs
|
|
146
|
+
* onto `nish:secret`.
|
|
147
|
+
*
|
|
148
|
+
* Written from RFC 9000, RFC 9001 and RFC 9221, in this module's own
|
|
149
|
+
* structure; nothing here is ported from another implementation. Private
|
|
150
|
+
* names carry the `quicConn` prefix (`docs/wp26-stdlib.md` §3e).
|
|
151
|
+
*/
|
|
152
|
+
import { timingSafeEqualAt } from "nish/crypto/ct"
|
|
153
|
+
import { HkdfScratch } from "nish/crypto/hkdf"
|
|
154
|
+
import { HmacSha256Scratch, hmacSha256 } from "nish/crypto/hmac"
|
|
155
|
+
import {
|
|
156
|
+
QUIC_AEAD_AES_128_GCM,
|
|
157
|
+
QUIC_AEAD_AES_256_GCM,
|
|
158
|
+
QUIC_AEAD_CHACHA20_POLY1305,
|
|
159
|
+
QUIC_AEAD_TAG_SIZE,
|
|
160
|
+
QUIC_ERR_RESERVED_BITS,
|
|
161
|
+
QUIC_MAX_CID_LENGTH,
|
|
162
|
+
QUIC_MAX_VARINT,
|
|
163
|
+
QUIC_PACKET_HANDSHAKE,
|
|
164
|
+
QUIC_PACKET_INITIAL,
|
|
165
|
+
QUIC_PACKET_OK,
|
|
166
|
+
QUIC_PACKET_SHORT,
|
|
167
|
+
QuicHeader,
|
|
168
|
+
QuicKeys,
|
|
169
|
+
QuicKeysSlot,
|
|
170
|
+
QuicPacket,
|
|
171
|
+
quicDecryptPayload,
|
|
172
|
+
quicInitialSecretsInto,
|
|
173
|
+
quicKeyUpdateSecretInto,
|
|
174
|
+
quicKeysInto,
|
|
175
|
+
quicKeysUpdateInto,
|
|
176
|
+
quicPacketNumberLength,
|
|
177
|
+
quicParseHeaderInto,
|
|
178
|
+
quicPutLongHeader,
|
|
179
|
+
quicPutShortHeader,
|
|
180
|
+
quicSealInPlace,
|
|
181
|
+
quicUnprotectHeader,
|
|
182
|
+
} from "nish/net/quic-packet"
|
|
183
|
+
import {
|
|
184
|
+
QUIC_ERROR_APPLICATION,
|
|
185
|
+
QUIC_ERROR_CRYPTO,
|
|
186
|
+
QUIC_ERROR_CRYPTO_BUFFER_EXCEEDED,
|
|
187
|
+
QUIC_ERROR_INTERNAL,
|
|
188
|
+
QUIC_ERROR_KEY_UPDATE,
|
|
189
|
+
QUIC_ERROR_NO_ERROR,
|
|
190
|
+
QUIC_ERROR_PROTOCOL_VIOLATION,
|
|
191
|
+
QUIC_ERROR_TRANSPORT_PARAMETER,
|
|
192
|
+
QUIC_FRAME_ACK,
|
|
193
|
+
QUIC_FRAME_ACK_ECN,
|
|
194
|
+
QUIC_FRAME_CONNECTION_CLOSE,
|
|
195
|
+
QUIC_FRAME_CONNECTION_CLOSE_APP,
|
|
196
|
+
QUIC_FRAME_CRYPTO,
|
|
197
|
+
QUIC_FRAME_DATAGRAM,
|
|
198
|
+
QUIC_FRAME_DATAGRAM_LENGTH,
|
|
199
|
+
QUIC_FRAME_HANDSHAKE_DONE,
|
|
200
|
+
QUIC_FRAME_MAX_DATA,
|
|
201
|
+
QUIC_FRAME_MAX_STREAM_DATA,
|
|
202
|
+
QUIC_FRAME_MAX_STREAMS_BIDI,
|
|
203
|
+
QUIC_FRAME_MAX_STREAMS_UNI,
|
|
204
|
+
QUIC_FRAME_NEW_CONNECTION_ID,
|
|
205
|
+
QUIC_FRAME_NEW_TOKEN,
|
|
206
|
+
QUIC_FRAME_PATH_CHALLENGE,
|
|
207
|
+
QUIC_FRAME_PATH_RESPONSE,
|
|
208
|
+
QUIC_FRAME_PING,
|
|
209
|
+
QUIC_FRAME_RESET_STREAM,
|
|
210
|
+
QUIC_FRAME_RETIRE_CONNECTION_ID,
|
|
211
|
+
QUIC_FRAME_STOP_SENDING,
|
|
212
|
+
QUIC_FRAME_STREAM,
|
|
213
|
+
QUIC_FRAME_STREAM_DATA_BLOCKED,
|
|
214
|
+
QUIC_PATH_DATA_SIZE,
|
|
215
|
+
QUIC_RESET_TOKEN_SIZE,
|
|
216
|
+
QuicFrame,
|
|
217
|
+
quicCryptoOverhead,
|
|
218
|
+
quicDatagramSize,
|
|
219
|
+
quicFrameAckEliciting,
|
|
220
|
+
quicFrameAllowed,
|
|
221
|
+
quicParseFrame,
|
|
222
|
+
quicPutConnectionClose,
|
|
223
|
+
quicPutCrypto,
|
|
224
|
+
quicPutNewConnectionId,
|
|
225
|
+
quicPutPadding,
|
|
226
|
+
quicPutPathData,
|
|
227
|
+
quicPutTypeOnly,
|
|
228
|
+
quicPutValue,
|
|
229
|
+
} from "nish/net/quic-frame"
|
|
230
|
+
import {
|
|
231
|
+
QuicTransportParameters,
|
|
232
|
+
quicEncodeTransportParametersInto,
|
|
233
|
+
quicParseTransportParametersInto,
|
|
234
|
+
} from "nish/net/quic-conn-params"
|
|
235
|
+
import { QuicAckRanges } from "nish/net/quic-conn-ack"
|
|
236
|
+
import {
|
|
237
|
+
QUIC_RECOVERY_APPLICATION_CAPACITY,
|
|
238
|
+
QUIC_RECOVERY_HANDSHAKE_CAPACITY,
|
|
239
|
+
QUIC_RECOVERY_TIMEOUT_LOSS,
|
|
240
|
+
QUIC_RECOVERY_TIMEOUT_PTO,
|
|
241
|
+
QuicRecovery,
|
|
242
|
+
QuicSentPackets,
|
|
243
|
+
} from "nish/net/quic-recovery"
|
|
244
|
+
import { QuicCidEntry, QuicCidTable, quicCidCopy } from "nish/net/quic-conn-cid"
|
|
245
|
+
import {
|
|
246
|
+
QUIC_SEND_RESET_SENT,
|
|
247
|
+
QUIC_STREAM_END,
|
|
248
|
+
QUIC_STREAM_ERR_FINISHED,
|
|
249
|
+
QUIC_STREAM_ERR_FLOW,
|
|
250
|
+
QUIC_STREAM_ERR_STATE,
|
|
251
|
+
QUIC_STREAM_ERR_UNKNOWN,
|
|
252
|
+
QUIC_STREAM_OK,
|
|
253
|
+
QuicStream,
|
|
254
|
+
QuicStreams,
|
|
255
|
+
quicStreamBit,
|
|
256
|
+
quicStreamSetBit,
|
|
257
|
+
} from "nish/net/quic-stream"
|
|
258
|
+
import { QuicDatagramQueue } from "nish/net/quic-datagram"
|
|
259
|
+
import {
|
|
260
|
+
TLS_LEVEL_APPLICATION,
|
|
261
|
+
TLS_LEVEL_HANDSHAKE,
|
|
262
|
+
TLS_LEVEL_INITIAL,
|
|
263
|
+
TLS_STATE_CONNECTED,
|
|
264
|
+
TLS_STATE_WAIT_CLIENT_HELLO,
|
|
265
|
+
TLS_STATE_WAIT_SIGNATURE,
|
|
266
|
+
TlsServer,
|
|
267
|
+
TlsServerConfig,
|
|
268
|
+
} from "nish/net/tls"
|
|
269
|
+
import { TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256 } from "nish/net/tls/schedule"
|
|
270
|
+
|
|
271
|
+
// ---- Sizes ----------------------------------------------------------------------
|
|
272
|
+
|
|
273
|
+
/** The length of every connection ID this server issues; a short header's DCID is read at this length. */
|
|
274
|
+
export const QUIC_CONN_CID_LENGTH: i32 = 8
|
|
275
|
+
/**
|
|
276
|
+
* The largest datagram the server sends, and the smallest a client's first
|
|
277
|
+
* Initial may arrive in (RFC 9000 §14.1): 1200 bytes, which every QUIC path
|
|
278
|
+
* carries, so no path MTU discovery is needed. `takeDatagramInto` needs this
|
|
279
|
+
* much room.
|
|
280
|
+
*/
|
|
281
|
+
export const QUIC_CONN_DATAGRAM_SIZE: i32 = 1200
|
|
282
|
+
/**
|
|
283
|
+
* How many random bytes the constructor and `reset` take: 32 for the TLS
|
|
284
|
+
* server random, 32 for the x25519 key, 8 for the first connection ID and
|
|
285
|
+
* 32 for the seed the later IDs and their reset tokens are derived from.
|
|
286
|
+
*/
|
|
287
|
+
export const QUIC_CONN_ENTROPY_SIZE: i32 = 104
|
|
288
|
+
/**
|
|
289
|
+
* How far ahead of what it has handed TLS the server buffers CRYPTO data.
|
|
290
|
+
* RFC 9000 §7.5 asks for at least 4096 bytes; a frame past this is
|
|
291
|
+
* CRYPTO_BUFFER_EXCEEDED. One buffer of this size serves every level, since
|
|
292
|
+
* the client's Initial flight is complete before its Handshake one starts.
|
|
293
|
+
*/
|
|
294
|
+
export const QUIC_CONN_CRYPTO_WINDOW: i32 = 16384
|
|
295
|
+
/** The most connection IDs the server keeps active for the client to use, its own first one included. */
|
|
296
|
+
export const QUIC_CONN_LOCAL_CIDS: i32 = 4
|
|
297
|
+
/** The largest per-stream credit a configuration may advertise, which is also each stream's buffer each way. */
|
|
298
|
+
export const QUIC_CONN_MAX_STREAM_DATA: i64 = 1048576
|
|
299
|
+
/** The most streams of one kind a configuration may let the client open at once, or open itself. */
|
|
300
|
+
export const QUIC_CONN_MAX_STREAMS: i64 = 1024
|
|
301
|
+
/**
|
|
302
|
+
* The most bytes of stream buffer a configuration may make each connection
|
|
303
|
+
* hold: every stream slot (`maxStreamsBidi + maxStreamsUni + localStreams`)
|
|
304
|
+
* has `maxStreamData` bytes each way, made when the connection is, so a
|
|
305
|
+
* configuration past 64 MiB of them is refused rather than allocated.
|
|
306
|
+
*/
|
|
307
|
+
export const QUIC_CONN_MAX_STREAM_BUFFERS: i64 = 67108864
|
|
308
|
+
/** The length of the configuration's static keys: the stateless reset key and the Retry token key. */
|
|
309
|
+
export const QUIC_CONN_STATIC_KEY_SIZE: i32 = 32
|
|
310
|
+
/**
|
|
311
|
+
* The shortest idle timeout the server keeps: three times the probe timeout
|
|
312
|
+
* of a path with no RTT sample yet (RFC 9002 §6.2.2, about a second), and
|
|
313
|
+
* never under three times the current one either. RFC 9000 §10.1 has an
|
|
314
|
+
* endpoint raise a smaller negotiated value to this, so that several probes
|
|
315
|
+
* can be lost before the connection is given up.
|
|
316
|
+
*/
|
|
317
|
+
export const QUIC_CONN_IDLE_FLOOR: i64 = 3000
|
|
318
|
+
/**
|
|
319
|
+
* The most STREAM frames one packet carries, which is how many chunks a
|
|
320
|
+
* packet's record keeps for retransmission; the rest of the streams go in
|
|
321
|
+
* the next packet.
|
|
322
|
+
*/
|
|
323
|
+
export const QUIC_CONN_PACKET_STREAMS: i32 = 4
|
|
324
|
+
/**
|
|
325
|
+
* The most control frames one packet carries — NEW_CONNECTION_ID,
|
|
326
|
+
* RETIRE_CONNECTION_ID and the flow-control, limit and stream-ending frames
|
|
327
|
+
* of `nish/net/quic-stream` — for the same reason: the three new IDs the
|
|
328
|
+
* server announces once the handshake is done fit one packet, and the rest
|
|
329
|
+
* go over the next few.
|
|
330
|
+
*/
|
|
331
|
+
export const QUIC_CONN_PACKET_CONTROL: i32 = 4
|
|
332
|
+
/**
|
|
333
|
+
* How many key updates a connection takes from its client (RFC 9001 §6).
|
|
334
|
+
* Each costs two key derivations of work, though none of their memory, so
|
|
335
|
+
* the cap bounds what a client can make the server derive (QUIC-4); the next one
|
|
336
|
+
* closes the connection with KEY_UPDATE_ERROR. A client also has to wait
|
|
337
|
+
* a probe timeout between two of them.
|
|
338
|
+
*/
|
|
339
|
+
export const QUIC_CONN_MAX_KEY_UPDATES: i32 = 64
|
|
340
|
+
/**
|
|
341
|
+
* How many ack-eliciting datagrams a probe timeout sends in the Initial and
|
|
342
|
+
* Handshake spaces (RFC 9002 §6.2.4 allows up to two). The server's first
|
|
343
|
+
* flight is usually one datagram, so with one probe a lossy path loses every
|
|
344
|
+
* copy of it about as often as it loses one datagram per probe timeout; two
|
|
345
|
+
* square that, and stay inside the amplification limit, which a one-datagram
|
|
346
|
+
* flight and two probes fill exactly. The Application Data space sends one.
|
|
347
|
+
*/
|
|
348
|
+
export const QUIC_CONN_HANDSHAKE_PROBES: i32 = 2
|
|
349
|
+
/**
|
|
350
|
+
* How many times a connection resends its handshake data before the probe
|
|
351
|
+
* timeout because the client showed it lacks it (RFC 9002 §6.2.3, "for a
|
|
352
|
+
* limited number of times per connection"): a client Initial carrying
|
|
353
|
+
* CRYPTO data the server already read, or an ack-eliciting Handshake packet,
|
|
354
|
+
* while the server's own CRYPTO data at that level is in flight. Each
|
|
355
|
+
* resend is one datagram, so a client cannot make the server answer more
|
|
356
|
+
* than this many packets that way, whatever it sends.
|
|
357
|
+
*/
|
|
358
|
+
export const QUIC_CONN_EARLY_RESENDS: i32 = 2
|
|
359
|
+
/**
|
|
360
|
+
* How many connection IDs a connection issues in all, its first included.
|
|
361
|
+
* The client retiring one asks for a replacement (§5.1.1, a SHOULD), and
|
|
362
|
+
* each costs two HMACs whose temporaries stay in the arena, so this bounds
|
|
363
|
+
* what a client can make the server derive by retiring IDs over and over;
|
|
364
|
+
* past it, a retired ID is not replaced.
|
|
365
|
+
*/
|
|
366
|
+
export const QUIC_CONN_MAX_ISSUED_CIDS: i64 = 64
|
|
367
|
+
/** How many DATAGRAM payloads each way a connection holds, when it takes any. */
|
|
368
|
+
export const QUIC_CONN_DATAGRAM_QUEUE: i32 = 8
|
|
369
|
+
/**
|
|
370
|
+
* The largest `maxDatagramFrameSize` a configuration may advertise: 1500
|
|
371
|
+
* bytes, an Ethernet path's whole UDP payload, past which no DATAGRAM frame
|
|
372
|
+
* reaches a server on a real path. Each of the receive ring's entries is
|
|
373
|
+
* sized to what is advertised, so every frame taken is held.
|
|
374
|
+
*/
|
|
375
|
+
export const QUIC_CONN_MAX_DATAGRAM_FRAME: i64 = 1500
|
|
376
|
+
|
|
377
|
+
// ---- States ---------------------------------------------------------------------
|
|
378
|
+
|
|
379
|
+
/** Waiting for the client's first Initial packet. */
|
|
380
|
+
export const QUIC_STATE_WAIT_INITIAL: i32 = 0
|
|
381
|
+
/** The handshake is under way. */
|
|
382
|
+
export const QUIC_STATE_HANDSHAKE: i32 = 1
|
|
383
|
+
/** The handshake is complete and confirmed; stream data flows. */
|
|
384
|
+
export const QUIC_STATE_CONNECTED: i32 = 2
|
|
385
|
+
/** This side closed the connection: one CONNECTION_CLOSE goes out, then nothing. `error` says why. */
|
|
386
|
+
export const QUIC_STATE_CLOSING: i32 = 3
|
|
387
|
+
/** The client closed the connection: nothing more goes out (RFC 9000 §10.2.2). `error` is its code. */
|
|
388
|
+
export const QUIC_STATE_DRAINING: i32 = 4
|
|
389
|
+
/**
|
|
390
|
+
* The idle timeout passed (RFC 9000 §10.1): the connection closed silently,
|
|
391
|
+
* its keys wiped, and nothing more goes out. Its state is to be discarded:
|
|
392
|
+
* drop the connection (or `reset` the slot), so that a later packet to its
|
|
393
|
+
* IDs reaches `nish/net/quic-listener`, whose stateless reset tells the client.
|
|
394
|
+
*/
|
|
395
|
+
export const QUIC_STATE_TIMED_OUT: i32 = 5
|
|
396
|
+
|
|
397
|
+
// ---- What the datagram calls answer ---------------------------------------------
|
|
398
|
+
|
|
399
|
+
/** `sendDatagram` queued the payload. */
|
|
400
|
+
export const QUIC_DATAGRAM_OK: i32 = 0
|
|
401
|
+
/** `readDatagram`: nothing has arrived. */
|
|
402
|
+
export const QUIC_DATAGRAM_NONE: i32 = -1
|
|
403
|
+
/** `readDatagram`: the next datagram does not fit in the room given; it is kept. */
|
|
404
|
+
export const QUIC_DATAGRAM_ERR_ROOM: i32 = -2
|
|
405
|
+
/** `sendDatagram`: the client did not offer to take DATAGRAM frames (RFC 9221 §3), or the connection is not connected. */
|
|
406
|
+
export const QUIC_DATAGRAM_ERR_DISABLED: i32 = -3
|
|
407
|
+
/** `sendDatagram`: the payload is larger than the client takes, or than fits one packet (`maxDatagramPayload`). */
|
|
408
|
+
export const QUIC_DATAGRAM_ERR_TOO_BIG: i32 = -4
|
|
409
|
+
/** `sendDatagram`: the send ring is full; try again once a datagram has gone. */
|
|
410
|
+
export const QUIC_DATAGRAM_ERR_FULL: i32 = -5
|
|
411
|
+
|
|
412
|
+
/** A typed zero for the offsets below: a bare literal is an `f64` under `--number-mode f64`. */
|
|
413
|
+
const QUIC_CONN_FROM: i32 = 0
|
|
414
|
+
/** A space's key slots (`QuicConnSpace.keySlot`): the read keys', the write keys', and where Application Data's write keys start. */
|
|
415
|
+
const QUIC_CONN_READ_SLOT: i32 = 0
|
|
416
|
+
const QUIC_CONN_WRITE_SLOT: i32 = 1
|
|
417
|
+
const QUIC_CONN_APP_WRITE_SLOTS: i32 = 2
|
|
418
|
+
/** The same for the `i64` arguments of the methods below, where a literal is not given its parameter's type. */
|
|
419
|
+
const QUIC_CONN_NONE: i64 = 0
|
|
420
|
+
/** TLS's unexpected_message alert, which CRYPTO data at a level TLS is not reading is (RFC 8446 §6). */
|
|
421
|
+
const QUIC_CONN_ALERT_UNEXPECTED: i64 = 10
|
|
422
|
+
|
|
423
|
+
/** A packet's record: the bit that says it carried HANDSHAKE_DONE, and the kinds of control frame of its own. */
|
|
424
|
+
const QUIC_CONN_SENT_HANDSHAKE_DONE: i32 = 1
|
|
425
|
+
const QUIC_CONN_CONTROL_NEW_CID: i32 = 1
|
|
426
|
+
const QUIC_CONN_CONTROL_RETIRE: i32 = 2
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* What a QUIC server is configured with, the same for every connection. The
|
|
430
|
+
* limits are the transport parameters it advertises (RFC 9000 §18.2); the
|
|
431
|
+
* stream limits and `maxStreamData` also size each connection's buffers,
|
|
432
|
+
* which are made once and reused.
|
|
433
|
+
*/
|
|
434
|
+
export interface QuicServerConfig {
|
|
435
|
+
/** DER certificates, leaf first, as `TlsServerConfig` takes them. */
|
|
436
|
+
certificateChain: u8[][]
|
|
437
|
+
/** The ALPN protocols the server speaks, most preferred first; QUIC requires one to match (RFC 9001 §8.1). */
|
|
438
|
+
alpn: string[]
|
|
439
|
+
/** The signature scheme the caller's key signs with, as `TlsServerConfig` takes it. */
|
|
440
|
+
signatureScheme: i32
|
|
441
|
+
/** Whether `nish/net/quic-listener` answers a client's first Initial with a Retry (§8.1.2). */
|
|
442
|
+
retry: boolean
|
|
443
|
+
/** `initial_max_data`: the connection's receive window, 0 to 2^62 − 1, raised as the application reads. */
|
|
444
|
+
maxData: i64
|
|
445
|
+
/**
|
|
446
|
+
* `initial_max_stream_data_bidi_remote` (and `_uni`, and `_bidi_local`
|
|
447
|
+
* when the server opens streams): each stream's receive window and the
|
|
448
|
+
* size of its buffer each way, 1 to 1 MiB.
|
|
449
|
+
*/
|
|
450
|
+
maxStreamData: i64
|
|
451
|
+
/** `initial_max_streams_bidi`: how many bidirectional streams the client may have open at once, 0 to 1024. */
|
|
452
|
+
maxStreamsBidi: i64
|
|
453
|
+
/** `initial_max_streams_uni`: how many unidirectional streams the client may have open at once, 0 to 1024. */
|
|
454
|
+
maxStreamsUni: i64
|
|
455
|
+
/** How many streams of its own the server may have open at once, 0 to 1024 (`openStream`). */
|
|
456
|
+
localStreams: i64
|
|
457
|
+
/**
|
|
458
|
+
* RFC 9221's `max_datagram_frame_size`: the largest DATAGRAM frame the
|
|
459
|
+
* server takes, 0 (none, and the parameter is not sent) to 1500. The
|
|
460
|
+
* server sends DATAGRAM frames only when it takes them too.
|
|
461
|
+
*/
|
|
462
|
+
maxDatagramFrameSize: i64
|
|
463
|
+
/**
|
|
464
|
+
* `max_idle_timeout` in milliseconds: 0 for none, at most 2^62 − 1. The
|
|
465
|
+
* connection times out after the smaller of this and the client's, and
|
|
466
|
+
* never sooner than `QUIC_CONN_IDLE_FLOOR`.
|
|
467
|
+
*/
|
|
468
|
+
maxIdleTimeout: i64
|
|
469
|
+
/** `active_connection_id_limit`: how many of its connection IDs the client may give the server, 2 to 8. */
|
|
470
|
+
activeConnectionIdLimit: i64
|
|
471
|
+
/** How long, in milliseconds, a Retry token is accepted after it was issued: 1 to 60000. */
|
|
472
|
+
retryTokenLifetime: i64
|
|
473
|
+
/**
|
|
474
|
+
* The static key every stateless reset token is derived from
|
|
475
|
+
* (`quicStatelessResetToken`, RFC 9000 §10.3.2), `QUIC_CONN_STATIC_KEY_SIZE`
|
|
476
|
+
* bytes. Keep it across restarts: a server that lost a connection resets it
|
|
477
|
+
* with the token this key gives for the connection ID the client sends to.
|
|
478
|
+
*/
|
|
479
|
+
statelessResetKey: u8[]
|
|
480
|
+
/** The key `nish/net/quic-listener` authenticates its Retry tokens with (§8.1.4), `QUIC_CONN_STATIC_KEY_SIZE` bytes. */
|
|
481
|
+
retryTokenKey: u8[]
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/** One run of stream data the client sent, in order, as `readStream` answers it: `fin` when it ends the stream. */
|
|
485
|
+
export class QuicStreamData {
|
|
486
|
+
streamId: i64 = 0
|
|
487
|
+
data: u8[]
|
|
488
|
+
fin: boolean = false
|
|
489
|
+
|
|
490
|
+
constructor(streamId: i64, data: u8[], fin: boolean) {
|
|
491
|
+
this.streamId = streamId
|
|
492
|
+
this.data = data
|
|
493
|
+
this.fin = fin
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* CRYPTO bytes put back in order: a ring of `capacity` bytes indexed by
|
|
499
|
+
* stream offset, with a bit per byte saying it arrived, so frames may come
|
|
500
|
+
* in any order and overlap. What continues the stream from `delivered` is
|
|
501
|
+
* handed to TLS from the ring itself. One serves the connection: it follows
|
|
502
|
+
* the level the client is sending at, since the Initial flight is complete
|
|
503
|
+
* before the Handshake one starts.
|
|
504
|
+
*/
|
|
505
|
+
class QuicConnReassembly {
|
|
506
|
+
ring: u8[]
|
|
507
|
+
have: u8[]
|
|
508
|
+
/** The level whose bytes the ring holds. */
|
|
509
|
+
level: i32 = 0
|
|
510
|
+
/** How many bytes of the level have been handed to TLS. */
|
|
511
|
+
delivered: i64 = 0
|
|
512
|
+
|
|
513
|
+
constructor(capacity: i32) {
|
|
514
|
+
this.ring = new Array<u8>(capacity)
|
|
515
|
+
this.have = new Array<u8>((capacity + 7) >> 3)
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/** Empties the ring for level `level`. */
|
|
519
|
+
reset(level: i32): void {
|
|
520
|
+
this.level = level
|
|
521
|
+
this.delivered = 0
|
|
522
|
+
this.have.fill(toU8(0))
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/** The ring slot of stream offset `offset`. */
|
|
526
|
+
slot(offset: i64): i32 {
|
|
527
|
+
const capacity: i32 = toI32(this.ring.length)
|
|
528
|
+
return capacity > 0 ? toI32(offset % toI64(capacity)) : 0
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** Whether the byte in ring slot `s` arrived and is not yet handed out. */
|
|
532
|
+
arrived(s: i32): boolean {
|
|
533
|
+
return quicStreamBit(this.have, s)
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/** Marks ring slot `s` as holding a byte, or not. */
|
|
537
|
+
mark(s: i32, on: boolean): void {
|
|
538
|
+
quicStreamSetBit(this.have, s, on)
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Takes `data[from .. from + length)`, which sits at stream offset
|
|
543
|
+
* `offset`. Answers `false`, taking nothing, when it reaches `capacity` or
|
|
544
|
+
* more past what was delivered. Bytes already delivered are skipped.
|
|
545
|
+
*/
|
|
546
|
+
insert(offset: i64, data: u8[], from: i32, length: i32): boolean {
|
|
547
|
+
const capacity: i32 = toI32(this.ring.length)
|
|
548
|
+
if (offset + toI64(length) > this.delivered + toI64(capacity)) {
|
|
549
|
+
return false
|
|
550
|
+
}
|
|
551
|
+
for (let k: i32 = 0; k < length; k += 1) {
|
|
552
|
+
const position: i64 = offset + toI64(k)
|
|
553
|
+
const at: i32 = from + k
|
|
554
|
+
if (position >= this.delivered && at >= 0 && at < toI32(data.length)) {
|
|
555
|
+
const s: i32 = this.slot(position)
|
|
556
|
+
if (s >= 0 && s < capacity) {
|
|
557
|
+
this.ring[s] = data[at]
|
|
558
|
+
this.mark(s, true)
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return true
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/** How many bytes from `delivered` have arrived without a gap, up to the end of the ring's array. */
|
|
566
|
+
run(): i32 {
|
|
567
|
+
const capacity: i32 = toI32(this.ring.length)
|
|
568
|
+
const start: i32 = this.slot(this.delivered)
|
|
569
|
+
let n: i32 = 0
|
|
570
|
+
while (start + n < capacity && this.arrived(start + n)) {
|
|
571
|
+
n += 1
|
|
572
|
+
}
|
|
573
|
+
return n
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/** Hands out the `n` bytes `run` counted: their bits cleared, `delivered` moved past them. */
|
|
577
|
+
consume(n: i32): void {
|
|
578
|
+
const start: i32 = this.slot(this.delivered)
|
|
579
|
+
for (let k: i32 = 0; k < n; k += 1) {
|
|
580
|
+
this.mark(start + k, false)
|
|
581
|
+
}
|
|
582
|
+
this.delivered = this.delivered + toI64(n)
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* One packet number space's state (RFC 9000 §12.3): its keys, its numbers,
|
|
588
|
+
* its CRYPTO stream, and what each of its packets in flight carried.
|
|
589
|
+
*
|
|
590
|
+
* The record of a packet is a row of the `sent*` arrays, indexed by the slot
|
|
591
|
+
* `nish/net/quic-recovery` gave it, so it is fixed when the connection is
|
|
592
|
+
* made. One row past the last slot, `staging`, is where the packet being
|
|
593
|
+
* built is described; `recordSent` copies it to the packet's slot. Only the
|
|
594
|
+
* Application Data space has rows for STREAM chunks and control frames,
|
|
595
|
+
* since only a 1-RTT packet carries them.
|
|
596
|
+
*/
|
|
597
|
+
class QuicConnSpace {
|
|
598
|
+
readKeys: QuicKeys | null = null
|
|
599
|
+
writeKeys: QuicKeys | null = null
|
|
600
|
+
/** The next packet number to send. */
|
|
601
|
+
nextPn: i64 = 0
|
|
602
|
+
/** The largest packet number the client acknowledged here, or -1. */
|
|
603
|
+
largestAcked: i64 = -1
|
|
604
|
+
received: QuicAckRanges
|
|
605
|
+
/**
|
|
606
|
+
* Every CRYPTO byte TLS wrote at this level, from stream offset 0, its
|
|
607
|
+
* first `cryptoOutLength` bytes, kept until the level is discarded so that
|
|
608
|
+
* a lost range can be sent again; the array is reused by the next
|
|
609
|
+
* connection in the slot. The bytes from `cryptoOutHead` (offset
|
|
610
|
+
* `cryptoOutOffset`) are not sent yet.
|
|
611
|
+
*/
|
|
612
|
+
cryptoOut: u8[]
|
|
613
|
+
cryptoOutLength: i32 = 0
|
|
614
|
+
/**
|
|
615
|
+
* How many ack-eliciting probe packets this space still owes, which the
|
|
616
|
+
* window does not hold back: what a probe timeout asked for (RFC 9002
|
|
617
|
+
* §6.2.4), or an early resend (§6.2.3).
|
|
618
|
+
*/
|
|
619
|
+
probes: i32 = 0
|
|
620
|
+
cryptoOutOffset: i64 = 0
|
|
621
|
+
/** How many CRYPTO bytes the client sent here that were handed to TLS, for a duplicate arriving after the next level started. */
|
|
622
|
+
cryptoInDelivered: i64 = 0
|
|
623
|
+
/** CRYPTO bytes declared lost, to send before new ones: `[cryptoResendLow, cryptoResendHigh)`, or -1. */
|
|
624
|
+
cryptoResendLow: i64 = -1
|
|
625
|
+
cryptoResendHigh: i64 = -1
|
|
626
|
+
/** Each packet's CRYPTO frame (offset, and length 0 for none), and its `QUIC_CONN_SENT_*` bits. */
|
|
627
|
+
sentCryptoOffset: i64[]
|
|
628
|
+
sentCryptoLength: i32[]
|
|
629
|
+
sentFlags: u8[]
|
|
630
|
+
/** Each packet's STREAM chunks, `QUIC_CONN_PACKET_STREAMS` to a row: how many, then each one's stream, offset, length and FIN. */
|
|
631
|
+
sentStreamCount: i32[]
|
|
632
|
+
sentStreamId: i64[]
|
|
633
|
+
sentStreamOffset: i64[]
|
|
634
|
+
sentStreamLength: i32[]
|
|
635
|
+
sentStreamFin: u8[]
|
|
636
|
+
/** Each packet's control frames, `QUIC_CONN_PACKET_CONTROL` to a row: their kind and value (a sequence number or a stream). */
|
|
637
|
+
sentControlCount: i32[]
|
|
638
|
+
sentControlKind: u8[]
|
|
639
|
+
sentControlValue: i64[]
|
|
640
|
+
/**
|
|
641
|
+
* Where this space's packet keys live, made once for the slot (QUIC-3):
|
|
642
|
+
* the read keys' and the write keys', and for the Application Data space a
|
|
643
|
+
* second of each, which a key update derives the next generation into.
|
|
644
|
+
*/
|
|
645
|
+
keySlots: QuicKeysSlot[]
|
|
646
|
+
/** `TLS_LEVEL_INITIAL`, `_HANDSHAKE` or `_APPLICATION`, which is also the space's index. */
|
|
647
|
+
level: i32 = 0
|
|
648
|
+
cryptoOutHead: i32 = 0
|
|
649
|
+
/** The packet `buildDatagram` is putting together here: where it starts in the datagram, or -1 for none, and its payload's end. */
|
|
650
|
+
builtStart: i32 = -1
|
|
651
|
+
builtEnd: i32 = 0
|
|
652
|
+
/** The row the packet being built is described in: one past the last slot. */
|
|
653
|
+
staging: i32 = 0
|
|
654
|
+
/** Whether the keys were discarded (RFC 9001 §4.9): nothing is sent or received here again. */
|
|
655
|
+
discarded: boolean = false
|
|
656
|
+
/** Whether the packet being built elicits an acknowledgement, and so is recorded. */
|
|
657
|
+
stagedEliciting: boolean = false
|
|
658
|
+
|
|
659
|
+
constructor(level: i32) {
|
|
660
|
+
this.level = level
|
|
661
|
+
this.received = new QuicAckRanges()
|
|
662
|
+
this.cryptoOut = []
|
|
663
|
+
this.keySlots = []
|
|
664
|
+
const slots: i32 = level === TLS_LEVEL_APPLICATION ? 4 : 2
|
|
665
|
+
for (let k: i32 = 0; k < slots; k += 1) {
|
|
666
|
+
this.keySlots.push(new QuicKeysSlot())
|
|
667
|
+
}
|
|
668
|
+
const capacity: i32 =
|
|
669
|
+
level === TLS_LEVEL_APPLICATION ? QUIC_RECOVERY_APPLICATION_CAPACITY : QUIC_RECOVERY_HANDSHAKE_CAPACITY
|
|
670
|
+
const rows: i32 = capacity + 1
|
|
671
|
+
// Only 1-RTT packets carry STREAM and control frames (RFC 9000 §12.4).
|
|
672
|
+
const chunkRows: i32 = level === TLS_LEVEL_APPLICATION ? rows : 0
|
|
673
|
+
this.staging = capacity
|
|
674
|
+
this.sentCryptoOffset = new Array<i64>(rows)
|
|
675
|
+
this.sentCryptoLength = new Array<i32>(rows)
|
|
676
|
+
this.sentFlags = new Array<u8>(rows)
|
|
677
|
+
this.sentStreamCount = new Array<i32>(rows)
|
|
678
|
+
this.sentStreamId = new Array<i64>(chunkRows * QUIC_CONN_PACKET_STREAMS)
|
|
679
|
+
this.sentStreamOffset = new Array<i64>(chunkRows * QUIC_CONN_PACKET_STREAMS)
|
|
680
|
+
this.sentStreamLength = new Array<i32>(chunkRows * QUIC_CONN_PACKET_STREAMS)
|
|
681
|
+
this.sentStreamFin = new Array<u8>(chunkRows * QUIC_CONN_PACKET_STREAMS)
|
|
682
|
+
this.sentControlCount = new Array<i32>(rows)
|
|
683
|
+
this.sentControlKind = new Array<u8>(chunkRows * QUIC_CONN_PACKET_CONTROL)
|
|
684
|
+
this.sentControlValue = new Array<i64>(chunkRows * QUIC_CONN_PACKET_CONTROL)
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/** Puts the space back as a new connection has it, keeping its arrays, for a slot reused for another peer. */
|
|
688
|
+
reset(): void {
|
|
689
|
+
this.readKeys = null
|
|
690
|
+
this.writeKeys = null
|
|
691
|
+
this.nextPn = 0
|
|
692
|
+
this.largestAcked = -1
|
|
693
|
+
this.received.clear()
|
|
694
|
+
this.cryptoOutLength = 0
|
|
695
|
+
this.cryptoOutOffset = 0
|
|
696
|
+
this.cryptoOutHead = 0
|
|
697
|
+
this.cryptoInDelivered = 0
|
|
698
|
+
this.cryptoResendLow = -1
|
|
699
|
+
this.cryptoResendHigh = -1
|
|
700
|
+
this.discarded = false
|
|
701
|
+
this.probes = 0
|
|
702
|
+
this.clearStaged()
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Appends `bytes[0 .. length)` to the CRYPTO stream kept, writing into the
|
|
707
|
+
* array's old room before growing it: TLS's output buffer is read in
|
|
708
|
+
* place, so its flight is not copied on the way.
|
|
709
|
+
*/
|
|
710
|
+
appendCrypto(bytes: u8[], length: i32): void {
|
|
711
|
+
for (let k: i32 = 0; k < length && k < toI32(bytes.length); k += 1) {
|
|
712
|
+
const at: i32 = this.cryptoOutLength
|
|
713
|
+
if (at >= 0 && at < toI32(this.cryptoOut.length)) {
|
|
714
|
+
this.cryptoOut[at] = bytes[k]
|
|
715
|
+
} else {
|
|
716
|
+
this.cryptoOut.push(bytes[k])
|
|
717
|
+
}
|
|
718
|
+
this.cryptoOutLength = at + 1
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Key slot `k` of this space: in the Initial and Handshake spaces 0 holds
|
|
724
|
+
* the read keys and 1 the write keys; in Application Data 0 and 1 hold
|
|
725
|
+
* the read keys' generations and 2 and 3 the write keys'.
|
|
726
|
+
*/
|
|
727
|
+
keySlot(k: i32): QuicKeysSlot {
|
|
728
|
+
const at: i32 = k >= 0 && k < toI32(this.keySlots.length) ? k : 0
|
|
729
|
+
return this.keySlots[at]
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/** Zeroes every key this space's slots hold, the AES schedules included. */
|
|
733
|
+
wipeKeySlots(): void {
|
|
734
|
+
for (const slot of this.keySlots) {
|
|
735
|
+
slot.wipe()
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/** The number of STREAM chunks row `row` holds, or 0. */
|
|
740
|
+
streamCount(row: i32): i32 {
|
|
741
|
+
return row >= 0 && row < toI32(this.sentStreamCount.length) ? this.sentStreamCount[row] : 0
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/** The number of control frames row `row` holds, or 0. */
|
|
745
|
+
controlCount(row: i32): i32 {
|
|
746
|
+
return row >= 0 && row < toI32(this.sentControlCount.length) ? this.sentControlCount[row] : 0
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/** The CRYPTO frame's length in row `row`, or 0. */
|
|
750
|
+
cryptoLength(row: i32): i32 {
|
|
751
|
+
return row >= 0 && row < toI32(this.sentCryptoLength.length) ? this.sentCryptoLength[row] : 0
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/** The CRYPTO frame's offset in row `row`, or 0. */
|
|
755
|
+
cryptoOffset(row: i32): i64 {
|
|
756
|
+
return row >= 0 && row < toI32(this.sentCryptoOffset.length) ? this.sentCryptoOffset[row] : 0
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/** The `QUIC_CONN_SENT_*` bits of row `row`. */
|
|
760
|
+
flags(row: i32): i32 {
|
|
761
|
+
return row >= 0 && row < toI32(this.sentFlags.length) ? toI32(this.sentFlags[row]) : 0
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/** Empties the staging row, for the next packet. */
|
|
765
|
+
clearStaged(): void {
|
|
766
|
+
this.stagedEliciting = false
|
|
767
|
+
this.setCrypto(this.staging, QUIC_CONN_NONE, QUIC_CONN_FROM)
|
|
768
|
+
this.setFlags(this.staging, QUIC_CONN_FROM)
|
|
769
|
+
this.setCounts(this.staging, QUIC_CONN_FROM, QUIC_CONN_FROM)
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/** Sets row `row`'s CRYPTO frame. */
|
|
773
|
+
setCrypto(row: i32, offset: i64, length: i32): void {
|
|
774
|
+
if (row >= 0 && row < toI32(this.sentCryptoOffset.length) && row < toI32(this.sentCryptoLength.length)) {
|
|
775
|
+
this.sentCryptoOffset[row] = offset
|
|
776
|
+
this.sentCryptoLength[row] = length
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/** Sets row `row`'s bits. */
|
|
781
|
+
setFlags(row: i32, flags: i32): void {
|
|
782
|
+
if (row >= 0 && row < toI32(this.sentFlags.length)) {
|
|
783
|
+
this.sentFlags[row] = toU8(flags)
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/** Sets how many STREAM chunks and control frames row `row` holds. */
|
|
788
|
+
setCounts(row: i32, streams: i32, control: i32): void {
|
|
789
|
+
if (row >= 0 && row < toI32(this.sentStreamCount.length) && row < toI32(this.sentControlCount.length)) {
|
|
790
|
+
this.sentStreamCount[row] = streams
|
|
791
|
+
this.sentControlCount[row] = control
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/** Sets STREAM chunk `j` of row `row`. */
|
|
796
|
+
setChunk(row: i32, j: i32, id: i64, offset: i64, length: i32, fin: boolean): void {
|
|
797
|
+
const at: i32 = row * QUIC_CONN_PACKET_STREAMS + j
|
|
798
|
+
if (
|
|
799
|
+
at >= 0 &&
|
|
800
|
+
at < toI32(this.sentStreamId.length) &&
|
|
801
|
+
at < toI32(this.sentStreamOffset.length) &&
|
|
802
|
+
at < toI32(this.sentStreamLength.length) &&
|
|
803
|
+
at < toI32(this.sentStreamFin.length)
|
|
804
|
+
) {
|
|
805
|
+
this.sentStreamId[at] = id
|
|
806
|
+
this.sentStreamOffset[at] = offset
|
|
807
|
+
this.sentStreamLength[at] = length
|
|
808
|
+
this.sentStreamFin[at] = toU8(fin ? 1 : 0)
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/** Sets control frame `j` of row `row`. */
|
|
813
|
+
setControl(row: i32, j: i32, kind: i32, value: i64): void {
|
|
814
|
+
const at: i32 = row * QUIC_CONN_PACKET_CONTROL + j
|
|
815
|
+
if (at >= 0 && at < toI32(this.sentControlKind.length) && at < toI32(this.sentControlValue.length)) {
|
|
816
|
+
this.sentControlKind[at] = toU8(kind)
|
|
817
|
+
this.sentControlValue[at] = value
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/** Stages a CRYPTO frame at `offset`, `length` bytes, in the packet being built. */
|
|
822
|
+
stageCrypto(offset: i64, length: i32): void {
|
|
823
|
+
this.setCrypto(this.staging, offset, length)
|
|
824
|
+
this.stagedEliciting = true
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/** Stages a STREAM chunk in the packet being built; the caller keeps under `QUIC_CONN_PACKET_STREAMS`. */
|
|
828
|
+
stageChunk(id: i64, offset: i64, length: i32, fin: boolean): void {
|
|
829
|
+
const streams: i32 = this.streamCount(this.staging)
|
|
830
|
+
this.setChunk(this.staging, streams, id, offset, length, fin)
|
|
831
|
+
this.setCounts(this.staging, streams + 1, this.controlCount(this.staging))
|
|
832
|
+
this.stagedEliciting = true
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/** Stages a control frame in the packet being built; the caller keeps under `QUIC_CONN_PACKET_CONTROL`. */
|
|
836
|
+
stageControl(kind: i32, value: i64): void {
|
|
837
|
+
const control: i32 = this.controlCount(this.staging)
|
|
838
|
+
this.setControl(this.staging, control, kind, value)
|
|
839
|
+
this.setCounts(this.staging, this.streamCount(this.staging), control + 1)
|
|
840
|
+
this.stagedEliciting = true
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/** Copies the staging row to row `row`, the slot of the packet just sealed. */
|
|
844
|
+
commitStaged(row: i32): void {
|
|
845
|
+
const from: i32 = this.staging
|
|
846
|
+
this.setCrypto(row, this.cryptoOffset(from), this.cryptoLength(from))
|
|
847
|
+
this.setFlags(row, this.flags(from))
|
|
848
|
+
const streams: i32 = this.streamCount(from)
|
|
849
|
+
const control: i32 = this.controlCount(from)
|
|
850
|
+
this.setCounts(row, streams, control)
|
|
851
|
+
for (let j: i32 = 0; j < streams; j += 1) {
|
|
852
|
+
const at: i32 = from * QUIC_CONN_PACKET_STREAMS + j
|
|
853
|
+
if (
|
|
854
|
+
at >= 0 &&
|
|
855
|
+
at < toI32(this.sentStreamId.length) &&
|
|
856
|
+
at < toI32(this.sentStreamOffset.length) &&
|
|
857
|
+
at < toI32(this.sentStreamLength.length) &&
|
|
858
|
+
at < toI32(this.sentStreamFin.length)
|
|
859
|
+
) {
|
|
860
|
+
this.setChunk(
|
|
861
|
+
row,
|
|
862
|
+
j,
|
|
863
|
+
this.sentStreamId[at],
|
|
864
|
+
this.sentStreamOffset[at],
|
|
865
|
+
this.sentStreamLength[at],
|
|
866
|
+
toI32(this.sentStreamFin[at]) !== 0
|
|
867
|
+
)
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
for (let j: i32 = 0; j < control; j += 1) {
|
|
871
|
+
const at: i32 = from * QUIC_CONN_PACKET_CONTROL + j
|
|
872
|
+
if (at >= 0 && at < toI32(this.sentControlKind.length) && at < toI32(this.sentControlValue.length)) {
|
|
873
|
+
this.setControl(row, j, toI32(this.sentControlKind[at]), this.sentControlValue[at])
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/** Queues CRYPTO bytes `[offset, offset + length)` to be sent again, merged with what is queued already. */
|
|
879
|
+
resendCrypto(offset: i64, length: i32): void {
|
|
880
|
+
if (length <= 0 || this.discarded) {
|
|
881
|
+
return
|
|
882
|
+
}
|
|
883
|
+
const end: i64 = offset + toI64(length)
|
|
884
|
+
if (this.cryptoResendLow < 0) {
|
|
885
|
+
this.cryptoResendLow = offset
|
|
886
|
+
this.cryptoResendHigh = end
|
|
887
|
+
return
|
|
888
|
+
}
|
|
889
|
+
if (offset < this.cryptoResendLow) {
|
|
890
|
+
this.cryptoResendLow = offset
|
|
891
|
+
}
|
|
892
|
+
if (end > this.cryptoResendHigh) {
|
|
893
|
+
this.cryptoResendHigh = end
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
/** How many CRYPTO bytes are waiting to be sent. */
|
|
898
|
+
cryptoUnsent(): i32 {
|
|
899
|
+
return this.cryptoOutLength - this.cryptoOutHead
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/** The bytes `bytes[from .. from + length)`, copied into an array of exactly that size. */
|
|
904
|
+
const quicConnSlice = (bytes: u8[], from: i32, length: i32): u8[] => {
|
|
905
|
+
const out: u8[] = new Array<u8>(length)
|
|
906
|
+
const n: i32 = toI32(out.length)
|
|
907
|
+
for (let k: i32 = 0; k < n; k += 1) {
|
|
908
|
+
if (from + k >= 0 && from + k < toI32(bytes.length)) {
|
|
909
|
+
out[k] = bytes[from + k]
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
return out
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
/** The packet-protection AEAD that goes with a TLS 1.3 suite (RFC 9001 §5.3). */
|
|
916
|
+
const quicConnAead = (suite: i32): i32 => {
|
|
917
|
+
if (suite === TLS_AES_256_GCM_SHA384) {
|
|
918
|
+
return QUIC_AEAD_AES_256_GCM
|
|
919
|
+
}
|
|
920
|
+
if (suite === TLS_CHACHA20_POLY1305_SHA256) {
|
|
921
|
+
return QUIC_AEAD_CHACHA20_POLY1305
|
|
922
|
+
}
|
|
923
|
+
return QUIC_AEAD_AES_128_GCM
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/** Wipes a level's key, IV and header-protection key; the expanded AES schedules are out of reach (QUIC-2). */
|
|
927
|
+
const quicConnWipeKeys = (keys: QuicKeys | null): void => {
|
|
928
|
+
quicConnWipePacketKey(keys)
|
|
929
|
+
if (keys !== null) {
|
|
930
|
+
secureZero(keys.hp)
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* Wipes a key generation's packet key and IV that a key update replaced. The
|
|
936
|
+
* header-protection key is left alone: every generation shares it (RFC 9001
|
|
937
|
+
* §6), so it goes only when the level does.
|
|
938
|
+
*/
|
|
939
|
+
const quicConnWipePacketKey = (keys: QuicKeys | null): void => {
|
|
940
|
+
if (keys !== null) {
|
|
941
|
+
secureZero(keys.key)
|
|
942
|
+
secureZero(keys.iv)
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* The stateless reset token for the connection ID `cid` (RFC 9000 §10.3.2):
|
|
948
|
+
* the first 16 bytes of HMAC-SHA256 under the server's static `key`. A
|
|
949
|
+
* connection hands it to the client with each ID it issues, and
|
|
950
|
+
* `nish/net/quic-listener` derives the same token from a packet's ID alone
|
|
951
|
+
* once the connection is gone, which is what lets a server that lost its
|
|
952
|
+
* state end the connection.
|
|
953
|
+
*/
|
|
954
|
+
export const quicStatelessResetToken = (key: u8[], cid: u8[]): u8[] => {
|
|
955
|
+
const mac: u8[] = hmacSha256(key, cid)
|
|
956
|
+
const token: u8[] = quicConnSlice(mac, 0, QUIC_RESET_TOKEN_SIZE)
|
|
957
|
+
secureZero(mac)
|
|
958
|
+
return token
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* `quicStatelessResetToken` of the ID `cid[at .. at + length)` into
|
|
963
|
+
* `out[outAt ..]`, computed in a connection's own HMAC scratch inside an
|
|
964
|
+
* arena block, so issuing an ID keeps nothing (QUIC-3). The scratch and the
|
|
965
|
+
* full MAC are wiped before it returns.
|
|
966
|
+
*/
|
|
967
|
+
export const quicConnResetTokenInto = (
|
|
968
|
+
mac: HmacSha256Scratch,
|
|
969
|
+
key: u8[],
|
|
970
|
+
cid: u8[],
|
|
971
|
+
at: i32,
|
|
972
|
+
length: i32,
|
|
973
|
+
out: u8[],
|
|
974
|
+
outAt: i32
|
|
975
|
+
): void => {
|
|
976
|
+
using _scope = arena()
|
|
977
|
+
const tag: u8[] = new Array<u8>(32)
|
|
978
|
+
mac.begin(key, QUIC_CONN_FROM, toI32(key.length))
|
|
979
|
+
mac.update(cid, at, length)
|
|
980
|
+
mac.finishInto(tag, QUIC_CONN_FROM)
|
|
981
|
+
for (
|
|
982
|
+
let k: i32 = 0;
|
|
983
|
+
k < QUIC_RESET_TOKEN_SIZE && k < toI32(tag.length) && outAt + k < toI32(out.length);
|
|
984
|
+
k += 1
|
|
985
|
+
) {
|
|
986
|
+
out[outAt + k] = tag[k]
|
|
987
|
+
}
|
|
988
|
+
secureZero(tag)
|
|
989
|
+
mac.wipe()
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/** Puts `from[at .. at + length)` in `to`, emptied first, in the room `to` already has. */
|
|
993
|
+
const quicConnRefill = (to: u8[], from: u8[], at: i32, length: i32): void => {
|
|
994
|
+
while (to.length > 0) {
|
|
995
|
+
to.pop()
|
|
996
|
+
}
|
|
997
|
+
for (let k: i32 = 0; k < length && at + k >= 0 && at + k < toI32(from.length); k += 1) {
|
|
998
|
+
to.push(from[at + k])
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* Secret `index` (0 or 1) of a direction's pool, of the length `aead`'s hash
|
|
1004
|
+
* gives: what a key update derives the next traffic secret into.
|
|
1005
|
+
*/
|
|
1006
|
+
const quicConnSecretAt = (pool: u8[][], aead: i32, index: i32): u8[] => {
|
|
1007
|
+
let wanted: i32 = index === 1 ? 1 : 0
|
|
1008
|
+
if (aead === QUIC_AEAD_AES_256_GCM) {
|
|
1009
|
+
wanted = wanted + 2
|
|
1010
|
+
}
|
|
1011
|
+
const at: i32 = wanted < toI32(pool.length) ? wanted : 0
|
|
1012
|
+
return pool[at]
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/** Two secrets of SHA-256's length and two of SHA-384's, for one direction's key updates. */
|
|
1016
|
+
const quicConnSecretPool = (): u8[][] => [
|
|
1017
|
+
new Array<u8>(32),
|
|
1018
|
+
new Array<u8>(32),
|
|
1019
|
+
new Array<u8>(48),
|
|
1020
|
+
new Array<u8>(48),
|
|
1021
|
+
]
|
|
1022
|
+
|
|
1023
|
+
/** Whether the configuration's limits are ones this module can honour. */
|
|
1024
|
+
const quicConnConfigFits = (config: QuicServerConfig): boolean =>
|
|
1025
|
+
config.maxStreamData >= 1 &&
|
|
1026
|
+
config.maxStreamData <= QUIC_CONN_MAX_STREAM_DATA &&
|
|
1027
|
+
config.maxStreamsBidi >= 0 &&
|
|
1028
|
+
config.maxStreamsBidi <= QUIC_CONN_MAX_STREAMS &&
|
|
1029
|
+
config.maxStreamsUni >= 0 &&
|
|
1030
|
+
config.maxStreamsUni <= QUIC_CONN_MAX_STREAMS &&
|
|
1031
|
+
config.localStreams >= 0 &&
|
|
1032
|
+
config.localStreams <= QUIC_CONN_MAX_STREAMS &&
|
|
1033
|
+
(config.maxStreamsBidi + config.maxStreamsUni + config.localStreams) * config.maxStreamData * 2 <=
|
|
1034
|
+
QUIC_CONN_MAX_STREAM_BUFFERS &&
|
|
1035
|
+
config.maxDatagramFrameSize >= 0 &&
|
|
1036
|
+
config.maxDatagramFrameSize <= QUIC_CONN_MAX_DATAGRAM_FRAME &&
|
|
1037
|
+
config.maxData >= 0 &&
|
|
1038
|
+
config.maxData <= QUIC_MAX_VARINT &&
|
|
1039
|
+
config.maxIdleTimeout >= 0 &&
|
|
1040
|
+
config.maxIdleTimeout <= QUIC_MAX_VARINT &&
|
|
1041
|
+
config.activeConnectionIdLimit >= 2 &&
|
|
1042
|
+
config.activeConnectionIdLimit <= 8 &&
|
|
1043
|
+
toI32(config.statelessResetKey.length) === QUIC_CONN_STATIC_KEY_SIZE
|
|
1044
|
+
|
|
1045
|
+
/**
|
|
1046
|
+
* One server connection, which is also a reusable slot. Make one, hand it
|
|
1047
|
+
* every datagram the client sends with `receiveWindow` (or `receive`), sign
|
|
1048
|
+
* when `signatureInput()` asks, send what `takeDatagramInto()` writes until
|
|
1049
|
+
* it answers 0, and use streams and datagrams once `state` is
|
|
1050
|
+
* `QUIC_STATE_CONNECTED`. Once it has closed, `reset` makes it a new
|
|
1051
|
+
* connection for the next client, with its buffers kept.
|
|
1052
|
+
*
|
|
1053
|
+
* The fields are readable: `state`, `error` (the transport error or
|
|
1054
|
+
* application code the connection closed with), `dropped`, `alpn` and
|
|
1055
|
+
* `serverName` once the handshake has read them, `peerParameters`, `streams`
|
|
1056
|
+
* (the stream table), and `cids`, the connection-ID table, which a server
|
|
1057
|
+
* that runs many connections routes datagrams by (`ownsConnectionId`).
|
|
1058
|
+
*/
|
|
1059
|
+
export class QuicConnection {
|
|
1060
|
+
config: QuicServerConfig
|
|
1061
|
+
/** The error code the connection closed with: a transport error, `0x100 + alert`, or the application's code. */
|
|
1062
|
+
error: i64 = 0
|
|
1063
|
+
/** For a transport error: the type of the frame that caused it, or 0. */
|
|
1064
|
+
errorFrameType: i64 = 0
|
|
1065
|
+
serverRandom: u8[]
|
|
1066
|
+
ephemeralPrivate: u8[]
|
|
1067
|
+
cidSeed: u8[]
|
|
1068
|
+
/** The DCID of the client's first Initial, which the Initial keys come from: its first `originalDcidLength` bytes. */
|
|
1069
|
+
originalDcid: u8[]
|
|
1070
|
+
/** The SCID of the client's first Initial: where long-header packets go, and what its transport parameters must name. */
|
|
1071
|
+
peerScid: u8[]
|
|
1072
|
+
/** The server's first connection ID, the SCID of every long-header packet it sends. */
|
|
1073
|
+
localScid: u8[]
|
|
1074
|
+
tls: TlsServer | null = null
|
|
1075
|
+
/** The slot's `TlsServer`, made by its first handshake and `restart`ed by every one after (QUIC-3). */
|
|
1076
|
+
tlsSlot: TlsServer | null = null
|
|
1077
|
+
/** Its configuration, made once: the parameters it carries are `localEncoded`, rewritten for each connection. */
|
|
1078
|
+
tlsConfig: TlsServerConfig
|
|
1079
|
+
/** The server's transport parameters, set for each connection, and their encoding. */
|
|
1080
|
+
localParameters: QuicTransportParameters
|
|
1081
|
+
localEncoded: u8[]
|
|
1082
|
+
/** The client's transport parameters, parsed into the slot's own object. */
|
|
1083
|
+
parsedParameters: QuicTransportParameters
|
|
1084
|
+
/** Where every key derivation and HMAC of the connection runs. */
|
|
1085
|
+
kdf: HkdfScratch
|
|
1086
|
+
/** The Initial secrets of RFC 9001 §5.2, the client's and the server's. */
|
|
1087
|
+
initialClient: u8[]
|
|
1088
|
+
initialServer: u8[]
|
|
1089
|
+
/**
|
|
1090
|
+
* The 1-RTT secrets a key update derives, two of each hash's length a
|
|
1091
|
+
* direction (SHA-256's first, then SHA-384's), used in turn: the next
|
|
1092
|
+
* generation's goes in the one the current generation's is not in.
|
|
1093
|
+
*/
|
|
1094
|
+
writeSecrets: u8[][]
|
|
1095
|
+
readSecrets: u8[][]
|
|
1096
|
+
/** A connection ID being issued and its stateless reset token, before the table copies them. */
|
|
1097
|
+
cidScratch: u8[]
|
|
1098
|
+
tokenScratch: u8[]
|
|
1099
|
+
initial: QuicConnSpace
|
|
1100
|
+
handshake: QuicConnSpace
|
|
1101
|
+
application: QuicConnSpace
|
|
1102
|
+
cids: QuicCidTable
|
|
1103
|
+
/** Loss detection, the RTT estimate, the congestion window and the pacer (RFC 9002). */
|
|
1104
|
+
recovery: QuicRecovery
|
|
1105
|
+
/** The client's transport parameters, once the handshake has checked them; until then, every default. */
|
|
1106
|
+
peerParameters: QuicTransportParameters
|
|
1107
|
+
/** The defaults `peerParameters` starts from, made once and set back by `reset`. */
|
|
1108
|
+
defaultParameters: QuicTransportParameters
|
|
1109
|
+
/** Bytes received and sent, for the anti-amplification limit (RFC 9000 §8.1). */
|
|
1110
|
+
bytesReceived: i64 = 0
|
|
1111
|
+
bytesSent: i64 = 0
|
|
1112
|
+
/** The ALPN protocol the handshake chose. */
|
|
1113
|
+
alpn: string = ""
|
|
1114
|
+
/** The client's `server_name`, or empty. */
|
|
1115
|
+
serverName: string = ""
|
|
1116
|
+
/** The streams, both ways (`nish/net/quic-stream`). */
|
|
1117
|
+
streams: QuicStreams
|
|
1118
|
+
/** DATAGRAM payloads waiting to be sent, and those the client sent waiting to be read. */
|
|
1119
|
+
datagramsOut: QuicDatagramQueue
|
|
1120
|
+
datagramsIn: QuicDatagramQueue
|
|
1121
|
+
/** The CRYPTO bytes the client sent, put back in order. */
|
|
1122
|
+
cryptoIn: QuicConnReassembly
|
|
1123
|
+
/** PATH_CHALLENGE data to answer: a ring of four, eight bytes each, `pathCount` from `pathHead`. */
|
|
1124
|
+
pathData: u8[]
|
|
1125
|
+
frame: QuicFrame
|
|
1126
|
+
/** The header and the packet numbers of the packet being read, reused for every one. */
|
|
1127
|
+
header: QuicHeader
|
|
1128
|
+
packet: QuicPacket
|
|
1129
|
+
/** For a connection `acceptRetry` set up: the DCID of the client's Initial before the Retry, and the Retry's SCID. */
|
|
1130
|
+
retryOriginalDcid: u8[]
|
|
1131
|
+
retryScid: u8[]
|
|
1132
|
+
/** An empty array, which a cleared secret or ID is set to. */
|
|
1133
|
+
none: u8[]
|
|
1134
|
+
/** The latest time the caller gave, in milliseconds; time never runs backwards here. */
|
|
1135
|
+
now: i64 = 0
|
|
1136
|
+
/** When the idle timer last restarted (RFC 9000 §10.1), or -1 before the first packet. */
|
|
1137
|
+
idleSince: i64 = -1
|
|
1138
|
+
/**
|
|
1139
|
+
* The 1-RTT secrets of the current generation, the next generation's read
|
|
1140
|
+
* secret, and its read keys (RFC 9001 §6.1, §6.3). `otherReadKeys` holds
|
|
1141
|
+
* the next generation's while `otherIsNext`, and for a probe timeout after
|
|
1142
|
+
* an update the previous one's, for reordered packets (§6.5).
|
|
1143
|
+
*/
|
|
1144
|
+
appReadSecret: u8[]
|
|
1145
|
+
appWriteSecret: u8[]
|
|
1146
|
+
nextReadSecret: u8[]
|
|
1147
|
+
otherReadKeys: QuicKeys | null = null
|
|
1148
|
+
/** The lowest and highest packet numbers opened with the current read keys, or -1. */
|
|
1149
|
+
readPhaseLowest: i64 = -1
|
|
1150
|
+
readPhaseHighest: i64 = -1
|
|
1151
|
+
/** The first packet number sent with the current write keys. */
|
|
1152
|
+
writePhaseFirst: i64 = 0
|
|
1153
|
+
/** When the previous read keys go and the next are derived, or -1. */
|
|
1154
|
+
keyRetainUntil: i64 = -1
|
|
1155
|
+
/** The packet number of a client key update read but not applied yet, or -1. */
|
|
1156
|
+
phasePending: i64 = -1
|
|
1157
|
+
/** The largest DATAGRAM frame the client takes (its `max_datagram_frame_size`), 0 for none. */
|
|
1158
|
+
peerMaxDatagramFrame: i64 = 0
|
|
1159
|
+
originalDcidLength: i32 = 0
|
|
1160
|
+
peerScidLength: i32 = 0
|
|
1161
|
+
pathHead: i32 = 0
|
|
1162
|
+
pathCount: i32 = 0
|
|
1163
|
+
state: i32 = 0
|
|
1164
|
+
/** Packets dropped without closing the connection: unparseable, unauthenticated, duplicated, or for keys not held. */
|
|
1165
|
+
dropped: i32 = 0
|
|
1166
|
+
/** Key updates the client started, against `QUIC_CONN_MAX_KEY_UPDATES`. */
|
|
1167
|
+
keyUpdates: i32 = 0
|
|
1168
|
+
/** Which of the Application Data space's two read slots, and two write slots, hold the current keys. */
|
|
1169
|
+
appReadSlot: i32 = 0
|
|
1170
|
+
appWriteSlot: i32 = 0
|
|
1171
|
+
/** Which of `readSecrets` and `writeSecrets` (0 or 1) holds the current secret, or -1 for `TlsServer`'s own. */
|
|
1172
|
+
readSecretCurrent: i32 = -1
|
|
1173
|
+
writeSecretCurrent: i32 = -1
|
|
1174
|
+
/** Handshake data sent again early, against `QUIC_CONN_EARLY_RESENDS` (RFC 9002 §6.2.3). */
|
|
1175
|
+
earlyResends: i32 = 0
|
|
1176
|
+
/** The level at which the packet just read showed the client lacks the server's handshake data, or -1. */
|
|
1177
|
+
resendLevel: i32 = -1
|
|
1178
|
+
/** Whether `error` is an application code (CONNECTION_CLOSE 0x1d) rather than a transport error. */
|
|
1179
|
+
errorIsApplication: boolean = false
|
|
1180
|
+
/** Whether the one CONNECTION_CLOSE this side owes has gone out. */
|
|
1181
|
+
closeSent: boolean = false
|
|
1182
|
+
/** Whether the client's address is validated: a Handshake packet from it was processed. */
|
|
1183
|
+
addressValidated: boolean = false
|
|
1184
|
+
/** Whether the handshake is complete, which for a server is also confirmed (RFC 9001 §4.1.2). */
|
|
1185
|
+
handshakeComplete: boolean = false
|
|
1186
|
+
handshakeDonePending: boolean = false
|
|
1187
|
+
/** Whether the connection was set up by `acceptRetry`. */
|
|
1188
|
+
retried: boolean = false
|
|
1189
|
+
/** Whether an ack-eliciting packet went out since the last packet was received. */
|
|
1190
|
+
elicitingSent: boolean = false
|
|
1191
|
+
/** Whether `otherReadKeys` are the next generation's rather than the previous one's. */
|
|
1192
|
+
otherIsNext: boolean = false
|
|
1193
|
+
/** The Key Phase bit of the current read keys, and of the current write keys. */
|
|
1194
|
+
readPhase: boolean = false
|
|
1195
|
+
writePhase: boolean = false
|
|
1196
|
+
/** Whether the packet just read brought CRYPTO bytes for TLS, or retired an ID that wants replacing. */
|
|
1197
|
+
cryptoArrived: boolean = false
|
|
1198
|
+
topUpOwed: boolean = false
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* A connection under `config`, with `entropy` its `QUIC_CONN_ENTROPY_SIZE`
|
|
1202
|
+
* random bytes, which are copied out and then wiped in the caller's array.
|
|
1203
|
+
* Every buffer the connection will use is made here, sized by `config`.
|
|
1204
|
+
* Entropy of another length, or limits outside what `QuicServerConfig`
|
|
1205
|
+
* documents, leave the connection closing with INTERNAL_ERROR before it
|
|
1206
|
+
* reads anything; it then sends nothing.
|
|
1207
|
+
*/
|
|
1208
|
+
constructor(config: QuicServerConfig, entropy: u8[]) {
|
|
1209
|
+
this.config = config
|
|
1210
|
+
this.serverRandom = new Array<u8>(32)
|
|
1211
|
+
this.ephemeralPrivate = new Array<u8>(32)
|
|
1212
|
+
this.localScid = new Array<u8>(QUIC_CONN_CID_LENGTH)
|
|
1213
|
+
this.cidSeed = new Array<u8>(32)
|
|
1214
|
+
this.originalDcid = new Array<u8>(QUIC_MAX_CID_LENGTH)
|
|
1215
|
+
this.peerScid = new Array<u8>(QUIC_MAX_CID_LENGTH)
|
|
1216
|
+
this.initial = new QuicConnSpace(TLS_LEVEL_INITIAL)
|
|
1217
|
+
this.handshake = new QuicConnSpace(TLS_LEVEL_HANDSHAKE)
|
|
1218
|
+
this.application = new QuicConnSpace(TLS_LEVEL_APPLICATION)
|
|
1219
|
+
this.cids = new QuicCidTable(
|
|
1220
|
+
config.activeConnectionIdLimit >= 2 && config.activeConnectionIdLimit <= 8
|
|
1221
|
+
? config.activeConnectionIdLimit
|
|
1222
|
+
: 2,
|
|
1223
|
+
QUIC_CONN_LOCAL_CIDS
|
|
1224
|
+
)
|
|
1225
|
+
this.recovery = new QuicRecovery()
|
|
1226
|
+
this.defaultParameters = new QuicTransportParameters()
|
|
1227
|
+
this.peerParameters = this.defaultParameters
|
|
1228
|
+
// A configuration that does not fit closes the connection at once (see
|
|
1229
|
+
// `begin`), so it gets no stream or datagram buffers at all.
|
|
1230
|
+
const fits: boolean = quicConnConfigFits(config)
|
|
1231
|
+
const streams: i64 = fits ? config.maxStreamsBidi : QUIC_CONN_NONE
|
|
1232
|
+
const uni: i64 = fits ? config.maxStreamsUni : QUIC_CONN_NONE
|
|
1233
|
+
const local: i64 = fits ? config.localStreams : QUIC_CONN_NONE
|
|
1234
|
+
const buffer: i32 = fits ? toI32(config.maxStreamData) : QUIC_CONN_FROM
|
|
1235
|
+
this.streams = new QuicStreams(streams, uni, local, buffer, fits ? config.maxData : QUIC_CONN_NONE)
|
|
1236
|
+
// The rings are made only when DATAGRAM frames are offered at all.
|
|
1237
|
+
const datagrams: i32 = fits && config.maxDatagramFrameSize > 0 ? QUIC_CONN_DATAGRAM_QUEUE : 0
|
|
1238
|
+
this.datagramsOut = new QuicDatagramQueue(datagrams, QUIC_CONN_DATAGRAM_SIZE)
|
|
1239
|
+
this.datagramsIn = new QuicDatagramQueue(
|
|
1240
|
+
datagrams,
|
|
1241
|
+
fits ? toI32(config.maxDatagramFrameSize) : QUIC_CONN_FROM
|
|
1242
|
+
)
|
|
1243
|
+
this.cryptoIn = new QuicConnReassembly(QUIC_CONN_CRYPTO_WINDOW)
|
|
1244
|
+
this.pathData = new Array<u8>(QUIC_PATH_DATA_SIZE * 4)
|
|
1245
|
+
this.frame = new QuicFrame()
|
|
1246
|
+
this.header = new QuicHeader()
|
|
1247
|
+
this.packet = new QuicPacket()
|
|
1248
|
+
this.none = []
|
|
1249
|
+
this.retryOriginalDcid = []
|
|
1250
|
+
this.retryScid = []
|
|
1251
|
+
this.localParameters = new QuicTransportParameters()
|
|
1252
|
+
this.localEncoded = []
|
|
1253
|
+
this.parsedParameters = new QuicTransportParameters()
|
|
1254
|
+
const extra: u8[] = []
|
|
1255
|
+
this.tlsConfig = {
|
|
1256
|
+
certificateChain: config.certificateChain,
|
|
1257
|
+
alpn: config.alpn,
|
|
1258
|
+
quicTransportParameters: this.localEncoded,
|
|
1259
|
+
extraExtensions: extra,
|
|
1260
|
+
signatureScheme: config.signatureScheme,
|
|
1261
|
+
quic: true,
|
|
1262
|
+
}
|
|
1263
|
+
this.kdf = new HkdfScratch()
|
|
1264
|
+
this.initialClient = new Array<u8>(32)
|
|
1265
|
+
this.initialServer = new Array<u8>(32)
|
|
1266
|
+
this.writeSecrets = quicConnSecretPool()
|
|
1267
|
+
this.readSecrets = quicConnSecretPool()
|
|
1268
|
+
this.cidScratch = new Array<u8>(QUIC_CONN_CID_LENGTH)
|
|
1269
|
+
this.tokenScratch = new Array<u8>(QUIC_RESET_TOKEN_SIZE)
|
|
1270
|
+
this.appReadSecret = this.none
|
|
1271
|
+
this.appWriteSecret = this.none
|
|
1272
|
+
this.nextReadSecret = this.none
|
|
1273
|
+
this.begin(entropy)
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
/**
|
|
1277
|
+
* Makes this slot a new connection for the next client, with `entropy`
|
|
1278
|
+
* its `QUIC_CONN_ENTROPY_SIZE` fresh random bytes (wiped in the caller's
|
|
1279
|
+
* array): every secret of the last connection is wiped first, as
|
|
1280
|
+
* `release()` does, and every buffer is kept and emptied. The limits are
|
|
1281
|
+
* the constructor's configuration's. A slot reset this way allocates
|
|
1282
|
+
* nothing, and neither does the next handshake (QUIC-3 in
|
|
1283
|
+
* `docs/security/quic.md`).
|
|
1284
|
+
*/
|
|
1285
|
+
reset(entropy: u8[]): void {
|
|
1286
|
+
this.wipeAll()
|
|
1287
|
+
this.initial.reset()
|
|
1288
|
+
this.handshake.reset()
|
|
1289
|
+
this.application.reset()
|
|
1290
|
+
this.cids.reset()
|
|
1291
|
+
this.recovery.reset()
|
|
1292
|
+
this.streams.reset()
|
|
1293
|
+
this.datagramsOut.reset()
|
|
1294
|
+
this.datagramsIn.reset()
|
|
1295
|
+
this.cryptoIn.reset(TLS_LEVEL_INITIAL)
|
|
1296
|
+
this.peerParameters = this.defaultParameters
|
|
1297
|
+
// The slot keeps its `TlsServer`, wiped by `wipeAll`; the next handshake restarts it.
|
|
1298
|
+
this.tls = null
|
|
1299
|
+
this.error = 0
|
|
1300
|
+
this.errorFrameType = 0
|
|
1301
|
+
this.bytesReceived = 0
|
|
1302
|
+
this.bytesSent = 0
|
|
1303
|
+
this.alpn = ""
|
|
1304
|
+
this.serverName = ""
|
|
1305
|
+
quicConnRefill(this.retryOriginalDcid, this.none, QUIC_CONN_FROM, QUIC_CONN_FROM)
|
|
1306
|
+
quicConnRefill(this.retryScid, this.none, QUIC_CONN_FROM, QUIC_CONN_FROM)
|
|
1307
|
+
this.appReadSecret = this.none
|
|
1308
|
+
this.appWriteSecret = this.none
|
|
1309
|
+
this.nextReadSecret = this.none
|
|
1310
|
+
this.otherReadKeys = null
|
|
1311
|
+
this.appReadSlot = 0
|
|
1312
|
+
this.appWriteSlot = 0
|
|
1313
|
+
this.readSecretCurrent = -1
|
|
1314
|
+
this.writeSecretCurrent = -1
|
|
1315
|
+
this.earlyResends = 0
|
|
1316
|
+
this.resendLevel = -1
|
|
1317
|
+
this.now = 0
|
|
1318
|
+
this.idleSince = -1
|
|
1319
|
+
this.readPhaseLowest = -1
|
|
1320
|
+
this.readPhaseHighest = -1
|
|
1321
|
+
this.writePhaseFirst = 0
|
|
1322
|
+
this.keyRetainUntil = -1
|
|
1323
|
+
this.phasePending = -1
|
|
1324
|
+
this.peerMaxDatagramFrame = 0
|
|
1325
|
+
this.originalDcidLength = 0
|
|
1326
|
+
this.peerScidLength = 0
|
|
1327
|
+
this.pathHead = 0
|
|
1328
|
+
this.pathCount = 0
|
|
1329
|
+
this.state = QUIC_STATE_WAIT_INITIAL
|
|
1330
|
+
this.dropped = 0
|
|
1331
|
+
this.keyUpdates = 0
|
|
1332
|
+
this.errorIsApplication = false
|
|
1333
|
+
this.closeSent = false
|
|
1334
|
+
this.addressValidated = false
|
|
1335
|
+
this.handshakeComplete = false
|
|
1336
|
+
this.handshakeDonePending = false
|
|
1337
|
+
this.retried = false
|
|
1338
|
+
this.elicitingSent = false
|
|
1339
|
+
this.otherIsNext = false
|
|
1340
|
+
this.readPhase = false
|
|
1341
|
+
this.writePhase = false
|
|
1342
|
+
this.cryptoArrived = false
|
|
1343
|
+
this.topUpOwed = false
|
|
1344
|
+
this.begin(entropy)
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
/** Takes `entropy` (and wipes it), or closes with INTERNAL_ERROR for entropy or a configuration that does not fit. */
|
|
1348
|
+
begin(entropy: u8[]): void {
|
|
1349
|
+
quicCidCopy(this.serverRandom, entropy, 0, 32)
|
|
1350
|
+
quicCidCopy(this.ephemeralPrivate, entropy, 32, 32)
|
|
1351
|
+
quicCidCopy(this.localScid, entropy, 64, QUIC_CONN_CID_LENGTH)
|
|
1352
|
+
quicCidCopy(this.cidSeed, entropy, 72, 32)
|
|
1353
|
+
const fits: boolean = toI32(entropy.length) === QUIC_CONN_ENTROPY_SIZE && quicConnConfigFits(this.config)
|
|
1354
|
+
secureZero(entropy)
|
|
1355
|
+
if (!fits) {
|
|
1356
|
+
this.state = QUIC_STATE_CLOSING
|
|
1357
|
+
this.error = QUIC_ERROR_INTERNAL
|
|
1358
|
+
this.closeSent = true
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
/** Counts a dropped packet; the count stops at the largest `i32` rather than overflow. */
|
|
1363
|
+
drop(): void {
|
|
1364
|
+
if (this.dropped < 2147483647) {
|
|
1365
|
+
this.dropped += 1
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
/** Whether the connection has closed, either way. */
|
|
1370
|
+
closed(): boolean {
|
|
1371
|
+
return (
|
|
1372
|
+
this.state === QUIC_STATE_CLOSING ||
|
|
1373
|
+
this.state === QUIC_STATE_DRAINING ||
|
|
1374
|
+
this.state === QUIC_STATE_TIMED_OUT
|
|
1375
|
+
)
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
/**
|
|
1379
|
+
* Sets up a connection whose client came back with a Retry token that
|
|
1380
|
+
* `nish/net/quic-listener` checked (RFC 9000 §8.1.2): `originalDcid` is the
|
|
1381
|
+
* DCID of the client's Initial before the Retry, and `retryScid` the
|
|
1382
|
+
* Retry's SCID, which the client now sends to. The server's transport
|
|
1383
|
+
* parameters then name both (§7.3), and the client's address counts as
|
|
1384
|
+
* validated, so the anti-amplification limit does not apply. Call it
|
|
1385
|
+
* before the first `receive`; answers whether it took: not once the
|
|
1386
|
+
* connection has started, and not for an ID over 20 bytes.
|
|
1387
|
+
*/
|
|
1388
|
+
acceptRetry(originalDcid: u8[], retryScid: u8[]): boolean {
|
|
1389
|
+
if (
|
|
1390
|
+
this.state !== QUIC_STATE_WAIT_INITIAL ||
|
|
1391
|
+
toI32(originalDcid.length) > QUIC_MAX_CID_LENGTH ||
|
|
1392
|
+
toI32(retryScid.length) > QUIC_MAX_CID_LENGTH
|
|
1393
|
+
) {
|
|
1394
|
+
return false
|
|
1395
|
+
}
|
|
1396
|
+
quicConnRefill(this.retryOriginalDcid, originalDcid, QUIC_CONN_FROM, toI32(originalDcid.length))
|
|
1397
|
+
quicConnRefill(this.retryScid, retryScid, QUIC_CONN_FROM, toI32(retryScid.length))
|
|
1398
|
+
this.retried = true
|
|
1399
|
+
return true
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
/** Closes the connection with transport error `code`, caused by a frame of `frameType` (0 for none). */
|
|
1403
|
+
fail(code: i64, frameType: i64): void {
|
|
1404
|
+
if (this.closed()) {
|
|
1405
|
+
return
|
|
1406
|
+
}
|
|
1407
|
+
this.state = QUIC_STATE_CLOSING
|
|
1408
|
+
this.error = code
|
|
1409
|
+
this.errorFrameType = frameType
|
|
1410
|
+
this.errorIsApplication = false
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
/**
|
|
1414
|
+
* Closes the connection from the application, with its own error `code`
|
|
1415
|
+
* (CONNECTION_CLOSE 0x1d; RFC 9000 §10.2). The next `takeDatagram` answers
|
|
1416
|
+
* the close, and nothing after it. Closing twice, or after the client
|
|
1417
|
+
* closed, does nothing.
|
|
1418
|
+
*/
|
|
1419
|
+
close(code: i64): void {
|
|
1420
|
+
if (this.closed()) {
|
|
1421
|
+
return
|
|
1422
|
+
}
|
|
1423
|
+
this.state = QUIC_STATE_CLOSING
|
|
1424
|
+
this.error = code
|
|
1425
|
+
this.errorFrameType = 0
|
|
1426
|
+
this.errorIsApplication = true
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
/** The space of packets of `type`, or `null` for a 0-RTT or Retry packet. */
|
|
1430
|
+
spaceOf(type: i32): QuicConnSpace | null {
|
|
1431
|
+
if (type === QUIC_PACKET_INITIAL) {
|
|
1432
|
+
return this.initial
|
|
1433
|
+
}
|
|
1434
|
+
if (type === QUIC_PACKET_HANDSHAKE) {
|
|
1435
|
+
return this.handshake
|
|
1436
|
+
}
|
|
1437
|
+
if (type === QUIC_PACKET_SHORT) {
|
|
1438
|
+
return this.application
|
|
1439
|
+
}
|
|
1440
|
+
return null
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
/** The space of TLS level `level`. */
|
|
1444
|
+
spaceAt(level: i32): QuicConnSpace {
|
|
1445
|
+
if (level === TLS_LEVEL_INITIAL) {
|
|
1446
|
+
return this.initial
|
|
1447
|
+
}
|
|
1448
|
+
return level === TLS_LEVEL_HANDSHAKE ? this.handshake : this.application
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* Whether a datagram whose first packet is sent to `dcid` belongs to this
|
|
1453
|
+
* connection: one of its active connection IDs, or, until the handshake is
|
|
1454
|
+
* done, the DCID of the client's first Initial. A closed connection still
|
|
1455
|
+
* answers for its IDs; the caller drops it once it is done with it.
|
|
1456
|
+
*/
|
|
1457
|
+
ownsConnectionId(dcid: u8[]): boolean {
|
|
1458
|
+
return this.ownsConnectionIdAt(dcid, QUIC_CONN_FROM, toI32(dcid.length))
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
/** `ownsConnectionId` of the ID `buf[at .. at + length)`, read in place, so a carrier routes without a copy. */
|
|
1462
|
+
ownsConnectionIdAt(buf: u8[], at: i32, length: i32): boolean {
|
|
1463
|
+
if (this.cids.ownsLocalAt(buf, at, length)) {
|
|
1464
|
+
return true
|
|
1465
|
+
}
|
|
1466
|
+
return (
|
|
1467
|
+
!this.handshakeComplete &&
|
|
1468
|
+
this.originalDcidLength > 0 &&
|
|
1469
|
+
length === this.originalDcidLength &&
|
|
1470
|
+
timingSafeEqualAt(buf, at, this.originalDcid, QUIC_CONN_FROM, length)
|
|
1471
|
+
)
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
/** `receiveWindow` of a whole datagram. */
|
|
1475
|
+
receive(datagram: u8[], now: i64): i64 {
|
|
1476
|
+
return this.receiveWindow(datagram, QUIC_CONN_FROM, toI32(datagram.length), now)
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
/**
|
|
1480
|
+
* Takes one datagram from the client, `buf[off .. off + len)`, which
|
|
1481
|
+
* arrived at `now` (the caller's monotonic time in milliseconds): every
|
|
1482
|
+
* packet in it is opened and its frames acted on, in order. `buf` is read,
|
|
1483
|
+
* never written, so a GRO receive buffer is handed over a datagram at a
|
|
1484
|
+
* time. Answers 0, or the error the connection closed with — in which case
|
|
1485
|
+
* `takeDatagramInto` writes the CONNECTION_CLOSE to send. A packet that
|
|
1486
|
+
* cannot be used is dropped and counted (see the module header). Once the
|
|
1487
|
+
* connection has closed, or its idle timeout has passed by `now`, every
|
|
1488
|
+
* datagram is ignored.
|
|
1489
|
+
*/
|
|
1490
|
+
receiveWindow(buf: u8[], off: i32, len: i32, now: i64): i64 {
|
|
1491
|
+
this.runClocks(now)
|
|
1492
|
+
if (this.closed()) {
|
|
1493
|
+
return this.error
|
|
1494
|
+
}
|
|
1495
|
+
if (off < 0 || len < 0 || off > toI32(buf.length) - len) {
|
|
1496
|
+
this.drop()
|
|
1497
|
+
return 0
|
|
1498
|
+
}
|
|
1499
|
+
// §14.1: a client's first Initial comes in a datagram of at least 1200 bytes.
|
|
1500
|
+
if (this.state === QUIC_STATE_WAIT_INITIAL && len < QUIC_CONN_DATAGRAM_SIZE) {
|
|
1501
|
+
this.drop()
|
|
1502
|
+
return 0
|
|
1503
|
+
}
|
|
1504
|
+
const end: i32 = off + len
|
|
1505
|
+
let at: i32 = off
|
|
1506
|
+
let counted: boolean = false
|
|
1507
|
+
while (at < end) {
|
|
1508
|
+
if (quicParseHeaderInto(this.header, buf, at, end, QUIC_CONN_CID_LENGTH) !== QUIC_PACKET_OK) {
|
|
1509
|
+
this.drop()
|
|
1510
|
+
break
|
|
1511
|
+
}
|
|
1512
|
+
counted = this.receivePacket(buf, this.header) || counted
|
|
1513
|
+
const type: i32 = this.header.type
|
|
1514
|
+
const next: i32 = this.header.end
|
|
1515
|
+
if (this.closed() || type === QUIC_PACKET_SHORT || next <= at) {
|
|
1516
|
+
break
|
|
1517
|
+
}
|
|
1518
|
+
at = next
|
|
1519
|
+
}
|
|
1520
|
+
// A datagram counts toward the amplification limit once one of its packets
|
|
1521
|
+
// proves it is this connection's (RFC 9000 §8.1).
|
|
1522
|
+
if (counted) {
|
|
1523
|
+
this.bytesReceived = this.bytesReceived + toI64(len)
|
|
1524
|
+
}
|
|
1525
|
+
// Loss recovery's timer runs after the datagram, so an acknowledgement
|
|
1526
|
+
// it carries settles what it acknowledges before a probe is owed.
|
|
1527
|
+
this.runRecoveryTimer()
|
|
1528
|
+
return this.closed() ? this.error : QUIC_ERROR_NO_ERROR
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
/**
|
|
1532
|
+
* Starts the connection from the client's first Initial, `header`: the
|
|
1533
|
+
* original DCID and the client's SCID, and the Initial keys (RFC 9001
|
|
1534
|
+
* §5.2). Answers whether it could: the client's DCID must be at least 8
|
|
1535
|
+
* bytes (§7.2). The rest of the connection — its first local ID and the
|
|
1536
|
+
* `TlsServer` — is made by `establish` once the Initial has opened, so an
|
|
1537
|
+
* Initial that does not authenticate costs only its keys.
|
|
1538
|
+
*/
|
|
1539
|
+
start(buf: u8[], header: QuicHeader): boolean {
|
|
1540
|
+
if (
|
|
1541
|
+
header.dcidLength < 8 ||
|
|
1542
|
+
header.dcidLength > QUIC_MAX_CID_LENGTH ||
|
|
1543
|
+
header.scidLength > QUIC_MAX_CID_LENGTH
|
|
1544
|
+
) {
|
|
1545
|
+
return false
|
|
1546
|
+
}
|
|
1547
|
+
const secrets: boolean = quicInitialSecretsInto(
|
|
1548
|
+
this.kdf,
|
|
1549
|
+
buf,
|
|
1550
|
+
header.dcidStart,
|
|
1551
|
+
header.dcidLength,
|
|
1552
|
+
this.initialClient,
|
|
1553
|
+
this.initialServer
|
|
1554
|
+
)
|
|
1555
|
+
if (!secrets) {
|
|
1556
|
+
return false
|
|
1557
|
+
}
|
|
1558
|
+
this.initial.readKeys = quicKeysInto(
|
|
1559
|
+
this.kdf,
|
|
1560
|
+
this.initial.keySlot(QUIC_CONN_READ_SLOT),
|
|
1561
|
+
QUIC_AEAD_AES_128_GCM,
|
|
1562
|
+
this.initialClient
|
|
1563
|
+
)
|
|
1564
|
+
this.initial.writeKeys = quicKeysInto(
|
|
1565
|
+
this.kdf,
|
|
1566
|
+
this.initial.keySlot(QUIC_CONN_WRITE_SLOT),
|
|
1567
|
+
QUIC_AEAD_AES_128_GCM,
|
|
1568
|
+
this.initialServer
|
|
1569
|
+
)
|
|
1570
|
+
quicCidCopy(this.originalDcid, buf, header.dcidStart, header.dcidLength)
|
|
1571
|
+
this.originalDcidLength = header.dcidLength
|
|
1572
|
+
quicCidCopy(this.peerScid, buf, header.scidStart, header.scidLength)
|
|
1573
|
+
this.peerScidLength = header.scidLength
|
|
1574
|
+
this.state = QUIC_STATE_HANDSHAKE
|
|
1575
|
+
return true
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
/**
|
|
1579
|
+
* The rest of `start`, once the first Initial has opened: the first local
|
|
1580
|
+
* connection ID, and the slot's `TlsServer` started with transport
|
|
1581
|
+
* parameters that name both IDs (RFC 9000 §7.3), the Retry's when
|
|
1582
|
+
* `acceptRetry` set one up, and the first ID's stateless reset token
|
|
1583
|
+
* (§18.2).
|
|
1584
|
+
*/
|
|
1585
|
+
establish(): void {
|
|
1586
|
+
const none: u8[] = this.none
|
|
1587
|
+
const token: u8[] = this.tokenScratch
|
|
1588
|
+
quicConnResetTokenInto(
|
|
1589
|
+
this.kdf.sha256,
|
|
1590
|
+
this.config.statelessResetKey,
|
|
1591
|
+
this.localScid,
|
|
1592
|
+
QUIC_CONN_FROM,
|
|
1593
|
+
QUIC_CONN_CID_LENGTH,
|
|
1594
|
+
token,
|
|
1595
|
+
QUIC_CONN_FROM
|
|
1596
|
+
)
|
|
1597
|
+
this.cids.addLocal(this.localScid, token)
|
|
1598
|
+
this.cids.addPeerAt(
|
|
1599
|
+
QUIC_CONN_NONE,
|
|
1600
|
+
QUIC_CONN_NONE,
|
|
1601
|
+
this.peerScid,
|
|
1602
|
+
QUIC_CONN_FROM,
|
|
1603
|
+
this.peerScidLength,
|
|
1604
|
+
none,
|
|
1605
|
+
QUIC_CONN_FROM,
|
|
1606
|
+
false
|
|
1607
|
+
)
|
|
1608
|
+
// §8.1.2: a Retry token the listener checked has validated the address.
|
|
1609
|
+
this.addressValidated = this.retried
|
|
1610
|
+
|
|
1611
|
+
// The parameters, and their encoding, are rewritten in the slot's own
|
|
1612
|
+
// arrays, which grow only when a connection needs more than any before.
|
|
1613
|
+
const params: QuicTransportParameters = this.localParameters
|
|
1614
|
+
if (this.retried) {
|
|
1615
|
+
quicConnRefill(
|
|
1616
|
+
params.originalDcid,
|
|
1617
|
+
this.retryOriginalDcid,
|
|
1618
|
+
QUIC_CONN_FROM,
|
|
1619
|
+
toI32(this.retryOriginalDcid.length)
|
|
1620
|
+
)
|
|
1621
|
+
} else {
|
|
1622
|
+
quicConnRefill(params.originalDcid, this.originalDcid, QUIC_CONN_FROM, this.originalDcidLength)
|
|
1623
|
+
}
|
|
1624
|
+
params.hasOriginalDcid = true
|
|
1625
|
+
params.initialScid = this.localScid
|
|
1626
|
+
params.hasInitialScid = true
|
|
1627
|
+
quicConnRefill(params.retryScid, this.retryScid, QUIC_CONN_FROM, toI32(this.retryScid.length))
|
|
1628
|
+
params.hasRetryScid = this.retried
|
|
1629
|
+
quicConnRefill(params.statelessResetToken, token, QUIC_CONN_FROM, QUIC_RESET_TOKEN_SIZE)
|
|
1630
|
+
params.hasStatelessResetToken = true
|
|
1631
|
+
secureZero(token)
|
|
1632
|
+
params.maxIdleTimeout = this.config.maxIdleTimeout
|
|
1633
|
+
params.initialMaxData = this.config.maxData
|
|
1634
|
+
params.initialMaxStreamDataBidiRemote = this.config.maxStreamData
|
|
1635
|
+
params.initialMaxStreamsBidi = this.config.maxStreamsBidi
|
|
1636
|
+
// Each of these is advertised only when the configuration uses it, so a
|
|
1637
|
+
// configuration without them sends the parameters it always has.
|
|
1638
|
+
params.initialMaxStreamDataBidiLocal = this.config.localStreams > 0 ? this.config.maxStreamData : 0
|
|
1639
|
+
params.initialMaxStreamsUni = this.config.maxStreamsUni
|
|
1640
|
+
params.initialMaxStreamDataUni = this.config.maxStreamsUni > 0 ? this.config.maxStreamData : 0
|
|
1641
|
+
params.maxDatagramFrameSize = this.config.maxDatagramFrameSize
|
|
1642
|
+
params.activeConnectionIdLimit = this.config.activeConnectionIdLimit
|
|
1643
|
+
// The server never migrates and asks the client not to (§9): path
|
|
1644
|
+
// validation beyond answering PATH_CHALLENGE is not here.
|
|
1645
|
+
params.disableActiveMigration = true
|
|
1646
|
+
quicEncodeTransportParametersInto(params, this.localEncoded)
|
|
1647
|
+
// One `TlsServer` a slot, made by its first handshake (so its flight is
|
|
1648
|
+
// sized for these parameters) and restarted, wiped, by each after it.
|
|
1649
|
+
const tls: TlsServer | null = this.tlsSlot
|
|
1650
|
+
if (tls !== null) {
|
|
1651
|
+
tls.restart(this.serverRandom, this.ephemeralPrivate)
|
|
1652
|
+
this.tls = tls
|
|
1653
|
+
} else {
|
|
1654
|
+
const made: TlsServer = new TlsServer(this.tlsConfig, this.serverRandom, this.ephemeralPrivate)
|
|
1655
|
+
this.tlsSlot = made
|
|
1656
|
+
this.tls = made
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
/**
|
|
1661
|
+
* One packet of a datagram. Answers whether the packet was used: whether it
|
|
1662
|
+
* authenticated as this connection's.
|
|
1663
|
+
*/
|
|
1664
|
+
receivePacket(buf: u8[], header: QuicHeader): boolean {
|
|
1665
|
+
const type: i32 = header.type
|
|
1666
|
+
const first: boolean = this.state === QUIC_STATE_WAIT_INITIAL
|
|
1667
|
+
if (first && (type !== QUIC_PACKET_INITIAL || !this.start(buf, header))) {
|
|
1668
|
+
this.drop()
|
|
1669
|
+
return false
|
|
1670
|
+
}
|
|
1671
|
+
const used: boolean = this.openPacket(buf, header, first)
|
|
1672
|
+
// A first Initial that does not open leaves nothing behind but its keys:
|
|
1673
|
+
// the next datagram is read as a first Initial again.
|
|
1674
|
+
if (first && !used) {
|
|
1675
|
+
this.unstart()
|
|
1676
|
+
}
|
|
1677
|
+
return used
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
/** Undoes `start`, for a first Initial that turned out not to authenticate. */
|
|
1681
|
+
unstart(): void {
|
|
1682
|
+
this.state = QUIC_STATE_WAIT_INITIAL
|
|
1683
|
+
this.addressValidated = false
|
|
1684
|
+
this.idleSince = -1
|
|
1685
|
+
this.initial.reset()
|
|
1686
|
+
this.originalDcidLength = 0
|
|
1687
|
+
this.peerScidLength = 0
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
/**
|
|
1691
|
+
* Opens one packet of a connection already started, and acts on its frames.
|
|
1692
|
+
* The opening and the frames run inside an arena block (`openScoped`), so
|
|
1693
|
+
* their temporaries go when it ends; what may grow a buffer of the slot —
|
|
1694
|
+
* a first Initial's `TlsServer` and parameters, a key update, TLS reading
|
|
1695
|
+
* the CRYPTO bytes and what it answers — runs after it. Answers whether
|
|
1696
|
+
* the packet was used.
|
|
1697
|
+
*/
|
|
1698
|
+
openPacket(buf: u8[], header: QuicHeader, first: boolean): boolean {
|
|
1699
|
+
const type: i32 = header.type
|
|
1700
|
+
const space: QuicConnSpace | null = this.spaceOf(type)
|
|
1701
|
+
if (space === null || !this.addressedHere(buf, header)) {
|
|
1702
|
+
this.drop()
|
|
1703
|
+
return false
|
|
1704
|
+
}
|
|
1705
|
+
const keys: QuicKeys | null = space.readKeys
|
|
1706
|
+
// RFC 9001 §5.7: no 1-RTT packet is processed before the handshake is complete.
|
|
1707
|
+
if (keys === null || space.discarded || (type === QUIC_PACKET_SHORT && !this.handshakeComplete)) {
|
|
1708
|
+
this.drop()
|
|
1709
|
+
return false
|
|
1710
|
+
}
|
|
1711
|
+
this.cryptoArrived = false
|
|
1712
|
+
this.topUpOwed = false
|
|
1713
|
+
this.phasePending = -1
|
|
1714
|
+
this.resendLevel = -1
|
|
1715
|
+
let used: boolean = false
|
|
1716
|
+
{
|
|
1717
|
+
using _scope = arena()
|
|
1718
|
+
used = this.openScoped(buf, header, space, keys)
|
|
1719
|
+
}
|
|
1720
|
+
if (!used) {
|
|
1721
|
+
return false
|
|
1722
|
+
}
|
|
1723
|
+
if (first) {
|
|
1724
|
+
this.establish()
|
|
1725
|
+
}
|
|
1726
|
+
if (this.phasePending >= 0 && !this.closed()) {
|
|
1727
|
+
this.applyPhase(this.phasePending)
|
|
1728
|
+
}
|
|
1729
|
+
if (this.closed()) {
|
|
1730
|
+
return true
|
|
1731
|
+
}
|
|
1732
|
+
if (type === QUIC_PACKET_HANDSHAKE) {
|
|
1733
|
+
// §8.1: a Handshake packet proves the client holds its address; RFC
|
|
1734
|
+
// 9001 §4.9.1: the server is then done with the Initial keys.
|
|
1735
|
+
this.addressValidated = true
|
|
1736
|
+
this.discard(this.initial)
|
|
1737
|
+
}
|
|
1738
|
+
if (this.cryptoArrived) {
|
|
1739
|
+
this.feedTls()
|
|
1740
|
+
}
|
|
1741
|
+
this.afterTls()
|
|
1742
|
+
if (this.topUpOwed) {
|
|
1743
|
+
this.topUpConnectionIds()
|
|
1744
|
+
}
|
|
1745
|
+
if (this.resendLevel >= 0 && !this.closed()) {
|
|
1746
|
+
this.resendEarly(this.resendLevel)
|
|
1747
|
+
}
|
|
1748
|
+
return true
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
/**
|
|
1752
|
+
* `openPacket`'s half inside the arena block: header protection off, the
|
|
1753
|
+
* payload decrypted (with the other key generation's keys when the Key
|
|
1754
|
+
* Phase bit says so), and every frame acted on. Nothing it calls stores
|
|
1755
|
+
* an allocation, which is what lets the block release the packet's
|
|
1756
|
+
* temporaries. Answers whether the packet was used rather than dropped.
|
|
1757
|
+
*/
|
|
1758
|
+
openScoped(buf: u8[], header: QuicHeader, space: QuicConnSpace, keys: QuicKeys): boolean {
|
|
1759
|
+
const packet: QuicPacket = this.packet
|
|
1760
|
+
// Every key generation shares the header-protection key (RFC 9001 §6),
|
|
1761
|
+
// so the current keys take it off whatever the Key Phase bit says.
|
|
1762
|
+
const clear: u8[] = quicUnprotectHeader(keys, buf, header, space.received.largest, packet)
|
|
1763
|
+
const type: i32 = header.type
|
|
1764
|
+
const other: boolean = type === QUIC_PACKET_SHORT && packet.keyPhase !== this.readPhase
|
|
1765
|
+
const open: QuicKeys | null = other ? this.otherReadKeys : keys
|
|
1766
|
+
if (open === null || packet.packetNumber < 0) {
|
|
1767
|
+
this.drop()
|
|
1768
|
+
return false
|
|
1769
|
+
}
|
|
1770
|
+
const payload: u8[] | null = quicDecryptPayload(open, buf, header, clear, packet)
|
|
1771
|
+
if (payload === null) {
|
|
1772
|
+
this.drop()
|
|
1773
|
+
return false
|
|
1774
|
+
}
|
|
1775
|
+
// §17.2, §17.3.1: reserved bits set under a valid tag close the connection.
|
|
1776
|
+
if (packet.error === QUIC_ERR_RESERVED_BITS) {
|
|
1777
|
+
this.fail(QUIC_ERROR_PROTOCOL_VIOLATION, QUIC_CONN_NONE)
|
|
1778
|
+
return true
|
|
1779
|
+
}
|
|
1780
|
+
const pn: i64 = packet.packetNumber
|
|
1781
|
+
if (space.received.contains(pn)) {
|
|
1782
|
+
this.drop()
|
|
1783
|
+
return true
|
|
1784
|
+
}
|
|
1785
|
+
if (type === QUIC_PACKET_SHORT && !this.notePhase(pn, other)) {
|
|
1786
|
+
return true
|
|
1787
|
+
}
|
|
1788
|
+
const eliciting: boolean = this.receiveFrames(space, type, payload, buf, header)
|
|
1789
|
+
if (this.closed()) {
|
|
1790
|
+
return true
|
|
1791
|
+
}
|
|
1792
|
+
// RFC 9002 §6.2.3: a client that sends an ack-eliciting Handshake packet
|
|
1793
|
+
// while the server's Handshake data is in flight may not have it all.
|
|
1794
|
+
if (eliciting && type === QUIC_PACKET_HANDSHAKE) {
|
|
1795
|
+
this.resendLevel = TLS_LEVEL_HANDSHAKE
|
|
1796
|
+
}
|
|
1797
|
+
space.received.record(pn, eliciting)
|
|
1798
|
+
// §10.1: a packet received and processed restarts the idle timer.
|
|
1799
|
+
this.idleSince = this.now
|
|
1800
|
+
this.elicitingSent = false
|
|
1801
|
+
return true
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
/**
|
|
1805
|
+
* Checks a 1-RTT packet `pn` that opened under the current read keys, or,
|
|
1806
|
+
* when `other`, under the other set (RFC 9001 §6.2, §6.4, §6.5). Under the
|
|
1807
|
+
* next generation's it is a key update, which `applyPhase` carries out
|
|
1808
|
+
* once the packet is read. Under the previous generation's it is a packet
|
|
1809
|
+
* the network delayed. Either is KEY_UPDATE_ERROR when it breaks §6.4's
|
|
1810
|
+
* order — a higher packet number under older keys than a lower one
|
|
1811
|
+
* already had — and an update past `QUIC_CONN_MAX_KEY_UPDATES` is too.
|
|
1812
|
+
* Answers whether the connection is still open.
|
|
1813
|
+
*/
|
|
1814
|
+
notePhase(pn: i64, other: boolean): boolean {
|
|
1815
|
+
if (other && !this.otherIsNext) {
|
|
1816
|
+
if (this.readPhaseLowest >= 0 && pn > this.readPhaseLowest) {
|
|
1817
|
+
this.fail(QUIC_ERROR_KEY_UPDATE, QUIC_CONN_NONE)
|
|
1818
|
+
return false
|
|
1819
|
+
}
|
|
1820
|
+
return true
|
|
1821
|
+
}
|
|
1822
|
+
if (!other) {
|
|
1823
|
+
if (this.readPhaseLowest < 0 || pn < this.readPhaseLowest) {
|
|
1824
|
+
this.readPhaseLowest = pn
|
|
1825
|
+
}
|
|
1826
|
+
if (pn > this.readPhaseHighest) {
|
|
1827
|
+
this.readPhaseHighest = pn
|
|
1828
|
+
}
|
|
1829
|
+
return true
|
|
1830
|
+
}
|
|
1831
|
+
if (pn < this.readPhaseHighest) {
|
|
1832
|
+
this.fail(QUIC_ERROR_KEY_UPDATE, QUIC_CONN_NONE)
|
|
1833
|
+
return false
|
|
1834
|
+
}
|
|
1835
|
+
if (this.writePhase === this.readPhase && this.keyUpdates >= QUIC_CONN_MAX_KEY_UPDATES) {
|
|
1836
|
+
this.fail(QUIC_ERROR_KEY_UPDATE, QUIC_CONN_NONE)
|
|
1837
|
+
return false
|
|
1838
|
+
}
|
|
1839
|
+
this.phasePending = pn
|
|
1840
|
+
return true
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
/**
|
|
1844
|
+
* Carries out the client's key update `notePhase` found in packet `pn`
|
|
1845
|
+
* (RFC 9001 §6.2): the write keys follow, unless the server started this
|
|
1846
|
+
* update, before anything acknowledges the packet; the read keys move on,
|
|
1847
|
+
* and the previous ones are kept for a probe timeout.
|
|
1848
|
+
*/
|
|
1849
|
+
applyPhase(pn: i64): void {
|
|
1850
|
+
if (this.writePhase === this.readPhase) {
|
|
1851
|
+
this.keyUpdates = this.keyUpdates + 1
|
|
1852
|
+
if (!this.updateWriteKeys()) {
|
|
1853
|
+
this.fail(QUIC_ERROR_INTERNAL, QUIC_CONN_NONE)
|
|
1854
|
+
return
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
const previous: QuicKeys | null = this.application.readKeys
|
|
1858
|
+
this.application.readKeys = this.otherReadKeys
|
|
1859
|
+
this.otherReadKeys = previous
|
|
1860
|
+
this.otherIsNext = false
|
|
1861
|
+
this.appReadSlot = 1 - this.appReadSlot
|
|
1862
|
+
secureZero(this.appReadSecret)
|
|
1863
|
+
this.appReadSecret = this.nextReadSecret
|
|
1864
|
+
this.readSecretCurrent = this.readSecretCurrent === 0 ? 1 : 0
|
|
1865
|
+
this.nextReadSecret = this.none
|
|
1866
|
+
this.readPhase = !this.readPhase
|
|
1867
|
+
this.readPhaseLowest = pn
|
|
1868
|
+
this.readPhaseHighest = pn
|
|
1869
|
+
this.keyRetainUntil = this.now + this.recovery.probeTimeout()
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
/**
|
|
1873
|
+
* Moves the write keys to the next generation (RFC 9001 §6.1): the next
|
|
1874
|
+
* secret by `quic ku`, its key and IV, the Key Phase bit toggled; the old
|
|
1875
|
+
* key, IV and secret wiped. Answers whether it could, which it cannot only
|
|
1876
|
+
* for keys `quicKeys` did not make.
|
|
1877
|
+
*/
|
|
1878
|
+
updateWriteKeys(): boolean {
|
|
1879
|
+
const keys: QuicKeys | null = this.application.writeKeys
|
|
1880
|
+
if (keys === null) {
|
|
1881
|
+
return false
|
|
1882
|
+
}
|
|
1883
|
+
const target: i32 = this.writeSecretCurrent === 0 ? 1 : 0
|
|
1884
|
+
const secret: u8[] = quicConnSecretAt(this.writeSecrets, keys.aead, target)
|
|
1885
|
+
if (!quicKeyUpdateSecretInto(this.kdf, keys.aead, this.appWriteSecret, secret)) {
|
|
1886
|
+
return false
|
|
1887
|
+
}
|
|
1888
|
+
const slot: QuicKeysSlot = this.application.keySlot(QUIC_CONN_APP_WRITE_SLOTS + 1 - this.appWriteSlot)
|
|
1889
|
+
const next: QuicKeys | null = quicKeysUpdateInto(this.kdf, slot, keys, secret)
|
|
1890
|
+
if (next === null) {
|
|
1891
|
+
secureZero(secret)
|
|
1892
|
+
return false
|
|
1893
|
+
}
|
|
1894
|
+
quicConnWipePacketKey(keys)
|
|
1895
|
+
secureZero(this.appWriteSecret)
|
|
1896
|
+
this.application.writeKeys = next
|
|
1897
|
+
this.appWriteSecret = secret
|
|
1898
|
+
this.writeSecretCurrent = target
|
|
1899
|
+
this.appWriteSlot = 1 - this.appWriteSlot
|
|
1900
|
+
this.writePhase = !this.writePhase
|
|
1901
|
+
this.writePhaseFirst = this.application.nextPn
|
|
1902
|
+
return true
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
/**
|
|
1906
|
+
* Derives the next generation's read secret and keys from the current
|
|
1907
|
+
* ones, into `otherReadKeys`, wiping the previous generation's key and IV
|
|
1908
|
+
* that sat there (RFC 9001 §6.3, §6.5). It runs when the 1-RTT keys are
|
|
1909
|
+
* installed and a probe timeout after each update, never while a packet is
|
|
1910
|
+
* being opened, so what a packet's Key Phase bit says shows in no timing.
|
|
1911
|
+
*/
|
|
1912
|
+
prepareNextReadKeys(): void {
|
|
1913
|
+
const keys: QuicKeys | null = this.application.readKeys
|
|
1914
|
+
quicConnWipePacketKey(this.otherReadKeys)
|
|
1915
|
+
this.otherReadKeys = null
|
|
1916
|
+
this.otherIsNext = false
|
|
1917
|
+
this.keyRetainUntil = -1
|
|
1918
|
+
if (keys === null) {
|
|
1919
|
+
return
|
|
1920
|
+
}
|
|
1921
|
+
const secret: u8[] = quicConnSecretAt(this.readSecrets, keys.aead, this.readSecretCurrent === 0 ? 1 : 0)
|
|
1922
|
+
if (!quicKeyUpdateSecretInto(this.kdf, keys.aead, this.appReadSecret, secret)) {
|
|
1923
|
+
return
|
|
1924
|
+
}
|
|
1925
|
+
this.nextReadSecret = secret
|
|
1926
|
+
this.otherReadKeys = quicKeysUpdateInto(
|
|
1927
|
+
this.kdf,
|
|
1928
|
+
this.application.keySlot(1 - this.appReadSlot),
|
|
1929
|
+
keys,
|
|
1930
|
+
secret
|
|
1931
|
+
)
|
|
1932
|
+
this.otherIsNext = this.otherReadKeys !== null
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
/**
|
|
1936
|
+
* Whether `header`'s connection IDs are this connection's: a long header
|
|
1937
|
+
* from the client's first SCID to the server's ID (or, for an Initial,
|
|
1938
|
+
* the original DCID), a short header to an active local ID. Read in place
|
|
1939
|
+
* from `buf`.
|
|
1940
|
+
*/
|
|
1941
|
+
addressedHere(buf: u8[], header: QuicHeader): boolean {
|
|
1942
|
+
if (header.type === QUIC_PACKET_SHORT) {
|
|
1943
|
+
return this.cids.ownsLocalAt(buf, header.dcidStart, header.dcidLength)
|
|
1944
|
+
}
|
|
1945
|
+
if (
|
|
1946
|
+
header.scidLength !== this.peerScidLength ||
|
|
1947
|
+
!timingSafeEqualAt(buf, header.scidStart, this.peerScid, QUIC_CONN_FROM, this.peerScidLength)
|
|
1948
|
+
) {
|
|
1949
|
+
return false
|
|
1950
|
+
}
|
|
1951
|
+
if (
|
|
1952
|
+
header.dcidLength === QUIC_CONN_CID_LENGTH &&
|
|
1953
|
+
timingSafeEqualAt(buf, header.dcidStart, this.localScid, QUIC_CONN_FROM, QUIC_CONN_CID_LENGTH)
|
|
1954
|
+
) {
|
|
1955
|
+
return true
|
|
1956
|
+
}
|
|
1957
|
+
return (
|
|
1958
|
+
header.type === QUIC_PACKET_INITIAL &&
|
|
1959
|
+
header.dcidLength === this.originalDcidLength &&
|
|
1960
|
+
timingSafeEqualAt(buf, header.dcidStart, this.originalDcid, QUIC_CONN_FROM, this.originalDcidLength)
|
|
1961
|
+
)
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
/**
|
|
1965
|
+
* Acts on every frame of a packet's payload, in order, and answers whether
|
|
1966
|
+
* any asked for an acknowledgement. A payload with no frames, a frame that
|
|
1967
|
+
* does not parse and a frame the packet type may not carry close the
|
|
1968
|
+
* connection (RFC 9000 §12.4).
|
|
1969
|
+
*/
|
|
1970
|
+
receiveFrames(
|
|
1971
|
+
space: QuicConnSpace,
|
|
1972
|
+
packetType: i32,
|
|
1973
|
+
payload: u8[],
|
|
1974
|
+
buf: u8[],
|
|
1975
|
+
header: QuicHeader
|
|
1976
|
+
): boolean {
|
|
1977
|
+
const end: i32 = toI32(payload.length)
|
|
1978
|
+
if (end === 0) {
|
|
1979
|
+
this.fail(QUIC_ERROR_PROTOCOL_VIOLATION, QUIC_CONN_NONE)
|
|
1980
|
+
return false
|
|
1981
|
+
}
|
|
1982
|
+
const frame: QuicFrame = this.frame
|
|
1983
|
+
let eliciting: boolean = false
|
|
1984
|
+
let at: i32 = 0
|
|
1985
|
+
while (at < end) {
|
|
1986
|
+
const error: i64 = quicParseFrame(frame, payload, at, end)
|
|
1987
|
+
if (error !== QUIC_ERROR_NO_ERROR) {
|
|
1988
|
+
this.fail(error, toI64(frame.type))
|
|
1989
|
+
return eliciting
|
|
1990
|
+
}
|
|
1991
|
+
if (!quicFrameAllowed(frame.type, packetType)) {
|
|
1992
|
+
this.fail(QUIC_ERROR_PROTOCOL_VIOLATION, toI64(frame.type))
|
|
1993
|
+
return eliciting
|
|
1994
|
+
}
|
|
1995
|
+
eliciting = eliciting || quicFrameAckEliciting(frame.type)
|
|
1996
|
+
this.receiveFrame(space, frame, payload, at, buf, header)
|
|
1997
|
+
if (this.closed() || frame.end <= at) {
|
|
1998
|
+
return eliciting
|
|
1999
|
+
}
|
|
2000
|
+
at = frame.end
|
|
2001
|
+
}
|
|
2002
|
+
return eliciting
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
/** Closes the connection with `error` from a stream frame of `type`, unless it is 0. */
|
|
2006
|
+
failOn(error: i64, type: i64): void {
|
|
2007
|
+
if (error !== QUIC_ERROR_NO_ERROR) {
|
|
2008
|
+
this.fail(error, type)
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
/**
|
|
2013
|
+
* One frame, which the packet type is allowed to carry: it starts at
|
|
2014
|
+
* `payload[frameStart]`, and the packet's header is `header` in `buf`.
|
|
2015
|
+
*/
|
|
2016
|
+
receiveFrame(
|
|
2017
|
+
space: QuicConnSpace,
|
|
2018
|
+
frame: QuicFrame,
|
|
2019
|
+
payload: u8[],
|
|
2020
|
+
frameStart: i32,
|
|
2021
|
+
buf: u8[],
|
|
2022
|
+
header: QuicHeader
|
|
2023
|
+
): void {
|
|
2024
|
+
const type: i64 = toI64(frame.type)
|
|
2025
|
+
const streams: QuicStreams = this.streams
|
|
2026
|
+
switch (frame.type) {
|
|
2027
|
+
case QUIC_FRAME_ACK:
|
|
2028
|
+
case QUIC_FRAME_ACK_ECN:
|
|
2029
|
+
this.receiveAck(space, frame)
|
|
2030
|
+
break
|
|
2031
|
+
case QUIC_FRAME_CRYPTO:
|
|
2032
|
+
this.receiveCrypto(space, frame, payload)
|
|
2033
|
+
break
|
|
2034
|
+
case QUIC_FRAME_STREAM:
|
|
2035
|
+
this.failOn(
|
|
2036
|
+
streams.onStream(
|
|
2037
|
+
frame.streamId,
|
|
2038
|
+
frame.offset,
|
|
2039
|
+
payload,
|
|
2040
|
+
frame.dataStart,
|
|
2041
|
+
frame.dataLength,
|
|
2042
|
+
frame.fin
|
|
2043
|
+
),
|
|
2044
|
+
type
|
|
2045
|
+
)
|
|
2046
|
+
break
|
|
2047
|
+
case QUIC_FRAME_RESET_STREAM:
|
|
2048
|
+
this.failOn(streams.onReset(frame.streamId, frame.errorCode, frame.value), type)
|
|
2049
|
+
break
|
|
2050
|
+
case QUIC_FRAME_STOP_SENDING:
|
|
2051
|
+
this.failOn(streams.onStopSending(frame.streamId, frame.errorCode), type)
|
|
2052
|
+
break
|
|
2053
|
+
case QUIC_FRAME_MAX_STREAM_DATA:
|
|
2054
|
+
this.failOn(streams.onMaxStreamData(frame.streamId, frame.value), type)
|
|
2055
|
+
break
|
|
2056
|
+
case QUIC_FRAME_STREAM_DATA_BLOCKED:
|
|
2057
|
+
this.failOn(streams.onStreamDataBlocked(frame.streamId), type)
|
|
2058
|
+
break
|
|
2059
|
+
case QUIC_FRAME_MAX_DATA:
|
|
2060
|
+
streams.onMaxData(frame.value)
|
|
2061
|
+
break
|
|
2062
|
+
case QUIC_FRAME_MAX_STREAMS_BIDI:
|
|
2063
|
+
streams.onMaxStreams(false, frame.value)
|
|
2064
|
+
break
|
|
2065
|
+
case QUIC_FRAME_MAX_STREAMS_UNI:
|
|
2066
|
+
streams.onMaxStreams(true, frame.value)
|
|
2067
|
+
break
|
|
2068
|
+
case QUIC_FRAME_DATAGRAM:
|
|
2069
|
+
this.receiveDatagram(frame, payload, frameStart)
|
|
2070
|
+
break
|
|
2071
|
+
case QUIC_FRAME_NEW_CONNECTION_ID: {
|
|
2072
|
+
// §19.15: a client that chose a zero-length connection ID has none to give.
|
|
2073
|
+
const error: i64 =
|
|
2074
|
+
this.peerScidLength === 0
|
|
2075
|
+
? QUIC_ERROR_PROTOCOL_VIOLATION
|
|
2076
|
+
: this.cids.addPeerAt(
|
|
2077
|
+
frame.value,
|
|
2078
|
+
frame.retirePriorTo,
|
|
2079
|
+
payload,
|
|
2080
|
+
frame.connectionIdStart,
|
|
2081
|
+
frame.connectionIdLength,
|
|
2082
|
+
payload,
|
|
2083
|
+
frame.resetTokenStart,
|
|
2084
|
+
true
|
|
2085
|
+
)
|
|
2086
|
+
this.failOn(error, type)
|
|
2087
|
+
break
|
|
2088
|
+
}
|
|
2089
|
+
case QUIC_FRAME_RETIRE_CONNECTION_ID: {
|
|
2090
|
+
const error: i64 = this.cids.retireLocal(frame.value, buf, header.dcidStart, header.dcidLength)
|
|
2091
|
+
this.failOn(error, type)
|
|
2092
|
+
this.topUpOwed = error === QUIC_ERROR_NO_ERROR
|
|
2093
|
+
break
|
|
2094
|
+
}
|
|
2095
|
+
case QUIC_FRAME_PATH_CHALLENGE:
|
|
2096
|
+
this.receivePathChallenge(frame, payload)
|
|
2097
|
+
break
|
|
2098
|
+
// A PATH_RESPONSE answers no PATH_CHALLENGE, since the server sends none
|
|
2099
|
+
// (§19.18); NEW_TOKEN and HANDSHAKE_DONE go only to a client (§19.7,
|
|
2100
|
+
// §19.20). Each is a PROTOCOL_VIOLATION here.
|
|
2101
|
+
case QUIC_FRAME_PATH_RESPONSE:
|
|
2102
|
+
case QUIC_FRAME_NEW_TOKEN:
|
|
2103
|
+
case QUIC_FRAME_HANDSHAKE_DONE:
|
|
2104
|
+
this.fail(QUIC_ERROR_PROTOCOL_VIOLATION, type)
|
|
2105
|
+
break
|
|
2106
|
+
case QUIC_FRAME_CONNECTION_CLOSE:
|
|
2107
|
+
this.drain(frame.errorCode, false)
|
|
2108
|
+
break
|
|
2109
|
+
case QUIC_FRAME_CONNECTION_CLOSE_APP:
|
|
2110
|
+
this.drain(frame.errorCode, true)
|
|
2111
|
+
break
|
|
2112
|
+
default:
|
|
2113
|
+
// PADDING, PING, DATA_BLOCKED and STREAMS_BLOCKED ask nothing: credit
|
|
2114
|
+
// and limits follow what the application reads and finishes, not
|
|
2115
|
+
// what the client asks for.
|
|
2116
|
+
break
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
/** The client closed the connection: enter the draining state, sending nothing more (RFC 9000 §10.2.2). */
|
|
2121
|
+
drain(code: i64, application: boolean): void {
|
|
2122
|
+
this.state = QUIC_STATE_DRAINING
|
|
2123
|
+
this.error = code
|
|
2124
|
+
this.errorIsApplication = application
|
|
2125
|
+
this.closeSent = true
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
/**
|
|
2129
|
+
* An ACK frame: acknowledging a packet number this space never sent is a
|
|
2130
|
+
* PROTOCOL_VIOLATION (§13.1). Otherwise loss recovery reads it (RFC 9002
|
|
2131
|
+
* §5, §6.1, §7): each packet it newly acknowledges releases what it
|
|
2132
|
+
* carried, and each it shows lost has its frames queued again.
|
|
2133
|
+
*/
|
|
2134
|
+
receiveAck(space: QuicConnSpace, frame: QuicFrame): void {
|
|
2135
|
+
if (frame.largest >= space.nextPn) {
|
|
2136
|
+
this.fail(QUIC_ERROR_PROTOCOL_VIOLATION, toI64(frame.type))
|
|
2137
|
+
return
|
|
2138
|
+
}
|
|
2139
|
+
if (frame.largest > space.largestAcked) {
|
|
2140
|
+
space.largestAcked = frame.largest
|
|
2141
|
+
}
|
|
2142
|
+
const sent: QuicSentPackets | null = this.recovery.space(space.level)
|
|
2143
|
+
const delay: i64 = this.ackDelayOf(frame.ackDelay)
|
|
2144
|
+
if (
|
|
2145
|
+
sent === null ||
|
|
2146
|
+
this.recovery.onAck(space.level, frame.ackRanges, frame.ackRangeCount, delay, this.now) <= 0
|
|
2147
|
+
) {
|
|
2148
|
+
return
|
|
2149
|
+
}
|
|
2150
|
+
for (let k: i32 = 0; k < sent.ackedCount; k += 1) {
|
|
2151
|
+
this.untrack(space, sent.ackedSlot(k))
|
|
2152
|
+
}
|
|
2153
|
+
this.packetsLost(space, sent)
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
/**
|
|
2157
|
+
* An ACK frame's ACK Delay in milliseconds: the field scaled by the
|
|
2158
|
+
* client's `ack_delay_exponent` (RFC 9000 §19.3, §18.2). A value too large
|
|
2159
|
+
* to scale is held at 2^40 microseconds first, which is still far past any
|
|
2160
|
+
* `max_ack_delay` it is then bounded by (RFC 9002 §5.3).
|
|
2161
|
+
*/
|
|
2162
|
+
ackDelayOf(field: i64): i64 {
|
|
2163
|
+
let exponent: i64 = this.peerParameters.ackDelayExponent
|
|
2164
|
+
if (exponent < 0 || exponent > 20) {
|
|
2165
|
+
exponent = 3
|
|
2166
|
+
}
|
|
2167
|
+
const ceiling: i64 = 1099511627776
|
|
2168
|
+
const value: i64 = field > ceiling || field < 0 ? ceiling : field
|
|
2169
|
+
return (value << exponent) / 1000
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
/** Every packet of `space` that loss recovery's last call declared lost: what each carried is queued again. */
|
|
2173
|
+
packetsLost(space: QuicConnSpace, sent: QuicSentPackets): void {
|
|
2174
|
+
for (let k: i32 = 0; k < sent.lostCount; k += 1) {
|
|
2175
|
+
this.requeue(space, sent.lostSlot(k))
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
/**
|
|
2180
|
+
* Queues again what the packet in row `row` of `space` carried (RFC 9000
|
|
2181
|
+
* §13.3): its CRYPTO range, its STREAM chunks with their FIN, HANDSHAKE_DONE,
|
|
2182
|
+
* the NEW_CONNECTION_ID of an ID still active, the RETIRE_CONNECTION_ID of
|
|
2183
|
+
* one not queued already, and each stream control frame that still says
|
|
2184
|
+
* something. A DATAGRAM is never in a record, so it never goes again.
|
|
2185
|
+
*/
|
|
2186
|
+
requeue(space: QuicConnSpace, row: i32): void {
|
|
2187
|
+
space.resendCrypto(space.cryptoOffset(row), space.cryptoLength(row))
|
|
2188
|
+
if ((space.flags(row) & QUIC_CONN_SENT_HANDSHAKE_DONE) !== 0) {
|
|
2189
|
+
this.handshakeDonePending = true
|
|
2190
|
+
}
|
|
2191
|
+
const streams: i32 = space.streamCount(row)
|
|
2192
|
+
for (let j: i32 = 0; j < streams; j += 1) {
|
|
2193
|
+
const at: i32 = row * QUIC_CONN_PACKET_STREAMS + j
|
|
2194
|
+
if (
|
|
2195
|
+
at >= 0 &&
|
|
2196
|
+
at < toI32(space.sentStreamId.length) &&
|
|
2197
|
+
at < toI32(space.sentStreamOffset.length) &&
|
|
2198
|
+
at < toI32(space.sentStreamLength.length) &&
|
|
2199
|
+
at < toI32(space.sentStreamFin.length)
|
|
2200
|
+
) {
|
|
2201
|
+
this.streams.chunkLost(
|
|
2202
|
+
space.sentStreamId[at],
|
|
2203
|
+
space.sentStreamOffset[at],
|
|
2204
|
+
space.sentStreamLength[at],
|
|
2205
|
+
toI32(space.sentStreamFin[at]) !== 0
|
|
2206
|
+
)
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
const control: i32 = space.controlCount(row)
|
|
2210
|
+
for (let j: i32 = 0; j < control; j += 1) {
|
|
2211
|
+
const at: i32 = row * QUIC_CONN_PACKET_CONTROL + j
|
|
2212
|
+
if (at >= 0 && at < toI32(space.sentControlKind.length) && at < toI32(space.sentControlValue.length)) {
|
|
2213
|
+
this.requeueControl(toI32(space.sentControlKind[at]), space.sentControlValue[at])
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
/** A lost control frame of kind `kind` (a sequence number or a stream in `value`), queued again unless it no longer matters. */
|
|
2219
|
+
requeueControl(kind: i32, value: i64): void {
|
|
2220
|
+
if (kind === QUIC_CONN_CONTROL_NEW_CID) {
|
|
2221
|
+
for (const entry of this.cids.local) {
|
|
2222
|
+
if (entry.used && entry.sequence === value) {
|
|
2223
|
+
entry.announced = false
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
return
|
|
2227
|
+
}
|
|
2228
|
+
if (kind === QUIC_CONN_CONTROL_RETIRE) {
|
|
2229
|
+
this.cids.queueRetire(value)
|
|
2230
|
+
return
|
|
2231
|
+
}
|
|
2232
|
+
this.streams.controlLost(kind, value)
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
/**
|
|
2236
|
+
* A probe timeout fired (RFC 9002 §6.2.4): in every space with packets in
|
|
2237
|
+
* flight, everything they carry is queued again and the next packets are
|
|
2238
|
+
* probes, sent whatever the window says, with a PING if nothing is left to
|
|
2239
|
+
* carry. During the handshake that resends the Initial and the Handshake
|
|
2240
|
+
* flight together, which is what a client that lost both needs, and does
|
|
2241
|
+
* it twice (`QUIC_CONN_HANDSHAKE_PROBES`), so that one more lost datagram
|
|
2242
|
+
* does not cost another, doubled, probe timeout.
|
|
2243
|
+
*/
|
|
2244
|
+
probe(): void {
|
|
2245
|
+
for (let level: i32 = 0; level < 3; level += 1) {
|
|
2246
|
+
const space: QuicConnSpace = this.spaceAt(level)
|
|
2247
|
+
const sent: QuicSentPackets | null = this.recovery.space(level)
|
|
2248
|
+
if (sent === null || space.discarded || sent.inFlight === 0) {
|
|
2249
|
+
continue
|
|
2250
|
+
}
|
|
2251
|
+
for (let k: i32 = 0; k < sent.count; k += 1) {
|
|
2252
|
+
const slot: i32 = sent.slot(k)
|
|
2253
|
+
if (sent.inFlightAt(slot)) {
|
|
2254
|
+
this.requeue(space, slot)
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
space.probes = QUIC_CONN_HANDSHAKE_PROBES
|
|
2258
|
+
if (level === TLS_LEVEL_APPLICATION) {
|
|
2259
|
+
space.probes = 1
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
/**
|
|
2265
|
+
* Speeds up the handshake (RFC 9002 §6.2.3): the client showed at `level`
|
|
2266
|
+
* that it lacks the server's handshake data there — an Initial with CRYPTO
|
|
2267
|
+
* data the server already read, or an ack-eliciting Handshake packet —
|
|
2268
|
+
* so, while the server's own packets at that level are in flight, the
|
|
2269
|
+
* oldest one's CRYPTO data at that level and at Handshake goes again now,
|
|
2270
|
+
* as one probe datagram, rather than at the probe timeout. It does so at
|
|
2271
|
+
* most `QUIC_CONN_EARLY_RESENDS` times a connection, and not while an
|
|
2272
|
+
* earlier probe is still owed, so each packet the client sends asks for
|
|
2273
|
+
* one datagram at most. The anti-amplification limit holds it like any
|
|
2274
|
+
* datagram (`takeDatagramInto`).
|
|
2275
|
+
*/
|
|
2276
|
+
resendEarly(level: i32): void {
|
|
2277
|
+
if (
|
|
2278
|
+
this.earlyResends >= QUIC_CONN_EARLY_RESENDS ||
|
|
2279
|
+
this.initial.probes > 0 ||
|
|
2280
|
+
this.handshake.probes > 0
|
|
2281
|
+
) {
|
|
2282
|
+
return
|
|
2283
|
+
}
|
|
2284
|
+
const asked: QuicSentPackets | null = this.recovery.space(level)
|
|
2285
|
+
if (asked === null || asked.inFlight === 0) {
|
|
2286
|
+
return
|
|
2287
|
+
}
|
|
2288
|
+
let queued: boolean = false
|
|
2289
|
+
for (let at: i32 = level; at <= TLS_LEVEL_HANDSHAKE; at += 1) {
|
|
2290
|
+
const space: QuicConnSpace = this.spaceAt(at)
|
|
2291
|
+
const sent: QuicSentPackets | null = this.recovery.space(at)
|
|
2292
|
+
if (sent === null || space.discarded || sent.inFlight === 0) {
|
|
2293
|
+
continue
|
|
2294
|
+
}
|
|
2295
|
+
// Compacted, the ring's oldest slot is the oldest packet in flight.
|
|
2296
|
+
sent.compact()
|
|
2297
|
+
const oldest: i32 = sent.slot(QUIC_CONN_FROM)
|
|
2298
|
+
const length: i32 = space.cryptoLength(oldest)
|
|
2299
|
+
if (length > 0) {
|
|
2300
|
+
space.resendCrypto(space.cryptoOffset(oldest), length)
|
|
2301
|
+
space.probes = 1
|
|
2302
|
+
queued = true
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
if (queued) {
|
|
2306
|
+
this.earlyResends += 1
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
/**
|
|
2311
|
+
* A CRYPTO frame: its data put back in order, for TLS to read once the
|
|
2312
|
+
* packet is (`feedTls`). The buffer follows the level the client is
|
|
2313
|
+
* sending at; data of a level it has moved past is a duplicate of what
|
|
2314
|
+
* TLS already read, or, past that, a message TLS is not reading, which is
|
|
2315
|
+
* its alert unexpected_message (RFC 9001 §4.8). A frame too far ahead is
|
|
2316
|
+
* CRYPTO_BUFFER_EXCEEDED.
|
|
2317
|
+
*/
|
|
2318
|
+
receiveCrypto(space: QuicConnSpace, frame: QuicFrame, payload: u8[]): void {
|
|
2319
|
+
const reassembly: QuicConnReassembly = this.cryptoIn
|
|
2320
|
+
if (space.level < reassembly.level) {
|
|
2321
|
+
if (frame.offset + toI64(frame.dataLength) > space.cryptoInDelivered) {
|
|
2322
|
+
this.fail(QUIC_ERROR_CRYPTO + QUIC_CONN_ALERT_UNEXPECTED, toI64(QUIC_FRAME_CRYPTO))
|
|
2323
|
+
}
|
|
2324
|
+
return
|
|
2325
|
+
}
|
|
2326
|
+
if (space.level > reassembly.level) {
|
|
2327
|
+
this.spaceAt(reassembly.level).cryptoInDelivered = reassembly.delivered
|
|
2328
|
+
reassembly.reset(space.level)
|
|
2329
|
+
}
|
|
2330
|
+
// RFC 9002 §6.2.3: an Initial with CRYPTO data the server already read
|
|
2331
|
+
// says the client did not get the server's Initial flight.
|
|
2332
|
+
if (space.level === TLS_LEVEL_INITIAL && frame.offset < reassembly.delivered) {
|
|
2333
|
+
this.resendLevel = TLS_LEVEL_INITIAL
|
|
2334
|
+
}
|
|
2335
|
+
if (!reassembly.insert(frame.offset, payload, frame.dataStart, frame.dataLength)) {
|
|
2336
|
+
this.fail(QUIC_ERROR_CRYPTO_BUFFER_EXCEEDED, toI64(QUIC_FRAME_CRYPTO))
|
|
2337
|
+
return
|
|
2338
|
+
}
|
|
2339
|
+
this.cryptoArrived = true
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
/** Hands TLS what now continues the CRYPTO stream, from the buffer itself; an alert closes with CRYPTO_ERROR. */
|
|
2343
|
+
feedTls(): void {
|
|
2344
|
+
const tls: TlsServer | null = this.tls
|
|
2345
|
+
const reassembly: QuicConnReassembly = this.cryptoIn
|
|
2346
|
+
if (tls === null) {
|
|
2347
|
+
return
|
|
2348
|
+
}
|
|
2349
|
+
// A run stops at the buffer's end, so a stream that wraps is two runs.
|
|
2350
|
+
for (let pass: i32 = 0; pass < 2; pass += 1) {
|
|
2351
|
+
const n: i32 = reassembly.run()
|
|
2352
|
+
if (n === 0) {
|
|
2353
|
+
return
|
|
2354
|
+
}
|
|
2355
|
+
const alert: i32 = tls.receive(
|
|
2356
|
+
reassembly.level,
|
|
2357
|
+
reassembly.ring,
|
|
2358
|
+
reassembly.slot(reassembly.delivered),
|
|
2359
|
+
n
|
|
2360
|
+
)
|
|
2361
|
+
reassembly.consume(n)
|
|
2362
|
+
if (alert !== 0) {
|
|
2363
|
+
this.fail(QUIC_ERROR_CRYPTO + toI64(alert), toI64(QUIC_FRAME_CRYPTO))
|
|
2364
|
+
return
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
/**
|
|
2370
|
+
* A DATAGRAM frame (RFC 9221 §4), which starts at `payload[frameStart]`:
|
|
2371
|
+
* one the server did not offer to take, or larger than it offered, is a
|
|
2372
|
+
* PROTOCOL_VIOLATION (§3); otherwise its payload waits for
|
|
2373
|
+
* `readDatagram`, or is dropped when the ring is full.
|
|
2374
|
+
*/
|
|
2375
|
+
receiveDatagram(frame: QuicFrame, payload: u8[], frameStart: i32): void {
|
|
2376
|
+
const size: i64 = toI64(frame.end - frameStart)
|
|
2377
|
+
if (this.config.maxDatagramFrameSize === 0 || size > this.config.maxDatagramFrameSize) {
|
|
2378
|
+
// `fin` is how the frame says it was 0x31, the type with a Length.
|
|
2379
|
+
this.fail(
|
|
2380
|
+
QUIC_ERROR_PROTOCOL_VIOLATION,
|
|
2381
|
+
toI64(frame.fin ? QUIC_FRAME_DATAGRAM_LENGTH : QUIC_FRAME_DATAGRAM)
|
|
2382
|
+
)
|
|
2383
|
+
return
|
|
2384
|
+
}
|
|
2385
|
+
this.datagramsIn.push(payload, frame.dataStart, frame.dataLength)
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
/**
|
|
2389
|
+
* A PATH_CHALLENGE (§8.2.2): answered with the same eight bytes. Only the
|
|
2390
|
+
* latest four are kept, so a burst of challenges cannot grow anything.
|
|
2391
|
+
*/
|
|
2392
|
+
receivePathChallenge(frame: QuicFrame, payload: u8[]): void {
|
|
2393
|
+
if (this.pathCount >= 4) {
|
|
2394
|
+
this.pathHead = 0
|
|
2395
|
+
this.pathCount = 0
|
|
2396
|
+
}
|
|
2397
|
+
const base: i32 = ((this.pathHead + this.pathCount) % 4) * QUIC_PATH_DATA_SIZE
|
|
2398
|
+
for (let k: i32 = 0; k < QUIC_PATH_DATA_SIZE; k += 1) {
|
|
2399
|
+
const from: i32 = frame.dataStart + k
|
|
2400
|
+
if (
|
|
2401
|
+
base + k >= 0 &&
|
|
2402
|
+
base + k < toI32(this.pathData.length) &&
|
|
2403
|
+
from >= 0 &&
|
|
2404
|
+
from < toI32(payload.length)
|
|
2405
|
+
) {
|
|
2406
|
+
this.pathData[base + k] = payload[from]
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
this.pathCount = this.pathCount + 1
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
/**
|
|
2413
|
+
* Whatever TLS's last step made due: its output moved into each level's
|
|
2414
|
+
* CRYPTO stream, the keys of each level it now has secrets for, the
|
|
2415
|
+
* client's transport parameters checked once it has read them, and, when
|
|
2416
|
+
* the handshake completes, the Handshake keys discarded (RFC 9001 §4.9.2),
|
|
2417
|
+
* HANDSHAKE_DONE queued (§4.1.2) and the client given more connection IDs.
|
|
2418
|
+
*/
|
|
2419
|
+
afterTls(): void {
|
|
2420
|
+
const tls: TlsServer | null = this.tls
|
|
2421
|
+
if (tls === null || this.closed()) {
|
|
2422
|
+
return
|
|
2423
|
+
}
|
|
2424
|
+
if (!this.initial.discarded) {
|
|
2425
|
+
this.initial.appendCrypto(tls.outputInitial, tls.outputInitialLength)
|
|
2426
|
+
tls.clearOutput(TLS_LEVEL_INITIAL)
|
|
2427
|
+
}
|
|
2428
|
+
if (!this.handshake.discarded) {
|
|
2429
|
+
this.handshake.appendCrypto(tls.outputHandshake, tls.outputHandshakeLength)
|
|
2430
|
+
tls.clearOutput(TLS_LEVEL_HANDSHAKE)
|
|
2431
|
+
}
|
|
2432
|
+
const aead: i32 = quicConnAead(tls.suite)
|
|
2433
|
+
if (
|
|
2434
|
+
!this.handshake.discarded &&
|
|
2435
|
+
this.handshake.writeKeys === null &&
|
|
2436
|
+
tls.state !== TLS_STATE_WAIT_CLIENT_HELLO
|
|
2437
|
+
) {
|
|
2438
|
+
if (!this.checkPeerParameters(tls)) {
|
|
2439
|
+
return
|
|
2440
|
+
}
|
|
2441
|
+
this.alpn = tls.alpn
|
|
2442
|
+
this.serverName = tls.serverName
|
|
2443
|
+
this.handshake.readKeys = this.keysFor(
|
|
2444
|
+
this.handshake.keySlot(QUIC_CONN_READ_SLOT),
|
|
2445
|
+
aead,
|
|
2446
|
+
tls.readSecret(TLS_LEVEL_HANDSHAKE)
|
|
2447
|
+
)
|
|
2448
|
+
this.handshake.writeKeys = this.keysFor(
|
|
2449
|
+
this.handshake.keySlot(QUIC_CONN_WRITE_SLOT),
|
|
2450
|
+
aead,
|
|
2451
|
+
tls.writeSecret(TLS_LEVEL_HANDSHAKE)
|
|
2452
|
+
)
|
|
2453
|
+
}
|
|
2454
|
+
if (
|
|
2455
|
+
!this.application.discarded &&
|
|
2456
|
+
this.application.writeKeys === null &&
|
|
2457
|
+
tls.state !== TLS_STATE_WAIT_SIGNATURE
|
|
2458
|
+
) {
|
|
2459
|
+
const write: u8[] | null = tls.writeSecret(TLS_LEVEL_APPLICATION)
|
|
2460
|
+
const read: u8[] | null = tls.readSecret(TLS_LEVEL_APPLICATION)
|
|
2461
|
+
if (write !== null && read !== null) {
|
|
2462
|
+
this.application.writeKeys = this.keysFor(
|
|
2463
|
+
this.application.keySlot(QUIC_CONN_APP_WRITE_SLOTS + this.appWriteSlot),
|
|
2464
|
+
aead,
|
|
2465
|
+
write
|
|
2466
|
+
)
|
|
2467
|
+
this.application.readKeys = this.keysFor(this.application.keySlot(this.appReadSlot), aead, read)
|
|
2468
|
+
// The key update secrets start from `TlsServer`'s own arrays, so the
|
|
2469
|
+
// first update wipes those too, rather than leaving a copy behind.
|
|
2470
|
+
this.appWriteSecret = write
|
|
2471
|
+
this.appReadSecret = read
|
|
2472
|
+
this.prepareNextReadKeys()
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
if (tls.state === TLS_STATE_CONNECTED && !this.handshakeComplete) {
|
|
2476
|
+
this.handshakeComplete = true
|
|
2477
|
+
this.recovery.handshakeConfirmed = true
|
|
2478
|
+
this.handshakeDonePending = true
|
|
2479
|
+
this.state = QUIC_STATE_CONNECTED
|
|
2480
|
+
this.discard(this.handshake)
|
|
2481
|
+
this.topUpConnectionIds()
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
/** `quicKeysInto` `slot` of a secret TLS has, or `null` when it has none yet. */
|
|
2486
|
+
keysFor(slot: QuicKeysSlot, aead: i32, secret: u8[] | null): QuicKeys | null {
|
|
2487
|
+
if (secret === null) {
|
|
2488
|
+
return null
|
|
2489
|
+
}
|
|
2490
|
+
return quicKeysInto(this.kdf, slot, aead, secret)
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
/**
|
|
2494
|
+
* The client's transport parameters (RFC 9000 §7.3, §7.4, §18.2): they must
|
|
2495
|
+
* parse, and must name as `initial_source_connection_id` the SCID its first
|
|
2496
|
+
* Initial came from. Missing, that is TRANSPORT_PARAMETER_ERROR; wrong, a
|
|
2497
|
+
* PROTOCOL_VIOLATION. Answers whether they hold, closing the connection
|
|
2498
|
+
* when they do not. The streams take the client's limits from them, and
|
|
2499
|
+
* the datagrams its `max_datagram_frame_size`.
|
|
2500
|
+
*/
|
|
2501
|
+
checkPeerParameters(tls: TlsServer): boolean {
|
|
2502
|
+
const p: QuicTransportParameters = quicParseTransportParametersInto(
|
|
2503
|
+
this.parsedParameters,
|
|
2504
|
+
tls.clientTransportParameters,
|
|
2505
|
+
false
|
|
2506
|
+
)
|
|
2507
|
+
if (p.error !== QUIC_ERROR_NO_ERROR || !p.hasInitialScid) {
|
|
2508
|
+
this.fail(QUIC_ERROR_TRANSPORT_PARAMETER, toI64(QUIC_FRAME_CRYPTO))
|
|
2509
|
+
return false
|
|
2510
|
+
}
|
|
2511
|
+
if (
|
|
2512
|
+
toI32(p.initialScid.length) !== this.peerScidLength ||
|
|
2513
|
+
!timingSafeEqualAt(p.initialScid, 0, this.peerScid, QUIC_CONN_FROM, this.peerScidLength)
|
|
2514
|
+
) {
|
|
2515
|
+
this.fail(QUIC_ERROR_PROTOCOL_VIOLATION, toI64(QUIC_FRAME_CRYPTO))
|
|
2516
|
+
return false
|
|
2517
|
+
}
|
|
2518
|
+
this.peerParameters = p
|
|
2519
|
+
this.streams.setPeerLimits(
|
|
2520
|
+
p.initialMaxData,
|
|
2521
|
+
p.initialMaxStreamDataBidiLocal,
|
|
2522
|
+
p.initialMaxStreamDataBidiRemote,
|
|
2523
|
+
p.initialMaxStreamDataUni,
|
|
2524
|
+
p.initialMaxStreamsBidi,
|
|
2525
|
+
p.initialMaxStreamsUni
|
|
2526
|
+
)
|
|
2527
|
+
this.peerMaxDatagramFrame = p.maxDatagramFrameSize
|
|
2528
|
+
this.recovery.maxAckDelay = p.maxAckDelay
|
|
2529
|
+
return true
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
/** Discards a space's keys and CRYPTO state (RFC 9001 §4.9), wiping the keys. */
|
|
2533
|
+
discard(space: QuicConnSpace): void {
|
|
2534
|
+
if (space.discarded) {
|
|
2535
|
+
return
|
|
2536
|
+
}
|
|
2537
|
+
space.discarded = true
|
|
2538
|
+
// RFC 9002 §6.4: the space's packets leave flight, uncounted.
|
|
2539
|
+
this.recovery.discardSpace(space.level)
|
|
2540
|
+
space.cryptoResendLow = -1
|
|
2541
|
+
space.cryptoResendHigh = -1
|
|
2542
|
+
space.probes = 0
|
|
2543
|
+
quicConnWipeKeys(space.readKeys)
|
|
2544
|
+
quicConnWipeKeys(space.writeKeys)
|
|
2545
|
+
space.wipeKeySlots()
|
|
2546
|
+
space.readKeys = null
|
|
2547
|
+
space.writeKeys = null
|
|
2548
|
+
space.cryptoOutLength = 0
|
|
2549
|
+
space.cryptoOutHead = 0
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
/**
|
|
2553
|
+
* The HMAC-SHA256 under the connection's seed of the sequence number
|
|
2554
|
+
* `sequence`, whose first 8 bytes are the local connection ID of that
|
|
2555
|
+
* number, into `cidScratch`: so the IDs are unlinkable to anyone without
|
|
2556
|
+
* the seed and need no more entropy. It runs in the connection's HMAC
|
|
2557
|
+
* scratch inside an arena block, and wipes the MAC and the scratch.
|
|
2558
|
+
*/
|
|
2559
|
+
deriveConnectionId(sequence: i64): void {
|
|
2560
|
+
const mac: HmacSha256Scratch = this.kdf.sha256
|
|
2561
|
+
using _scope = arena()
|
|
2562
|
+
const counter: u8[] = new Array<u8>(8)
|
|
2563
|
+
for (let k: i32 = 0; k < 8 && k < toI32(counter.length); k += 1) {
|
|
2564
|
+
counter[k] = toU8(toI32((sequence >> (toI64(7 - k) * 8)) & 255))
|
|
2565
|
+
}
|
|
2566
|
+
const tag: u8[] = new Array<u8>(32)
|
|
2567
|
+
mac.begin(this.cidSeed, QUIC_CONN_FROM, toI32(this.cidSeed.length))
|
|
2568
|
+
mac.update(counter, QUIC_CONN_FROM, toI32(counter.length))
|
|
2569
|
+
mac.finishInto(tag, QUIC_CONN_FROM)
|
|
2570
|
+
for (
|
|
2571
|
+
let k: i32 = 0;
|
|
2572
|
+
k < QUIC_CONN_CID_LENGTH && k < toI32(tag.length) && k < toI32(this.cidScratch.length);
|
|
2573
|
+
k += 1
|
|
2574
|
+
) {
|
|
2575
|
+
this.cidScratch[k] = tag[k]
|
|
2576
|
+
}
|
|
2577
|
+
secureZero(tag)
|
|
2578
|
+
mac.wipe()
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
/**
|
|
2582
|
+
* Issues local connection IDs until the client holds as many as it said it
|
|
2583
|
+
* would take (its `active_connection_id_limit`), up to
|
|
2584
|
+
* `QUIC_CONN_LOCAL_CIDS` at once and `QUIC_CONN_MAX_ISSUED_CIDS` in all;
|
|
2585
|
+
* each goes out in a NEW_CONNECTION_ID frame with the stateless reset
|
|
2586
|
+
* token the configuration's static key gives it. Each ID and token is made
|
|
2587
|
+
* in the slot's scratch and copied into the table, so issuing keeps
|
|
2588
|
+
* nothing (QUIC-3).
|
|
2589
|
+
*/
|
|
2590
|
+
topUpConnectionIds(): void {
|
|
2591
|
+
if (!this.handshakeComplete) {
|
|
2592
|
+
return
|
|
2593
|
+
}
|
|
2594
|
+
let want: i64 = this.peerParameters.activeConnectionIdLimit
|
|
2595
|
+
if (want > toI64(QUIC_CONN_LOCAL_CIDS)) {
|
|
2596
|
+
want = toI64(QUIC_CONN_LOCAL_CIDS)
|
|
2597
|
+
}
|
|
2598
|
+
while (this.cids.nextLocal < QUIC_CONN_MAX_ISSUED_CIDS && toI64(this.cids.activeLocal()) < want) {
|
|
2599
|
+
this.deriveConnectionId(this.cids.nextLocal)
|
|
2600
|
+
quicConnResetTokenInto(
|
|
2601
|
+
this.kdf.sha256,
|
|
2602
|
+
this.config.statelessResetKey,
|
|
2603
|
+
this.cidScratch,
|
|
2604
|
+
QUIC_CONN_FROM,
|
|
2605
|
+
QUIC_CONN_CID_LENGTH,
|
|
2606
|
+
this.tokenScratch,
|
|
2607
|
+
QUIC_CONN_FROM
|
|
2608
|
+
)
|
|
2609
|
+
const added: i64 = this.cids.addLocal(this.cidScratch, this.tokenScratch)
|
|
2610
|
+
secureZero(this.tokenScratch)
|
|
2611
|
+
if (added < 0) {
|
|
2612
|
+
return
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
/** What CertificateVerify must sign while the handshake waits for it, else `null` (see `TlsServer.signatureInput`). */
|
|
2618
|
+
signatureInput(): u8[] | null {
|
|
2619
|
+
const tls: TlsServer | null = this.tls
|
|
2620
|
+
if (tls === null || this.closed()) {
|
|
2621
|
+
return null
|
|
2622
|
+
}
|
|
2623
|
+
return tls.signatureInput()
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
/**
|
|
2627
|
+
* Hands the signature over `signatureInput()` to TLS, which writes the
|
|
2628
|
+
* rest of the server's flight. Answers 0, or the CRYPTO_ERROR the
|
|
2629
|
+
* connection closed with when TLS refused it (a call at the wrong time, or
|
|
2630
|
+
* an empty signature: `internal_error`).
|
|
2631
|
+
*/
|
|
2632
|
+
sign(signature: u8[]): i64 {
|
|
2633
|
+
const tls: TlsServer | null = this.tls
|
|
2634
|
+
if (tls === null || this.closed()) {
|
|
2635
|
+
return this.error
|
|
2636
|
+
}
|
|
2637
|
+
const alert: i32 = tls.sign(signature)
|
|
2638
|
+
if (alert !== 0) {
|
|
2639
|
+
this.fail(QUIC_ERROR_CRYPTO + toI64(alert), QUIC_CONN_NONE)
|
|
2640
|
+
return this.error
|
|
2641
|
+
}
|
|
2642
|
+
this.afterTls()
|
|
2643
|
+
return QUIC_ERROR_NO_ERROR
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
/**
|
|
2647
|
+
* The effective idle timeout in milliseconds (RFC 9000 §10.1): the smaller
|
|
2648
|
+
* of the two sides' `max_idle_timeout`, or the one that is not 0, raised to
|
|
2649
|
+
* `QUIC_CONN_IDLE_FLOOR` and to three probe timeouts; -1 when both are 0,
|
|
2650
|
+
* for none. Until the client's transport parameters are read, the server's
|
|
2651
|
+
* own alone.
|
|
2652
|
+
*/
|
|
2653
|
+
idleTimeout(): i64 {
|
|
2654
|
+
const local: i64 = this.config.maxIdleTimeout
|
|
2655
|
+
const peer: i64 = this.peerParameters.maxIdleTimeout
|
|
2656
|
+
let timeout: i64 = local
|
|
2657
|
+
if (local === 0 || (peer !== 0 && peer < local)) {
|
|
2658
|
+
timeout = peer
|
|
2659
|
+
}
|
|
2660
|
+
if (timeout === 0) {
|
|
2661
|
+
return -1
|
|
2662
|
+
}
|
|
2663
|
+
let floor: i64 = this.recovery.probeTimeout() * 3
|
|
2664
|
+
if (floor < QUIC_CONN_IDLE_FLOOR) {
|
|
2665
|
+
floor = QUIC_CONN_IDLE_FLOOR
|
|
2666
|
+
}
|
|
2667
|
+
return timeout < floor ? floor : timeout
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
/** When the idle timeout passes, or -1 when there is none or the timer has not started. */
|
|
2671
|
+
idleDeadline(): i64 {
|
|
2672
|
+
const timeout: i64 = this.idleTimeout()
|
|
2673
|
+
return timeout >= 0 && this.idleSince >= 0 ? this.idleSince + timeout : -1
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
/**
|
|
2677
|
+
* When `handleTimer` is next due, in the caller's milliseconds: the idle
|
|
2678
|
+
* timeout, loss recovery's timer (a time-threshold loss or the probe
|
|
2679
|
+
* timeout), or the end of a key update's probe timeout, whichever comes
|
|
2680
|
+
* first. -1 when nothing is timed: before the first packet, once the
|
|
2681
|
+
* connection has closed, and with no idle timeout, nothing in flight and
|
|
2682
|
+
* no update pending.
|
|
2683
|
+
*/
|
|
2684
|
+
deadline(): i64 {
|
|
2685
|
+
if (this.closed() || this.state === QUIC_STATE_WAIT_INITIAL) {
|
|
2686
|
+
return -1
|
|
2687
|
+
}
|
|
2688
|
+
let due: i64 = this.idleDeadline()
|
|
2689
|
+
if (this.keyRetainUntil >= 0 && (due < 0 || this.keyRetainUntil < due)) {
|
|
2690
|
+
due = this.keyRetainUntil
|
|
2691
|
+
}
|
|
2692
|
+
const recovery: i64 = this.recovery.deadline(this.amplificationBlocked())
|
|
2693
|
+
if (recovery >= 0 && (due < 0 || recovery < due)) {
|
|
2694
|
+
due = recovery
|
|
2695
|
+
}
|
|
2696
|
+
return due
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2699
|
+
/**
|
|
2700
|
+
* Whether the anti-amplification limit stops the server sending (RFC 9000
|
|
2701
|
+
* §8.1): the client's address is not validated and three times what it
|
|
2702
|
+
* sent leaves no room for a whole datagram.
|
|
2703
|
+
*/
|
|
2704
|
+
amplificationBlocked(): boolean {
|
|
2705
|
+
return !this.addressValidated && this.bytesReceived * 3 - this.bytesSent < toI64(QUIC_CONN_DATAGRAM_SIZE)
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
/**
|
|
2709
|
+
* Runs whatever is due by `now`, the caller's monotonic time in
|
|
2710
|
+
* milliseconds; call it at `deadline()`, or any time. Past the idle timeout
|
|
2711
|
+
* the connection closes silently (`QUIC_STATE_TIMED_OUT`, RFC 9000 §10.1):
|
|
2712
|
+
* no CONNECTION_CLOSE, every key wiped. Past a key update's probe timeout
|
|
2713
|
+
* the previous read keys are wiped and the next generation's derived (RFC
|
|
2714
|
+
* 9001 §6.5). Past loss recovery's timer, packets lost by the time
|
|
2715
|
+
* threshold have their frames queued again, or a probe timeout queues
|
|
2716
|
+
* every space's packets in flight and asks for a probe (RFC 9002 §6.1.2,
|
|
2717
|
+
* §6.2.4): `takeDatagram` then sends it. `receive` and `takeDatagram` run
|
|
2718
|
+
* it themselves. Time never runs backwards here: an earlier `now` than one
|
|
2719
|
+
* already given counts as that one.
|
|
2720
|
+
*/
|
|
2721
|
+
handleTimer(now: i64): void {
|
|
2722
|
+
this.runClocks(now)
|
|
2723
|
+
this.runRecoveryTimer()
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
/** `handleTimer`'s idle timeout and key retention, which `receive` runs before it reads a datagram. */
|
|
2727
|
+
runClocks(now: i64): void {
|
|
2728
|
+
if (now > this.now) {
|
|
2729
|
+
this.now = now
|
|
2730
|
+
}
|
|
2731
|
+
if (this.closed() || this.state === QUIC_STATE_WAIT_INITIAL) {
|
|
2732
|
+
return
|
|
2733
|
+
}
|
|
2734
|
+
const idle: i64 = this.idleDeadline()
|
|
2735
|
+
if (idle >= 0 && this.now >= idle) {
|
|
2736
|
+
this.state = QUIC_STATE_TIMED_OUT
|
|
2737
|
+
this.error = QUIC_ERROR_NO_ERROR
|
|
2738
|
+
this.closeSent = true
|
|
2739
|
+
this.wipeAll()
|
|
2740
|
+
return
|
|
2741
|
+
}
|
|
2742
|
+
if (this.keyRetainUntil >= 0 && this.now >= this.keyRetainUntil) {
|
|
2743
|
+
this.prepareNextReadKeys()
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
/** Loss recovery's timer, if it is due (RFC 9002 §6.2, A.9). */
|
|
2748
|
+
runRecoveryTimer(): void {
|
|
2749
|
+
if (this.closed() || this.state === QUIC_STATE_WAIT_INITIAL) {
|
|
2750
|
+
return
|
|
2751
|
+
}
|
|
2752
|
+
const kind: i32 = this.recovery.onTimeout(this.now, this.amplificationBlocked())
|
|
2753
|
+
if (kind === QUIC_RECOVERY_TIMEOUT_LOSS) {
|
|
2754
|
+
const level: i32 = this.recovery.timeoutSpace
|
|
2755
|
+
const sent: QuicSentPackets | null = this.recovery.space(level)
|
|
2756
|
+
if (sent !== null) {
|
|
2757
|
+
this.packetsLost(this.spaceAt(level), sent)
|
|
2758
|
+
}
|
|
2759
|
+
} else if (kind === QUIC_RECOVERY_TIMEOUT_PTO) {
|
|
2760
|
+
this.probe()
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
/**
|
|
2765
|
+
* Starts a key update from the server (RFC 9001 §6.1): the write keys move
|
|
2766
|
+
* to the next generation and the Key Phase bit of every packet after
|
|
2767
|
+
* toggles; the client follows when it reads one. Answers whether it did.
|
|
2768
|
+
* It does not while it may not: before the handshake is confirmed, while
|
|
2769
|
+
* an update is still in flight (the client has not answered the last one,
|
|
2770
|
+
* or answered it less than a probe timeout ago, so the next read keys are
|
|
2771
|
+
* not ready), and until the client has acknowledged a packet sent with the
|
|
2772
|
+
* current keys.
|
|
2773
|
+
*/
|
|
2774
|
+
updateKeys(): boolean {
|
|
2775
|
+
if (
|
|
2776
|
+
this.state !== QUIC_STATE_CONNECTED ||
|
|
2777
|
+
this.writePhase !== this.readPhase ||
|
|
2778
|
+
!this.otherIsNext ||
|
|
2779
|
+
this.application.largestAcked < this.writePhaseFirst
|
|
2780
|
+
) {
|
|
2781
|
+
return false
|
|
2782
|
+
}
|
|
2783
|
+
return this.updateWriteKeys()
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
// ---- Streams ------------------------------------------------------------------
|
|
2787
|
+
|
|
2788
|
+
/**
|
|
2789
|
+
* The ID of the next stream with something new for the application — the
|
|
2790
|
+
* client opened it, it has bytes or its FIN to read, it was reset or
|
|
2791
|
+
* stopped, or it has room to write again after a short write — or -1
|
|
2792
|
+
* when there is none. Each stream comes up once however much happened;
|
|
2793
|
+
* read and write it until it has nothing more to say.
|
|
2794
|
+
*/
|
|
2795
|
+
nextStreamEvent(): i64 {
|
|
2796
|
+
return this.streams.nextEvent()
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
/**
|
|
2800
|
+
* Opens a stream of the server's (RFC 9000 §2.1): bidirectional when
|
|
2801
|
+
* `bidirectional`, else unidirectional. Answers its ID, or
|
|
2802
|
+
* `QUIC_STREAM_ERR_STATE` when not connected, or `QUIC_STREAM_ERR_LIMIT`
|
|
2803
|
+
* when the client's MAX_STREAMS is reached (STREAMS_BLOCKED then goes out)
|
|
2804
|
+
* or `localStreams` of the server's are open.
|
|
2805
|
+
*/
|
|
2806
|
+
openStream(bidirectional: boolean): i64 {
|
|
2807
|
+
if (this.state !== QUIC_STATE_CONNECTED) {
|
|
2808
|
+
return toI64(QUIC_STREAM_ERR_STATE)
|
|
2809
|
+
}
|
|
2810
|
+
return this.streams.open(bidirectional)
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
/**
|
|
2814
|
+
* Writes up to `length` bytes of `buf` from `from` to stream `id`, and
|
|
2815
|
+
* finishes its sending side when `fin` and every byte fit. Answers how many
|
|
2816
|
+
* bytes it took — fewer than `length` when the stream's buffer is full, in
|
|
2817
|
+
* which case `nextStreamEvent` names it again once acknowledgements free
|
|
2818
|
+
* room — or a `QUIC_STREAM_ERR_*`: not connected, an unknown stream, one
|
|
2819
|
+
* only the client sends on, one already finished or reset, or one the
|
|
2820
|
+
* client asked to stop (STOP_SENDING, which this side answered with
|
|
2821
|
+
* RESET_STREAM).
|
|
2822
|
+
*/
|
|
2823
|
+
streamWrite(id: i64, buf: u8[], from: i32, length: i32, fin: boolean): i32 {
|
|
2824
|
+
if (this.state !== QUIC_STATE_CONNECTED) {
|
|
2825
|
+
return QUIC_STREAM_ERR_STATE
|
|
2826
|
+
}
|
|
2827
|
+
return this.streams.write(id, buf, from, length, fin)
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
/**
|
|
2831
|
+
* Reads up to `length` bytes of stream `id` into `buf` from `at`. Answers
|
|
2832
|
+
* how many it read, 0 when nothing more has arrived yet, `QUIC_STREAM_END`
|
|
2833
|
+
* once every byte up to the FIN was read, `QUIC_STREAM_ERR_RESET` once the
|
|
2834
|
+
* client reset the stream (its code is the stream's `resetCode`), or
|
|
2835
|
+
* another `QUIC_STREAM_ERR_*`. Reading is what gives the client more credit.
|
|
2836
|
+
*/
|
|
2837
|
+
streamRead(id: i64, buf: u8[], at: i32, length: i32): i32 {
|
|
2838
|
+
return this.streams.read(id, buf, at, length)
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
/** Abandons stream `id`'s sending side with application error `code` (RESET_STREAM). Answers `QUIC_STREAM_OK` or a `QUIC_STREAM_ERR_*`. */
|
|
2842
|
+
streamReset(id: i64, code: i64): i32 {
|
|
2843
|
+
if (this.state !== QUIC_STATE_CONNECTED) {
|
|
2844
|
+
return QUIC_STREAM_ERR_STATE
|
|
2845
|
+
}
|
|
2846
|
+
return this.streams.resetStream(id, code)
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
/** Asks the client to stop sending on stream `id`, with application error `code` (STOP_SENDING). Answers `QUIC_STREAM_OK` or a `QUIC_STREAM_ERR_*`. */
|
|
2850
|
+
streamStopSending(id: i64, code: i64): i32 {
|
|
2851
|
+
if (this.state !== QUIC_STATE_CONNECTED) {
|
|
2852
|
+
return QUIC_STREAM_ERR_STATE
|
|
2853
|
+
}
|
|
2854
|
+
return this.streams.stopSending(id, code)
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2857
|
+
/**
|
|
2858
|
+
* The next run of stream data the client sent, or `null` when there is
|
|
2859
|
+
* none: the all-at-once form of `nextStreamEvent` and `streamRead`, which
|
|
2860
|
+
* allocates the run it answers. `fin` marks the last of its stream; a
|
|
2861
|
+
* stream the client reset is passed over.
|
|
2862
|
+
*/
|
|
2863
|
+
readStream(): QuicStreamData | null {
|
|
2864
|
+
let id: i64 = this.streams.nextEvent()
|
|
2865
|
+
while (id >= 0) {
|
|
2866
|
+
const stream: QuicStream | null = this.streams.find(id)
|
|
2867
|
+
const available: i64 = stream !== null ? stream.readable() : 0
|
|
2868
|
+
const data: u8[] = new Array<u8>(toI32(available))
|
|
2869
|
+
const n: i32 = this.streams.read(id, data, QUIC_CONN_FROM, toI32(available))
|
|
2870
|
+
// Every byte up to the FIN read: one more read sees the end, which lets the side finish.
|
|
2871
|
+
const end: boolean =
|
|
2872
|
+
n === QUIC_STREAM_END ||
|
|
2873
|
+
(n > 0 && this.streams.read(id, data, QUIC_CONN_FROM, QUIC_CONN_FROM) === QUIC_STREAM_END)
|
|
2874
|
+
if (n > 0 || end) {
|
|
2875
|
+
return new QuicStreamData(id, data, end)
|
|
2876
|
+
}
|
|
2877
|
+
// A reset stream answered QUIC_STREAM_ERR_RESET, which saw it and let
|
|
2878
|
+
// it finish; there is nothing to hand over.
|
|
2879
|
+
id = this.streams.nextEvent()
|
|
2880
|
+
}
|
|
2881
|
+
return null
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
/**
|
|
2885
|
+
* Queues all of `data` on stream `id`, finishing its sending side when
|
|
2886
|
+
* `fin`: the all-or-nothing form of `streamWrite`. Answers
|
|
2887
|
+
* `QUIC_STREAM_OK`, or `QUIC_STREAM_ERR_*`: the connection is not
|
|
2888
|
+
* connected, the stream is unknown, its side is already finished (or the
|
|
2889
|
+
* client stopped it), or the data would pass the credit the client gave
|
|
2890
|
+
* for the stream or the connection, or the room in the stream's buffer.
|
|
2891
|
+
*/
|
|
2892
|
+
writeStream(id: i64, data: u8[], fin: boolean): i32 {
|
|
2893
|
+
if (this.state !== QUIC_STATE_CONNECTED) {
|
|
2894
|
+
return QUIC_STREAM_ERR_STATE
|
|
2895
|
+
}
|
|
2896
|
+
const stream: QuicStream | null = this.streams.find(id)
|
|
2897
|
+
if (stream === null) {
|
|
2898
|
+
return QUIC_STREAM_ERR_UNKNOWN
|
|
2899
|
+
}
|
|
2900
|
+
if (stream.finQueued || stream.stopCode >= 0 || stream.sendState >= QUIC_SEND_RESET_SENT) {
|
|
2901
|
+
return QUIC_STREAM_ERR_FINISHED
|
|
2902
|
+
}
|
|
2903
|
+
const length: i64 = toI64(toI32(data.length))
|
|
2904
|
+
if (
|
|
2905
|
+
stream.sendEnd + length > stream.sendLimit ||
|
|
2906
|
+
this.streams.writtenTotal + length > this.streams.sendMaxData ||
|
|
2907
|
+
length > stream.room()
|
|
2908
|
+
) {
|
|
2909
|
+
return QUIC_STREAM_ERR_FLOW
|
|
2910
|
+
}
|
|
2911
|
+
const n: i32 = this.streams.write(id, data, QUIC_CONN_FROM, toI32(data.length), fin)
|
|
2912
|
+
return n < 0 ? n : QUIC_STREAM_OK
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
// ---- Datagrams ----------------------------------------------------------------
|
|
2916
|
+
|
|
2917
|
+
/**
|
|
2918
|
+
* The largest DATAGRAM payload `sendDatagram` takes now: what fits the
|
|
2919
|
+
* client's `max_datagram_frame_size` with the frame's type and Length, and
|
|
2920
|
+
* a 1-RTT packet of `QUIC_CONN_DATAGRAM_SIZE` with the longest packet
|
|
2921
|
+
* number; 0 when the client takes none, or the server sends none.
|
|
2922
|
+
*/
|
|
2923
|
+
maxDatagramPayload(): i32 {
|
|
2924
|
+
const entry: QuicCidEntry | null = this.cids.currentPeerEntry()
|
|
2925
|
+
const cid: i32 = entry !== null ? entry.length : QUIC_CONN_FROM
|
|
2926
|
+
let limit: i64 = toI64(QUIC_CONN_DATAGRAM_SIZE - 1 - cid - 4 - QUIC_AEAD_TAG_SIZE)
|
|
2927
|
+
if (this.peerMaxDatagramFrame < limit) {
|
|
2928
|
+
limit = this.peerMaxDatagramFrame
|
|
2929
|
+
}
|
|
2930
|
+
// The frame is its type, a Length of 1, 2 or 4 bytes, and the payload.
|
|
2931
|
+
let payload: i64 = limit - 2
|
|
2932
|
+
if (payload > 63) {
|
|
2933
|
+
payload = limit - 3
|
|
2934
|
+
}
|
|
2935
|
+
if (payload > 16383) {
|
|
2936
|
+
payload = limit - 5
|
|
2937
|
+
}
|
|
2938
|
+
const entrySize: i64 = toI64(this.datagramsOut.entrySize)
|
|
2939
|
+
if (payload > entrySize) {
|
|
2940
|
+
payload = entrySize
|
|
2941
|
+
}
|
|
2942
|
+
return payload > 0 && this.datagramsOut.capacity() > 0 ? toI32(payload) : QUIC_CONN_FROM
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
/**
|
|
2946
|
+
* Queues `buf[from .. from + length)` as a DATAGRAM (RFC 9221) for the next
|
|
2947
|
+
* packet the congestion window allows; it is never sent again if lost.
|
|
2948
|
+
* Answers `QUIC_DATAGRAM_OK`, `QUIC_DATAGRAM_ERR_DISABLED` when not
|
|
2949
|
+
* connected, the client takes none, or the server takes none and so sends
|
|
2950
|
+
* none, `QUIC_DATAGRAM_ERR_TOO_BIG` for a frame past the client's limit or
|
|
2951
|
+
* a payload past `maxDatagramPayload()`, or `QUIC_DATAGRAM_ERR_FULL` while
|
|
2952
|
+
* the ring holds `QUIC_CONN_DATAGRAM_QUEUE` waiting.
|
|
2953
|
+
*/
|
|
2954
|
+
sendDatagram(buf: u8[], from: i32, length: i32): i32 {
|
|
2955
|
+
if (
|
|
2956
|
+
this.state !== QUIC_STATE_CONNECTED ||
|
|
2957
|
+
this.peerMaxDatagramFrame === 0 ||
|
|
2958
|
+
this.datagramsOut.capacity() === 0
|
|
2959
|
+
) {
|
|
2960
|
+
return QUIC_DATAGRAM_ERR_DISABLED
|
|
2961
|
+
}
|
|
2962
|
+
if (
|
|
2963
|
+
length < 0 ||
|
|
2964
|
+
toI64(quicDatagramSize(length)) > this.peerMaxDatagramFrame ||
|
|
2965
|
+
length > this.maxDatagramPayload()
|
|
2966
|
+
) {
|
|
2967
|
+
return QUIC_DATAGRAM_ERR_TOO_BIG
|
|
2968
|
+
}
|
|
2969
|
+
if (this.datagramsOut.count >= this.datagramsOut.capacity()) {
|
|
2970
|
+
return QUIC_DATAGRAM_ERR_FULL
|
|
2971
|
+
}
|
|
2972
|
+
return this.datagramsOut.push(buf, from, length) ? QUIC_DATAGRAM_OK : QUIC_DATAGRAM_ERR_TOO_BIG
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
/**
|
|
2976
|
+
* Copies the oldest DATAGRAM payload the client sent into `buf` at `at`,
|
|
2977
|
+
* at most `cap` bytes, and forgets it. Answers its length,
|
|
2978
|
+
* `QUIC_DATAGRAM_NONE` when none is waiting, or `QUIC_DATAGRAM_ERR_ROOM`,
|
|
2979
|
+
* keeping it, when it does not fit.
|
|
2980
|
+
*/
|
|
2981
|
+
readDatagram(buf: u8[], at: i32, cap: i32): i32 {
|
|
2982
|
+
return this.datagramsIn.pop(buf, at, cap)
|
|
2983
|
+
}
|
|
2984
|
+
|
|
2985
|
+
// ---- Sending ------------------------------------------------------------------
|
|
2986
|
+
|
|
2987
|
+
/**
|
|
2988
|
+
* The next datagram to send, or `null` when nothing is due: the
|
|
2989
|
+
* allocating form of `takeDatagramInto`, which a test or a recording uses.
|
|
2990
|
+
*/
|
|
2991
|
+
takeDatagram(now: i64): u8[] | null {
|
|
2992
|
+
const out: u8[] = new Array<u8>(QUIC_CONN_DATAGRAM_SIZE)
|
|
2993
|
+
const n: i32 = this.takeDatagramInto(out, QUIC_CONN_FROM, now)
|
|
2994
|
+
return n > 0 ? quicConnSlice(out, 0, n) : null
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
/**
|
|
2998
|
+
* Writes the next datagram to send into `out` at `at`, which needs
|
|
2999
|
+
* `QUIC_CONN_DATAGRAM_SIZE` bytes of room, and answers its length, or 0
|
|
3000
|
+
* when nothing is due (or the room is not there). Call it until it answers
|
|
3001
|
+
* 0 after every `receive`, `sign`, write and `close`. It coalesces an
|
|
3002
|
+
* Initial, a Handshake and a 1-RTT packet as each has something to carry,
|
|
3003
|
+
* pads a datagram with an ack-eliciting Initial to 1200 bytes (RFC 9000
|
|
3004
|
+
* §14.1), and before the client's address is validated sends only while
|
|
3005
|
+
* three times what was received allows (§8.1). After a close it writes the
|
|
3006
|
+
* CONNECTION_CLOSE once, then nothing. While the handshake waits for a
|
|
3007
|
+
* signature it writes nothing, so the server's flight leaves whole. Lost
|
|
3008
|
+
* data goes before new data. While the congestion window is full (RFC
|
|
3009
|
+
* 9002 §7), or a space's record of packets in flight is, a space sends
|
|
3010
|
+
* only an ACK, unless a probe timeout asked it for a probe. `now` is the
|
|
3011
|
+
* caller's time in milliseconds: a datagram that elicits an
|
|
3012
|
+
* acknowledgement restarts the idle timer when it is the first since the
|
|
3013
|
+
* client's last packet (RFC 9000 §10.1), and once the idle timeout has
|
|
3014
|
+
* passed nothing goes out. The datagram is built and sealed in place,
|
|
3015
|
+
* inside an arena block: nothing it does allocates past the call. This is
|
|
3016
|
+
* not paced: a carrier that paces calls `nish/net/quic-listener`'s
|
|
3017
|
+
* `quicListenerTakeFlight` instead.
|
|
3018
|
+
*/
|
|
3019
|
+
takeDatagramInto(out: u8[], at: i32, now: i64): i32 {
|
|
3020
|
+
this.handleTimer(now)
|
|
3021
|
+
if (
|
|
3022
|
+
this.state === QUIC_STATE_WAIT_INITIAL ||
|
|
3023
|
+
this.state === QUIC_STATE_DRAINING ||
|
|
3024
|
+
this.state === QUIC_STATE_TIMED_OUT ||
|
|
3025
|
+
at < 0 ||
|
|
3026
|
+
at > toI32(out.length) - QUIC_CONN_DATAGRAM_SIZE
|
|
3027
|
+
) {
|
|
3028
|
+
return 0
|
|
3029
|
+
}
|
|
3030
|
+
let n: i32 = 0
|
|
3031
|
+
if (this.state === QUIC_STATE_CLOSING) {
|
|
3032
|
+
using _scope = arena()
|
|
3033
|
+
n = this.takeCloseInto(out, at)
|
|
3034
|
+
return n
|
|
3035
|
+
}
|
|
3036
|
+
const tls: TlsServer | null = this.tls
|
|
3037
|
+
if (tls !== null && tls.state === TLS_STATE_WAIT_SIGNATURE) {
|
|
3038
|
+
return 0
|
|
3039
|
+
}
|
|
3040
|
+
// Unvalidated, a datagram goes only when a whole padded one fits the budget.
|
|
3041
|
+
if (this.amplificationBlocked()) {
|
|
3042
|
+
return 0
|
|
3043
|
+
}
|
|
3044
|
+
{
|
|
3045
|
+
using _scope = arena()
|
|
3046
|
+
n = this.buildDatagram(out, at)
|
|
3047
|
+
}
|
|
3048
|
+
return n
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
/**
|
|
3052
|
+
* `takeDatagramInto`'s datagram, built and sealed into `out` at `at`: one
|
|
3053
|
+
* packet per space with something to carry, each written in place, the
|
|
3054
|
+
* last padded when an Initial elicits, then each sealed where it lies.
|
|
3055
|
+
* Answers the datagram's length, or 0 when no space had anything.
|
|
3056
|
+
*/
|
|
3057
|
+
buildDatagram(out: u8[], at: i32): i32 {
|
|
3058
|
+
let remaining: i32 = QUIC_CONN_DATAGRAM_SIZE
|
|
3059
|
+
let position: i32 = at
|
|
3060
|
+
let paddedInitial: boolean = false
|
|
3061
|
+
let eliciting: boolean = false
|
|
3062
|
+
const open: boolean = this.recovery.canSend()
|
|
3063
|
+
let last: i32 = -1
|
|
3064
|
+
for (let level: i32 = 0; level < 3; level += 1) {
|
|
3065
|
+
const space: QuicConnSpace = this.spaceAt(level)
|
|
3066
|
+
space.builtStart = -1
|
|
3067
|
+
const overhead: i32 = this.overhead(space)
|
|
3068
|
+
const sent: QuicSentPackets | null = this.recovery.space(level)
|
|
3069
|
+
if (sent === null || overhead === 0 || remaining <= overhead + 8) {
|
|
3070
|
+
continue
|
|
3071
|
+
}
|
|
3072
|
+
if (space.probes > 0 && sent.full()) {
|
|
3073
|
+
// A probe has to be recorded: the oldest packet gives up its slot,
|
|
3074
|
+
// and what it carried, already queued again by the probe, stays queued.
|
|
3075
|
+
const slot: i32 = this.recovery.evictOldest(level)
|
|
3076
|
+
if (slot >= 0) {
|
|
3077
|
+
this.requeue(space, slot)
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
const elicit: boolean = space.probes > 0 || (open && !sent.full())
|
|
3081
|
+
const headerLength: i32 = overhead - QUIC_AEAD_TAG_SIZE
|
|
3082
|
+
const payloadStart: i32 = position + headerLength
|
|
3083
|
+
let payloadEnd: i32 = this.buildPayloadInto(
|
|
3084
|
+
space,
|
|
3085
|
+
out,
|
|
3086
|
+
payloadStart,
|
|
3087
|
+
payloadStart + remaining - overhead,
|
|
3088
|
+
elicit
|
|
3089
|
+
)
|
|
3090
|
+
if (payloadEnd <= payloadStart) {
|
|
3091
|
+
continue
|
|
3092
|
+
}
|
|
3093
|
+
// RFC 9001 §5.4.2: the packet number and payload give header
|
|
3094
|
+
// protection at least four bytes to sample from.
|
|
3095
|
+
const pnLength: i32 = quicPacketNumberLength(space.nextPn, space.largestAcked)
|
|
3096
|
+
if (pnLength + payloadEnd - payloadStart < 4) {
|
|
3097
|
+
payloadEnd = quicPutPadding(out, payloadEnd, 4 - pnLength - (payloadEnd - payloadStart))
|
|
3098
|
+
}
|
|
3099
|
+
eliciting = eliciting || space.stagedEliciting
|
|
3100
|
+
// RFC 9000 §14.1: a datagram with an ack-eliciting Initial is padded.
|
|
3101
|
+
if (level === TLS_LEVEL_INITIAL) {
|
|
3102
|
+
paddedInitial = space.stagedEliciting
|
|
3103
|
+
}
|
|
3104
|
+
space.builtStart = position
|
|
3105
|
+
space.builtEnd = payloadEnd
|
|
3106
|
+
last = level
|
|
3107
|
+
remaining = remaining - (payloadEnd + QUIC_AEAD_TAG_SIZE - position)
|
|
3108
|
+
position = payloadEnd + QUIC_AEAD_TAG_SIZE
|
|
3109
|
+
}
|
|
3110
|
+
if (last < 0) {
|
|
3111
|
+
return 0
|
|
3112
|
+
}
|
|
3113
|
+
if (paddedInitial && remaining > 0) {
|
|
3114
|
+
const padded: QuicConnSpace = this.spaceAt(last)
|
|
3115
|
+
padded.builtEnd = quicPutPadding(out, padded.builtEnd, remaining)
|
|
3116
|
+
}
|
|
3117
|
+
if (eliciting && !this.elicitingSent) {
|
|
3118
|
+
this.idleSince = this.now
|
|
3119
|
+
this.elicitingSent = true
|
|
3120
|
+
}
|
|
3121
|
+
// A packet that cannot be sealed, which only keys `quicKeys` did not
|
|
3122
|
+
// make cause, leaves the datagram unsendable: none of it goes.
|
|
3123
|
+
let total: i32 = 0
|
|
3124
|
+
let sealed: boolean = true
|
|
3125
|
+
for (let level: i32 = 0; level <= last; level += 1) {
|
|
3126
|
+
const space: QuicConnSpace = this.spaceAt(level)
|
|
3127
|
+
if (space.builtStart >= 0) {
|
|
3128
|
+
const n: i32 = this.sealAt(space, out, space.builtStart, space.builtEnd)
|
|
3129
|
+
sealed = sealed && n > 0
|
|
3130
|
+
total = total + n
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
if (!sealed) {
|
|
3134
|
+
return 0
|
|
3135
|
+
}
|
|
3136
|
+
this.bytesSent = this.bytesSent + toI64(total)
|
|
3137
|
+
return total
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
/**
|
|
3141
|
+
* The bytes a packet of `space` takes beyond its payload: the header with
|
|
3142
|
+
* its packet number, and the AEAD tag. 0 when the space cannot send: no
|
|
3143
|
+
* keys, discarded, or no packet number length for its next number.
|
|
3144
|
+
*/
|
|
3145
|
+
overhead(space: QuicConnSpace): i32 {
|
|
3146
|
+
if (space.writeKeys === null || space.discarded) {
|
|
3147
|
+
return 0
|
|
3148
|
+
}
|
|
3149
|
+
const pnLength: i32 = quicPacketNumberLength(space.nextPn, space.largestAcked)
|
|
3150
|
+
if (pnLength === 0) {
|
|
3151
|
+
return 0
|
|
3152
|
+
}
|
|
3153
|
+
if (space.level === TLS_LEVEL_APPLICATION) {
|
|
3154
|
+
const entry: QuicCidEntry | null = this.cids.currentPeerEntry()
|
|
3155
|
+
const cid: i32 = entry !== null ? entry.length : QUIC_CONN_FROM
|
|
3156
|
+
return 1 + cid + pnLength + QUIC_AEAD_TAG_SIZE
|
|
3157
|
+
}
|
|
3158
|
+
// First byte, version, both IDs with their lengths, the Length (two
|
|
3159
|
+
// bytes, always) and, for an Initial, the empty token's length.
|
|
3160
|
+
const token: i32 = space.level === TLS_LEVEL_INITIAL ? 1 : 0
|
|
3161
|
+
return 7 + this.peerScidLength + QUIC_CONN_CID_LENGTH + token + 2 + pnLength + QUIC_AEAD_TAG_SIZE
|
|
3162
|
+
}
|
|
3163
|
+
|
|
3164
|
+
/**
|
|
3165
|
+
* The frames of one packet of `space`, written into `out[start .. end)`:
|
|
3166
|
+
* an ACK when one is due, and, when `elicit` allows frames that elicit an
|
|
3167
|
+
* acknowledgement, CRYPTO data (lost bytes first), at the application
|
|
3168
|
+
* level HANDSHAKE_DONE, RETIRE_CONNECTION_ID, NEW_CONNECTION_ID,
|
|
3169
|
+
* PATH_RESPONSE, the streams' control frames, DATAGRAMs and stream data,
|
|
3170
|
+
* and for a probe with nothing else to carry a PING. Answers the offset
|
|
3171
|
+
* past the last frame, `start` when nothing is due. What it carries is
|
|
3172
|
+
* written to the space's staging row, and `stagedEliciting` says whether
|
|
3173
|
+
* anything elicits an acknowledgement.
|
|
3174
|
+
*/
|
|
3175
|
+
buildPayloadInto(space: QuicConnSpace, out: u8[], start: i32, end: i32, elicit: boolean): i32 {
|
|
3176
|
+
let at: i32 = start
|
|
3177
|
+
space.clearStaged()
|
|
3178
|
+
if (space.received.ackPending) {
|
|
3179
|
+
// One too large for the room is left out and stays due.
|
|
3180
|
+
const next: i32 = space.received.putAck(out, at, end, QUIC_CONN_NONE)
|
|
3181
|
+
if (next >= 0) {
|
|
3182
|
+
at = next
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
if (!elicit) {
|
|
3186
|
+
return at
|
|
3187
|
+
}
|
|
3188
|
+
at = this.buildCrypto(space, out, at, end)
|
|
3189
|
+
if (space.level === TLS_LEVEL_APPLICATION) {
|
|
3190
|
+
at = this.buildApplication(space, out, at, end)
|
|
3191
|
+
}
|
|
3192
|
+
if (space.probes > 0 && !space.stagedEliciting && at < end) {
|
|
3193
|
+
at = quicPutTypeOnly(out, at, end, QUIC_FRAME_PING)
|
|
3194
|
+
space.stagedEliciting = true
|
|
3195
|
+
}
|
|
3196
|
+
return at
|
|
3197
|
+
}
|
|
3198
|
+
|
|
3199
|
+
/**
|
|
3200
|
+
* One CRYPTO frame into `out[at .. end)`: the bytes lost first, from the
|
|
3201
|
+
* level's kept stream, then those not sent yet. The level's CRYPTO stream
|
|
3202
|
+
* is kept whole, so a byte's index in `cryptoOut` is its offset.
|
|
3203
|
+
*/
|
|
3204
|
+
buildCrypto(space: QuicConnSpace, out: u8[], at: i32, end: i32): i32 {
|
|
3205
|
+
if (space.cryptoResendLow >= 0) {
|
|
3206
|
+
const low: i64 = space.cryptoResendLow
|
|
3207
|
+
const want: i32 = toI32(space.cryptoResendHigh - low)
|
|
3208
|
+
const left: i32 = end - at - quicCryptoOverhead(low, want)
|
|
3209
|
+
const n: i32 = left < want ? left : want
|
|
3210
|
+
if (n <= 0) {
|
|
3211
|
+
return at
|
|
3212
|
+
}
|
|
3213
|
+
const next: i32 = quicPutCrypto(out, at, end, low, space.cryptoOut, toI32(low), n)
|
|
3214
|
+
if (next < 0) {
|
|
3215
|
+
return at
|
|
3216
|
+
}
|
|
3217
|
+
space.stageCrypto(low, n)
|
|
3218
|
+
space.cryptoResendLow = low + toI64(n)
|
|
3219
|
+
if (space.cryptoResendLow >= space.cryptoResendHigh) {
|
|
3220
|
+
space.cryptoResendLow = -1
|
|
3221
|
+
space.cryptoResendHigh = -1
|
|
3222
|
+
}
|
|
3223
|
+
return next
|
|
3224
|
+
}
|
|
3225
|
+
const unsent: i32 = space.cryptoUnsent()
|
|
3226
|
+
if (unsent <= 0) {
|
|
3227
|
+
return at
|
|
3228
|
+
}
|
|
3229
|
+
const left: i32 = end - at - quicCryptoOverhead(space.cryptoOutOffset, unsent)
|
|
3230
|
+
const n: i32 = left < unsent ? left : unsent
|
|
3231
|
+
if (n <= 0) {
|
|
3232
|
+
return at
|
|
3233
|
+
}
|
|
3234
|
+
const next: i32 = quicPutCrypto(
|
|
3235
|
+
out,
|
|
3236
|
+
at,
|
|
3237
|
+
end,
|
|
3238
|
+
space.cryptoOutOffset,
|
|
3239
|
+
space.cryptoOut,
|
|
3240
|
+
space.cryptoOutHead,
|
|
3241
|
+
n
|
|
3242
|
+
)
|
|
3243
|
+
if (next < 0) {
|
|
3244
|
+
return at
|
|
3245
|
+
}
|
|
3246
|
+
space.stageCrypto(space.cryptoOutOffset, n)
|
|
3247
|
+
space.cryptoOutHead = space.cryptoOutHead + n
|
|
3248
|
+
space.cryptoOutOffset = space.cryptoOutOffset + toI64(n)
|
|
3249
|
+
return next
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
/**
|
|
3253
|
+
* The 1-RTT frames beyond ACK, as many as fit in `out[at .. end)` and in
|
|
3254
|
+
* the packet's record: `QUIC_CONN_PACKET_CONTROL` control frames and
|
|
3255
|
+
* `QUIC_CONN_PACKET_STREAMS` STREAM frames, and every DATAGRAM that fits.
|
|
3256
|
+
*/
|
|
3257
|
+
buildApplication(space: QuicConnSpace, out: u8[], start: i32, end: i32): i32 {
|
|
3258
|
+
let at: i32 = start
|
|
3259
|
+
if (this.handshakeDonePending && at < end) {
|
|
3260
|
+
at = quicPutTypeOnly(out, at, end, QUIC_FRAME_HANDSHAKE_DONE)
|
|
3261
|
+
this.handshakeDonePending = false
|
|
3262
|
+
space.setFlags(space.staging, QUIC_CONN_SENT_HANDSHAKE_DONE)
|
|
3263
|
+
space.stagedEliciting = true
|
|
3264
|
+
}
|
|
3265
|
+
// RETIRE_CONNECTION_ID is at most 9 bytes, NEW_CONNECTION_ID with an
|
|
3266
|
+
// 8-byte ID at most 42, PATH_RESPONSE 9.
|
|
3267
|
+
while (
|
|
3268
|
+
this.cids.retireCount > 0 &&
|
|
3269
|
+
end - at >= 9 &&
|
|
3270
|
+
space.controlCount(space.staging) < QUIC_CONN_PACKET_CONTROL
|
|
3271
|
+
) {
|
|
3272
|
+
const sequence: i64 = this.cids.takeRetire()
|
|
3273
|
+
at = quicPutValue(out, at, end, QUIC_FRAME_RETIRE_CONNECTION_ID, sequence)
|
|
3274
|
+
space.stageControl(QUIC_CONN_CONTROL_RETIRE, sequence)
|
|
3275
|
+
}
|
|
3276
|
+
let entry: QuicCidEntry | null = this.cids.nextUnannounced()
|
|
3277
|
+
while (entry !== null && end - at >= 42 && space.controlCount(space.staging) < QUIC_CONN_PACKET_CONTROL) {
|
|
3278
|
+
const next: i32 = quicPutNewConnectionId(
|
|
3279
|
+
out,
|
|
3280
|
+
at,
|
|
3281
|
+
end,
|
|
3282
|
+
entry.sequence,
|
|
3283
|
+
QUIC_CONN_NONE,
|
|
3284
|
+
entry.cid,
|
|
3285
|
+
entry.length,
|
|
3286
|
+
entry.resetToken
|
|
3287
|
+
)
|
|
3288
|
+
if (next < 0) {
|
|
3289
|
+
break
|
|
3290
|
+
}
|
|
3291
|
+
at = next
|
|
3292
|
+
entry.announced = true
|
|
3293
|
+
space.stageControl(QUIC_CONN_CONTROL_NEW_CID, entry.sequence)
|
|
3294
|
+
entry = this.cids.nextUnannounced()
|
|
3295
|
+
}
|
|
3296
|
+
while (this.pathCount > 0 && end - at >= 9) {
|
|
3297
|
+
const next: i32 = quicPutPathData(
|
|
3298
|
+
out,
|
|
3299
|
+
at,
|
|
3300
|
+
end,
|
|
3301
|
+
QUIC_FRAME_PATH_RESPONSE,
|
|
3302
|
+
this.pathData,
|
|
3303
|
+
(this.pathHead % 4) * QUIC_PATH_DATA_SIZE
|
|
3304
|
+
)
|
|
3305
|
+
if (next < 0) {
|
|
3306
|
+
break
|
|
3307
|
+
}
|
|
3308
|
+
at = next
|
|
3309
|
+
this.pathHead = (this.pathHead + 1) % 4
|
|
3310
|
+
this.pathCount = this.pathCount - 1
|
|
3311
|
+
// Ack-eliciting, but never sent again (RFC 9000 §13.3).
|
|
3312
|
+
space.stagedEliciting = true
|
|
3313
|
+
}
|
|
3314
|
+
const streams: QuicStreams = this.streams
|
|
3315
|
+
while (space.controlCount(space.staging) < QUIC_CONN_PACKET_CONTROL) {
|
|
3316
|
+
const next: i32 = streams.putNextControl(out, at, end)
|
|
3317
|
+
if (next <= at) {
|
|
3318
|
+
break
|
|
3319
|
+
}
|
|
3320
|
+
at = next
|
|
3321
|
+
space.stageControl(streams.lastKind, streams.lastValue)
|
|
3322
|
+
}
|
|
3323
|
+
// DATAGRAMs ride in any packet the window lets elicit, and are recorded
|
|
3324
|
+
// only by size: a lost one is not sent again (RFC 9221 §5.2).
|
|
3325
|
+
let datagram: i32 = this.datagramsOut.putFrame(out, at, end)
|
|
3326
|
+
while (datagram > at) {
|
|
3327
|
+
at = datagram
|
|
3328
|
+
space.stagedEliciting = true
|
|
3329
|
+
datagram = this.datagramsOut.putFrame(out, at, end)
|
|
3330
|
+
}
|
|
3331
|
+
streams.beginPacket()
|
|
3332
|
+
while (space.streamCount(space.staging) < QUIC_CONN_PACKET_STREAMS) {
|
|
3333
|
+
const next: i32 = streams.putNextChunk(out, at, end)
|
|
3334
|
+
if (next <= at) {
|
|
3335
|
+
break
|
|
3336
|
+
}
|
|
3337
|
+
at = next
|
|
3338
|
+
space.stageChunk(streams.lastId, streams.lastOffset, streams.lastLength, streams.lastFin)
|
|
3339
|
+
}
|
|
3340
|
+
return at
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
/**
|
|
3344
|
+
* Seals the packet of `space` whose payload `buildDatagram` wrote from
|
|
3345
|
+
* `start` plus its header to `payloadEnd`: the header written in front of
|
|
3346
|
+
* it, the payload sealed where it lies, and the packet recorded. Answers
|
|
3347
|
+
* the packet's length, or 0 when it could not be sealed, which only keys
|
|
3348
|
+
* `quicKeys` did not make cause.
|
|
3349
|
+
*/
|
|
3350
|
+
sealAt(space: QuicConnSpace, out: u8[], start: i32, payloadEnd: i32): i32 {
|
|
3351
|
+
const keys: QuicKeys | null = space.writeKeys
|
|
3352
|
+
const pn: i64 = space.nextPn
|
|
3353
|
+
const pnLength: i32 = quicPacketNumberLength(pn, space.largestAcked)
|
|
3354
|
+
const headerLength: i32 = this.overhead(space) - QUIC_AEAD_TAG_SIZE
|
|
3355
|
+
const payloadLength: i32 = payloadEnd - start - headerLength
|
|
3356
|
+
const entry: QuicCidEntry | null = this.cids.currentPeerEntry()
|
|
3357
|
+
const headerEnd: i32 =
|
|
3358
|
+
space.level === TLS_LEVEL_APPLICATION
|
|
3359
|
+
? quicPutShortHeader(
|
|
3360
|
+
out,
|
|
3361
|
+
start,
|
|
3362
|
+
entry !== null ? entry.cid : this.none,
|
|
3363
|
+
entry !== null ? entry.length : 0,
|
|
3364
|
+
false,
|
|
3365
|
+
this.writePhase,
|
|
3366
|
+
pn,
|
|
3367
|
+
pnLength
|
|
3368
|
+
)
|
|
3369
|
+
: quicPutLongHeader(
|
|
3370
|
+
out,
|
|
3371
|
+
start,
|
|
3372
|
+
space.level === TLS_LEVEL_INITIAL ? QUIC_PACKET_INITIAL : QUIC_PACKET_HANDSHAKE,
|
|
3373
|
+
this.peerScid,
|
|
3374
|
+
this.peerScidLength,
|
|
3375
|
+
this.localScid,
|
|
3376
|
+
QUIC_CONN_CID_LENGTH,
|
|
3377
|
+
this.none,
|
|
3378
|
+
pn,
|
|
3379
|
+
pnLength,
|
|
3380
|
+
payloadLength
|
|
3381
|
+
)
|
|
3382
|
+
if (keys === null || headerEnd !== start + headerLength) {
|
|
3383
|
+
return 0
|
|
3384
|
+
}
|
|
3385
|
+
const end: i32 = quicSealInPlace(keys, out, start, headerLength, payloadLength, pn)
|
|
3386
|
+
if (end < 0) {
|
|
3387
|
+
return 0
|
|
3388
|
+
}
|
|
3389
|
+
space.nextPn = pn + 1
|
|
3390
|
+
this.recordSent(space, pn, end - start)
|
|
3391
|
+
return end - start
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
/**
|
|
3395
|
+
* Hands an ack-eliciting packet just sealed to loss recovery (RFC 9002
|
|
3396
|
+
* §A.5), with what it carried copied from the staging row to its slot. A
|
|
3397
|
+
* packet that elicits nothing (an ACK, a CONNECTION_CLOSE) is not
|
|
3398
|
+
* recorded. `buildDatagram` builds nothing ack-eliciting into a space
|
|
3399
|
+
* whose record is full, so recovery does not refuse one; if it ever did,
|
|
3400
|
+
* what the packet carried is queued again at once, as though lost.
|
|
3401
|
+
*/
|
|
3402
|
+
recordSent(space: QuicConnSpace, pn: i64, size: i32): void {
|
|
3403
|
+
if (!space.stagedEliciting) {
|
|
3404
|
+
return
|
|
3405
|
+
}
|
|
3406
|
+
const slot: i32 = this.recovery.onPacketSent(space.level, pn, size, this.now)
|
|
3407
|
+
if (slot >= 0) {
|
|
3408
|
+
space.commitStaged(slot)
|
|
3409
|
+
if (space.probes > 0) {
|
|
3410
|
+
space.probes -= 1
|
|
3411
|
+
// §6.2.4: a second probe carries unacknowledged data too. When the
|
|
3412
|
+
// first took everything queued, the oldest — what it just carried —
|
|
3413
|
+
// goes again.
|
|
3414
|
+
if (space.probes > 0 && space.cryptoResendLow < 0) {
|
|
3415
|
+
this.requeue(space, slot)
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
} else {
|
|
3419
|
+
this.requeue(space, space.staging)
|
|
3420
|
+
}
|
|
3421
|
+
space.clearStaged()
|
|
3422
|
+
}
|
|
3423
|
+
|
|
3424
|
+
/** An acknowledged packet in row `row`: each stream chunk and stream control frame it carried is settled. */
|
|
3425
|
+
untrack(space: QuicConnSpace, row: i32): void {
|
|
3426
|
+
const streams: i32 = space.streamCount(row)
|
|
3427
|
+
for (let j: i32 = 0; j < streams; j += 1) {
|
|
3428
|
+
const at: i32 = row * QUIC_CONN_PACKET_STREAMS + j
|
|
3429
|
+
if (
|
|
3430
|
+
at >= 0 &&
|
|
3431
|
+
at < toI32(space.sentStreamId.length) &&
|
|
3432
|
+
at < toI32(space.sentStreamOffset.length) &&
|
|
3433
|
+
at < toI32(space.sentStreamLength.length) &&
|
|
3434
|
+
at < toI32(space.sentStreamFin.length)
|
|
3435
|
+
) {
|
|
3436
|
+
this.streams.chunkAcked(
|
|
3437
|
+
space.sentStreamId[at],
|
|
3438
|
+
space.sentStreamOffset[at],
|
|
3439
|
+
space.sentStreamLength[at],
|
|
3440
|
+
toI32(space.sentStreamFin[at]) !== 0
|
|
3441
|
+
)
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
const control: i32 = space.controlCount(row)
|
|
3445
|
+
for (let j: i32 = 0; j < control; j += 1) {
|
|
3446
|
+
const at: i32 = row * QUIC_CONN_PACKET_CONTROL + j
|
|
3447
|
+
if (at >= 0 && at < toI32(space.sentControlKind.length) && at < toI32(space.sentControlValue.length)) {
|
|
3448
|
+
this.streams.controlAcked(toI32(space.sentControlKind[at]), space.sentControlValue[at])
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3453
|
+
/**
|
|
3454
|
+
* The one CONNECTION_CLOSE this side owes, written into `out` at `at`,
|
|
3455
|
+
* then nothing (0). It goes in every space the server still has keys for,
|
|
3456
|
+
* since the client may not yet have the newest (RFC 9000 §10.2.3); in an
|
|
3457
|
+
* Initial or Handshake packet an application's close is sent as the
|
|
3458
|
+
* transport close APPLICATION_ERROR, since those packets may not carry
|
|
3459
|
+
* 0x1d (§12.4). Before the handshake is complete no 1-RTT packet carries
|
|
3460
|
+
* it, because the client cannot yet read one.
|
|
3461
|
+
*/
|
|
3462
|
+
takeCloseInto(out: u8[], at: i32): i32 {
|
|
3463
|
+
if (this.closeSent) {
|
|
3464
|
+
return 0
|
|
3465
|
+
}
|
|
3466
|
+
this.closeSent = true
|
|
3467
|
+
let position: i32 = at
|
|
3468
|
+
const limit: i32 = at + QUIC_CONN_DATAGRAM_SIZE
|
|
3469
|
+
for (let level: i32 = 0; level < 3; level += 1) {
|
|
3470
|
+
const space: QuicConnSpace = this.spaceAt(level)
|
|
3471
|
+
const overhead: i32 = this.overhead(space)
|
|
3472
|
+
if (overhead === 0 || (level === TLS_LEVEL_APPLICATION && !this.handshakeComplete)) {
|
|
3473
|
+
continue
|
|
3474
|
+
}
|
|
3475
|
+
space.clearStaged()
|
|
3476
|
+
const payloadStart: i32 = position + overhead - QUIC_AEAD_TAG_SIZE
|
|
3477
|
+
const room: i32 = limit - QUIC_AEAD_TAG_SIZE
|
|
3478
|
+
let payloadEnd: i32 =
|
|
3479
|
+
level === TLS_LEVEL_APPLICATION || !this.errorIsApplication
|
|
3480
|
+
? quicPutConnectionClose(
|
|
3481
|
+
out,
|
|
3482
|
+
payloadStart,
|
|
3483
|
+
room,
|
|
3484
|
+
this.errorIsApplication,
|
|
3485
|
+
this.error,
|
|
3486
|
+
this.errorFrameType,
|
|
3487
|
+
this.none
|
|
3488
|
+
)
|
|
3489
|
+
: quicPutConnectionClose(
|
|
3490
|
+
out,
|
|
3491
|
+
payloadStart,
|
|
3492
|
+
room,
|
|
3493
|
+
false,
|
|
3494
|
+
QUIC_ERROR_APPLICATION,
|
|
3495
|
+
QUIC_CONN_NONE,
|
|
3496
|
+
this.none
|
|
3497
|
+
)
|
|
3498
|
+
if (payloadEnd < 0) {
|
|
3499
|
+
continue
|
|
3500
|
+
}
|
|
3501
|
+
const pnLength: i32 = quicPacketNumberLength(space.nextPn, space.largestAcked)
|
|
3502
|
+
if (pnLength + payloadEnd - payloadStart < 4) {
|
|
3503
|
+
payloadEnd = quicPutPadding(out, payloadEnd, 4 - pnLength - (payloadEnd - payloadStart))
|
|
3504
|
+
}
|
|
3505
|
+
position = position + this.sealAt(space, out, position, payloadEnd)
|
|
3506
|
+
}
|
|
3507
|
+
this.bytesSent = this.bytesSent + toI64(position - at)
|
|
3508
|
+
return position - at
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3511
|
+
/**
|
|
3512
|
+
* Wipes everything secret the connection and its `TlsServer` still hold:
|
|
3513
|
+
* every level's packet keys, the next generation's read keys, the traffic
|
|
3514
|
+
* secrets and expected client Finished, the ephemeral key, the
|
|
3515
|
+
* connection-ID seed and the stateless reset tokens (QUIC-2). Call it when
|
|
3516
|
+
* the connection is done with (or `reset` the slot, which does the same
|
|
3517
|
+
* first); the connection is closed after it and sends nothing more.
|
|
3518
|
+
*/
|
|
3519
|
+
release(): void {
|
|
3520
|
+
this.wipeAll()
|
|
3521
|
+
if (!this.closed()) {
|
|
3522
|
+
this.state = QUIC_STATE_CLOSING
|
|
3523
|
+
this.error = QUIC_ERROR_NO_ERROR
|
|
3524
|
+
}
|
|
3525
|
+
this.closeSent = true
|
|
3526
|
+
}
|
|
3527
|
+
|
|
3528
|
+
/**
|
|
3529
|
+
* Wipes every key and secret the connection holds and discards every
|
|
3530
|
+
* level: `release()`'s work, the idle timeout's and `reset`'s. That takes
|
|
3531
|
+
* in both sides' stateless reset tokens and `TlsServer`'s expected client
|
|
3532
|
+
* Finished.
|
|
3533
|
+
*/
|
|
3534
|
+
wipeAll(): void {
|
|
3535
|
+
this.discard(this.initial)
|
|
3536
|
+
this.discard(this.handshake)
|
|
3537
|
+
this.discard(this.application)
|
|
3538
|
+
quicConnWipePacketKey(this.otherReadKeys)
|
|
3539
|
+
this.otherReadKeys = null
|
|
3540
|
+
this.otherIsNext = false
|
|
3541
|
+
this.keyRetainUntil = -1
|
|
3542
|
+
secureZero(this.appReadSecret)
|
|
3543
|
+
secureZero(this.appWriteSecret)
|
|
3544
|
+
secureZero(this.nextReadSecret)
|
|
3545
|
+
for (const secret of this.writeSecrets) {
|
|
3546
|
+
secureZero(secret)
|
|
3547
|
+
}
|
|
3548
|
+
for (const secret of this.readSecrets) {
|
|
3549
|
+
secureZero(secret)
|
|
3550
|
+
}
|
|
3551
|
+
secureZero(this.initialClient)
|
|
3552
|
+
secureZero(this.initialServer)
|
|
3553
|
+
secureZero(this.cidSeed)
|
|
3554
|
+
secureZero(this.ephemeralPrivate)
|
|
3555
|
+
// The stateless reset tokens of both sides' IDs: either one ends the
|
|
3556
|
+
// connection for whoever learns it (RFC 9000 §10.3).
|
|
3557
|
+
for (const entry of this.cids.local) {
|
|
3558
|
+
secureZero(entry.resetToken)
|
|
3559
|
+
}
|
|
3560
|
+
for (const entry of this.cids.peer) {
|
|
3561
|
+
secureZero(entry.resetToken)
|
|
3562
|
+
}
|
|
3563
|
+
secureZero(this.peerParameters.statelessResetToken)
|
|
3564
|
+
secureZero(this.localParameters.statelessResetToken)
|
|
3565
|
+
const tls: TlsServer | null = this.tls
|
|
3566
|
+
if (tls !== null) {
|
|
3567
|
+
// The server's encoded transport parameters carry its first ID's token.
|
|
3568
|
+
secureZero(tls.config.quicTransportParameters)
|
|
3569
|
+
secureZero(tls.expectedClientFinished)
|
|
3570
|
+
secureZero(tls.handshakeSecret)
|
|
3571
|
+
secureZero(tls.clientHandshakeSecret)
|
|
3572
|
+
secureZero(tls.serverHandshakeSecret)
|
|
3573
|
+
secureZero(tls.clientApplicationSecret)
|
|
3574
|
+
secureZero(tls.serverApplicationSecret)
|
|
3575
|
+
secureZero(tls.exporterSecret)
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
}
|