quico 0.1.3 → 0.3.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.
@@ -0,0 +1,667 @@
1
+ /*
2
+ * quico: HTTP/3 and QUIC implementation for Node.js
3
+ * Copyright 2025 colocohen
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at:
8
+ *
9
+ * https://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * This file is part of the open-source project hosted at:
12
+ * https://github.com/colocohen/quico
13
+ */
14
+
15
+ import {
16
+ DEBUG,
17
+ concatUint8Arrays,
18
+ writeVarInt,
19
+ readVarInt
20
+ } from './utils.js';
21
+
22
+
23
+ // ============================================================
24
+ // QUIC Datagram → Packet splitting (RFC 9000 §12.2)
25
+ // ============================================================
26
+
27
+ /**
28
+ * Parse a single QUIC packet header (enough to get type + totalLength).
29
+ */
30
+ function parse_quic_packet(array, offset0) {
31
+ if (!(array instanceof Uint8Array)) return null;
32
+ if (offset0 === undefined) offset0 = 0;
33
+ if (offset0 >= array.length) return null;
34
+
35
+ var firstByte = array[offset0];
36
+ var isLong = (firstByte & 0x80) !== 0;
37
+
38
+ if (isLong) {
39
+ if (offset0 + 6 > array.length) return null;
40
+
41
+ var version = (
42
+ (array[offset0 + 1] << 24) |
43
+ (array[offset0 + 2] << 16) |
44
+ (array[offset0 + 3] << 8) |
45
+ array[offset0 + 4]
46
+ ) >>> 0;
47
+
48
+ var dcidLen = array[offset0 + 5];
49
+ var offset = offset0 + 6;
50
+
51
+ if (offset + dcidLen + 1 > array.length) return null;
52
+ var dcid = array.slice(offset, offset + dcidLen);
53
+ offset += dcidLen;
54
+
55
+ var scidLen = array[offset++];
56
+ if (offset + scidLen > array.length) return null;
57
+ var scid = array.slice(offset, offset + scidLen);
58
+ offset += scidLen;
59
+
60
+ // Version Negotiation
61
+ if (version === 0) {
62
+ var supportedVersions = [];
63
+ while (offset + 4 <= array.length) {
64
+ supportedVersions.push(
65
+ (array[offset] << 24) | (array[offset + 1] << 16) |
66
+ (array[offset + 2] << 8) | array[offset + 3]
67
+ );
68
+ offset += 4;
69
+ }
70
+ return {
71
+ form: 'long', type: 'version_negotiation',
72
+ version: version, dcid: dcid, scid: scid,
73
+ supportedVersions: supportedVersions,
74
+ totalLength: offset - offset0
75
+ };
76
+ }
77
+
78
+ var typeBits = (firstByte & 0x30) >> 4;
79
+ var typeMap = ['initial', '0rtt', 'handshake', 'retry'];
80
+ var packetType = typeMap[typeBits] || 'unknown';
81
+
82
+ if (packetType === 'retry') {
83
+ return {
84
+ form: 'long', type: 'retry',
85
+ version: version, dcid: dcid, scid: scid,
86
+ totalLength: array.length - offset0
87
+ };
88
+ }
89
+
90
+ // Initial: read token
91
+ var token = null;
92
+ if (packetType === 'initial') {
93
+ try {
94
+ var tokenLen = readVarInt(array, offset);
95
+ offset += tokenLen.byteLength;
96
+ if (offset + tokenLen.value > array.length) return null;
97
+ token = array.slice(offset, offset + tokenLen.value);
98
+ offset += tokenLen.value;
99
+ } catch (e) { return null; }
100
+ }
101
+
102
+ // Length field
103
+ try {
104
+ var lengthInfo = readVarInt(array, offset);
105
+ offset += lengthInfo.byteLength;
106
+ var totalLength = offset - offset0 + lengthInfo.value;
107
+ if (offset0 + totalLength > array.length) return null;
108
+
109
+ return {
110
+ form: 'long', type: packetType,
111
+ version: version, dcid: dcid, scid: scid, token: token,
112
+ totalLength: totalLength
113
+ };
114
+ } catch (e) { return null; }
115
+
116
+ } 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 {
120
+ form: 'short', type: '1rtt',
121
+ totalLength: array.length - offset0
122
+ };
123
+ }
124
+ }
125
+
126
+
127
+ /**
128
+ * Split a UDP datagram into QUIC packets.
129
+ */
130
+ function parse_quic_datagram(array) {
131
+ var packets = [];
132
+ var offset = 0;
133
+
134
+ while (offset < array.length) {
135
+ var pkt = parse_quic_packet(array, offset);
136
+ if (!pkt || !pkt.totalLength) break;
137
+
138
+ var start = offset;
139
+ var end = offset + pkt.totalLength;
140
+
141
+ pkt.raw = (start === 0 && end === array.length)
142
+ ? array
143
+ : array.slice(start, end);
144
+
145
+ if (packets.length > 0) {
146
+ if (DEBUG) console.log('[quic] coalesced pkt #' + packets.length + ' type=' + pkt.type + ' offset=' + start + ' len=' + pkt.totalLength + ' firstByte=0x' + array[start].toString(16).padStart(2,'0'));
147
+ }
148
+
149
+ packets.push(pkt);
150
+ offset = end;
151
+ }
152
+
153
+ return packets;
154
+ }
155
+
156
+
157
+ // ============================================================
158
+ // QUIC Frame encoding (RFC 9000 §12.4)
159
+ // ============================================================
160
+
161
+ function encode_quic_frames(frames) {
162
+ var parts = [];
163
+
164
+ for (var i = 0; i < frames.length; i++) {
165
+ var frame = frames[i];
166
+
167
+ if (frame.type === 'padding') {
168
+ parts.push(new Uint8Array(frame.length));
169
+
170
+ } else if (frame.type === 'ping') {
171
+ parts.push(new Uint8Array([0x01]));
172
+
173
+ } else if (frame.type === 'ack') {
174
+ var hasECN = frame.ecn !== null && frame.ecn !== undefined;
175
+ var typeByte = hasECN ? 0x03 : 0x02;
176
+ var temp = [
177
+ new Uint8Array([typeByte]),
178
+ writeVarInt(frame.largest),
179
+ writeVarInt(frame.delay),
180
+ writeVarInt(frame.ranges.length),
181
+ writeVarInt(frame.firstRange != null ? frame.firstRange : 0)
182
+ ];
183
+ for (var j = 0; j < frame.ranges.length; j++) {
184
+ temp.push(writeVarInt(frame.ranges[j].gap));
185
+ temp.push(writeVarInt(frame.ranges[j].length));
186
+ }
187
+ if (hasECN) {
188
+ temp.push(writeVarInt(frame.ecn.ect0));
189
+ temp.push(writeVarInt(frame.ecn.ect1));
190
+ temp.push(writeVarInt(frame.ecn.ce));
191
+ }
192
+ parts.push(concatUint8Arrays(temp));
193
+
194
+ } else if (frame.type === 'crypto') {
195
+ parts.push(concatUint8Arrays([
196
+ new Uint8Array([0x06]),
197
+ writeVarInt(frame.offset),
198
+ writeVarInt(frame.data.length),
199
+ frame.data
200
+ ]));
201
+
202
+ } else if (frame.type === 'stream') {
203
+ var typeByte = 0x08;
204
+ var hasOffset = (frame.offset != null && frame.offset > 0);
205
+ var hasFin = !!frame.fin;
206
+ var dataLen = (frame.data && frame.data.length) ? frame.data.length : 0;
207
+
208
+ // ALWAYS emit the LEN field. A STREAM frame without the LEN bit means
209
+ // "data runs to the end of the packet"; if anything follows it in the
210
+ // packet (another frame — e.g. a zero-length FIN-only batched with
211
+ // others — or padding), the peer reads those trailing bytes as stream
212
+ // data and the offset overshoots the declared final size
213
+ // (FINAL_SIZE / PROTOCOL_VIOLATION 0xa). An explicit length is always
214
+ // safe and self-delimiting.
215
+ if (hasOffset) typeByte |= 0x04;
216
+ typeByte |= 0x02; // LEN — always present
217
+ if (hasFin) typeByte |= 0x01;
218
+
219
+ parts.push(concatUint8Arrays([
220
+ new Uint8Array([typeByte]),
221
+ writeVarInt(frame.id),
222
+ hasOffset ? writeVarInt(frame.offset) : new Uint8Array(0),
223
+ writeVarInt(dataLen),
224
+ frame.data || new Uint8Array(0)
225
+ ]));
226
+
227
+ } else if (frame.type === 'new_token') {
228
+ parts.push(concatUint8Arrays([
229
+ new Uint8Array([0x07]),
230
+ writeVarInt(frame.token.length),
231
+ frame.token
232
+ ]));
233
+
234
+ } else if (frame.type === 'max_data') {
235
+ parts.push(concatUint8Arrays([
236
+ new Uint8Array([0x10]),
237
+ writeVarInt(frame.max)
238
+ ]));
239
+
240
+ } else if (frame.type === 'max_stream_data') {
241
+ parts.push(concatUint8Arrays([
242
+ new Uint8Array([0x11]),
243
+ writeVarInt(frame.id),
244
+ writeVarInt(frame.max)
245
+ ]));
246
+
247
+ } else if (frame.type === 'max_streams_bidi') {
248
+ parts.push(concatUint8Arrays([
249
+ new Uint8Array([0x12]),
250
+ writeVarInt(frame.max)
251
+ ]));
252
+
253
+ } else if (frame.type === 'max_streams_uni') {
254
+ parts.push(concatUint8Arrays([
255
+ new Uint8Array([0x13]),
256
+ writeVarInt(frame.max)
257
+ ]));
258
+
259
+ } else if (frame.type === 'connection_close') {
260
+ var code = frame.application ? 0x1d : 0x1c;
261
+ var errorCode = writeVarInt(frame.error || 0);
262
+ var frameType = frame.application ? new Uint8Array(0) : writeVarInt(frame.frameType || 0);
263
+ var reason = new TextEncoder().encode(frame.reason || '');
264
+ parts.push(concatUint8Arrays([
265
+ new Uint8Array([code]),
266
+ errorCode,
267
+ frameType,
268
+ writeVarInt(reason.length),
269
+ reason
270
+ ]));
271
+
272
+ } else if (frame.type === 'handshake_done') {
273
+ parts.push(new Uint8Array([0x1e]));
274
+
275
+ } else if (frame.type === 'new_connection_id') {
276
+ parts.push(concatUint8Arrays([
277
+ new Uint8Array([0x18]),
278
+ writeVarInt(frame.seq),
279
+ writeVarInt(frame.retire),
280
+ new Uint8Array([frame.connId.length]),
281
+ frame.connId,
282
+ frame.token
283
+ ]));
284
+
285
+ } else if (frame.type === 'retire_connection_id') {
286
+ parts.push(concatUint8Arrays([
287
+ new Uint8Array([0x19]),
288
+ writeVarInt(frame.seq)
289
+ ]));
290
+
291
+ } else if (frame.type === 'path_challenge' || frame.type === 'path_response') {
292
+ parts.push(concatUint8Arrays([
293
+ new Uint8Array([frame.type === 'path_challenge' ? 0x1a : 0x1b]),
294
+ frame.data
295
+ ]));
296
+
297
+ } else if (frame.type === 'reset_stream') {
298
+ parts.push(concatUint8Arrays([
299
+ new Uint8Array([0x04]),
300
+ writeVarInt(frame.id),
301
+ writeVarInt(frame.error),
302
+ writeVarInt(frame.finalSize)
303
+ ]));
304
+
305
+ } else if (frame.type === 'stop_sending') {
306
+ parts.push(concatUint8Arrays([
307
+ new Uint8Array([0x05]),
308
+ writeVarInt(frame.id),
309
+ writeVarInt(frame.error)
310
+ ]));
311
+
312
+ } else if (frame.type === 'data_blocked') {
313
+ parts.push(concatUint8Arrays([
314
+ new Uint8Array([0x14]),
315
+ writeVarInt(frame.limit)
316
+ ]));
317
+
318
+ } else if (frame.type === 'stream_data_blocked') {
319
+ parts.push(concatUint8Arrays([
320
+ new Uint8Array([0x15]),
321
+ writeVarInt(frame.id),
322
+ writeVarInt(frame.limit)
323
+ ]));
324
+
325
+ } 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.)
330
+ parts.push(concatUint8Arrays([
331
+ new Uint8Array([0x30]),
332
+ frame.data
333
+ ]));
334
+ }
335
+ }
336
+
337
+ return parts.length === 1 ? parts[0] : concatUint8Arrays(parts);
338
+ }
339
+
340
+
341
+ // ============================================================
342
+ // QUIC Frame parsing (RFC 9000 §12.4)
343
+ // ============================================================
344
+
345
+ function parse_quic_frames(buf) {
346
+ var offset = 0;
347
+ var frames = [];
348
+
349
+ function safeReadVarInt() {
350
+ if (offset >= buf.length) return null;
351
+ var res = readVarInt(buf, offset);
352
+ if (!res) return null;
353
+ offset += res.byteLength;
354
+ return res;
355
+ }
356
+
357
+ while (offset < buf.length) {
358
+ var type = buf[offset++];
359
+
360
+ // Multi-byte frame type
361
+ if (type >= 0x80) {
362
+ offset--;
363
+ var t = safeReadVarInt();
364
+ if (!t) break;
365
+ type = t.value;
366
+ }
367
+
368
+ if (type === 0x00) {
369
+ // PADDING — skip
370
+ } else if (type === 0x01) {
371
+ frames.push({ type: 'ping' });
372
+
373
+ } else if ((type & 0xfe) === 0x02) {
374
+ var hasECN = (type & 0x01) === 1;
375
+ var largest = safeReadVarInt(); if (!largest) break;
376
+ var delay = safeReadVarInt(); if (!delay) break;
377
+ var rangeCount = safeReadVarInt(); if (!rangeCount) break;
378
+ var firstRange = safeReadVarInt(); if (!firstRange) break;
379
+
380
+ var ranges = [];
381
+ for (var i = 0; i < rangeCount.value; i++) {
382
+ var gap = safeReadVarInt(); if (!gap) break;
383
+ var len = safeReadVarInt(); if (!len) break;
384
+ ranges.push({ gap: gap.value, length: len.value });
385
+ }
386
+
387
+ var ecn = null;
388
+ if (hasECN) {
389
+ var ect0 = safeReadVarInt(); if (!ect0) break;
390
+ var ect1 = safeReadVarInt(); if (!ect1) break;
391
+ var ce = safeReadVarInt(); if (!ce) break;
392
+ ecn = { ect0: ect0.value, ect1: ect1.value, ce: ce.value };
393
+ }
394
+
395
+ frames.push({
396
+ type: 'ack', largest: largest.value, delay: delay.value,
397
+ firstRange: firstRange.value, ranges: ranges, ecn: ecn
398
+ });
399
+
400
+ } else if (type === 0x04) {
401
+ var id = safeReadVarInt(); if (!id) break;
402
+ var error = safeReadVarInt(); if (!error) break;
403
+ var finalSize = safeReadVarInt(); if (!finalSize) break;
404
+ frames.push({ type: 'reset_stream', id: id.value, error: error.value, finalSize: finalSize.value });
405
+
406
+ } else if (type === 0x05) {
407
+ var id = safeReadVarInt(); if (!id) break;
408
+ var error = safeReadVarInt(); if (!error) break;
409
+ frames.push({ type: 'stop_sending', id: id.value, error: error.value });
410
+
411
+ } else if (type === 0x06) {
412
+ var off = safeReadVarInt(); if (!off) break;
413
+ var len = safeReadVarInt(); if (!len) break;
414
+ if (offset + len.value > buf.length) break;
415
+ var data = buf.slice(offset, offset + len.value); offset += len.value;
416
+ frames.push({ type: 'crypto', offset: off.value, data: data });
417
+
418
+ } else if (type === 0x07) {
419
+ var len = safeReadVarInt(); if (!len) break;
420
+ if (offset + len.value > buf.length) break;
421
+ var token = buf.slice(offset, offset + len.value); offset += len.value;
422
+ frames.push({ type: 'new_token', token: token });
423
+
424
+ } else if (type >= 0x08 && type <= 0x0f) {
425
+ var fin = !!(type & 0x01);
426
+ var lenb = !!(type & 0x02);
427
+ var offb = !!(type & 0x04);
428
+
429
+ var stream_id = safeReadVarInt(); if (!stream_id) break;
430
+ var offset_val = offb ? safeReadVarInt() : { value: 0 }; if (!offset_val) break;
431
+ var length_val = lenb ? safeReadVarInt() : { value: buf.length - offset }; if (!length_val) break;
432
+ if (offset + length_val.value > buf.length) break;
433
+
434
+ var data = buf.slice(offset, offset + length_val.value); offset += length_val.value;
435
+ frames.push({
436
+ type: 'stream', id: stream_id.value,
437
+ offset: offset_val.value, fin: fin, data: data
438
+ });
439
+
440
+ } else if (type === 0x10) {
441
+ var max = safeReadVarInt(); if (!max) break;
442
+ frames.push({ type: 'max_data', max: max.value });
443
+
444
+ } else if (type === 0x11) {
445
+ var id = safeReadVarInt(); if (!id) break;
446
+ var max = safeReadVarInt(); if (!max) break;
447
+ frames.push({ type: 'max_stream_data', id: id.value, max: max.value });
448
+
449
+ } else if (type === 0x12 || type === 0x13) {
450
+ var max = safeReadVarInt(); if (!max) break;
451
+ frames.push({ type: type === 0x12 ? 'max_streams_bidi' : 'max_streams_uni', max: max.value });
452
+
453
+ } else if (type === 0x14) {
454
+ var limit = safeReadVarInt(); if (!limit) break;
455
+ frames.push({ type: 'data_blocked', limit: limit.value });
456
+
457
+ } else if (type === 0x15) {
458
+ var id = safeReadVarInt(); if (!id) break;
459
+ var limit = safeReadVarInt(); if (!limit) break;
460
+ frames.push({ type: 'stream_data_blocked', id: id.value, limit: limit.value });
461
+
462
+ } else if (type === 0x16 || type === 0x17) {
463
+ var limit = safeReadVarInt(); if (!limit) break;
464
+ frames.push({ type: type === 0x16 ? 'streams_blocked_bidi' : 'streams_blocked_uni', limit: limit.value });
465
+
466
+ } else if (type === 0x18) {
467
+ var seq = safeReadVarInt(); if (!seq) break;
468
+ var retire = safeReadVarInt(); if (!retire) break;
469
+ if (offset >= buf.length) break;
470
+ var cidLen = buf[offset++];
471
+ if (offset + cidLen + 16 > buf.length) break;
472
+ var connId = buf.slice(offset, offset + cidLen); offset += cidLen;
473
+ var token = buf.slice(offset, offset + 16); offset += 16;
474
+ frames.push({ type: 'new_connection_id', seq: seq.value, retire: retire.value, connId: connId, token: token });
475
+
476
+ } else if (type === 0x19) {
477
+ var seq = safeReadVarInt(); if (!seq) break;
478
+ frames.push({ type: 'retire_connection_id', seq: seq.value });
479
+
480
+ } else if (type === 0x1a || type === 0x1b) {
481
+ if (offset + 8 > buf.length) break;
482
+ var data = buf.slice(offset, offset + 8); offset += 8;
483
+ frames.push({ type: type === 0x1a ? 'path_challenge' : 'path_response', data: data });
484
+
485
+ } else if (type === 0x1c || type === 0x1d) {
486
+ var error = safeReadVarInt(); if (!error) break;
487
+ var frameType = null;
488
+ if (type === 0x1c) {
489
+ var ft = safeReadVarInt(); if (!ft) break;
490
+ frameType = ft.value;
491
+ }
492
+ var reasonLen = safeReadVarInt(); if (!reasonLen) break;
493
+ if (offset + reasonLen.value > buf.length) break;
494
+ var reason = new TextDecoder().decode(buf.slice(offset, offset + reasonLen.value));
495
+ offset += reasonLen.value;
496
+ frames.push({
497
+ type: 'connection_close', application: type === 0x1d,
498
+ error: error.value, frameType: frameType, reason: reason
499
+ });
500
+
501
+ } else if (type === 0x1e) {
502
+ frames.push({ type: 'handshake_done' });
503
+
504
+ } else if (type === 0x30 || type === 0x31) {
505
+ // RFC 9221 DATAGRAM frame. The 0x31 form carries an explicit Length
506
+ // varint and the datagram data is exactly that many bytes; the 0x30 form
507
+ // has no Length and the data runs to the end of the packet (so a 0x30
508
+ // DATAGRAM must be the last frame). The optional field is a Length — not
509
+ // a "context ID" (that draft-era concept was removed before RFC 9221).
510
+ var data;
511
+ if (type === 0x31) {
512
+ var len = safeReadVarInt(); if (!len) break;
513
+ var end = offset + len.value;
514
+ if (end > buf.length) break; // truncated / invalid length
515
+ data = buf.slice(offset, end); offset = end;
516
+ } else {
517
+ data = buf.slice(offset); offset = buf.length;
518
+ }
519
+ frames.push({ type: 'datagram', data: data });
520
+
521
+ } else {
522
+ // Unknown frame — stop
523
+ frames.push({ type: 'unknown', frameType: type });
524
+ break;
525
+ }
526
+ }
527
+
528
+ return frames;
529
+ }
530
+
531
+
532
+ // ============================================================
533
+ // Transport Parameters (RFC 9000 §18)
534
+ // ============================================================
535
+
536
+ function build_transport_params(params) {
537
+ var out = [];
538
+
539
+ function addParam(id, value) {
540
+ var idBytes = writeVarInt(id);
541
+ var valueBytes;
542
+
543
+ if (typeof value === 'number') {
544
+ valueBytes = writeVarInt(value);
545
+ } else if (value instanceof Uint8Array) {
546
+ valueBytes = Array.from(value);
547
+ } else if (value === true) {
548
+ valueBytes = [];
549
+ } else {
550
+ throw new Error('Unsupported param value type for id ' + id);
551
+ }
552
+
553
+ var lengthBytes = writeVarInt(valueBytes.length);
554
+ out.push.apply(out, Array.from(idBytes));
555
+ out.push.apply(out, Array.from(lengthBytes));
556
+ out.push.apply(out, valueBytes);
557
+ }
558
+
559
+ if (params.original_destination_connection_id)
560
+ addParam(0x00, params.original_destination_connection_id);
561
+ if (params.max_idle_timeout)
562
+ addParam(0x01, params.max_idle_timeout);
563
+ if (params.stateless_reset_token)
564
+ addParam(0x02, params.stateless_reset_token);
565
+ if (params.max_udp_payload_size)
566
+ addParam(0x03, params.max_udp_payload_size);
567
+ if (params.initial_max_data)
568
+ addParam(0x04, params.initial_max_data);
569
+ if (params.initial_max_stream_data_bidi_local)
570
+ addParam(0x05, params.initial_max_stream_data_bidi_local);
571
+ if (params.initial_max_stream_data_bidi_remote)
572
+ addParam(0x06, params.initial_max_stream_data_bidi_remote);
573
+ if (params.initial_max_stream_data_uni)
574
+ addParam(0x07, params.initial_max_stream_data_uni);
575
+ if (params.initial_max_streams_bidi)
576
+ addParam(0x08, params.initial_max_streams_bidi);
577
+ if (params.initial_max_streams_uni)
578
+ addParam(0x09, params.initial_max_streams_uni);
579
+ if (params.ack_delay_exponent !== undefined)
580
+ addParam(0x0a, params.ack_delay_exponent);
581
+ if (params.max_ack_delay !== undefined)
582
+ addParam(0x0b, params.max_ack_delay);
583
+ if (params.disable_active_migration)
584
+ addParam(0x0c, true);
585
+ if (params.active_connection_id_limit)
586
+ addParam(0x0e, params.active_connection_id_limit);
587
+ if (params.initial_source_connection_id)
588
+ addParam(0x0f, params.initial_source_connection_id);
589
+ if (params.retry_source_connection_id)
590
+ addParam(0x10, params.retry_source_connection_id);
591
+ if (params.max_datagram_frame_size)
592
+ addParam(0x20, params.max_datagram_frame_size);
593
+
594
+ return new Uint8Array(out);
595
+ }
596
+
597
+
598
+ function parse_transport_params(buf, start) {
599
+ if (!(buf instanceof Uint8Array)) throw new Error('Expect Uint8Array');
600
+ var offset = start || 0;
601
+ var end = buf.length;
602
+ var out = {};
603
+
604
+ while (offset < end) {
605
+ var idVar = readVarInt(buf, offset);
606
+ if (!idVar) break;
607
+ offset += idVar.byteLength;
608
+
609
+ var lenVar = readVarInt(buf, offset);
610
+ if (!lenVar) break;
611
+ offset += lenVar.byteLength;
612
+
613
+ if (offset + lenVar.value > end) break;
614
+ var valueBytes = buf.slice(offset, offset + lenVar.value);
615
+ offset += lenVar.value;
616
+
617
+ var id = idVar.value;
618
+
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; }
622
+
623
+ switch (id) {
624
+ case 0x00: out.original_destination_connection_id = valueBytes; break;
625
+ 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;
628
+ case 0x04: out.initial_max_data = vi(valueBytes); break;
629
+ case 0x05: out.initial_max_stream_data_bidi_local = vi(valueBytes); break;
630
+ case 0x06: out.initial_max_stream_data_bidi_remote = vi(valueBytes); break;
631
+ 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;
640
+ case 0x20: out.max_datagram_frame_size = vi(valueBytes); break;
641
+ default:
642
+ if (!out.unknown) out.unknown = [];
643
+ out.unknown.push({ id: id, bytes: valueBytes });
644
+ }
645
+ }
646
+
647
+ return out;
648
+ }
649
+
650
+
651
+ // ============================================================
652
+ // Exports
653
+ // ============================================================
654
+
655
+ export {
656
+ // Datagram/packet parsing
657
+ parse_quic_datagram,
658
+ parse_quic_packet,
659
+
660
+ // Frame encode/decode
661
+ encode_quic_frames,
662
+ parse_quic_frames,
663
+
664
+ // Transport parameters
665
+ build_transport_params,
666
+ parse_transport_params
667
+ };