quico 0.1.1 → 0.1.3

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/h3_server.js ADDED
@@ -0,0 +1,416 @@
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 };