@amritk/nish-x86_64-linux 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/INSTALL.md +43 -34
  2. package/bin/nish +0 -0
  3. package/package.json +1 -1
  4. package/runtime/nish.d.ts +117 -8
  5. package/runtime/nish.h +30 -7
  6. package/runtime/nish.mjs +71 -7
  7. package/runtime/runtime-host.c +43 -24
  8. package/runtime/runtime-net.c +57 -10
  9. package/runtime/runtime-os.c +62 -20
  10. package/runtime/runtime-wasm.c +1 -0
  11. package/runtime/runtime.c +46 -8
  12. package/runtime/shim.mjs +114 -5
  13. package/scripts/build.sh +1 -3
  14. package/std/README.md +166 -35
  15. package/std/crypto/aes.ts +51 -8
  16. package/std/crypto/base64url.ts +25 -12
  17. package/std/crypto/chacha20poly1305.ts +43 -29
  18. package/std/crypto/ct.ts +6 -1
  19. package/std/crypto/hkdf.ts +364 -3
  20. package/std/crypto/hmac.ts +307 -7
  21. package/std/crypto/p256.ts +148 -27
  22. package/std/crypto/sha1.ts +147 -0
  23. package/std/crypto/sha256.ts +3 -2
  24. package/std/crypto/x25519.ts +88 -36
  25. package/std/crypto/x509.ts +130 -47
  26. package/std/net/hpack.ts +1094 -0
  27. package/std/net/http-fields.ts +604 -0
  28. package/std/net/http1-server.ts +1389 -0
  29. package/std/net/http1.ts +1492 -0
  30. package/std/net/http2-frame.ts +745 -0
  31. package/std/net/http2-tls.ts +216 -0
  32. package/std/net/http2.ts +1433 -0
  33. package/std/net/http3-frame.ts +457 -0
  34. package/std/net/http3-server.ts +905 -0
  35. package/std/net/http3.ts +2145 -0
  36. package/std/net/qpack.ts +1163 -0
  37. package/std/net/quic-conn-ack.ts +206 -0
  38. package/std/net/quic-conn-cid.ts +425 -0
  39. package/std/net/quic-conn-params.ts +392 -0
  40. package/std/net/quic-datagram.ts +130 -0
  41. package/std/net/quic-frame.ts +1206 -0
  42. package/std/net/quic-listener.ts +653 -0
  43. package/std/net/quic-packet.ts +1474 -0
  44. package/std/net/quic-recovery.ts +849 -0
  45. package/std/net/quic-stream.ts +1603 -0
  46. package/std/net/quic.ts +3214 -0
  47. package/std/net/tls/codec.ts +1089 -0
  48. package/std/net/tls/record-server.ts +1173 -0
  49. package/std/net/tls/record.ts +621 -0
  50. package/std/net/tls/schedule.ts +429 -0
  51. package/std/net/tls-tcp.ts +410 -0
  52. package/std/net/tls.ts +1368 -0
  53. package/std/net/websocket.ts +823 -0
  54. package/std/net/webtransport.ts +1334 -0
  55. package/std/secret.ts +62 -0
