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/README.md +2489 -147
- package/index.js +56 -34
- package/package.json +3 -3
- package/src/agent.js +364 -36
- package/src/client.js +474 -118
- 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 +257 -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 +476 -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 +6666 -632
- package/src/quic_server.js +822 -69
- package/src/quic_socket.js +183 -7
- package/src/quic_version.js +143 -0
- package/src/server.js +843 -115
- package/src/server_stats.js +93 -0
- package/src/streams.js +376 -36
- package/src/tcp_listener.js +557 -0
- package/src/tls_bridge.js +516 -75
- package/src/tls_options.js +108 -0
- package/src/transport.js +541 -39
- 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;
|
|
@@ -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
|
-
|
|
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
|
-
//
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
//
|
|
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([
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
620
|
-
|
|
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:
|
|
627
|
-
|
|
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:
|
|
633
|
-
case 0x09:
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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
|
};
|