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/utils.js
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
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
|
+
// Emitter — lightweight event emitter (single shared copy)
|
|
18
|
+
// ============================================================
|
|
19
|
+
|
|
20
|
+
function Emitter() {
|
|
21
|
+
var listeners = {};
|
|
22
|
+
return {
|
|
23
|
+
on: function (name, fn) {
|
|
24
|
+
(listeners[name] = listeners[name] || []).push(fn);
|
|
25
|
+
},
|
|
26
|
+
off: function (name, fn) {
|
|
27
|
+
var arr = listeners[name];
|
|
28
|
+
if (arr) {
|
|
29
|
+
var idx = arr.indexOf(fn);
|
|
30
|
+
if (idx !== -1) arr.splice(idx, 1);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
emit: function (name) {
|
|
34
|
+
var args = Array.prototype.slice.call(arguments, 1);
|
|
35
|
+
var arr = listeners[name] || [];
|
|
36
|
+
for (var i = 0; i < arr.length; i++) {
|
|
37
|
+
try { arr[i].apply(null, args); } catch (e) { /* swallow */ }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
// ============================================================
|
|
45
|
+
// QUIC Variable-Length Integer (RFC 9000 §16)
|
|
46
|
+
// ============================================================
|
|
47
|
+
|
|
48
|
+
function writeVarInt(value) {
|
|
49
|
+
if (value < 0x40) {
|
|
50
|
+
return new Uint8Array([value]);
|
|
51
|
+
}
|
|
52
|
+
if (value < 0x4000) {
|
|
53
|
+
return new Uint8Array([
|
|
54
|
+
0x40 | (value >> 8),
|
|
55
|
+
value & 0xff
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
if (value < 0x40000000) {
|
|
59
|
+
return new Uint8Array([
|
|
60
|
+
0x80 | (value >> 24),
|
|
61
|
+
(value >> 16) & 0xff,
|
|
62
|
+
(value >> 8) & 0xff,
|
|
63
|
+
value & 0xff
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
if (value <= Number.MAX_SAFE_INTEGER) {
|
|
67
|
+
var hi = Math.floor(value / 2 ** 32);
|
|
68
|
+
var lo = value >>> 0;
|
|
69
|
+
return new Uint8Array([
|
|
70
|
+
0xc0 | (hi >> 24),
|
|
71
|
+
(hi >> 16) & 0xff,
|
|
72
|
+
(hi >> 8) & 0xff,
|
|
73
|
+
hi & 0xff,
|
|
74
|
+
(lo >> 24) & 0xff,
|
|
75
|
+
(lo >> 16) & 0xff,
|
|
76
|
+
(lo >> 8) & 0xff,
|
|
77
|
+
lo & 0xff
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
throw new Error('Value too large for QUIC VarInt');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
function readVarInt(array, offset) {
|
|
85
|
+
if (offset >= array.length) return null;
|
|
86
|
+
|
|
87
|
+
var first = array[offset];
|
|
88
|
+
var prefix = first >> 6;
|
|
89
|
+
|
|
90
|
+
if (prefix === 0) {
|
|
91
|
+
return { value: first & 0x3f, byteLength: 1 };
|
|
92
|
+
}
|
|
93
|
+
if (prefix === 1) {
|
|
94
|
+
if (offset + 1 >= array.length) return null;
|
|
95
|
+
return {
|
|
96
|
+
value: ((first & 0x3f) << 8) | array[offset + 1],
|
|
97
|
+
byteLength: 2
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (prefix === 2) {
|
|
101
|
+
if (offset + 3 >= array.length) return null;
|
|
102
|
+
return {
|
|
103
|
+
value: (
|
|
104
|
+
((first & 0x3f) << 24) |
|
|
105
|
+
(array[offset + 1] << 16) |
|
|
106
|
+
(array[offset + 2] << 8) |
|
|
107
|
+
array[offset + 3]
|
|
108
|
+
) >>> 0,
|
|
109
|
+
byteLength: 4
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (prefix === 3) {
|
|
113
|
+
if (offset + 7 >= array.length) return null;
|
|
114
|
+
var hi = (
|
|
115
|
+
((first & 0x3f) << 24) |
|
|
116
|
+
(array[offset + 1] << 16) |
|
|
117
|
+
(array[offset + 2] << 8) |
|
|
118
|
+
array[offset + 3]
|
|
119
|
+
) >>> 0;
|
|
120
|
+
var lo = (
|
|
121
|
+
(array[offset + 4] << 24) |
|
|
122
|
+
(array[offset + 5] << 16) |
|
|
123
|
+
(array[offset + 6] << 8) |
|
|
124
|
+
array[offset + 7]
|
|
125
|
+
) >>> 0;
|
|
126
|
+
var full = BigInt(hi) * 4294967296n + BigInt(lo);
|
|
127
|
+
if (full <= BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
128
|
+
return { value: Number(full), byteLength: 8 };
|
|
129
|
+
}
|
|
130
|
+
return { value: full, byteLength: 8 };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
// ============================================================
|
|
138
|
+
// Binary helpers
|
|
139
|
+
// ============================================================
|
|
140
|
+
|
|
141
|
+
function concatUint8Arrays(arrays) {
|
|
142
|
+
var totalLength = 0;
|
|
143
|
+
for (var i = 0; i < arrays.length; i++) {
|
|
144
|
+
totalLength += arrays[i].length;
|
|
145
|
+
}
|
|
146
|
+
var result = new Uint8Array(totalLength);
|
|
147
|
+
var offset = 0;
|
|
148
|
+
for (var i = 0; i < arrays.length; i++) {
|
|
149
|
+
result.set(arrays[i], offset);
|
|
150
|
+
offset += arrays[i].length;
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
function uint8Equal(a, b) {
|
|
157
|
+
if (a === b) return true;
|
|
158
|
+
if (!a || !b) return false;
|
|
159
|
+
if (a.byteLength !== b.byteLength) return false;
|
|
160
|
+
for (var i = 0; i < a.byteLength; i++) {
|
|
161
|
+
if (a[i] !== b[i]) return false;
|
|
162
|
+
}
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
// ============================================================
|
|
168
|
+
// ACK range helpers (flat [from,to, from,to, ...] format)
|
|
169
|
+
// ============================================================
|
|
170
|
+
|
|
171
|
+
function ack_frame_to_ranges(ackFrame) {
|
|
172
|
+
var flat = [];
|
|
173
|
+
if (!ackFrame || ackFrame.type !== 'ack') return flat;
|
|
174
|
+
|
|
175
|
+
var largest = Number(ackFrame.largest);
|
|
176
|
+
var firstRange = Number(ackFrame.firstRange);
|
|
177
|
+
if (!isFinite(largest) || !isFinite(firstRange)) return flat;
|
|
178
|
+
|
|
179
|
+
var rangesDesc = [];
|
|
180
|
+
|
|
181
|
+
// First range
|
|
182
|
+
var rangeEnd = largest;
|
|
183
|
+
var rangeStart = rangeEnd - firstRange;
|
|
184
|
+
if (rangeStart <= rangeEnd) {
|
|
185
|
+
rangesDesc.push([rangeStart, rangeEnd]);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Additional ranges
|
|
189
|
+
var more = ackFrame.ranges || [];
|
|
190
|
+
var prevStart = rangeStart;
|
|
191
|
+
for (var i = 0; i < more.length; i++) {
|
|
192
|
+
var gap = Number(more[i].gap);
|
|
193
|
+
var length = Number(more[i].length);
|
|
194
|
+
if (!isFinite(gap) || !isFinite(length)) continue;
|
|
195
|
+
|
|
196
|
+
rangeEnd = prevStart - gap - 2;
|
|
197
|
+
rangeStart = rangeEnd - length;
|
|
198
|
+
if (rangeStart <= rangeEnd) {
|
|
199
|
+
rangesDesc.push([rangeStart, rangeEnd]);
|
|
200
|
+
prevStart = rangeStart;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Sort ascending, merge, and flatten
|
|
205
|
+
rangesDesc.sort(function (a, b) { return a[0] - b[0]; });
|
|
206
|
+
|
|
207
|
+
var merged = [];
|
|
208
|
+
for (var j = 0; j < rangesDesc.length; j++) {
|
|
209
|
+
var s = rangesDesc[j][0], e = rangesDesc[j][1];
|
|
210
|
+
if (merged.length === 0) {
|
|
211
|
+
merged.push([s, e]);
|
|
212
|
+
} else {
|
|
213
|
+
var last = merged[merged.length - 1];
|
|
214
|
+
if (s <= last[1] + 1) {
|
|
215
|
+
if (e > last[1]) last[1] = e;
|
|
216
|
+
} else {
|
|
217
|
+
merged.push([s, e]);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
for (var k = 0; k < merged.length; k++) {
|
|
223
|
+
flat.push(merged[k][0], merged[k][1]);
|
|
224
|
+
}
|
|
225
|
+
return flat;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
function ranges_to_ack_frame(flatRanges, ecnStats, ackDelay) {
|
|
230
|
+
if (!flatRanges || flatRanges.length === 0) return null;
|
|
231
|
+
if (flatRanges.length % 2 !== 0) throw new Error('flatRanges must be [from,to,...] pairs');
|
|
232
|
+
|
|
233
|
+
// flat_ranges uses exclusive end: [from, to) → convert to inclusive [start, end]
|
|
234
|
+
var ranges = [];
|
|
235
|
+
for (var i = 0; i < flatRanges.length; i += 2) {
|
|
236
|
+
if (flatRanges[i + 1] > flatRanges[i]) {
|
|
237
|
+
ranges.push({ start: flatRanges[i], end: flatRanges[i + 1] - 1 });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (ranges.length === 0) return null;
|
|
242
|
+
|
|
243
|
+
ranges.sort(function (a, b) { return b.end - a.end; });
|
|
244
|
+
|
|
245
|
+
// Merge overlapping/adjacent
|
|
246
|
+
var merged = [ranges[0]];
|
|
247
|
+
for (var i = 1; i < ranges.length; i++) {
|
|
248
|
+
var last = merged[merged.length - 1];
|
|
249
|
+
var curr = ranges[i];
|
|
250
|
+
if (curr.end >= last.start - 1) {
|
|
251
|
+
last.start = Math.min(last.start, curr.start);
|
|
252
|
+
} else {
|
|
253
|
+
merged.push(curr);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
var largest = merged[0].end;
|
|
258
|
+
var firstRange = largest - merged[0].start;
|
|
259
|
+
var ackRanges = [];
|
|
260
|
+
|
|
261
|
+
for (var i = 1; i < merged.length; i++) {
|
|
262
|
+
var gap = merged[i - 1].start - merged[i].end - 2;
|
|
263
|
+
var length = merged[i].end - merged[i].start;
|
|
264
|
+
ackRanges.push({ gap: gap, length: length });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
type: 'ack',
|
|
269
|
+
largest: largest,
|
|
270
|
+
delay: ackDelay || 0,
|
|
271
|
+
firstRange: firstRange,
|
|
272
|
+
ranges: ackRanges,
|
|
273
|
+
ecn: ecnStats ? {
|
|
274
|
+
ect0: ecnStats.ect0 || 0,
|
|
275
|
+
ect1: ecnStats.ect1 || 0,
|
|
276
|
+
ce: ecnStats.ce || 0
|
|
277
|
+
} : null
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
export {
|
|
283
|
+
Emitter,
|
|
284
|
+
writeVarInt,
|
|
285
|
+
readVarInt,
|
|
286
|
+
concatUint8Arrays,
|
|
287
|
+
uint8Equal,
|
|
288
|
+
ack_frame_to_ranges,
|
|
289
|
+
ranges_to_ack_frame
|
|
290
|
+
};
|