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