quico 0.4.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;
@@ -158,15 +180,12 @@ function parse_quic_datagram(array) {
158
180
  // Version Negotiation (RFC 9000 §6, §17.2.1)
159
181
  // ============================================================
160
182
 
161
- /** QUIC versions this implementation can actually speak. */
162
- var SUPPORTED_VERSIONS = [0x00000001];
163
-
164
183
  /**
165
- * Is this a version we can handle?
184
+ * Is this a version we can handle? (quic_version.js has them.)
166
185
  * Version 0 identifies a Version Negotiation packet, not a real version.
167
186
  */
168
187
  function is_supported_version(version) {
169
- return SUPPORTED_VERSIONS.indexOf(version >>> 0) !== -1;
188
+ return isSupportedVersion(version);
170
189
  }
171
190
 
172
191
  /**
@@ -193,8 +212,10 @@ function is_supported_version(version) {
193
212
  *
194
213
  * @param {Uint8Array} recvDcid DCID from the client's packet (becomes our SCID)
195
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
196
217
  */
197
- function build_version_negotiation(recvDcid, recvScid) {
218
+ function build_version_negotiation(recvDcid, recvScid, offered) {
198
219
  var dcid = recvScid || new Uint8Array(0);
199
220
  var scid = recvDcid || new Uint8Array(0);
200
221
 
@@ -202,7 +223,7 @@ function build_version_negotiation(recvDcid, recvScid) {
202
223
  // clients don't come to depend on a fixed value.
203
224
  var first = 0x80 | (Math.floor(Math.random() * 128) & 0x7f);
204
225
 
205
- var versions = SUPPORTED_VERSIONS.slice();
226
+ var versions = (offered || SUPPORTED_VERSIONS).slice();
206
227
 
207
228
  // A reserved ("greased") version, per RFC 9000 §6.3. Its pattern is
208
229
  // 0x?a?a?a?a, and no endpoint will ever support it. Including one keeps
@@ -246,7 +267,132 @@ function build_version_negotiation(recvDcid, recvScid) {
246
267
  // QUIC Frame encoding (RFC 9000 §12.4)
247
268
  // ============================================================
248
269
 
249
- 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) {
250
396
  var parts = [];
251
397
 
252
398
  for (var i = 0; i < frames.length; i++) {
@@ -360,6 +506,21 @@ function encode_quic_frames(frames) {
360
506
  } else if (frame.type === 'handshake_done') {
361
507
  parts.push(new Uint8Array([0x1e]));
362
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
+
363
524
  } else if (frame.type === 'new_connection_id') {
364
525
  parts.push(concatUint8Arrays([
365
526
  new Uint8Array([0x18]),
@@ -390,6 +551,17 @@ function encode_quic_frames(frames) {
390
551
  writeVarInt(frame.finalSize)
391
552
  ]));
392
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
+
393
565
  } else if (frame.type === 'stop_sending') {
394
566
  parts.push(concatUint8Arrays([
395
567
  new Uint8Array([0x05]),
@@ -403,6 +575,12 @@ function encode_quic_frames(frames) {
403
575
  writeVarInt(frame.limit)
404
576
  ]));
405
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
+
406
584
  } else if (frame.type === 'stream_data_blocked') {
407
585
  parts.push(concatUint8Arrays([
408
586
  new Uint8Array([0x15]),
@@ -411,12 +589,13 @@ function encode_quic_frames(frames) {
411
589
  ]));
412
590
 
413
591
  } else if (frame.type === 'datagram') {
414
- // Emit the 0x30 form (no Length): the datagram data runs to the end of
415
- // the packet. QUICO sends each DATAGRAM as the sole/last frame of its
416
- // packet, so the length-prefixed 0x31 form is not needed here. (The
417
- // 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.)
418
596
  parts.push(concatUint8Arrays([
419
- new Uint8Array([0x30]),
597
+ new Uint8Array([0x31]),
598
+ writeVarInt(frame.data.byteLength),
420
599
  frame.data
421
600
  ]));
422
601
  }
@@ -442,11 +621,21 @@ function parse_quic_frames(buf) {
442
621
  return res;
443
622
  }
444
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;
445
630
  while (offset < buf.length) {
631
+ whole = false;
446
632
  var type = buf[offset++];
447
633
 
448
- // Multi-byte frame type
449
- 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) {
450
639
  offset--;
451
640
  var t = safeReadVarInt();
452
641
  if (!t) break;
@@ -454,7 +643,9 @@ function parse_quic_frames(buf) {
454
643
  }
455
644
 
456
645
  if (type === 0x00) {
457
- // 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;
458
649
  } else if (type === 0x01) {
459
650
  frames.push({ type: 'ping' });
460
651
 
@@ -471,6 +662,7 @@ function parse_quic_frames(buf) {
471
662
  var len = safeReadVarInt(); if (!len) break;
472
663
  ranges.push({ gap: gap.value, length: len.value });
473
664
  }
665
+ if (ranges.length !== rangeCount.value) break;
474
666
 
475
667
  var ecn = null;
476
668
  if (hasECN) {
@@ -491,6 +683,13 @@ function parse_quic_frames(buf) {
491
683
  var finalSize = safeReadVarInt(); if (!finalSize) break;
492
684
  frames.push({ type: 'reset_stream', id: id.value, error: error.value, finalSize: finalSize.value });
493
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
+
494
693
  } else if (type === 0x05) {
495
694
  var id = safeReadVarInt(); if (!id) break;
496
695
  var error = safeReadVarInt(); if (!error) break;
@@ -589,6 +788,16 @@ function parse_quic_frames(buf) {
589
788
  } else if (type === 0x1e) {
590
789
  frames.push({ type: 'handshake_done' });
591
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
+
592
801
  } else if (type === 0x30 || type === 0x31) {
593
802
  // RFC 9221 DATAGRAM frame. The 0x31 form carries an explicit Length
594
803
  // varint and the datagram data is exactly that many bytes; the 0x30 form
@@ -607,11 +816,14 @@ function parse_quic_frames(buf) {
607
816
  frames.push({ type: 'datagram', data: data });
608
817
 
609
818
  } else {
610
- // 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.
611
821
  frames.push({ type: 'unknown', frameType: type });
612
- break;
822
+ return frames;
613
823
  }
824
+ whole = true;
614
825
  }
826
+ if (!whole) frames.push({ type: 'malformed', frameType: type });
615
827
 
616
828
  return frames;
617
829
  }
@@ -678,63 +890,257 @@ function build_transport_params(params) {
678
890
  addParam(0x10, params.retry_source_connection_id);
679
891
  if (params.max_datagram_frame_size)
680
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);
681
917
 
682
918
  return new Uint8Array(out);
683
919
  }
684
920
 
685
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
+
686
975
  function parse_transport_params(buf, start) {
687
976
  if (!(buf instanceof Uint8Array)) throw new Error('Expect Uint8Array');
688
977
  var offset = start || 0;
689
978
  var end = buf.length;
690
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
+ }
691
1005
 
692
1006
  while (offset < end) {
693
1007
  var idVar = readVarInt(buf, offset);
694
- if (!idVar) break;
1008
+ if (!idVar) { bad('truncated'); break; }
695
1009
  offset += idVar.byteLength;
696
1010
 
697
1011
  var lenVar = readVarInt(buf, offset);
698
- if (!lenVar) break;
1012
+ if (!lenVar) { bad('truncated'); break; }
699
1013
  offset += lenVar.byteLength;
700
1014
 
701
- if (offset + lenVar.value > end) break;
1015
+ if (offset + lenVar.value > end) { bad('truncated'); break; }
702
1016
  var valueBytes = buf.slice(offset, offset + lenVar.value);
703
1017
  offset += lenVar.value;
704
1018
 
705
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);
706
1024
 
707
- // Safe inner varint: a malformed param could carry an empty/short value,
708
- // in which case readVarInt returns null — never dereference .value blindly.
709
- 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); };
710
1027
 
711
1028
  switch (id) {
712
- case 0x00: out.original_destination_connection_id = valueBytes; break;
1029
+ case 0x00: out.original_destination_connection_id = cid(valueBytes); break;
713
1030
  case 0x01: out.max_idle_timeout = vi(valueBytes); break;
714
- case 0x02: out.stateless_reset_token = valueBytes; break;
715
- 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;
716
1040
  case 0x04: out.initial_max_data = vi(valueBytes); break;
717
1041
  case 0x05: out.initial_max_stream_data_bidi_local = vi(valueBytes); break;
718
1042
  case 0x06: out.initial_max_stream_data_bidi_remote = vi(valueBytes); break;
719
1043
  case 0x07: out.initial_max_stream_data_uni = vi(valueBytes); break;
720
- case 0x08: out.initial_max_streams_bidi = vi(valueBytes); break;
721
- case 0x09: out.initial_max_streams_uni = vi(valueBytes); break;
722
- case 0x0a: out.ack_delay_exponent = vi(valueBytes); break;
723
- case 0x0b: out.max_ack_delay = vi(valueBytes); break;
724
- case 0x0c: out.disable_active_migration = true; break;
725
- case 0x0e: out.active_connection_id_limit = vi(valueBytes); break;
726
- case 0x0f: out.initial_source_connection_id = valueBytes; break;
727
- 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;
728
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;
729
1092
  default:
730
1093
  if (!out.unknown) out.unknown = [];
731
1094
  out.unknown.push({ id: id, bytes: valueBytes });
732
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
+ });
733
1116
  }
734
1117
 
1118
+ Object.defineProperty(out, '_ordered', { value: ordered, enumerable: false });
1119
+ Object.defineProperty(out, '_invalid', { value: invalid, enumerable: false });
735
1120
  return out;
736
1121
  }
737
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
+
738
1144
 
739
1145
  // ============================================================
740
1146
  // Exports
@@ -752,9 +1158,12 @@ export {
752
1158
 
753
1159
  // Frame encode/decode
754
1160
  encode_quic_frames,
1161
+ encode_quic_frames_each,
1162
+ quic_frames_size,
755
1163
  parse_quic_frames,
756
1164
 
757
1165
  // Transport parameters
758
1166
  build_transport_params,
759
- parse_transport_params
1167
+ parse_transport_params,
1168
+ transport_params_error
760
1169
  };