@amritk/nish-x86_64-linux 0.15.0 → 0.17.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 (57) hide show
  1. package/INSTALL.md +58 -40
  2. package/bin/nish +0 -0
  3. package/package.json +1 -1
  4. package/runtime/nish.d.ts +117 -8
  5. package/runtime/nish.h +67 -10
  6. package/runtime/nish.mjs +71 -7
  7. package/runtime/runtime-host.c +72 -26
  8. package/runtime/runtime-net.c +72 -19
  9. package/runtime/runtime-os.c +127 -45
  10. package/runtime/runtime-parallel.c +5 -1
  11. package/runtime/runtime-wasm.c +26 -2
  12. package/runtime/runtime.c +86 -14
  13. package/runtime/shim.mjs +114 -5
  14. package/scripts/build.sh +1 -3
  15. package/std/README.md +192 -36
  16. package/std/crypto/aes.ts +104 -21
  17. package/std/crypto/base64url.ts +43 -17
  18. package/std/crypto/chacha20poly1305.ts +52 -31
  19. package/std/crypto/ct.ts +14 -3
  20. package/std/crypto/hkdf.ts +385 -8
  21. package/std/crypto/hmac.ts +325 -9
  22. package/std/crypto/p256.ts +153 -27
  23. package/std/crypto/sha1.ts +147 -0
  24. package/std/crypto/sha256.ts +15 -3
  25. package/std/crypto/sha512.ts +16 -2
  26. package/std/crypto/x25519.ts +90 -37
  27. package/std/crypto/x509.ts +265 -65
  28. package/std/net/hpack.ts +1094 -0
  29. package/std/net/http-fields.ts +604 -0
  30. package/std/net/http1-server.ts +1389 -0
  31. package/std/net/http1.ts +1492 -0
  32. package/std/net/http2-frame.ts +745 -0
  33. package/std/net/http2-tls.ts +216 -0
  34. package/std/net/http2.ts +1433 -0
  35. package/std/net/http3-frame.ts +457 -0
  36. package/std/net/http3-server.ts +905 -0
  37. package/std/net/http3.ts +2145 -0
  38. package/std/net/qpack.ts +1163 -0
  39. package/std/net/quic-conn-ack.ts +206 -0
  40. package/std/net/quic-conn-cid.ts +425 -0
  41. package/std/net/quic-conn-params.ts +392 -0
  42. package/std/net/quic-datagram.ts +130 -0
  43. package/std/net/quic-frame.ts +1206 -0
  44. package/std/net/quic-listener.ts +653 -0
  45. package/std/net/quic-packet.ts +1474 -0
  46. package/std/net/quic-recovery.ts +849 -0
  47. package/std/net/quic-stream.ts +1603 -0
  48. package/std/net/quic.ts +3214 -0
  49. package/std/net/tls/codec.ts +1089 -0
  50. package/std/net/tls/record-server.ts +1173 -0
  51. package/std/net/tls/record.ts +621 -0
  52. package/std/net/tls/schedule.ts +429 -0
  53. package/std/net/tls-tcp.ts +410 -0
  54. package/std/net/tls.ts +1368 -0
  55. package/std/net/websocket.ts +823 -0
  56. package/std/net/webtransport.ts +1334 -0
  57. package/std/secret.ts +62 -0
