@amritk/nish-x86_64-linux 0.16.0 → 0.18.0

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