@amritk/nish-x86_64-linux 0.18.0 → 0.20.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.
Files changed (59) hide show
  1. package/INSTALL.md +43 -25
  2. package/bin/nish +0 -0
  3. package/package.json +2 -2
  4. package/runtime/nish.d.ts +55 -5
  5. package/runtime/nish.h +215 -18
  6. package/runtime/nish.mjs +16 -0
  7. package/runtime/runtime-aes.c +490 -0
  8. package/runtime/runtime-cross.c +366 -0
  9. package/runtime/runtime-host.c +7 -6
  10. package/runtime/runtime-net.c +93 -34
  11. package/runtime/runtime-os.c +213 -15
  12. package/runtime/runtime-parallel.c +590 -107
  13. package/runtime/runtime-simd.c +4 -4
  14. package/runtime/runtime-wasm.c +5 -3
  15. package/runtime/runtime.c +69 -13
  16. package/runtime/shim.mjs +71 -1
  17. package/scripts/build.sh +223 -22
  18. package/std/README.md +81 -51
  19. package/std/bytes.ts +163 -0
  20. package/std/crypto/LICENSE-libsecp256k1 +19 -0
  21. package/std/crypto/aes.ts +238 -76
  22. package/std/crypto/chacha20poly1305.ts +43 -9
  23. package/std/crypto/ed25519.ts +840 -0
  24. package/std/crypto/hkdf.ts +304 -12
  25. package/std/crypto/hmac.ts +245 -53
  26. package/std/crypto/p256.ts +932 -61
  27. package/std/crypto/x25519.ts +55 -37
  28. package/std/crypto/x509-random.ts +71 -0
  29. package/std/crypto/x509.ts +9 -9
  30. package/std/json-tokenizer.ts +1296 -0
  31. package/std/json-typed.ts +1014 -0
  32. package/std/json-value.ts +1163 -0
  33. package/std/json.ts +1120 -34
  34. package/std/net/buffer-pool.ts +151 -0
  35. package/std/net/hpack.ts +416 -112
  36. package/std/net/http-fields.ts +93 -0
  37. package/std/net/http1-server.ts +417 -55
  38. package/std/net/http1.ts +31 -6
  39. package/std/net/http2-tls.ts +112 -24
  40. package/std/net/http2.ts +333 -75
  41. package/std/net/http3-server.ts +211 -31
  42. package/std/net/http3.ts +187 -28
  43. package/std/net/prefork.ts +412 -0
  44. package/std/net/quic-listener.ts +88 -7
  45. package/std/net/quic-packet.ts +243 -82
  46. package/std/net/quic-recovery.ts +361 -30
  47. package/std/net/quic-stream.ts +424 -24
  48. package/std/net/quic.ts +519 -167
  49. package/std/net/router.ts +676 -0
  50. package/std/net/tls/codec.ts +426 -27
  51. package/std/net/tls/record-server.ts +478 -132
  52. package/std/net/tls/record.ts +133 -42
  53. package/std/net/tls/schedule.ts +464 -55
  54. package/std/net/tls/ticket.ts +610 -0
  55. package/std/net/tls-tcp.ts +196 -53
  56. package/std/net/tls.ts +1018 -260
  57. package/std/net/url.ts +571 -0
  58. package/std/random.ts +94 -0
  59. package/std/threads.ts +438 -14
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. Every field
59
- * a decoder answers is a fresh array of its own, so a caller may change one
60
- * without touching the table. Private helpers share the importing program's
61
- * flat symbol namespace (`docs/wp26-stdlib.md` §3e), which is why each one
62
- * carries the module's name.
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
- /** The bytes of an ASCII string. */
309
- const hpackBytesOf = (text: string): u8[] => {
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
- /** A copy of `bytes`, so that an answered field and a table entry never alias. */
319
- const hpackCopy = (bytes: u8[]): u8[] => {
320
- const out: u8[] = []
321
- for (const b of bytes) {
322
- out.push(b)
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 two byte arrays hold the same bytes. */
344
- const hpackSameBytes = (a: u8[], b: u8[]): boolean => {
345
- const n: i32 = toI32(a.length)
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(b.length); i += 1) {
350
- if (a[i] !== b[i]) {
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
- * The entries are kept oldest first in `names` and `values` from `first` on,
647
- * so that adding one is a `push` and evicting one moves `first`; the evicted
648
- * prefix is dropped by a copy once it is half the arrays, which keeps both
649
- * amortised constant time without a ring's index arithmetic. Dynamic index 1,
650
- * the newest entry, is the last element.
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
- names: u8[][]
654
- values: u8[][]
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.names = []
664
- this.values = []
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 toI32(this.names.length) - this.first
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
- /** The name of dynamic entry `i`, 1 being the newest; `i` must be in range. */
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
- return this.names[toI32(this.names.length) - i]
773
+ const out: u8[] = []
774
+ hpackAppend(out, this.bytes, this.nameStart(i), this.nameLength(i))
775
+ return out
678
776
  }
679
777
 
680
- /** The value of dynamic entry `i`, 1 being the newest. */
778
+ /** A fresh copy of the value of dynamic entry `i`, as `name` makes the name. */
681
779
  value(i: i32): u8[] {
682
- return this.values[toI32(this.values.length) - i]
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 -= toI32(hpackEntrySize(this.names[this.first], this.values[this.first]))
788
+ this.size -= this.costOf(this.first)
689
789
  this.first += 1
690
790
  }
691
- if (this.first >= 16 && this.first * 2 >= toI32(this.names.length)) {
692
- const names: u8[][] = []
693
- const values: u8[][] = []
694
- for (let i: i32 = this.first; i < toI32(this.names.length); i += 1) {
695
- names.push(this.names[i])
696
- values.push(this.values[i])
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, evicting first to make room. An entry
706
- * larger than the whole table empties it and is not added (§4.4): that is
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[]): void {
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.names.push(name)
718
- this.values.push(value)
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 replaces on every call. `settingsLimit` is the
762
- * SETTINGS_HEADER_TABLE_SIZE this endpoint advertised and the peer
763
- * acknowledged; the table starts at it. `maxHeaderListSize` counts each field
764
- * as its name, its value and 32 bytes, as SETTINGS_MAX_HEADER_LIST_SIZE does
765
- * (RFC 9113 §6.5.2).
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
- * The name of static or dynamic index `index`, which must be in range. A
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
- return hpackBytesOf(hpackStaticName(index))
1041
+ hpackAppendText(out, hpackStaticName(index))
1042
+ return
820
1043
  }
821
- return this.table.name(index - HPACK_STATIC_LENGTH)
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
- * A fresh copy of the name at `index`, for the caller: built straight from
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
- return hpackBytesOf(hpackStaticName(index))
1052
+ hpackAppendText(out, hpackStaticValue(index))
1053
+ return
832
1054
  }
833
- return hpackCopy(this.table.name(index - HPACK_STATIC_LENGTH))
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
- /** A fresh copy of the value at `index`, as `copyNameAt` makes the name. */
837
- copyValueAt(index: i32): u8[] {
1060
+ /** How many octets the name at `index` has. */
1061
+ nameLengthAt(index: i32): i32 {
838
1062
  if (index <= HPACK_STATIC_LENGTH) {
839
- return hpackBytesOf(hpackStaticValue(index))
1063
+ return toI32(hpackStaticName(index).length)
840
1064
  }
841
- return hpackCopy(this.table.value(index - HPACK_STATIC_LENGTH))
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
- const i: i32 = index - HPACK_STATIC_LENGTH
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.names = []
870
- this.values = []
871
- this.neverIndexed = []
1110
+ this.clear()
872
1111
  if (this.failed !== 0) {
873
1112
  return this.failed
874
1113
  }
875
- const result: i32 = this.decodeFields(new HpackCursor(block, off, len))
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.names = []
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 allocating the
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. A literal's strings come from the block itself,
893
- * so what they allocate is bounded by the block's length.
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
- listSize += this.sizeAt(index)
1183
+ const size: i64 = this.sizeAt(index)
1184
+ listSize += size
932
1185
  tooLarge = tooLarge || listSize > toI64(this.maxHeaderListSize)
933
1186
  if (!tooLarge) {
934
- this.names.push(this.copyNameAt(index))
935
- this.values.push(this.copyValueAt(index))
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
- let name: u8[] = []
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
- // The table takes these arrays as they are: `name` may be another
962
- // entry's, which is safe because no entry is ever changed, and the
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 += hpackEntrySize(name, value)
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
- this.names.push(hpackCopy(name))
972
- this.values.push(hpackCopy(value))
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
- if (hpackSameBytes(name, this.table.name(i)) && hpackSameBytes(value, this.table.value(i))) {
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 (hpackSameBytes(name, this.table.name(i))) {
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(hpackCopy(name), hpackCopy(value))
1395
+ this.table.add(name, value)
1092
1396
  }
1093
1397
  }
1094
1398
  }