@@ -0,0 +1,1094 @@
1
+ /**
2
+ * `nish/net/hpack` — HPACK, the header compression of HTTP/2, as RFC 7541
3
+ * defines it: the §5.1 integer with an N-bit prefix, the §5.2 string literal
4
+ * with Appendix B's Huffman code, the Appendix A static table, the §2.3.2
5
+ * dynamic table with its §4 size accounting and eviction, and the four §6
6
+ * representations.
7
+ *
8
+ * It is sans-IO, like every lane of WP34 §5 below the sockets: a header block
9
+ * goes in as bytes and comes out as fields, and fields go in and come out as
10
+ * bytes. Framing the block (HEADERS and CONTINUATION) is HTTP/2's job, not this
11
+ * module's, so a block handed to `HpackDecoder.decode` is already whole.
12
+ *
13
+ * import { HpackDecoder, HpackEncoder, HPACK_INDEX_INCREMENTAL, HPACK_OK } from "nish/net/hpack";
14
+ *
15
+ * const enc = new HpackEncoder(4096);
16
+ * const block: u8[] = [];
17
+ * enc.encodeField(block, name, value, HPACK_INDEX_INCREMENTAL, true);
18
+ *
19
+ * const dec = new HpackDecoder(4096, 65536);
20
+ * if (dec.decode(block, 0, toI32(block.length)) === HPACK_OK) {
21
+ * // dec.names[i], dec.values[i], dec.neverIndexed[i]
22
+ * }
23
+ *
24
+ * Names and values are octets (`u8[]`), not strings, because a field value is
25
+ * an octet sequence (RFC 9113 §8.2.1) and HPACK neither knows nor checks what
26
+ * it spells. Checking a name or value is HTTP/2's job, one layer up.
27
+ *
28
+ * **The decoder refuses** a block that would leave its table out of step with
29
+ * the peer's, and it answers a negative `HPACK_ERR_*` code: a block that ends
30
+ * inside a representation, an integer past 2^31 - 1 (or one padded past the
31
+ * five continuation bytes such an integer needs), an index of zero or past the
32
+ * end of the tables, Huffman padding longer than seven bits or not all ones,
33
+ * the EOS symbol inside a string, a table size update above the SETTINGS limit
34
+ * or after the block's first field, and a block that does not begin with the
35
+ * update a lowered limit requires (§4.2). Each of those is HTTP/2's
36
+ * COMPRESSION_ERROR, which closes the connection, so the code is sticky: every
37
+ * later `decode` answers it again without reading anything.
38
+ *
39
+ * A header list larger than the caller's limit is not one of them. The block
40
+ * is still decoded to its end, so that the table stays in step with the
41
+ * encoder's (RFC 9113 §10.5.1 requires it unless the connection closes), but
42
+ * the fields past the limit are dropped and `decode` answers
43
+ * `HPACK_LIST_TOO_LARGE`, which is positive: the next block decodes normally.
44
+ * HTTP/2 answers that one with a 431 or a stream reset, not a GOAWAY.
45
+ *
46
+ * **The encoder's choices are the caller's.** Huffman or not is a per-field
47
+ * argument, as is the representation: incremental indexing, without
48
+ * indexing, or never indexed. A field the caller marks never indexed is always
49
+ * sent as a literal, even when the table holds it, so that an intermediary
50
+ * re-encoding it keeps it out of every table (§7.1.3). The encoder answers the
51
+ * smallest representation the tables allow otherwise, which is what RFC 7541
52
+ * Appendix C's encoder does: a full match by index, else the name by index,
53
+ * the static table before the dynamic one and the lowest index first.
54
+ *
55
+ * Nothing here is constant time. HPACK's secrets are protected by the
56
+ * never-indexed representation, not by the codec's timing (§7.1).
57
+ *
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.
63
+ *
64
+ * Written from RFC 7541, not ported from another implementation. The Huffman
65
+ * code is Appendix B's, stored as its 257 code lengths: the code is canonical
66
+ * (shorter codes first, and symbols in order within a length), so the lengths
67
+ * alone determine every code, and `tests/link/net_hpack_huffman` checks each
68
+ * one against the appendix's table.
69
+ */
70
+
71
+ /** A decode that read the whole block and answered every field. */
72
+ export const HPACK_OK: i32 = 0
73
+
74
+ /**
75
+ * A decode that read the whole block and kept the table in step, but dropped
76
+ * the fields past the caller's header-list limit. Not fatal.
77
+ */
78
+ export const HPACK_LIST_TOO_LARGE: i32 = 1
79
+
80
+ /** The block ended inside a representation, an integer or a string. */
81
+ export const HPACK_ERR_TRUNCATED: i32 = -1
82
+
83
+ /** An integer past 2^31 - 1, or padded past five continuation bytes (§5.1). */
84
+ export const HPACK_ERR_INTEGER_OVERFLOW: i32 = -2
85
+
86
+ /** An index of zero, or one past the end of both tables (§2.3.3, §6.1). */
87
+ export const HPACK_ERR_INDEX: i32 = -3
88
+
89
+ /** Huffman padding longer than seven bits, or not all ones (§5.2). */
90
+ export const HPACK_ERR_HUFFMAN_PADDING: i32 = -4
91
+
92
+ /** The EOS symbol inside a Huffman-coded string (§5.2). */
93
+ export const HPACK_ERR_HUFFMAN_EOS: i32 = -5
94
+
95
+ /** A table size update above the limit the decoder's SETTINGS allow (§6.3). */
96
+ export const HPACK_ERR_TABLE_SIZE: i32 = -6
97
+
98
+ /** A table size update after the block's first field (§4.2). */
99
+ export const HPACK_ERR_SIZE_UPDATE_POSITION: i32 = -7
100
+
101
+ /**
102
+ * A block after the SETTINGS limit was lowered below the table's size that did
103
+ * not begin with an update to at most the smallest limit since (§4.2).
104
+ */
105
+ export const HPACK_ERR_SIZE_UPDATE_MISSING: i32 = -8
106
+
107
+ /** Add the field to the dynamic table (§6.2.1). */
108
+ export const HPACK_INDEX_INCREMENTAL: i32 = 0
109
+
110
+ /** Do not add the field, but let an intermediary choose (§6.2.2). */
111
+ export const HPACK_INDEX_WITHOUT: i32 = 1
112
+
113
+ /** Never add the field to any table, here or downstream (§6.2.3). */
114
+ export const HPACK_INDEX_NEVER: i32 = 2
115
+
116
+ /** The number of entries in the static table (Appendix A). */
117
+ export const HPACK_STATIC_LENGTH: i32 = 61
118
+
119
+ /** What an entry costs beyond its name and value (§4.1). */
120
+ export const HPACK_ENTRY_OVERHEAD: i32 = 32
121
+
122
+ /** SETTINGS_HEADER_TABLE_SIZE's initial value (RFC 9113 §6.5.2). */
123
+ export const HPACK_DEFAULT_TABLE_SIZE: i32 = 4096
124
+
125
+ /**
126
+ * Appendix B's code length for each symbol, 0 to 255 and then EOS (256), one
127
+ * letter a symbol: `A` is 5 bits and `Z` is 30, which are the shortest and
128
+ * longest lengths the code has.
129
+ */
130
+ const HPACK_HUFFMAN_LENGTHS: string =
131
+ "ISXXXXXXXTZXXZXXXXXXXXZXXXXXXXXXBFFHIBDGFFDGDBBBAAABBBBBBBCDKBHFIBCCCCCCCCCCCCCCCCCCCCCCDCDIOIJBKABABABBBACCBBBABCBAABCCCCCKGJIXPRPPRRRSRSSSSSTSTTRSTSSSSQRSRSSTRQPRRSSQSRRTQRSSQQRQSRSSPRRRSRRSVVPORSRUVVVWWVTUOQVWWVWTQQVVXWWWPTPQRQQSRRUUTTVSVWVVWWWWWXWWWWWVZ"
132
+
133
+ /** The EOS symbol, which only ever appears as padding (§5.2). */
134
+ const HPACK_EOS: i32 = 256
135
+
136
+ /** The longest code, in bits. */
137
+ const HPACK_HUFFMAN_MAX_BITS: i32 = 30
138
+
139
+ /**
140
+ * Appendix A's names, by index. A `switch` rather than a table because a
141
+ * module constant cannot be an array
142
+ * ([LANGUAGE.md](../../docs/LANGUAGE.md#module-constants)); LLVM turns a
143
+ * `switch` whose arms each return a constant into a lookup.
144
+ */
145
+ const hpackStaticName = (index: i32): string => {
146
+ switch (index) {
147
+ case 1:
148
+ return ":authority"
149
+ case 2:
150
+ return ":method"
151
+ case 3:
152
+ return ":method"
153
+ case 4:
154
+ return ":path"
155
+ case 5:
156
+ return ":path"
157
+ case 6:
158
+ return ":scheme"
159
+ case 7:
160
+ return ":scheme"
161
+ case 8:
162
+ return ":status"
163
+ case 9:
164
+ return ":status"
165
+ case 10:
166
+ return ":status"
167
+ case 11:
168
+ return ":status"
169
+ case 12:
170
+ return ":status"
171
+ case 13:
172
+ return ":status"
173
+ case 14:
174
+ return ":status"
175
+ case 15:
176
+ return "accept-charset"
177
+ case 16:
178
+ return "accept-encoding"
179
+ case 17:
180
+ return "accept-language"
181
+ case 18:
182
+ return "accept-ranges"
183
+ case 19:
184
+ return "accept"
185
+ case 20:
186
+ return "access-control-allow-origin"
187
+ case 21:
188
+ return "age"
189
+ case 22:
190
+ return "allow"
191
+ case 23:
192
+ return "authorization"
193
+ case 24:
194
+ return "cache-control"
195
+ case 25:
196
+ return "content-disposition"
197
+ case 26:
198
+ return "content-encoding"
199
+ case 27:
200
+ return "content-language"
201
+ case 28:
202
+ return "content-length"
203
+ case 29:
204
+ return "content-location"
205
+ case 30:
206
+ return "content-range"
207
+ case 31:
208
+ return "content-type"
209
+ case 32:
210
+ return "cookie"
211
+ case 33:
212
+ return "date"
213
+ case 34:
214
+ return "etag"
215
+ case 35:
216
+ return "expect"
217
+ case 36:
218
+ return "expires"
219
+ case 37:
220
+ return "from"
221
+ case 38:
222
+ return "host"
223
+ case 39:
224
+ return "if-match"
225
+ case 40:
226
+ return "if-modified-since"
227
+ case 41:
228
+ return "if-none-match"
229
+ case 42:
230
+ return "if-range"
231
+ case 43:
232
+ return "if-unmodified-since"
233
+ case 44:
234
+ return "last-modified"
235
+ case 45:
236
+ return "link"
237
+ case 46:
238
+ return "location"
239
+ case 47:
240
+ return "max-forwards"
241
+ case 48:
242
+ return "proxy-authenticate"
243
+ case 49:
244
+ return "proxy-authorization"
245
+ case 50:
246
+ return "range"
247
+ case 51:
248
+ return "referer"
249
+ case 52:
250
+ return "refresh"
251
+ case 53:
252
+ return "retry-after"
253
+ case 54:
254
+ return "server"
255
+ case 55:
256
+ return "set-cookie"
257
+ case 56:
258
+ return "strict-transport-security"
259
+ case 57:
260
+ return "transfer-encoding"
261
+ case 58:
262
+ return "user-agent"
263
+ case 59:
264
+ return "vary"
265
+ case 60:
266
+ return "via"
267
+ default:
268
+ return "www-authenticate"
269
+ }
270
+ }
271
+
272
+ /** Appendix A's values, by index; every entry past 16 has an empty value. */
273
+ const hpackStaticValue = (index: i32): string => {
274
+ switch (index) {
275
+ case 2:
276
+ return "GET"
277
+ case 3:
278
+ return "POST"
279
+ case 4:
280
+ return "/"
281
+ case 5:
282
+ return "/index.html"
283
+ case 6:
284
+ return "http"
285
+ case 7:
286
+ return "https"
287
+ case 8:
288
+ return "200"
289
+ case 9:
290
+ return "204"
291
+ case 10:
292
+ return "206"
293
+ case 11:
294
+ return "304"
295
+ case 12:
296
+ return "400"
297
+ case 13:
298
+ return "404"
299
+ case 14:
300
+ return "500"
301
+ case 16:
302
+ return "gzip, deflate"
303
+ default:
304
+ return ""
305
+ }
306
+ }
307
+
308
+ /** The bytes of an ASCII string. */
309
+ const hpackBytesOf = (text: string): u8[] => {
310
+ const out: u8[] = []
311
+ const n: i32 = toI32(text.length)
312
+ for (let i: i32 = 0; i < n; i += 1) {
313
+ out.push(toU8(text.charCodeAt(i)))
314
+ }
315
+ return out
316
+ }
317
+
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)
323
+ }
324
+ return out
325
+ }
326
+
327
+ /** Whether `bytes` spell the ASCII string `text`. */
328
+ const hpackSameAsText = (bytes: u8[], text: string): boolean => {
329
+ const n: i32 = toI32(bytes.length)
330
+ if (n !== toI32(text.length)) {
331
+ return false
332
+ }
333
+ // Bounded by both lengths, which are equal here, so the prover drops both
334
+ // bounds checks rather than trusting the comparison above.
335
+ for (let i: i32 = 0; i < n && i < toI32(text.length); i += 1) {
336
+ if (toI32(bytes[i]) !== toI32(text.charCodeAt(i))) {
337
+ return false
338
+ }
339
+ }
340
+ return true
341
+ }
342
+
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)) {
347
+ return false
348
+ }
349
+ for (let i: i32 = 0; i < n && i < toI32(b.length); i += 1) {
350
+ if (a[i] !== b[i]) {
351
+ return false
352
+ }
353
+ }
354
+ return true
355
+ }
356
+
357
+ /**
358
+ * What an entry of this name and value costs in the table (§4.1), in `i64`
359
+ * because two lengths of up to 2^31 - 1 and the overhead do not fit an `i32`.
360
+ */
361
+ const hpackEntrySize = (name: u8[], value: u8[]): i64 =>
362
+ toI64(name.length) + toI64(value.length) + toI64(HPACK_ENTRY_OVERHEAD)
363
+
364
+ /** Panics unless `[off, off + len)` lies inside `buf`; the window is the caller's. */
365
+ const hpackCheckWindow = (what: string, buf: u8[], off: i32, len: i32): void => {
366
+ if (off < 0 || len < 0 || toI64(off) + toI64(len) > toI64(buf.length)) {
367
+ panic(`${what}: the window [${off}, ${off} + ${len}) is outside a buffer of ${toI32(buf.length)} bytes`)
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Appendix B's Huffman code, rebuilt from its lengths, with the tables that
373
+ * both directions read. Built once per encoder or decoder rather than once per
374
+ * string; a program may build its own to call `hpackHuffmanEncode` and
375
+ * `hpackHuffmanDecode` directly.
376
+ *
377
+ * Canonical means that the codes of one length are consecutive integers in
378
+ * symbol order, and that the first code of a length is one past the last code
379
+ * of the length before, shifted left by the difference. So `firstCode[L]` and
380
+ * `countAt[L]` say which `L`-bit integers are codes, and `symbols` lists the
381
+ * symbols in code order, `baseRank[L]` being where length `L` starts in it.
382
+ */
383
+ export class HpackHuffman {
384
+ /** The code of each symbol, aligned on its least significant bit. */
385
+ codes: i32[]
386
+ /** The length of each symbol's code, in bits. */
387
+ lengths: i32[]
388
+ /** The symbols in code order. */
389
+ symbols: i32[]
390
+ /** The first code of each length, by length (0 to 30). */
391
+ firstCode: i32[]
392
+ /** How many codes each length has, by length. */
393
+ countAt: i32[]
394
+ /** Where each length's symbols start in `symbols`, by length. */
395
+ baseRank: i32[]
396
+
397
+ constructor() {
398
+ this.codes = new Array<i32>(257)
399
+ this.lengths = new Array<i32>(257)
400
+ this.symbols = new Array<i32>(257)
401
+ this.firstCode = new Array<i32>(31)
402
+ this.countAt = new Array<i32>(31)
403
+ this.baseRank = new Array<i32>(31)
404
+ for (let sym: i32 = 0; sym <= HPACK_EOS; sym += 1) {
405
+ this.lengths[sym] = toI32(HPACK_HUFFMAN_LENGTHS.charCodeAt(sym)) - 60
406
+ }
407
+ let code: i32 = 0
408
+ let rank: i32 = 0
409
+ for (let len: i32 = 5; len <= HPACK_HUFFMAN_MAX_BITS; len += 1) {
410
+ this.firstCode[len] = code
411
+ this.baseRank[len] = rank
412
+ for (let sym: i32 = 0; sym <= HPACK_EOS; sym += 1) {
413
+ if (this.lengths[sym] === len) {
414
+ this.codes[sym] = code
415
+ this.symbols[rank] = sym
416
+ code += 1
417
+ rank += 1
418
+ }
419
+ }
420
+ this.countAt[len] = rank - this.baseRank[len]
421
+ code = code << 1
422
+ }
423
+ }
424
+ }
425
+
426
+ /** The length of `src[off, off + len)` once Huffman-coded, in bytes. */
427
+ export const hpackHuffmanLength = (h: HpackHuffman, src: u8[], off: i32, len: i32): i32 => {
428
+ hpackCheckWindow("hpackHuffmanLength", src, off, len)
429
+ let bits: i64 = 0
430
+ const end: i32 = off + len
431
+ // The window check has already proved `0 <= i < src.length` for every `i`
432
+ // below `end`; the loop condition says so again, which is what lets the
433
+ // prover drop the bounds check on each read. The two loops below do the same.
434
+ for (let i: i32 = off; i >= 0 && i < end && i < toI32(src.length); i += 1) {
435
+ bits += toI64(h.lengths[toI32(src[i])])
436
+ }
437
+ const bytes: i64 = (bits + toI64(7)) >> toI64(3)
438
+ if (bytes > toI64(2147483647)) {
439
+ panic("hpackHuffmanLength: more than 2^31 - 1 bytes once coded")
440
+ }
441
+ return toI32(bytes)
442
+ }
443
+
444
+ /**
445
+ * Appends `src[off, off + len)` Huffman-coded to `out`, padded to a byte
446
+ * with the most significant bits of EOS, which are all ones (§5.2).
447
+ *
448
+ * The accumulator holds fewer than eight waiting bits between symbols, and a
449
+ * code is at most 30, so it never needs more than 37 bits.
450
+ */
451
+ export const hpackHuffmanEncode = (h: HpackHuffman, src: u8[], off: i32, len: i32, out: u8[]): void => {
452
+ hpackCheckWindow("hpackHuffmanEncode", src, off, len)
453
+ let acc: i64 = 0
454
+ let bits: i32 = 0
455
+ const end: i32 = off + len
456
+ for (let i: i32 = off; i >= 0 && i < end && i < toI32(src.length); i += 1) {
457
+ const sym: i32 = toI32(src[i])
458
+ const n: i32 = h.lengths[sym]
459
+ acc = (acc << toI64(n)) | toI64(h.codes[sym])
460
+ bits += n
461
+ while (bits >= 8) {
462
+ bits -= 8
463
+ out.push(toU8((acc >> toI64(bits)) & toI64(255)))
464
+ }
465
+ acc = acc & ((toI64(1) << toI64(bits)) - toI64(1))
466
+ }
467
+ if (bits > 0) {
468
+ const pad: i32 = 8 - bits
469
+ out.push(toU8(((acc << toI64(pad)) | ((toI64(1) << toI64(pad)) - toI64(1))) & toI64(255)))
470
+ }
471
+ }
472
+
473
+ /**
474
+ * Appends the Huffman decoding of `src[off, off + len)` to `out`, and answers
475
+ * `HPACK_OK`, `HPACK_ERR_HUFFMAN_EOS` or `HPACK_ERR_HUFFMAN_PADDING`.
476
+ *
477
+ * It reads one bit at a time, and after each one asks whether the bits since
478
+ * the last symbol are a code of their length. Because the code is complete,
479
+ * thirty bits always are. What is left when the input ends is padding, which
480
+ * §5.2 requires to be a prefix of EOS — all ones — and shorter than a byte.
481
+ * `out` keeps what was decoded before an error; a caller discards it.
482
+ */
483
+ export const hpackHuffmanDecode = (h: HpackHuffman, src: u8[], off: i32, len: i32, out: u8[]): i32 => {
484
+ hpackCheckWindow("hpackHuffmanDecode", src, off, len)
485
+ let code: i32 = 0
486
+ let bits: i32 = 0
487
+ const end: i32 = off + len
488
+ for (let i: i32 = off; i >= 0 && i < end && i < toI32(src.length); i += 1) {
489
+ const byte: i32 = toI32(src[i])
490
+ for (let k: i32 = 7; k >= 0; k -= 1) {
491
+ code = (code << 1) | ((byte >> k) & 1)
492
+ bits += 1
493
+ if (bits >= 5) {
494
+ const at: i32 = code - h.firstCode[bits]
495
+ if (at >= 0 && at < h.countAt[bits]) {
496
+ const sym: i32 = h.symbols[h.baseRank[bits] + at]
497
+ if (sym === HPACK_EOS) {
498
+ return HPACK_ERR_HUFFMAN_EOS
499
+ }
500
+ out.push(toU8(sym))
501
+ code = 0
502
+ bits = 0
503
+ }
504
+ }
505
+ }
506
+ }
507
+ if (bits > 7 || code !== (1 << bits) - 1) {
508
+ return HPACK_ERR_HUFFMAN_PADDING
509
+ }
510
+ return HPACK_OK
511
+ }
512
+
513
+ /**
514
+ * A read position in a header block: the bytes, where the next read starts,
515
+ * and where the block ends. The primitives below advance `pos`.
516
+ */
517
+ export class HpackCursor {
518
+ buf: u8[]
519
+ pos: i32
520
+ end: i32
521
+
522
+ /** A cursor over `buf[off, off + len)`, which must lie inside `buf`. */
523
+ constructor(buf: u8[], off: i32, len: i32) {
524
+ hpackCheckWindow("HpackCursor", buf, off, len)
525
+ this.buf = buf
526
+ this.pos = off
527
+ this.end = off + len
528
+ }
529
+ }
530
+
531
+ /**
532
+ * Appends `value` as a §5.1 integer with a `prefixBits`-bit prefix, the
533
+ * first byte's high bits being `flags` (the representation's pattern).
534
+ * A negative value, or a prefix outside 1 to 8 bits, panics: both are the
535
+ * program's choice.
536
+ */
537
+ export const hpackEncodeInteger = (out: u8[], flags: i32, prefixBits: i32, value: i32): void => {
538
+ if (prefixBits < 1 || prefixBits > 8 || value < 0) {
539
+ panic(`hpackEncodeInteger: a value of ${value} with a ${prefixBits}-bit prefix, outside 1 to 8`)
540
+ }
541
+ const max: i32 = (1 << prefixBits) - 1
542
+ if (value < max) {
543
+ out.push(toU8(flags | value))
544
+ return
545
+ }
546
+ out.push(toU8(flags | max))
547
+ let rest: i32 = value - max
548
+ while (rest >= 128) {
549
+ out.push(toU8((rest & 127) | 128))
550
+ rest = rest >> 7
551
+ }
552
+ out.push(toU8(rest))
553
+ }
554
+
555
+ /**
556
+ * Reads a §5.1 integer whose prefix is the low `prefixBits` bits of the byte at
557
+ * the cursor, and answers it, or `HPACK_ERR_TRUNCATED` or
558
+ * `HPACK_ERR_INTEGER_OVERFLOW`, both negative. A prefix outside 1 to 8 bits
559
+ * panics, as it does for `hpackEncodeInteger`.
560
+ *
561
+ * An `i32` needs at most five continuation bytes after a full prefix, so a
562
+ * sixth is refused even when its bits are zero; otherwise a run of `0x80`
563
+ * bytes would be read for as long as the peer cared to send it.
564
+ */
565
+ export const hpackDecodeInteger = (c: HpackCursor, prefixBits: i32): i32 => {
566
+ if (prefixBits < 1 || prefixBits > 8) {
567
+ panic(`hpackDecodeInteger: a ${prefixBits}-bit prefix, outside 1 to 8`)
568
+ }
569
+ if (c.pos >= c.end) {
570
+ return HPACK_ERR_TRUNCATED
571
+ }
572
+ const max: i32 = (1 << prefixBits) - 1
573
+ const first: i32 = toI32(c.buf[c.pos]) & max
574
+ c.pos += 1
575
+ if (first < max) {
576
+ return first
577
+ }
578
+ let value: i64 = toI64(max)
579
+ for (let shift: i32 = 0; shift <= 28; shift += 7) {
580
+ if (c.pos >= c.end) {
581
+ return HPACK_ERR_TRUNCATED
582
+ }
583
+ const b: i32 = toI32(c.buf[c.pos])
584
+ c.pos += 1
585
+ value += toI64(b & 127) << toI64(shift)
586
+ if (value > toI64(2147483647)) {
587
+ return HPACK_ERR_INTEGER_OVERFLOW
588
+ }
589
+ if ((b & 128) === 0) {
590
+ return toI32(value)
591
+ }
592
+ }
593
+ return HPACK_ERR_INTEGER_OVERFLOW
594
+ }
595
+
596
+ /**
597
+ * Appends `data` as a §5.2 string literal: Huffman-coded when `huffman` is
598
+ * true, else raw. A caller who wants the shorter of the two compares
599
+ * `hpackHuffmanLength` with the raw length.
600
+ */
601
+ export const hpackEncodeString = (h: HpackHuffman, out: u8[], data: u8[], huffman: boolean): void => {
602
+ const n: i32 = toI32(data.length)
603
+ if (huffman) {
604
+ hpackEncodeInteger(out, 128, 7, hpackHuffmanLength(h, data, 0, n))
605
+ hpackHuffmanEncode(h, data, 0, n, out)
606
+ return
607
+ }
608
+ hpackEncodeInteger(out, 0, 7, n)
609
+ for (const b of data) {
610
+ out.push(b)
611
+ }
612
+ }
613
+
614
+ /**
615
+ * Reads a §5.2 string literal at the cursor and appends its octets to `out`,
616
+ * answering `HPACK_OK` or a negative `HPACK_ERR_*` code. The length is checked
617
+ * against what the block has left before anything is read.
618
+ */
619
+ export const hpackDecodeString = (h: HpackHuffman, c: HpackCursor, out: u8[]): i32 => {
620
+ if (c.pos >= c.end) {
621
+ return HPACK_ERR_TRUNCATED
622
+ }
623
+ const huffman: boolean = (toI32(c.buf[c.pos]) & 128) !== 0
624
+ const n: i32 = hpackDecodeInteger(c, 7)
625
+ if (n < 0) {
626
+ return n
627
+ }
628
+ if (n > c.end - c.pos) {
629
+ return HPACK_ERR_TRUNCATED
630
+ }
631
+ const at: i32 = c.pos
632
+ c.pos += n
633
+ if (huffman) {
634
+ return hpackHuffmanDecode(h, c.buf, at, n, out)
635
+ }
636
+ for (let i: i32 = at; i < at + n; i += 1) {
637
+ out.push(c.buf[i])
638
+ }
639
+ return HPACK_OK
640
+ }
641
+
642
+ /**
643
+ * The dynamic table (§2.3.2, §4): a FIFO of fields, newest first by index,
644
+ * whose size — every entry's name and value plus 32 — never passes `maxSize`.
645
+ *
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.
651
+ */
652
+ export class HpackTable {
653
+ names: u8[][]
654
+ values: u8[][]
655
+ first: i32
656
+ size: i32
657
+ maxSize: i32
658
+
659
+ constructor(maxSize: i32) {
660
+ if (maxSize < 0) {
661
+ panic(`HpackTable: a maximum size of ${maxSize}`)
662
+ }
663
+ this.names = []
664
+ this.values = []
665
+ this.first = 0
666
+ this.size = 0
667
+ this.maxSize = maxSize
668
+ }
669
+
670
+ /** How many entries the table holds. */
671
+ count(): i32 {
672
+ return toI32(this.names.length) - this.first
673
+ }
674
+
675
+ /** The name of dynamic entry `i`, 1 being the newest; `i` must be in range. */
676
+ name(i: i32): u8[] {
677
+ return this.names[toI32(this.names.length) - i]
678
+ }
679
+
680
+ /** The value of dynamic entry `i`, 1 being the newest. */
681
+ value(i: i32): u8[] {
682
+ return this.values[toI32(this.values.length) - i]
683
+ }
684
+
685
+ /** Evicts the oldest entries until the size is at most `limit` (§4.4). */
686
+ evictTo(limit: i32): void {
687
+ while (toI64(this.size) > toI64(limit) && this.count() > 0) {
688
+ this.size -= toI32(hpackEntrySize(this.names[this.first], this.values[this.first]))
689
+ this.first += 1
690
+ }
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])
697
+ }
698
+ this.names = names
699
+ this.values = values
700
+ this.first = 0
701
+ }
702
+ }
703
+
704
+ /**
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.
708
+ */
709
+ add(name: u8[], value: u8[]): void {
710
+ const cost: i64 = hpackEntrySize(name, value)
711
+ if (cost > toI64(this.maxSize)) {
712
+ const nothing: i32 = 0
713
+ this.evictTo(nothing)
714
+ return
715
+ }
716
+ this.evictTo(this.maxSize - toI32(cost))
717
+ this.names.push(name)
718
+ this.values.push(value)
719
+ this.size += toI32(cost)
720
+ }
721
+
722
+ /** Sets the maximum size, evicting what no longer fits (§4.3). */
723
+ resize(maxSize: i32): void {
724
+ if (maxSize < 0) {
725
+ panic(`HpackTable.resize: a maximum size of ${maxSize}`)
726
+ }
727
+ this.maxSize = maxSize
728
+ this.evictTo(maxSize)
729
+ }
730
+ }
731
+
732
+ /** The four kinds of representation, as the decoder's `switch` reads them. */
733
+ const HPACK_REP_INDEXED: i32 = 0
734
+ const HPACK_REP_INCREMENTAL: i32 = 1
735
+ const HPACK_REP_SIZE_UPDATE: i32 = 2
736
+ const HPACK_REP_NEVER: i32 = 3
737
+ const HPACK_REP_WITHOUT: i32 = 4
738
+
739
+ /** Which representation a block's next byte starts (§6, Figures 5–12). */
740
+ const hpackRepresentationOf = (b: i32): i32 => {
741
+ if ((b & 128) !== 0) {
742
+ return HPACK_REP_INDEXED
743
+ }
744
+ if ((b & 64) !== 0) {
745
+ return HPACK_REP_INCREMENTAL
746
+ }
747
+ if ((b & 32) !== 0) {
748
+ return HPACK_REP_SIZE_UPDATE
749
+ }
750
+ if ((b & 16) !== 0) {
751
+ return HPACK_REP_NEVER
752
+ }
753
+ return HPACK_REP_WITHOUT
754
+ }
755
+
756
+ /**
757
+ * One HTTP/2 connection's receiving side: its dynamic table, the SETTINGS
758
+ * limit the table may grow to, and the header-list limit.
759
+ *
760
+ * `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).
766
+ */
767
+ export class HpackDecoder {
768
+ table: HpackTable
769
+ huffman: HpackHuffman
770
+ settingsLimit: i32
771
+ maxHeaderListSize: i32
772
+ /** The smallest limit set since the last size update, or -1 when none is owed. */
773
+ owedUpdate: i32
774
+ /** The fatal error a decode met, which every later decode answers; 0 until then. */
775
+ failed: i32
776
+ names: u8[][]
777
+ values: u8[][]
778
+ neverIndexed: boolean[]
779
+
780
+ constructor(settingsLimit: i32, maxHeaderListSize: i32) {
781
+ if (maxHeaderListSize < 0) {
782
+ panic(`HpackDecoder: a header-list limit of ${maxHeaderListSize}`)
783
+ }
784
+ this.table = new HpackTable(settingsLimit)
785
+ this.huffman = new HpackHuffman()
786
+ this.settingsLimit = settingsLimit
787
+ this.maxHeaderListSize = maxHeaderListSize
788
+ this.owedUpdate = -1
789
+ this.failed = 0
790
+ this.names = []
791
+ this.values = []
792
+ this.neverIndexed = []
793
+ }
794
+
795
+ /**
796
+ * Takes a new SETTINGS_HEADER_TABLE_SIZE, once the peer has acknowledged it.
797
+ * A limit below the table's current maximum obliges the peer's encoder to
798
+ * begin its next block with an update to at most the smallest limit set
799
+ * since its last one (§4.2); the decoder holds it to that.
800
+ */
801
+ setSettingsLimit(limit: i32): void {
802
+ if (limit < 0) {
803
+ panic(`HpackDecoder.setSettingsLimit: a limit of ${limit}`)
804
+ }
805
+ this.settingsLimit = limit
806
+ if (limit < this.table.maxSize && (this.owedUpdate < 0 || limit < this.owedUpdate)) {
807
+ this.owedUpdate = limit
808
+ }
809
+ }
810
+
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[] {
818
+ if (index <= HPACK_STATIC_LENGTH) {
819
+ return hpackBytesOf(hpackStaticName(index))
820
+ }
821
+ return this.table.name(index - HPACK_STATIC_LENGTH)
822
+ }
823
+
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[] {
830
+ if (index <= HPACK_STATIC_LENGTH) {
831
+ return hpackBytesOf(hpackStaticName(index))
832
+ }
833
+ return hpackCopy(this.table.name(index - HPACK_STATIC_LENGTH))
834
+ }
835
+
836
+ /** A fresh copy of the value at `index`, as `copyNameAt` makes the name. */
837
+ copyValueAt(index: i32): u8[] {
838
+ if (index <= HPACK_STATIC_LENGTH) {
839
+ return hpackBytesOf(hpackStaticValue(index))
840
+ }
841
+ return hpackCopy(this.table.value(index - HPACK_STATIC_LENGTH))
842
+ }
843
+
844
+ /** What the entry at `index` counts towards a header list, without building it. */
845
+ sizeAt(index: i32): i64 {
846
+ if (index <= HPACK_STATIC_LENGTH) {
847
+ return (
848
+ toI64(hpackStaticName(index).length) +
849
+ toI64(hpackStaticValue(index).length) +
850
+ toI64(HPACK_ENTRY_OVERHEAD)
851
+ )
852
+ }
853
+ const i: i32 = index - HPACK_STATIC_LENGTH
854
+ return hpackEntrySize(this.table.name(i), this.table.value(i))
855
+ }
856
+
857
+ /** Whether `index` names an entry of either table. */
858
+ hasIndex(index: i32): boolean {
859
+ return index >= 1 && index <= HPACK_STATIC_LENGTH + this.table.count()
860
+ }
861
+
862
+ /**
863
+ * Decodes one whole header block, `block[off, off + len)`, and answers
864
+ * `HPACK_OK`, `HPACK_LIST_TOO_LARGE` or a negative `HPACK_ERR_*` code; the
865
+ * module comment lists what each means. After a negative answer the fields
866
+ * are empty and the decoder is spent.
867
+ */
868
+ decode(block: u8[], off: i32, len: i32): i32 {
869
+ this.names = []
870
+ this.values = []
871
+ this.neverIndexed = []
872
+ if (this.failed !== 0) {
873
+ return this.failed
874
+ }
875
+ const result: i32 = this.decodeFields(new HpackCursor(block, off, len))
876
+ if (result < 0) {
877
+ this.failed = result
878
+ this.names = []
879
+ this.values = []
880
+ this.neverIndexed = []
881
+ }
882
+ return result
883
+ }
884
+
885
+ /**
886
+ * The body of `decode`: one pass over the representations.
887
+ *
888
+ * A field is counted against the header-list limit before it is copied out,
889
+ * 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
891
+ * 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.
894
+ */
895
+ decodeFields(c: HpackCursor): i32 {
896
+ let listSize: i64 = 0
897
+ let tooLarge: boolean = false
898
+ let sawField: boolean = false
899
+ if (this.owedUpdate >= 0) {
900
+ if (c.pos >= c.end || hpackRepresentationOf(toI32(c.buf[c.pos])) !== HPACK_REP_SIZE_UPDATE) {
901
+ return HPACK_ERR_SIZE_UPDATE_MISSING
902
+ }
903
+ }
904
+ while (c.pos < c.end) {
905
+ const rep: i32 = hpackRepresentationOf(toI32(c.buf[c.pos]))
906
+ switch (rep) {
907
+ case HPACK_REP_SIZE_UPDATE: {
908
+ if (sawField) {
909
+ return HPACK_ERR_SIZE_UPDATE_POSITION
910
+ }
911
+ const size: i32 = hpackDecodeInteger(c, 5)
912
+ if (size < 0) {
913
+ return size
914
+ }
915
+ if (size > this.settingsLimit || (this.owedUpdate >= 0 && size > this.owedUpdate)) {
916
+ return HPACK_ERR_TABLE_SIZE
917
+ }
918
+ this.owedUpdate = -1
919
+ this.table.resize(size)
920
+ break
921
+ }
922
+ case HPACK_REP_INDEXED: {
923
+ const index: i32 = hpackDecodeInteger(c, 7)
924
+ if (index < 0) {
925
+ return index
926
+ }
927
+ if (!this.hasIndex(index)) {
928
+ return HPACK_ERR_INDEX
929
+ }
930
+ sawField = true
931
+ listSize += this.sizeAt(index)
932
+ tooLarge = tooLarge || listSize > toI64(this.maxHeaderListSize)
933
+ if (!tooLarge) {
934
+ this.names.push(this.copyNameAt(index))
935
+ this.values.push(this.copyValueAt(index))
936
+ this.neverIndexed.push(false)
937
+ }
938
+ break
939
+ }
940
+ default: {
941
+ const index: i32 = hpackDecodeInteger(c, rep === HPACK_REP_INCREMENTAL ? 6 : 4)
942
+ if (index < 0) {
943
+ return index
944
+ }
945
+ let name: u8[] = []
946
+ if (index === 0) {
947
+ const status: i32 = hpackDecodeString(this.huffman, c, name)
948
+ if (status < 0) {
949
+ return status
950
+ }
951
+ } else if (this.hasIndex(index)) {
952
+ name = this.nameAt(index)
953
+ } else {
954
+ return HPACK_ERR_INDEX
955
+ }
956
+ const value: u8[] = []
957
+ const status: i32 = hpackDecodeString(this.huffman, c, value)
958
+ if (status < 0) {
959
+ return status
960
+ }
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
+ }
967
+ sawField = true
968
+ listSize += hpackEntrySize(name, value)
969
+ tooLarge = tooLarge || listSize > toI64(this.maxHeaderListSize)
970
+ if (!tooLarge) {
971
+ this.names.push(hpackCopy(name))
972
+ this.values.push(hpackCopy(value))
973
+ this.neverIndexed.push(rep === HPACK_REP_NEVER)
974
+ }
975
+ }
976
+ }
977
+ }
978
+ return tooLarge ? HPACK_LIST_TOO_LARGE : HPACK_OK
979
+ }
980
+ }
981
+
982
+ /**
983
+ * One HTTP/2 connection's sending side: its dynamic table and the size
984
+ * updates it owes the peer.
985
+ *
986
+ * The table starts at `maxSize`, which both ends must already agree on —
987
+ * HTTP/2's 4096 until SETTINGS say otherwise — so no update is sent for it.
988
+ */
989
+ export class HpackEncoder {
990
+ table: HpackTable
991
+ huffman: HpackHuffman
992
+ /** The smallest size set since the last block, or -1 when no update is owed. */
993
+ owedMinimum: i32
994
+
995
+ constructor(maxSize: i32) {
996
+ this.table = new HpackTable(maxSize)
997
+ this.huffman = new HpackHuffman()
998
+ this.owedMinimum = -1
999
+ }
1000
+
1001
+ /**
1002
+ * Sets the table's maximum size, which must not pass the peer's
1003
+ * SETTINGS_HEADER_TABLE_SIZE; the encoder cannot know that limit, so it is
1004
+ * the caller's to keep. Call it between blocks: the next `encodeField`
1005
+ * begins with the update, or with two when the size went down and then up
1006
+ * again, the smallest first (§4.2).
1007
+ */
1008
+ setMaxTableSize(maxSize: i32): void {
1009
+ if (this.owedMinimum < 0 || maxSize < this.owedMinimum) {
1010
+ this.owedMinimum = maxSize
1011
+ }
1012
+ this.table.resize(maxSize)
1013
+ }
1014
+
1015
+ /**
1016
+ * The index of an entry holding both `name` and `value`, or 0 when neither
1017
+ * table has one. The static table is searched first, then the dynamic one
1018
+ * from the newest entry.
1019
+ */
1020
+ findField(name: u8[], value: u8[]): i32 {
1021
+ for (let i: i32 = 1; i <= HPACK_STATIC_LENGTH; i += 1) {
1022
+ if (hpackSameAsText(name, hpackStaticName(i)) && hpackSameAsText(value, hpackStaticValue(i))) {
1023
+ return i
1024
+ }
1025
+ }
1026
+ const n: i32 = this.table.count()
1027
+ for (let i: i32 = 1; i <= n; i += 1) {
1028
+ if (hpackSameBytes(name, this.table.name(i)) && hpackSameBytes(value, this.table.value(i))) {
1029
+ return HPACK_STATIC_LENGTH + i
1030
+ }
1031
+ }
1032
+ return 0
1033
+ }
1034
+
1035
+ /** The lowest index whose entry has `name`, or 0 when neither table has one. */
1036
+ findName(name: u8[]): i32 {
1037
+ for (let i: i32 = 1; i <= HPACK_STATIC_LENGTH; i += 1) {
1038
+ if (hpackSameAsText(name, hpackStaticName(i))) {
1039
+ return i
1040
+ }
1041
+ }
1042
+ const n: i32 = this.table.count()
1043
+ for (let i: i32 = 1; i <= n; i += 1) {
1044
+ if (hpackSameBytes(name, this.table.name(i))) {
1045
+ return HPACK_STATIC_LENGTH + i
1046
+ }
1047
+ }
1048
+ return 0
1049
+ }
1050
+
1051
+ /**
1052
+ * Appends one field to the block in `out`: by index when a table holds it
1053
+ * and `indexing` is not `HPACK_INDEX_NEVER`, else as a literal of the kind
1054
+ * `indexing` names, its name by index when a table has it and its strings
1055
+ * Huffman-coded when `huffman` is true. Any other `indexing` panics.
1056
+ */
1057
+ encodeField(out: u8[], name: u8[], value: u8[], indexing: i32, huffman: boolean): void {
1058
+ if (indexing < HPACK_INDEX_INCREMENTAL || indexing > HPACK_INDEX_NEVER) {
1059
+ panic(`HpackEncoder.encodeField: an indexing mode of ${indexing}`)
1060
+ }
1061
+ if (this.owedMinimum >= 0) {
1062
+ if (this.owedMinimum < this.table.maxSize) {
1063
+ hpackEncodeInteger(out, 32, 5, this.owedMinimum)
1064
+ }
1065
+ hpackEncodeInteger(out, 32, 5, this.table.maxSize)
1066
+ this.owedMinimum = -1
1067
+ }
1068
+ if (indexing !== HPACK_INDEX_NEVER) {
1069
+ const full: i32 = this.findField(name, value)
1070
+ if (full > 0) {
1071
+ hpackEncodeInteger(out, 128, 7, full)
1072
+ return
1073
+ }
1074
+ }
1075
+ const nameIndex: i32 = this.findName(name)
1076
+ switch (indexing) {
1077
+ case HPACK_INDEX_INCREMENTAL:
1078
+ hpackEncodeInteger(out, 64, 6, nameIndex)
1079
+ break
1080
+ case HPACK_INDEX_WITHOUT:
1081
+ hpackEncodeInteger(out, 0, 4, nameIndex)
1082
+ break
1083
+ default:
1084
+ hpackEncodeInteger(out, 16, 4, nameIndex)
1085
+ }
1086
+ if (nameIndex === 0) {
1087
+ hpackEncodeString(this.huffman, out, name, huffman)
1088
+ }
1089
+ hpackEncodeString(this.huffman, out, value, huffman)
1090
+ if (indexing === HPACK_INDEX_INCREMENTAL) {
1091
+ this.table.add(hpackCopy(name), hpackCopy(value))
1092
+ }
1093
+ }
1094
+ }