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/README.md +1768 -155
- package/index.js +55 -36
- package/package.json +3 -3
- package/src/agent.js +306 -36
- package/src/client.js +447 -120
- package/src/client_profile.js +266 -0
- package/src/connect_udp.js +578 -0
- package/src/connect_udp_client.js +770 -0
- package/src/crypto.js +240 -140
- package/src/h1.js +953 -0
- package/src/h1_client.js +731 -0
- package/src/h1_server.js +624 -0
- package/src/h2.js +1325 -0
- package/src/h2_client.js +675 -0
- package/src/h2_fingerprint.js +403 -0
- package/src/h2_frames.js +582 -0
- package/src/h2_server.js +862 -0
- package/src/h3.js +1966 -469
- package/src/h3_client.js +467 -48
- package/src/h3_server.js +1349 -94
- package/src/hpack.js +455 -0
- package/src/hpack_primitives.js +520 -0
- package/src/http_common.js +901 -0
- package/src/ja4h.js +256 -0
- package/src/qpack.js +555 -0
- package/src/quic_connection.js +6517 -657
- package/src/quic_server.js +768 -73
- package/src/quic_socket.js +134 -9
- package/src/quic_version.js +143 -0
- package/src/server.js +843 -115
- package/src/server_stats.js +93 -0
- package/src/streams.js +361 -35
- package/src/tcp_listener.js +557 -0
- package/src/tls_bridge.js +474 -73
- package/src/tls_options.js +108 -0
- package/src/transport.js +455 -46
- package/src/utils.js +94 -12
- package/src/webtransport.js +433 -436
- package/src/ws.js +624 -0
- package/src/ws_client.js +641 -0
- package/src/ws_server.js +833 -0
- package/src/wt_stream.js +618 -0
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
415
|
-
//
|
|
416
|
-
//
|
|
417
|
-
//
|
|
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([
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
708
|
-
|
|
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:
|
|
715
|
-
|
|
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:
|
|
721
|
-
case 0x09:
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
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
|
};
|