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/quic_socket.js ADDED
@@ -0,0 +1,1550 @@
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
+ import { createRequire } from 'node:module';
19
+ const require = createRequire(import.meta.url);
20
+
21
+ // flat-ranges (CJS בלבד)
22
+ const flat_ranges = require('flat-ranges');
23
+
24
+ // Node core
25
+ import fs from 'node:fs';
26
+ import crypto from 'node:crypto';
27
+ import process from 'node:process';
28
+
29
+ // lemon-tls (מודול חיצוני, מניח שתומך ב-ESM)
30
+ import { TLSSession } from 'lemon-tls';
31
+
32
+ // ---- מודולים פנימיים ----
33
+ import {
34
+ concatUint8Arrays,
35
+ arraybufferEqual,
36
+ quic_acked_info_to_ranges,
37
+ build_ack_info_from_ranges,
38
+ readVarInt,
39
+ writeVarInt
40
+ } from './libs/utils.js';
41
+
42
+ import {
43
+ decrypt_quic_packet,
44
+ quic_derive_init_secrets,
45
+ quic_derive_from_tls_secrets,
46
+ build_quic_ext,
47
+ hkdf_expand_label,
48
+ encode_quic_frames,
49
+ encrypt_quic_packet,
50
+ parse_quic_datagram,
51
+ parse_quic_packet,
52
+ parse_quic_frames,
53
+ extract_tls_messages_from_chunks,
54
+ build_alpn_ext,
55
+ parse_transport_parameters
56
+ } from './libs/crypto.js';
57
+
58
+ function Emitter(){
59
+ var listeners = {};
60
+ return {
61
+ on: function(name, fn){ (listeners[name] = listeners[name] || []).push(fn); },
62
+ emit: function(name){
63
+ var args = Array.prototype.slice.call(arguments, 1);
64
+ var arr = listeners[name] || [];
65
+ for (var i=0;i<arr.length;i++){ try{ arr[i].apply(null, args); }catch(e){} }
66
+ }
67
+ };
68
+ }
69
+
70
+ var TLS_CIPHER_SUITES = {
71
+ // ----------------------
72
+ // TLS 1.3 (RFC 8446)
73
+ // ----------------------
74
+ 0x1301: { // TLS_AES_128_GCM_SHA256
75
+ tls: 13,
76
+ kex: 'TLS13',
77
+ sig: 'TLS13',
78
+ cipher: 'AES_128_GCM',
79
+ aead: true,
80
+ keylen: 16,
81
+ ivlen: 12,
82
+ hash: 'sha256'
83
+ },
84
+ 0x1302: { // TLS_AES_256_GCM_SHA384
85
+ tls: 13,
86
+ kex: 'TLS13',
87
+ sig: 'TLS13',
88
+ cipher: 'AES_256_GCM',
89
+ aead: true,
90
+ keylen: 32,
91
+ ivlen: 12,
92
+ hash: 'sha384'
93
+ },
94
+ 0x1303: { // TLS_CHACHA20_POLY1305_SHA256
95
+ tls: 13,
96
+ kex: 'TLS13',
97
+ sig: 'TLS13',
98
+ cipher: 'CHACHA20_POLY1305',
99
+ aead: true,
100
+ keylen: 32,
101
+ ivlen: 12,
102
+ hash: 'sha256'
103
+ }
104
+ }
105
+
106
+ // ==== QUICSocket ====
107
+ function QUICSocket(options){
108
+ if (!(this instanceof QUICSocket)) return new QUICSocket(options);
109
+ options = options || {};
110
+
111
+ var ev = Emitter();
112
+
113
+ var context = {
114
+ connection_status: 4,//0 - connecting... | 1 - connected | 2 - disconnected | ...
115
+
116
+ version: 1,
117
+
118
+ my_cids: [], // SCIDים שאתה נתת (כנראה אחד ראשוני ועוד future)
119
+ their_cids: [], // DCIDים שהצד השני השתמש בהם (כלומר שלך כשרת)
120
+ original_dcid: null, // ל־Initial ול־Retry
121
+
122
+
123
+ tls_session: null,
124
+
125
+ SNICallback: options.SNICallback || null,
126
+
127
+ //....
128
+ init_read_key: null,
129
+ init_read_iv: null,
130
+ init_read_hp: null,
131
+
132
+ init_write_key: null,
133
+ init_write_iv: null,
134
+ init_write_hp: null,
135
+
136
+ handshake_read_key: null,
137
+ handshake_read_iv: null,
138
+ handshake_read_hp: null,
139
+
140
+ handshake_write_key: null,
141
+ handshake_write_iv: null,
142
+ handshake_write_hp: null,
143
+
144
+ app_prev_read_key: null,
145
+ app_prev_read_iv: null,
146
+ app_prev_read_hp: null,
147
+
148
+ app_read_key: null,
149
+ app_read_iv: null,
150
+ app_read_hp: null,
151
+
152
+ read_key_phase: false,
153
+
154
+ app_write_key: null,
155
+ app_write_iv: null,
156
+ app_write_hp: null,
157
+
158
+ handshake_done: false,
159
+ handshake_done_sent: false,
160
+
161
+ //sending...
162
+
163
+ sending_init_pn_next: 1,
164
+ sending_init_chunks: [],
165
+ sending_init_offset_next: 0,
166
+ sending_init_pn_acked_ranges: [],
167
+
168
+ sending_handshake_pn_next: 1,
169
+ sending_handshake_chunks: [],
170
+ sending_handshake_offset_next: 0,
171
+ sending_handshake_pn_acked_ranges: [],
172
+
173
+
174
+ sending_streams: {},
175
+ sending_stream_id_next: 0,
176
+
177
+
178
+
179
+ max_sending_packets_per_sec: 1000,
180
+ max_sending_total_bytes_per_sec: 150000,
181
+ max_sending_packet_size: 1200,
182
+ min_sending_packet_size: 35,
183
+
184
+ max_sending_packets_in_flight: 20,
185
+ max_sending_bytes_in_flight: 150000,
186
+
187
+ sending_app_pn_base: 1,
188
+ sending_app_pn_history: [],
189
+ rtt_history: [],
190
+ sending_app_pn_in_flight: new Set(),
191
+
192
+ next_send_quic_packet_timer: null,
193
+ sending_quic_packet_now: false,
194
+
195
+
196
+ //received...
197
+
198
+ receiving_init_pn_largest: -1,
199
+ receiving_init_pn_ranges: [],
200
+ receiving_init_chunks: {},
201
+ receiving_init_from_offset: 0,
202
+ receiving_init_ranges: [],//מערך שטוח של מ עד
203
+
204
+ receiving_handshake_pn_largest: -1,
205
+ receiving_handshake_pn_ranges: [],
206
+ receiving_handshake_chunks: {},
207
+ receiving_handshake_from_offset: 0,
208
+ receiving_handshake_ranges: [],//מערך שטוח של מ עד
209
+
210
+ receiving_app_pn_largest: -1,
211
+ receiving_app_pn_ranges: [],
212
+ receiving_app_pn_history: [],
213
+
214
+ receiving_app_pn_pending_ack: [],
215
+
216
+
217
+ receiving_streams: {}, // stream_id → stream object
218
+ receiving_streams_next_check_timer: null,
219
+
220
+
221
+ remote_ack_delay_exponent: 3,
222
+ remote_max_udp_payload_size: 1000,
223
+
224
+ };
225
+
226
+
227
+ function set_context(options){
228
+ var has_changed=false;
229
+
230
+ var fields=[
231
+ 'connection_status',
232
+ ];
233
+
234
+ var prev={};
235
+
236
+ if (options && typeof options === 'object'){
237
+ if('connection_status' in options){
238
+ if(context.connection_status!==options.connection_status){
239
+ prev['connection_status']=context['connection_status'];
240
+ context.connection_status=options.connection_status;
241
+ has_changed=true;
242
+ }
243
+ }
244
+ }
245
+
246
+ if(has_changed==true){
247
+ if(context.connection_status==1 && context.connection_status!==prev.connection_status){
248
+
249
+
250
+ ev.emit('connect');
251
+
252
+ //we need to flush...
253
+
254
+ }
255
+ }
256
+ }
257
+
258
+
259
+
260
+
261
+
262
+ function get_quic_stream_chunks_to_send(stream_id, allowed_bytes) {
263
+
264
+ var stream = context.sending_streams[stream_id];
265
+ if (!stream || !stream.pending_data) {
266
+ return {
267
+ chunks: [],
268
+ send_offset_next: stream ? stream.send_offset_next : 0
269
+ };
270
+ }
271
+
272
+ // הגודל הכולל של ה־stream
273
+ var total_bytes = stream.total_size;
274
+
275
+ if(typeof total_bytes !== 'number' || total_bytes<=0){
276
+ total_bytes = stream.write_offset_next;
277
+ }
278
+
279
+ var base_offset = stream.pending_offset_start;
280
+ var send_offset_next = stream.send_offset_next;
281
+
282
+ // טווחים חסרים (יחסיים)
283
+ var relative_missing = flat_ranges.invert(stream.acked_ranges, 0, total_bytes);
284
+
285
+ // המרה ל־offset מוחלט
286
+ for (var i = 0; i < relative_missing.length; i++) {
287
+ relative_missing[i] += base_offset;
288
+ }
289
+
290
+ var chunks = [];
291
+ var total_bytes_used = 0;
292
+ var first_chunk_offset = null;
293
+
294
+ // שלב ראשון – קדימה מהמקום האחרון
295
+ for (var i = 0; i < relative_missing.length; i += 2) {
296
+ var f = relative_missing[i];
297
+ var t = relative_missing[i + 1];
298
+
299
+ if (f <= send_offset_next && send_offset_next < t) {
300
+ var offset = send_offset_next;
301
+
302
+ while (offset < t && total_bytes_used < allowed_bytes) {
303
+ var space_left = allowed_bytes - total_bytes_used;
304
+ var len = Math.min(space_left, t - offset);
305
+ if (len <= 0) break;
306
+
307
+ if (first_chunk_offset === null) first_chunk_offset = offset;
308
+
309
+ var rel_start = offset - base_offset;
310
+ var rel_end = rel_start + len;
311
+ var chunk_data = stream.pending_data.slice(rel_start, rel_end);
312
+
313
+ chunks.push({
314
+ offset: offset,
315
+ data: chunk_data
316
+ });
317
+
318
+ total_bytes_used += len;
319
+ offset += len;
320
+ }
321
+
322
+ break;
323
+ }
324
+ }
325
+
326
+ // שלב שני – התחלה עד first_chunk_offset
327
+ if (total_bytes_used < allowed_bytes && first_chunk_offset !== null) {
328
+ for (var i = 0; i < relative_missing.length; i += 2) {
329
+ var f = relative_missing[i];
330
+ var t = relative_missing[i + 1];
331
+
332
+ var offset = f;
333
+ while (offset < t && offset < first_chunk_offset && total_bytes_used < allowed_bytes) {
334
+ var space_left = allowed_bytes - total_bytes_used;
335
+ var len = Math.min(space_left, t - offset, first_chunk_offset - offset);
336
+ if (len <= 0) break;
337
+
338
+ var rel_start = offset - base_offset;
339
+ var rel_end = rel_start + len;
340
+ var chunk_data = stream.pending_data.slice(rel_start, rel_end);
341
+
342
+ chunks.push({
343
+ offset: offset,
344
+ data: chunk_data
345
+ });
346
+
347
+ total_bytes_used += len;
348
+ offset += len;
349
+ }
350
+ }
351
+ }
352
+
353
+ // חישוב המצביע הבא אם נשלחו רצפים מהמצביע הנוכחי
354
+ var new_send_offset = send_offset_next;
355
+ for (var i = 0; i < chunks.length; i++) {
356
+ var chunk = chunks[i];
357
+ if (chunk.offset === new_send_offset) {
358
+ new_send_offset = chunk.offset + chunk.data.length;
359
+ } else {
360
+ break;
361
+ }
362
+ }
363
+
364
+ return {
365
+ chunks: chunks,
366
+ send_offset_next: new_send_offset,
367
+ };
368
+ }
369
+
370
+
371
+ function prepare_and_send_quic_packet() {
372
+
373
+ //console.log('prepare_and_send_quic_packet...............');
374
+
375
+ //console.log(context);
376
+
377
+ //console.log(context.sending_streams);
378
+
379
+ if(context.sending_quic_packet_now==false){
380
+ context.sending_quic_packet_now=true;
381
+
382
+ if(context.next_send_quic_packet_timer!==null){
383
+ clearTimeout(context.next_send_quic_packet_timer);
384
+ context.next_send_quic_packet_timer=null;
385
+ }
386
+
387
+ var now = Math.floor(performance.timeOrigin + performance.now());
388
+
389
+ var total_bytes_last_1s = 0;
390
+ var packet_count_last_1s = 0;
391
+
392
+ var oldest_packet_time_bytes = null;
393
+ var oldest_packet_time_packets = null;
394
+
395
+ // סריקת ההיסטוריה
396
+ for (var i in context.sending_app_pn_history) {
397
+ var [ts, size] = context.sending_app_pn_history[i];
398
+
399
+ if (ts > now - 1000) {
400
+ total_bytes_last_1s += size;
401
+ packet_count_last_1s++;
402
+ } else {
403
+ // שומרים מתי יפוג כל פאקט מההיסטוריה
404
+ if (oldest_packet_time_bytes === null || ts < oldest_packet_time_bytes) {
405
+ oldest_packet_time_bytes = ts;
406
+ }
407
+ if (oldest_packet_time_packets === null || ts < oldest_packet_time_packets) {
408
+ oldest_packet_time_packets = ts;
409
+ }
410
+ }
411
+ }
412
+
413
+ var bytes_left = context.max_sending_total_bytes_per_sec - total_bytes_last_1s;
414
+ var packets_left = context.max_sending_packets_per_sec - packet_count_last_1s;
415
+
416
+
417
+ var in_flight_packet_count = context.sending_app_pn_in_flight.size;
418
+ var in_flight_total_bytes = 0;
419
+ for (var pn of context.sending_app_pn_in_flight) {
420
+ var pn_index = Number(pn) - (context.sending_app_pn_base - context.sending_app_pn_history.length);
421
+ if (pn_index >= 0 && pn_index < context.sending_app_pn_history.length) {
422
+ var info = context.sending_app_pn_history[pn_index];
423
+ if (info){
424
+ in_flight_total_bytes=in_flight_total_bytes+info[1];//size
425
+ }
426
+ }
427
+ }
428
+
429
+
430
+ var in_flight_room = context.max_sending_bytes_in_flight - in_flight_total_bytes;
431
+
432
+
433
+ var allowed_packet_size = Math.min(bytes_left, context.max_sending_packet_size, in_flight_room);
434
+
435
+ //console.log('@@ 1');
436
+ if (
437
+ packets_left > 0 &&
438
+ allowed_packet_size >= context.min_sending_packet_size &&
439
+ in_flight_packet_count < context.max_sending_packets_in_flight &&
440
+ in_flight_total_bytes + allowed_packet_size <= context.max_sending_bytes_in_flight
441
+ ) {
442
+ // מותר לשלוח *******************************
443
+ //console.log('@@ 2');
444
+
445
+ var encoded_frames=[];
446
+ var update_streams={};
447
+ var remove_pending_ack=[];
448
+
449
+
450
+
451
+
452
+ if(context.receiving_app_pn_pending_ack.length>0 && 1==1){
453
+ var ack_delay_ms = 0;
454
+ var largest_pn = context.receiving_app_pn_pending_ack[context.receiving_app_pn_pending_ack.length - 1];
455
+ for (var i2 = 0; i2 < context.receiving_app_pn_history.length; i2++) {
456
+ var [pn_recv, ts_recv, size_recv] = context.receiving_app_pn_history[i2];
457
+ if(pn_recv==largest_pn){
458
+ ack_delay_ms = now - ts_recv;
459
+ break;
460
+ }
461
+ }
462
+
463
+ var delay_ns = ack_delay_ms * 1_000_000;
464
+ var ack_delay_raw = Math.floor(delay_ns / (1 << context.remote_ack_delay_exponent));
465
+
466
+ var ack_frame = build_ack_info_from_ranges(context.receiving_app_pn_pending_ack, null, ack_delay_raw);
467
+
468
+ //var padding=new Uint8Array([0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]);
469
+ encoded_frames.push(encode_quic_frames([ack_frame]));
470
+
471
+ /*
472
+
473
+ console.log('ack frame sent:');
474
+
475
+ console.log(ack_frame);
476
+
477
+ console.log('ack for ranges:');
478
+
479
+ console.log(context.receiving_app_pn_pending_ack);
480
+
481
+ console.log('raw:');
482
+ console.log(encoded_frames);
483
+ //allowed_packet_size=allowed_packet_size-encoded_frames[0].byteLength;
484
+
485
+ */
486
+
487
+ remove_pending_ack = context.receiving_app_pn_pending_ack.slice();
488
+ }
489
+
490
+
491
+
492
+ var active_stream_count=0;
493
+ for(var stream_id in context.sending_streams){
494
+ //צריך שיהיה בדיקה אם זה סטרים שעדיין לא נשלח במלואו...
495
+ active_stream_count++;
496
+ }
497
+ var per_stream_bytes = Math.floor(allowed_packet_size / active_stream_count);
498
+
499
+
500
+ for(var stream_id in context.sending_streams){
501
+
502
+ var chunks_ranges=[];
503
+
504
+ var {chunks,send_offset_next}=get_quic_stream_chunks_to_send(Number(stream_id),per_stream_bytes);
505
+
506
+ if(chunks.length>0){
507
+
508
+ for(var i in chunks){
509
+
510
+ var is_fin=false;
511
+
512
+ if(typeof context.sending_streams[stream_id].total_size == 'number' && context.sending_streams[stream_id].total_size>0 && chunks[i].offset+chunks[i].data.byteLength>=context.sending_streams[stream_id].total_size){
513
+ is_fin=true;
514
+ }
515
+
516
+ var stream_frame={
517
+ type: 'stream',
518
+ id: Number(stream_id),
519
+ offset: chunks[i].offset,
520
+ fin: is_fin,
521
+ data: chunks[i].data
522
+ };
523
+
524
+ encoded_frames.push(encode_quic_frames([stream_frame]));
525
+
526
+ chunks_ranges.push(chunks[i].offset, chunks[i].offset + chunks[i].data.length);
527
+
528
+ }
529
+
530
+ chunks_ranges.sort(function(a, b) {
531
+ return a - b;
532
+ });
533
+
534
+ update_streams[stream_id]={
535
+ chunks_ranges: chunks_ranges,
536
+ send_offset_next: send_offset_next
537
+ };
538
+
539
+ }
540
+ }
541
+
542
+
543
+ /*
544
+ console.log('allowed_packet_size::::::::::::');
545
+ console.log(allowed_packet_size);
546
+
547
+ console.log('update_streams::::::::::::');
548
+ console.log(update_streams);
549
+
550
+ console.log('encoded_frames::::::::::::');
551
+ console.log(encoded_frames);
552
+ */
553
+
554
+ if(encoded_frames.length>0){
555
+
556
+
557
+
558
+ if(encoded_frames.length==1){
559
+ var all_encoded_frames = encoded_frames[0];
560
+ }else if(encoded_frames.length>1){
561
+ var all_encoded_frames = concatUint8Arrays(encoded_frames);
562
+ }
563
+
564
+ send_quic_packet_frames('1rtt',all_encoded_frames);
565
+
566
+
567
+ if(1==1 || is_sent==true){
568
+
569
+ now = Math.floor(performance.timeOrigin + performance.now());
570
+
571
+ var packet_number=context.sending_app_pn_base;
572
+ context.sending_app_pn_history.push([now, all_encoded_frames.length]);
573
+ context.sending_app_pn_in_flight.add(packet_number);
574
+
575
+ for(var stream_id in update_streams){
576
+ context.sending_streams[stream_id].in_flight_ranges[packet_number]=update_streams[stream_id].chunks_ranges;
577
+ context.sending_streams[stream_id].send_offset_next=update_streams[stream_id].send_offset_next;
578
+ }
579
+
580
+ //console.log(context.sending_streams);
581
+
582
+ if(remove_pending_ack.length>0){
583
+ flat_ranges.remove(context.receiving_app_pn_pending_ack,remove_pending_ack);
584
+ }
585
+
586
+
587
+ context.sending_app_pn_base++;
588
+ }
589
+
590
+ // אם שלחנו הרגע פאקט ואין עדיין מגבלות, נמתין את הזמן הזה כדי לפזר נכון
591
+ //var interval_between_packets = Math.ceil(1000 / server.max_sending_packets_per_sec);
592
+
593
+
594
+
595
+
596
+
597
+
598
+ //context.next_send_quic_packet_timer=null;
599
+ //prepare_and_send_quic_packet();
600
+
601
+ context.next_send_quic_packet_timer=setTimeout(function(){
602
+ context.sending_quic_packet_now=false;
603
+ context.next_send_quic_packet_timer=null;
604
+ prepare_and_send_quic_packet();
605
+ }, 0);
606
+
607
+
608
+
609
+ }else{
610
+
611
+ context.next_send_quic_packet_timer=null;
612
+ context.sending_quic_packet_now=false;
613
+
614
+ }
615
+
616
+ }else{
617
+
618
+ //console.log('not now...');
619
+
620
+ // ✋ לא ניתן לשלוח כרגע — צריך לחשב מתי כן יהיה אפשר
621
+ var wait_options = [];
622
+
623
+ // זמן עד שיימחק פאקט שמפחית מגבלת פאקטים
624
+ if (packets_left <= 0 && oldest_packet_time_packets !== null) {
625
+ var wait_packets = Math.max(0, (oldest_packet_time_packets + 1000) - now);
626
+ wait_options.push(wait_packets);
627
+ }
628
+
629
+ // זמן עד שיימחק מספיק בייטים
630
+ if (bytes_left < context.min_sending_packet_size && oldest_packet_time_bytes !== null) {
631
+ var wait_bytes = Math.max(0, (oldest_packet_time_bytes + 1000) - now);
632
+ wait_options.push(wait_bytes);
633
+ }
634
+
635
+
636
+
637
+ if (wait_options.length > 0) {
638
+ context.next_send_quic_packet_timer = setTimeout(function(){
639
+ context.next_send_quic_packet_timer=null;
640
+ context.sending_quic_packet_now=false;
641
+ prepare_and_send_quic_packet();
642
+ }, Math.max(...wait_options));
643
+
644
+ //console.log('next_time: ',(Math.max(...wait_options)));
645
+ }else{
646
+ context.sending_quic_packet_now=false;
647
+ }
648
+
649
+ }
650
+
651
+ }
652
+
653
+ }
654
+
655
+
656
+
657
+ function stream_write(stream_id,data,fin){
658
+
659
+ if(stream_id in context.sending_streams==false){
660
+ context.sending_streams[stream_id]={
661
+ pending_data: null,
662
+ write_offset_next: 0,
663
+ pending_offset_start: 0,
664
+ send_offset_next: 0,
665
+ total_size: 0,
666
+
667
+ in_flight_ranges: {},
668
+ acked_ranges: [],
669
+ };
670
+ }
671
+
672
+ var stream = context.sending_streams[stream_id];
673
+
674
+ if(data==null){
675
+
676
+ if(stream.total_size==0){
677
+ stream.total_size=stream.write_offset_next;
678
+ }
679
+
680
+ }else{
681
+
682
+ var start_offset = stream.write_offset_next;
683
+ var end_offset = start_offset + data.byteLength;
684
+ stream.write_offset_next = end_offset;
685
+ //stream.total_size = end_offset;
686
+
687
+ // קבע את התחלת ה־pending לפי acked_ranges
688
+ var pending_offset_start = 0;
689
+ if (stream.acked_ranges.length > 0 && stream.acked_ranges[0] === 0) {
690
+ pending_offset_start = stream.acked_ranges[1];
691
+ }
692
+
693
+ // גזור רק את החלק שטרם קיבל ACK
694
+ var skip = Math.max(pending_offset_start - start_offset, 0);
695
+ if (skip >= data.byteLength) return; // אין מה להוסיף
696
+
697
+ var trimmed_data = data.slice(skip);
698
+
699
+ if (stream.pending_data === null) {
700
+ stream.pending_data = trimmed_data;
701
+ stream.pending_offset_start = start_offset + skip;
702
+ } else {
703
+ // מיזוג ל־Uint8Array חדש
704
+ var old = stream.pending_data;
705
+ var old_offset = stream.pending_offset_start;
706
+ var new_offset = start_offset + skip;
707
+
708
+ var new_start = Math.min(old_offset, new_offset);
709
+ var new_end = Math.max(old_offset + old.length, new_offset + trimmed_data.length);
710
+ var total_len = new_end - new_start;
711
+
712
+ var merged = new Uint8Array(total_len);
713
+
714
+ // העתק ישן
715
+ merged.set(old, old_offset - new_start);
716
+
717
+ // העתק חדש
718
+ merged.set(trimmed_data, new_offset - new_start);
719
+
720
+ stream.pending_data = merged;
721
+ stream.pending_offset_start = new_start;
722
+
723
+ }
724
+ }
725
+
726
+ if((typeof fin=='boolean' && fin==true) || data==null){
727
+ if(stream.total_size==0){
728
+ stream.total_size = stream.write_offset_next;
729
+ }
730
+ }
731
+
732
+
733
+ prepare_and_send_quic_packet();
734
+ }
735
+
736
+
737
+ function send_quic_packet_frames(packet_type,encoded_frames){
738
+ var write_key=null;
739
+ var write_iv=null;
740
+ var write_hp=null;
741
+
742
+ var packet_number=1;
743
+
744
+ if(packet_type=='initial'){
745
+
746
+ if(context.init_write_key!==null && context.init_write_iv!==null && context.init_write_hp!==null){
747
+ write_key=context.init_write_key;
748
+ write_iv=context.init_write_iv;
749
+ write_hp=context.init_write_hp;
750
+
751
+ }else{
752
+ var d = quic_derive_init_secrets(context.original_dcid,context.version,'write');
753
+ write_key=d.key;
754
+ write_iv=d.iv;
755
+ write_hp=d.hp;
756
+
757
+ context.init_write_key=d.key;
758
+ context.init_write_iv=d.iv;
759
+ context.init_write_hp=d.hp;
760
+ }
761
+
762
+ packet_number=Number(context.sending_init_pn_next)+0;
763
+
764
+ }else if(packet_type=='handshake'){
765
+
766
+ if(context.handshake_write_key!==null && context.handshake_write_iv!==null && context.handshake_write_hp!==null){
767
+ write_key=context.handshake_write_key;
768
+ write_iv=context.handshake_write_iv;
769
+ write_hp=context.handshake_write_hp;
770
+
771
+ }else if(context.tls_session.context.server_handshake_traffic_secret!==null){
772
+ var d = quic_derive_from_tls_secrets(context.tls_session.context.server_handshake_traffic_secret,TLS_CIPHER_SUITES[context.tls_session.context.selected_cipher_suite].hash);
773
+
774
+ write_key=d.key;
775
+ write_iv=d.iv;
776
+ write_hp=d.hp;
777
+
778
+ context.handshake_write_key=d.key;
779
+ context.handshake_write_iv=d.iv;
780
+ context.handshake_write_hp=d.hp;
781
+ }
782
+
783
+ packet_number=Number(context.sending_handshake_pn_next)+0;
784
+
785
+ }else if(packet_type=='1rtt'){
786
+
787
+ if(context.app_write_key!==null && context.app_write_iv!==null && context.app_write_hp!==null){
788
+ write_key=context.app_write_key;
789
+ write_iv=context.app_write_iv;
790
+ write_hp=context.app_write_hp;
791
+
792
+ }else if(context.tls_session.context.server_app_traffic_secret!==null){
793
+ var d = quic_derive_from_tls_secrets(context.tls_session.context.server_app_traffic_secret,TLS_CIPHER_SUITES[context.tls_session.context.selected_cipher_suite].hash);
794
+
795
+ write_key=d.key;
796
+ write_iv=d.iv;
797
+ write_hp=d.hp;
798
+
799
+ context.app_write_key=d.key;
800
+ context.app_write_iv=d.iv;
801
+ context.app_write_hp=d.hp;
802
+ }
803
+
804
+ packet_number=Number(context.sending_app_pn_base)+0;
805
+
806
+ }
807
+
808
+ var dcid=new Uint8Array(0);
809
+
810
+
811
+ if(context.their_cids.length>0){
812
+ dcid=context.their_cids[0];
813
+ }
814
+
815
+ var encrypted_quic_packet=encrypt_quic_packet(packet_type, encoded_frames, write_key, write_iv, write_hp, packet_number, dcid, context.original_dcid, new Uint8Array(0));
816
+
817
+ if(packet_type=='initial'){
818
+ context.sending_init_pn_next++;
819
+ }else if(packet_type=='handshake'){
820
+ context.sending_handshake_pn_next++;
821
+ }else if(packet_type=='1rtt'){
822
+ var now = Math.floor(performance.timeOrigin + performance.now());
823
+
824
+ context.sending_app_pn_history.push([now, encoded_frames.length]);
825
+ context.sending_app_pn_base++;
826
+ }
827
+
828
+ ev.emit('packet',encrypted_quic_packet);
829
+
830
+ }
831
+
832
+
833
+ function process_reset_stream_frame(packet_type,stream_id,final_size,reset_error_code){
834
+
835
+ if(stream_id in context.receiving_streams==false){
836
+ context.receiving_streams[stream_id]={
837
+ data_chunks: {},
838
+ total_size: 0,
839
+ receiving_ranges: [],
840
+ next_flush_offset: 0,
841
+ next_flush_timer: null,
842
+
843
+ reset_error_code: 0,
844
+ delete_timer: null
845
+ };
846
+ }
847
+
848
+ context.receiving_streams[stream_id].total_size = final_size|0;
849
+ context.receiving_streams[stream_id].reset_error_code = reset_error_code|0;
850
+
851
+ }
852
+
853
+ function process_stream_frame(packet_type,stream_id,offset,data,fin){
854
+
855
+ var is_new_chunk=false;
856
+
857
+ if(stream_id in context.receiving_streams==false){
858
+ context.receiving_streams[stream_id]={
859
+ data_chunks: {},
860
+ total_size: 0,
861
+ receiving_ranges: [],
862
+ next_flush_offset: 0,
863
+ next_flush_timer: null,
864
+
865
+ reset_error_code: 0,
866
+ delete_timer: null
867
+ };
868
+ }
869
+
870
+ if(flat_ranges.add(context.receiving_streams[stream_id].receiving_ranges, [offset, offset + data.length])==true){
871
+
872
+ if(offset in context.receiving_streams[stream_id].data_chunks==false || context.receiving_streams[stream_id].data_chunks[offset].byteLength<data.byteLength){
873
+ context.receiving_streams[stream_id].data_chunks[offset]=data;
874
+ }
875
+
876
+ if(typeof fin=='boolean' && fin==true){
877
+ context.receiving_streams[stream_id].total_size=offset+data.byteLength;
878
+ }
879
+
880
+ is_new_chunk=true;
881
+
882
+ }
883
+
884
+ if(is_new_chunk==true){
885
+
886
+ if(context.receiving_streams[stream_id].receiving_ranges.length==2 && typeof context.receiving_streams[stream_id].total_size == 'number' && context.receiving_streams[stream_id].total_size>0 && context.receiving_streams[stream_id].receiving_ranges[0]==0 && context.receiving_streams[stream_id].receiving_ranges[1]==context.receiving_streams[stream_id].total_size){
887
+
888
+ flush_stream_chunk(stream_id);
889
+
890
+ }else if(context.receiving_streams[stream_id].next_flush_timer==null){
891
+ //run timer...
892
+ context.receiving_streams[stream_id].next_flush_timer=setTimeout(function(){
893
+ context.receiving_streams[stream_id].next_flush_timer=null;
894
+ flush_stream_chunk(stream_id);
895
+ },3);
896
+ }
897
+ }
898
+ }
899
+
900
+ function flush_stream_chunk(stream_id){
901
+
902
+ if (stream_id in context.receiving_streams==true) {
903
+
904
+ if(context.receiving_streams[stream_id].next_flush_timer!==null){
905
+ clearTimeout(context.receiving_streams[stream_id].next_flush_timer);
906
+ context.receiving_streams[stream_id].next_flush_timer=null;
907
+ }
908
+
909
+ var to_concat = [];
910
+
911
+ var next_flush_offset=context.receiving_streams[stream_id].next_flush_offset;
912
+ while ((next_flush_offset in context.receiving_streams[stream_id].data_chunks)) {
913
+ var part = context.receiving_streams[stream_id].data_chunks[next_flush_offset];
914
+
915
+ delete context.receiving_streams[stream_id].data_chunks[next_flush_offset];
916
+
917
+ to_concat.push(part);
918
+ next_flush_offset += part.byteLength;
919
+ }
920
+
921
+ if (to_concat.length > 0) {
922
+ context.receiving_streams[stream_id].next_flush_offset=next_flush_offset;
923
+
924
+ if(to_concat.length>2){
925
+ var out = concatUint8Arrays(to_concat);
926
+ }else{
927
+ var out = to_concat[0];
928
+ }
929
+
930
+ if(context.receiving_streams[stream_id].total_size>0 && context.receiving_streams[stream_id].total_size<=context.receiving_streams[stream_id].next_flush_offset){
931
+ var fin=true;
932
+ }else{
933
+ var fin=false;
934
+ }
935
+
936
+ ev.emit('stream', Number(stream_id), out, fin);
937
+ }
938
+
939
+ }
940
+ }
941
+
942
+
943
+
944
+
945
+ function crypto_write(packet_type,data){
946
+
947
+ if(packet_type=='initial'){
948
+
949
+ var encoded_frames=encode_quic_frames([{
950
+ type: 'crypto',
951
+ offset: context.sending_init_offset_next,
952
+ data: data
953
+ }]);
954
+
955
+ context.sending_init_offset_next=context.sending_init_offset_next+data.byteLength;
956
+
957
+
958
+ }else if(packet_type=='handshake'){
959
+
960
+ var encoded_frames=encode_quic_frames([{
961
+ type: 'crypto',
962
+ offset: context.sending_handshake_offset_next,
963
+ data: data
964
+ }]);
965
+
966
+ context.sending_handshake_offset_next=context.sending_handshake_offset_next+data.byteLength;
967
+
968
+ }
969
+
970
+ send_quic_packet_frames(packet_type,encoded_frames);
971
+
972
+ }
973
+
974
+ function process_crypto_frame(packet_type,offset,data){
975
+
976
+ var is_new_chunk=false;
977
+
978
+ if(packet_type=='initial'){
979
+
980
+ if(flat_ranges.add(context.receiving_init_ranges, [offset, offset + data.length])==true){
981
+
982
+ if(offset in context.receiving_init_chunks==false || context.receiving_init_chunks[offset].byteLength<data.byteLength){
983
+ context.receiving_init_chunks[offset]=data;
984
+ }
985
+
986
+ is_new_chunk=true;
987
+
988
+ }
989
+
990
+ }else if(packet_type=='handshake'){
991
+
992
+ if(flat_ranges.add(context.receiving_handshake_ranges, [offset, offset + data.length])==true){
993
+
994
+ if(offset in context.receiving_handshake_chunks==false || context.receiving_handshake_chunks[offset].byteLength<data.byteLength){
995
+ context.receiving_handshake_chunks[offset]=data;
996
+ }
997
+
998
+ is_new_chunk=true;
999
+
1000
+ }
1001
+
1002
+ }
1003
+
1004
+
1005
+ if(is_new_chunk==true){
1006
+
1007
+ var tls_messages=[];
1008
+
1009
+ if(packet_type=='initial'){
1010
+
1011
+ var ext=extract_tls_messages_from_chunks(context.receiving_init_chunks, context.receiving_init_from_offset);
1012
+
1013
+ if(ext){
1014
+ tls_messages=ext.tls_messages;
1015
+ context.receiving_init_from_offset=ext.new_from_offset;
1016
+ }
1017
+
1018
+ }else if(packet_type=='handshake'){
1019
+
1020
+ var ext=extract_tls_messages_from_chunks(context.receiving_handshake_chunks, context.receiving_handshake_from_offset);
1021
+
1022
+ if(ext){
1023
+ tls_messages=ext.tls_messages;
1024
+ context.receiving_handshake_from_offset=ext.new_from_offset;
1025
+ }
1026
+
1027
+ }
1028
+
1029
+ if(tls_messages && tls_messages.length>0){
1030
+
1031
+ //console.log('tls messages ******');
1032
+
1033
+ if(context.tls_session==null){
1034
+ context.tls_session = new TLSSession({
1035
+ isServer: true,
1036
+ SNICallback: context.SNICallback
1037
+ });
1038
+
1039
+ context.tls_session.on('message', function(epoch, seq, type, data){
1040
+
1041
+ //console.log('tls message to send:');
1042
+ //console.log(data);
1043
+
1044
+ if(epoch==0){
1045
+ crypto_write('initial',data);
1046
+ }else if(epoch==1){
1047
+ crypto_write('handshake',data);
1048
+ }else if(epoch==3){
1049
+ crypto_write('app',data);
1050
+ }
1051
+
1052
+ });
1053
+
1054
+ context.tls_session.on('hello', function(info){
1055
+ //console.log('tls hello...');
1056
+
1057
+ /*
1058
+ var quic_transport_params=parse_transport_parameters(parsed.quic_transport_parameters_raw);
1059
+
1060
+ if ('ack_delay_exponent' in quic_transport_params) {
1061
+ context.remote_ack_delay_exponent=quic_transport_params['ack_delay_exponent'];
1062
+ }
1063
+
1064
+ if ('max_udp_payload_size' in quic_transport_params) {
1065
+ context.remote_max_udp_payload_size=quic_transport_params['max_udp_payload_size'];
1066
+ }
1067
+ */
1068
+
1069
+
1070
+ var quic_ext_data=build_quic_ext({
1071
+ original_destination_connection_id: context.original_dcid,
1072
+ initial_source_connection_id: context.original_dcid,
1073
+ max_udp_payload_size: 65527,
1074
+ max_idle_timeout: 30000,
1075
+ stateless_reset_token: new Uint8Array(16).fill(0xab),
1076
+ initial_max_data: 1048576,
1077
+ initial_max_stream_data_bidi_local: 262144,
1078
+ initial_max_stream_data_bidi_remote: 262144,
1079
+ initial_max_stream_data_uni: 131072,
1080
+ initial_max_streams_bidi: 100,
1081
+ initial_max_streams_uni: 3,
1082
+ ack_delay_exponent: 3,
1083
+ max_ack_delay: 25,
1084
+ disable_active_migration: true,
1085
+ active_connection_id_limit: 4,
1086
+ max_datagram_frame_size: 65527,
1087
+ web_accepted_origins: [
1088
+ "*" //or your domain
1089
+ ]
1090
+ });
1091
+
1092
+ //var serverName=context.tls_session.context.selected_sni;
1093
+
1094
+ context.tls_session.set_context({
1095
+ local_versions: [0x0304],
1096
+ local_alpns: ['h3'],
1097
+ local_groups: [29, 23, 24],
1098
+ local_cipher_suites: [
1099
+ 0x1301,
1100
+ 0x1302,
1101
+ 0xC02F, // ECDHE_RSA_WITH_AES_128_GCM_SHA256
1102
+ 0xC030, // ECDHE_RSA_WITH_AES_256_GCM_SHA384
1103
+ 0xCCA8 // ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (אם מימשת)
1104
+ ],
1105
+
1106
+ local_extensions: [
1107
+ { type: 0x39, data: quic_ext_data}
1108
+ ],
1109
+
1110
+ // ---- אלגוריתמי חתימה (TLS 1.2 → RSA-PKCS1, לא PSS) ----
1111
+ // 0x0401 = rsa_pkcs1_sha256, 0x0501 = rsa_pkcs1_sha384, 0x0601 = rsa_pkcs1_sha512
1112
+ local_signature_algorithms: [0x0401, 0x0501, 0x0601],
1113
+ // אופציונלי (לטובת חלק מהלקוחות): אותו דבר גם ל-signature_algorithms_cert
1114
+ local_signature_algorithms_cert: [0x0401, 0x0501, 0x0601],
1115
+
1116
+ //local_cert_chain: [{ cert: new Uint8Array(cert.raw)}],
1117
+ //cert_private_key: new Uint8Array(private_key_der)
1118
+ });
1119
+
1120
+ });
1121
+
1122
+ context.tls_session.on('secureConnect', function(){
1123
+ set_context({
1124
+ connection_status: 1,
1125
+ handshake_done: true
1126
+ });
1127
+ });
1128
+ }
1129
+
1130
+ for(var i in tls_messages){
1131
+ context.tls_session.message(tls_messages[i]);
1132
+ }
1133
+ }
1134
+
1135
+ }
1136
+
1137
+ }
1138
+
1139
+
1140
+ function process_ack_frame(packet_type,frame){
1141
+
1142
+ var acked_ranges=quic_acked_info_to_ranges(frame);
1143
+
1144
+ if(packet_type=='initial'){
1145
+
1146
+ if(flat_ranges.add(context.sending_init_pn_acked_ranges, acked_ranges)==true){
1147
+ //console.log(context.sending_init_pn_acked_ranges);
1148
+ }
1149
+
1150
+ }else if(packet_type=='handshake'){
1151
+
1152
+ if(flat_ranges.add(context.sending_handshake_pn_acked_ranges, acked_ranges)==true){
1153
+ //console.log(context.sending_handshake_pn_acked_ranges);
1154
+ }
1155
+
1156
+ }else if(packet_type=='1rtt'){
1157
+
1158
+ if('largest' in frame && 'delay' in frame){
1159
+ var largest_pn=frame.largest;
1160
+
1161
+ if(context.sending_app_pn_in_flight.has(largest_pn)==true){
1162
+ var now = Math.floor(performance.timeOrigin + performance.now());
1163
+ var ack_delay_raw=frame.delay;
1164
+ var ack_delay_ms = Math.round((ack_delay_raw * Math.pow(2, 3)) / 1000);
1165
+
1166
+ var pn_index = largest_pn - (context.sending_app_pn_base - context.sending_app_pn_history.length);
1167
+
1168
+ if (pn_index >= 0 && pn_index < context.sending_app_pn_history.length) {
1169
+ /*
1170
+ console.log('pn_index: ',pn_index);
1171
+ console.log('sending_app_pn_history.length: ',context.sending_app_pn_history.length);
1172
+ console.log('largest_pn: ',largest_pn);
1173
+ console.log('sending_app_pn_base: ',context.sending_app_pn_base);
1174
+ */
1175
+
1176
+ var start_time=context.sending_app_pn_history[pn_index][0];
1177
+
1178
+
1179
+ var received_time_estimate = now - ack_delay_ms;
1180
+
1181
+ var measured_rtt = now - start_time - ack_delay_ms;
1182
+
1183
+ var sent_bytes_during = 0;
1184
+ var sent_packets_during = 0;
1185
+
1186
+ for (var i2 = pn_index; i2 < context.sending_app_pn_history.length; i2++) {
1187
+ var [ts, size] = context.sending_app_pn_history[i2];
1188
+ if (received_time_estimate >= ts) {
1189
+ sent_bytes_during += size;
1190
+ sent_packets_during++;
1191
+ }
1192
+ }
1193
+
1194
+ var received_bytes_during = 0;
1195
+ var received_packets_during = 0;
1196
+
1197
+ for (var i2 = 0; i2 < context.receiving_app_pn_history.length; i2++) {
1198
+ var [pn_recv, ts_recv, size_recv] = context.receiving_app_pn_history[i2];
1199
+ if (ts_recv > received_time_estimate){
1200
+ break;
1201
+
1202
+ }else if (ts_recv >= start_time) {
1203
+ received_bytes_during += size_recv;
1204
+ received_packets_during++;
1205
+ }
1206
+ }
1207
+
1208
+
1209
+ var last_rtt_record=null;
1210
+ if(context.rtt_history.length>0){
1211
+ last_rtt_record=context.rtt_history[context.rtt_history.length-1];
1212
+ }
1213
+
1214
+ if(last_rtt_record==null || (last_rtt_record[0]!==start_time && last_rtt_record[1]!==received_time_estimate)){
1215
+ context.rtt_history.push([
1216
+ start_time, // 0 - מתי נשלח
1217
+ received_time_estimate, // 1 - מתי התקבל ACK
1218
+ sent_bytes_during, // 2 - כמה נשלח בזמן הזה
1219
+ sent_packets_during, // 3 - כמה פאקטים נשלחו
1220
+ received_bytes_during, // 4 - כמה התקבל באותו זמן
1221
+ received_packets_during, // 5 - כמה פאקטים התקבלו
1222
+ measured_rtt, // 6 - RTT
1223
+ ]);
1224
+ }
1225
+
1226
+
1227
+ //console.log(context);
1228
+ }
1229
+
1230
+ }
1231
+ }
1232
+
1233
+ for (var pn of context.sending_app_pn_in_flight) {
1234
+ var is_ack_in_ranges = false;
1235
+ for (var i2 = 0; i2 < acked_ranges.length; i2 += 2) {
1236
+ var from = acked_ranges[i2];
1237
+ var to = acked_ranges[i2 + 1];
1238
+ if (pn >= from && pn <= to) {
1239
+ is_ack_in_ranges = true;
1240
+ break;
1241
+ }
1242
+ }
1243
+
1244
+ if(is_ack_in_ranges==true){
1245
+ context.sending_app_pn_in_flight.delete(pn);
1246
+
1247
+
1248
+ for(var stream_id in context.sending_streams){
1249
+
1250
+ if('in_flight_ranges' in context.sending_streams[stream_id] && pn in context.sending_streams[stream_id].in_flight_ranges==true){
1251
+
1252
+ if(flat_ranges.add(context.sending_streams[stream_id].acked_ranges, context.sending_streams[stream_id].in_flight_ranges[pn])==true){
1253
+
1254
+ }
1255
+
1256
+ delete context.sending_streams[stream_id].in_flight_ranges[pn];
1257
+
1258
+ if(context.sending_streams[stream_id].acked_ranges.length==2 && typeof context.sending_streams[stream_id].total_size == 'number' && context.sending_streams[stream_id].total_size>0 && context.sending_streams[stream_id].acked_ranges[0]==0 && context.sending_streams[stream_id].acked_ranges[1]==context.sending_streams[stream_id].total_size){
1259
+ //we can delete it...
1260
+ delete context.sending_streams[stream_id];
1261
+ }
1262
+
1263
+ //console.log(context.sending_streams[stream_id]);
1264
+ }
1265
+ }
1266
+
1267
+
1268
+
1269
+ }
1270
+ }
1271
+ }
1272
+ }
1273
+
1274
+ function process_decrypted_quic_packet(packet_type,packet_number,data){
1275
+
1276
+ var ack_eliciting=false;
1277
+
1278
+
1279
+ var frames=parse_quic_frames(data);
1280
+
1281
+ for(var i in frames){
1282
+
1283
+ if(ack_eliciting==false && (frames[i].type=='stream' || frames[i].type=='crypto' || frames[i].type=='new_connection_id' || frames[i].type=='handshake_done' || frames[i].type=='path_challenge' || frames[i].type=='path_response' || frames[i].type=='ping')){
1284
+ ack_eliciting=true;
1285
+ }
1286
+
1287
+
1288
+ if(frames[i].type=='crypto'){
1289
+
1290
+ process_crypto_frame(packet_type,frames[i].offset,frames[i].data);
1291
+
1292
+ }else if(frames[i].type=='stream'){
1293
+
1294
+ process_stream_frame(packet_type,frames[i].id,frames[i].offset,frames[i].data,frames[i].fin);
1295
+
1296
+ }else if(frames[i].type=='reset_stream'){
1297
+
1298
+ //process_reset_stream_frame(packet_type,frames[i].id, frames[i].finalSize, frames[i].error);
1299
+
1300
+ }else if(frames[i].type=='stop_sending'){
1301
+
1302
+
1303
+
1304
+ }else if(frames[i].type=='datagram'){
1305
+
1306
+ ev.emit('datagram',frames[i].contextId,frames[i].data);
1307
+
1308
+ }else if(frames[i].type=='ack'){
1309
+
1310
+ process_ack_frame(packet_type,frames[i]);
1311
+
1312
+ }else{
1313
+
1314
+
1315
+
1316
+ }
1317
+
1318
+ }
1319
+
1320
+ if(ack_eliciting==true){
1321
+ var ack_frame_to_send = [];
1322
+
1323
+ if(packet_type=='initial'){
1324
+
1325
+ ack_frame_to_send.push(build_ack_info_from_ranges(context.receiving_init_pn_ranges, null, 0));
1326
+
1327
+ }else if(packet_type=='handshake'){
1328
+
1329
+ ack_frame_to_send.push(build_ack_info_from_ranges(context.receiving_handshake_pn_ranges, null, 0));
1330
+
1331
+ }else if(packet_type=='1rtt'){
1332
+
1333
+ flat_ranges.add(context.receiving_app_pn_pending_ack, [packet_number,packet_number]);
1334
+
1335
+ prepare_and_send_quic_packet();
1336
+
1337
+ }
1338
+
1339
+
1340
+ if(ack_frame_to_send.length>0){
1341
+ var encoded_frames=encode_quic_frames(ack_frame_to_send);
1342
+ send_quic_packet_frames(packet_type,encoded_frames);
1343
+ }
1344
+ }
1345
+
1346
+ }
1347
+
1348
+
1349
+ function process_quic_packet(data){
1350
+
1351
+ if('version' in data){
1352
+ if(context.version!==data.version){
1353
+ context.version=data.version;
1354
+ }
1355
+ }
1356
+
1357
+ if('dcid' in data && data.dcid && data.dcid.byteLength>0){
1358
+ if(context.original_dcid==null || context.original_dcid.byteLength<=0 || arraybufferEqual(data.dcid.buffer,context.original_dcid.buffer)==false){
1359
+ context.original_dcid=data.dcid;
1360
+ }
1361
+ }
1362
+
1363
+ if('scid' in data && data.scid && data.scid.byteLength>0){
1364
+
1365
+ var is_scid_exist=false;
1366
+ for(var i in context.their_cids){
1367
+ if(arraybufferEqual(data.scid.buffer,context.their_cids[i].buffer)==true){
1368
+ is_scid_exist=true;
1369
+ break;
1370
+ }
1371
+ }
1372
+
1373
+ if(is_scid_exist==false){
1374
+ context.their_cids.push(data.scid);
1375
+ //is_modified=true;
1376
+ }
1377
+ }
1378
+
1379
+ var read_key=null;
1380
+ var read_iv=null;
1381
+ var read_hp=null;
1382
+
1383
+ var largest_pn=-1;
1384
+
1385
+ if(data['type']=='initial'){
1386
+
1387
+ if(context.init_read_key!==null && context.init_read_iv!==null && context.init_read_hp!==null){
1388
+ read_key=context.init_read_key;
1389
+ read_iv=context.init_read_iv;
1390
+ read_hp=context.init_read_hp;
1391
+
1392
+ }else{
1393
+ var d = quic_derive_init_secrets(context.original_dcid,context.version,'read');
1394
+
1395
+ read_key=d.key;
1396
+ read_iv=d.iv;
1397
+ read_hp=d.hp;
1398
+
1399
+ context.init_read_key=d.key;
1400
+ context.init_read_iv=d.iv;
1401
+ context.init_read_hp=d.hp;
1402
+ }
1403
+
1404
+ largest_pn=Number(context.receiving_init_pn_largest)+0;
1405
+
1406
+ }else if(data['type']=='handshake'){
1407
+
1408
+ if(context.tls_session!==null){
1409
+ if(context.handshake_read_key!==null && context.handshake_read_iv!==null && context.handshake_read_hp!==null){
1410
+ read_key=context.handshake_read_key;
1411
+ read_iv=context.handshake_read_iv;
1412
+ read_hp=context.handshake_read_hp;
1413
+
1414
+ }else if(context.tls_session.context.client_handshake_traffic_secret!==null){
1415
+ var d = quic_derive_from_tls_secrets(context.tls_session.context.client_handshake_traffic_secret,TLS_CIPHER_SUITES[context.tls_session.context.selected_cipher_suite].hash);
1416
+
1417
+ read_key=d.key;
1418
+ read_iv=d.iv;
1419
+ read_hp=d.hp;
1420
+
1421
+ context.handshake_read_key=d.key;
1422
+ context.handshake_read_iv=d.iv;
1423
+ context.handshake_read_hp=d.hp;
1424
+
1425
+ }
1426
+
1427
+ largest_pn=Number(context.receiving_handshake_pn_largest)+0;
1428
+ }else{
1429
+ //console.log('tls still not start...');
1430
+ }
1431
+
1432
+ }else if(data['type']=='1rtt'){
1433
+
1434
+ if(context.tls_session!==null){
1435
+
1436
+ if(context.app_read_key!==null && context.app_read_iv!==null && context.app_read_hp!==null){
1437
+ read_key=context.app_read_key;
1438
+ read_iv=context.app_read_iv;
1439
+ read_hp=context.app_read_hp;
1440
+
1441
+ }else if(context.tls_session.context.client_app_traffic_secret!==null){
1442
+
1443
+ var d = quic_derive_from_tls_secrets(context.tls_session.context.client_app_traffic_secret,TLS_CIPHER_SUITES[context.tls_session.context.selected_cipher_suite].hash);
1444
+
1445
+ read_key=d.key;
1446
+ read_iv=d.iv;
1447
+ read_hp=d.hp;
1448
+
1449
+ context.app_read_key=d.key;
1450
+ context.app_read_iv=d.iv;
1451
+ context.app_read_hp=d.hp;
1452
+
1453
+
1454
+ }
1455
+
1456
+ largest_pn=Number(context.receiving_app_pn_largest)+0;
1457
+ }else{
1458
+ //console.log('tls still not start...');
1459
+ }
1460
+
1461
+ }
1462
+
1463
+ if(read_key!==null && read_iv!==null){
1464
+
1465
+ var decrypted_packet = decrypt_quic_packet(data['raw'], read_key, read_iv, read_hp, context.original_dcid,largest_pn);
1466
+
1467
+ if(decrypted_packet && decrypted_packet.plaintext!==null && decrypted_packet.plaintext.byteLength>0){
1468
+
1469
+ var is_new_packet=false;
1470
+
1471
+ if(data['type']=='initial'){
1472
+
1473
+ is_new_packet=flat_ranges.add(context.receiving_init_pn_ranges, [decrypted_packet.packet_number,decrypted_packet.packet_number]);
1474
+
1475
+ if(is_new_packet && context.receiving_init_pn_largest<decrypted_packet.packet_number){
1476
+ context.receiving_init_pn_largest=decrypted_packet.packet_number;
1477
+ }
1478
+
1479
+ }else if(data['type']=='handshake'){
1480
+
1481
+ is_new_packet=flat_ranges.add(context.receiving_handshake_pn_ranges, [decrypted_packet.packet_number,decrypted_packet.packet_number]);
1482
+
1483
+ if(is_new_packet && context.receiving_handshake_pn_largest<decrypted_packet.packet_number){
1484
+ context.receiving_handshake_pn_largest=decrypted_packet.packet_number;
1485
+ }
1486
+
1487
+ }else if(data['type']=='1rtt'){
1488
+
1489
+ is_new_packet=flat_ranges.add(context.receiving_app_pn_ranges, [decrypted_packet.packet_number,decrypted_packet.packet_number]);
1490
+
1491
+ if(is_new_packet && context.receiving_app_pn_largest<decrypted_packet.packet_number){
1492
+ context.receiving_app_pn_largest=decrypted_packet.packet_number;
1493
+ }
1494
+
1495
+
1496
+ if(context.handshake_done_sent==false){
1497
+ context.handshake_done_sent=true;
1498
+
1499
+ send_quic_packet_frames(data['type'],encode_quic_frames([{
1500
+ type: 'handshake_done'
1501
+ }]));
1502
+ }
1503
+
1504
+ }
1505
+
1506
+ if(is_new_packet==true){
1507
+
1508
+ process_decrypted_quic_packet(data['type'],decrypted_packet.packet_number,decrypted_packet.plaintext);
1509
+
1510
+ }
1511
+
1512
+
1513
+ }else{
1514
+ //console.log('decrypted fail...'+data['type']);
1515
+ }
1516
+ }else{
1517
+ //console.log('no read key...');
1518
+ }
1519
+ }
1520
+
1521
+
1522
+
1523
+ // === API ציבורי (ללא חשיפת session) ===
1524
+ var api = {
1525
+ on: function(name, fn){ ev.on(name, fn); },
1526
+
1527
+ packet: process_quic_packet,
1528
+
1529
+ stream: stream_write,
1530
+
1531
+ end: function(data){
1532
+ if (context.destroyed) return;
1533
+ if (typeof data !== 'undefined' && data !== null) api.write(data);
1534
+ try { context.transport && context.transport.end && context.transport.end(); } catch(e){}
1535
+ },
1536
+
1537
+ destroy: function(){
1538
+ if (context.destroyed) return;
1539
+ context.destroyed = true;
1540
+ try { context.transport && context.transport.destroy && context.transport.destroy(); } catch(e){}
1541
+ }
1542
+ };
1543
+
1544
+ for (var k in api) if (Object.prototype.hasOwnProperty.call(api,k)) this[k] = api[k];
1545
+
1546
+
1547
+ return this;
1548
+ }
1549
+
1550
+ export default QUICSocket;