@amritk/nish-x86_64-linux 0.18.0 → 0.21.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/INSTALL.md +58 -28
- package/bin/nish +0 -0
- package/docs/AI.md +2759 -0
- package/package.json +3 -2
- package/runtime/browser.d.ts +14 -0
- package/runtime/browser.mjs +88 -0
- package/runtime/core/arrays.d.ts +18 -0
- package/runtime/core/arrays.mjs +125 -0
- package/runtime/core/checks.mjs +20 -0
- package/runtime/core/host.d.ts +70 -0
- package/runtime/core/host.mjs +311 -0
- package/runtime/core/numbers.d.ts +53 -0
- package/runtime/core/numbers.mjs +316 -0
- package/runtime/core/result.d.ts +41 -0
- package/runtime/core/result.mjs +127 -0
- package/runtime/core/strings.d.ts +14 -0
- package/runtime/core/strings.mjs +169 -0
- package/runtime/core.d.ts +9 -0
- package/runtime/core.mjs +55 -0
- package/runtime/host.d.ts +6 -0
- package/runtime/nish.d.ts +99 -5
- package/runtime/nish.h +289 -19
- package/runtime/nish.mjs +39 -3
- package/runtime/node.mjs +383 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-bytes.c +401 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +8 -7
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +214 -16
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +235 -11
- package/runtime/runtime-term.c +504 -0
- package/runtime/runtime-wasm.c +5 -14
- package/runtime/runtime.c +257 -17
- package/runtime/shim.mjs +6 -915
- package/runtime/sqlite/LICENSE.md +85 -0
- package/runtime/sqlite/README.md +45 -0
- package/runtime/sqlite/nish-sqlite.c +359 -0
- package/runtime/sqlite/sqlite3.c +271131 -0
- package/runtime/sqlite/sqlite3.h +14447 -0
- package/scripts/build.sh +437 -28
- package/std/LICENSE-fast-float +23 -0
- package/std/LICENSE-fdlibm +8 -0
- package/std/README.md +108 -58
- package/std/base64.ts +116 -0
- package/std/bytes.ts +419 -0
- package/std/cli.ts +328 -0
- package/std/collections.ts +6 -5
- package/std/crypto/LICENSE-crypt_blowfish +26 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/base64url.ts +125 -0
- package/std/crypto/bcrypt.ts +760 -0
- package/std/crypto/chacha20poly1305.ts +43 -9
- package/std/crypto/ed25519.ts +840 -0
- package/std/crypto/hkdf.ts +304 -12
- package/std/crypto/hmac.ts +382 -54
- package/std/crypto/p256.ts +932 -61
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +20 -131
- package/std/json-tokenizer.ts +4237 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1182 -0
- package/std/json.ts +1202 -34
- package/std/math.ts +608 -0
- package/std/net/buffer-pool.ts +151 -0
- package/std/net/hpack.ts +416 -112
- package/std/net/http-fields.ts +93 -0
- package/std/net/http1-server.ts +501 -62
- package/std/net/http1.ts +31 -6
- package/std/net/http2-tls.ts +112 -24
- package/std/net/http2.ts +333 -75
- package/std/net/http3-server.ts +261 -43
- package/std/net/http3.ts +187 -28
- package/std/net/postgres.ts +1875 -0
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-listener.ts +108 -11
- package/std/net/quic-packet.ts +243 -82
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +424 -24
- package/std/net/quic.ts +519 -167
- package/std/net/router.ts +676 -0
- package/std/net/tls/codec.ts +426 -27
- package/std/net/tls/record-server.ts +478 -132
- package/std/net/tls/record.ts +133 -42
- package/std/net/tls/schedule.ts +464 -55
- package/std/net/tls/ticket.ts +610 -0
- package/std/net/tls-tcp.ts +196 -53
- package/std/net/tls.ts +1018 -260
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/regex.ts +4274 -0
- package/std/sqlite.ts +391 -0
- package/std/term-io.ts +978 -0
- package/std/term-pixels.ts +874 -0
- package/std/term-probe.ts +459 -0
- package/std/term-rate.ts +993 -0
- package/std/term.ts +2453 -0
- package/std/text.ts +90 -7
- package/std/threads.ts +438 -14
- package/std/yaml-lines.ts +141 -0
- package/std/yaml.ts +4675 -0
package/std/net/hpack.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*
|
|
19
19
|
* const dec = new HpackDecoder(4096, 65536);
|
|
20
20
|
* if (dec.decode(block, 0, toI32(block.length)) === HPACK_OK) {
|
|
21
|
-
* // dec.names[i], dec.values[i], dec.neverIndexed[i]
|
|
21
|
+
* // dec.names[i], dec.values[i], dec.neverIndexed[i], until the next decode
|
|
22
22
|
* }
|
|
23
23
|
*
|
|
24
24
|
* Names and values are octets (`u8[]`), not strings, because a field value is
|
|
@@ -34,7 +34,10 @@
|
|
|
34
34
|
* or after the block's first field, and a block that does not begin with the
|
|
35
35
|
* update a lowered limit requires (§4.2). Each of those is HTTP/2's
|
|
36
36
|
* COMPRESSION_ERROR, which closes the connection, so the code is sticky: every
|
|
37
|
-
* later `decode` answers it again without reading anything.
|
|
37
|
+
* later `decode` answers it again without reading anything. So is
|
|
38
|
+
* `HPACK_ERR_COPY_BUDGET`, a block that would make the table copy more than
|
|
39
|
+
* `HPACK_COPY_FACTOR` times the larger of its size and the block's length,
|
|
40
|
+
* which HTTP/2 answers with ENHANCE_YOUR_CALM.
|
|
38
41
|
*
|
|
39
42
|
* A header list larger than the caller's limit is not one of them. The block
|
|
40
43
|
* is still decoded to its end, so that the table stays in step with the
|
|
@@ -55,11 +58,15 @@
|
|
|
55
58
|
* Nothing here is constant time. HPACK's secrets are protected by the
|
|
56
59
|
* never-indexed representation, not by the codec's timing (§7.1).
|
|
57
60
|
*
|
|
58
|
-
* State is a class and dispatch is a `switch`, with no closures.
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
61
|
+
* State is a class and dispatch is a `switch`, with no closures. A decoder
|
|
62
|
+
* answers its fields in arrays it owns and reuses, never the table's own
|
|
63
|
+
* octets, so a caller may change one without touching the table, and keeps
|
|
64
|
+
* one past the next `decode` by copying it. Once its arrays have grown to the
|
|
65
|
+
* largest block and header list the peer has sent, decoding allocates
|
|
66
|
+
* nothing, which bounds what a peer can make a connection hold (RFC 9113
|
|
67
|
+
* §10.5.1; `docs/security/http2.md`, H2-1). Private helpers share the
|
|
68
|
+
* importing program's flat symbol namespace (`docs/wp26-stdlib.md` §3e),
|
|
69
|
+
* which is why each one carries the module's name.
|
|
63
70
|
*
|
|
64
71
|
* Written from RFC 7541, not ported from another implementation. The Huffman
|
|
65
72
|
* code is Appendix B's, stored as its 257 code lengths: the code is canonical
|
|
@@ -104,6 +111,27 @@ export const HPACK_ERR_SIZE_UPDATE_POSITION: i32 = -7
|
|
|
104
111
|
*/
|
|
105
112
|
export const HPACK_ERR_SIZE_UPDATE_MISSING: i32 = -8
|
|
106
113
|
|
|
114
|
+
/**
|
|
115
|
+
* A block that asked the table to take more octets than its copy budget
|
|
116
|
+
* allows: `HPACK_COPY_FACTOR` times the larger of the table's maximum size
|
|
117
|
+
* and the block's own length. Valid HPACK, but no encoder needs it, and
|
|
118
|
+
* HTTP/2 answers it with ENHANCE_YOUR_CALM, not COMPRESSION_ERROR.
|
|
119
|
+
*/
|
|
120
|
+
export const HPACK_ERR_COPY_BUDGET: i32 = -9
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* How many times the larger of the table's maximum size and a block's length
|
|
124
|
+
* the decoder copies into its table for one block, at most
|
|
125
|
+
* (`docs/security/http2.md`, H2-1). An index naming a dynamic entry is one or
|
|
126
|
+
* two octets, and a literal with incremental indexing that names it copies
|
|
127
|
+
* the name out of the table and back in, so without a budget a block of them
|
|
128
|
+
* costs about three times the table's size of work per two octets it holds.
|
|
129
|
+
* Four leaves an encoder room to re-add a full table's worth of fields twice
|
|
130
|
+
* over in one block, which none needs, since every add past the table's size
|
|
131
|
+
* evicts one of its own.
|
|
132
|
+
*/
|
|
133
|
+
export const HPACK_COPY_FACTOR: i32 = 4
|
|
134
|
+
|
|
107
135
|
/** Add the field to the dynamic table (§6.2.1). */
|
|
108
136
|
export const HPACK_INDEX_INCREMENTAL: i32 = 0
|
|
109
137
|
|
|
@@ -305,23 +333,27 @@ const hpackStaticValue = (index: i32): string => {
|
|
|
305
333
|
}
|
|
306
334
|
}
|
|
307
335
|
|
|
308
|
-
/**
|
|
309
|
-
const
|
|
310
|
-
const out: u8[] = []
|
|
336
|
+
/** Appends the octets of the ASCII string `text` to `out`. */
|
|
337
|
+
const hpackAppendText = (out: u8[], text: string): void => {
|
|
311
338
|
const n: i32 = toI32(text.length)
|
|
312
339
|
for (let i: i32 = 0; i < n; i += 1) {
|
|
313
340
|
out.push(toU8(text.charCodeAt(i)))
|
|
314
341
|
}
|
|
315
|
-
return out
|
|
316
342
|
}
|
|
317
343
|
|
|
318
|
-
/**
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
for (
|
|
322
|
-
out.push(
|
|
344
|
+
/** Appends `src[at, at + n)`, a window the caller has checked, to `out`. */
|
|
345
|
+
const hpackAppend = (out: u8[], src: u8[], at: i32, n: i32): void => {
|
|
346
|
+
// The length test is the caller's check again, there so the prover drops the bounds check.
|
|
347
|
+
for (let i: i32 = at; i < at + n && i >= 0 && i < toI32(src.length); i += 1) {
|
|
348
|
+
out.push(src[i])
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** Empties `bytes`, keeping its storage for the next fill. */
|
|
353
|
+
const hpackEmpty = (bytes: u8[]): void => {
|
|
354
|
+
while (toI32(bytes.length) > 0) {
|
|
355
|
+
bytes.pop()
|
|
323
356
|
}
|
|
324
|
-
return out
|
|
325
357
|
}
|
|
326
358
|
|
|
327
359
|
/** Whether `bytes` spell the ASCII string `text`. */
|
|
@@ -340,14 +372,13 @@ const hpackSameAsText = (bytes: u8[], text: string): boolean => {
|
|
|
340
372
|
return true
|
|
341
373
|
}
|
|
342
374
|
|
|
343
|
-
/** Whether
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
if (n !== toI32(b.length)) {
|
|
375
|
+
/** Whether `bytes` hold the same octets as `store[at, at + n)`, a window inside `store`. */
|
|
376
|
+
const hpackSameAsWindow = (bytes: u8[], store: u8[], at: i32, n: i32): boolean => {
|
|
377
|
+
if (toI32(bytes.length) !== n) {
|
|
347
378
|
return false
|
|
348
379
|
}
|
|
349
|
-
for (let i: i32 = 0; i < n && i < toI32(
|
|
350
|
-
if (
|
|
380
|
+
for (let i: i32 = 0; i < n && i < toI32(bytes.length); i += 1) {
|
|
381
|
+
if (bytes[i] !== store[at + i]) {
|
|
351
382
|
return false
|
|
352
383
|
}
|
|
353
384
|
}
|
|
@@ -526,6 +557,14 @@ export class HpackCursor {
|
|
|
526
557
|
this.pos = off
|
|
527
558
|
this.end = off + len
|
|
528
559
|
}
|
|
560
|
+
|
|
561
|
+
/** Points the cursor at `buf[off, off + len)` instead, which must lie inside `buf`. */
|
|
562
|
+
reset(buf: u8[], off: i32, len: i32): void {
|
|
563
|
+
hpackCheckWindow("HpackCursor", buf, off, len)
|
|
564
|
+
this.buf = buf
|
|
565
|
+
this.pos = off
|
|
566
|
+
this.end = off + len
|
|
567
|
+
}
|
|
529
568
|
}
|
|
530
569
|
|
|
531
570
|
/**
|
|
@@ -643,15 +682,22 @@ export const hpackDecodeString = (h: HpackHuffman, c: HpackCursor, out: u8[]): i
|
|
|
643
682
|
* The dynamic table (§2.3.2, §4): a FIFO of fields, newest first by index,
|
|
644
683
|
* whose size — every entry's name and value plus 32 — never passes `maxSize`.
|
|
645
684
|
*
|
|
646
|
-
*
|
|
647
|
-
*
|
|
648
|
-
*
|
|
649
|
-
*
|
|
650
|
-
*
|
|
685
|
+
* Every entry's octets live in one store, `bytes`: its name, then its value,
|
|
686
|
+
* oldest entry first. `entries` says where each entry sits, two numbers to an
|
|
687
|
+
* entry — where it starts in `bytes` and how long its name is; its value runs
|
|
688
|
+
* to where the next one starts — oldest first from entry `first` on, so that
|
|
689
|
+
* adding one is two `push`es and evicting one moves `first`. The evicted
|
|
690
|
+
* prefix of `entries`, and of `bytes`, is shifted out in place once it is half
|
|
691
|
+
* of either, which keeps both amortised constant time without a ring's index
|
|
692
|
+
* arithmetic. Shifting and popping keep an array's storage, and `bytes` never
|
|
693
|
+
* holds more than three times `maxSize`, so a table the peer fills and empties
|
|
694
|
+
* for as long as it likes stops allocating once its arrays have grown that far
|
|
695
|
+
* (`docs/security/http2.md`, H2-1). Dynamic index 1, the newest entry, is the
|
|
696
|
+
* last.
|
|
651
697
|
*/
|
|
652
698
|
export class HpackTable {
|
|
653
|
-
|
|
654
|
-
|
|
699
|
+
bytes: u8[]
|
|
700
|
+
entries: i32[]
|
|
655
701
|
first: i32
|
|
656
702
|
size: i32
|
|
657
703
|
maxSize: i32
|
|
@@ -660,63 +706,148 @@ export class HpackTable {
|
|
|
660
706
|
if (maxSize < 0) {
|
|
661
707
|
panic(`HpackTable: a maximum size of ${maxSize}`)
|
|
662
708
|
}
|
|
663
|
-
this.
|
|
664
|
-
this.
|
|
709
|
+
this.bytes = []
|
|
710
|
+
this.entries = []
|
|
665
711
|
this.first = 0
|
|
666
712
|
this.size = 0
|
|
667
713
|
this.maxSize = maxSize
|
|
668
714
|
}
|
|
669
715
|
|
|
716
|
+
/** How many entries `entries` describes, evicted ones included. */
|
|
717
|
+
slots(): i32 {
|
|
718
|
+
return toI32(this.entries.length) >> 1
|
|
719
|
+
}
|
|
720
|
+
|
|
670
721
|
/** How many entries the table holds. */
|
|
671
722
|
count(): i32 {
|
|
672
|
-
return
|
|
723
|
+
return this.slots() - this.first
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/** Where dynamic entry `i`, 1 being the newest, sits in `entries`; `i` must be in range. */
|
|
727
|
+
slot(i: i32): i32 {
|
|
728
|
+
return this.slots() - i
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/** Where entry `k` of `entries` starts in `bytes`. */
|
|
732
|
+
startOf(k: i32): i32 {
|
|
733
|
+
return this.entries[k * 2]
|
|
673
734
|
}
|
|
674
735
|
|
|
675
|
-
/**
|
|
736
|
+
/** Where entry `k` of `entries` ends in `bytes`: where the next starts, or the end of `bytes`. */
|
|
737
|
+
endOf(k: i32): i32 {
|
|
738
|
+
return k + 1 < this.slots() ? this.entries[k * 2 + 2] : toI32(this.bytes.length)
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/** What entry `k` of `entries` costs (§4.1); it fits an `i32` because the table holds it. */
|
|
742
|
+
costOf(k: i32): i32 {
|
|
743
|
+
return this.endOf(k) - this.startOf(k) + HPACK_ENTRY_OVERHEAD
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/** Where the name of dynamic entry `i` starts in `bytes`. */
|
|
747
|
+
nameStart(i: i32): i32 {
|
|
748
|
+
return this.startOf(this.slot(i))
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/** How many octets the name of dynamic entry `i` has. */
|
|
752
|
+
nameLength(i: i32): i32 {
|
|
753
|
+
return this.entries[this.slot(i) * 2 + 1]
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/** Where the value of dynamic entry `i` starts in `bytes`, straight after its name. */
|
|
757
|
+
valueStart(i: i32): i32 {
|
|
758
|
+
return this.nameStart(i) + this.nameLength(i)
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/** How many octets the value of dynamic entry `i` has. */
|
|
762
|
+
valueLength(i: i32): i32 {
|
|
763
|
+
return this.endOf(this.slot(i)) - this.valueStart(i)
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/** What dynamic entry `i` costs (§4.1). */
|
|
767
|
+
entrySize(i: i32): i32 {
|
|
768
|
+
return this.costOf(this.slot(i))
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/** A fresh copy of the name of dynamic entry `i`. It allocates; the codec reads the window instead. */
|
|
676
772
|
name(i: i32): u8[] {
|
|
677
|
-
|
|
773
|
+
const out: u8[] = []
|
|
774
|
+
hpackAppend(out, this.bytes, this.nameStart(i), this.nameLength(i))
|
|
775
|
+
return out
|
|
678
776
|
}
|
|
679
777
|
|
|
680
|
-
/**
|
|
778
|
+
/** A fresh copy of the value of dynamic entry `i`, as `name` makes the name. */
|
|
681
779
|
value(i: i32): u8[] {
|
|
682
|
-
|
|
780
|
+
const out: u8[] = []
|
|
781
|
+
hpackAppend(out, this.bytes, this.valueStart(i), this.valueLength(i))
|
|
782
|
+
return out
|
|
683
783
|
}
|
|
684
784
|
|
|
685
785
|
/** Evicts the oldest entries until the size is at most `limit` (§4.4). */
|
|
686
786
|
evictTo(limit: i32): void {
|
|
687
787
|
while (toI64(this.size) > toI64(limit) && this.count() > 0) {
|
|
688
|
-
this.size -=
|
|
788
|
+
this.size -= this.costOf(this.first)
|
|
689
789
|
this.first += 1
|
|
690
790
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
791
|
+
this.compact()
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/** Shifts the evicted prefix out of `entries`, and out of `bytes`, once it is half of either. */
|
|
795
|
+
compact(): void {
|
|
796
|
+
const end: i32 = toI32(this.bytes.length)
|
|
797
|
+
const start: i32 = this.count() > 0 ? this.startOf(this.first) : end
|
|
798
|
+
const n: i32 = this.slots()
|
|
799
|
+
if (this.first > 0 && this.first * 2 >= n) {
|
|
800
|
+
const live: i32 = (n - this.first) * 2
|
|
801
|
+
const from: i32 = this.first * 2
|
|
802
|
+
for (let k: i32 = 0; k < live; k += 1) {
|
|
803
|
+
this.entries[k] = this.entries[from + k]
|
|
804
|
+
}
|
|
805
|
+
while (toI32(this.entries.length) > live) {
|
|
806
|
+
this.entries.pop()
|
|
697
807
|
}
|
|
698
|
-
this.names = names
|
|
699
|
-
this.values = values
|
|
700
808
|
this.first = 0
|
|
701
809
|
}
|
|
810
|
+
if (start > 0 && start * 2 >= end) {
|
|
811
|
+
for (let k: i32 = start; k < end; k += 1) {
|
|
812
|
+
this.bytes[k - start] = this.bytes[k]
|
|
813
|
+
}
|
|
814
|
+
while (toI32(this.bytes.length) > end - start) {
|
|
815
|
+
this.bytes.pop()
|
|
816
|
+
}
|
|
817
|
+
for (let k: i32 = this.first; k < this.slots(); k += 1) {
|
|
818
|
+
this.entries[k * 2] -= start
|
|
819
|
+
}
|
|
820
|
+
}
|
|
702
821
|
}
|
|
703
822
|
|
|
704
823
|
/**
|
|
705
|
-
* Adds a field as the newest entry,
|
|
706
|
-
* larger than the whole table empties it and is not
|
|
707
|
-
* not an error.
|
|
824
|
+
* Adds a field as the newest entry, copying its octets in, evicting first
|
|
825
|
+
* to make room. An entry larger than the whole table empties it and is not
|
|
826
|
+
* added (§4.4): that is not an error. Neither array may be `bytes` itself,
|
|
827
|
+
* since eviction may move what it holds. Returns the octets it pushed into
|
|
828
|
+
* `bytes`: none for an entry it did not add.
|
|
708
829
|
*/
|
|
709
|
-
add(name: u8[], value: u8[]):
|
|
830
|
+
add(name: u8[], value: u8[]): i32 {
|
|
710
831
|
const cost: i64 = hpackEntrySize(name, value)
|
|
711
832
|
if (cost > toI64(this.maxSize)) {
|
|
712
833
|
const nothing: i32 = 0
|
|
713
834
|
this.evictTo(nothing)
|
|
714
|
-
return
|
|
835
|
+
return nothing
|
|
715
836
|
}
|
|
716
837
|
this.evictTo(this.maxSize - toI32(cost))
|
|
717
|
-
this.
|
|
718
|
-
this.
|
|
838
|
+
this.entries.push(toI32(this.bytes.length))
|
|
839
|
+
this.entries.push(toI32(name.length))
|
|
840
|
+
let pushed: i32 = 0
|
|
841
|
+
for (const b of name) {
|
|
842
|
+
this.bytes.push(b)
|
|
843
|
+
pushed += 1
|
|
844
|
+
}
|
|
845
|
+
for (const b of value) {
|
|
846
|
+
this.bytes.push(b)
|
|
847
|
+
pushed += 1
|
|
848
|
+
}
|
|
719
849
|
this.size += toI32(cost)
|
|
850
|
+
return pushed
|
|
720
851
|
}
|
|
721
852
|
|
|
722
853
|
/** Sets the maximum size, evicting what no longer fits (§4.3). */
|
|
@@ -753,16 +884,90 @@ const hpackRepresentationOf = (b: i32): i32 => {
|
|
|
753
884
|
return HPACK_REP_WITHOUT
|
|
754
885
|
}
|
|
755
886
|
|
|
887
|
+
/**
|
|
888
|
+
* The size classes of a decoded field's storage: class `j` holds arrays made
|
|
889
|
+
* with room for 2^j octets, up to 2^23, past any header list a connection
|
|
890
|
+
* would accept; a longer field grows the last class's array once.
|
|
891
|
+
*/
|
|
892
|
+
const HPACK_FIELD_CLASSES: i32 = 24
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* What an `HpackDecoder` decodes a block with: the read position, reset for
|
|
896
|
+
* each block; a literal's name and value as they are read, before they are
|
|
897
|
+
* counted, copied or indexed; and the arrays the fields gave back, by size
|
|
898
|
+
* class, each class made when first needed.
|
|
899
|
+
*/
|
|
900
|
+
export class HpackWork {
|
|
901
|
+
cursor: HpackCursor
|
|
902
|
+
nameScratch: u8[]
|
|
903
|
+
valueScratch: u8[]
|
|
904
|
+
pools: u8[][][]
|
|
905
|
+
|
|
906
|
+
constructor() {
|
|
907
|
+
this.nameScratch = []
|
|
908
|
+
this.valueScratch = []
|
|
909
|
+
const nothing: i32 = 0
|
|
910
|
+
this.cursor = new HpackCursor(this.nameScratch, nothing, nothing)
|
|
911
|
+
this.pools = []
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/** The smallest size class with room for `length` octets: the first `j` with 2^j past it, at most the last. */
|
|
915
|
+
classOf(length: i32): i32 {
|
|
916
|
+
let j: i32 = 0
|
|
917
|
+
while (j < HPACK_FIELD_CLASSES - 1 && 1 << j <= length) {
|
|
918
|
+
j += 1
|
|
919
|
+
}
|
|
920
|
+
return j
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/** The pool of class `j`, made the first time a field needs it. */
|
|
924
|
+
pool(j: i32): u8[][] {
|
|
925
|
+
while (toI32(this.pools.length) <= j) {
|
|
926
|
+
const fresh: u8[][] = []
|
|
927
|
+
this.pools.push(fresh)
|
|
928
|
+
}
|
|
929
|
+
return this.pools[j]
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* An empty array with room for `length` octets: one an earlier block gave
|
|
934
|
+
* back, emptied, to its class's pool, or, once the pool is empty, a new one made with
|
|
935
|
+
* the class's room.
|
|
936
|
+
*/
|
|
937
|
+
take(length: i32): u8[] {
|
|
938
|
+
const j: i32 = this.classOf(length)
|
|
939
|
+
const pool: u8[][] = this.pool(j)
|
|
940
|
+
const kept: i32 = toI32(pool.length)
|
|
941
|
+
if (kept === 0) {
|
|
942
|
+
const made: u8[] = new Array<u8>(1 << j)
|
|
943
|
+
hpackEmpty(made)
|
|
944
|
+
return made
|
|
945
|
+
}
|
|
946
|
+
const out: u8[] = pool[kept - 1]
|
|
947
|
+
pool.pop()
|
|
948
|
+
return out
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
756
952
|
/**
|
|
757
953
|
* One HTTP/2 connection's receiving side: its dynamic table, the SETTINGS
|
|
758
954
|
* limit the table may grow to, and the header-list limit.
|
|
759
955
|
*
|
|
760
956
|
* `decode` answers the block's fields in `names`, `values` and
|
|
761
|
-
* `neverIndexed`, which it
|
|
762
|
-
*
|
|
763
|
-
*
|
|
764
|
-
*
|
|
765
|
-
*
|
|
957
|
+
* `neverIndexed`, which it empties and refills on every call. Each name and
|
|
958
|
+
* value is an array the decoder owns, taken from its working storage's pools
|
|
959
|
+
* by its length and given back, emptied, at the next block, so **a field is
|
|
960
|
+
* valid until the next `decode`**: a caller that keeps one past it copies it,
|
|
961
|
+
* since the next block may refill the same array with another field. That is
|
|
962
|
+
* what keeps the storage a peer can make a connection hold bounded by the
|
|
963
|
+
* header-list limit, however many blocks it sends and whatever becomes of
|
|
964
|
+
* their streams (`docs/security/http2.md`, H2-1): a class `j` array holds a
|
|
965
|
+
* field of at least 2^(j-1) octets, so no class ever holds more than about
|
|
966
|
+
* twice the limit, and only the classes up to log2 of the limit, plus one,
|
|
967
|
+
* hold any. `settingsLimit` is the SETTINGS_HEADER_TABLE_SIZE this endpoint
|
|
968
|
+
* advertised and the peer acknowledged; the table starts at it.
|
|
969
|
+
* `maxHeaderListSize` counts each field as its name, its value and 32 bytes,
|
|
970
|
+
* as SETTINGS_MAX_HEADER_LIST_SIZE does (RFC 9113 §6.5.2).
|
|
766
971
|
*/
|
|
767
972
|
export class HpackDecoder {
|
|
768
973
|
table: HpackTable
|
|
@@ -776,6 +981,17 @@ export class HpackDecoder {
|
|
|
776
981
|
names: u8[][]
|
|
777
982
|
values: u8[][]
|
|
778
983
|
neverIndexed: boolean[]
|
|
984
|
+
/** The storage a block is decoded with, made by the first `decode`, so an idle connection holds none. */
|
|
985
|
+
work: HpackWork | null = null
|
|
986
|
+
/**
|
|
987
|
+
* The octets the decoder has moved through its dynamic table, counted where
|
|
988
|
+
* they move: each name or value `appendNameAt` and `appendValueAt` copy out
|
|
989
|
+
* of the table, and each octet `table.add` pushes in. The budget
|
|
990
|
+
* (`HPACK_COPY_FACTOR`) is charged before a literal's copies, from the
|
|
991
|
+
* sizes it will copy; this counts what was copied, so that tests can hold
|
|
992
|
+
* the two equal. It is public so that tests can read it.
|
|
993
|
+
*/
|
|
994
|
+
tableCopied: i64 = 0
|
|
779
995
|
|
|
780
996
|
constructor(settingsLimit: i32, maxHeaderListSize: i32) {
|
|
781
997
|
if (maxHeaderListSize < 0) {
|
|
@@ -792,6 +1008,17 @@ export class HpackDecoder {
|
|
|
792
1008
|
this.neverIndexed = []
|
|
793
1009
|
}
|
|
794
1010
|
|
|
1011
|
+
/** The decoder's working storage, made the first time it is asked for. */
|
|
1012
|
+
working(): HpackWork {
|
|
1013
|
+
const work: HpackWork | null = this.work
|
|
1014
|
+
if (work !== null) {
|
|
1015
|
+
return work
|
|
1016
|
+
}
|
|
1017
|
+
const made: HpackWork = new HpackWork()
|
|
1018
|
+
this.work = made
|
|
1019
|
+
return made
|
|
1020
|
+
}
|
|
1021
|
+
|
|
795
1022
|
/**
|
|
796
1023
|
* Takes a new SETTINGS_HEADER_TABLE_SIZE, once the peer has acknowledged it.
|
|
797
1024
|
* A limit below the table's current maximum obliges the peer's encoder to
|
|
@@ -808,37 +1035,34 @@ export class HpackDecoder {
|
|
|
808
1035
|
}
|
|
809
1036
|
}
|
|
810
1037
|
|
|
811
|
-
/**
|
|
812
|
-
|
|
813
|
-
* dynamic name is the table's own array, not a copy: it may become another
|
|
814
|
-
* entry's name, since the table never changes an entry, but it is never
|
|
815
|
-
* answered to the caller as it is.
|
|
816
|
-
*/
|
|
817
|
-
nameAt(index: i32): u8[] {
|
|
1038
|
+
/** Appends the name at static or dynamic index `index`, which must be in range, to `out`. */
|
|
1039
|
+
appendNameAt(index: i32, out: u8[]): void {
|
|
818
1040
|
if (index <= HPACK_STATIC_LENGTH) {
|
|
819
|
-
|
|
1041
|
+
hpackAppendText(out, hpackStaticName(index))
|
|
1042
|
+
return
|
|
820
1043
|
}
|
|
821
|
-
|
|
1044
|
+
const i: i32 = index - HPACK_STATIC_LENGTH
|
|
1045
|
+
hpackAppend(out, this.table.bytes, this.table.nameStart(i), this.table.nameLength(i))
|
|
1046
|
+
this.tableCopied += toI64(this.table.nameLength(i))
|
|
822
1047
|
}
|
|
823
1048
|
|
|
824
|
-
/**
|
|
825
|
-
|
|
826
|
-
* the static table, or copied once from the dynamic one, so that nothing
|
|
827
|
-
* else is allocated on the way.
|
|
828
|
-
*/
|
|
829
|
-
copyNameAt(index: i32): u8[] {
|
|
1049
|
+
/** Appends the value at `index` to `out`, as `appendNameAt` does the name. */
|
|
1050
|
+
appendValueAt(index: i32, out: u8[]): void {
|
|
830
1051
|
if (index <= HPACK_STATIC_LENGTH) {
|
|
831
|
-
|
|
1052
|
+
hpackAppendText(out, hpackStaticValue(index))
|
|
1053
|
+
return
|
|
832
1054
|
}
|
|
833
|
-
|
|
1055
|
+
const i: i32 = index - HPACK_STATIC_LENGTH
|
|
1056
|
+
hpackAppend(out, this.table.bytes, this.table.valueStart(i), this.table.valueLength(i))
|
|
1057
|
+
this.tableCopied += toI64(this.table.valueLength(i))
|
|
834
1058
|
}
|
|
835
1059
|
|
|
836
|
-
/**
|
|
837
|
-
|
|
1060
|
+
/** How many octets the name at `index` has. */
|
|
1061
|
+
nameLengthAt(index: i32): i32 {
|
|
838
1062
|
if (index <= HPACK_STATIC_LENGTH) {
|
|
839
|
-
return
|
|
1063
|
+
return toI32(hpackStaticName(index).length)
|
|
840
1064
|
}
|
|
841
|
-
return
|
|
1065
|
+
return this.table.nameLength(index - HPACK_STATIC_LENGTH)
|
|
842
1066
|
}
|
|
843
1067
|
|
|
844
1068
|
/** What the entry at `index` counts towards a header list, without building it. */
|
|
@@ -850,8 +1074,7 @@ export class HpackDecoder {
|
|
|
850
1074
|
toI64(HPACK_ENTRY_OVERHEAD)
|
|
851
1075
|
)
|
|
852
1076
|
}
|
|
853
|
-
|
|
854
|
-
return hpackEntrySize(this.table.name(i), this.table.value(i))
|
|
1077
|
+
return toI64(this.table.entrySize(index - HPACK_STATIC_LENGTH))
|
|
855
1078
|
}
|
|
856
1079
|
|
|
857
1080
|
/** Whether `index` names an entry of either table. */
|
|
@@ -859,6 +1082,24 @@ export class HpackDecoder {
|
|
|
859
1082
|
return index >= 1 && index <= HPACK_STATIC_LENGTH + this.table.count()
|
|
860
1083
|
}
|
|
861
1084
|
|
|
1085
|
+
/** Empties the fields and gives each one's array back, emptied, to its class's pool, for the next block. */
|
|
1086
|
+
clear(): void {
|
|
1087
|
+
while (toI32(this.names.length) > 0) {
|
|
1088
|
+
const work: HpackWork = this.working()
|
|
1089
|
+
const last: i32 = toI32(this.names.length) - 1
|
|
1090
|
+
const name: u8[] = this.names[last]
|
|
1091
|
+
const value: u8[] = this.values[last]
|
|
1092
|
+
this.names.pop()
|
|
1093
|
+
this.values.pop()
|
|
1094
|
+
work.pool(work.classOf(toI32(name.length))).push(name)
|
|
1095
|
+
work.pool(work.classOf(toI32(value.length))).push(value)
|
|
1096
|
+
// Emptied as it goes back, so a reference a caller kept past the block reads nothing until the array is taken again.
|
|
1097
|
+
hpackEmpty(name)
|
|
1098
|
+
hpackEmpty(value)
|
|
1099
|
+
this.neverIndexed.pop()
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
862
1103
|
/**
|
|
863
1104
|
* Decodes one whole header block, `block[off, off + len)`, and answers
|
|
864
1105
|
* `HPACK_OK`, `HPACK_LIST_TOO_LARGE` or a negative `HPACK_ERR_*` code; the
|
|
@@ -866,18 +1107,16 @@ export class HpackDecoder {
|
|
|
866
1107
|
* are empty and the decoder is spent.
|
|
867
1108
|
*/
|
|
868
1109
|
decode(block: u8[], off: i32, len: i32): i32 {
|
|
869
|
-
this.
|
|
870
|
-
this.values = []
|
|
871
|
-
this.neverIndexed = []
|
|
1110
|
+
this.clear()
|
|
872
1111
|
if (this.failed !== 0) {
|
|
873
1112
|
return this.failed
|
|
874
1113
|
}
|
|
875
|
-
const
|
|
1114
|
+
const cursor: HpackCursor = this.working().cursor
|
|
1115
|
+
cursor.reset(block, off, len)
|
|
1116
|
+
const result: i32 = this.decodeFields(cursor)
|
|
876
1117
|
if (result < 0) {
|
|
877
1118
|
this.failed = result
|
|
878
|
-
this.
|
|
879
|
-
this.values = []
|
|
880
|
-
this.neverIndexed = []
|
|
1119
|
+
this.clear()
|
|
881
1120
|
}
|
|
882
1121
|
return result
|
|
883
1122
|
}
|
|
@@ -887,15 +1126,28 @@ export class HpackDecoder {
|
|
|
887
1126
|
*
|
|
888
1127
|
* A field is counted against the header-list limit before it is copied out,
|
|
889
1128
|
* and one past the limit is never copied. That is what keeps a block of
|
|
890
|
-
* one-byte indexes, each naming a large dynamic entry, from
|
|
1129
|
+
* one-byte indexes, each naming a large dynamic entry, from copying the
|
|
891
1130
|
* entry's size per byte read: past the limit an indexed field costs a
|
|
892
|
-
* lookup and nothing else
|
|
893
|
-
*
|
|
1131
|
+
* lookup and nothing else, and a literal whose name is an index costs its
|
|
1132
|
+
* value's octets, which the block holds, and a lookup. The one copy a peer
|
|
1133
|
+
* can ask for past the limit is a literal with incremental indexing, which
|
|
1134
|
+
* the table must take: its name and value are copied into the table, at
|
|
1135
|
+
* most the table's maximum size per field, after its name is copied into
|
|
1136
|
+
* the scratch when it is an index, and a block whose copies pass
|
|
1137
|
+
* `HPACK_COPY_FACTOR` times the larger of the table's size and its own
|
|
1138
|
+
* length ends the connection (`docs/security/http2.md`, H2-1). A
|
|
1139
|
+
* literal's strings are read into the two scratch arrays, which the block's
|
|
1140
|
+
* own length and the table's size bound, and a field within the limit is
|
|
1141
|
+
* copied into the pools, which the limit bounds; nothing is allocated once
|
|
1142
|
+
* those have grown to the largest block and list seen.
|
|
894
1143
|
*/
|
|
895
1144
|
decodeFields(c: HpackCursor): i32 {
|
|
896
1145
|
let listSize: i64 = 0
|
|
897
1146
|
let tooLarge: boolean = false
|
|
898
1147
|
let sawField: boolean = false
|
|
1148
|
+
// What this block has asked the table to copy, against its budget.
|
|
1149
|
+
let copied: i64 = 0
|
|
1150
|
+
const length: i32 = c.end - c.pos
|
|
899
1151
|
if (this.owedUpdate >= 0) {
|
|
900
1152
|
if (c.pos >= c.end || hpackRepresentationOf(toI32(c.buf[c.pos])) !== HPACK_REP_SIZE_UPDATE) {
|
|
901
1153
|
return HPACK_ERR_SIZE_UPDATE_MISSING
|
|
@@ -928,11 +1180,18 @@ export class HpackDecoder {
|
|
|
928
1180
|
return HPACK_ERR_INDEX
|
|
929
1181
|
}
|
|
930
1182
|
sawField = true
|
|
931
|
-
|
|
1183
|
+
const size: i64 = this.sizeAt(index)
|
|
1184
|
+
listSize += size
|
|
932
1185
|
tooLarge = tooLarge || listSize > toI64(this.maxHeaderListSize)
|
|
933
1186
|
if (!tooLarge) {
|
|
934
|
-
|
|
935
|
-
this.
|
|
1187
|
+
// The entry's cost less the overhead is its name and value.
|
|
1188
|
+
const nameLength: i32 = this.nameLengthAt(index)
|
|
1189
|
+
const name: u8[] = this.working().take(nameLength)
|
|
1190
|
+
this.appendNameAt(index, name)
|
|
1191
|
+
const value: u8[] = this.working().take(toI32(size) - HPACK_ENTRY_OVERHEAD - nameLength)
|
|
1192
|
+
this.appendValueAt(index, value)
|
|
1193
|
+
this.names.push(name)
|
|
1194
|
+
this.values.push(value)
|
|
936
1195
|
this.neverIndexed.push(false)
|
|
937
1196
|
}
|
|
938
1197
|
break
|
|
@@ -942,41 +1201,82 @@ export class HpackDecoder {
|
|
|
942
1201
|
if (index < 0) {
|
|
943
1202
|
return index
|
|
944
1203
|
}
|
|
945
|
-
|
|
1204
|
+
if (index !== 0 && !this.hasIndex(index)) {
|
|
1205
|
+
return HPACK_ERR_INDEX
|
|
1206
|
+
}
|
|
1207
|
+
const work: HpackWork = this.working()
|
|
1208
|
+
// A new name is read into the scratch; one named by index stays in its table until it is needed.
|
|
1209
|
+
const name: u8[] = work.nameScratch
|
|
1210
|
+
hpackEmpty(name)
|
|
946
1211
|
if (index === 0) {
|
|
947
1212
|
const status: i32 = hpackDecodeString(this.huffman, c, name)
|
|
948
1213
|
if (status < 0) {
|
|
949
1214
|
return status
|
|
950
1215
|
}
|
|
951
|
-
} else if (this.hasIndex(index)) {
|
|
952
|
-
name = this.nameAt(index)
|
|
953
|
-
} else {
|
|
954
|
-
return HPACK_ERR_INDEX
|
|
955
1216
|
}
|
|
956
|
-
const value: u8[] =
|
|
1217
|
+
const value: u8[] = work.valueScratch
|
|
1218
|
+
hpackEmpty(value)
|
|
957
1219
|
const status: i32 = hpackDecodeString(this.huffman, c, value)
|
|
958
1220
|
if (status < 0) {
|
|
959
1221
|
return status
|
|
960
1222
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
// caller is handed copies below.
|
|
964
|
-
if (rep === HPACK_REP_INCREMENTAL) {
|
|
965
|
-
this.table.add(name, value)
|
|
966
|
-
}
|
|
1223
|
+
const nameLength: i32 = index === 0 ? toI32(name.length) : this.nameLengthAt(index)
|
|
1224
|
+
const size: i64 = toI64(nameLength) + toI64(value.length) + toI64(HPACK_ENTRY_OVERHEAD)
|
|
967
1225
|
sawField = true
|
|
968
|
-
listSize +=
|
|
1226
|
+
listSize += size
|
|
969
1227
|
tooLarge = tooLarge || listSize > toI64(this.maxHeaderListSize)
|
|
1228
|
+
const incremental: boolean = rep === HPACK_REP_INCREMENTAL
|
|
1229
|
+
const fits: boolean = size <= toI64(this.table.maxSize)
|
|
1230
|
+
// The table copies the field in, and an indexed name out first.
|
|
1231
|
+
const added: boolean = incremental && fits
|
|
1232
|
+
const nameOut: i64 = index !== 0 ? toI64(nameLength) : toI64(0)
|
|
1233
|
+
const cost: i64 = added ? size - toI64(HPACK_ENTRY_OVERHEAD) + nameOut : toI64(0)
|
|
1234
|
+
copied += cost
|
|
1235
|
+
const scale: i32 = this.table.maxSize > length ? this.table.maxSize : length
|
|
1236
|
+
if (copied > toI64(HPACK_COPY_FACTOR) * toI64(scale)) {
|
|
1237
|
+
return HPACK_ERR_COPY_BUDGET
|
|
1238
|
+
}
|
|
1239
|
+
// An indexed name is copied out only when the table takes the field,
|
|
1240
|
+
// into the scratch, or the program does, straight into its field; both
|
|
1241
|
+
// before the table changes, since adding the field, or emptying the
|
|
1242
|
+
// table for one too large, may evict the entry the name came from and
|
|
1243
|
+
// move what `bytes` holds (§4.4). A field that is neither costs its
|
|
1244
|
+
// value's octets and a lookup.
|
|
1245
|
+
if (index !== 0 && added) {
|
|
1246
|
+
this.appendNameAt(index, name)
|
|
1247
|
+
}
|
|
970
1248
|
if (!tooLarge) {
|
|
971
|
-
|
|
972
|
-
|
|
1249
|
+
const kept: u8[] = work.take(nameLength)
|
|
1250
|
+
if (index === 0 || added) {
|
|
1251
|
+
const from: i32 = 0
|
|
1252
|
+
hpackAppend(kept, name, from, toI32(name.length))
|
|
1253
|
+
} else {
|
|
1254
|
+
this.appendNameAt(index, kept)
|
|
1255
|
+
}
|
|
1256
|
+
this.names.push(kept)
|
|
1257
|
+
this.values.push(this.copyOf(value))
|
|
973
1258
|
this.neverIndexed.push(rep === HPACK_REP_NEVER)
|
|
974
1259
|
}
|
|
1260
|
+
if (added) {
|
|
1261
|
+
this.tableCopied += toI64(this.table.add(name, value))
|
|
1262
|
+
} else if (incremental) {
|
|
1263
|
+
const nothing: i32 = 0
|
|
1264
|
+
this.table.evictTo(nothing)
|
|
1265
|
+
}
|
|
975
1266
|
}
|
|
976
1267
|
}
|
|
977
1268
|
}
|
|
978
1269
|
return tooLarge ? HPACK_LIST_TOO_LARGE : HPACK_OK
|
|
979
1270
|
}
|
|
1271
|
+
|
|
1272
|
+
/** A pooled copy of `bytes`, for the caller. */
|
|
1273
|
+
copyOf(bytes: u8[]): u8[] {
|
|
1274
|
+
const out: u8[] = this.working().take(toI32(bytes.length))
|
|
1275
|
+
for (const b of bytes) {
|
|
1276
|
+
out.push(b)
|
|
1277
|
+
}
|
|
1278
|
+
return out
|
|
1279
|
+
}
|
|
980
1280
|
}
|
|
981
1281
|
|
|
982
1282
|
/**
|
|
@@ -1025,7 +1325,11 @@ export class HpackEncoder {
|
|
|
1025
1325
|
}
|
|
1026
1326
|
const n: i32 = this.table.count()
|
|
1027
1327
|
for (let i: i32 = 1; i <= n; i += 1) {
|
|
1028
|
-
|
|
1328
|
+
const t: HpackTable = this.table
|
|
1329
|
+
if (
|
|
1330
|
+
hpackSameAsWindow(name, t.bytes, t.nameStart(i), t.nameLength(i)) &&
|
|
1331
|
+
hpackSameAsWindow(value, t.bytes, t.valueStart(i), t.valueLength(i))
|
|
1332
|
+
) {
|
|
1029
1333
|
return HPACK_STATIC_LENGTH + i
|
|
1030
1334
|
}
|
|
1031
1335
|
}
|
|
@@ -1041,7 +1345,7 @@ export class HpackEncoder {
|
|
|
1041
1345
|
}
|
|
1042
1346
|
const n: i32 = this.table.count()
|
|
1043
1347
|
for (let i: i32 = 1; i <= n; i += 1) {
|
|
1044
|
-
if (
|
|
1348
|
+
if (hpackSameAsWindow(name, this.table.bytes, this.table.nameStart(i), this.table.nameLength(i))) {
|
|
1045
1349
|
return HPACK_STATIC_LENGTH + i
|
|
1046
1350
|
}
|
|
1047
1351
|
}
|
|
@@ -1088,7 +1392,7 @@ export class HpackEncoder {
|
|
|
1088
1392
|
}
|
|
1089
1393
|
hpackEncodeString(this.huffman, out, value, huffman)
|
|
1090
1394
|
if (indexing === HPACK_INDEX_INCREMENTAL) {
|
|
1091
|
-
this.table.add(
|
|
1395
|
+
this.table.add(name, value)
|
|
1092
1396
|
}
|
|
1093
1397
|
}
|
|
1094
1398
|
}
|