@@ -0,0 +1,1474 @@
1
+ /**
2
+ * `nish/net/quic-packet` — QUIC version 1's packets, as RFC 9000 §16-§17
3
+ * frames them and RFC 9001 §5 protects them: the variable-length integer,
4
+ * the long and short headers, packet-number encoding and recovery, the
5
+ * Initial secrets, AEAD packet protection, header protection and the Retry
6
+ * integrity tag.
7
+ *
8
+ * import { quicInitialSecrets, quicKeys, quicParseHeader, quicOpenPacket,
9
+ * QUIC_AEAD_AES_128_GCM, QUIC_PACKET_OK } from "nish/net/quic-packet";
10
+ *
11
+ * const secrets = quicInitialSecrets(clientDcid); // RFC 9001 §5.2
12
+ * const keys = quicKeys(QUIC_AEAD_AES_128_GCM, secrets.client);
13
+ * const header = quicParseHeader(datagram, 0, 8); // 8: our short-header DCID length
14
+ * const packet = quicOpenPacket(keys, datagram, header, largestReceived);
15
+ * if (packet.error === QUIC_PACKET_OK) { … packet.payload holds the frames … }
16
+ *
17
+ * The module is sans-IO: bytes in, bytes out, no socket and no clock. It
18
+ * frames and protects packets and stops there, so it knows nothing of frames,
19
+ * of a connection's state or of which keys a level is on; that is
20
+ * `nish/net/quic` (WP34 Q2). Only version 1 (`0x00000001`) is spoken. A long
21
+ * header of another version is parsed as far as RFC 8999's invariants go,
22
+ * which is what a server needs to send Version Negotiation, and reported as
23
+ * `QUIC_ERR_VERSION`.
24
+ *
25
+ * **What a parse refuses.** Every read is bounds-checked against the
26
+ * datagram, so nothing a peer sends can make this module panic. A header
27
+ * that runs past the datagram is `QUIC_ERR_TRUNCATED`; a clear fixed bit
28
+ * `QUIC_ERR_FIXED_BIT`; a connection ID over 20 bytes in a version 1 header
29
+ * `QUIC_ERR_CID_LENGTH`; a Length field reaching past the datagram
30
+ * `QUIC_ERR_LENGTH`; a packet too short to sample for header protection
31
+ * `QUIC_ERR_SAMPLE`; a payload that does not authenticate `QUIC_ERR_DECRYPT`;
32
+ * and nonzero reserved bits once both protections are off
33
+ * `QUIC_ERR_RESERVED_BITS`, which RFC 9000 §17.2 makes a connection error
34
+ * rather than a drop, so the payload is still handed back with it. The
35
+ * builders, whose inputs come from this side rather than from a peer, answer
36
+ * `null` for an argument out of range.
37
+ *
38
+ * **In place.** For a connection that allocates nothing per packet,
39
+ * `quicParseHeaderInto` reads a header as windows into the datagram, the
40
+ * `quicPut*` builders write a header into a caller's buffer, `quicSealInPlace`
41
+ * seals a packet where it lies, and `quicUnprotectHeader` and
42
+ * `quicDecryptPayload` open one storing only numbers into the `QuicPacket`
43
+ * they are given. The AEADs allocate; `quicSealInPlace` releases what they
44
+ * allocated before it answers, and the opening halves answer arrays the
45
+ * caller releases, inside its own arena block.
46
+ *
47
+ * **Coalesced packets** (RFC 9000 §12.2) are split by length: a long header
48
+ * carries its own Length, and `QuicHeader.end` is where the next packet in the
49
+ * datagram starts. A short header has none, so it runs to the datagram's end.
50
+ *
51
+ * **Secrets are not wiped.** `QuicKeys` holds a packet key, an IV and a
52
+ * header-protection key, and `quicKeys`, `quicKeyUpdateSecret` and the seal
53
+ * and open functions hold traffic secrets and keys in arena memory until that
54
+ * memory is reused. `nish/crypto`'s signers and X25519 now hold their keys as
55
+ * a `Secret` from `nish:secret` and wipe them (ECC-2, X509-7); this module
56
+ * does not yet, and `docs/security/quic.md` records that as QUIC-1. The
57
+ * Initial keys are not secret, because anyone who reads the client's first
58
+ * DCID can derive them (RFC 9001 §5.2); the Handshake and 1-RTT keys are.
59
+ *
60
+ * **Constant time.** The AEADs and the header-protection masks are
61
+ * `nish/crypto/aes` and `nish/crypto/chacha20poly1305`, with their guarantees.
62
+ * What this module branches on is public: lengths, the packet type, the
63
+ * packet number once header protection is off (it is in the clear to anyone
64
+ * who holds the header-protection key, and RFC 9001 §5.4.1 protects it against
65
+ * observers, not timing), and whether a payload authenticated.
66
+ *
67
+ * Written from RFC 9000 and RFC 9001, in this module's own structure: nothing
68
+ * here is ported from another implementation. The packet-number decoding
69
+ * follows the arithmetic of RFC 9000 §A.3 and the length choice the rule of
70
+ * §A.2, neither line by line.
71
+ *
72
+ * Private names carry the `quicPacket` prefix because a `std/` module's
73
+ * private functions share the importing program's flat symbol namespace
74
+ * (`docs/wp26-stdlib.md` §3e).
75
+ */
76
+ import { aesGcmOpen, aesGcmSeal, aesHeaderMask, aesKey, AesKey } from "nish/crypto/aes"
77
+ import { chacha20HeaderMask, chacha20Poly1305Open, chacha20Poly1305Seal } from "nish/crypto/chacha20poly1305"
78
+ import { timingSafeEqual } from "nish/crypto/ct"
79
+ import { hkdfExpandLabelSha256, hkdfExpandLabelSha384, hkdfExtractSha256 } from "nish/crypto/hkdf"
80
+
81
+ /** QUIC version 1 (RFC 9000 §15), the only version this module protects. */
82
+ export const QUIC_VERSION_1: i64 = 1
83
+ /** The longest connection ID version 1 allows (RFC 9000 §17.2). */
84
+ export const QUIC_MAX_CID_LENGTH: i32 = 20
85
+ /**
86
+ * The largest value a variable-length integer holds, 2^62 - 1 (RFC 9000 §16),
87
+ * and so the largest packet number. Spelled as 2^30 × 2^32 - 1 because an
88
+ * `i64` literal past 2^53 is refused (`docs/LANGUAGE.md`, numeric literals).
89
+ */
90
+ export const QUIC_MAX_VARINT: i64 = 1073741824 * 4294967296 - 1
91
+ /** Every version 1 AEAD's tag, and so what each protected payload adds, in bytes (RFC 9001 §5.3). */
92
+ export const QUIC_AEAD_TAG_SIZE: i32 = 16
93
+ /** The bytes of ciphertext header protection samples (RFC 9001 §5.4.2). */
94
+ export const QUIC_SAMPLE_SIZE: i32 = 16
95
+ /** The Retry integrity tag, in bytes (RFC 9001 §5.8). */
96
+ export const QUIC_RETRY_TAG_SIZE: i32 = 16
97
+
98
+ /** A long-header Initial packet (RFC 9000 §17.2.2). */
99
+ export const QUIC_PACKET_INITIAL: i32 = 0
100
+ /** A long-header 0-RTT packet (RFC 9000 §17.2.3): parsed, since a server may be sent one; `quicLongHeader` builds one for a client. */
101
+ export const QUIC_PACKET_ZERO_RTT: i32 = 1
102
+ /** A long-header Handshake packet (RFC 9000 §17.2.4). */
103
+ export const QUIC_PACKET_HANDSHAKE: i32 = 2
104
+ /** A long-header Retry packet (RFC 9000 §17.2.5). */
105
+ export const QUIC_PACKET_RETRY: i32 = 3
106
+ /** A short-header, 1-RTT packet (RFC 9000 §17.3.1). */
107
+ export const QUIC_PACKET_SHORT: i32 = 4
108
+
109
+ /** AEAD_AES_128_GCM with SHA-256, the Initial packets' AEAD (TLS_AES_128_GCM_SHA256). */
110
+ export const QUIC_AEAD_AES_128_GCM: i32 = 1
111
+ /** AEAD_AES_256_GCM with SHA-384 (TLS_AES_256_GCM_SHA384). */
112
+ export const QUIC_AEAD_AES_256_GCM: i32 = 2
113
+ /** AEAD_CHACHA20_POLY1305 with SHA-256 (TLS_CHACHA20_POLY1305_SHA256). */
114
+ export const QUIC_AEAD_CHACHA20_POLY1305: i32 = 3
115
+
116
+ /** A packet parsed, or opened, with nothing wrong. */
117
+ export const QUIC_PACKET_OK: i32 = 0
118
+ /** The header runs past the end of the datagram. */
119
+ export const QUIC_ERR_TRUNCATED: i32 = 1
120
+ /** The fixed bit, 0x40 of the first byte, is clear (RFC 9000 §17.2, §17.3.1). */
121
+ export const QUIC_ERR_FIXED_BIT: i32 = 2
122
+ /** A long header of a version other than 1; `version`, `dcid` and `scid` are still filled in (RFC 8999 §5.1). */
123
+ export const QUIC_ERR_VERSION: i32 = 3
124
+ /** A version 1 connection ID longer than 20 bytes (RFC 9000 §17.2). */
125
+ export const QUIC_ERR_CID_LENGTH: i32 = 4
126
+ /** A long header whose Length field reaches past the end of the datagram (RFC 9000 §17.2). */
127
+ export const QUIC_ERR_LENGTH: i32 = 5
128
+ /** Too few bytes after the packet number's offset to take the 16-byte sample (RFC 9001 §5.4.2). */
129
+ export const QUIC_ERR_SAMPLE: i32 = 6
130
+ /** The payload does not authenticate under the keys given (RFC 9001 §5.3). */
131
+ export const QUIC_ERR_DECRYPT: i32 = 7
132
+ /** The payload authenticated but a reserved bit is set, a PROTOCOL_VIOLATION (RFC 9000 §17.2, §17.3.1). */
133
+ export const QUIC_ERR_RESERVED_BITS: i32 = 8
134
+ /** The packet carries no protected payload: a Retry (RFC 9000 §17.2.5). */
135
+ export const QUIC_ERR_NOT_PROTECTED: i32 = 9
136
+ /** Keys `quicKeys` or `quicKeysUpdate` did not make: a caller's own mistake, never a peer's. */
137
+ export const QUIC_ERR_KEYS: i32 = 10
138
+
139
+ /** RFC 9001 §5.2's `initial_salt` for version 1. A function, because a module constant cannot be an array. */
140
+ const quicPacketInitialSalt = (): u8[] => [
141
+ 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb,
142
+ 0x7f, 0x0a,
143
+ ]
144
+
145
+ /** RFC 9001 §5.8's fixed AES-128-GCM key for version 1's Retry integrity tag. */
146
+ const quicPacketRetryKey = (): u8[] => [
147
+ 0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a, 0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68, 0xc8, 0x4e,
148
+ ]
149
+
150
+ /** RFC 9001 §5.8's fixed nonce for version 1's Retry integrity tag. */
151
+ const quicPacketRetryNonce = (): u8[] => [
152
+ 0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb,
153
+ ]
154
+
155
+ /** The largest Length a long header carries, the most a two-byte varint holds. */
156
+ const QUIC_MAX_LENGTH: i32 = 16383
157
+
158
+ /** The AEAD nonce, and so every packet IV, in bytes (RFC 9001 §5.3). */
159
+ const QUIC_IV_SIZE: i32 = 12
160
+
161
+ /**
162
+ * One packet's header as `quicParseHeader` read it from a datagram. The
163
+ * fields after `error` are meaningful as far as the parse got: all of them
164
+ * when `error` is `QUIC_PACKET_OK`, and `version`, `dcid` and `scid` for
165
+ * `QUIC_ERR_VERSION`, which is what Version Negotiation needs.
166
+ *
167
+ * The packet number and the low bits of the first byte are still under
168
+ * header protection here, so the header carries where they are rather than
169
+ * what they hold; `quicOpenPacket` reads them.
170
+ */
171
+ export class QuicHeader {
172
+ /** `QUIC_PACKET_OK`, or the `QUIC_ERR_*` that stopped the parse. */
173
+ error: i32 = 0
174
+ /** `QUIC_PACKET_INITIAL`, `_ZERO_RTT`, `_HANDSHAKE`, `_RETRY` or `_SHORT`. */
175
+ type: i32 = 0
176
+ /** The long header's 32-bit version, held in an `i64`; 0 for a short header, which has none. */
177
+ version: i64 = 0
178
+ /** The Destination Connection ID. */
179
+ dcid: u8[]
180
+ /** The Source Connection ID; empty for a short header. */
181
+ scid: u8[]
182
+ /** An Initial's token, or a Retry's; empty for every other type. */
183
+ token: u8[]
184
+ /** A Retry's integrity tag, the datagram's last 16 bytes; empty for every other type. */
185
+ retryTag: u8[]
186
+ /** Where in the datagram the packet starts. */
187
+ start: i32 = 0
188
+ /**
189
+ * Where the protected packet number starts: the header-protection sample
190
+ * starts 4 bytes after it. For a Retry, which has none, where its tag starts.
191
+ */
192
+ pnOffset: i32 = 0
193
+ /** One past the packet's last byte, which is where the next coalesced packet starts. */
194
+ end: i32 = 0
195
+ /**
196
+ * Where `dcid`, `scid` and `token` sit in the datagram, as far as the
197
+ * parse got. `quicParseHeaderInto` fills these and nothing else, so a
198
+ * connection reads a header with no copy; `quicParseHeader` copies the
199
+ * windows out into the arrays above.
200
+ */
201
+ dcidStart: i32 = 0
202
+ dcidLength: i32 = 0
203
+ scidStart: i32 = 0
204
+ scidLength: i32 = 0
205
+ tokenStart: i32 = 0
206
+ tokenLength: i32 = 0
207
+
208
+ constructor() {
209
+ this.dcid = []
210
+ this.scid = []
211
+ this.token = []
212
+ this.retryTag = []
213
+ }
214
+ }
215
+
216
+ /**
217
+ * One packet's protection keys (RFC 9001 §5.1): the AEAD's key and IV, and
218
+ * the header-protection key, all derived from one traffic secret by
219
+ * `quicKeys`. For the AES suites the two keys are also kept expanded, so a
220
+ * connection expands them once rather than once a packet.
221
+ */
222
+ export class QuicKeys {
223
+ /** `QUIC_AEAD_AES_128_GCM`, `_AES_256_GCM` or `_CHACHA20_POLY1305`. */
224
+ aead: i32 = 0
225
+ /** The packet-protection key: 16 bytes for AES-128-GCM, 32 for the others. */
226
+ key: u8[]
227
+ /** The 12-byte IV the packet number is XORed into. */
228
+ iv: u8[]
229
+ /** The header-protection key, the same length as `key`. */
230
+ hp: u8[]
231
+ /** `key` expanded, for the AES suites; `null` for ChaCha20-Poly1305. */
232
+ packetAes: AesKey | null = null
233
+ /** `hp` expanded, for the AES suites; `null` for ChaCha20-Poly1305. */
234
+ hpAes: AesKey | null = null
235
+
236
+ constructor(aead: i32, key: u8[], iv: u8[], hp: u8[]) {
237
+ this.aead = aead
238
+ this.key = key
239
+ this.iv = iv
240
+ this.hp = hp
241
+ }
242
+ }
243
+
244
+ /** The two Initial secrets RFC 9001 §5.2 derives from the client's first Destination Connection ID. */
245
+ export class QuicInitialSecrets {
246
+ /** `client_initial_secret`: what protects the client's Initial packets. */
247
+ client: u8[]
248
+ /** `server_initial_secret`: what protects the server's. */
249
+ server: u8[]
250
+
251
+ constructor(client: u8[], server: u8[]) {
252
+ this.client = client
253
+ this.server = server
254
+ }
255
+ }
256
+
257
+ /**
258
+ * One packet with its protection removed by `quicOpenPacket`, or as far as
259
+ * that got. With `error` at `QUIC_PACKET_OK` or `QUIC_ERR_RESERVED_BITS`
260
+ * every field is filled in; with `QUIC_ERR_DECRYPT` all but `payload`.
261
+ */
262
+ export class QuicPacket {
263
+ /** The full packet number, recovered from the truncated one (RFC 9000 §17.1). */
264
+ packetNumber: i64 = 0
265
+ /** The header in the clear, packet number included: the AEAD's associated data. */
266
+ header: u8[]
267
+ /** The decrypted frames. */
268
+ payload: u8[]
269
+ /** `QUIC_PACKET_OK`, or the `QUIC_ERR_*` that stopped the open. */
270
+ error: i32 = 0
271
+ /** The first byte with header protection removed. */
272
+ firstByte: i32 = 0
273
+ /** The packet number's length on the wire, 1 to 4 bytes. */
274
+ pnLength: i32 = 0
275
+ /** A short header's Key Phase bit (RFC 9001 §6); `false` for a long header. */
276
+ keyPhase: boolean = false
277
+
278
+ constructor() {
279
+ this.header = []
280
+ this.payload = []
281
+ }
282
+ }
283
+
284
+ /**
285
+ * HKDF-Expand-Label (RFC 8446 §7.1, RFC 9001 §5.1) with an empty context,
286
+ * under the hash `aead` pairs with: SHA-384 for AES-256-GCM, SHA-256 for the
287
+ * others. Every label and length here is a fixed one the expand accepts.
288
+ */
289
+ const quicPacketExpandLabel = (aead: i32, secret: u8[], label: string, length: i32): u8[] =>
290
+ aead === QUIC_AEAD_AES_256_GCM
291
+ ? hkdfExpandLabelSha384(secret, label, [], length)
292
+ : hkdfExpandLabelSha256(secret, label, [], length)
293
+
294
+ /**
295
+ * The bytes `bytes[from .. to)` as a fresh array of exactly that size, so a
296
+ * packet-sized copy is one allocation rather than a push-grown one; the
297
+ * window is clamped to `bytes`, though every caller has checked it.
298
+ */
299
+ const quicPacketSlice = (bytes: u8[], from: i32, to: i32): u8[] => {
300
+ const length: i32 = toI32(bytes.length)
301
+ const start: i32 = from < 0 ? 0 : from
302
+ const stop: i32 = to > length ? length : to
303
+ const size: i32 = stop > start ? stop - start : 0
304
+ const out: u8[] = new Array<u8>(size)
305
+ const outLength: i32 = toI32(out.length)
306
+ for (let k: i32 = 0; k < outLength; k += 1) {
307
+ if (start + k < toI32(bytes.length)) {
308
+ out[k] = bytes[start + k]
309
+ }
310
+ }
311
+ return out
312
+ }
313
+
314
+ /** Appends every byte of `bytes` to `out`. */
315
+ const quicPacketAppend = (out: u8[], bytes: u8[]): void => {
316
+ for (const b of bytes) {
317
+ out.push(b)
318
+ }
319
+ }
320
+
321
+ /**
322
+ * The bytes a variable-length integer needs (RFC 9000 §16): 1, 2, 4 or 8, or
323
+ * 0 for a value that has no encoding, below zero or past 2^62 - 1.
324
+ */
325
+ export const quicVarintSize = (value: i64): i32 => {
326
+ if (value < 0 || value > QUIC_MAX_VARINT) {
327
+ return 0
328
+ }
329
+ if (value <= 63) {
330
+ return 1
331
+ }
332
+ if (value <= 16383) {
333
+ return 2
334
+ }
335
+ if (value <= 1073741823) {
336
+ return 4
337
+ }
338
+ return 8
339
+ }
340
+
341
+ /**
342
+ * Writes `value` into `buf[at .. at + size)` as a variable-length integer of
343
+ * exactly `size` bytes, which may be longer than it needs (RFC 9000 §16
344
+ * allows that, and a Length field written before its packet is complete uses
345
+ * it). Answers `at + size`, or -1, writing nothing, when `size` is not 1, 2,
346
+ * 4 or 8, is too short for `value`, or does not fit in `buf` from `at`.
347
+ */
348
+ export const quicVarintPut = (buf: u8[], at: i32, value: i64, size: i32): i32 => {
349
+ const needed: i32 = quicVarintSize(value)
350
+ if (needed === 0 || size < needed || at < 0 || at > toI32(buf.length) - size) {
351
+ return -1
352
+ }
353
+ // The two-bit prefix is log2 of the size: 0, 1, 2 or 3 for 1, 2, 4 or 8 bytes.
354
+ let prefix: i32 = 0
355
+ if (size === 2) {
356
+ prefix = 1
357
+ } else if (size === 4) {
358
+ prefix = 2
359
+ } else if (size === 8) {
360
+ prefix = 3
361
+ } else if (size !== 1) {
362
+ return -1
363
+ }
364
+ for (let k: i32 = 0; k < size; k += 1) {
365
+ const shift: i64 = toI64(size - 1 - k) * 8
366
+ let b: i32 = toI32((value >> shift) & 255)
367
+ if (k === 0) {
368
+ b = b | (prefix << 6)
369
+ }
370
+ if (at + k < toI32(buf.length)) {
371
+ buf[at + k] = toU8(b)
372
+ }
373
+ }
374
+ return at + size
375
+ }
376
+
377
+ /**
378
+ * Appends `count` zero bytes to `out`, which a writer then fills in place
379
+ * with the `put` form of what it appends: one encoder serves both.
380
+ */
381
+ export const quicPacketGrow = (out: u8[], count: i32): void => {
382
+ for (let k: i32 = 0; k < count; k += 1) {
383
+ out.push(toU8(0))
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Appends `value` to `out` as a variable-length integer of exactly `size`
389
+ * bytes, as `quicVarintPut` writes it. Answers `false`, appending nothing,
390
+ * when `size` is not 1, 2, 4 or 8 or is too short for `value`.
391
+ */
392
+ export const quicVarintPushSized = (out: u8[], value: i64, size: i32): boolean => {
393
+ const needed: i32 = quicVarintSize(value)
394
+ if (needed === 0 || size < needed || (size !== 1 && size !== 2 && size !== 4 && size !== 8)) {
395
+ return false
396
+ }
397
+ const at: i32 = toI32(out.length)
398
+ quicPacketGrow(out, size)
399
+ return quicVarintPut(out, at, value, size) >= 0
400
+ }
401
+
402
+ /**
403
+ * Appends `value` to `out` as a variable-length integer in the fewest bytes
404
+ * (RFC 9000 §16). Answers `false`, appending nothing, for a value below zero
405
+ * or past 2^62 - 1.
406
+ */
407
+ export const quicVarintPush = (out: u8[], value: i64): boolean =>
408
+ quicVarintPushSized(out, value, quicVarintSize(value))
409
+
410
+ /**
411
+ * The length of the variable-length integer whose first byte is at
412
+ * `bytes[at]`, read from its two-bit prefix: 1, 2, 4 or 8. Answers 0 when
413
+ * `at` is outside `bytes`.
414
+ */
415
+ export const quicVarintLength = (bytes: u8[], at: i32): i32 => {
416
+ if (at < 0 || at >= toI32(bytes.length)) {
417
+ return 0
418
+ }
419
+ return 1 << toI32(bytes[at] >> 6)
420
+ }
421
+
422
+ /**
423
+ * The variable-length integer at `bytes[at]` (RFC 9000 §16, §A.1), read only
424
+ * from `bytes[at .. end)`. Answers -1, which no varint holds, when it does not
425
+ * fit in that window or the window is not inside `bytes`. Advance past it by
426
+ * `quicVarintLength(bytes, at)`.
427
+ */
428
+ export const quicVarintRead = (bytes: u8[], at: i32, end: i32): i64 => {
429
+ const size: i32 = quicVarintLength(bytes, at)
430
+ if (size === 0 || end > toI32(bytes.length) || at + size > end) {
431
+ return -1
432
+ }
433
+ let value: i64 = toI64(bytes[at] & 0x3f)
434
+ for (let k: i32 = 1; k < size; k += 1) {
435
+ value = (value << 8) | toI64(bytes[at + k])
436
+ }
437
+ return value
438
+ }
439
+
440
+ /**
441
+ * How many bytes to send packet number `fullPn` in (RFC 9000 §17.1, §A.2),
442
+ * when `largestAcked` is the largest the peer has acknowledged in its space,
443
+ * or -1 if none: enough that twice the unacknowledged range fits, 1 to 4.
444
+ * Answers 0 when no length will do — `fullPn` not above `largestAcked`, not a
445
+ * packet number, or more than 2^31 packets unacknowledged.
446
+ */
447
+ export const quicPacketNumberLength = (fullPn: i64, largestAcked: i64): i32 => {
448
+ if (fullPn < 0 || fullPn > QUIC_MAX_VARINT || largestAcked < -1 || fullPn <= largestAcked) {
449
+ return 0
450
+ }
451
+ // `fullPn - largestAcked` packets are in flight with the new one, and the
452
+ // receiver's window is centred on what it expects, so the encoding has to
453
+ // span twice that: `unacked <= 2^(8n - 1)` for n bytes.
454
+ const unacked: i64 = fullPn - largestAcked
455
+ for (let n: i32 = 1; n <= 4; n += 1) {
456
+ const half: i64 = toI64(1) << (toI64(n) * 8 - 1)
457
+ if (unacked <= half) {
458
+ return n
459
+ }
460
+ }
461
+ return 0
462
+ }
463
+
464
+ /**
465
+ * The full packet number from the `pnLength`-byte `truncatedPn` on the wire,
466
+ * given `largestPn`, the largest packet number this endpoint has processed in
467
+ * that space, or -1 for none (RFC 9000 §17.1, §A.3): the one closest to
468
+ * `largestPn + 1` whose low bytes are `truncatedPn`. Answers -1 when
469
+ * `pnLength` is not 1 to 4 or `truncatedPn` does not fit in it.
470
+ */
471
+ export const quicPacketNumberDecode = (largestPn: i64, truncatedPn: i64, pnLength: i32): i64 => {
472
+ if (pnLength < 1 || pnLength > 4 || largestPn < -1 || largestPn > QUIC_MAX_VARINT) {
473
+ return -1
474
+ }
475
+ const window: i64 = toI64(1) << (toI64(pnLength) * 8)
476
+ if (truncatedPn < 0 || truncatedPn >= window) {
477
+ return -1
478
+ }
479
+ const expected: i64 = largestPn + 1
480
+ const halfWindow: i64 = window >> 1
481
+ const candidate: i64 = (expected & ~(window - 1)) | truncatedPn
482
+ // The candidate shares `expected`'s high bits; one window up or down is
483
+ // nearer when it falls outside (expected - half, expected + half]. The
484
+ // bound checks keep the answer inside [0, 2^62). One case is left that
485
+ // RFC 9000 §A.3's pseudocode does not catch: with `largestPn` at 2^62 - 1,
486
+ // `expected` is 2^62 itself, and a candidate equal to it falls through
487
+ // both tests. Every packet number below 2^62 with these low bytes is a
488
+ // window down, and the nearest of them is the answer.
489
+ if (candidate <= expected - halfWindow && candidate < QUIC_MAX_VARINT + 1 - window) {
490
+ return candidate + window
491
+ }
492
+ if ((candidate > expected + halfWindow || candidate > QUIC_MAX_VARINT) && candidate >= window) {
493
+ return candidate - window
494
+ }
495
+ return candidate
496
+ }
497
+
498
+ /** Whether `cid` is a connection ID version 1 allows: 0 to 20 bytes. */
499
+ const quicPacketCidFits = (cid: u8[]): boolean => toI32(cid.length) <= QUIC_MAX_CID_LENGTH
500
+
501
+ /** Whether `length` bytes from the start of `cid` are a connection ID version 1 allows: 0 to 20 bytes, inside `cid`. */
502
+ const quicPacketCidWindowFits = (cid: u8[], length: i32): boolean =>
503
+ length >= 0 && length <= QUIC_MAX_CID_LENGTH && length <= toI32(cid.length)
504
+
505
+ /** Writes `bytes[from .. from + length)` into `buf` at `at`; the caller has checked both windows. */
506
+ export const quicPacketCopy = (buf: u8[], at: i32, bytes: u8[], from: i32, length: i32): void => {
507
+ for (let k: i32 = 0; k < length; k += 1) {
508
+ if (at + k >= 0 && at + k < toI32(buf.length) && from + k >= 0 && from + k < toI32(bytes.length)) {
509
+ buf[at + k] = bytes[from + k]
510
+ }
511
+ }
512
+ }
513
+
514
+ /** Writes one byte at `at` when it is inside `buf`. */
515
+ const quicPacketPutByte = (buf: u8[], at: i32, value: i32): void => {
516
+ if (at >= 0 && at < toI32(buf.length)) {
517
+ buf[at] = toU8(value)
518
+ }
519
+ }
520
+
521
+ /**
522
+ * Writes what every version 1 long header starts with (RFC 9000 §17.2) into
523
+ * `buf` at `at`: the first byte, the version, and each connection ID (the
524
+ * first `dcidLength` and `scidLength` bytes of `dcid` and `scid`) after its
525
+ * length. Answers the offset past it; the caller has checked that it fits.
526
+ */
527
+ const quicPacketPutLongPrefix = (
528
+ buf: u8[],
529
+ at: i32,
530
+ first: i32,
531
+ dcid: u8[],
532
+ dcidLength: i32,
533
+ scid: u8[],
534
+ scidLength: i32
535
+ ): i32 => {
536
+ const version: i32 = toI32(QUIC_VERSION_1)
537
+ quicPacketPutByte(buf, at, first)
538
+ quicPacketPutByte(buf, at + 1, (version >> 24) & 255)
539
+ quicPacketPutByte(buf, at + 2, (version >> 16) & 255)
540
+ quicPacketPutByte(buf, at + 3, (version >> 8) & 255)
541
+ quicPacketPutByte(buf, at + 4, version & 255)
542
+ quicPacketPutByte(buf, at + 5, dcidLength)
543
+ quicPacketCopy(buf, at + 6, dcid, 0, dcidLength)
544
+ const scidAt: i32 = at + 6 + dcidLength
545
+ quicPacketPutByte(buf, scidAt, scidLength)
546
+ quicPacketCopy(buf, scidAt + 1, scid, 0, scidLength)
547
+ return scidAt + 1 + scidLength
548
+ }
549
+
550
+ /** Writes the low `pnLength` bytes of `packetNumber` into `buf` at `at`, big-endian, and answers the offset past them. */
551
+ const quicPacketPutNumber = (buf: u8[], at: i32, packetNumber: i64, pnLength: i32): i32 => {
552
+ for (let k: i32 = 0; k < pnLength; k += 1) {
553
+ if (at + k >= 0 && at + k < toI32(buf.length)) {
554
+ buf[at + k] = toU8(toI32((packetNumber >> (toI64(pnLength - 1 - k) * 8)) & 255))
555
+ }
556
+ }
557
+ return at + pnLength
558
+ }
559
+
560
+ /**
561
+ * The size of the long header `quicPutLongHeader` writes for these
562
+ * arguments, or 0 when it would refuse them (see `quicLongHeader`).
563
+ */
564
+ export const quicLongHeaderSize = (
565
+ type: i32,
566
+ dcidLength: i32,
567
+ scidLength: i32,
568
+ tokenLength: i32,
569
+ packetNumber: i64,
570
+ pnLength: i32,
571
+ payloadLength: i32
572
+ ): i32 => {
573
+ if (
574
+ (type !== QUIC_PACKET_INITIAL && type !== QUIC_PACKET_ZERO_RTT && type !== QUIC_PACKET_HANDSHAKE) ||
575
+ (type !== QUIC_PACKET_INITIAL && tokenLength !== 0) ||
576
+ dcidLength < 0 ||
577
+ dcidLength > QUIC_MAX_CID_LENGTH ||
578
+ scidLength < 0 ||
579
+ scidLength > QUIC_MAX_CID_LENGTH ||
580
+ tokenLength < 0 ||
581
+ pnLength < 1 ||
582
+ pnLength > 4 ||
583
+ packetNumber < 0 ||
584
+ packetNumber > QUIC_MAX_VARINT ||
585
+ payloadLength < 0 ||
586
+ payloadLength > QUIC_MAX_LENGTH - pnLength - QUIC_AEAD_TAG_SIZE
587
+ ) {
588
+ return 0
589
+ }
590
+ const token: i32 = type === QUIC_PACKET_INITIAL ? quicVarintSize(toI64(tokenLength)) + tokenLength : 0
591
+ return 7 + dcidLength + scidLength + token + 2 + pnLength
592
+ }
593
+
594
+ /**
595
+ * Writes into `buf` at `at` the unprotected long header `quicLongHeader`
596
+ * answers, with the connection IDs the first `dcidLength` and `scidLength`
597
+ * bytes of `dcid` and `scid`, and answers the offset past it: where the
598
+ * payload goes. Answers -1, writing nothing, where `quicLongHeader` answers
599
+ * `null`, and for a header that does not fit in `buf` from `at`.
600
+ */
601
+ export const quicPutLongHeader = (
602
+ buf: u8[],
603
+ at: i32,
604
+ type: i32,
605
+ dcid: u8[],
606
+ dcidLength: i32,
607
+ scid: u8[],
608
+ scidLength: i32,
609
+ token: u8[],
610
+ packetNumber: i64,
611
+ pnLength: i32,
612
+ payloadLength: i32
613
+ ): i32 => {
614
+ const tokenLength: i32 = toI32(token.length)
615
+ const size: i32 = quicLongHeaderSize(
616
+ type,
617
+ dcidLength,
618
+ scidLength,
619
+ tokenLength,
620
+ packetNumber,
621
+ pnLength,
622
+ payloadLength
623
+ )
624
+ if (
625
+ size === 0 ||
626
+ !quicPacketCidWindowFits(dcid, dcidLength) ||
627
+ !quicPacketCidWindowFits(scid, scidLength) ||
628
+ at < 0 ||
629
+ at > toI32(buf.length) - size
630
+ ) {
631
+ return -1
632
+ }
633
+ let cursor: i32 = quicPacketPutLongPrefix(
634
+ buf,
635
+ at,
636
+ (type << 4) | (pnLength - 1) | 0xc0,
637
+ dcid,
638
+ dcidLength,
639
+ scid,
640
+ scidLength
641
+ )
642
+ if (type === QUIC_PACKET_INITIAL) {
643
+ cursor = quicVarintPut(buf, cursor, toI64(tokenLength), quicVarintSize(toI64(tokenLength)))
644
+ quicPacketCopy(buf, cursor, token, 0, tokenLength)
645
+ cursor = cursor + tokenLength
646
+ }
647
+ cursor = quicVarintPut(buf, cursor, toI64(pnLength + payloadLength + QUIC_AEAD_TAG_SIZE), 2)
648
+ return quicPacketPutNumber(buf, cursor, packetNumber, pnLength)
649
+ }
650
+
651
+ /**
652
+ * The unprotected long header of an Initial, 0-RTT or Handshake packet
653
+ * (RFC 9000 §17.2), ending with the packet number in `pnLength` bytes, for a
654
+ * payload of `payloadLength` bytes that `quicSealPacket` will protect. The
655
+ * Length field counts the packet number, the payload and the AEAD tag, and is
656
+ * always written in two bytes (RFC 9000 §16 allows the longer form), so the
657
+ * header's size does not move with the payload's and a caller padding a
658
+ * packet to a size knows the header first. `token` goes in an Initial and
659
+ * must be empty otherwise.
660
+ *
661
+ * Answers `null` for another `type`, a connection ID over 20 bytes, a token
662
+ * on a non-Initial packet, a `pnLength` outside 1 to 4, a packet number that
663
+ * is not one, a negative `payloadLength`, or a Length past 16383, the most two
664
+ * bytes hold and far more than a datagram carries.
665
+ */
666
+ export const quicLongHeader = (
667
+ type: i32,
668
+ dcid: u8[],
669
+ scid: u8[],
670
+ token: u8[],
671
+ packetNumber: i64,
672
+ pnLength: i32,
673
+ payloadLength: i32
674
+ ): u8[] | null => {
675
+ const dcidLength: i32 = toI32(dcid.length)
676
+ const scidLength: i32 = toI32(scid.length)
677
+ const size: i32 = quicLongHeaderSize(
678
+ type,
679
+ dcidLength,
680
+ scidLength,
681
+ toI32(token.length),
682
+ packetNumber,
683
+ pnLength,
684
+ payloadLength
685
+ )
686
+ if (size === 0) {
687
+ return null
688
+ }
689
+ const out: u8[] = new Array<u8>(size)
690
+ if (
691
+ quicPutLongHeader(
692
+ out,
693
+ 0,
694
+ type,
695
+ dcid,
696
+ dcidLength,
697
+ scid,
698
+ scidLength,
699
+ token,
700
+ packetNumber,
701
+ pnLength,
702
+ payloadLength
703
+ ) < 0
704
+ ) {
705
+ return null
706
+ }
707
+ return out
708
+ }
709
+
710
+ /**
711
+ * Writes into `buf` at `at` the unprotected short header `quicShortHeader`
712
+ * answers, with the Destination Connection ID the first `dcidLength` bytes of
713
+ * `dcid`, and answers the offset past it. Answers -1, writing nothing, where
714
+ * `quicShortHeader` answers `null`, and for a header that does not fit.
715
+ */
716
+ export const quicPutShortHeader = (
717
+ buf: u8[],
718
+ at: i32,
719
+ dcid: u8[],
720
+ dcidLength: i32,
721
+ spin: boolean,
722
+ keyPhase: boolean,
723
+ packetNumber: i64,
724
+ pnLength: i32
725
+ ): i32 => {
726
+ const size: i32 = 1 + dcidLength + pnLength
727
+ if (
728
+ !quicPacketCidWindowFits(dcid, dcidLength) ||
729
+ pnLength < 1 ||
730
+ pnLength > 4 ||
731
+ packetNumber < 0 ||
732
+ packetNumber > QUIC_MAX_VARINT ||
733
+ at < 0 ||
734
+ at > toI32(buf.length) - size
735
+ ) {
736
+ return -1
737
+ }
738
+ const spinBit: i32 = spin ? 0x20 : 0
739
+ const keyPhaseBit: i32 = keyPhase ? 0x04 : 0
740
+ buf[at] = toU8((pnLength - 1) | spinBit | keyPhaseBit | 0x40)
741
+ quicPacketCopy(buf, at + 1, dcid, 0, dcidLength)
742
+ return quicPacketPutNumber(buf, at + 1 + dcidLength, packetNumber, pnLength)
743
+ }
744
+
745
+ /**
746
+ * The unprotected short header of a 1-RTT packet (RFC 9000 §17.3.1): the
747
+ * first byte with the spin and key-phase bits, the Destination Connection ID
748
+ * and the packet number in `pnLength` bytes. Answers `null` for a connection
749
+ * ID over 20 bytes, a `pnLength` outside 1 to 4 or a packet number that is
750
+ * not one.
751
+ */
752
+ export const quicShortHeader = (
753
+ dcid: u8[],
754
+ spin: boolean,
755
+ keyPhase: boolean,
756
+ packetNumber: i64,
757
+ pnLength: i32
758
+ ): u8[] | null => {
759
+ const dcidLength: i32 = toI32(dcid.length)
760
+ if (!quicPacketCidFits(dcid) || pnLength < 1 || pnLength > 4) {
761
+ return null
762
+ }
763
+ const out: u8[] = new Array<u8>(1 + dcidLength + pnLength)
764
+ if (quicPutShortHeader(out, 0, dcid, dcidLength, spin, keyPhase, packetNumber, pnLength) < 0) {
765
+ return null
766
+ }
767
+ return out
768
+ }
769
+
770
+ /** A header that stopped at `error`, with what it had read so far; answers the error. */
771
+ const quicPacketRefuse = (header: QuicHeader, error: i32): i32 => {
772
+ header.error = error
773
+ return error
774
+ }
775
+
776
+ /**
777
+ * Reads a one-byte connection-ID length at `at` inside `[at, end)`. Answers
778
+ * the length, or -1 when the length byte or the ID after it does not fit.
779
+ */
780
+ const quicPacketCidLength = (bytes: u8[], at: i32, end: i32): i32 => {
781
+ if (at < 0 || at >= end || at >= toI32(bytes.length)) {
782
+ return -1
783
+ }
784
+ const length: i32 = toI32(bytes[at])
785
+ return at + 1 + length > end ? -1 : length
786
+ }
787
+
788
+ /**
789
+ * Parses the header of the packet starting at `datagram[at]` into `header`,
790
+ * filling only its numbers and the windows of its connection IDs and token
791
+ * (`dcidStart`, `dcidLength` and the rest), so nothing is copied or
792
+ * allocated: the form a connection reads every packet with. Answers
793
+ * `header.error`. Every field is reset first, so one `QuicHeader` serves
794
+ * every packet; its `dcid`, `scid`, `token` and `retryTag` arrays are left
795
+ * as they were. The datagram is `datagram[at .. limit)`, so one buffer can
796
+ * hold several (a GRO receive); a `limit` past the array reads as its length.
797
+ * See `quicParseHeader` for what is read and refused.
798
+ */
799
+ export const quicParseHeaderInto = (
800
+ header: QuicHeader,
801
+ datagram: u8[],
802
+ at: i32,
803
+ limit: i32,
804
+ shortDcidLength: i32
805
+ ): i32 => {
806
+ const end: i32 = limit > toI32(datagram.length) ? toI32(datagram.length) : limit
807
+ header.error = QUIC_PACKET_OK
808
+ header.type = 0
809
+ header.version = 0
810
+ header.start = at
811
+ header.end = end
812
+ header.pnOffset = 0
813
+ header.dcidStart = 0
814
+ header.dcidLength = 0
815
+ header.scidStart = 0
816
+ header.scidLength = 0
817
+ header.tokenStart = 0
818
+ header.tokenLength = 0
819
+ if (at < 0 || at >= end) {
820
+ return quicPacketRefuse(header, QUIC_ERR_TRUNCATED)
821
+ }
822
+ const first: i32 = toI32(datagram[at])
823
+ if ((first & 0x80) === 0) {
824
+ header.type = QUIC_PACKET_SHORT
825
+ if ((first & 0x40) === 0) {
826
+ return quicPacketRefuse(header, QUIC_ERR_FIXED_BIT)
827
+ }
828
+ if (shortDcidLength < 0 || shortDcidLength > QUIC_MAX_CID_LENGTH || at + 1 + shortDcidLength > end) {
829
+ return quicPacketRefuse(header, QUIC_ERR_TRUNCATED)
830
+ }
831
+ header.dcidStart = at + 1
832
+ header.dcidLength = shortDcidLength
833
+ header.pnOffset = at + 1 + shortDcidLength
834
+ return QUIC_PACKET_OK
835
+ }
836
+ if (at + 5 > end) {
837
+ return quicPacketRefuse(header, QUIC_ERR_TRUNCATED)
838
+ }
839
+ let version: i64 = 0
840
+ for (let k: i32 = at + 1; k < at + 5; k += 1) {
841
+ if (k < toI32(datagram.length)) {
842
+ version = (version << 8) | toI64(datagram[k])
843
+ }
844
+ }
845
+ header.version = version
846
+ const dcidLength: i32 = quicPacketCidLength(datagram, at + 5, end)
847
+ if (dcidLength < 0) {
848
+ return quicPacketRefuse(header, QUIC_ERR_TRUNCATED)
849
+ }
850
+ header.dcidStart = at + 6
851
+ header.dcidLength = dcidLength
852
+ const afterDcid: i32 = at + 6 + dcidLength
853
+ const scidLength: i32 = quicPacketCidLength(datagram, afterDcid, end)
854
+ if (scidLength < 0) {
855
+ return quicPacketRefuse(header, QUIC_ERR_TRUNCATED)
856
+ }
857
+ header.scidStart = afterDcid + 1
858
+ header.scidLength = scidLength
859
+ let cursor: i32 = afterDcid + 1 + scidLength
860
+ if (version !== QUIC_VERSION_1) {
861
+ return quicPacketRefuse(header, QUIC_ERR_VERSION)
862
+ }
863
+ if ((first & 0x40) === 0) {
864
+ return quicPacketRefuse(header, QUIC_ERR_FIXED_BIT)
865
+ }
866
+ if (dcidLength > QUIC_MAX_CID_LENGTH || scidLength > QUIC_MAX_CID_LENGTH) {
867
+ return quicPacketRefuse(header, QUIC_ERR_CID_LENGTH)
868
+ }
869
+ const type: i32 = (first >> 4) & 3
870
+ header.type = type
871
+ if (type === QUIC_PACKET_RETRY) {
872
+ // A Retry has no Length and no packet number: the token runs to the
873
+ // integrity tag, and the tag to the end of the datagram (§17.2.5).
874
+ if (cursor + QUIC_RETRY_TAG_SIZE > end) {
875
+ return quicPacketRefuse(header, QUIC_ERR_TRUNCATED)
876
+ }
877
+ header.tokenStart = cursor
878
+ header.tokenLength = end - QUIC_RETRY_TAG_SIZE - cursor
879
+ header.pnOffset = end - QUIC_RETRY_TAG_SIZE
880
+ return QUIC_PACKET_OK
881
+ }
882
+ if (type === QUIC_PACKET_INITIAL) {
883
+ const tokenLength: i64 = quicVarintRead(datagram, cursor, end)
884
+ if (tokenLength < 0) {
885
+ return quicPacketRefuse(header, QUIC_ERR_TRUNCATED)
886
+ }
887
+ cursor += quicVarintLength(datagram, cursor)
888
+ if (tokenLength > toI64(end - cursor)) {
889
+ return quicPacketRefuse(header, QUIC_ERR_TRUNCATED)
890
+ }
891
+ header.tokenStart = cursor
892
+ header.tokenLength = toI32(tokenLength)
893
+ cursor = cursor + toI32(tokenLength)
894
+ }
895
+ const length: i64 = quicVarintRead(datagram, cursor, end)
896
+ if (length < 0) {
897
+ return quicPacketRefuse(header, QUIC_ERR_TRUNCATED)
898
+ }
899
+ cursor += quicVarintLength(datagram, cursor)
900
+ if (length > toI64(end - cursor)) {
901
+ return quicPacketRefuse(header, QUIC_ERR_LENGTH)
902
+ }
903
+ header.pnOffset = cursor
904
+ header.end = cursor + toI32(length)
905
+ return QUIC_PACKET_OK
906
+ }
907
+
908
+ /**
909
+ * Parses the header of the packet starting at `datagram[at]`, which is how a
910
+ * datagram of coalesced packets is walked: parse at 0, then at the answer's
911
+ * `end`, until `end` is the datagram's length (RFC 9000 §12.2).
912
+ * `shortDcidLength` is the length of the connection IDs this endpoint hands
913
+ * out, which is the only way to know where a short header's DCID ends
914
+ * (RFC 9000 §17.3.1); one outside 0 to 20 reads as `QUIC_ERR_TRUNCATED`.
915
+ * The connection IDs, the token and a Retry's tag are copied out into the
916
+ * answer's arrays; `quicParseHeaderInto` reads the same without a copy.
917
+ *
918
+ * The answer's `error` says what stopped it; nothing a peer sends panics.
919
+ */
920
+ export const quicParseHeader = (datagram: u8[], at: i32, shortDcidLength: i32): QuicHeader => {
921
+ const header: QuicHeader = new QuicHeader()
922
+ quicParseHeaderInto(header, datagram, at, toI32(datagram.length), shortDcidLength)
923
+ header.dcid = quicPacketSlice(datagram, header.dcidStart, header.dcidStart + header.dcidLength)
924
+ header.scid = quicPacketSlice(datagram, header.scidStart, header.scidStart + header.scidLength)
925
+ header.token = quicPacketSlice(datagram, header.tokenStart, header.tokenStart + header.tokenLength)
926
+ if (header.type === QUIC_PACKET_RETRY && header.error === QUIC_PACKET_OK) {
927
+ header.retryTag = quicPacketSlice(datagram, header.end - QUIC_RETRY_TAG_SIZE, header.end)
928
+ }
929
+ return header
930
+ }
931
+
932
+ /**
933
+ * The secrets of RFC 9001 §5.2 for the client's first Destination Connection
934
+ * ID, `dcid`: `initial_secret` is HKDF-Extract under version 1's salt, and the
935
+ * client's and the server's are expanded from it with the labels `client in`
936
+ * and `server in`. Answers `null` for a `dcid` over 20 bytes.
937
+ */
938
+ export const quicInitialSecrets = (dcid: u8[]): QuicInitialSecrets | null => {
939
+ if (!quicPacketCidFits(dcid)) {
940
+ return null
941
+ }
942
+ const initial: u8[] = hkdfExtractSha256(quicPacketInitialSalt(), dcid)
943
+ return new QuicInitialSecrets(
944
+ quicPacketExpandLabel(QUIC_AEAD_AES_128_GCM, initial, "client in", 32),
945
+ quicPacketExpandLabel(QUIC_AEAD_AES_128_GCM, initial, "server in", 32)
946
+ )
947
+ }
948
+
949
+ /** The key length of `aead` in bytes, or 0 for no AEAD this module knows. */
950
+ const quicPacketKeyLength = (aead: i32): i32 => {
951
+ if (aead === QUIC_AEAD_AES_128_GCM) {
952
+ return 16
953
+ }
954
+ if (aead === QUIC_AEAD_AES_256_GCM || aead === QUIC_AEAD_CHACHA20_POLY1305) {
955
+ return 32
956
+ }
957
+ return 0
958
+ }
959
+
960
+ /** The length of `aead`'s hash, which is its traffic secret's length: 48 for SHA-384, else 32. */
961
+ const quicPacketSecretLength = (aead: i32): i32 => (aead === QUIC_AEAD_AES_256_GCM ? 48 : 32)
962
+
963
+ /** Whether `secret` is a traffic secret for `aead`: an AEAD this module knows, and its hash's length. */
964
+ const quicPacketSecretFits = (aead: i32, secret: u8[]): boolean =>
965
+ quicPacketKeyLength(aead) !== 0 && toI32(secret.length) === quicPacketSecretLength(aead)
966
+
967
+ /**
968
+ * Whether `keys` has the shape `quicKeys` gives: a known AEAD, key and
969
+ * header-protection key of its length, a 12-byte IV, and for the AES suites
970
+ * both keys expanded. The constructor is public only because the language
971
+ * has no private one, so keys made by hand are checked here, once, rather
972
+ * than failing deep inside an AEAD as though a peer had forged the packet.
973
+ */
974
+ const quicPacketKeysUsable = (keys: QuicKeys): boolean => {
975
+ const keyLength: i32 = quicPacketKeyLength(keys.aead)
976
+ if (
977
+ keyLength === 0 ||
978
+ toI32(keys.key.length) !== keyLength ||
979
+ toI32(keys.hp.length) !== keyLength ||
980
+ toI32(keys.iv.length) !== QUIC_IV_SIZE
981
+ ) {
982
+ return false
983
+ }
984
+ return keys.aead === QUIC_AEAD_CHACHA20_POLY1305 || (keys.packetAes !== null && keys.hpAes !== null)
985
+ }
986
+
987
+ /**
988
+ * Keys for `aead` with the packet key and IV derived from `secret` (RFC 9001
989
+ * §5.1) and the header-protection key given, which a key update keeps
990
+ * (§6). The caller has checked `secret` with `quicPacketSecretFits`.
991
+ */
992
+ const quicPacketDerive = (aead: i32, secret: u8[], hp: u8[], hpAes: AesKey | null): QuicKeys => {
993
+ const keys: QuicKeys = new QuicKeys(
994
+ aead,
995
+ quicPacketExpandLabel(aead, secret, "quic key", quicPacketKeyLength(aead)),
996
+ quicPacketExpandLabel(aead, secret, "quic iv", QUIC_IV_SIZE),
997
+ hp
998
+ )
999
+ keys.hpAes = hpAes
1000
+ if (aead !== QUIC_AEAD_CHACHA20_POLY1305) {
1001
+ keys.packetAes = aesKey(keys.key)
1002
+ }
1003
+ return keys
1004
+ }
1005
+
1006
+ /**
1007
+ * The packet-protection keys of RFC 9001 §5.1 from a traffic `secret`: the
1008
+ * AEAD key (`quic key`), the IV (`quic iv`) and the header-protection key
1009
+ * (`quic hp`), each HKDF-Expand-Label with an empty context under the hash
1010
+ * `aead` pairs with. Answers `null` for an `aead` this module does not know,
1011
+ * or a secret that is not that hash's length.
1012
+ */
1013
+ export const quicKeys = (aead: i32, secret: u8[]): QuicKeys | null => {
1014
+ if (!quicPacketSecretFits(aead, secret)) {
1015
+ return null
1016
+ }
1017
+ const hp: u8[] = quicPacketExpandLabel(aead, secret, "quic hp", quicPacketKeyLength(aead))
1018
+ return quicPacketDerive(aead, secret, hp, aead === QUIC_AEAD_CHACHA20_POLY1305 ? null : aesKey(hp))
1019
+ }
1020
+
1021
+ /**
1022
+ * The next generation's traffic secret for a key update (RFC 9001 §6.1):
1023
+ * HKDF-Expand-Label(`secret`, `quic ku`, "", Hash.length). The
1024
+ * header-protection key does not change with it (§6), so the new keys are
1025
+ * `quicKeysUpdate` of the old keys and this. Answers `null` where `quicKeys`
1026
+ * would.
1027
+ */
1028
+ export const quicKeyUpdateSecret = (aead: i32, secret: u8[]): u8[] | null => {
1029
+ if (!quicPacketSecretFits(aead, secret)) {
1030
+ return null
1031
+ }
1032
+ return quicPacketExpandLabel(aead, secret, "quic ku", quicPacketSecretLength(aead))
1033
+ }
1034
+
1035
+ /**
1036
+ * The next generation's keys in a key update (RFC 9001 §6): the packet key
1037
+ * and IV derived from `nextSecret`, which `quicKeyUpdateSecret` made from the
1038
+ * current secret, and `keys`' header-protection key kept, expanded as it was,
1039
+ * since header protection never changes with the phase. Answers `null` for
1040
+ * keys `quicKeys` did not make or a secret of the wrong length.
1041
+ */
1042
+ export const quicKeysUpdate = (keys: QuicKeys, nextSecret: u8[]): QuicKeys | null => {
1043
+ if (!quicPacketKeysUsable(keys) || !quicPacketSecretFits(keys.aead, nextSecret)) {
1044
+ return null
1045
+ }
1046
+ return quicPacketDerive(keys.aead, nextSecret, keys.hp, keys.hpAes)
1047
+ }
1048
+
1049
+ /** The bits of the first byte header protection covers: four in a long header, five in a short one (RFC 9001 §5.4.1). */
1050
+ const quicPacketProtectedBits = (first: i32): i32 => ((first & 0x80) !== 0 ? 0x0f : 0x1f)
1051
+
1052
+ /** The first byte's reserved bits: 0x0c in a long header, 0x18 in a short one (RFC 9000 §17.2, §17.3.1). */
1053
+ const quicPacketReservedBits = (first: i32): i32 => ((first & 0x80) !== 0 ? 0x0c : 0x18)
1054
+
1055
+ /** The AEAD nonce for `packetNumber` (RFC 9001 §5.3): the IV with the number XORed into its low 8 bytes, big-endian. */
1056
+ const quicPacketNonce = (keys: QuicKeys, packetNumber: i64): u8[] => {
1057
+ const nonce: u8[] = new Array<u8>(QUIC_IV_SIZE)
1058
+ const nonceLength: i32 = toI32(nonce.length)
1059
+ for (let k: i32 = 0; k < nonceLength; k += 1) {
1060
+ // Byte `k` of the nonce takes byte `k - 4` of the number's eight.
1061
+ const pn: i64 = k < 4 ? 0 : (packetNumber >> (toI64(11 - k) * 8)) & 255
1062
+ const iv: i32 = k < toI32(keys.iv.length) ? toI32(keys.iv[k]) : 0
1063
+ nonce[k] = toU8(iv ^ toI32(pn))
1064
+ }
1065
+ return nonce
1066
+ }
1067
+
1068
+ /** The five-byte header-protection mask for `sample` under `keys` (RFC 9001 §5.4.3, §5.4.4), or `null`. */
1069
+ const quicPacketMask = (keys: QuicKeys, sample: u8[]): u8[] | null => {
1070
+ if (keys.aead === QUIC_AEAD_CHACHA20_POLY1305) {
1071
+ return chacha20HeaderMask(keys.hp, sample)
1072
+ }
1073
+ const hpAes: AesKey | null = keys.hpAes
1074
+ if (hpAes !== null) {
1075
+ return aesHeaderMask(hpAes, sample)
1076
+ }
1077
+ return null
1078
+ }
1079
+
1080
+ /** `keys`' AEAD over `plaintext` with `aad`: the ciphertext and its tag, or `null`. */
1081
+ const quicPacketSeal = (keys: QuicKeys, nonce: u8[], aad: u8[], plaintext: u8[]): u8[] | null => {
1082
+ if (keys.aead === QUIC_AEAD_CHACHA20_POLY1305) {
1083
+ return chacha20Poly1305Seal(keys.key, nonce, aad, plaintext)
1084
+ }
1085
+ const packetAes: AesKey | null = keys.packetAes
1086
+ if (packetAes !== null) {
1087
+ return aesGcmSeal(packetAes, nonce, aad, plaintext)
1088
+ }
1089
+ return null
1090
+ }
1091
+
1092
+ /** `keys`' AEAD opening `sealed` with `aad`: the plaintext, or `null` when it does not authenticate. */
1093
+ const quicPacketOpen = (keys: QuicKeys, nonce: u8[], aad: u8[], sealed: u8[]): u8[] | null => {
1094
+ if (keys.aead === QUIC_AEAD_CHACHA20_POLY1305) {
1095
+ return chacha20Poly1305Open(keys.key, nonce, aad, sealed)
1096
+ }
1097
+ const packetAes: AesKey | null = keys.packetAes
1098
+ if (packetAes !== null) {
1099
+ return aesGcmOpen(packetAes, nonce, aad, sealed)
1100
+ }
1101
+ return null
1102
+ }
1103
+
1104
+ /**
1105
+ * Protects one packet in place (RFC 9001 §5.3, §5.4.1). `buf[start ..
1106
+ * start + headerLength)` is a header `quicPutLongHeader` or
1107
+ * `quicPutShortHeader` wrote, ending with the packet number, and the
1108
+ * `payloadLength` bytes after it are the frames; `packetNumber` is the full
1109
+ * number the header's last bytes truncate. The payload is sealed where it
1110
+ * lies, its tag written after it, and header protection put on: the answer
1111
+ * is the offset past the tag, which is where the packet ends, or -1, with
1112
+ * `buf` not to be sent, for anything `quicSealPacket` refuses and for a tag
1113
+ * that does not fit in `buf`. The AEAD's temporaries are released before it
1114
+ * answers, so sealing allocates nothing that outlives the call.
1115
+ */
1116
+ export const quicSealInPlace = (
1117
+ keys: QuicKeys,
1118
+ buf: u8[],
1119
+ start: i32,
1120
+ headerLength: i32,
1121
+ payloadLength: i32,
1122
+ packetNumber: i64
1123
+ ): i32 => {
1124
+ const end: i32 = start + headerLength + payloadLength + QUIC_AEAD_TAG_SIZE
1125
+ if (start < 0 || headerLength < 2 || payloadLength < 0 || end > toI32(buf.length) || end < start) {
1126
+ return -1
1127
+ }
1128
+ const first: i32 = toI32(buf[start])
1129
+ const pnLength: i32 = (first & 3) + 1
1130
+ const pnOffset: i32 = start + headerLength - pnLength
1131
+ if (
1132
+ !quicPacketKeysUsable(keys) ||
1133
+ pnOffset < start + 1 ||
1134
+ pnLength + payloadLength < 4 ||
1135
+ packetNumber < 0 ||
1136
+ packetNumber > QUIC_MAX_VARINT
1137
+ ) {
1138
+ return -1
1139
+ }
1140
+ for (let k: i32 = 0; k < pnLength; k += 1) {
1141
+ const want: i32 = toI32((packetNumber >> (toI64(pnLength - 1 - k) * 8)) & 255)
1142
+ if (pnOffset + k < toI32(buf.length) && toI32(buf[pnOffset + k]) !== want) {
1143
+ return -1
1144
+ }
1145
+ }
1146
+ if (
1147
+ (first & 0x80) !== 0 &&
1148
+ quicVarintRead(buf, pnOffset - 2, pnOffset) !== toI64(pnLength + payloadLength + QUIC_AEAD_TAG_SIZE)
1149
+ ) {
1150
+ return -1
1151
+ }
1152
+ const payloadAt: i32 = start + headerLength
1153
+ {
1154
+ using _scope = arena()
1155
+ const sealed: u8[] | null = quicPacketSeal(
1156
+ keys,
1157
+ quicPacketNonce(keys, packetNumber),
1158
+ quicPacketSlice(buf, start, payloadAt),
1159
+ quicPacketSlice(buf, payloadAt, payloadAt + payloadLength)
1160
+ )
1161
+ if (sealed === null || payloadAt + toI32(sealed.length) > toI32(buf.length)) {
1162
+ return -1
1163
+ }
1164
+ buf.set(sealed, payloadAt)
1165
+ const sampleAt: i32 = pnOffset + 4
1166
+ const mask: u8[] | null = quicPacketMask(
1167
+ keys,
1168
+ quicPacketSlice(buf, sampleAt, sampleAt + QUIC_SAMPLE_SIZE)
1169
+ )
1170
+ if (mask === null || toI32(mask.length) < 5) {
1171
+ return -1
1172
+ }
1173
+ buf[start] = toU8(first ^ (toI32(mask[0]) & quicPacketProtectedBits(first)))
1174
+ for (let k: i32 = 0; k < pnLength && k + 1 < toI32(mask.length); k += 1) {
1175
+ if (pnOffset + k >= 0 && pnOffset + k < toI32(buf.length)) {
1176
+ buf[pnOffset + k] = buf[pnOffset + k] ^ mask[k + 1]
1177
+ }
1178
+ }
1179
+ }
1180
+ return end
1181
+ }
1182
+
1183
+ /**
1184
+ * Protects one packet (RFC 9001 §5.3, §5.4.1): `header` is what
1185
+ * `quicLongHeader` or `quicShortHeader` built, ending with the packet number,
1186
+ * and `packetNumber` is the full number its last bytes truncate. The answer
1187
+ * is the header under header protection followed by the AEAD-sealed payload
1188
+ * and its tag, ready to send or to coalesce; `quicSealInPlace` does the same
1189
+ * in a caller's buffer.
1190
+ *
1191
+ * The header and the arguments are held to each other, because a packet that
1192
+ * disagrees with its own header is one the peer silently drops: the header's
1193
+ * packet-number bytes must be `packetNumber`'s low bytes, and a long header's
1194
+ * two-byte Length must count this `payload`. The sample is the 16 bytes from
1195
+ * 4 past the packet number's start, so the packet number and payload together
1196
+ * must be at least 4 bytes; a shorter payload has to be padded first (RFC 9001
1197
+ * §5.4.2). Answers `null` for any of those, for a header too short for its
1198
+ * packet-number length, and for keys `quicKeys` did not make.
1199
+ */
1200
+ export const quicSealPacket = (
1201
+ keys: QuicKeys,
1202
+ header: u8[],
1203
+ packetNumber: i64,
1204
+ payload: u8[]
1205
+ ): u8[] | null => {
1206
+ const headerLength: i32 = toI32(header.length)
1207
+ const payloadLength: i32 = toI32(payload.length)
1208
+ const packet: u8[] = new Array<u8>(headerLength + payloadLength + QUIC_AEAD_TAG_SIZE)
1209
+ packet.set(header)
1210
+ packet.set(payload, headerLength)
1211
+ if (quicSealInPlace(keys, packet, 0, headerLength, payloadLength, packetNumber) < 0) {
1212
+ return null
1213
+ }
1214
+ return packet
1215
+ }
1216
+
1217
+ /**
1218
+ * Removes header protection from the packet `header` describes (RFC 9001
1219
+ * §5.4.1) without changing `datagram`: `packet`'s `error`, `firstByte`,
1220
+ * `pnLength`, `packetNumber` and `keyPhase` are set, and the answer is the
1221
+ * header in the clear, packet number included, which is the AEAD's
1222
+ * associated data: empty when `error` is not `QUIC_PACKET_OK`. Only numbers
1223
+ * are stored into `packet`, so a connection reuses one for every packet and
1224
+ * the answer is its own to let go. See `quicRemoveHeaderProtection`.
1225
+ */
1226
+ export const quicUnprotectHeader = (
1227
+ keys: QuicKeys,
1228
+ datagram: u8[],
1229
+ header: QuicHeader,
1230
+ largestPn: i64,
1231
+ packet: QuicPacket
1232
+ ): u8[] => {
1233
+ packet.error = QUIC_PACKET_OK
1234
+ packet.firstByte = 0
1235
+ packet.pnLength = 0
1236
+ packet.packetNumber = 0
1237
+ packet.keyPhase = false
1238
+ const datagramLength: i32 = toI32(datagram.length)
1239
+ if (header.error !== QUIC_PACKET_OK) {
1240
+ packet.error = header.error
1241
+ return []
1242
+ }
1243
+ if (header.type === QUIC_PACKET_RETRY) {
1244
+ packet.error = QUIC_ERR_NOT_PROTECTED
1245
+ return []
1246
+ }
1247
+ if (!quicPacketKeysUsable(keys)) {
1248
+ packet.error = QUIC_ERR_KEYS
1249
+ return []
1250
+ }
1251
+ const pnOffset: i32 = header.pnOffset
1252
+ const sampleAt: i32 = pnOffset + 4
1253
+ if (
1254
+ header.start < 0 ||
1255
+ header.start >= pnOffset ||
1256
+ header.end > datagramLength ||
1257
+ sampleAt + QUIC_SAMPLE_SIZE > header.end
1258
+ ) {
1259
+ packet.error = QUIC_ERR_SAMPLE
1260
+ return []
1261
+ }
1262
+ const mask: u8[] | null = quicPacketMask(
1263
+ keys,
1264
+ quicPacketSlice(datagram, sampleAt, sampleAt + QUIC_SAMPLE_SIZE)
1265
+ )
1266
+ if (mask === null || toI32(mask.length) < 5) {
1267
+ packet.error = QUIC_ERR_KEYS
1268
+ return []
1269
+ }
1270
+ const protectedFirst: i32 = toI32(datagram[header.start])
1271
+ const long: boolean = (protectedFirst & 0x80) !== 0
1272
+ const first: i32 = protectedFirst ^ (toI32(mask[0]) & quicPacketProtectedBits(protectedFirst))
1273
+ const pnLength: i32 = (first & 3) + 1
1274
+ // The sample check above put 20 bytes after `pnOffset` inside the packet,
1275
+ // so the packet number's at most 4 are there too.
1276
+ const clear: u8[] = quicPacketSlice(datagram, header.start, pnOffset + pnLength)
1277
+ clear[0] = toU8(first)
1278
+ let truncated: i64 = 0
1279
+ for (let k: i32 = 0; k < pnLength; k += 1) {
1280
+ const at: i32 = pnOffset - header.start + k
1281
+ if (at >= 0 && at < toI32(clear.length) && k + 1 < toI32(mask.length)) {
1282
+ const b: u8 = clear[at] ^ mask[k + 1]
1283
+ clear[at] = b
1284
+ truncated = (truncated << 8) | toI64(b)
1285
+ }
1286
+ }
1287
+ packet.firstByte = first
1288
+ packet.pnLength = pnLength
1289
+ packet.packetNumber = quicPacketNumberDecode(largestPn, truncated, pnLength)
1290
+ packet.keyPhase = !long && (first & 0x04) !== 0
1291
+ return clear
1292
+ }
1293
+
1294
+ /**
1295
+ * Removes header protection from the packet `header` describes (RFC 9001
1296
+ * §5.4.1): the answer's `firstByte`, `pnLength`, `packetNumber`, `keyPhase`
1297
+ * and `header` are filled in, the packet number recovered against
1298
+ * `largestPn`, the largest this endpoint has processed in the packet's space
1299
+ * (or -1). The header-protection key never changes with a key update, so a
1300
+ * caller holding two generations of keys calls this, reads `keyPhase`, and
1301
+ * hands the answer to `quicDecryptPacket` with the generation it names
1302
+ * (RFC 9001 §6.3).
1303
+ *
1304
+ * `error` is the header's own for a header that did not parse,
1305
+ * `QUIC_ERR_NOT_PROTECTED` for a Retry, `QUIC_ERR_KEYS` for keys `quicKeys`
1306
+ * did not make, and `QUIC_ERR_SAMPLE` for a packet too short to sample.
1307
+ */
1308
+ export const quicRemoveHeaderProtection = (
1309
+ keys: QuicKeys,
1310
+ datagram: u8[],
1311
+ header: QuicHeader,
1312
+ largestPn: i64
1313
+ ): QuicPacket => {
1314
+ const packet: QuicPacket = new QuicPacket()
1315
+ packet.header = quicUnprotectHeader(keys, datagram, header, largestPn, packet)
1316
+ return packet
1317
+ }
1318
+
1319
+ /**
1320
+ * Decrypts the payload of the packet `header` describes, whose header in the
1321
+ * clear `quicUnprotectHeader` answered as `clear` (RFC 9001 §5.3), without
1322
+ * changing `datagram`. Sets `packet.error` as `quicDecryptPacket` does and
1323
+ * answers the plaintext frames, or `null` when they did not authenticate
1324
+ * (or `packet` was already in error). Only numbers are stored into `packet`.
1325
+ */
1326
+ export const quicDecryptPayload = (
1327
+ keys: QuicKeys,
1328
+ datagram: u8[],
1329
+ header: QuicHeader,
1330
+ clear: u8[],
1331
+ packet: QuicPacket
1332
+ ): u8[] | null => {
1333
+ if (packet.error !== QUIC_PACKET_OK) {
1334
+ return null
1335
+ }
1336
+ if (!quicPacketKeysUsable(keys)) {
1337
+ packet.error = QUIC_ERR_KEYS
1338
+ return null
1339
+ }
1340
+ const payloadStart: i32 = header.start + toI32(clear.length)
1341
+ const plain: u8[] | null = quicPacketOpen(
1342
+ keys,
1343
+ quicPacketNonce(keys, packet.packetNumber),
1344
+ clear,
1345
+ quicPacketSlice(datagram, payloadStart, header.end)
1346
+ )
1347
+ if (plain === null) {
1348
+ packet.error = QUIC_ERR_DECRYPT
1349
+ return null
1350
+ }
1351
+ if ((packet.firstByte & quicPacketReservedBits(packet.firstByte)) !== 0) {
1352
+ packet.error = QUIC_ERR_RESERVED_BITS
1353
+ }
1354
+ return plain
1355
+ }
1356
+
1357
+ /**
1358
+ * Decrypts the payload of a packet whose header protection
1359
+ * `quicRemoveHeaderProtection` took off (RFC 9001 §5.3), filling in
1360
+ * `packet.payload` and setting `packet.error`: `QUIC_ERR_KEYS` for keys
1361
+ * `quicKeys` did not make, `QUIC_ERR_DECRYPT` when it does not authenticate
1362
+ * under `keys`, and `QUIC_ERR_RESERVED_BITS` when it
1363
+ * does but the header's reserved bits are not zero (RFC 9000 §17.2, §17.3.1),
1364
+ * which the caller is to close the connection over. Answers whether the
1365
+ * payload authenticated. A packet already in error is left as it is.
1366
+ */
1367
+ export const quicDecryptPacket = (
1368
+ keys: QuicKeys,
1369
+ datagram: u8[],
1370
+ header: QuicHeader,
1371
+ packet: QuicPacket
1372
+ ): boolean => {
1373
+ const plain: u8[] | null = quicDecryptPayload(keys, datagram, header, packet.header, packet)
1374
+ if (plain === null) {
1375
+ return false
1376
+ }
1377
+ packet.payload = plain
1378
+ return true
1379
+ }
1380
+
1381
+ /**
1382
+ * Opens one packet of a datagram (RFC 9001 §5.3, §5.4): header protection
1383
+ * off, the packet number recovered against `largestPn` (or -1), and the
1384
+ * payload decrypted. The answer's `error` is `QUIC_PACKET_OK` with `payload`
1385
+ * holding the frames, or the `QUIC_ERR_*` of the step that failed — see
1386
+ * `quicRemoveHeaderProtection` and `quicDecryptPacket`.
1387
+ */
1388
+ export const quicOpenPacket = (
1389
+ keys: QuicKeys,
1390
+ datagram: u8[],
1391
+ header: QuicHeader,
1392
+ largestPn: i64
1393
+ ): QuicPacket => {
1394
+ const packet: QuicPacket = quicRemoveHeaderProtection(keys, datagram, header, largestPn)
1395
+ quicDecryptPacket(keys, datagram, header, packet)
1396
+ return packet
1397
+ }
1398
+
1399
+ /**
1400
+ * The Retry integrity tag of RFC 9001 §5.8: AES-128-GCM under version 1's
1401
+ * fixed key and nonce over an empty plaintext, with the Retry pseudo-packet —
1402
+ * `odcid`'s length and bytes, then the Retry packet up to its tag — as the
1403
+ * associated data. `odcid` is the Destination Connection ID of the client's
1404
+ * Initial the Retry answers. Answers `null` for an `odcid` over 20 bytes.
1405
+ */
1406
+ export const quicRetryIntegrityTag = (odcid: u8[], retryWithoutTag: u8[]): u8[] | null => {
1407
+ const key: AesKey | null = aesKey(quicPacketRetryKey())
1408
+ if (key === null || !quicPacketCidFits(odcid)) {
1409
+ return null
1410
+ }
1411
+ const pseudo: u8[] = []
1412
+ pseudo.push(toU8(toI32(odcid.length)))
1413
+ quicPacketAppend(pseudo, odcid)
1414
+ quicPacketAppend(pseudo, retryWithoutTag)
1415
+ return aesGcmSeal(key, quicPacketRetryNonce(), pseudo, [])
1416
+ }
1417
+
1418
+ /**
1419
+ * A whole Retry packet (RFC 9000 §17.2.5, RFC 9001 §5.8): the long header with
1420
+ * `unused` in the first byte's low four bits (the server picks them; mask
1421
+ * them with randomness), the client's SCID as `dcid`, the server's new SCID,
1422
+ * the address-validation `token`, and the integrity tag over `odcid`, the
1423
+ * DCID the client's Initial was sent to. Answers `null` for a connection ID
1424
+ * over 20 bytes, an empty token (a client discards that Retry, §17.2.5.2) or
1425
+ * `unused` outside 0 to 15.
1426
+ */
1427
+ export const quicRetryPacket = (
1428
+ dcid: u8[],
1429
+ scid: u8[],
1430
+ token: u8[],
1431
+ odcid: u8[],
1432
+ unused: i32
1433
+ ): u8[] | null => {
1434
+ if (
1435
+ !quicPacketCidFits(dcid) ||
1436
+ !quicPacketCidFits(scid) ||
1437
+ toI32(token.length) === 0 ||
1438
+ unused < 0 ||
1439
+ unused > 15
1440
+ ) {
1441
+ return null
1442
+ }
1443
+ const dcidLength: i32 = toI32(dcid.length)
1444
+ const scidLength: i32 = toI32(scid.length)
1445
+ const out: u8[] = new Array<u8>(7 + dcidLength + scidLength)
1446
+ quicPacketPutLongPrefix(out, 0, unused | 0xf0, dcid, dcidLength, scid, scidLength)
1447
+ quicPacketAppend(out, token)
1448
+ const tag: u8[] | null = quicRetryIntegrityTag(odcid, out)
1449
+ if (tag === null) {
1450
+ return null
1451
+ }
1452
+ quicPacketAppend(out, tag)
1453
+ return out
1454
+ }
1455
+
1456
+ /**
1457
+ * Whether the Retry `header` describes carries a valid integrity tag for
1458
+ * `odcid`, the DCID of the Initial this client sent (RFC 9001 §5.8). The tag
1459
+ * is recomputed over the datagram's bytes and compared in constant time.
1460
+ * Answers `false` for a header that is not a Retry that parsed.
1461
+ */
1462
+ export const quicRetryVerify = (odcid: u8[], datagram: u8[], header: QuicHeader): boolean => {
1463
+ if (header.error !== QUIC_PACKET_OK || header.type !== QUIC_PACKET_RETRY) {
1464
+ return false
1465
+ }
1466
+ const tag: u8[] | null = quicRetryIntegrityTag(
1467
+ odcid,
1468
+ quicPacketSlice(datagram, header.start, header.pnOffset)
1469
+ )
1470
+ if (tag === null) {
1471
+ return false
1472
+ }
1473
+ return timingSafeEqual(tag, header.retryTag)
1474
+ }