quico 0.3.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/transport.js CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  writeVarInt,
19
19
  readVarInt
20
20
  } from './utils.js';
21
+ import { SUPPORTED_VERSIONS, isSupportedVersion, longPacketType } from './quic_version.js';
21
22
 
22
23
 
23
24
  // ============================================================
@@ -26,8 +27,14 @@ import {
26
27
 
27
28
  /**
28
29
  * Parse a single QUIC packet header (enough to get type + totalLength).
30
+ *
31
+ * shortCidLen: the length of the connection IDs this receiver issues. A short
32
+ * header carries its Destination Connection ID without a length (RFC 9000
33
+ * §17.3) — only the receiver, who chose it, knows where it ends. A server whose
34
+ * IDs all have one length passes it, and short-header packets then come back
35
+ * with `dcid` like long-header ones do. Without it their `dcid` is absent.
29
36
  */
30
- function parse_quic_packet(array, offset0) {
37
+ function parse_quic_packet(array, offset0, shortCidLen) {
31
38
  if (!(array instanceof Uint8Array)) return null;
32
39
  if (offset0 === undefined) offset0 = 0;
33
40
  if (offset0 >= array.length) return null;
@@ -75,9 +82,17 @@ function parse_quic_packet(array, offset0) {
75
82
  };
76
83
  }
77
84
 
78
- var typeBits = (firstByte & 0x30) >> 4;
79
- var typeMap = ['initial', '0rtt', 'handshake', 'retry'];
80
- var packetType = typeMap[typeBits] || 'unknown';
85
+ // What the type bits mean is the version's (RFC 9369 §3.2). A version
86
+ // this side does not speak has no known layout past the connection IDs:
87
+ // what is known is enough to answer it with Version Negotiation.
88
+ var packetType = longPacketType(version, (firstByte & 0x30) >> 4);
89
+ if (!packetType) {
90
+ return {
91
+ form: 'long', type: 'unknown',
92
+ version: version, dcid: dcid, scid: scid,
93
+ totalLength: array.length - offset0
94
+ };
95
+ }
81
96
 
82
97
  if (packetType === 'retry') {
83
98
  return {
@@ -114,25 +129,32 @@ function parse_quic_packet(array, offset0) {
114
129
  } catch (e) { return null; }
115
130
 
116
131
  } else {
117
- // Short Header — Fixed Bit (0x40) MUST be 1 (RFC 9000 §17.3)
118
- if ((firstByte & 0x40) === 0) return null; // padding or invalid
119
- return {
132
+ // Short Header. Its fixed bit ("QUIC bit", 0x40) is not checked: every
133
+ // connection here advertises grease_quic_bit (RFC 9287), which lets the
134
+ // peer send it as 0. Bytes that are not a packet — zeros padding a
135
+ // datagram after its packets — fail decryption like any other garbage.
136
+ var shortPkt = {
120
137
  form: 'short', type: '1rtt',
121
138
  totalLength: array.length - offset0
122
139
  };
140
+ if (shortCidLen > 0) {
141
+ if (offset0 + 1 + shortCidLen > array.length) return null;
142
+ shortPkt.dcid = array.slice(offset0 + 1, offset0 + 1 + shortCidLen);
143
+ }
144
+ return shortPkt;
123
145
  }
124
146
  }
125
147
 
126
148
 
127
149
  /**
128
- * Split a UDP datagram into QUIC packets.
150
+ * Split a UDP datagram into QUIC packets. shortCidLen: see parse_quic_packet.
129
151
  */
130
- function parse_quic_datagram(array) {
152
+ function parse_quic_datagram(array, shortCidLen) {
131
153
  var packets = [];
132
154
  var offset = 0;
133
155
 
134
156
  while (offset < array.length) {
135
- var pkt = parse_quic_packet(array, offset);
157
+ var pkt = parse_quic_packet(array, offset, shortCidLen);
136
158
  if (!pkt || !pkt.totalLength) break;
137
159
 
138
160
  var start = offset;
@@ -154,11 +176,223 @@ function parse_quic_datagram(array) {
154
176
  }
155
177
 
156
178
 
179
+ // ============================================================
180
+ // Version Negotiation (RFC 9000 §6, §17.2.1)
181
+ // ============================================================
182
+
183
+ /**
184
+ * Is this a version we can handle? (quic_version.js has them.)
185
+ * Version 0 identifies a Version Negotiation packet, not a real version.
186
+ */
187
+ function is_supported_version(version) {
188
+ return isSupportedVersion(version);
189
+ }
190
+
191
+ /**
192
+ * Build a Version Negotiation packet (RFC 9000 §17.2.1).
193
+ *
194
+ * A server MUST send one when it receives a long-header packet carrying a
195
+ * version it does not support — it is how a client discovers what the server
196
+ * speaks, and how any QUIC prober checks whether a server is alive without
197
+ * completing a handshake. The interop runner's simulator does exactly that
198
+ * before starting a test, so without this the server looks dead.
199
+ *
200
+ * The packet is unusual: no encryption, no packet number, no frames, and no
201
+ * connection state. It is valid only as an immediate response.
202
+ *
203
+ * IMPORTANT: the connection IDs are swapped. The DCID we send is the SCID we
204
+ * received, and vice versa — the client matches the reply against its own
205
+ * Source CID, so getting this backwards makes the packet be ignored.
206
+ *
207
+ * byte 0 1 (long header) + 7 unpredictable bits
208
+ * 1..4 version 0x00000000
209
+ * 5 DCID length, then DCID
210
+ * ... SCID length, then SCID
211
+ * ... list of supported versions, 4 bytes each
212
+ *
213
+ * @param {Uint8Array} recvDcid DCID from the client's packet (becomes our SCID)
214
+ * @param {Uint8Array} recvScid SCID from the client's packet (becomes our DCID)
215
+ * @param {number[]} [versions] the versions offered (the server's, in its
216
+ * preference); every supported one by default
217
+ */
218
+ function build_version_negotiation(recvDcid, recvScid, offered) {
219
+ var dcid = recvScid || new Uint8Array(0);
220
+ var scid = recvDcid || new Uint8Array(0);
221
+
222
+ // RFC 9000 §17.2.1: the low 7 bits are unused and SHOULD be random, so that
223
+ // clients don't come to depend on a fixed value.
224
+ var first = 0x80 | (Math.floor(Math.random() * 128) & 0x7f);
225
+
226
+ var versions = (offered || SUPPORTED_VERSIONS).slice();
227
+
228
+ // A reserved ("greased") version, per RFC 9000 §6.3. Its pattern is
229
+ // 0x?a?a?a?a, and no endpoint will ever support it. Including one keeps
230
+ // clients honest: any client that assumes it understands every version in
231
+ // the list will break here rather than in the field.
232
+ var grease = ((Math.floor(Math.random() * 16) << 28) |
233
+ (0x0a << 24) |
234
+ (Math.floor(Math.random() * 16) << 20) |
235
+ (0x0a << 16) |
236
+ (Math.floor(Math.random() * 16) << 12) |
237
+ (0x0a << 8) |
238
+ (Math.floor(Math.random() * 16) << 4) |
239
+ 0x0a) >>> 0;
240
+ versions.push(grease);
241
+
242
+ var out = new Uint8Array(1 + 4 + 1 + dcid.length + 1 + scid.length + versions.length * 4);
243
+ var o = 0;
244
+
245
+ out[o++] = first;
246
+ out[o++] = 0; out[o++] = 0; out[o++] = 0; out[o++] = 0; // version 0
247
+
248
+ out[o++] = dcid.length;
249
+ out.set(dcid, o); o += dcid.length;
250
+
251
+ out[o++] = scid.length;
252
+ out.set(scid, o); o += scid.length;
253
+
254
+ for (var i = 0; i < versions.length; i++) {
255
+ var v = versions[i] >>> 0;
256
+ out[o++] = (v >>> 24) & 0xff;
257
+ out[o++] = (v >>> 16) & 0xff;
258
+ out[o++] = (v >>> 8) & 0xff;
259
+ out[o++] = v & 0xff;
260
+ }
261
+
262
+ return out;
263
+ }
264
+
265
+
157
266
  // ============================================================
158
267
  // QUIC Frame encoding (RFC 9000 §12.4)
159
268
  // ============================================================
160
269
 
161
- function encode_quic_frames(frames) {
270
+ // ---- Sizes and in-place writing, for the frames every packet carries ----
271
+ //
272
+ // A packet's frames were encoded one by one (each frame's pieces joined),
273
+ // then all joined: the stream data was copied twice before encryption — and
274
+ // sendFrames encoded whole packets once or twice more only to learn their
275
+ // size. The frames that carry data (STREAM, CRYPTO, DATAGRAM) and those in
276
+ // most packets (ACK, PADDING, PING) are now measured without encoding and
277
+ // written straight into one buffer; the rest, rare and small, as before.
278
+
279
+ function varIntSize(v) { return v < 0x40 ? 1 : v < 0x4000 ? 2 : v < 0x40000000 ? 4 : 8; }
280
+
281
+ function writeVarIntInto(buf, off, v) {
282
+ if (v < 0x40) { buf[off] = v; return off + 1; }
283
+ if (v < 0x4000) { buf[off] = 0x40 | (v >> 8); buf[off + 1] = v & 0xff; return off + 2; }
284
+ if (v < 0x40000000) {
285
+ buf[off] = 0x80 | (v >>> 24); buf[off + 1] = (v >>> 16) & 0xff; buf[off + 2] = (v >>> 8) & 0xff; buf[off + 3] = v & 0xff;
286
+ return off + 4;
287
+ }
288
+ var w = writeVarInt(v);
289
+ buf.set(w, off);
290
+ return off + w.length;
291
+ }
292
+
293
+ function hotFrameSize(f) {
294
+ var t = f.type;
295
+ if (t === 'stream') {
296
+ var n = (f.data && f.data.length) ? f.data.length : 0;
297
+ return 1 + varIntSize(f.id) + ((f.offset != null && f.offset > 0) ? varIntSize(f.offset) : 0) + varIntSize(n) + n;
298
+ }
299
+ if (t === 'crypto') return 1 + varIntSize(f.offset) + varIntSize(f.data.length) + f.data.length;
300
+ if (t === 'datagram') return 1 + varIntSize(f.data.byteLength) + f.data.byteLength;
301
+ if (t === 'padding') return f.length;
302
+ if (t === 'ping') return 1;
303
+ if (t === 'ack') {
304
+ var sz = 1 + varIntSize(f.largest) + varIntSize(f.delay) + varIntSize(f.ranges.length) + varIntSize(f.firstRange != null ? f.firstRange : 0);
305
+ for (var j = 0; j < f.ranges.length; j++) sz += varIntSize(f.ranges[j].gap) + varIntSize(f.ranges[j].length);
306
+ if (f.ecn !== null && f.ecn !== undefined) sz += varIntSize(f.ecn.ect0) + varIntSize(f.ecn.ect1) + varIntSize(f.ecn.ce);
307
+ return sz;
308
+ }
309
+ return -1;
310
+ }
311
+
312
+ // What encode_quic_frames(frames) would be, in bytes — without encoding.
313
+ function quic_frames_size(frames) {
314
+ var total = 0;
315
+ for (var i = 0; i < frames.length; i++) {
316
+ var sz = hotFrameSize(frames[i]);
317
+ total += sz >= 0 ? sz : encode_quic_frames_each([frames[i]]).length;
318
+ }
319
+ return total;
320
+ }
321
+
322
+ function writeHotFrame(f, out, off) {
323
+ var t = f.type;
324
+ if (t === 'stream') {
325
+ var data = f.data || null, n = data ? data.length : 0;
326
+ var hasOffset = (f.offset != null && f.offset > 0);
327
+ // LEN always present (see encode_quic_frames_each).
328
+ out[off++] = 0x08 | 0x02 | (hasOffset ? 0x04 : 0) | (f.fin ? 0x01 : 0);
329
+ off = writeVarIntInto(out, off, f.id);
330
+ if (hasOffset) off = writeVarIntInto(out, off, f.offset);
331
+ off = writeVarIntInto(out, off, n);
332
+ if (n) { out.set(data, off); off += n; }
333
+ return off;
334
+ }
335
+ if (t === 'crypto') {
336
+ out[off++] = 0x06;
337
+ off = writeVarIntInto(out, off, f.offset);
338
+ off = writeVarIntInto(out, off, f.data.length);
339
+ out.set(f.data, off);
340
+ return off + f.data.length;
341
+ }
342
+ if (t === 'datagram') {
343
+ out[off++] = 0x31;
344
+ off = writeVarIntInto(out, off, f.data.byteLength);
345
+ out.set(f.data, off);
346
+ return off + f.data.byteLength;
347
+ }
348
+ if (t === 'padding') return off + f.length; // zeros: the buffer is new
349
+ if (t === 'ping') { out[off] = 0x01; return off + 1; }
350
+ // ack
351
+ var hasECN = f.ecn !== null && f.ecn !== undefined;
352
+ out[off++] = hasECN ? 0x03 : 0x02;
353
+ off = writeVarIntInto(out, off, f.largest);
354
+ off = writeVarIntInto(out, off, f.delay);
355
+ off = writeVarIntInto(out, off, f.ranges.length);
356
+ off = writeVarIntInto(out, off, f.firstRange != null ? f.firstRange : 0);
357
+ for (var j = 0; j < f.ranges.length; j++) {
358
+ off = writeVarIntInto(out, off, f.ranges[j].gap);
359
+ off = writeVarIntInto(out, off, f.ranges[j].length);
360
+ }
361
+ if (hasECN) {
362
+ off = writeVarIntInto(out, off, f.ecn.ect0);
363
+ off = writeVarIntInto(out, off, f.ecn.ect1);
364
+ off = writeVarIntInto(out, off, f.ecn.ce);
365
+ }
366
+ return off;
367
+ }
368
+
369
+ // A packet's frames, encoded into one buffer: each byte of data copied once.
370
+ // `extra` zero bytes after them (PADDING), so a caller that pads need not
371
+ // copy the whole again.
372
+ function encode_quic_frames(frames, extra) {
373
+ var sizes = new Array(frames.length), rare = null, total = 0;
374
+ for (var i = 0; i < frames.length; i++) {
375
+ var sz = hotFrameSize(frames[i]);
376
+ if (sz < 0) {
377
+ if (!rare) rare = {};
378
+ rare[i] = encode_quic_frames_each([frames[i]]);
379
+ sz = rare[i].length;
380
+ }
381
+ sizes[i] = sz;
382
+ total += sz;
383
+ }
384
+ var out = new Uint8Array(total + (extra > 0 ? extra : 0));
385
+ var off = 0;
386
+ for (var k = 0; k < frames.length; k++) {
387
+ if (rare && rare[k]) { out.set(rare[k], off); off += rare[k].length; }
388
+ else off = writeHotFrame(frames[k], out, off);
389
+ }
390
+ return out;
391
+ }
392
+
393
+ // Each frame on its own, then joined — every frame type; what the rare ones
394
+ // still go through (and the reference the fast path is tested against).
395
+ function encode_quic_frames_each(frames) {
162
396
  var parts = [];
163
397
 
164
398
  for (var i = 0; i < frames.length; i++) {
@@ -272,6 +506,21 @@ function encode_quic_frames(frames) {
272
506
  } else if (frame.type === 'handshake_done') {
273
507
  parts.push(new Uint8Array([0x1e]));
274
508
 
509
+ } else if (frame.type === 'immediate_ack') {
510
+ // draft-ietf-quic-ack-frequency §5
511
+ parts.push(new Uint8Array([0x1f]));
512
+
513
+ } else if (frame.type === 'ack_frequency') {
514
+ // draft-ietf-quic-ack-frequency §4: Sequence Number, Ack-Eliciting
515
+ // Threshold, Requested Max Ack Delay (µs), Reordering Threshold.
516
+ parts.push(concatUint8Arrays([
517
+ writeVarInt(0xaf),
518
+ writeVarInt(frame.seq),
519
+ writeVarInt(frame.threshold),
520
+ writeVarInt(frame.maxDelayUs),
521
+ writeVarInt(frame.reorder)
522
+ ]));
523
+
275
524
  } else if (frame.type === 'new_connection_id') {
276
525
  parts.push(concatUint8Arrays([
277
526
  new Uint8Array([0x18]),
@@ -302,6 +551,17 @@ function encode_quic_frames(frames) {
302
551
  writeVarInt(frame.finalSize)
303
552
  ]));
304
553
 
554
+ } else if (frame.type === 'reset_stream_at') {
555
+ // RESET_STREAM_AT (draft-ietf-quic-reliable-stream-reset §4): a reset
556
+ // that keeps the stream's first reliableSize bytes delivered.
557
+ parts.push(concatUint8Arrays([
558
+ new Uint8Array([0x24]),
559
+ writeVarInt(frame.id),
560
+ writeVarInt(frame.error),
561
+ writeVarInt(frame.finalSize),
562
+ writeVarInt(frame.reliableSize)
563
+ ]));
564
+
305
565
  } else if (frame.type === 'stop_sending') {
306
566
  parts.push(concatUint8Arrays([
307
567
  new Uint8Array([0x05]),
@@ -315,6 +575,12 @@ function encode_quic_frames(frames) {
315
575
  writeVarInt(frame.limit)
316
576
  ]));
317
577
 
578
+ } else if (frame.type === 'streams_blocked_bidi' || frame.type === 'streams_blocked_uni') {
579
+ parts.push(concatUint8Arrays([
580
+ new Uint8Array([frame.type === 'streams_blocked_bidi' ? 0x16 : 0x17]),
581
+ writeVarInt(frame.limit)
582
+ ]));
583
+
318
584
  } else if (frame.type === 'stream_data_blocked') {
319
585
  parts.push(concatUint8Arrays([
320
586
  new Uint8Array([0x15]),
@@ -323,12 +589,13 @@ function encode_quic_frames(frames) {
323
589
  ]));
324
590
 
325
591
  } else if (frame.type === 'datagram') {
326
- // Emit the 0x30 form (no Length): the datagram data runs to the end of
327
- // the packet. QUICO sends each DATAGRAM as the sole/last frame of its
328
- // packet, so the length-prefixed 0x31 form is not needed here. (The
329
- // parser above accepts both forms on receive.)
592
+ // The 0x31 form, with a Length: datagrams share packets with each other
593
+ // and with stream data now (execute_quic_burst), so one is not always
594
+ // the last frame, which the 0x30 form requires. (The parser accepts
595
+ // both forms on receive.)
330
596
  parts.push(concatUint8Arrays([
331
- new Uint8Array([0x30]),
597
+ new Uint8Array([0x31]),
598
+ writeVarInt(frame.data.byteLength),
332
599
  frame.data
333
600
  ]));
334
601
  }
@@ -354,11 +621,21 @@ function parse_quic_frames(buf) {
354
621
  return res;
355
622
  }
356
623
 
624
+ // A frame that ends before its fields do — every `break` below but the
625
+ // unknown type's — is reported as { type: 'malformed' }, last: the frames
626
+ // before it stand, and the connection closes with FRAME_ENCODING_ERROR
627
+ // (RFC 9000 §12.4, §19). Dropped silently, as it was, a truncated frame
628
+ // was simply not there, and the packet acknowledged all the same.
629
+ var whole = true;
357
630
  while (offset < buf.length) {
631
+ whole = false;
358
632
  var type = buf[offset++];
359
633
 
360
- // Multi-byte frame type
361
- if (type >= 0x80) {
634
+ // Multi-byte frame type: a varint of two bytes or more (RFC 9000 §12.4
635
+ // — a type is a varint like any other). Only 4- and 8-byte ones were
636
+ // read as such; a 2-byte one (0x40..0x3fff: ACK_FREQUENCY is 0xaf) was
637
+ // read as its first byte.
638
+ if (type >= 0x40) {
362
639
  offset--;
363
640
  var t = safeReadVarInt();
364
641
  if (!t) break;
@@ -366,7 +643,9 @@ function parse_quic_frames(buf) {
366
643
  }
367
644
 
368
645
  if (type === 0x00) {
369
- // PADDING — skip
646
+ // PADDING — skip. Noted, so a packet of PADDING alone is told apart
647
+ // from one with no frames (RFC 9000 §12.4).
648
+ frames.padding = true;
370
649
  } else if (type === 0x01) {
371
650
  frames.push({ type: 'ping' });
372
651
 
@@ -383,6 +662,7 @@ function parse_quic_frames(buf) {
383
662
  var len = safeReadVarInt(); if (!len) break;
384
663
  ranges.push({ gap: gap.value, length: len.value });
385
664
  }
665
+ if (ranges.length !== rangeCount.value) break;
386
666
 
387
667
  var ecn = null;
388
668
  if (hasECN) {
@@ -403,6 +683,13 @@ function parse_quic_frames(buf) {
403
683
  var finalSize = safeReadVarInt(); if (!finalSize) break;
404
684
  frames.push({ type: 'reset_stream', id: id.value, error: error.value, finalSize: finalSize.value });
405
685
 
686
+ } else if (type === 0x24) {
687
+ var id = safeReadVarInt(); if (!id) break;
688
+ var error = safeReadVarInt(); if (!error) break;
689
+ var finalSize = safeReadVarInt(); if (!finalSize) break;
690
+ var reliableSize = safeReadVarInt(); if (!reliableSize) break;
691
+ frames.push({ type: 'reset_stream_at', id: id.value, error: error.value, finalSize: finalSize.value, reliableSize: reliableSize.value });
692
+
406
693
  } else if (type === 0x05) {
407
694
  var id = safeReadVarInt(); if (!id) break;
408
695
  var error = safeReadVarInt(); if (!error) break;
@@ -501,6 +788,16 @@ function parse_quic_frames(buf) {
501
788
  } else if (type === 0x1e) {
502
789
  frames.push({ type: 'handshake_done' });
503
790
 
791
+ } else if (type === 0x1f) {
792
+ frames.push({ type: 'immediate_ack' });
793
+
794
+ } else if (type === 0xaf) {
795
+ var afSeq = safeReadVarInt(); if (!afSeq) break;
796
+ var afThr = safeReadVarInt(); if (!afThr) break;
797
+ var afDelay = safeReadVarInt(); if (!afDelay) break;
798
+ var afReorder = safeReadVarInt(); if (!afReorder) break;
799
+ frames.push({ type: 'ack_frequency', seq: afSeq.value, threshold: afThr.value, maxDelayUs: afDelay.value, reorder: afReorder.value });
800
+
504
801
  } else if (type === 0x30 || type === 0x31) {
505
802
  // RFC 9221 DATAGRAM frame. The 0x31 form carries an explicit Length
506
803
  // varint and the datagram data is exactly that many bytes; the 0x30 form
@@ -519,11 +816,14 @@ function parse_quic_frames(buf) {
519
816
  frames.push({ type: 'datagram', data: data });
520
817
 
521
818
  } else {
522
- // Unknown frame — stop
819
+ // Unknown frame — stop: its length is not known, so nothing after it
820
+ // can be read. FRAME_ENCODING_ERROR (§12.4), the connection's to send.
523
821
  frames.push({ type: 'unknown', frameType: type });
524
- break;
822
+ return frames;
525
823
  }
824
+ whole = true;
526
825
  }
826
+ if (!whole) frames.push({ type: 'malformed', frameType: type });
527
827
 
528
828
  return frames;
529
829
  }
@@ -590,63 +890,257 @@ function build_transport_params(params) {
590
890
  addParam(0x10, params.retry_source_connection_id);
591
891
  if (params.max_datagram_frame_size)
592
892
  addParam(0x20, params.max_datagram_frame_size);
893
+ // reset_stream_at (draft-ietf-quic-reliable-stream-reset §3): empty; this
894
+ // side takes RESET_STREAM_AT.
895
+ if (params.reset_stream_at)
896
+ addParam(0x1d, true);
897
+ // version_information (RFC 9368 §3): Chosen Version, then the Available
898
+ // Versions, 32 bits each.
899
+ if (params.version_information) {
900
+ var vi = params.version_information;
901
+ var list = [vi.chosen].concat(vi.available || []);
902
+ var vb = new Uint8Array(list.length * 4);
903
+ for (var i = 0; i < list.length; i++) {
904
+ var v = list[i] >>> 0;
905
+ vb[i * 4] = (v >>> 24) & 0xff; vb[i * 4 + 1] = (v >>> 16) & 0xff;
906
+ vb[i * 4 + 2] = (v >>> 8) & 0xff; vb[i * 4 + 3] = v & 0xff;
907
+ }
908
+ addParam(0x11, vb);
909
+ }
910
+ // grease_quic_bit (RFC 9287): the peer may send the fixed bit as 0.
911
+ if (params.grease_quic_bit)
912
+ addParam(0x2ab2, true);
913
+ // min_ack_delay (draft-ietf-quic-ack-frequency §3), µs: this side takes
914
+ // ACK_FREQUENCY and IMMEDIATE_ACK.
915
+ if (params.min_ack_delay !== undefined)
916
+ addParam(0xff04de1b, params.min_ack_delay);
593
917
 
594
918
  return new Uint8Array(out);
595
919
  }
596
920
 
597
921
 
922
+ // Transport parameters whose value is a byte string rather than a number
923
+ // (RFC 9000 §18.2). Their contents are per-connection random; only their
924
+ // length is a stable property of the sender.
925
+ var OPAQUE_PARAMS = {
926
+ 0x00: 1, // original_destination_connection_id
927
+ 0x02: 1, // stateless_reset_token
928
+ 0x0d: 1, // preferred_address
929
+ 0x0f: 1, // initial_source_connection_id
930
+ 0x10: 1 // retry_source_connection_id
931
+ };
932
+
933
+ // version_information (RFC 9368 §3) → { chosen, available: [...] }, or
934
+ // 'invalid' — shorter than one version, not a whole number of them, or a
935
+ // zero version among them (§4: closed with a TRANSPORT_PARAMETER_ERROR).
936
+ function parseVersionInformation(bytes) {
937
+ if (bytes.length < 4 || bytes.length % 4 !== 0) return 'invalid';
938
+ var list = [];
939
+ for (var i = 0; i < bytes.length; i += 4) {
940
+ var v = ((bytes[i] << 24) | (bytes[i + 1] << 16) | (bytes[i + 2] << 8) | bytes[i + 3]) >>> 0;
941
+ if (v === 0) return 'invalid';
942
+ list.push(v);
943
+ }
944
+ return { chosen: list[0], available: list.slice(1) };
945
+ }
946
+
947
+ // preferred_address (RFC 9000 §18.2): IPv4 address and port, IPv6 address
948
+ // and port, a connection ID (1-20 bytes — "a server MUST NOT include a
949
+ // zero-length connection ID"), its stateless reset token. Anything else:
950
+ // null, malformed.
951
+ function parsePreferredAddress(b) {
952
+ if (b.length < 4 + 2 + 16 + 2 + 1 + 16) return null;
953
+ var cidLen = b[24];
954
+ if (cidLen < 1 || cidLen > 20 || b.length !== 25 + cidLen + 16) return null;
955
+ return {
956
+ ipv4: b.slice(0, 4), ipv4Port: (b[4] << 8) | b[5],
957
+ ipv6: b.slice(6, 22), ipv6Port: (b[22] << 8) | b[23],
958
+ connectionId: b.slice(25, 25 + cidLen),
959
+ statelessResetToken: b.slice(25 + cidLen)
960
+ };
961
+ }
962
+
963
+ // The largest a stream count may be (RFC 9000 §4.6, §18.2): 2^60. Read
964
+ // exactly, not through a Number — 2^60 + 1 and 2^60 are one Number.
965
+ var MAX_STREAMS_LIMIT = 1n << 60n;
966
+ function streamCountTooLarge(bytes) {
967
+ if (bytes.length !== 8 || (bytes[0] >> 6) !== 3) return false;
968
+ var v = BigInt(bytes[0] & 0x3f);
969
+ for (var i = 1; i < 8; i++) v = (v << 8n) | BigInt(bytes[i]);
970
+ return v > MAX_STREAMS_LIMIT;
971
+ }
972
+
973
+ function quietInt(bytes) { var r = readVarInt(bytes, 0); return r ? r.value : 0; }
974
+
598
975
  function parse_transport_params(buf, start) {
599
976
  if (!(buf instanceof Uint8Array)) throw new Error('Expect Uint8Array');
600
977
  var offset = start || 0;
601
978
  var end = buf.length;
602
979
  var out = {};
980
+ // The first reason the parameters are malformed, if they are (RFC 9000
981
+ // §7.4: a TRANSPORT_PARAMETER_ERROR) — on a non-enumerable property, like
982
+ // _ordered: the connection decides. Truncated, an identifier twice, an
983
+ // integer whose varint does not fill the value exactly, a value out of
984
+ // the range §18.2 gives it.
985
+ var invalid = null;
986
+ function bad(why) { if (!invalid) invalid = why; }
987
+ var seen = new Set();
988
+ // The parameters again in the order sent, on a non-enumerable property so
989
+ // nothing that iterates the result is affected. Both order and unknown
990
+ // identifiers are lost by the object form, and both are fingerprintable
991
+ // properties of the peer's implementation.
992
+ var ordered = [];
993
+
994
+ // An integer parameter (§18): one varint that fills the value exactly.
995
+ // Empty, short or with bytes left over: malformed (and read as 0).
996
+ function viOf(id, bytes) {
997
+ var r = readVarInt(bytes, 0);
998
+ if (!r || r.byteLength !== bytes.length) { bad('parameter 0x' + id.toString(16) + ' is not one integer'); return 0; }
999
+ return r.value;
1000
+ }
1001
+ function cidOf(id, bytes) {
1002
+ if (bytes.length > 20) bad('parameter 0x' + id.toString(16) + ': a connection ID longer than 20 bytes');
1003
+ return bytes;
1004
+ }
603
1005
 
604
1006
  while (offset < end) {
605
1007
  var idVar = readVarInt(buf, offset);
606
- if (!idVar) break;
1008
+ if (!idVar) { bad('truncated'); break; }
607
1009
  offset += idVar.byteLength;
608
1010
 
609
1011
  var lenVar = readVarInt(buf, offset);
610
- if (!lenVar) break;
1012
+ if (!lenVar) { bad('truncated'); break; }
611
1013
  offset += lenVar.byteLength;
612
1014
 
613
- if (offset + lenVar.value > end) break;
1015
+ if (offset + lenVar.value > end) { bad('truncated'); break; }
614
1016
  var valueBytes = buf.slice(offset, offset + lenVar.value);
615
1017
  offset += lenVar.value;
616
1018
 
617
1019
  var id = idVar.value;
1020
+ // §7.4: "An endpoint SHOULD treat receipt of duplicate transport
1021
+ // parameters as a connection error" — unknown ones too.
1022
+ if (seen.has(id)) bad('parameter 0x' + id.toString(16) + ' twice');
1023
+ seen.add(id);
618
1024
 
619
- // Safe inner varint: a malformed param could carry an empty/short value,
620
- // in which case readVarInt returns null — never dereference .value blindly.
621
- function vi(bytes) { var r = readVarInt(bytes, 0); return r ? r.value : 0; }
1025
+ var vi = function (bytes) { return viOf(id, bytes); };
1026
+ var cid = function (bytes) { return cidOf(id, bytes); };
622
1027
 
623
1028
  switch (id) {
624
- case 0x00: out.original_destination_connection_id = valueBytes; break;
1029
+ case 0x00: out.original_destination_connection_id = cid(valueBytes); break;
625
1030
  case 0x01: out.max_idle_timeout = vi(valueBytes); break;
626
- case 0x02: out.stateless_reset_token = valueBytes; break;
627
- case 0x03: out.max_udp_payload_size = vi(valueBytes); break;
1031
+ case 0x02:
1032
+ if (valueBytes.length !== 16) bad('stateless_reset_token of ' + valueBytes.length + ' bytes');
1033
+ out.stateless_reset_token = valueBytes;
1034
+ break;
1035
+ case 0x03:
1036
+ // "Values below 1200 are invalid."
1037
+ out.max_udp_payload_size = vi(valueBytes);
1038
+ if (out.max_udp_payload_size < 1200) bad('max_udp_payload_size below 1200');
1039
+ break;
628
1040
  case 0x04: out.initial_max_data = vi(valueBytes); break;
629
1041
  case 0x05: out.initial_max_stream_data_bidi_local = vi(valueBytes); break;
630
1042
  case 0x06: out.initial_max_stream_data_bidi_remote = vi(valueBytes); break;
631
1043
  case 0x07: out.initial_max_stream_data_uni = vi(valueBytes); break;
632
- case 0x08: out.initial_max_streams_bidi = vi(valueBytes); break;
633
- case 0x09: out.initial_max_streams_uni = vi(valueBytes); break;
634
- case 0x0a: out.ack_delay_exponent = vi(valueBytes); break;
635
- case 0x0b: out.max_ack_delay = vi(valueBytes); break;
636
- case 0x0c: out.disable_active_migration = true; break;
637
- case 0x0e: out.active_connection_id_limit = vi(valueBytes); break;
638
- case 0x0f: out.initial_source_connection_id = valueBytes; break;
639
- case 0x10: out.retry_source_connection_id = valueBytes; break;
1044
+ case 0x08:
1045
+ case 0x09:
1046
+ // "...cannot exceed 2^60, as it is not possible to encode stream
1047
+ // IDs larger than 2^62-1" (§18.2).
1048
+ var sname = id === 0x08 ? 'initial_max_streams_bidi' : 'initial_max_streams_uni';
1049
+ if (streamCountTooLarge(valueBytes)) bad(sname + ' above 2^60');
1050
+ out[sname] = vi(valueBytes);
1051
+ break;
1052
+ case 0x0a:
1053
+ // "Values above 20 are invalid."
1054
+ out.ack_delay_exponent = vi(valueBytes);
1055
+ if (out.ack_delay_exponent > 20) bad('ack_delay_exponent above 20');
1056
+ break;
1057
+ case 0x0b:
1058
+ // "Values of 2^14 or greater are invalid."
1059
+ out.max_ack_delay = vi(valueBytes);
1060
+ if (out.max_ack_delay >= 16384) bad('max_ack_delay of 2^14 or more');
1061
+ break;
1062
+ case 0x0c:
1063
+ // "This parameter is a zero-length value."
1064
+ if (valueBytes.length !== 0) bad('disable_active_migration with a value');
1065
+ out.disable_active_migration = true;
1066
+ break;
1067
+ case 0x0d:
1068
+ out.preferred_address = parsePreferredAddress(valueBytes);
1069
+ if (!out.preferred_address) bad('preferred_address malformed');
1070
+ break;
1071
+ case 0x0e:
1072
+ // "The value ... MUST be at least 2."
1073
+ out.active_connection_id_limit = vi(valueBytes);
1074
+ if (out.active_connection_id_limit < 2) bad('active_connection_id_limit below 2');
1075
+ break;
1076
+ case 0x0f: out.initial_source_connection_id = cid(valueBytes); break;
1077
+ case 0x10: out.retry_source_connection_id = cid(valueBytes); break;
640
1078
  case 0x20: out.max_datagram_frame_size = vi(valueBytes); break;
1079
+ case 0x1d:
1080
+ // reset_stream_at: "a value ... MUST be treated as a connection
1081
+ // error of type TRANSPORT_PARAMETER_ERROR".
1082
+ if (valueBytes.length !== 0) bad('reset_stream_at with a value');
1083
+ out.reset_stream_at = true;
1084
+ break;
1085
+ case 0x11: out.version_information = parseVersionInformation(valueBytes); break;
1086
+ case 0xff04de1b: out.min_ack_delay = vi(valueBytes); break;
1087
+ case 0x2ab2:
1088
+ // RFC 9287 §3: a value is a TRANSPORT_PARAMETER_ERROR; noted, the
1089
+ // connection decides.
1090
+ out.grease_quic_bit = valueBytes.length === 0 ? true : 'invalid';
1091
+ break;
641
1092
  default:
642
1093
  if (!out.unknown) out.unknown = [];
643
1094
  out.unknown.push({ id: id, bytes: valueBytes });
644
1095
  }
1096
+
1097
+ // Recorded for every parameter, known or not.
1098
+ //
1099
+ // OPAQUE parameters are recorded by LENGTH, never by content: connection
1100
+ // ids, stateless-reset tokens and preferred-address blobs are freshly
1101
+ // random on every connection, so hashing their contents produces a
1102
+ // fingerprint that never repeats. Their length, on the other hand, is a
1103
+ // property of the implementation and is exactly what should be captured.
1104
+ //
1105
+ // Chosen by identifier rather than by size, which was the first attempt
1106
+ // and was wrong: an 8-byte connection id is short enough to look like a
1107
+ // number, so a length test silently let it through and the fingerprint
1108
+ // changed on every connection. Which parameters are opaque is fixed by
1109
+ // RFC 9000 §18.2, so it can simply be listed.
1110
+ ordered.push({
1111
+ id: id,
1112
+ value: OPAQUE_PARAMS[id] ? ('L' + valueBytes.length)
1113
+ : id === 0x11 ? ('V' + Buffer.from(valueBytes).toString('hex'))
1114
+ : quietInt(valueBytes)
1115
+ });
645
1116
  }
646
1117
 
1118
+ Object.defineProperty(out, '_ordered', { value: ordered, enumerable: false });
1119
+ Object.defineProperty(out, '_invalid', { value: invalid, enumerable: false });
647
1120
  return out;
648
1121
  }
649
1122
 
1123
+ // The parameters only a server sends (RFC 9000 §18.2): "A client MUST NOT
1124
+ // include any server-only transport parameter ... A server MUST treat receipt
1125
+ // of any of these transport parameters as a connection error of type
1126
+ // TRANSPORT_PARAMETER_ERROR."
1127
+ var SERVER_ONLY_PARAMS = [
1128
+ 'original_destination_connection_id', 'preferred_address',
1129
+ 'retry_source_connection_id', 'stateless_reset_token'
1130
+ ];
1131
+
1132
+ // Why the peer's parameters are a TRANSPORT_PARAMETER_ERROR, or null:
1133
+ // malformed (above), or — fromClient — one only a server sends.
1134
+ function transport_params_error(p, fromClient) {
1135
+ if (p._invalid) return p._invalid;
1136
+ if (fromClient) {
1137
+ for (var i = 0; i < SERVER_ONLY_PARAMS.length; i++) {
1138
+ if (p[SERVER_ONLY_PARAMS[i]] !== undefined) return SERVER_ONLY_PARAMS[i] + ' from a client';
1139
+ }
1140
+ }
1141
+ return null;
1142
+ }
1143
+
650
1144
 
651
1145
  // ============================================================
652
1146
  // Exports
@@ -657,11 +1151,19 @@ export {
657
1151
  parse_quic_datagram,
658
1152
  parse_quic_packet,
659
1153
 
1154
+ // Version negotiation
1155
+ SUPPORTED_VERSIONS,
1156
+ is_supported_version,
1157
+ build_version_negotiation,
1158
+
660
1159
  // Frame encode/decode
661
1160
  encode_quic_frames,
1161
+ encode_quic_frames_each,
1162
+ quic_frames_size,
662
1163
  parse_quic_frames,
663
1164
 
664
1165
  // Transport parameters
665
1166
  build_transport_params,
666
- parse_transport_params
1167
+ parse_transport_params,
1168
+ transport_params_error
667
1169
  };