quico 0.1.3 → 0.3.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 +49 -23
- package/package.json +83 -60
- package/src/agent.js +283 -0
- package/src/client.js +444 -0
- package/src/crypto.js +534 -0
- package/src/h3.js +920 -0
- package/src/h3_client.js +332 -0
- package/src/h3_server.js +392 -0
- package/src/quic_connection.js +2338 -0
- package/src/quic_server.js +314 -0
- package/src/quic_socket.js +76 -0
- package/src/server.js +354 -0
- package/src/streams.js +471 -0
- package/src/tls_bridge.js +273 -0
- package/src/transport.js +667 -0
- package/src/utils.js +345 -0
- package/src/webtransport.js +655 -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
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<title>WebTransport Datagram Echo Client</title>
|
|
6
|
-
</head>
|
|
7
|
-
<body>
|
|
8
|
-
<h1>WebTransport Datagram Echo</h1>
|
|
9
|
-
<button id="sendBtn">Send "Hello QUIC"</button>
|
|
10
|
-
<pre id="log"></pre>
|
|
11
|
-
|
|
12
|
-
<script>
|
|
13
|
-
const log = (msg) => {
|
|
14
|
-
console.log(msg);
|
|
15
|
-
document.getElementById('log').textContent += msg + '\n';
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
let transport;
|
|
19
|
-
let writer;
|
|
20
|
-
|
|
21
|
-
async function connect() {
|
|
22
|
-
try {
|
|
23
|
-
transport = new WebTransport('https://localhost:4433/');
|
|
24
|
-
await transport.ready;
|
|
25
|
-
log('✅ Connected to WebTransport server');
|
|
26
|
-
|
|
27
|
-
// שליחה
|
|
28
|
-
writer = transport.datagrams.writable.getWriter();
|
|
29
|
-
|
|
30
|
-
// קבלה
|
|
31
|
-
const reader = transport.datagrams.readable.getReader();
|
|
32
|
-
(async function readLoop() {
|
|
33
|
-
while (true) {
|
|
34
|
-
const { value, done } = await reader.read();
|
|
35
|
-
if (done) break;
|
|
36
|
-
const text = new TextDecoder().decode(value);
|
|
37
|
-
log('📥 Received: ' + text);
|
|
38
|
-
}
|
|
39
|
-
})();
|
|
40
|
-
} catch (err) {
|
|
41
|
-
log('❌ Connection failed: ' + err);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
document.getElementById('sendBtn').addEventListener('click', async () => {
|
|
46
|
-
if (!writer) {
|
|
47
|
-
log('Not connected yet...');
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
const msg = 'Hello QUIC';
|
|
51
|
-
await writer.write(new TextEncoder().encode(msg));
|
|
52
|
-
log('📤 Sent: ' + msg);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
connect();
|
|
56
|
-
</script>
|
|
57
|
-
</body>
|
|
58
|
-
</html>
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import quico from 'quico';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import tls from 'lemon-tls';
|
|
4
|
-
|
|
5
|
-
var server = quico.createServer({
|
|
6
|
-
SNICallback: function (servername, cb) {
|
|
7
|
-
console.log('Getting certificate for:', servername);
|
|
8
|
-
cb(null, tls.createSecureContext({
|
|
9
|
-
key: fs.readFileSync('./certs/localhost2.key'),
|
|
10
|
-
cert: fs.readFileSync('./certs/localhost2.crt')
|
|
11
|
-
}));
|
|
12
|
-
}
|
|
13
|
-
},function(req,res){
|
|
14
|
-
if (req.headers[':protocol'] === 'webtransport') {
|
|
15
|
-
res.writeHead(200);
|
|
16
|
-
|
|
17
|
-
//TODO
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
server.listen(4433, function() {
|
|
22
|
-
console.log('QUIC server running on port 4433');
|
|
23
|
-
});
|
package/h3_server.js
DELETED
|
@@ -1,416 +0,0 @@
|
|
|
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
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
15
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import process from 'node:process';
|
|
20
|
-
import dgram from 'node:dgram';
|
|
21
|
-
import crypto from 'node:crypto';
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
import { createRequire } from 'node:module';
|
|
25
|
-
const require = createRequire(import.meta.url);
|
|
26
|
-
const flat_ranges = require('flat-ranges');
|
|
27
|
-
|
|
28
|
-
// ---- local modules (ESM) ----
|
|
29
|
-
import {
|
|
30
|
-
decrypt_quic_packet,
|
|
31
|
-
quic_derive_init_secrets,
|
|
32
|
-
quic_derive_from_tls_secrets,
|
|
33
|
-
build_quic_ext,
|
|
34
|
-
hkdf_expand_label,
|
|
35
|
-
encode_quic_frames,
|
|
36
|
-
encrypt_quic_packet,
|
|
37
|
-
parse_quic_datagram,
|
|
38
|
-
parse_quic_packet,
|
|
39
|
-
parse_quic_frames,
|
|
40
|
-
extract_tls_messages_from_chunks,
|
|
41
|
-
build_alpn_ext,
|
|
42
|
-
parse_transport_parameters
|
|
43
|
-
} from './libs/crypto.js';
|
|
44
|
-
|
|
45
|
-
import QUICSocket from './quic_socket.js';
|
|
46
|
-
import H3Socket from './h3_socket.js';
|
|
47
|
-
|
|
48
|
-
function Emitter(){
|
|
49
|
-
var listeners = {};
|
|
50
|
-
return {
|
|
51
|
-
on: function(name, fn){ (listeners[name] = listeners[name] || []).push(fn); },
|
|
52
|
-
emit: function(name){
|
|
53
|
-
var args = Array.prototype.slice.call(arguments, 1);
|
|
54
|
-
var arr = listeners[name] || [];
|
|
55
|
-
for (var i=0;i<arr.length;i++){ try{ arr[i].apply(null, args); }catch(e){} }
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
function createServer(options, handler){
|
|
63
|
-
options = options || {};
|
|
64
|
-
|
|
65
|
-
var ev = Emitter();
|
|
66
|
-
|
|
67
|
-
var context = {
|
|
68
|
-
udp4: null,
|
|
69
|
-
udp6: null,
|
|
70
|
-
port: null,
|
|
71
|
-
_handler: handler || null,
|
|
72
|
-
SNICallback: options.SNICallback || null,
|
|
73
|
-
connections: {},
|
|
74
|
-
address_binds: {},
|
|
75
|
-
timeout: null,
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
function set_http_stream(quic_connection_id,stream_id,params){
|
|
81
|
-
|
|
82
|
-
var is_new=false;
|
|
83
|
-
|
|
84
|
-
if(stream_id in context.connections[quic_connection_id].http_streams==false){
|
|
85
|
-
|
|
86
|
-
var req={
|
|
87
|
-
method: null,
|
|
88
|
-
path: null,
|
|
89
|
-
headers: {},
|
|
90
|
-
stream_id: Number(stream_id)
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
var res={
|
|
94
|
-
statusCode: null,
|
|
95
|
-
headers: {},
|
|
96
|
-
headersSent: false,
|
|
97
|
-
|
|
98
|
-
/*
|
|
99
|
-
close_wt: function(){
|
|
100
|
-
context.connections[quic_connection_id].h3_socket.close_wt(stream_id);
|
|
101
|
-
},
|
|
102
|
-
*/
|
|
103
|
-
|
|
104
|
-
writeHead: function(statusCode, headers) {
|
|
105
|
-
if(res.headersSent==false){
|
|
106
|
-
res.statusCode=statusCode;
|
|
107
|
-
res.headers[":status"]=String(statusCode);
|
|
108
|
-
|
|
109
|
-
if(typeof headers=='object'){
|
|
110
|
-
for(var field_name in headers){
|
|
111
|
-
res.headers[field_name]=headers[field_name];
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
//res.headersSent=true;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
context.connections[quic_connection_id].h3_socket.http_header(stream_id,res.headers);
|
|
120
|
-
},
|
|
121
|
-
writeEarlyHints: function (hints){
|
|
122
|
-
|
|
123
|
-
},
|
|
124
|
-
write: function(chunk) {
|
|
125
|
-
if(typeof chunk=='string'){
|
|
126
|
-
var data=new TextEncoder().encode(chunk);
|
|
127
|
-
context.connections[quic_connection_id].h3_socket.http_body(Number(stream_id),data);
|
|
128
|
-
}else{
|
|
129
|
-
context.connections[quic_connection_id].h3_socket.http_body(Number(stream_id),chunk);
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
end: function(chunk) {
|
|
133
|
-
if(typeof chunk!=='undefined'){
|
|
134
|
-
if(typeof chunk=='string'){
|
|
135
|
-
var data=new TextEncoder().encode(chunk);
|
|
136
|
-
context.connections[quic_connection_id].h3_socket.http_body(Number(stream_id),data,true);
|
|
137
|
-
}else{
|
|
138
|
-
context.connections[quic_connection_id].h3_socket.http_body(Number(stream_id),chunk,true);
|
|
139
|
-
}
|
|
140
|
-
}else{
|
|
141
|
-
context.connections[quic_connection_id].h3_socket.http_body(Number(stream_id),null);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
context.connections[quic_connection_id].http_streams[stream_id]={
|
|
150
|
-
req: req,
|
|
151
|
-
res: res
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
is_new=true;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if(typeof params == "object"){
|
|
158
|
-
|
|
159
|
-
if('request_headers' in params){
|
|
160
|
-
for(var field_name in params['request_headers']){
|
|
161
|
-
context.connections[quic_connection_id].http_streams[stream_id].req.headers[field_name]=params['request_headers'][field_name];
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if(context.connections[quic_connection_id].http_streams[stream_id].req.method==null && ":method" in context.connections[quic_connection_id].http_streams[stream_id].req.headers==true){
|
|
166
|
-
context.connections[quic_connection_id].http_streams[stream_id].req.method=context.connections[quic_connection_id].http_streams[stream_id].req.headers[':method'];
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if(context.connections[quic_connection_id].http_streams[stream_id].req.path==null && ":path" in context.connections[quic_connection_id].http_streams[stream_id].req.headers==true){
|
|
170
|
-
context.connections[quic_connection_id].http_streams[stream_id].req.path=context.connections[quic_connection_id].http_streams[stream_id].req.headers[':path'];
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
//console.log(context.connections[quic_connection_id].http_streams[stream_id]);
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if(is_new==true){
|
|
180
|
-
context._handler(context.connections[quic_connection_id].http_streams[stream_id].req, context.connections[quic_connection_id].http_streams[stream_id].res);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
function receiving_quic_packet(from_ip,from_port,data){
|
|
190
|
-
var quic_connection_id=null;
|
|
191
|
-
|
|
192
|
-
var address_str = from_ip + ':' + from_port;
|
|
193
|
-
|
|
194
|
-
var dcid_str=null;
|
|
195
|
-
if('dcid' in data && data.dcid && data.dcid.byteLength>0){
|
|
196
|
-
dcid_str = Array.from(data.dcid).join("");
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if(dcid_str!==null){
|
|
200
|
-
if(dcid_str in context.connections==true){
|
|
201
|
-
quic_connection_id=dcid_str;
|
|
202
|
-
}
|
|
203
|
-
}else{
|
|
204
|
-
if(address_str in context.address_binds==true){
|
|
205
|
-
if(context.address_binds[address_str] in context.connections==true){
|
|
206
|
-
quic_connection_id=context.address_binds[address_str];
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if(quic_connection_id==null){
|
|
213
|
-
if(dcid_str!==null){
|
|
214
|
-
quic_connection_id=dcid_str;
|
|
215
|
-
}else{
|
|
216
|
-
quic_connection_id=Math.floor(Math.random() * 9007199254740991);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if(address_str in context.address_binds==false || context.address_binds[address_str]!==quic_connection_id){
|
|
221
|
-
context.address_binds[address_str]=quic_connection_id;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if(quic_connection_id in context.connections==false){
|
|
226
|
-
|
|
227
|
-
context.connections[quic_connection_id]={
|
|
228
|
-
quic_socket: null,
|
|
229
|
-
h3_socket: null,
|
|
230
|
-
http_streams: {}
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if(context.connections[quic_connection_id].quic_socket==null){
|
|
240
|
-
|
|
241
|
-
var quic_socket=new QUICSocket({
|
|
242
|
-
isServer: true,
|
|
243
|
-
SNICallback: context.SNICallback
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
context.connections[quic_connection_id].quic_socket=quic_socket;
|
|
247
|
-
|
|
248
|
-
quic_socket.on('packet',function(data_to_send){
|
|
249
|
-
send_udp_packet(from_ip,from_port,data_to_send);
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
quic_socket.on('connect',function(){
|
|
253
|
-
|
|
254
|
-
//console.log('now connected:');
|
|
255
|
-
|
|
256
|
-
var h3_socket=new H3Socket({
|
|
257
|
-
isServer: true
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
context.connections[quic_connection_id].h3_socket=h3_socket;
|
|
261
|
-
|
|
262
|
-
quic_socket.on('stream',function(stream_id,data,fin){
|
|
263
|
-
h3_socket.stream(stream_id,data,fin);
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
h3_socket.on('stream',function(stream_id,data,fin){
|
|
267
|
-
quic_socket.stream(stream_id,data,fin);
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
h3_socket.on('http_headers',function(stream_id,headers){
|
|
271
|
-
set_http_stream(quic_connection_id,stream_id,{
|
|
272
|
-
request_headers: headers
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
h3_socket.on('http_body',function(stream_id,payload){
|
|
277
|
-
set_http_stream(quic_connection_id,stream_id,{
|
|
278
|
-
add_request_body_chunk: payload
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
/*
|
|
283
|
-
quic_socket.on('datagram',function(context_id,data){
|
|
284
|
-
//console.log(data);
|
|
285
|
-
//context.connections[quic_connection_id].h3_socket.datagram(context_id,data);
|
|
286
|
-
});
|
|
287
|
-
*/
|
|
288
|
-
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
context.connections[quic_connection_id].quic_socket.packet(data);
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
//...
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
function receiving_udp_packet(from_ip,from_port,data){
|
|
303
|
-
|
|
304
|
-
var quic_packets=parse_quic_datagram(data);
|
|
305
|
-
|
|
306
|
-
if(quic_packets.length>0){
|
|
307
|
-
for(var i in quic_packets){
|
|
308
|
-
if(quic_packets[i]!==null){
|
|
309
|
-
|
|
310
|
-
receiving_quic_packet(from_ip,from_port,quic_packets[i]);
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function send_udp_packet(to_ip,to_port,data,callback){
|
|
319
|
-
if(to_ip.indexOf(':')>=0){
|
|
320
|
-
context.udp6.send(data, to_port, to_ip, function(error){
|
|
321
|
-
if (error) {
|
|
322
|
-
if(typeof callback=='function'){
|
|
323
|
-
callback(false);
|
|
324
|
-
}
|
|
325
|
-
} else {
|
|
326
|
-
if(typeof callback=='function'){
|
|
327
|
-
callback(true);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
});
|
|
331
|
-
}else{
|
|
332
|
-
context.udp4.send(data, to_port, to_ip, function(error){
|
|
333
|
-
if (error) {
|
|
334
|
-
if(typeof callback=='function'){
|
|
335
|
-
callback(false);
|
|
336
|
-
}
|
|
337
|
-
} else {
|
|
338
|
-
if(typeof callback=='function'){
|
|
339
|
-
callback(true);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
function listen(port, host, callback){
|
|
347
|
-
if (typeof host === 'function') {
|
|
348
|
-
callback = host;
|
|
349
|
-
host = null;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
context.port = port || 443;
|
|
353
|
-
host = host || '::';
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
// יצירת סוקט UDP4
|
|
359
|
-
context.udp4 = dgram.createSocket('udp4');
|
|
360
|
-
|
|
361
|
-
context.udp4.on('message', function(message, rinfo) {
|
|
362
|
-
receiving_udp_packet(rinfo.address, rinfo.port, new Uint8Array(message));
|
|
363
|
-
});
|
|
364
|
-
context.udp4.on('error', function(error) {
|
|
365
|
-
//console.error('UDP4 error:', err);
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
if (host === '::' || host.indexOf('.') !== -1) {
|
|
369
|
-
var host4 = host.indexOf('.') !== -1 ? host : '0.0.0.0';
|
|
370
|
-
context.udp4.bind(context.port, host4);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
// יצירת סוקט UDP6
|
|
377
|
-
context.udp6 = dgram.createSocket({ type: 'udp6', ipv6Only: true });
|
|
378
|
-
|
|
379
|
-
context.udp6.on('message', function(message, rinfo) {
|
|
380
|
-
receiving_udp_packet(rinfo.address, rinfo.port, new Uint8Array(message));
|
|
381
|
-
});
|
|
382
|
-
context.udp6.on('error', function(error) {
|
|
383
|
-
//console.error('UDP6 error:', err);
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
var host6 = host.indexOf(':') !== -1 ? host : '::';
|
|
387
|
-
context.udp6.bind(context.port, host6, function() {
|
|
388
|
-
if (typeof callback === 'function') {
|
|
389
|
-
callback();
|
|
390
|
-
}
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function close(){
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
var api={
|
|
399
|
-
context: context,
|
|
400
|
-
|
|
401
|
-
on: function(name, fn){ ev.on(name, fn); },
|
|
402
|
-
|
|
403
|
-
listen: listen,
|
|
404
|
-
|
|
405
|
-
close: close,
|
|
406
|
-
|
|
407
|
-
setTimeout: function(){
|
|
408
|
-
|
|
409
|
-
},
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
for (var k in api) if (Object.prototype.hasOwnProperty.call(api,k)) this[k] = api[k];
|
|
413
|
-
return this;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
export { createServer };
|