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/quic_socket.js
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
import dgram from 'node:dgram';
|
|
16
16
|
import { QUICConnection } from './quic_connection.js';
|
|
17
|
+
import lemonTls from 'lemon-tls';
|
|
18
|
+
import { SUPPORTED_VERSIONS, parseVersionList, versionName } from './quic_version.js';
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
/**
|
|
@@ -27,6 +29,26 @@ import { QUICConnection } from './quic_connection.js';
|
|
|
27
29
|
* opts:
|
|
28
30
|
* remoteIp, remotePort — where to send packets
|
|
29
31
|
* hostname — SNI / :authority for the QUIC/TLS handshake
|
|
32
|
+
* alpn — ALPN protocol(s) to offer. String or array.
|
|
33
|
+
* Defaults to ['h3']. Non-HTTP/3 consumers pass
|
|
34
|
+
* their own (e.g. ['hq-interop'] for HTTP/0.9,
|
|
35
|
+
* ['doq'] for DNS-over-QUIC).
|
|
36
|
+
* rejectUnauthorized — verify the server certificate (default true)
|
|
37
|
+
* ca — CA certificate(s) for verification
|
|
38
|
+
* idleTimeout, keepAlive — as QUICConnection's (the idle timeout in force
|
|
39
|
+
* is the smaller of the two sides', RFC 9000 §10.1)
|
|
40
|
+
* key, cert, passphrase — a client certificate, sent if the server asks
|
|
41
|
+
* for one (requestCert), as with tls.connect
|
|
42
|
+
* quicVersions — QUIC versions, the first one's first flight
|
|
43
|
+
* and the rest offered (default ['v1', 'v2'])
|
|
44
|
+
* token — a token from an earlier connection's
|
|
45
|
+
* 'new_token' event (NEW_TOKEN), used once
|
|
46
|
+
* session — a session saved from an earlier connection's
|
|
47
|
+
* 'session' event, to resume (tls.connect's name)
|
|
48
|
+
* onSocket(quic, socket) — called as soon as the QUICConnection exists,
|
|
49
|
+
* BEFORE the handshake starts. Use this to attach
|
|
50
|
+
* listeners that must observe the handshake, such
|
|
51
|
+
* as 'keylog'. onConnect is too late for those.
|
|
30
52
|
* onConnect(quic, socket) — called once the QUIC handshake completes
|
|
31
53
|
* onClose() — called when the connection closes (the socket is
|
|
32
54
|
* already closed by the time this runs)
|
|
@@ -36,6 +58,14 @@ import { QUICConnection } from './quic_connection.js';
|
|
|
36
58
|
* socket binds, so it is delivered via onConnect rather than returned here.
|
|
37
59
|
*/
|
|
38
60
|
function createQuicClientSocket(opts) {
|
|
61
|
+
// ecdhCurve (tls.connect's): the key-exchange groups to offer, in order —
|
|
62
|
+
// 'X25519MLKEM768:X25519' for the post-quantum hybrid first. Unset or
|
|
63
|
+
// 'auto': X25519, P-256, P-384. A name not implemented (or the hybrid in a
|
|
64
|
+
// Node without ML-KEM) throws here, as tls.connect's does.
|
|
65
|
+
var groups = lemonTls.parseEcdhCurve(opts.ecdhCurve);
|
|
66
|
+
// sigalgs (tls.connect's): the signature schemes to offer, in order; unset,
|
|
67
|
+
// lemon-tls's list. A name it does not know throws here.
|
|
68
|
+
var sigalgs = lemonTls.parseSigalgs(opts.sigalgs);
|
|
39
69
|
var isIPv6 = opts.remoteIp.indexOf(':') >= 0;
|
|
40
70
|
var udpSocket = dgram.createSocket(isIPv6 ? 'udp6' : 'udp4');
|
|
41
71
|
var quic = null;
|
|
@@ -49,25 +79,171 @@ function createQuicClientSocket(opts) {
|
|
|
49
79
|
});
|
|
50
80
|
|
|
51
81
|
udpSocket.bind(0, function () {
|
|
52
|
-
|
|
82
|
+
// Grow the kernel socket buffers before any traffic flows.
|
|
83
|
+
//
|
|
84
|
+
// Node's defaults are small (and notably small on Windows). When the send
|
|
85
|
+
// buffer overflows, the datagram is dropped locally with no error — it
|
|
86
|
+
// looks exactly like network loss, so the loss-recovery machinery kicks in
|
|
87
|
+
// and the connection crawls. This was the failure that killed the third
|
|
88
|
+
// pacer-calibration attempt (see LOSS_PTO_PLAN 7.4): larger bursts spilled
|
|
89
|
+
// over the buffer and turned a lossless link into a slow one.
|
|
90
|
+
//
|
|
91
|
+
// Wrapped in try/catch because the call can throw on platforms that cap
|
|
92
|
+
// the size; failing to enlarge the buffer is not a reason to fail the
|
|
93
|
+
// connection.
|
|
94
|
+
try { udpSocket.setSendBufferSize(2 * 1024 * 1024); } catch (e) {}
|
|
95
|
+
try { udpSocket.setRecvBufferSize(2 * 1024 * 1024); } catch (e) {}
|
|
53
96
|
|
|
54
|
-
|
|
97
|
+
// One attempt; after a Version Negotiation, a second in a version the
|
|
98
|
+
// server offered (RFC 9000 §6.2) — once.
|
|
99
|
+
start(null, null);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
function start(version, vnOffered) {
|
|
103
|
+
var q = quic = new QUICConnection({
|
|
104
|
+
isServer: false,
|
|
105
|
+
hostname: opts.hostname,
|
|
106
|
+
// Without this the client always offered 'h3' regardless of what the
|
|
107
|
+
// caller wanted, because TLSBridge falls back to ['h3'] when alpn is
|
|
108
|
+
// undefined. Normalize a bare string into an array here so callers can
|
|
109
|
+
// pass either form.
|
|
110
|
+
alpn: (function () {
|
|
111
|
+
var a = opts.alpn || ['h3'];
|
|
112
|
+
return Array.isArray(a) ? a : [a];
|
|
113
|
+
})(),
|
|
114
|
+
// Defaults to true. Pass false for self-signed certificates, or supply
|
|
115
|
+
// `ca` for a private CA.
|
|
116
|
+
rejectUnauthorized: opts.rejectUnauthorized !== false,
|
|
117
|
+
// A client certificate, for a server that asks (tls.connect's names).
|
|
118
|
+
key: opts.key || null, cert: opts.cert || null, passphrase: opts.passphrase,
|
|
119
|
+
ca: opts.ca || null,
|
|
120
|
+
session: opts.session || null,
|
|
121
|
+
requestOCSP: opts.requestOCSP === true,
|
|
122
|
+
// Encrypted Client Hello: the server's ECHConfigList, or GREASE ECH.
|
|
123
|
+
ech: opts.ech || null,
|
|
124
|
+
echGrease: opts.echGrease === true,
|
|
125
|
+
// The groups to offer (ecdhCurve, above); null: the default.
|
|
126
|
+
groups: groups,
|
|
127
|
+
sigalgs: sigalgs,
|
|
128
|
+
// A NEW_TOKEN token from an earlier connection to the server (RFC 9000
|
|
129
|
+
// §8.1.3), for the Initial packets.
|
|
130
|
+
token: opts.token || null,
|
|
131
|
+
// quic.remoteAddress / remotePort, as on a net.Socket.
|
|
132
|
+
remoteAddress: opts.remoteIp,
|
|
133
|
+
remotePort: opts.remotePort,
|
|
134
|
+
// Our transport parameters, where the caller set them (tls_bridge).
|
|
135
|
+
transportParams: opts.transportParams || null,
|
|
136
|
+
// The connection's own options: idleTimeout (ms; or
|
|
137
|
+
// transportParams.maxIdleTimeout) and keepAlive (true: PING at half the
|
|
138
|
+
// idle timeout in force; a number: every that many ms).
|
|
139
|
+
idleTimeout: opts.idleTimeout,
|
|
140
|
+
keepAlive: opts.keepAlive,
|
|
141
|
+
// The congestion window it starts with, where the caller set it.
|
|
142
|
+
initialCongestionWindow: opts.initialCongestionWindow,
|
|
143
|
+
// The connection's options (quic_connection's CONNECTION_OPTIONS).
|
|
144
|
+
startupCap: opts.startupCap,
|
|
145
|
+
pmtuDiscovery: opts.pmtuDiscovery,
|
|
146
|
+
maxUdpPayloadSize: opts.maxUdpPayloadSize,
|
|
147
|
+
initialRtt: opts.initialRtt,
|
|
148
|
+
reorderWindow: opts.reorderWindow,
|
|
149
|
+
// 0-RTT with `session` when its ticket allows it (RFC 9001 §4.6).
|
|
150
|
+
earlyData: opts.earlyData === true,
|
|
151
|
+
// The QUIC versions: the first flight's, then the others accepted
|
|
152
|
+
// (offered through version_information, RFC 9368). ['v1', 'v2'].
|
|
153
|
+
quicVersions: opts.quicVersions,
|
|
154
|
+
version: version || undefined,
|
|
155
|
+
versionNegotiation: vnOffered || undefined
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Hand the connection to the caller BEFORE connect() runs.
|
|
159
|
+
//
|
|
160
|
+
// onConnect fires after the handshake, which is too late for anything that
|
|
161
|
+
// has to observe the handshake itself. Key logging is the concrete case:
|
|
162
|
+
// lemon-tls skips building NSS lines while nothing is listening, and the
|
|
163
|
+
// handshake secrets are emitted during connect() — so a listener attached
|
|
164
|
+
// in onConnect receives nothing at all, and the client half of a capture
|
|
165
|
+
// is undecryptable. onSocket gives callers a hook at the only moment that
|
|
166
|
+
// works.
|
|
167
|
+
if (opts.onSocket) opts.onSocket(q, udpSocket);
|
|
168
|
+
|
|
169
|
+
q.on('packet', function (data) {
|
|
55
170
|
udpSocket.send(data, opts.remotePort, opts.remoteIp, function (err) {
|
|
56
171
|
if (err && opts.onError) opts.onError(err);
|
|
57
172
|
});
|
|
58
173
|
});
|
|
59
174
|
|
|
60
|
-
|
|
61
|
-
|
|
175
|
+
// A connection that never connects is a failure the caller must hear
|
|
176
|
+
// about, once: the TLS reason when there is one (a refused certificate,
|
|
177
|
+
// with node:tls's code), the handshake timeout, or — when the peer simply
|
|
178
|
+
// closed — its CONNECTION_CLOSE. Before this a refused certificate closed
|
|
179
|
+
// the connection in silence, and an HTTP/3 request or a WebTransport
|
|
180
|
+
// `ready` waited on it forever.
|
|
181
|
+
var connected = false, failed = false;
|
|
182
|
+
function fail(err) {
|
|
183
|
+
if (connected || failed) return;
|
|
184
|
+
failed = true;
|
|
185
|
+
if (opts.onError) opts.onError(err);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
q.on('error', function (err) { if (q === quic) fail(err); });
|
|
189
|
+
|
|
190
|
+
q.on('connect', function () {
|
|
191
|
+
connected = true;
|
|
192
|
+
if (opts.onConnect) opts.onConnect(q, udpSocket);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// 0-RTT: the connection may carry application data now, before the
|
|
196
|
+
// handshake — onEarly(quic, socket). Refused by the server, what went is
|
|
197
|
+
// forgotten and 'early_rejected' says so (send it again after onConnect).
|
|
198
|
+
q.on('early', function () { if (opts.onEarly) opts.onEarly(q, udpSocket); });
|
|
199
|
+
|
|
200
|
+
// The server speaks none of the versions offered in this attempt: a new
|
|
201
|
+
// one, in the first of ours it offers. A Version Negotiation that
|
|
202
|
+
// answers the second attempt is not followed (§6.2: once).
|
|
203
|
+
q.on('version_negotiation', function (offered) {
|
|
204
|
+
if (vnOffered) return;
|
|
205
|
+
var prefs = opts.quicVersions ? parseVersionList(opts.quicVersions, 'quicVersions') : SUPPORTED_VERSIONS;
|
|
206
|
+
var pick = null;
|
|
207
|
+
for (var i = 0; i < prefs.length && pick === null; i++) if (offered.indexOf(prefs[i]) >= 0) pick = prefs[i];
|
|
208
|
+
if (pick === null) {
|
|
209
|
+
var err = new Error('the server supports none of our QUIC versions (it offers ' + offered.map(versionName).join(', ') + ')');
|
|
210
|
+
err.code = 'ERR_QUIC_VERSION_NEGOTIATION';
|
|
211
|
+
fail(err);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
start(pick, offered);
|
|
62
215
|
});
|
|
63
216
|
|
|
64
|
-
|
|
217
|
+
function closedError() {
|
|
218
|
+
var pc = quic.context && quic.context.peer_close;
|
|
219
|
+
var lc = !pc && quic.context && quic.context.close_frame;
|
|
220
|
+
var err = new Error('QUIC connection closed before the handshake completed' +
|
|
221
|
+
(pc ? ' (peer error 0x' + pc.error.toString(16) + (pc.reason ? ': ' + pc.reason : '') + ')' :
|
|
222
|
+
lc ? ' (error 0x' + lc.error.toString(16) + (lc.reason ? ': ' + lc.reason : '') + ')' : ''));
|
|
223
|
+
err.code = 'ERR_QUIC_HANDSHAKE_FAILED';
|
|
224
|
+
return err;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// A handshake that will not complete is reported when that is known —
|
|
228
|
+
// the peer's CONNECTION_CLOSE, or ours — not when the connection's drain
|
|
229
|
+
// (RFC 9000 §10.2, up to 3 s) ends and 'close' comes: a server that
|
|
230
|
+
// refused the ClientHello was heard 3 s late, and an HTTP/3 request
|
|
231
|
+
// waited that long to fail. On the next turn: a TLS alert of ours closes
|
|
232
|
+
// the connection first and gives its reason ('error') right after.
|
|
233
|
+
q.on('handshake_ended', function (confirmed) {
|
|
234
|
+
if (confirmed || q !== quic) return;
|
|
235
|
+
setImmediate(function () { if (q === quic && !connected) fail(closedError()); });
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
q.on('close', function () {
|
|
239
|
+
if (q !== quic) return; // an attempt given up for another
|
|
65
240
|
try { udpSocket.close(); } catch (e) {}
|
|
241
|
+
if (!connected) fail(closedError());
|
|
66
242
|
if (opts.onClose) opts.onClose();
|
|
67
243
|
});
|
|
68
244
|
|
|
69
|
-
|
|
70
|
-
}
|
|
245
|
+
q.connect();
|
|
246
|
+
}
|
|
71
247
|
|
|
72
248
|
return udpSocket;
|
|
73
249
|
}
|
|
@@ -0,0 +1,143 @@
|
|
|
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
|
+
|
|
16
|
+
// ============================================================
|
|
17
|
+
// QUIC versions — what differs between them, in one place
|
|
18
|
+
//
|
|
19
|
+
// QUIC v1 (RFC 9000/9001) and v2 (RFC 9369) are one protocol: the same
|
|
20
|
+
// frames, the same handshake, the same recovery. v2 changes what an
|
|
21
|
+
// on-path device could have learned to depend on — the version number, the
|
|
22
|
+
// salt the Initial keys come from, the labels every packet key is derived
|
|
23
|
+
// with, the Retry integrity key, and which long-header type bits mean
|
|
24
|
+
// Initial, 0-RTT, Handshake and Retry — so that QUIC stays free to change
|
|
25
|
+
// (anti-ossification). Everything a version decides lives in its profile
|
|
26
|
+
// here, and every place that builds or reads a packet, or derives a key,
|
|
27
|
+
// asks the connection's profile: the rest of the code is the same for all
|
|
28
|
+
// versions, as lemon-tls's is for TLS 1.2 and 1.3.
|
|
29
|
+
//
|
|
30
|
+
// A new version is a profile here, and its entry in COMPATIBLE when a first
|
|
31
|
+
// flight in one can be read as the other (RFC 9368 §2.3).
|
|
32
|
+
// ============================================================
|
|
33
|
+
|
|
34
|
+
var QUIC_V1 = 0x00000001;
|
|
35
|
+
var QUIC_V2 = 0x6b3343cf;
|
|
36
|
+
|
|
37
|
+
function hex(s) { return new Uint8Array(Buffer.from(s, 'hex')); }
|
|
38
|
+
|
|
39
|
+
var PROFILES = {};
|
|
40
|
+
|
|
41
|
+
PROFILES[QUIC_V1] = {
|
|
42
|
+
version: QUIC_V1,
|
|
43
|
+
name: 'v1',
|
|
44
|
+
// RFC 9001 §5.2
|
|
45
|
+
initialSalt: hex('38762cf7f55934b34d179ae6a4c80cadccbb7f0a'),
|
|
46
|
+
// RFC 9001 §5.1, §6.1
|
|
47
|
+
labels: { key: 'quic key', iv: 'quic iv', hp: 'quic hp', ku: 'quic ku' },
|
|
48
|
+
// RFC 9001 §5.8
|
|
49
|
+
retryKey: hex('be0c690b9f66575a1d766b54e368c84e'),
|
|
50
|
+
retryNonce: hex('461599d35d632bf2239825bb'),
|
|
51
|
+
// RFC 9000 §17.2: long header type bits → packet type
|
|
52
|
+
longTypes: ['initial', '0rtt', 'handshake', 'retry']
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
PROFILES[QUIC_V2] = {
|
|
56
|
+
version: QUIC_V2,
|
|
57
|
+
name: 'v2',
|
|
58
|
+
// RFC 9369 §3.3.1
|
|
59
|
+
initialSalt: hex('0dede3def700a6db819381be6e269dcbf9bd2ed9'),
|
|
60
|
+
// RFC 9369 §3.3.2
|
|
61
|
+
labels: { key: 'quicv2 key', iv: 'quicv2 iv', hp: 'quicv2 hp', ku: 'quicv2 ku' },
|
|
62
|
+
// RFC 9369 §3.3.3
|
|
63
|
+
retryKey: hex('8fb4b01b56ac48e260fbcbcead7ccc92'),
|
|
64
|
+
retryNonce: hex('d86969bc2d7c6d9990efb04a'),
|
|
65
|
+
// RFC 9369 §3.2: Retry 0b00, Initial 0b01, 0-RTT 0b10, Handshake 0b11
|
|
66
|
+
longTypes: ['retry', 'initial', '0rtt', 'handshake']
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Which first flights can be read as another version's (RFC 9368 §2.3):
|
|
70
|
+
// v1 and v2 differ in nothing a ClientHello carries, both ways (RFC 9369 §4).
|
|
71
|
+
var COMPATIBLE = {};
|
|
72
|
+
COMPATIBLE[QUIC_V1] = [QUIC_V2];
|
|
73
|
+
COMPATIBLE[QUIC_V2] = [QUIC_V1];
|
|
74
|
+
|
|
75
|
+
/** The versions this implementation speaks, in its default preference. */
|
|
76
|
+
var SUPPORTED_VERSIONS = [QUIC_V1, QUIC_V2];
|
|
77
|
+
|
|
78
|
+
function versionProfile(version) {
|
|
79
|
+
return PROFILES[version >>> 0] || null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isSupportedVersion(version) {
|
|
83
|
+
return !!PROFILES[version >>> 0];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Whether a first flight in `from` can be taken as one in `to`. */
|
|
87
|
+
function isCompatible(from, to) {
|
|
88
|
+
from >>>= 0; to >>>= 0;
|
|
89
|
+
return from === to || (COMPATIBLE[from] || []).indexOf(to) >= 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Long header type bits → 'initial' | '0rtt' | 'handshake' | 'retry', or null. */
|
|
93
|
+
function longPacketType(version, typeBits) {
|
|
94
|
+
var p = PROFILES[version >>> 0];
|
|
95
|
+
return p ? p.longTypes[typeBits & 3] : null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Packet type → its long header type bits in `version`. */
|
|
99
|
+
function longTypeBits(version, packetType) {
|
|
100
|
+
var p = PROFILES[version >>> 0];
|
|
101
|
+
var i = p ? p.longTypes.indexOf(packetType) : -1;
|
|
102
|
+
if (i < 0) throw new Error('no long header type ' + packetType + ' in QUIC version 0x' + (version >>> 0).toString(16));
|
|
103
|
+
return i;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* A version as an application names it — 1, 0x6b3343cf, 'v1', 'v2' — as a
|
|
108
|
+
* version number; throws on one this implementation does not speak.
|
|
109
|
+
*/
|
|
110
|
+
function parseVersion(v, what) {
|
|
111
|
+
var n = null;
|
|
112
|
+
if (typeof v === 'number' && Number.isInteger(v)) n = v >>> 0;
|
|
113
|
+
else if (typeof v === 'string') {
|
|
114
|
+
var s = v.toLowerCase();
|
|
115
|
+
for (var k in PROFILES) if (PROFILES[k].name === s) n = PROFILES[k].version;
|
|
116
|
+
}
|
|
117
|
+
if (n === null || !PROFILES[n]) throw new TypeError((what || 'quicVersions') + ': unsupported QUIC version ' + JSON.stringify(v) + ' (v1 or v2)');
|
|
118
|
+
return n;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** An option listing versions in preference order → [number, …]. */
|
|
122
|
+
function parseVersionList(list, what) {
|
|
123
|
+
if (list === undefined || list === null) return null;
|
|
124
|
+
if (!Array.isArray(list) || list.length === 0) throw new TypeError((what || 'quicVersions') + ' must be a non-empty array of QUIC versions');
|
|
125
|
+
var out = [];
|
|
126
|
+
for (var i = 0; i < list.length; i++) {
|
|
127
|
+
var n = parseVersion(list[i], what);
|
|
128
|
+
if (out.indexOf(n) < 0) out.push(n);
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function versionName(version) {
|
|
134
|
+
var p = PROFILES[version >>> 0];
|
|
135
|
+
return p ? p.name : '0x' + (version >>> 0).toString(16);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export {
|
|
139
|
+
QUIC_V1, QUIC_V2, SUPPORTED_VERSIONS,
|
|
140
|
+
versionProfile, isSupportedVersion, isCompatible,
|
|
141
|
+
longPacketType, longTypeBits,
|
|
142
|
+
parseVersion, parseVersionList, versionName
|
|
143
|
+
};
|