quico 0.4.0 → 0.7.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.
@@ -0,0 +1,582 @@
1
+ /*
2
+ * quico: HTTP/3 and QUIC implementation for Node.js
3
+ * Copyright 2025 colocohen
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at:
8
+ *
9
+ * https://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * This file is part of the open-source project hosted at:
12
+ * https://github.com/colocohen/quico
13
+ */
14
+
15
+
16
+ // ============================================================
17
+ // HTTP/2 framing (RFC 9113 §4-§6).
18
+ //
19
+ // Pure codec: bytes in, frame objects out, no connection state. The state
20
+ // machine, flow control and HPACK live in h2.js, exactly as h1.js is a parser
21
+ // and h1_server is the thing that owns a socket.
22
+ //
23
+ // Every frame is a 9-byte header followed by a payload:
24
+ //
25
+ // +-----------------------------------------------+
26
+ // | Length (24) |
27
+ // +---------------+---------------+---------------+
28
+ // | Type (8) | Flags (8) |
29
+ // +-+-------------+---------------+-------------------------------+
30
+ // |R| Stream Identifier (31) |
31
+ // +=+=============================================================+
32
+ // | Frame Payload (0...) ...
33
+ //
34
+ // Note the R bit: the stream id is 31 bits, not 32. Reading it as a uint32
35
+ // makes every id with the high bit set enormous and negative-ish, and RFC
36
+ // 9113 §4.1 says a receiver MUST ignore that bit rather than reject the
37
+ // frame — so it is masked off here, once, for every frame type.
38
+ // ============================================================
39
+
40
+ var FRAME_HEADER_SIZE = 9;
41
+
42
+ var TYPE = {
43
+ DATA: 0x0,
44
+ HEADERS: 0x1,
45
+ PRIORITY: 0x2,
46
+ RST_STREAM: 0x3,
47
+ SETTINGS: 0x4,
48
+ PUSH_PROMISE: 0x5,
49
+ PING: 0x6,
50
+ GOAWAY: 0x7,
51
+ WINDOW_UPDATE: 0x8,
52
+ CONTINUATION: 0x9,
53
+ // RFC 9218 §7.1: a stream's priority after its request, client to server,
54
+ // on stream 0 — the scheme that replaced PRIORITY (RFC 9113 §5.3.2).
55
+ PRIORITY_UPDATE: 0x10
56
+ };
57
+
58
+ var TYPE_NAME = {};
59
+ for (var _t in TYPE) TYPE_NAME[TYPE[_t]] = _t;
60
+
61
+ var FLAG = {
62
+ END_STREAM: 0x01, // DATA, HEADERS
63
+ ACK: 0x01, // SETTINGS, PING — same bit, different meaning
64
+ END_HEADERS: 0x04, // HEADERS, PUSH_PROMISE, CONTINUATION
65
+ PADDED: 0x08, // DATA, HEADERS, PUSH_PROMISE
66
+ PRIORITY: 0x20 // HEADERS
67
+ };
68
+
69
+ // RFC 9113 §7.
70
+ var ERROR = {
71
+ NO_ERROR: 0x0,
72
+ PROTOCOL_ERROR: 0x1,
73
+ INTERNAL_ERROR: 0x2,
74
+ FLOW_CONTROL_ERROR: 0x3,
75
+ SETTINGS_TIMEOUT: 0x4,
76
+ STREAM_CLOSED: 0x5,
77
+ FRAME_SIZE_ERROR: 0x6,
78
+ REFUSED_STREAM: 0x7,
79
+ CANCEL: 0x8,
80
+ COMPRESSION_ERROR: 0x9,
81
+ CONNECT_ERROR: 0xa,
82
+ ENHANCE_YOUR_CALM: 0xb,
83
+ INADEQUATE_SECURITY: 0xc,
84
+ HTTP_1_1_REQUIRED: 0xd
85
+ };
86
+
87
+ var ERROR_NAME = {};
88
+ for (var _e in ERROR) ERROR_NAME[ERROR[_e]] = _e;
89
+
90
+ // RFC 9113 §6.5.2.
91
+ var SETTINGS = {
92
+ HEADER_TABLE_SIZE: 0x1,
93
+ ENABLE_PUSH: 0x2,
94
+ MAX_CONCURRENT_STREAMS: 0x3,
95
+ INITIAL_WINDOW_SIZE: 0x4,
96
+ MAX_FRAME_SIZE: 0x5,
97
+ MAX_HEADER_LIST_SIZE: 0x6,
98
+ // RFC 8441 §3. A server that sends this with the value 1 is telling clients
99
+ // they may use extended CONNECT — a CONNECT request carrying a `:protocol`
100
+ // pseudo-header — which is how WebSocket runs over HTTP/2. Without it, a
101
+ // browser will not even try, and opens a separate HTTP/1.1 connection
102
+ // instead.
103
+ ENABLE_CONNECT_PROTOCOL: 0x8,
104
+ // RFC 9218 §2.1: 1 — this endpoint ignores RFC 7540's priority tree (as
105
+ // RFC 9113 lets it), and reads RFC 9218's signals instead.
106
+ NO_RFC7540_PRIORITIES: 0x9
107
+ };
108
+
109
+ var SETTINGS_NAME = {};
110
+ for (var _s in SETTINGS) SETTINGS_NAME[SETTINGS[_s]] = _s;
111
+
112
+ // The connection preface a client sends before anything else (§3.4). It is
113
+ // deliberately something an HTTP/1.1 server cannot mistake for a request, so
114
+ // that speaking h2 to a server that does not know it fails immediately rather
115
+ // than being half-understood.
116
+ var PREFACE = Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n', 'latin1');
117
+
118
+ var MAX_WINDOW = 2147483647; // 2^31 - 1
119
+ var DEFAULT_MAX_FRAME_SIZE = 16384; // §6.5.2, also the minimum allowed
120
+ var MAX_ALLOWED_FRAME_SIZE = 16777215;
121
+ var DEFAULT_WINDOW_SIZE = 65535;
122
+
123
+
124
+ /**
125
+ * A protocol failure.
126
+ *
127
+ * `stream` distinguishes the two kinds RFC 9113 §5.4 defines and never mixes:
128
+ * a CONNECTION error (stream === 0) ends everything with GOAWAY, a STREAM
129
+ * error resets one stream with RST_STREAM and leaves the rest running.
130
+ * Choosing wrongly in either direction is a real bug — tearing down a
131
+ * connection over one malformed request is a denial of service against the
132
+ * other streams on it, and continuing after a framing error means every
133
+ * subsequent frame is read at the wrong offset.
134
+ */
135
+ function h2Error(message, code, streamId) {
136
+ var err = new Error(message);
137
+ err.code = 'H2_' + (ERROR_NAME[code] || 'PROTOCOL_ERROR');
138
+ err.h2Code = code === undefined ? ERROR.PROTOCOL_ERROR : code;
139
+ err.streamId = streamId || 0;
140
+ return err;
141
+ }
142
+
143
+
144
+ // ============================================================
145
+ // Decoding
146
+ // ============================================================
147
+
148
+ /**
149
+ * Read one frame from `buf` at `offset`.
150
+ *
151
+ * Returns null when the buffer does not yet hold a whole frame — a partial
152
+ * frame is the normal state of affairs on a TCP stream, not an error.
153
+ * Throws h2Error for anything malformed.
154
+ */
155
+ function readFrame(buf, offset, maxFrameSize) {
156
+ offset = offset || 0;
157
+ maxFrameSize = maxFrameSize || DEFAULT_MAX_FRAME_SIZE;
158
+
159
+ if (buf.length - offset < FRAME_HEADER_SIZE) return null;
160
+
161
+ var length = (buf[offset] << 16) | (buf[offset + 1] << 8) | buf[offset + 2];
162
+ var type = buf[offset + 3];
163
+ var flags = buf[offset + 4];
164
+ // Mask the reserved bit (§4.1).
165
+ var streamId = buf.readUInt32BE(offset + 5) & 0x7fffffff;
166
+
167
+ if (length > maxFrameSize) {
168
+ // §4.2. Checked before waiting for the payload: otherwise a peer could
169
+ // announce a 16 MB frame and make us buffer it before we objected.
170
+ throw h2Error('frame of ' + length + ' bytes exceeds SETTINGS_MAX_FRAME_SIZE ' +
171
+ maxFrameSize, ERROR.FRAME_SIZE_ERROR);
172
+ }
173
+
174
+ if (buf.length - offset < FRAME_HEADER_SIZE + length) return null;
175
+
176
+ var payload = buf.subarray(offset + FRAME_HEADER_SIZE, offset + FRAME_HEADER_SIZE + length);
177
+ var frame;
178
+ try {
179
+ frame = parsePayload(type, flags, streamId, payload);
180
+ } catch (err) {
181
+ // The frame is whole, so its extent is known: a caller that answers a
182
+ // STREAM error with RST_STREAM skips exactly this frame and reads on.
183
+ err.consumed = FRAME_HEADER_SIZE + length;
184
+ throw err;
185
+ }
186
+ frame.length = length;
187
+ frame.consumed = FRAME_HEADER_SIZE + length;
188
+ return frame;
189
+ }
190
+
191
+
192
+ function parsePayload(type, flags, streamId, payload) {
193
+ var frame = {
194
+ type: type,
195
+ typeName: TYPE_NAME[type] || ('UNKNOWN_0x' + type.toString(16)),
196
+ flags: flags,
197
+ streamId: streamId
198
+ };
199
+
200
+ switch (type) {
201
+
202
+ case TYPE.DATA: {
203
+ if (streamId === 0) {
204
+ throw h2Error('DATA on stream 0', ERROR.PROTOCOL_ERROR);
205
+ }
206
+ var d = stripPadding(payload, flags);
207
+ frame.data = d;
208
+ frame.endStream = (flags & FLAG.END_STREAM) !== 0;
209
+ return frame;
210
+ }
211
+
212
+ case TYPE.HEADERS: {
213
+ if (streamId === 0) {
214
+ throw h2Error('HEADERS on stream 0', ERROR.PROTOCOL_ERROR);
215
+ }
216
+ var p = stripPadding(payload, flags);
217
+ var pos = 0;
218
+ if (flags & FLAG.PRIORITY) {
219
+ if (p.length < 5) {
220
+ throw h2Error('HEADERS with PRIORITY is shorter than 5 bytes',
221
+ ERROR.FRAME_SIZE_ERROR);
222
+ }
223
+ var dep = p.readUInt32BE(0);
224
+ frame.exclusive = (dep & 0x80000000) !== 0;
225
+ frame.dependency = dep & 0x7fffffff;
226
+ frame.weight = p[4] + 1;
227
+ // §5.3.1: a stream cannot depend on itself. RFC 9113 deprecates
228
+ // priority signalling but keeps this as a stream error.
229
+ if (frame.dependency === streamId) {
230
+ throw h2Error('stream depends on itself', ERROR.PROTOCOL_ERROR, streamId);
231
+ }
232
+ pos = 5;
233
+ }
234
+ frame.headerBlock = p.subarray(pos);
235
+ frame.endStream = (flags & FLAG.END_STREAM) !== 0;
236
+ frame.endHeaders = (flags & FLAG.END_HEADERS) !== 0;
237
+ return frame;
238
+ }
239
+
240
+ case TYPE.PRIORITY: {
241
+ if (streamId === 0) throw h2Error('PRIORITY on stream 0', ERROR.PROTOCOL_ERROR);
242
+ // A stream error, not a connection error (§6.3) — the frame is
243
+ // self-contained, so the connection is still readable.
244
+ if (payload.length !== 5) {
245
+ throw h2Error('PRIORITY payload must be 5 bytes', ERROR.FRAME_SIZE_ERROR, streamId);
246
+ }
247
+ var dep2 = payload.readUInt32BE(0);
248
+ frame.exclusive = (dep2 & 0x80000000) !== 0;
249
+ frame.dependency = dep2 & 0x7fffffff;
250
+ frame.weight = payload[4] + 1;
251
+ if (frame.dependency === streamId) {
252
+ throw h2Error('stream depends on itself', ERROR.PROTOCOL_ERROR, streamId);
253
+ }
254
+ return frame;
255
+ }
256
+
257
+ case TYPE.RST_STREAM: {
258
+ if (streamId === 0) throw h2Error('RST_STREAM on stream 0', ERROR.PROTOCOL_ERROR);
259
+ if (payload.length !== 4) {
260
+ throw h2Error('RST_STREAM payload must be 4 bytes', ERROR.FRAME_SIZE_ERROR);
261
+ }
262
+ frame.errorCode = payload.readUInt32BE(0);
263
+ return frame;
264
+ }
265
+
266
+ case TYPE.SETTINGS: {
267
+ if (streamId !== 0) throw h2Error('SETTINGS on a non-zero stream', ERROR.PROTOCOL_ERROR);
268
+ frame.ack = (flags & FLAG.ACK) !== 0;
269
+ if (frame.ack) {
270
+ // §6.5: an ACK carries no settings. A payload here means the peer is
271
+ // confused about which direction this frame is going.
272
+ if (payload.length !== 0) {
273
+ throw h2Error('SETTINGS ACK must have an empty payload', ERROR.FRAME_SIZE_ERROR);
274
+ }
275
+ frame.settings = {};
276
+ return frame;
277
+ }
278
+ if (payload.length % 6 !== 0) {
279
+ throw h2Error('SETTINGS payload is not a multiple of 6', ERROR.FRAME_SIZE_ERROR);
280
+ }
281
+ var settings = {};
282
+ // The same parameters again as an ORDERED list, including the ones
283
+ // dropped above. Both forms are needed and neither substitutes for the
284
+ // other: the protocol wants a lookup by name, and fingerprinting wants
285
+ // the sequence exactly as sent — which parameters appear, in which
286
+ // order, and which are absent. Unknown identifiers are kept here for the
287
+ // same reason: an unrecognised setting is still a fact about the peer.
288
+ var settingsList = [];
289
+ for (var i = 0; i + 6 <= payload.length; i += 6) {
290
+ var id = payload.readUInt16BE(i);
291
+ var value = payload.readUInt32BE(i + 2);
292
+ validateSetting(id, value);
293
+ settingsList.push({ id: id, value: value });
294
+ // Unknown identifiers must be IGNORED (§6.5.2), not rejected: that is
295
+ // the extension point the whole protocol relies on for future settings.
296
+ if (SETTINGS_NAME[id]) settings[SETTINGS_NAME[id]] = value;
297
+ }
298
+ frame.settings = settings;
299
+ frame.settingsList = settingsList;
300
+ return frame;
301
+ }
302
+
303
+ case TYPE.PUSH_PROMISE: {
304
+ if (streamId === 0) throw h2Error('PUSH_PROMISE on stream 0', ERROR.PROTOCOL_ERROR);
305
+ var pp = stripPadding(payload, flags);
306
+ if (pp.length < 4) {
307
+ throw h2Error('PUSH_PROMISE is shorter than 4 bytes', ERROR.FRAME_SIZE_ERROR);
308
+ }
309
+ frame.promisedStreamId = pp.readUInt32BE(0) & 0x7fffffff;
310
+ frame.headerBlock = pp.subarray(4);
311
+ frame.endHeaders = (flags & FLAG.END_HEADERS) !== 0;
312
+ return frame;
313
+ }
314
+
315
+ case TYPE.PING: {
316
+ if (streamId !== 0) throw h2Error('PING on a non-zero stream', ERROR.PROTOCOL_ERROR);
317
+ if (payload.length !== 8) {
318
+ throw h2Error('PING payload must be 8 bytes', ERROR.FRAME_SIZE_ERROR);
319
+ }
320
+ frame.ack = (flags & FLAG.ACK) !== 0;
321
+ frame.payload = Buffer.from(payload);
322
+ return frame;
323
+ }
324
+
325
+ case TYPE.GOAWAY: {
326
+ if (streamId !== 0) throw h2Error('GOAWAY on a non-zero stream', ERROR.PROTOCOL_ERROR);
327
+ if (payload.length < 8) {
328
+ throw h2Error('GOAWAY is shorter than 8 bytes', ERROR.FRAME_SIZE_ERROR);
329
+ }
330
+ frame.lastStreamId = payload.readUInt32BE(0) & 0x7fffffff;
331
+ frame.errorCode = payload.readUInt32BE(4);
332
+ frame.debugData = Buffer.from(payload.subarray(8));
333
+ return frame;
334
+ }
335
+
336
+ case TYPE.WINDOW_UPDATE: {
337
+ if (payload.length !== 4) {
338
+ throw h2Error('WINDOW_UPDATE payload must be 4 bytes', ERROR.FRAME_SIZE_ERROR);
339
+ }
340
+ frame.increment = payload.readUInt32BE(0) & 0x7fffffff;
341
+ if (frame.increment === 0) {
342
+ // §6.9. On a stream it is a stream error; on the connection it ends
343
+ // everything, because a zero increment can only be a sender that has
344
+ // lost track of its own window.
345
+ throw h2Error('WINDOW_UPDATE increment of 0', ERROR.PROTOCOL_ERROR, streamId);
346
+ }
347
+ return frame;
348
+ }
349
+
350
+ case TYPE.PRIORITY_UPDATE: {
351
+ // RFC 9218 §7.1: on stream 0 only, and naming a stream.
352
+ if (streamId !== 0) throw h2Error('PRIORITY_UPDATE on stream ' + streamId, ERROR.PROTOCOL_ERROR);
353
+ if (payload.length < 4) throw h2Error('PRIORITY_UPDATE shorter than 4 bytes', ERROR.FRAME_SIZE_ERROR);
354
+ frame.prioritizedStreamId = payload.readUInt32BE(0) & 0x7fffffff;
355
+ if (frame.prioritizedStreamId === 0) throw h2Error('PRIORITY_UPDATE for stream 0', ERROR.PROTOCOL_ERROR);
356
+ frame.priorityField = payload.subarray(4).toString('latin1');
357
+ return frame;
358
+ }
359
+
360
+ case TYPE.CONTINUATION: {
361
+ if (streamId === 0) throw h2Error('CONTINUATION on stream 0', ERROR.PROTOCOL_ERROR);
362
+ frame.headerBlock = payload;
363
+ frame.endHeaders = (flags & FLAG.END_HEADERS) !== 0;
364
+ return frame;
365
+ }
366
+
367
+ default:
368
+ // §4.1: a frame of unknown type MUST be ignored and discarded. Another
369
+ // extension point — rejecting it would break every future frame type.
370
+ frame.unknown = true;
371
+ frame.payload = Buffer.from(payload);
372
+ return frame;
373
+ }
374
+ }
375
+
376
+
377
+ /**
378
+ * Remove the padding a PADDED frame carries (§6.1).
379
+ *
380
+ * The length byte counts padding AFTER itself, so a value >= the remaining
381
+ * length means the padding would consume the real payload and then some. §6.1
382
+ * calls that a connection error, and it is the shape of an attempt to make two
383
+ * implementations disagree about where the payload ends.
384
+ */
385
+ function stripPadding(payload, flags) {
386
+ if ((flags & FLAG.PADDED) === 0) return payload;
387
+ if (payload.length < 1) {
388
+ throw h2Error('PADDED frame with no padding length', ERROR.PROTOCOL_ERROR);
389
+ }
390
+ var padLength = payload[0];
391
+ if (padLength >= payload.length) {
392
+ throw h2Error('padding length ' + padLength + ' is not smaller than the payload',
393
+ ERROR.PROTOCOL_ERROR);
394
+ }
395
+ return payload.subarray(1, payload.length - padLength);
396
+ }
397
+
398
+
399
+ function validateSetting(id, value) {
400
+ switch (id) {
401
+ case SETTINGS.ENABLE_PUSH:
402
+ if (value !== 0 && value !== 1) {
403
+ throw h2Error('SETTINGS_ENABLE_PUSH must be 0 or 1', ERROR.PROTOCOL_ERROR);
404
+ }
405
+ break;
406
+ case SETTINGS.ENABLE_CONNECT_PROTOCOL:
407
+ // RFC 8441 §3: 0 or 1, and once advertised as 1 it may never go back to
408
+ // 0 — a peer that has started using extended CONNECT cannot be told to
409
+ // stop mid-connection. The one-way rule is enforced in h2.js, which is
410
+ // where the previous value is known.
411
+ if (value !== 0 && value !== 1) {
412
+ throw h2Error('SETTINGS_ENABLE_CONNECT_PROTOCOL must be 0 or 1', ERROR.PROTOCOL_ERROR);
413
+ }
414
+ break;
415
+ case SETTINGS.NO_RFC7540_PRIORITIES:
416
+ if (value !== 0 && value !== 1) {
417
+ throw h2Error('SETTINGS_NO_RFC7540_PRIORITIES must be 0 or 1', ERROR.PROTOCOL_ERROR);
418
+ }
419
+ break;
420
+ case SETTINGS.INITIAL_WINDOW_SIZE:
421
+ if (value > MAX_WINDOW) {
422
+ throw h2Error('SETTINGS_INITIAL_WINDOW_SIZE above 2^31-1',
423
+ ERROR.FLOW_CONTROL_ERROR);
424
+ }
425
+ break;
426
+ case SETTINGS.MAX_FRAME_SIZE:
427
+ if (value < DEFAULT_MAX_FRAME_SIZE || value > MAX_ALLOWED_FRAME_SIZE) {
428
+ throw h2Error('SETTINGS_MAX_FRAME_SIZE outside 16384..16777215',
429
+ ERROR.PROTOCOL_ERROR);
430
+ }
431
+ break;
432
+ }
433
+ }
434
+
435
+
436
+ // ============================================================
437
+ // Encoding
438
+ // ============================================================
439
+
440
+ function frameHeader(length, type, flags, streamId) {
441
+ var h = Buffer.allocUnsafe(FRAME_HEADER_SIZE);
442
+ h[0] = (length >>> 16) & 0xff;
443
+ h[1] = (length >>> 8) & 0xff;
444
+ h[2] = length & 0xff;
445
+ h[3] = type;
446
+ h[4] = flags;
447
+ h.writeUInt32BE(streamId & 0x7fffffff, 5);
448
+ return h;
449
+ }
450
+
451
+ function buildFrame(type, flags, streamId, payload) {
452
+ payload = payload || Buffer.alloc(0);
453
+ return Buffer.concat([frameHeader(payload.length, type, flags, streamId), payload]);
454
+ }
455
+
456
+ function buildData(streamId, data, endStream) {
457
+ return buildFrame(TYPE.DATA, endStream ? FLAG.END_STREAM : 0, streamId, data);
458
+ }
459
+
460
+ function buildHeaders(streamId, headerBlock, options) {
461
+ options = options || {};
462
+ var flags = 0;
463
+ if (options.endStream) flags |= FLAG.END_STREAM;
464
+ if (options.endHeaders !== false) flags |= FLAG.END_HEADERS;
465
+ return buildFrame(TYPE.HEADERS, flags, streamId, headerBlock);
466
+ }
467
+
468
+ function buildContinuation(streamId, headerBlock, endHeaders) {
469
+ return buildFrame(TYPE.CONTINUATION, endHeaders ? FLAG.END_HEADERS : 0,
470
+ streamId, headerBlock);
471
+ }
472
+
473
+ function buildRstStream(streamId, errorCode) {
474
+ var p = Buffer.allocUnsafe(4);
475
+ p.writeUInt32BE(errorCode >>> 0, 0);
476
+ return buildFrame(TYPE.RST_STREAM, 0, streamId, p);
477
+ }
478
+
479
+ function buildSettings(settings, ack) {
480
+ if (ack) return buildFrame(TYPE.SETTINGS, FLAG.ACK, 0, Buffer.alloc(0));
481
+ var names = Object.keys(settings || {});
482
+ var p = Buffer.allocUnsafe(names.length * 6);
483
+ var off = 0;
484
+ for (var i = 0; i < names.length; i++) {
485
+ var id = SETTINGS[names[i]];
486
+ if (id === undefined) continue;
487
+ p.writeUInt16BE(id, off);
488
+ p.writeUInt32BE(settings[names[i]] >>> 0, off + 2);
489
+ off += 6;
490
+ }
491
+ return buildFrame(TYPE.SETTINGS, 0, 0, p.subarray(0, off));
492
+ }
493
+
494
+ function buildPing(payload, ack) {
495
+ var p = Buffer.alloc(8);
496
+ if (payload) Buffer.from(payload).copy(p, 0, 0, Math.min(8, payload.length));
497
+ return buildFrame(TYPE.PING, ack ? FLAG.ACK : 0, 0, p);
498
+ }
499
+
500
+ function buildGoaway(lastStreamId, errorCode, debugData) {
501
+ var d = debugData ? Buffer.from(debugData) : Buffer.alloc(0);
502
+ var p = Buffer.allocUnsafe(8 + d.length);
503
+ p.writeUInt32BE(lastStreamId & 0x7fffffff, 0);
504
+ p.writeUInt32BE(errorCode >>> 0, 4);
505
+ d.copy(p, 8);
506
+ return buildFrame(TYPE.GOAWAY, 0, 0, p);
507
+ }
508
+
509
+ // RFC 9218 §7.1: the stream, then its Priority Field Value.
510
+ function buildPriorityUpdate(streamId, field) {
511
+ var f = Buffer.from(field || '', 'latin1');
512
+ var p = Buffer.allocUnsafe(4 + f.length);
513
+ p.writeUInt32BE(streamId & 0x7fffffff, 0);
514
+ f.copy(p, 4);
515
+ return buildFrame(TYPE.PRIORITY_UPDATE, 0, 0, p);
516
+ }
517
+
518
+ function buildWindowUpdate(streamId, increment) {
519
+ var p = Buffer.allocUnsafe(4);
520
+ p.writeUInt32BE(increment & 0x7fffffff, 0);
521
+ return buildFrame(TYPE.WINDOW_UPDATE, 0, streamId, p);
522
+ }
523
+
524
+ /**
525
+ * Split a header block into HEADERS + as many CONTINUATION frames as needed.
526
+ *
527
+ * A header block larger than MAX_FRAME_SIZE is ordinary — a request with a big
528
+ * cookie reaches it easily — and the ONLY legal way to send it is to fragment.
529
+ * The fragments must be contiguous on the connection with no other frame
530
+ * between them (§6.10), which is why this returns them together rather than
531
+ * letting a caller interleave.
532
+ */
533
+ function buildHeaderFrames(streamId, headerBlock, options, maxFrameSize) {
534
+ options = options || {};
535
+ maxFrameSize = maxFrameSize || DEFAULT_MAX_FRAME_SIZE;
536
+
537
+ if (headerBlock.length <= maxFrameSize) {
538
+ return [buildHeaders(streamId, headerBlock, { endStream: options.endStream, endHeaders: true })];
539
+ }
540
+
541
+ var out = [];
542
+ out.push(buildHeaders(streamId, headerBlock.subarray(0, maxFrameSize), {
543
+ endStream: options.endStream,
544
+ endHeaders: false
545
+ }));
546
+ var pos = maxFrameSize;
547
+ while (pos < headerBlock.length) {
548
+ var end = Math.min(pos + maxFrameSize, headerBlock.length);
549
+ out.push(buildContinuation(streamId, headerBlock.subarray(pos, end),
550
+ end >= headerBlock.length));
551
+ pos = end;
552
+ }
553
+ return out;
554
+ }
555
+
556
+
557
+ export {
558
+ FRAME_HEADER_SIZE,
559
+ TYPE, TYPE_NAME,
560
+ FLAG,
561
+ ERROR, ERROR_NAME,
562
+ SETTINGS, SETTINGS_NAME,
563
+ PREFACE,
564
+ MAX_WINDOW,
565
+ DEFAULT_MAX_FRAME_SIZE,
566
+ MAX_ALLOWED_FRAME_SIZE,
567
+ DEFAULT_WINDOW_SIZE,
568
+ h2Error,
569
+ readFrame,
570
+ buildFrame,
571
+ frameHeader,
572
+ buildData,
573
+ buildHeaders,
574
+ buildContinuation,
575
+ buildRstStream,
576
+ buildSettings,
577
+ buildPing,
578
+ buildGoaway,
579
+ buildWindowUpdate,
580
+ buildPriorityUpdate,
581
+ buildHeaderFrames
582
+ };