quico 0.2.0 → 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/index.js +5 -2
- package/package.json +2 -2
- package/src/agent.js +9 -1
- package/src/client.js +6 -1
- package/src/crypto.js +20 -5
- package/src/h3.js +52 -43
- package/src/h3_client.js +93 -78
- package/src/h3_server.js +52 -183
- package/src/quic_connection.js +1221 -121
- package/src/quic_server.js +314 -0
- package/src/quic_socket.js +76 -0
- package/src/server.js +30 -22
- package/src/tls_bridge.js +74 -12
- package/src/transport.js +50 -34
- package/src/utils.js +59 -4
- package/src/webtransport.js +25 -78
package/src/transport.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import {
|
|
16
|
+
DEBUG,
|
|
16
17
|
concatUint8Arrays,
|
|
17
18
|
writeVarInt,
|
|
18
19
|
readVarInt
|
|
@@ -142,7 +143,7 @@ function parse_quic_datagram(array) {
|
|
|
142
143
|
: array.slice(start, end);
|
|
143
144
|
|
|
144
145
|
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
|
+
if (DEBUG) 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
|
}
|
|
147
148
|
|
|
148
149
|
packets.push(pkt);
|
|
@@ -201,18 +202,25 @@ function encode_quic_frames(frames) {
|
|
|
201
202
|
} else if (frame.type === 'stream') {
|
|
202
203
|
var typeByte = 0x08;
|
|
203
204
|
var hasOffset = (frame.offset != null && frame.offset > 0);
|
|
204
|
-
var hasLen = (frame.data && frame.data.length > 0);
|
|
205
205
|
var hasFin = !!frame.fin;
|
|
206
|
-
|
|
206
|
+
var dataLen = (frame.data && frame.data.length) ? frame.data.length : 0;
|
|
207
|
+
|
|
208
|
+
// ALWAYS emit the LEN field. A STREAM frame without the LEN bit means
|
|
209
|
+
// "data runs to the end of the packet"; if anything follows it in the
|
|
210
|
+
// packet (another frame — e.g. a zero-length FIN-only batched with
|
|
211
|
+
// others — or padding), the peer reads those trailing bytes as stream
|
|
212
|
+
// data and the offset overshoots the declared final size
|
|
213
|
+
// (FINAL_SIZE / PROTOCOL_VIOLATION 0xa). An explicit length is always
|
|
214
|
+
// safe and self-delimiting.
|
|
207
215
|
if (hasOffset) typeByte |= 0x04;
|
|
208
|
-
|
|
216
|
+
typeByte |= 0x02; // LEN — always present
|
|
209
217
|
if (hasFin) typeByte |= 0x01;
|
|
210
218
|
|
|
211
219
|
parts.push(concatUint8Arrays([
|
|
212
220
|
new Uint8Array([typeByte]),
|
|
213
221
|
writeVarInt(frame.id),
|
|
214
222
|
hasOffset ? writeVarInt(frame.offset) : new Uint8Array(0),
|
|
215
|
-
|
|
223
|
+
writeVarInt(dataLen),
|
|
216
224
|
frame.data || new Uint8Array(0)
|
|
217
225
|
]));
|
|
218
226
|
|
|
@@ -315,18 +323,14 @@ function encode_quic_frames(frames) {
|
|
|
315
323
|
]));
|
|
316
324
|
|
|
317
325
|
} else if (frame.type === 'datagram') {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
])
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
new Uint8Array([0x30]),
|
|
327
|
-
frame.data
|
|
328
|
-
]));
|
|
329
|
-
}
|
|
326
|
+
// Emit the 0x30 form (no Length): the datagram data runs to the end of
|
|
327
|
+
// the packet. QUICO sends each DATAGRAM as the sole/last frame of its
|
|
328
|
+
// packet, so the length-prefixed 0x31 form is not needed here. (The
|
|
329
|
+
// parser above accepts both forms on receive.)
|
|
330
|
+
parts.push(concatUint8Arrays([
|
|
331
|
+
new Uint8Array([0x30]),
|
|
332
|
+
frame.data
|
|
333
|
+
]));
|
|
330
334
|
}
|
|
331
335
|
}
|
|
332
336
|
|
|
@@ -498,13 +502,21 @@ function parse_quic_frames(buf) {
|
|
|
498
502
|
frames.push({ type: 'handshake_done' });
|
|
499
503
|
|
|
500
504
|
} else if (type === 0x30 || type === 0x31) {
|
|
501
|
-
|
|
505
|
+
// RFC 9221 DATAGRAM frame. The 0x31 form carries an explicit Length
|
|
506
|
+
// varint and the datagram data is exactly that many bytes; the 0x30 form
|
|
507
|
+
// has no Length and the data runs to the end of the packet (so a 0x30
|
|
508
|
+
// DATAGRAM must be the last frame). The optional field is a Length — not
|
|
509
|
+
// a "context ID" (that draft-era concept was removed before RFC 9221).
|
|
510
|
+
var data;
|
|
502
511
|
if (type === 0x31) {
|
|
503
|
-
var
|
|
504
|
-
|
|
512
|
+
var len = safeReadVarInt(); if (!len) break;
|
|
513
|
+
var end = offset + len.value;
|
|
514
|
+
if (end > buf.length) break; // truncated / invalid length
|
|
515
|
+
data = buf.slice(offset, end); offset = end;
|
|
516
|
+
} else {
|
|
517
|
+
data = buf.slice(offset); offset = buf.length;
|
|
505
518
|
}
|
|
506
|
-
|
|
507
|
-
frames.push({ type: 'datagram', contextId: contextId, data: data });
|
|
519
|
+
frames.push({ type: 'datagram', data: data });
|
|
508
520
|
|
|
509
521
|
} else {
|
|
510
522
|
// Unknown frame — stop
|
|
@@ -604,24 +616,28 @@ function parse_transport_params(buf, start) {
|
|
|
604
616
|
|
|
605
617
|
var id = idVar.value;
|
|
606
618
|
|
|
619
|
+
// Safe inner varint: a malformed param could carry an empty/short value,
|
|
620
|
+
// in which case readVarInt returns null — never dereference .value blindly.
|
|
621
|
+
function vi(bytes) { var r = readVarInt(bytes, 0); return r ? r.value : 0; }
|
|
622
|
+
|
|
607
623
|
switch (id) {
|
|
608
624
|
case 0x00: out.original_destination_connection_id = valueBytes; break;
|
|
609
|
-
case 0x01: out.max_idle_timeout =
|
|
625
|
+
case 0x01: out.max_idle_timeout = vi(valueBytes); break;
|
|
610
626
|
case 0x02: out.stateless_reset_token = valueBytes; break;
|
|
611
|
-
case 0x03: out.max_udp_payload_size =
|
|
612
|
-
case 0x04: out.initial_max_data =
|
|
613
|
-
case 0x05: out.initial_max_stream_data_bidi_local =
|
|
614
|
-
case 0x06: out.initial_max_stream_data_bidi_remote =
|
|
615
|
-
case 0x07: out.initial_max_stream_data_uni =
|
|
616
|
-
case 0x08: out.initial_max_streams_bidi =
|
|
617
|
-
case 0x09: out.initial_max_streams_uni =
|
|
618
|
-
case 0x0a: out.ack_delay_exponent =
|
|
619
|
-
case 0x0b: out.max_ack_delay =
|
|
627
|
+
case 0x03: out.max_udp_payload_size = vi(valueBytes); break;
|
|
628
|
+
case 0x04: out.initial_max_data = vi(valueBytes); break;
|
|
629
|
+
case 0x05: out.initial_max_stream_data_bidi_local = vi(valueBytes); break;
|
|
630
|
+
case 0x06: out.initial_max_stream_data_bidi_remote = vi(valueBytes); break;
|
|
631
|
+
case 0x07: out.initial_max_stream_data_uni = vi(valueBytes); break;
|
|
632
|
+
case 0x08: out.initial_max_streams_bidi = vi(valueBytes); break;
|
|
633
|
+
case 0x09: out.initial_max_streams_uni = vi(valueBytes); break;
|
|
634
|
+
case 0x0a: out.ack_delay_exponent = vi(valueBytes); break;
|
|
635
|
+
case 0x0b: out.max_ack_delay = vi(valueBytes); break;
|
|
620
636
|
case 0x0c: out.disable_active_migration = true; break;
|
|
621
|
-
case 0x0e: out.active_connection_id_limit =
|
|
637
|
+
case 0x0e: out.active_connection_id_limit = vi(valueBytes); break;
|
|
622
638
|
case 0x0f: out.initial_source_connection_id = valueBytes; break;
|
|
623
639
|
case 0x10: out.retry_source_connection_id = valueBytes; break;
|
|
624
|
-
case 0x20: out.max_datagram_frame_size =
|
|
640
|
+
case 0x20: out.max_datagram_frame_size = vi(valueBytes); break;
|
|
625
641
|
default:
|
|
626
642
|
if (!out.unknown) out.unknown = [];
|
|
627
643
|
out.unknown.push({ id: id, bytes: valueBytes });
|
package/src/utils.js
CHANGED
|
@@ -13,6 +13,16 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
// ============================================================
|
|
17
|
+
// Debug flag — set QUICO_DEBUG=1 to enable verbose logging.
|
|
18
|
+
// Read once at module load. Call sites guard with `if (DEBUG)`
|
|
19
|
+
// so the (expensive) log-string construction is skipped entirely
|
|
20
|
+
// when debugging is off.
|
|
21
|
+
// ============================================================
|
|
22
|
+
|
|
23
|
+
var DEBUG = !!(typeof process !== 'undefined' && process.env && process.env.QUICO_DEBUG);
|
|
24
|
+
|
|
25
|
+
|
|
16
26
|
// ============================================================
|
|
17
27
|
// Emitter — lightweight event emitter (single shared copy)
|
|
18
28
|
// ============================================================
|
|
@@ -124,10 +134,12 @@ function readVarInt(array, offset) {
|
|
|
124
134
|
array[offset + 7]
|
|
125
135
|
) >>> 0;
|
|
126
136
|
var full = BigInt(hi) * 4294967296n + BigInt(lo);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
137
|
+
// Always return a Number. QUIC fields (lengths, offsets, IDs, limits) never
|
|
138
|
+
// legitimately exceed 2^53, and a malicious 8-byte value that does will be
|
|
139
|
+
// rejected by the parsers' bounds checks anyway. Returning a BigInt here
|
|
140
|
+
// would throw "Cannot mix BigInt and other types" the moment the value is
|
|
141
|
+
// combined with a Number offset during parsing.
|
|
142
|
+
return { value: Number(full), byteLength: 8 };
|
|
131
143
|
}
|
|
132
144
|
|
|
133
145
|
return null;
|
|
@@ -164,6 +176,46 @@ function uint8Equal(a, b) {
|
|
|
164
176
|
}
|
|
165
177
|
|
|
166
178
|
|
|
179
|
+
// ============================================================
|
|
180
|
+
// WebTransport framing helpers (draft-ietf-webtrans-http3 / RFC 9297)
|
|
181
|
+
//
|
|
182
|
+
// Both client (webtransport.js) and server (h3.js) build the same
|
|
183
|
+
// byte layouts, so they live here to avoid duplicating the prefix
|
|
184
|
+
// construction across the stream and datagram send paths.
|
|
185
|
+
// ============================================================
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Build a WebTransport stream header: VarInt(streamType) + VarInt(sessionId) [+ data].
|
|
189
|
+
* streamType is 0x41 for a bidirectional WT stream, 0x54 for a unidirectional one.
|
|
190
|
+
* The header prefixes the FIRST chunk on a WT stream (or a lone FIN frame); pass
|
|
191
|
+
* `data` for "header + payload", omit it for a header-only (e.g. FIN) frame.
|
|
192
|
+
*/
|
|
193
|
+
function wt_stream_header(isBidi, sessionId, data) {
|
|
194
|
+
var typePrefix = writeVarInt(isBidi ? 0x41 : 0x54);
|
|
195
|
+
var sessionPrefix = writeVarInt(sessionId);
|
|
196
|
+
var dataLen = (data && data.byteLength) ? data.byteLength : 0;
|
|
197
|
+
var out = new Uint8Array(typePrefix.length + sessionPrefix.length + dataLen);
|
|
198
|
+
out.set(typePrefix, 0);
|
|
199
|
+
out.set(sessionPrefix, typePrefix.length);
|
|
200
|
+
if (dataLen) out.set(data, typePrefix.length + sessionPrefix.length);
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Build a WebTransport datagram payload: VarInt(quarter_stream_id) + data.
|
|
206
|
+
* The quarter-stream-id is the session's CONNECT stream id divided by 4
|
|
207
|
+
* (RFC 9297 §2.1). Pass the session stream id; the division is done here.
|
|
208
|
+
*/
|
|
209
|
+
function wt_datagram_payload(sessionStreamId, data) {
|
|
210
|
+
var prefix = writeVarInt(Math.floor(sessionStreamId / 4));
|
|
211
|
+
var dataLen = (data && data.byteLength) ? data.byteLength : 0;
|
|
212
|
+
var out = new Uint8Array(prefix.length + dataLen);
|
|
213
|
+
out.set(prefix, 0);
|
|
214
|
+
if (dataLen) out.set(data, prefix.length);
|
|
215
|
+
return out;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
|
|
167
219
|
// ============================================================
|
|
168
220
|
// ACK range helpers (flat [from,to, from,to, ...] format)
|
|
169
221
|
// ============================================================
|
|
@@ -280,11 +332,14 @@ function ranges_to_ack_frame(flatRanges, ecnStats, ackDelay) {
|
|
|
280
332
|
|
|
281
333
|
|
|
282
334
|
export {
|
|
335
|
+
DEBUG,
|
|
283
336
|
Emitter,
|
|
284
337
|
writeVarInt,
|
|
285
338
|
readVarInt,
|
|
286
339
|
concatUint8Arrays,
|
|
287
340
|
uint8Equal,
|
|
341
|
+
wt_stream_header,
|
|
342
|
+
wt_datagram_payload,
|
|
288
343
|
ack_frame_to_ranges,
|
|
289
344
|
ranges_to_ack_frame
|
|
290
345
|
};
|
package/src/webtransport.js
CHANGED
|
@@ -14,11 +14,10 @@
|
|
|
14
14
|
|
|
15
15
|
import { Duplex, Readable, Writable } from 'node:stream';
|
|
16
16
|
import { EventEmitter } from 'node:events';
|
|
17
|
-
import dgram from 'node:dgram';
|
|
18
17
|
import dns from 'node:dns';
|
|
19
|
-
import { QUICConnection } from './quic_connection.js';
|
|
20
18
|
import { H3Connection } from './h3.js';
|
|
21
|
-
import {
|
|
19
|
+
import { createQuicClientSocket } from './quic_socket.js';
|
|
20
|
+
import { readVarInt, concatUint8Arrays, wt_stream_header, wt_datagram_payload } from './utils.js';
|
|
22
21
|
|
|
23
22
|
|
|
24
23
|
// ============================================================
|
|
@@ -101,13 +100,7 @@ class WebTransportBidiStream extends Duplex {
|
|
|
101
100
|
// First write on this stream: prepend VarInt(0x41) + VarInt(session_id)
|
|
102
101
|
if (!this._prefixSent) {
|
|
103
102
|
this._prefixSent = true;
|
|
104
|
-
|
|
105
|
-
var sessionPrefix = writeVarInt(this._sessionId);
|
|
106
|
-
var combined = new Uint8Array(typePrefix.length + sessionPrefix.length + chunk.byteLength);
|
|
107
|
-
combined.set(typePrefix, 0);
|
|
108
|
-
combined.set(sessionPrefix, typePrefix.length);
|
|
109
|
-
combined.set(chunk, typePrefix.length + sessionPrefix.length);
|
|
110
|
-
this._quic.sendStream(this._streamId, combined, false);
|
|
103
|
+
this._quic.sendStream(this._streamId, wt_stream_header(true, this._sessionId, chunk), false);
|
|
111
104
|
} else {
|
|
112
105
|
this._quic.sendStream(this._streamId, chunk, false);
|
|
113
106
|
}
|
|
@@ -118,12 +111,7 @@ class WebTransportBidiStream extends Duplex {
|
|
|
118
111
|
// Send FIN (with type + session_id prefix if nothing was written yet)
|
|
119
112
|
if (!this._prefixSent) {
|
|
120
113
|
this._prefixSent = true;
|
|
121
|
-
|
|
122
|
-
var sessionPrefix = writeVarInt(this._sessionId);
|
|
123
|
-
var header = new Uint8Array(typePrefix.length + sessionPrefix.length);
|
|
124
|
-
header.set(typePrefix, 0);
|
|
125
|
-
header.set(sessionPrefix, typePrefix.length);
|
|
126
|
-
this._quic.sendStream(this._streamId, header, true);
|
|
114
|
+
this._quic.sendStream(this._streamId, wt_stream_header(true, this._sessionId), true);
|
|
127
115
|
} else {
|
|
128
116
|
this._quic.sendStream(this._streamId, new Uint8Array(0), true);
|
|
129
117
|
}
|
|
@@ -168,13 +156,7 @@ class WebTransportUniWriteStream extends Writable {
|
|
|
168
156
|
if (!this._headerSent) {
|
|
169
157
|
this._headerSent = true;
|
|
170
158
|
// Uni stream: VarInt(0x54) (WEBTRANSPORT_STREAM) + VarInt(session_id) + data
|
|
171
|
-
|
|
172
|
-
var sessionPrefix = writeVarInt(this._sessionId);
|
|
173
|
-
var header = new Uint8Array(typePrefix.length + sessionPrefix.length + chunk.byteLength);
|
|
174
|
-
header.set(typePrefix, 0);
|
|
175
|
-
header.set(sessionPrefix, typePrefix.length);
|
|
176
|
-
header.set(chunk, typePrefix.length + sessionPrefix.length);
|
|
177
|
-
this._quic.sendStream(this._streamId, header, false);
|
|
159
|
+
this._quic.sendStream(this._streamId, wt_stream_header(false, this._sessionId, chunk), false);
|
|
178
160
|
} else {
|
|
179
161
|
this._quic.sendStream(this._streamId, chunk, false);
|
|
180
162
|
}
|
|
@@ -184,12 +166,7 @@ class WebTransportUniWriteStream extends Writable {
|
|
|
184
166
|
_final(callback) {
|
|
185
167
|
if (!this._headerSent) {
|
|
186
168
|
this._headerSent = true;
|
|
187
|
-
|
|
188
|
-
var sessionPrefix = writeVarInt(this._sessionId);
|
|
189
|
-
var header = new Uint8Array(typePrefix.length + sessionPrefix.length);
|
|
190
|
-
header.set(typePrefix, 0);
|
|
191
|
-
header.set(sessionPrefix, typePrefix.length);
|
|
192
|
-
this._quic.sendStream(this._streamId, header, true);
|
|
169
|
+
this._quic.sendStream(this._streamId, wt_stream_header(false, this._sessionId), true);
|
|
193
170
|
} else {
|
|
194
171
|
this._quic.sendStream(this._streamId, new Uint8Array(0), true);
|
|
195
172
|
}
|
|
@@ -350,43 +327,31 @@ class WebTransport extends EventEmitter {
|
|
|
350
327
|
_startQuic(remoteIp) {
|
|
351
328
|
var self = this;
|
|
352
329
|
var remotePort = this._port;
|
|
353
|
-
var isIPv6 = remoteIp.indexOf(':') >= 0;
|
|
354
330
|
|
|
355
|
-
this._udpSocket =
|
|
331
|
+
this._udpSocket = createQuicClientSocket({
|
|
332
|
+
remoteIp: remoteIp,
|
|
333
|
+
remotePort: remotePort,
|
|
334
|
+
hostname: this._hostname,
|
|
356
335
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
this._udpSocket.on('error', function (err) {
|
|
362
|
-
if (self._state === 'connecting') self._readyReject(err);
|
|
363
|
-
self.emit('error', err);
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
this._udpSocket.bind(0, function () {
|
|
367
|
-
self._quic = new QUICConnection({
|
|
368
|
-
isServer: false,
|
|
369
|
-
hostname: self._hostname
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
self._quic.on('packet', function (data) {
|
|
373
|
-
self._udpSocket.send(data, remotePort, remoteIp);
|
|
374
|
-
});
|
|
336
|
+
onError: function (err) {
|
|
337
|
+
if (self._state === 'connecting') self._readyReject(err);
|
|
338
|
+
self.emit('error', err);
|
|
339
|
+
},
|
|
375
340
|
|
|
376
|
-
|
|
341
|
+
onConnect: function (q, s) {
|
|
342
|
+
self._quic = q;
|
|
343
|
+
self._udpSocket = s;
|
|
377
344
|
self._h3 = new H3Connection({ quicConnection: self._quic, isServer: false, enableWebTransport: true });
|
|
378
345
|
self._setupH3Handlers();
|
|
379
346
|
self._sendConnect();
|
|
380
|
-
}
|
|
347
|
+
},
|
|
381
348
|
|
|
382
|
-
|
|
349
|
+
onClose: function () {
|
|
383
350
|
self._state = 'closed';
|
|
384
351
|
self._closedResolve();
|
|
385
352
|
self.emit('close');
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
self._quic.connect();
|
|
353
|
+
// The helper closes the UDP socket.
|
|
354
|
+
}
|
|
390
355
|
});
|
|
391
356
|
}
|
|
392
357
|
|
|
@@ -424,7 +389,7 @@ class WebTransport extends EventEmitter {
|
|
|
424
389
|
});
|
|
425
390
|
|
|
426
391
|
// Datagrams — QUIC DATAGRAM payload = VarInt(quarter_stream_id) + app data (RFC 9297)
|
|
427
|
-
this._quic.on('datagram', function (
|
|
392
|
+
this._quic.on('datagram', function (rawData) {
|
|
428
393
|
if (!rawData || rawData.byteLength === 0) return;
|
|
429
394
|
|
|
430
395
|
// Always parse VarInt quarter_stream_id from payload
|
|
@@ -484,7 +449,7 @@ class WebTransport extends EventEmitter {
|
|
|
484
449
|
buf.totalLen += data.byteLength;
|
|
485
450
|
|
|
486
451
|
// Try to parse prefix
|
|
487
|
-
var combined = buf.totalLen === data.byteLength ? data :
|
|
452
|
+
var combined = buf.totalLen === data.byteLength ? data : concatUint8Arrays(buf.chunks);
|
|
488
453
|
var isBidi = (streamId & 0x2) === 0;
|
|
489
454
|
var offset = 0;
|
|
490
455
|
|
|
@@ -552,18 +517,6 @@ class WebTransport extends EventEmitter {
|
|
|
552
517
|
}
|
|
553
518
|
}
|
|
554
519
|
|
|
555
|
-
_combineChunks(chunks, totalLen) {
|
|
556
|
-
if (chunks.length === 1) return chunks[0];
|
|
557
|
-
var out = new Uint8Array(totalLen);
|
|
558
|
-
var off = 0;
|
|
559
|
-
for (var i = 0; i < chunks.length; i++) {
|
|
560
|
-
out.set(chunks[i], off);
|
|
561
|
-
off += chunks[i].byteLength;
|
|
562
|
-
}
|
|
563
|
-
return out;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
|
|
567
520
|
// ---- Send CONNECT ----
|
|
568
521
|
|
|
569
522
|
_sendConnect() {
|
|
@@ -660,14 +613,8 @@ class WebTransport extends EventEmitter {
|
|
|
660
613
|
if (typeof data === 'string') data = Buffer.from(data);
|
|
661
614
|
if (Buffer.isBuffer(data)) data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
662
615
|
|
|
663
|
-
//
|
|
664
|
-
var
|
|
665
|
-
|
|
666
|
-
// Build datagram: VarInt(contextId) + payload
|
|
667
|
-
var contextPrefix = writeVarInt(contextId);
|
|
668
|
-
var frame = new Uint8Array(contextPrefix.length + data.byteLength);
|
|
669
|
-
frame.set(contextPrefix, 0);
|
|
670
|
-
frame.set(data, contextPrefix.length);
|
|
616
|
+
// Build datagram: VarInt(quarter_stream_id) + payload (RFC 9297)
|
|
617
|
+
var frame = wt_datagram_payload(this._sessionId, data);
|
|
671
618
|
|
|
672
619
|
// Send via QUIC DATAGRAM frame
|
|
673
620
|
if (this._quic && this._quic.sendDatagram) {
|