h2lean 0.0.0-stage → 0.1.1

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/lib/hpack.js ADDED
@@ -0,0 +1,1288 @@
1
+ // HPACK codec (RFC 7541) for h2lean.
2
+ //
3
+ // Deliberate choices, all CPU-driven:
4
+ // * field names are interned, so a repeated name costs no new string and the
5
+ // header object keeps a stable hidden class
6
+ // * the encoder keeps a dynamic table (RFC 7541 2.3.2), so a repeated field line
7
+ // costs one indexed octet on the wire instead of its full literal bytes -- and it
8
+ // inserts a value only once it has seen it twice, so a header that is unique to
9
+ // every response never enters the table and never disturbs the caches
10
+ // * the encoder caches the *encoded bytes* of every (name, value, neverIndex)
11
+ // triple it has produced: a repeated header line becomes a Map lookup plus a
12
+ // memcpy, with no integer or Huffman work at all. Those bytes can name a dynamic
13
+ // index, so the cache is dropped whenever the table changes -- after warm-up the
14
+ // table stops changing and the cache stops being cleared
15
+ // * the cache is keyed in two levels -- the name, then the value -- and not by one
16
+ // composite 'name\0value' string. The composite key would have to be built for
17
+ // every field line of every response and hashed by the Map on the way in
18
+ // (OPTIMIZATION.md item 11). That is what a header block that cannot be cached as
19
+ // a whole costs per field line, and a block cannot be cached whenever the response
20
+ // headers are not the same object twice -- the compat API, and any response that
21
+ // echoes a request id
22
+ // * the two forms that are deliberately NOT cached -- the first sighting of a pair
23
+ // and the insertion that follows it -- are written straight into the destination
24
+ // buffer. They used to be built in a buffer of their own and copied in, which is
25
+ // one allocation and one memcpy per line, and the lines that take those forms are
26
+ // exactly the ones a server with a unique value in every response pays for
27
+ // * the encoded form of a NAME is cached, separately from the pair: a name that is
28
+ // in neither table is written in full, and a server that echoes a request id
29
+ // writes the same name with a new value every response, so the name's octets
30
+ // were Huffman-coded again for every one of those values
31
+ // * the "seen exactly once" set is bounded per name (ONCE_PER_NAME = 64): a name
32
+ // whose values never repeat -- the request id a server echoes in every response --
33
+ // stops being remembered, because the check costs a hash of a fresh value string
34
+ // on every response and the second sighting the set exists for never comes
35
+ //
36
+ // The encoder therefore holds per-connection state and must not be shared between
37
+ // sessions: the table has to match the one the peer's decoder builds from the same
38
+ // stream. lib/session.js owns one Encoder per session, exactly as it owns a Decoder.
39
+ //
40
+ // Where this differs from nghttp2, which is what node:http2 embeds, and why. Read from
41
+ // lib/nghttp2_hd.c v1.70.0 (hd_deflate_decide_indexing(), deflate_nv()); the differences
42
+ // were decided on measurements in bench/hpack-churn.js, not on preference:
43
+ // * insert on the second sighting (nghttp2 inserts on the first). A value that appears
44
+ // once per response is table poison -- it evicts a stable entry to be referenced by a
45
+ // block that will never use it again. Cost: one response warms up later than node's;
46
+ // in steady state the wire cost matches node's exactly (OPTIMIZATION.md item 11).
47
+ // * a name-based never-index list is NOT copied (:path, etag, location, age,
48
+ // content-length, if-*). An ETag is per-resource, not per-response, so indexing it is
49
+ // the whole point; copying that list measured worse on both axes (OPTIMIZATION.md
50
+ // item 27). The size half of the same rule is copied: nothing is inserted that needs
51
+ // more than three quarters of the table.
52
+ // * REINSERT_MAX: if the working set keeps evicting entries that are still wanted, the
53
+ // session stops inserting altogether. nghttp2 has no such thing because an eviction
54
+ // there costs a memcpy; here it costs both byte caches on every response.
55
+ // * ONCE_PER_NAME: a name that has produced 64 distinct values without any of them
56
+ // coming back stops being remembered, so it never inserts at all. The rule above
57
+ // would have inserted the pair on a second sighting, and for a header that is unique
58
+ // to every response that sighting never comes -- but a name whose values do repeat
59
+ // after 64 distinct ones pays for it: it stays a literal instead of being indexed.
60
+ // The per-line cost of the check: OPTIMIZATION.md item 11.
61
+ 'use strict';
62
+
63
+ const { STATIC, HUFF_BITS, HUFF_CODES } = require('./hpack-data');
64
+
65
+ const STATIC_LEN = STATIC.length;
66
+ const EOS = 256;
67
+ const MAX_LITERAL = 1 << 20; // hard cap on one field name or value
68
+ const CACHE_LIMIT = 8192;
69
+
70
+ class HPackError extends Error {
71
+ constructor(msg) {
72
+ super(msg);
73
+ this.name = 'HPackError';
74
+ }
75
+ }
76
+
77
+ // ---- static table lookups -----------------------------------------------------
78
+ const FULL_INDEX = new Map(); // 'name\0value' -> 1-based static index
79
+ const NAME_INDEX = new Map(); // 'name' -> first 1-based index with that name
80
+ // The same lookup in two levels: the name, then the value. The encoder asks it on
81
+ // every field line it has to build, and the composite key it would otherwise build
82
+ // for the one-level Map is what this table exists to avoid (see the header).
83
+ const STATIC_FULL = new Map(); // name -> Map<value, 1-based static index>
84
+ for (let i = 0; i < STATIC_LEN; i++) {
85
+ const [n, v] = STATIC[i];
86
+ const k = `${n}\0${v}`;
87
+ if (!FULL_INDEX.has(k)) {
88
+ FULL_INDEX.set(k, i + 1);
89
+ }
90
+ if (!NAME_INDEX.has(n)) {
91
+ NAME_INDEX.set(n, i + 1);
92
+ }
93
+ let inner = STATIC_FULL.get(n);
94
+ if (inner === undefined) {
95
+ inner = new Map();
96
+ STATIC_FULL.set(n, inner);
97
+ }
98
+ if (!inner.has(v)) {
99
+ inner.set(v, i + 1);
100
+ }
101
+ }
102
+
103
+ // ---- Huffman decode trie ------------------------------------------------------
104
+ const TR_LEAF = new Int32Array(1 + 257 * 30).fill(-1);
105
+ const TR_LEFT = new Int32Array(1 + 257 * 30);
106
+ const TR_RIGHT = new Int32Array(1 + 257 * 30);
107
+ {
108
+ let nodes = 1;
109
+ for (let s = 0; s <= EOS; s++) {
110
+ let node = 0;
111
+ for (let i = HUFF_BITS[s] - 1; i >= 0; i--) {
112
+ if ((HUFF_CODES[s] >>> i) & 1) {
113
+ if (TR_RIGHT[node] === 0) {
114
+ TR_RIGHT[node] = nodes++;
115
+ }
116
+ node = TR_RIGHT[node];
117
+ } else {
118
+ if (TR_LEFT[node] === 0) {
119
+ TR_LEFT[node] = nodes++;
120
+ }
121
+ node = TR_LEFT[node];
122
+ }
123
+ }
124
+ TR_LEAF[node] = s;
125
+ }
126
+ }
127
+
128
+ // ---- integer codec (RFC 7541 §5.1) --------------------------------------------
129
+ // The result lands in RI/RP so that no tuple is allocated per call.
130
+ let RI = 0;
131
+ let RP = 0;
132
+
133
+ function readInt(buf, p, bits, end) {
134
+ const mask = (1 << bits) - 1;
135
+ if (p >= end) {
136
+ throw new HPackError('hpack: truncated integer');
137
+ }
138
+ let v = buf[p++] & mask;
139
+ if (v < mask) {
140
+ RI = v; RP = p; return;
141
+ }
142
+ // RFC 7541 5.1: the first continuation octet carries the low 7 bits (weight 1),
143
+ // each following one weighs 128 times more.
144
+ let mul = 1;
145
+ while (p < end) {
146
+ const b = buf[p++];
147
+ v += (b & 0x7f) * mul;
148
+ if (v > 0x7fffffff) {
149
+ throw new HPackError('hpack: integer too large');
150
+ }
151
+ mul *= 128;
152
+ if ((b & 0x80) === 0) {
153
+ RI = v; RP = p; return;
154
+ }
155
+ }
156
+ throw new HPackError('hpack: truncated integer');
157
+ }
158
+
159
+ function writeInt(dst, off, value, bits, firstHigh) {
160
+ const max = (1 << bits) - 1;
161
+ if (value < max) {
162
+ dst[off++] = firstHigh | value;
163
+ return off;
164
+ }
165
+ dst[off++] = firstHigh | max;
166
+ let v = value - max;
167
+ while (v >= 128) {
168
+ dst[off++] = (v & 0x7f) | 0x80;
169
+ v >>>= 7;
170
+ }
171
+ dst[off++] = v;
172
+ return off;
173
+ }
174
+
175
+ // ---- Huffman ------------------------------------------------------------------
176
+
177
+ function huffBitLen(buf, start, end) {
178
+ let bits = 0;
179
+ for (let i = start; i < end; i++) {
180
+ bits += HUFF_BITS[buf[i]];
181
+ }
182
+ return bits;
183
+ }
184
+
185
+ // The same sum for a string, without building its bytes: latin1 truncates every
186
+ // code unit to its low byte, so the byte of a character is its code unit & 0xff.
187
+ // Building the bytes first is the allocation this avoids (OPTIMIZATION.md item 12).
188
+ function huffBitsOf(s) {
189
+ let bits = 0;
190
+ for (let i = 0; i < s.length; i++) {
191
+ bits += HUFF_BITS[s.charCodeAt(i) & 0xff];
192
+ }
193
+ return bits;
194
+ }
195
+
196
+ // The octets an integer takes in the HPACK integer encoding (RFC 7541 5.1).
197
+ function intLen(value, bits) {
198
+ const max = (1 << bits) - 1;
199
+ if (value < max) {
200
+ return 1;
201
+ }
202
+ let v = value - max;
203
+ let n = 2;
204
+ while (v >= 128) {
205
+ v >>>= 7;
206
+ n++;
207
+ }
208
+ return n;
209
+ }
210
+
211
+ // The octets one literal string takes on the wire: its length prefix and the
212
+ // string itself, Huffman-coded when that is shorter.
213
+ function strLen(s, huffBits) {
214
+ const hlen = (huffBits + 7) >> 3;
215
+ const len = hlen < s.length ? hlen : s.length;
216
+ return intLen(len, 7) + len;
217
+ }
218
+
219
+
220
+ const POW2 = new Float64Array(53);
221
+ for (let i = 0; i < 53; i++) {
222
+ POW2[i] = 2 ** i;
223
+ }
224
+
225
+ // Bit accumulation must not use 32-bit operators: after adding a 30-bit code the
226
+ // buffer can hold up to 37 bits. Doubles are exact below 2^53, so multiply/divide.
227
+ function huffEncode(src, start, end, dst, off) {
228
+ let acc = 0;
229
+ let nbits = 0;
230
+ for (let i = start; i < end; i++) {
231
+ const b = HUFF_BITS[src[i]];
232
+ acc = acc * POW2[b] + HUFF_CODES[src[i]];
233
+ nbits += b;
234
+ while (nbits >= 8) {
235
+ nbits -= 8;
236
+ const byte = Math.floor(acc / POW2[nbits]);
237
+ dst[off++] = byte;
238
+ acc -= byte * POW2[nbits];
239
+ }
240
+ }
241
+ // EOS is the all-ones code, so padding the last byte with ones *is* EOS
242
+ if (nbits > 0) {
243
+ const pad = POW2[8 - nbits] - 1;
244
+ dst[off++] = acc * POW2[8 - nbits] + pad;
245
+ }
246
+ return off;
247
+ }
248
+
249
+ // The same encoder for a string: latin1 truncates every code unit to its low byte,
250
+ // so the byte of a character is its code unit & 0xff -- the byte the buffer version
251
+ // above would have been handed. Coding straight from the string saves the copy into
252
+ // a scratch buffer and the view of it that the copy needed (OPTIMIZATION.md item 12).
253
+ function huffEncodeStr(s, dst, off) {
254
+ let acc = 0;
255
+ let nbits = 0;
256
+ for (let i = 0; i < s.length; i++) {
257
+ const sym = s.charCodeAt(i) & 0xff;
258
+ const b = HUFF_BITS[sym];
259
+ acc = acc * POW2[b] + HUFF_CODES[sym];
260
+ nbits += b;
261
+ while (nbits >= 8) {
262
+ nbits -= 8;
263
+ const byte = Math.floor(acc / POW2[nbits]);
264
+ dst[off++] = byte;
265
+ acc -= byte * POW2[nbits];
266
+ }
267
+ }
268
+ // EOS is the all-ones code, so padding the last byte with ones *is* EOS
269
+ if (nbits > 0) {
270
+ const pad = POW2[8 - nbits] - 1;
271
+ dst[off++] = acc * POW2[8 - nbits] + pad;
272
+ }
273
+ return off;
274
+ }
275
+
276
+ // The octets of a string into a buffer at `off`: latin1, one byte per code unit.
277
+ // Short strings go straight from charCodeAt, long through Buffer#write('latin1')
278
+ // -- the encode-side mirror of the latin1() helper below. Threshold and
279
+ // measurement: OPTIMIZATION.md item 35.
280
+ function latin1Into(dst, off, s) {
281
+ const len = s.length;
282
+ if (len <= RAW_WRITE_MAX) {
283
+ for (let i = 0; i < len; i++) {
284
+ dst[off + i] = s.charCodeAt(i);
285
+ }
286
+ } else {
287
+ dst.write(s, off, 'latin1');
288
+ }
289
+ return off + len;
290
+ }
291
+
292
+ // Returns the decoded byte count, or -1 if the input is not a valid sequence of
293
+ // complete codes (an explicit EOS, or a tail that is neither empty nor all ones).
294
+ function huffDecode(src, start, end, dst) {
295
+ let node = 0;
296
+ let out = 0;
297
+ let tail = 0;
298
+ for (let i = start; i < end; i++) {
299
+ const byte = src[i];
300
+ for (let k = 7; k >= 0; k--) {
301
+ node = ((byte >>> k) & 1) ? TR_RIGHT[node] : TR_LEFT[node];
302
+ const sym = TR_LEAF[node];
303
+ if (sym !== -1) {
304
+ if (sym === EOS) {
305
+ return -1;
306
+ }
307
+ dst[out++] = sym;
308
+ node = 0;
309
+ tail = 0;
310
+ } else {
311
+ tail++;
312
+ }
313
+ }
314
+ }
315
+ // the unwalked tail is the last `tail` bits of the stream; a valid padding is
316
+ // a prefix of EOS, i.e. at most 7 bits, all of them ones
317
+ if (tail > 7) {
318
+ return -1;
319
+ }
320
+ if (tail > 0 && (src[end - 1] & ((1 << tail) - 1)) !== (1 << tail) - 1) {
321
+ return -1;
322
+ }
323
+ return out;
324
+ }
325
+
326
+ // ---- decoder ------------------------------------------------------------------
327
+
328
+ // The stand-in for a view past the end of the range: reading from it is a bug
329
+ // the range check above catches first.
330
+ const EMPTY = Buffer.alloc(0);
331
+
332
+ // A latin1 string out of a buffer: the octets become code points, which is what
333
+ // latin1 decoding is -- the library keeps a field value's octets one to one
334
+ // (RFC 9113 8.2). Switch, chunked default and the measurement that shaped
335
+ // both: OPTIMIZATION.md item 13.
336
+ function latin1(buf, start, end) {
337
+ const len = end - start;
338
+ switch (len) {
339
+ case 0: return '';
340
+ case 1: return String.fromCharCode(buf[start]);
341
+ case 2: return String.fromCharCode(buf[start], buf[start + 1]);
342
+ case 3: return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2]);
343
+ case 4: return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3]);
344
+ default: {
345
+ // the rest is at most 8 octets, so the recursion is at most two calls deep
346
+ if (len <= 12) {
347
+ return String.fromCharCode(buf[start], buf[start + 1], buf[start + 2], buf[start + 3])
348
+ + latin1(buf, start + 4, end);
349
+ }
350
+ return buf.toString('latin1', start, end);
351
+ }
352
+ }
353
+ }
354
+
355
+ // A cursor over the octets of a header block that arrived in pieces.
356
+ //
357
+ // The block is a list of views of the socket chunks it came in, and the octets
358
+ // of a field line -- its prefix, the length of a literal, the literal itself --
359
+ // may straddle the boundary between two of them. The cursor walks the views in
360
+ // order and is the piece-aware twin of the module-level slots the contiguous
361
+ // decoder reads into (RI/RP): same rules, same order, same table updates, only
362
+ // the place the octets come from is different.
363
+ //
364
+ // test/hpack.test.js pins the two decoders against each other over the same
365
+ // blocks, so the twin cannot drift away from the original.
366
+ class PieceCursor {
367
+ // `pieces` are the views in the order they arrived; `start` and `end` are
368
+ // offsets into their concatenation.
369
+ constructor(pieces, start, end) {
370
+ this.pieces = pieces;
371
+ this.i = -1; // the view the cursor is in
372
+ this.o = 0; // the offset inside it
373
+ this.left = end - start; // the octets left in the range
374
+ this.value = 0; // the last integer read
375
+ this.cur = EMPTY;
376
+ this.pieceEnd = 0;
377
+ this.next();
378
+ let skip = start;
379
+ while (skip > 0) {
380
+ const avail = this.pieceEnd - this.o;
381
+ if (skip < avail) {
382
+ this.o += skip; skip = 0;
383
+ } else {
384
+ skip -= avail; this.next();
385
+ }
386
+ }
387
+ }
388
+
389
+ // The next view with octets in it. An empty one is skipped: a frame whose
390
+ // payload starts exactly at the end of a read arrives as an empty view and
391
+ // the octets that follow, and the cursor may not sit on an empty one.
392
+ next() {
393
+ this.i++;
394
+ this.o = 0;
395
+ while (this.i < this.pieces.length && this.pieces[this.i].length === 0) {
396
+ this.i++;
397
+ }
398
+ if (this.i >= this.pieces.length) {
399
+ // past the last view: the range is over, and nothing may be read from it
400
+ this.cur = EMPTY;
401
+ this.pieceEnd = 0;
402
+ return;
403
+ }
404
+ this.cur = this.pieces[this.i];
405
+ this.pieceEnd = this.cur.length;
406
+ }
407
+
408
+ // The octet at the cursor, without moving it.
409
+ peek() {
410
+ return this.cur[this.o];
411
+ }
412
+
413
+ // The octet at the cursor, moving it on.
414
+ byte() {
415
+ const b = this.cur[this.o++];
416
+ this.left--;
417
+ if (this.o === this.pieceEnd) {
418
+ this.next();
419
+ }
420
+ return b;
421
+ }
422
+
423
+ // RFC 7541 5.1: the first octet carries `bits` bits of the value, and each
424
+ // continuation octet carries seven bits more, the first of them weighing 1.
425
+ readInt(bits) {
426
+ const mask = (1 << bits) - 1;
427
+ if (this.left <= 0) {
428
+ throw new HPackError('hpack: truncated integer');
429
+ }
430
+ let v = this.byte() & mask;
431
+ if (v < mask) {
432
+ this.value = v; return;
433
+ }
434
+ let mul = 1;
435
+ while (this.left > 0) {
436
+ const b = this.byte();
437
+ v += (b & 0x7f) * mul;
438
+ if (v > 0x7fffffff) {
439
+ throw new HPackError('hpack: integer too large');
440
+ }
441
+ mul *= 128;
442
+ if ((b & 0x80) === 0) {
443
+ this.value = v; return;
444
+ }
445
+ }
446
+ throw new HPackError('hpack: truncated integer');
447
+ }
448
+
449
+ // The next `len` octets as a latin1 string. Inside one view: the native
450
+ // conversion and nothing in between. Across several: one conversion per view,
451
+ // which is what a string that a socket read split costs.
452
+ str(len) {
453
+ if (this.o + len <= this.pieceEnd) {
454
+ const whole = latin1(this.cur, this.o, this.o + len);
455
+ this.o += len;
456
+ this.left -= len;
457
+ if (this.o === this.pieceEnd) {
458
+ this.next();
459
+ }
460
+ return whole;
461
+ }
462
+ let s = '';
463
+ let left = len;
464
+ while (left > 0) {
465
+ const avail = this.pieceEnd - this.o;
466
+ if (avail === 0) {
467
+ throw new HPackError('hpack: truncated literal');
468
+ }
469
+ const take = avail < left ? avail : left;
470
+ s += latin1(this.cur, this.o, this.o + take);
471
+ this.o += take;
472
+ this.left -= take;
473
+ left -= take;
474
+ if (this.o === this.pieceEnd) {
475
+ this.next();
476
+ }
477
+ }
478
+ return s;
479
+ }
480
+ }
481
+
482
+ // The Huffman decoder over a run of octets that arrived in pieces: the same
483
+ // walk as huffDecode(), with the octets coming from the cursor.
484
+ function huffDecodePieces(cur, len, dst) {
485
+ let node = 0;
486
+ let out = 0;
487
+ let tail = 0;
488
+ let last = 0;
489
+ for (let i = 0; i < len; i++) {
490
+ const byte = cur.byte();
491
+ last = byte;
492
+ for (let k = 7; k >= 0; k--) {
493
+ node = ((byte >>> k) & 1) ? TR_RIGHT[node] : TR_LEFT[node];
494
+ const sym = TR_LEAF[node];
495
+ if (sym !== -1) {
496
+ if (sym === EOS) {
497
+ return -1;
498
+ }
499
+ dst[out++] = sym;
500
+ node = 0;
501
+ tail = 0;
502
+ } else {
503
+ tail++;
504
+ }
505
+ }
506
+ }
507
+ // the unwalked tail is the last `tail` bits of the stream; a valid padding is
508
+ // a prefix of EOS, i.e. at most 7 bits, all of them ones
509
+ if (tail > 7) {
510
+ return -1;
511
+ }
512
+ if (tail > 0 && (last & ((1 << tail) - 1)) !== (1 << tail) - 1) {
513
+ return -1;
514
+ }
515
+ return out;
516
+ }
517
+
518
+ class Decoder {
519
+ // `capacity` = what we advertised in our SETTINGS_HEADER_TABLE_SIZE
520
+ constructor(capacity = 4096) {
521
+ this.capacity = capacity;
522
+ this.maxSize = capacity;
523
+ this.size = 0;
524
+ this.dyn = []; // oldest first; entries below `base` are evicted
525
+ this.base = 0;
526
+ this.epoch = 0; // bumped on every table change (see §4.4, 4.3)
527
+ this.names = new Map(); // interned field names
528
+ this.scratch = Buffer.allocUnsafe(4096);
529
+ }
530
+
531
+ // HPACK index (1-based, newest dynamic entry first) -> [name, value]
532
+ at(index) {
533
+ if (index >= 1 && index <= STATIC_LEN) {
534
+ return STATIC[index - 1];
535
+ }
536
+ const j = index - STATIC_LEN - 1;
537
+ if (j < 0 || j >= this.dyn.length - this.base) {
538
+ return undefined;
539
+ }
540
+ return this.dyn[this.dyn.length - 1 - j];
541
+ }
542
+
543
+ insert(name, value) {
544
+ this.epoch++;
545
+ const sz = name.length + value.length + 32;
546
+ if (sz > this.maxSize) { // RFC 7541 §4.4: table ends up empty
547
+ this.dyn.length = this.base = 0;
548
+ this.size = 0;
549
+ return;
550
+ }
551
+ this.dyn.push([name, value]);
552
+ this.size += sz;
553
+ while (this.size > this.maxSize && this.dyn.length > this.base + 1) {
554
+ const old = this.dyn[this.base++];
555
+ this.size -= old[0].length + old[1].length + 32;
556
+ }
557
+ if (this.base > 64) { // amortised compaction
558
+ this.dyn.copyWithin(0, this.base);
559
+ this.dyn.length -= this.base;
560
+ this.base = 0;
561
+ this.size = 0;
562
+ for (const e of this.dyn) {
563
+ this.size += e[0].length + e[1].length + 32;
564
+ }
565
+ }
566
+ }
567
+
568
+ setMaxSize(n) {
569
+ if (n > this.capacity) {
570
+ throw new HPackError('hpack: table size update exceeds advertised capacity');
571
+ }
572
+ if (this.maxSize !== n) {
573
+ this.epoch++;
574
+ }
575
+ this.maxSize = n;
576
+ while (this.size > n && this.dyn.length > this.base) {
577
+ const old = this.dyn[this.base++];
578
+ this.size -= old[0].length + old[1].length + 32;
579
+ }
580
+ }
581
+
582
+ // Refills the reusable flat array `out` with name, value, name, value, ...
583
+ // and returns the number of field lines.
584
+ decode(buf, start, end, out) {
585
+ out.length = 0;
586
+ let p = start;
587
+ while (p < end) {
588
+ const b = buf[p];
589
+ if (b & 0x80) { // 1xxxxxxx indexed
590
+ readInt(buf, p, 7, end);
591
+ const e = this.at(RI);
592
+ if (e === undefined) {
593
+ throw new HPackError(`hpack: invalid index ${RI}`);
594
+ }
595
+ out.push(e[0], e[1]);
596
+ p = RP;
597
+ } else if ((b & 0xc0) === 0x40) { // 01xxxxxx incremental
598
+ p = this.literal(buf, p, end, out, 6);
599
+ this.insert(out[out.length - 2], out[out.length - 1]);
600
+ } else if ((b & 0xe0) === 0x20) { // 001xxxxx size update
601
+ // RFC 7541 6.3: a size update may only appear at the beginning of a header block --
602
+ // before its first field line, and `out.length === 0` is exactly that. Measured on node
603
+ // (h2spec hpack/4.2), one that follows a field line is GOAWAY(COMPRESSION_ERROR) whether
604
+ // or not it changes anything: the indices of the lines already decoded name a table that
605
+ // the update says was different, so a second decoder that reads the block in the order
606
+ // the octets arrive would rebuild a different context from ours.
607
+ if (out.length !== 0) {
608
+ throw new HPackError('hpack: table size update after a field line');
609
+ }
610
+ readInt(buf, p, 5, end);
611
+ this.setMaxSize(RI);
612
+ p = RP;
613
+ } else { // 0000/0001 literal
614
+ p = this.literal(buf, p, end, out, 4);
615
+ }
616
+ }
617
+ return out.length >> 1;
618
+ }
619
+
620
+ literal(buf, p, end, out, bits) {
621
+ readInt(buf, p, bits, end);
622
+ let name;
623
+ if (RI === 0) {
624
+ const s = this.str(buf, RP, end);
625
+ name = this.intern(s.value);
626
+ p = s.p;
627
+ } else {
628
+ const e = this.at(RI);
629
+ if (e === undefined) {
630
+ throw new HPackError(`hpack: invalid name index ${RI}`);
631
+ }
632
+ name = e[0];
633
+ p = RP;
634
+ }
635
+ const v = this.str(buf, p, end);
636
+ out.push(name, v.value);
637
+ return v.p;
638
+ }
639
+
640
+ str(buf, p, end) {
641
+ if (p >= end) {
642
+ throw new HPackError('hpack: truncated string');
643
+ }
644
+ const huff = (buf[p] & 0x80) !== 0;
645
+ readInt(buf, p, 7, end);
646
+ const len = RI;
647
+ p = RP;
648
+ if (len > MAX_LITERAL) {
649
+ throw new HPackError('hpack: literal too long');
650
+ }
651
+ if (p + len > end) {
652
+ throw new HPackError('hpack: truncated literal');
653
+ }
654
+ if (!huff) {
655
+ return { value: latin1(buf, p, p + len), p: p + len };
656
+ }
657
+ // the shortest code is 5 bits, so `len` encoded bytes decode to <= len*8/5
658
+ const need = (len * 8) / 5 + 8 | 0;
659
+ if (this.scratch.length < need) {
660
+ this.scratch = Buffer.allocUnsafe(need);
661
+ }
662
+ const n = huffDecode(buf, p, p + len, this.scratch);
663
+ if (n < 0) {
664
+ throw new HPackError('hpack: invalid huffman encoding');
665
+ }
666
+ return { value: latin1(this.scratch, 0, n), p: p + len };
667
+ }
668
+
669
+ // The same decoder over a block that arrived in pieces: the octets are read
670
+ // through a cursor that walks the views the block arrived in, instead of out
671
+ // of one buffer. The rules, the order and the table updates are the ones
672
+ // decode() applies -- this is the same decoder, not a second set of rules.
673
+ //
674
+ // Only a block that a socket read split takes this path. A block that arrived
675
+ // whole is decoded out of the chunk it came in, with no cursor and no copy.
676
+ decodePieces(pieces, start, end, out) {
677
+ out.length = 0;
678
+ const cur = new PieceCursor(pieces, start, end);
679
+ while (cur.left > 0) {
680
+ const b = cur.peek();
681
+ if (b & 0x80) { // 1xxxxxxx indexed
682
+ cur.readInt(7);
683
+ const e = this.at(cur.value);
684
+ if (e === undefined) {
685
+ throw new HPackError(`hpack: invalid index ${cur.value}`);
686
+ }
687
+ out.push(e[0], e[1]);
688
+ } else if ((b & 0xc0) === 0x40) { // 01xxxxxx incremental
689
+ this.literalPieces(cur, out, 6);
690
+ this.insert(out[out.length - 2], out[out.length - 1]);
691
+ } else if ((b & 0xe0) === 0x20) { // 001xxxxx size update
692
+ // RFC 7541 6.3: a size update may only appear at the beginning of a header
693
+ // block -- before its first field line. See decode() for the measurement.
694
+ if (out.length !== 0) {
695
+ throw new HPackError('hpack: table size update after a field line');
696
+ }
697
+ cur.readInt(5);
698
+ this.setMaxSize(cur.value);
699
+ } else { // 0000/0001 literal
700
+ this.literalPieces(cur, out, 4);
701
+ }
702
+ }
703
+ return out.length >> 1;
704
+ }
705
+
706
+ literalPieces(cur, out, bits) {
707
+ cur.readInt(bits);
708
+ let name;
709
+ if (cur.value === 0) {
710
+ name = this.intern(this.strPieces(cur));
711
+ } else {
712
+ const e = this.at(cur.value);
713
+ if (e === undefined) {
714
+ throw new HPackError(`hpack: invalid name index ${cur.value}`);
715
+ }
716
+ name = e[0];
717
+ }
718
+ const v = this.strPieces(cur);
719
+ out.push(name, v);
720
+ }
721
+
722
+ // A literal string out of the views: the length prefix, then the octets. The
723
+ // prefix's top bit says whether the octets are Huffman-coded.
724
+ strPieces(cur) {
725
+ const huff = (cur.peek() & 0x80) !== 0;
726
+ cur.readInt(7);
727
+ const len = cur.value;
728
+ if (len > MAX_LITERAL) {
729
+ throw new HPackError('hpack: literal too long');
730
+ }
731
+ if (cur.left < len) {
732
+ throw new HPackError('hpack: truncated literal');
733
+ }
734
+ if (!huff) {
735
+ return cur.str(len);
736
+ }
737
+ // the shortest code is 5 bits, so `len` encoded octets decode to <= len*8/5
738
+ const need = (len * 8) / 5 + 8 | 0;
739
+ if (this.scratch.length < need) {
740
+ this.scratch = Buffer.allocUnsafe(need);
741
+ }
742
+ const n = huffDecodePieces(cur, len, this.scratch);
743
+ if (n < 0) {
744
+ throw new HPackError('hpack: invalid huffman encoding');
745
+ }
746
+ return latin1(this.scratch, 0, n);
747
+ }
748
+
749
+ intern(name) {
750
+ const hit = this.names.get(name);
751
+ if (hit !== undefined) {
752
+ return hit;
753
+ }
754
+ if (this.names.size > 4096) {
755
+ this.names.clear();
756
+ }
757
+ this.names.set(name, name);
758
+ return name;
759
+ }
760
+ }
761
+
762
+ // ---- encoder ------------------------------------------------------------------
763
+
764
+ // Names whose values are not to be remembered in a table on either side: the five that
765
+ // RFC 7541 7.1.3 names and its A.2 example list carries -- credentials and session
766
+ // tokens. The application's own sensitiveHeaders path (the neverIndex flag, 0x10) is the
767
+ // per-call mechanism; this is the default for the headers an application did not mark.
768
+ //
769
+ // nghttp2 refuses to index a longer list (lib/nghttp2_hd.c v1.70.0,
770
+ // hd_deflate_decide_indexing(): :path, age, content-length, etag, if-modified-since,
771
+ // if-none-match, location, set-cookie) on the theory that a per-response value is not
772
+ // worth a table entry. That list was tried here, against this encoder's second-sighting
773
+ // rule, and it lost on both axes: an ETag is per-*resource*, so it repeats, and
774
+ // indexing it is what makes a cached response cheap (OPTIMIZATION.md item 27). The
775
+ // churn the name list protects against is already handled by REINSERT_MAX below, which
776
+ // reacts to the eviction pattern instead of guessing from the name. Only the size half
777
+ // of that rule is kept (see compile()).
778
+ const NEVER_INSERT = new Set(['set-cookie', 'www-authenticate', 'proxy-authenticate',
779
+ 'authorization', 'cookie']);
780
+
781
+ // How often a table entry has to come back after being evicted before this session
782
+ // gives up on its dynamic table. See insert().
783
+ const REINSERT_MAX = 8;
784
+
785
+ // Raw literal strings at or below this length are written straight from charCodeAt
786
+ // instead of through Buffer#write('latin1'); see latin1Into() and OPTIMIZATION.md
787
+ // item 35 for the measurement that set the threshold.
788
+ const RAW_WRITE_MAX = 16;
789
+
790
+ // How many distinct values one name may have before the encoder stops remembering
791
+ // them. The "seen exactly once" set is what makes the second-sighting rule work,
792
+ // and the set is not free: the value is a fresh string, so every check hashes it
793
+ // and every insert grows a Set that reaches 8192 entries per session. A name whose
794
+ // values never repeat -- the request id a server echoes in every response -- pays
795
+ // that for every response and gets nothing for it, because a pair that is
796
+ // remembered here is a pair that will be inserted on its second sighting, and that
797
+ // second sighting never comes.
798
+ //
799
+ // Past this many distinct values the name is treated as one whose values are
800
+ // unique to the response: the check is skipped and the line stays a literal.
801
+ // A name whose values do repeat keeps its entries -- the set holds the values seen
802
+ // exactly once, and a value that comes back is removed from it -- so the bound only
803
+ // bites on a name that has already produced 64 values nobody asked for twice
804
+ // (the cost of the check: OPTIMIZATION.md item 11).
805
+ const ONCE_PER_NAME = 64;
806
+
807
+ // The encoded form of a field name: the length prefix and the octets.
808
+ //
809
+ // A name is written in full whenever it is in neither the static nor the dynamic
810
+ // table, and a response whose header block cannot be cached as a whole pays that
811
+ // for every response -- a server that echoes a request id writes the same name
812
+ // with a new value every time. Huffman-coding the name again for every one of
813
+ // those values is the waste this cache removes (OPTIMIZATION.md item 11).
814
+ //
815
+ // The bytes depend on the name alone -- the length prefix and the octets, not the
816
+ // representation prefix in front of them -- so the cache is process-wide and not
817
+ // per session. Bounded like VALID_NAMES in lib/headers.js: the names come from
818
+ // the application, and a name that is new to a full cache is encoded as before.
819
+ const NAME_BYTES = new Map();
820
+ const NAME_BYTES_MAX = 1024;
821
+
822
+ // The name's own encoding, the same octets literalStrInto() would write.
823
+ function nameBytes(name) {
824
+ let b = NAME_BYTES.get(name);
825
+ if (b === undefined) {
826
+ const huffBits = huffBitsOf(name);
827
+ const hlen = (huffBits + 7) >> 3;
828
+ const huff = hlen < name.length;
829
+ const len = huff ? hlen : name.length;
830
+ b = Buffer.allocUnsafe(intLen(len, 7) + len);
831
+ const off = writeInt(b, 0, len, 7, huff ? 0x80 : 0x00);
832
+ if (huff) {
833
+ huffEncodeStr(name, b, off);
834
+ } else {
835
+ b.write(name, off, 'latin1');
836
+ }
837
+ if (NAME_BYTES.size < NAME_BYTES_MAX) {
838
+ NAME_BYTES.set(name, b);
839
+ }
840
+ }
841
+ return b;
842
+ }
843
+
844
+ // The size of the name cache, for the test that pins the bound.
845
+ function nameBytesSize() {
846
+ return NAME_BYTES.size;
847
+ }
848
+
849
+ // The shape of a literal, in module-level slots: the name it uses (an index, or 0
850
+ // for the full name), the Huffman bits of its two strings, and the octets the whole
851
+ // line takes. Asking for it costs no allocation, which is why it is not an object.
852
+ let L_BITS = 0;
853
+ let L_NAME_IDX = 0;
854
+ let L_NAME_BYTES = null;
855
+ let L_VALUE_BITS = 0;
856
+ let L_NEED = 0;
857
+
858
+ class Encoder {
859
+ // `peerSize` is the peer's SETTINGS_HEADER_TABLE_SIZE -- the size it assumes the table
860
+ // we build for it has -- and `cap` is the ceiling we are willing to spend on it. The
861
+ // two are normally the same number, and when they are not we have to announce ours, or
862
+ // the peer would evict at a different point than we do and the tables would drift.
863
+ constructor(peerSize = 4096, cap = peerSize) {
864
+ this.cap = cap;
865
+ this.peerSize = peerSize;
866
+ this.maxSize = Math.min(peerSize, cap);
867
+ this.size = 0;
868
+ this.dyn = []; // oldest first; everything below `base` is evicted
869
+ this.base = 0;
870
+ this.epoch = 0; // bumped on every change the peer's table mirrors
871
+ this.pendingSize = this.maxSize === peerSize ? null : this.maxSize;
872
+ // The encoded bytes of a field line, in two levels: the name, then the value.
873
+ // The never-indexed form is a map of its own, because the same pair can be sent
874
+ // both ways and the two forms are different bytes.
875
+ this.cache = new Map(); // name -> Map<value, Buffer>
876
+ this.cacheNever = new Map(); // name -> Map<value, Buffer>
877
+ this.cacheCount = 0; // field lines in both maps
878
+ // The pairs seen exactly once: not worth an insertion yet. And the pairs whose
879
+ // entry left the table, oldest signal first. Both in two levels, for the same
880
+ // reason as the byte cache above.
881
+ this.once = new Map(); // name -> Set<value>
882
+ this.onceCount = 0;
883
+ this.evicted = new Map(); // name -> Set<value>
884
+ this.evictedCount = 0;
885
+ this.reinserts = 0; // of them, how often we had to put one back
886
+ this.noInsert = false; // the working set does not fit; stop inserting
887
+ this.unsettled = false; // set by a line whose form will change next time
888
+ }
889
+
890
+ // Newest dynamic index holding exactly this pair, 0 when absent. Only reached on a
891
+ // byte-cache miss, so the scan of a table of at most ~100 entries is cold-path work.
892
+ findFull(name, value) {
893
+ for (let k = this.dyn.length - 1; k >= this.base; k--) {
894
+ const e = this.dyn[k];
895
+ if (e[0] === name && e[1] === value) {
896
+ return STATIC_LEN + this.dyn.length - k;
897
+ }
898
+ }
899
+ return 0;
900
+ }
901
+
902
+ findName(name) {
903
+ for (let k = this.dyn.length - 1; k >= this.base; k--) {
904
+ if (this.dyn[k][0] === name) {
905
+ return STATIC_LEN + this.dyn.length - k;
906
+ }
907
+ }
908
+ return 0;
909
+ }
910
+
911
+ // Adds an entry and evicts to fit. Every cached byte sequence may name an index that
912
+ // just moved, so the cache cannot survive the change -- which is why insertion is
913
+ // reserved for values that repeat.
914
+ insert(name, value) {
915
+ const sz = name.length + value.length + 32;
916
+ let dropped = 0;
917
+ if (sz > this.maxSize) { // RFC 7541 4.4: the table ends up empty
918
+ this.rememberEvicted(this.dyn, this.base, this.dyn.length);
919
+ this.dyn.length = this.base = 0;
920
+ this.size = 0;
921
+ } else {
922
+ this.dyn.push([name, value]);
923
+ this.size += sz;
924
+ const from = this.base;
925
+ while (this.size > this.maxSize && this.dyn.length > this.base + 1) {
926
+ const old = this.dyn[this.base++];
927
+ this.size -= old[0].length + old[1].length + 32;
928
+ dropped++;
929
+ }
930
+ if (dropped > 0) {
931
+ this.rememberEvicted(this.dyn, from, this.base);
932
+ }
933
+ if (this.base > 64) { // amortised compaction
934
+ this.dyn.copyWithin(0, this.base);
935
+ this.dyn.length -= this.base;
936
+ this.base = 0;
937
+ this.size = 0;
938
+ for (const e of this.dyn) {
939
+ this.size += e[0].length + e[1].length + 32;
940
+ }
941
+ }
942
+ }
943
+ this.touch();
944
+ // The signal that matters is not the number of evictions but whether an evicted
945
+ // entry was still wanted: if the same pair comes back and has to be inserted again,
946
+ // the working set is bigger than the table. A workload that fits never re-inserts
947
+ // anything, so this cannot fire during its warm-up.
948
+ if (this.evictedDelete(name, value) && ++this.reinserts >= REINSERT_MAX) {
949
+ // Measured (OPTIMIZATION.md item 11): a working set that
950
+ // keeps overflowing the table spends a multiple of the no-table cost on every
951
+ // block, because each eviction invalidates both byte caches -- and inserting stops
952
+ // returns it within a few hundred responses. Nothing has to be announced for
953
+ // that: the table keeps what is in it, stops moving, and so do the caches --
954
+ // the peer's decoder sees exactly the same indices, because we never touch
955
+ // them again.
956
+ this.noInsert = true;
957
+ }
958
+ }
959
+
960
+ rememberEvicted(dyn, from, to) {
961
+ if (this.evictedCount >= CACHE_LIMIT) {
962
+ this.evicted.clear();
963
+ this.evictedCount = 0;
964
+ }
965
+ for (let k = from; k < to; k++) {
966
+ const e = dyn[k];
967
+ let inner = this.evicted.get(e[0]);
968
+ if (inner === undefined) {
969
+ inner = new Set();
970
+ this.evicted.set(e[0], inner);
971
+ }
972
+ if (!inner.has(e[1])) {
973
+ inner.add(e[1]);
974
+ this.evictedCount++;
975
+ }
976
+ }
977
+ }
978
+
979
+ // Whether this pair was evicted before, and forgets it: it is back, so the entry
980
+ // it had is wanted again.
981
+ evictedDelete(name, value) {
982
+ const inner = this.evicted.get(name);
983
+ if (inner === undefined) {
984
+ return false;
985
+ }
986
+ const had = inner.delete(value);
987
+ if (had) {
988
+ this.evictedCount--;
989
+ }
990
+ if (inner.size === 0) {
991
+ this.evicted.delete(name);
992
+ }
993
+ return had;
994
+ }
995
+
996
+ // The peer changed the table size it advertises for its own decoder, which is the
997
+ // bound on ours. Any change has to be restated to it with a size update -- that is what
998
+ // nghttp2 does on receiving this setting -- because its decompression context is sized
999
+ // by that number and it has no other way of learning where ours ended up. An unchanged
1000
+ // value, which is every normal SETTINGS, costs nothing.
1001
+ setPeerTableSize(peerSize) {
1002
+ const want = Math.min(peerSize, this.cap);
1003
+ if (peerSize !== this.peerSize || want !== this.maxSize) {
1004
+ this.pendingSize = want;
1005
+ }
1006
+ this.peerSize = peerSize;
1007
+ if (want === this.maxSize) {
1008
+ return;
1009
+ }
1010
+ this.maxSize = want;
1011
+ let moved = false;
1012
+ while (this.size > want && this.dyn.length > this.base) {
1013
+ const old = this.dyn[this.base++];
1014
+ this.size -= old[0].length + old[1].length + 32;
1015
+ moved = true;
1016
+ }
1017
+ if (moved) {
1018
+ this.touch();
1019
+ } // the indices the caches name just shifted
1020
+ }
1021
+
1022
+ // The size update octets for the next field block, or null. Consumed here so a block
1023
+ // never announces the same change twice.
1024
+ takeSizeUpdate() {
1025
+ const n = this.pendingSize;
1026
+ if (n === null) {
1027
+ return null;
1028
+ }
1029
+ this.pendingSize = null;
1030
+ const b = Buffer.allocUnsafe(5);
1031
+ return b.subarray(0, writeInt(b, 0, n, 5, 0x20));
1032
+ }
1033
+
1034
+ touch() {
1035
+ this.epoch++;
1036
+ this.cache.clear();
1037
+ this.cacheNever.clear();
1038
+ this.cacheCount = 0;
1039
+ }
1040
+
1041
+ // The bytes of one field line, or undefined when they have to be built. Two
1042
+ // lookups, no key to build and no key to hash.
1043
+ lookup(name, value, neverIndex) {
1044
+ const inner = (neverIndex ? this.cacheNever : this.cache).get(name);
1045
+ return inner === undefined ? undefined : inner.get(value);
1046
+ }
1047
+
1048
+ // Appends one field line to `dst` at `off`, returns the new offset.
1049
+ // Cached bytes are only valid for the table state that produced them: see touch().
1050
+ write(dst, off, name, value, neverIndex) {
1051
+ const bytes = this.lookup(name, value, neverIndex);
1052
+ if (bytes !== undefined) {
1053
+ return this.put(dst, off, bytes);
1054
+ }
1055
+ return this.emit(dst, off, name, value, neverIndex);
1056
+ }
1057
+
1058
+ // Copies bytes into the destination and returns the new offset. The check belongs
1059
+ // here because the destination is a shared scratch block sized by an upper bound,
1060
+ // so a line that does not fit is a bug in that bound, not in the peer.
1061
+ put(dst, off, bytes) {
1062
+ if (off + bytes.length > dst.length) {
1063
+ throw new HPackError('hpack: output buffer too small');
1064
+ }
1065
+ // set() against copy(): the same memcpy with less argument juggling, and the
1066
+ // difference is real at the size of a field line (OPTIMIZATION.md item 12).
1067
+ dst.set(bytes, off);
1068
+ return off + bytes.length;
1069
+ }
1070
+
1071
+ // One field line, cold: choose the representation, and the table's fate with it.
1072
+ // Appends the line to `dst` at `off` and returns the new offset. The forms that are
1073
+ // cached (table-independent, or valid for this epoch) are built in a buffer of their
1074
+ // own and copied in; the two that are deliberately not cached -- the first sighting
1075
+ // of a pair, and the insertion that follows it -- are written straight into `dst`,
1076
+ // so a line whose value is new to the response costs no buffer at all.
1077
+ emit(dst, off, name, value, neverIndex) {
1078
+ // 11xxxxxx: the value is named in full and neither table stores it. Emitted as a
1079
+ // literal even when the pair is in the static table, which is what node's
1080
+ // nghttp2 does for sensitive values too -- the point is that nothing is remembered.
1081
+ if (neverIndex) {
1082
+ const bytes = this.literal(name, value, 0x10);
1083
+ this.remember(name, value, neverIndex, bytes);
1084
+ return this.put(dst, off, bytes);
1085
+ }
1086
+ const hit = this.fullIndex(name, value);
1087
+ if (hit !== undefined) {
1088
+ const bytes = this.indexed(hit);
1089
+ this.remember(name, value, neverIndex, bytes);
1090
+ return this.put(dst, off, bytes);
1091
+ }
1092
+ // Its name can never reach the table -- insert() is only reached past this line --
1093
+ // so the bytes reference nothing but static indices and are safe to cache despite
1094
+ // being literals.
1095
+ if (NEVER_INSERT.has(name)) {
1096
+ const bytes = this.literal(name, value, 0x00);
1097
+ this.remember(name, value, neverIndex, bytes);
1098
+ return this.put(dst, off, bytes);
1099
+ }
1100
+ // Nothing may be added to the table: either the peer has no room for it (inserting
1101
+ // would still be correct on the wire, since both tables empty at once, but it would
1102
+ // bump the epoch and clear the caches on every response), or this session gave up on
1103
+ // its table because its working set does not fit. Entries already in it keep their
1104
+ // one-octet references above -- only new ones stop. From here the table stands still,
1105
+ // so these bytes, which may name a dynamic *name* index, stay valid for the session.
1106
+ if (this.maxSize === 0 || this.noInsert) {
1107
+ const bytes = this.literal(name, value, 0x00);
1108
+ this.remember(name, value, neverIndex, bytes);
1109
+ return this.put(dst, off, bytes);
1110
+ }
1111
+ // One more rule taken from hd_deflate_decide_indexing(): an entry bigger than three
1112
+ // quarters of the table is not inserted at all. It would evict nearly the whole table
1113
+ // to make room for itself, and a single reference is worth less than the stable
1114
+ // entries it displaces -- which is the same reasoning as NEVER_INSERT above, applied
1115
+ // by size instead of by name.
1116
+ if (name.length + value.length + 32 > this.maxSize - (this.maxSize >> 2)) {
1117
+ const bytes = this.literal(name, value, 0x00);
1118
+ this.remember(name, value, neverIndex, bytes);
1119
+ return this.put(dst, off, bytes);
1120
+ }
1121
+ // Insert on the second sighting. The first one pays a literal and is only
1122
+ // remembered, so a value that is unique to every response never enters the table,
1123
+ // never evicts, and never clears the cache. Not cached: caching it here is what
1124
+ // would keep it a literal forever. Written straight into the destination, because
1125
+ // this line is thrown away with the response anyway.
1126
+ if (this.onceCount >= CACHE_LIMIT) {
1127
+ this.once.clear();
1128
+ this.onceCount = 0;
1129
+ }
1130
+ let seen = this.once.get(name);
1131
+ if (seen === undefined) {
1132
+ seen = new Set();
1133
+ this.once.set(name, seen);
1134
+ }
1135
+ if (seen.size >= ONCE_PER_NAME) {
1136
+ // This name has produced ONCE_PER_NAME distinct values and none of them came
1137
+ // back, so its values are unique to the response. Stop remembering them: the
1138
+ // check is the cost (the hash of a fresh value string) and the pair would not
1139
+ // be inserted on a second sighting anyway -- that is the rule this name wants.
1140
+ this.unsettled = true;
1141
+ return this.literalInto(dst, off, name, value, 0x00);
1142
+ }
1143
+ if (!seen.has(value)) {
1144
+ seen.add(value);
1145
+ this.onceCount++;
1146
+ this.unsettled = true;
1147
+ return this.literalInto(dst, off, name, value, 0x00);
1148
+ }
1149
+ seen.delete(value);
1150
+ this.onceCount--;
1151
+ // 01xxxxxx: both tables insert. The octets name the table as it is now, so they
1152
+ // are written before insert() moves it.
1153
+ const end = this.literalInto(dst, off, name, value, 0x40);
1154
+ this.insert(name, value);
1155
+ return end; // not cached, on purpose
1156
+ }
1157
+
1158
+ // Static table first, then ours: the index a peer can resolve without any literal.
1159
+ fullIndex(name, value) {
1160
+ const inner = STATIC_FULL.get(name);
1161
+ const stat = inner === undefined ? undefined : inner.get(value);
1162
+ if (stat !== undefined) {
1163
+ return stat;
1164
+ }
1165
+ const dyn = this.findFull(name, value);
1166
+ return dyn === 0 ? undefined : dyn;
1167
+ }
1168
+
1169
+ remember(name, value, neverIndex, bytes) {
1170
+ if (bytes === undefined) {
1171
+ return bytes;
1172
+ }
1173
+ const cache = neverIndex ? this.cacheNever : this.cache;
1174
+ let inner = cache.get(name);
1175
+ if (inner === undefined) {
1176
+ inner = new Map();
1177
+ cache.set(name, inner);
1178
+ }
1179
+ if (!inner.has(value)) {
1180
+ inner.set(value, bytes);
1181
+ if (++this.cacheCount >= CACHE_LIMIT) {
1182
+ this.cache.clear();
1183
+ this.cacheNever.clear();
1184
+ this.cacheCount = 0;
1185
+ }
1186
+ }
1187
+ return bytes;
1188
+ }
1189
+
1190
+
1191
+ indexed(index) {
1192
+ const b = Buffer.allocUnsafe(4);
1193
+ return b.subarray(0, writeInt(b, 0, index, 7, 0x80));
1194
+ }
1195
+
1196
+ // prefix 0x00 = without indexing (4-bit name field), 0x10 = never indexed,
1197
+ // 0x40 = with incremental indexing (6-bit name field). A never-indexed line names
1198
+ // its field in full as well: nothing about the field may be left in a table.
1199
+ literal(name, value, prefix) {
1200
+ this.plan(name, value, prefix);
1201
+ const out = Buffer.allocUnsafe(L_NEED);
1202
+ let off = writeInt(out, 0, L_NAME_IDX, L_BITS, prefix);
1203
+ if (L_NAME_IDX === 0) {
1204
+ out.set(L_NAME_BYTES, off);
1205
+ off += L_NAME_BYTES.length;
1206
+ }
1207
+ this.literalStrInto(out, off, value, L_VALUE_BITS);
1208
+ return out; // the buffer is exactly as long as the line
1209
+ }
1210
+
1211
+ // The same line, written straight into the destination: no buffer of the line's
1212
+ // own, and the size is known before a single octet is written, so the destination
1213
+ // can be checked first. Used for the two forms that are not cached.
1214
+ literalInto(dst, off, name, value, prefix) {
1215
+ this.plan(name, value, prefix);
1216
+ if (off + L_NEED > dst.length) {
1217
+ throw new HPackError('hpack: output buffer too small');
1218
+ }
1219
+ let o = writeInt(dst, off, L_NAME_IDX, L_BITS, prefix);
1220
+ if (L_NAME_IDX === 0) {
1221
+ dst.set(L_NAME_BYTES, o);
1222
+ o += L_NAME_BYTES.length;
1223
+ }
1224
+ o = this.literalStrInto(dst, o, value, L_VALUE_BITS);
1225
+ return o;
1226
+ }
1227
+
1228
+ // The shape of the line, in the module-level slots above.
1229
+ plan(name, value, prefix) {
1230
+ L_BITS = prefix === 0x40 ? 6 : 4;
1231
+ let nameIdx = 0;
1232
+ if (prefix !== 0x10) {
1233
+ const stat = NAME_INDEX.get(name);
1234
+ nameIdx = stat !== undefined ? stat : this.findName(name);
1235
+ }
1236
+ L_NAME_IDX = nameIdx;
1237
+ L_VALUE_BITS = huffBitsOf(value);
1238
+ if (nameIdx === 0) {
1239
+ // the name is written in full: its octets are the cached ones
1240
+ L_NAME_BYTES = nameBytes(name);
1241
+ L_NEED = intLen(0, L_BITS) + L_NAME_BYTES.length + strLen(value, L_VALUE_BITS);
1242
+ } else {
1243
+ L_NAME_BYTES = null;
1244
+ L_NEED = intLen(nameIdx, L_BITS) + strLen(value, L_VALUE_BITS);
1245
+ }
1246
+ }
1247
+
1248
+ // One literal string: its length prefix and the string itself, Huffman-coded when
1249
+ // that is shorter. The octets of a string are its latin1 bytes -- one per code
1250
+ // unit, the low byte of each -- so the Huffman form is coded straight from the
1251
+ // string and the raw form goes through latin1Into().
1252
+ literalStrInto(dst, off, s, huffBits) {
1253
+ const rawLen = s.length;
1254
+ const hlen = (huffBits + 7) >> 3;
1255
+ if (hlen < rawLen) { // Huffman wins
1256
+ off = writeInt(dst, off, hlen, 7, 0x80);
1257
+ return huffEncodeStr(s, dst, off);
1258
+ }
1259
+ off = writeInt(dst, off, rawLen, 7, 0x00);
1260
+ return latin1Into(dst, off, s);
1261
+ }
1262
+
1263
+
1264
+ sizeUpdate(dst, off, size) {
1265
+ return writeInt(dst, off, size, 5, 0x20);
1266
+ }
1267
+
1268
+ // Upper bound on the bytes one field line can produce, for capacity planning.
1269
+ static maxBytesOf(name, value) {
1270
+ return 1 + 4 + (name.length + 5) * 2 + (value.length + 5) * 2;
1271
+ }
1272
+ }
1273
+
1274
+ module.exports = {
1275
+ Decoder,
1276
+ Encoder,
1277
+ HPackError,
1278
+ STATIC,
1279
+ FULL_INDEX,
1280
+ NAME_INDEX,
1281
+ nameBytesSize,
1282
+ huffEncode,
1283
+ huffEncodeStr,
1284
+ huffDecode,
1285
+ huffBitLen,
1286
+ writeInt,
1287
+ readInt
1288
+ };