@amritk/nish-x86_64-linux 0.16.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 (55) hide show
  1. package/INSTALL.md +43 -34
  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 +30 -7
  6. package/runtime/nish.mjs +71 -7
  7. package/runtime/runtime-host.c +43 -24
  8. package/runtime/runtime-net.c +57 -10
  9. package/runtime/runtime-os.c +62 -20
  10. package/runtime/runtime-wasm.c +1 -0
  11. package/runtime/runtime.c +46 -8
  12. package/runtime/shim.mjs +114 -5
  13. package/scripts/build.sh +1 -3
  14. package/std/README.md +166 -35
  15. package/std/crypto/aes.ts +51 -8
  16. package/std/crypto/base64url.ts +25 -12
  17. package/std/crypto/chacha20poly1305.ts +43 -29
  18. package/std/crypto/ct.ts +6 -1
  19. package/std/crypto/hkdf.ts +364 -3
  20. package/std/crypto/hmac.ts +307 -7
  21. package/std/crypto/p256.ts +148 -27
  22. package/std/crypto/sha1.ts +147 -0
  23. package/std/crypto/sha256.ts +3 -2
  24. package/std/crypto/x25519.ts +88 -36
  25. package/std/crypto/x509.ts +130 -47
  26. package/std/net/hpack.ts +1094 -0
  27. package/std/net/http-fields.ts +604 -0
  28. package/std/net/http1-server.ts +1389 -0
  29. package/std/net/http1.ts +1492 -0
  30. package/std/net/http2-frame.ts +745 -0
  31. package/std/net/http2-tls.ts +216 -0
  32. package/std/net/http2.ts +1433 -0
  33. package/std/net/http3-frame.ts +457 -0
  34. package/std/net/http3-server.ts +905 -0
  35. package/std/net/http3.ts +2145 -0
  36. package/std/net/qpack.ts +1163 -0
  37. package/std/net/quic-conn-ack.ts +206 -0
  38. package/std/net/quic-conn-cid.ts +425 -0
  39. package/std/net/quic-conn-params.ts +392 -0
  40. package/std/net/quic-datagram.ts +130 -0
  41. package/std/net/quic-frame.ts +1206 -0
  42. package/std/net/quic-listener.ts +653 -0
  43. package/std/net/quic-packet.ts +1474 -0
  44. package/std/net/quic-recovery.ts +849 -0
  45. package/std/net/quic-stream.ts +1603 -0
  46. package/std/net/quic.ts +3214 -0
  47. package/std/net/tls/codec.ts +1089 -0
  48. package/std/net/tls/record-server.ts +1173 -0
  49. package/std/net/tls/record.ts +621 -0
  50. package/std/net/tls/schedule.ts +429 -0
  51. package/std/net/tls-tcp.ts +410 -0
  52. package/std/net/tls.ts +1368 -0
  53. package/std/net/websocket.ts +823 -0
  54. package/std/net/webtransport.ts +1334 -0
  55. package/std/secret.ts +62 -0
@@ -0,0 +1,1163 @@
1
+ /**
2
+ * `nish/net/qpack` — QPACK, the field compression of HTTP/3, as RFC 9204
3
+ * defines it with a dynamic table of capacity 0: the Appendix A static table,
4
+ * the §4.1.1 prefixed integer up to 62 bits, the §4.1.2 string literal with an
5
+ * N-bit prefix, the §4.5 field-line representations under a Required Insert
6
+ * Count of 0, and what the encoder and decoder streams of §4.2 may carry when
7
+ * neither side ever inserts.
8
+ *
9
+ * import { QpackDecoder, QpackEncoder, QPACK_OK } from "nish/net/qpack";
10
+ *
11
+ * const enc = new QpackEncoder(); // one per connection
12
+ * const section: u8[] = [];
13
+ * enc.beginSection(section);
14
+ * enc.encodeField(section, name, 0, toI32(name.length), value, 0, toI32(value.length), false, true);
15
+ *
16
+ * const dec = new QpackDecoder(16384); // one per connection, reused
17
+ * if (dec.decode(section, 0, toI32(section.length)) === QPACK_OK) {
18
+ * // field i: dec.bytes[dec.nameStart[i], + dec.nameLength[i]), and the value alike
19
+ * }
20
+ *
21
+ * **Capacity 0 is the whole dynamic table.** This endpoint advertises
22
+ * SETTINGS_QPACK_MAX_TABLE_CAPACITY 0 (the default, §5), so a peer's encoder
23
+ * may never insert, every field section it sends has a Required Insert Count
24
+ * of 0, and nothing in it may name the dynamic table. Its own encoder never
25
+ * inserts either, whatever the peer allows, so the field sections it writes
26
+ * begin `00 00` and reference the static table alone. That is what lets a
27
+ * stream be decoded the moment its HEADERS frame is whole: there is nothing to
28
+ * block on (§2.1.2) and nothing to acknowledge (§4.4.1).
29
+ *
30
+ * **What is refused.** `QpackDecoder.decode` answers QPACK_DECOMPRESSION_FAILED
31
+ * for a section that ends inside a representation, an integer past 2^62 - 1
32
+ * (or padded past the nine continuation bytes such an integer needs), a
33
+ * non-zero Required Insert Count (§4.5.1.1), a negative Base (§4.5.1.2), any
34
+ * reference to the dynamic table, relative or post-Base (§2.2.3), a static
35
+ * index past 98, and Huffman padding that is not EOS's prefix or an EOS inside
36
+ * a string. `receiveEncoderStream` answers QPACK_ENCODER_STREAM_ERROR for a Set
37
+ * Dynamic Table Capacity above 0 (§4.3.1), any Insert (§3.2.2: every entry is
38
+ * larger than the table) and any Duplicate (§2.2.3: there is nothing to
39
+ * duplicate). The encoder's `receiveDecoderStream` answers
40
+ * QPACK_DECODER_STREAM_ERROR for a Section Acknowledgment (§4.4.1: no section
41
+ * it sent needs one), any Insert Count Increment (§4.4.3: it has inserted
42
+ * nothing) and a stream ID past 2^62 - 1. Each of those is a connection error,
43
+ * so each is sticky: the same answer comes back from every later call without
44
+ * reading anything, and `reason` says which rule it was (`QPACK_REASON_*`).
45
+ *
46
+ * **What is accepted.** A Set Dynamic Table Capacity of 0 on the encoder
47
+ * stream, and a Stream Cancellation on the decoder stream, read a byte at a
48
+ * time so that an instruction may be split across STREAM frames anywhere. The
49
+ * only instruction this endpoint may want to send is a Stream Cancellation on
50
+ * its own decoder stream, which §4.2 lets a decoder of capacity 0 omit; it is
51
+ * `qpackPushStreamCancellation`. Opening the two streams (types
52
+ * `QPACK_ENCODER_STREAM` and `QPACK_DECODER_STREAM`) and refusing a second one
53
+ * of either are HTTP/3's.
54
+ *
55
+ * A field section larger than the decoder's limit — each field its name, its
56
+ * value and 32 bytes, as SETTINGS_MAX_FIELD_SECTION_SIZE counts it (RFC 9114
57
+ * §4.2.2) — answers `QPACK_SECTION_TOO_LARGE`, which is negative because it is
58
+ * not one of the RFC's codes: no table depends on the section, so decoding
59
+ * stops there, nothing is kept, and the next section decodes normally. HTTP/3
60
+ * answers it with a 431, not by closing the connection.
61
+ *
62
+ * **Byte windows, and nothing allocated per section.** Names and values go in
63
+ * as windows `(buf, off, len)` and come out as windows into the decoder's own
64
+ * `bytes`, which holds every name and value of the last section back to back.
65
+ * The decoder's arrays are emptied with `pop`, which keeps their storage, so a
66
+ * connection's decoder stops allocating once it has seen its largest section
67
+ * (WP34 N9). A window outside its buffer is the program's mistake and panics.
68
+ *
69
+ * The Huffman code is RFC 7541 Appendix B's, which §4.1.2 uses unmodified, and
70
+ * comes from `nish/net/hpack` rather than a copy. The integers are this
71
+ * module's own because QPACK needs 62 bits and HPACK's stop at 31.
72
+ *
73
+ * Written from RFC 9204, not ported from another implementation. Private
74
+ * names carry the `qpack` prefix because a `std/` module's private functions
75
+ * share the importing program's flat symbol namespace (`docs/wp26-stdlib.md`
76
+ * §3e).
77
+ */
78
+ import {
79
+ HPACK_OK,
80
+ HpackHuffman,
81
+ hpackHuffmanDecode,
82
+ hpackHuffmanEncode,
83
+ hpackHuffmanLength,
84
+ } from "nish/net/hpack"
85
+
86
+ /** A call that met nothing to refuse. */
87
+ export const QPACK_OK: i64 = 0
88
+
89
+ /**
90
+ * A field section past the decoder's size limit. Not one of RFC 9204's codes
91
+ * and not fatal: HTTP/3 answers the request with a 431.
92
+ */
93
+ export const QPACK_SECTION_TOO_LARGE: i64 = -1
94
+
95
+ /** The decoder could not interpret a field section (§6). */
96
+ export const QPACK_DECOMPRESSION_FAILED: i64 = 0x0200
97
+
98
+ /** The decoder could not interpret an instruction on the encoder stream (§6). */
99
+ export const QPACK_ENCODER_STREAM_ERROR: i64 = 0x0201
100
+
101
+ /** The encoder could not interpret an instruction on the decoder stream (§6). */
102
+ export const QPACK_DECODER_STREAM_ERROR: i64 = 0x0202
103
+
104
+ /** The unidirectional stream type of an encoder stream (§4.2). */
105
+ export const QPACK_ENCODER_STREAM: i64 = 0x02
106
+
107
+ /** The unidirectional stream type of a decoder stream (§4.2). */
108
+ export const QPACK_DECODER_STREAM: i64 = 0x03
109
+
110
+ /** SETTINGS_QPACK_MAX_TABLE_CAPACITY's identifier (§5); this module's value is 0. */
111
+ export const QPACK_SETTINGS_MAX_TABLE_CAPACITY: i64 = 0x01
112
+
113
+ /** SETTINGS_QPACK_BLOCKED_STREAMS's identifier (§5); with no table nothing blocks, so 0. */
114
+ export const QPACK_SETTINGS_BLOCKED_STREAMS: i64 = 0x07
115
+
116
+ /** The number of entries in the static table, indexes 0 to 98 (Appendix A). */
117
+ export const QPACK_STATIC_LENGTH: i32 = 99
118
+
119
+ /** What a field costs a field section beyond its name and value (RFC 9114 §4.2.2). */
120
+ export const QPACK_FIELD_OVERHEAD: i32 = 32
121
+
122
+ /** Nothing refused yet. */
123
+ export const QPACK_REASON_NONE: i32 = 0
124
+
125
+ /** The bytes ended inside a representation, an integer or a string. */
126
+ export const QPACK_REASON_TRUNCATED: i32 = 1
127
+
128
+ /** An integer past 2^62 - 1, or padded past nine continuation bytes (§4.1.1). */
129
+ export const QPACK_REASON_INTEGER: i32 = 2
130
+
131
+ /** A field section whose Required Insert Count is not 0 (§4.5.1.1). */
132
+ export const QPACK_REASON_INSERT_COUNT: i32 = 3
133
+
134
+ /** A field section whose Base is negative (§4.5.1.2). */
135
+ export const QPACK_REASON_BASE: i32 = 4
136
+
137
+ /** A field line naming the dynamic table, by relative or post-Base index (§2.2.3). */
138
+ export const QPACK_REASON_DYNAMIC: i32 = 5
139
+
140
+ /** A static index past 98 (§3.1). */
141
+ export const QPACK_REASON_STATIC_INDEX: i32 = 6
142
+
143
+ /** Huffman padding that is not a prefix of EOS, or EOS inside a string (RFC 7541 §5.2). */
144
+ export const QPACK_REASON_HUFFMAN: i32 = 7
145
+
146
+ /** A Set Dynamic Table Capacity above 0 (§4.3.1). */
147
+ export const QPACK_REASON_CAPACITY: i32 = 8
148
+
149
+ /** An Insert with Name Reference or with Literal Name (§3.2.2, §4.3.2, §4.3.3). */
150
+ export const QPACK_REASON_INSERT: i32 = 9
151
+
152
+ /** A Duplicate (§4.3.4). */
153
+ export const QPACK_REASON_DUPLICATE: i32 = 10
154
+
155
+ /** A Section Acknowledgment (§4.4.1). */
156
+ export const QPACK_REASON_ACKNOWLEDGMENT: i32 = 11
157
+
158
+ /** An Insert Count Increment (§4.4.3). */
159
+ export const QPACK_REASON_INCREMENT: i32 = 12
160
+
161
+ /**
162
+ * Where the instruction counters stop. A peer may send a legal one-byte
163
+ * instruction as often as it likes, and an `i32` that overflows panics, so a
164
+ * count saturates here instead: it is for a caller's statistics, not a limit.
165
+ */
166
+ const QPACK_COUNT_MAX: i32 = 2147483647
167
+
168
+ /**
169
+ * 2^62 - 1, the largest integer §4.1.1 requires. Spelled as a product: an
170
+ * `i64` literal past 2^53 is refused. It is `QUIC_MAX_VARINT`'s value, kept
171
+ * here rather than imported so that QPACK does not pull in QUIC's packet
172
+ * protection for one constant.
173
+ */
174
+ const QPACK_MAX_INTEGER: i64 = 1073741824 * 4294967296 - 1
175
+
176
+ /** What `qpackReadInteger` answers for bytes that end inside the integer. */
177
+ const QPACK_INTEGER_TRUNCATED: i64 = -1
178
+
179
+ /** What `qpackReadInteger` answers for an integer past `QPACK_MAX_INTEGER`. */
180
+ const QPACK_INTEGER_OVERFLOW: i64 = -2
181
+
182
+ /** What `qpackReadString` answers for a string that would take the section past its limit. */
183
+ const QPACK_STRING_PAST_LIMIT: i32 = -1
184
+
185
+ /**
186
+ * Appendix A's names, by index. A `switch` rather than a table because a
187
+ * module constant cannot be an array
188
+ * ([LANGUAGE.md](../../docs/LANGUAGE.md#module-constants)). Entries that
189
+ * share a name share an arm.
190
+ */
191
+ const qpackStaticName = (index: i32): string => {
192
+ switch (index) {
193
+ case 0:
194
+ return ":authority"
195
+ case 1:
196
+ return ":path"
197
+ case 2:
198
+ return "age"
199
+ case 3:
200
+ return "content-disposition"
201
+ case 4:
202
+ return "content-length"
203
+ case 5:
204
+ return "cookie"
205
+ case 6:
206
+ return "date"
207
+ case 7:
208
+ return "etag"
209
+ case 8:
210
+ return "if-modified-since"
211
+ case 9:
212
+ return "if-none-match"
213
+ case 10:
214
+ return "last-modified"
215
+ case 11:
216
+ return "link"
217
+ case 12:
218
+ return "location"
219
+ case 13:
220
+ return "referer"
221
+ case 14:
222
+ return "set-cookie"
223
+ case 15:
224
+ case 16:
225
+ case 17:
226
+ case 18:
227
+ case 19:
228
+ case 20:
229
+ case 21:
230
+ return ":method"
231
+ case 22:
232
+ case 23:
233
+ return ":scheme"
234
+ case 24:
235
+ case 25:
236
+ case 26:
237
+ case 27:
238
+ case 28:
239
+ case 63:
240
+ case 64:
241
+ case 65:
242
+ case 66:
243
+ case 67:
244
+ case 68:
245
+ case 69:
246
+ case 70:
247
+ case 71:
248
+ return ":status"
249
+ case 29:
250
+ case 30:
251
+ return "accept"
252
+ case 31:
253
+ return "accept-encoding"
254
+ case 32:
255
+ return "accept-ranges"
256
+ case 33:
257
+ case 34:
258
+ case 75:
259
+ return "access-control-allow-headers"
260
+ case 35:
261
+ return "access-control-allow-origin"
262
+ case 36:
263
+ case 37:
264
+ case 38:
265
+ case 39:
266
+ case 40:
267
+ case 41:
268
+ return "cache-control"
269
+ case 42:
270
+ case 43:
271
+ return "content-encoding"
272
+ case 44:
273
+ case 45:
274
+ case 46:
275
+ case 47:
276
+ case 48:
277
+ case 49:
278
+ case 50:
279
+ case 51:
280
+ case 52:
281
+ case 53:
282
+ case 54:
283
+ return "content-type"
284
+ case 55:
285
+ return "range"
286
+ case 56:
287
+ case 57:
288
+ case 58:
289
+ return "strict-transport-security"
290
+ case 59:
291
+ case 60:
292
+ return "vary"
293
+ case 61:
294
+ return "x-content-type-options"
295
+ case 62:
296
+ return "x-xss-protection"
297
+ case 72:
298
+ return "accept-language"
299
+ case 73:
300
+ case 74:
301
+ return "access-control-allow-credentials"
302
+ case 76:
303
+ case 77:
304
+ case 78:
305
+ return "access-control-allow-methods"
306
+ case 79:
307
+ return "access-control-expose-headers"
308
+ case 80:
309
+ return "access-control-request-headers"
310
+ case 81:
311
+ case 82:
312
+ return "access-control-request-method"
313
+ case 83:
314
+ return "alt-svc"
315
+ case 84:
316
+ return "authorization"
317
+ case 85:
318
+ return "content-security-policy"
319
+ case 86:
320
+ return "early-data"
321
+ case 87:
322
+ return "expect-ct"
323
+ case 88:
324
+ return "forwarded"
325
+ case 89:
326
+ return "if-range"
327
+ case 90:
328
+ return "origin"
329
+ case 91:
330
+ return "purpose"
331
+ case 92:
332
+ return "server"
333
+ case 93:
334
+ return "timing-allow-origin"
335
+ case 94:
336
+ return "upgrade-insecure-requests"
337
+ case 95:
338
+ return "user-agent"
339
+ case 96:
340
+ return "x-forwarded-for"
341
+ default:
342
+ return "x-frame-options"
343
+ }
344
+ }
345
+
346
+ /** Appendix A's values, by index; the entries with none answer the empty string. */
347
+ const qpackStaticValue = (index: i32): string => {
348
+ switch (index) {
349
+ case 1:
350
+ return "/"
351
+ case 2:
352
+ return "0"
353
+ case 4:
354
+ return "0"
355
+ case 15:
356
+ return "CONNECT"
357
+ case 16:
358
+ return "DELETE"
359
+ case 17:
360
+ return "GET"
361
+ case 18:
362
+ return "HEAD"
363
+ case 19:
364
+ return "OPTIONS"
365
+ case 20:
366
+ return "POST"
367
+ case 21:
368
+ return "PUT"
369
+ case 22:
370
+ return "http"
371
+ case 23:
372
+ return "https"
373
+ case 24:
374
+ return "103"
375
+ case 25:
376
+ return "200"
377
+ case 26:
378
+ return "304"
379
+ case 27:
380
+ return "404"
381
+ case 28:
382
+ return "503"
383
+ case 29:
384
+ return "*/*"
385
+ case 30:
386
+ return "application/dns-message"
387
+ case 31:
388
+ return "gzip, deflate, br"
389
+ case 32:
390
+ return "bytes"
391
+ case 33:
392
+ return "cache-control"
393
+ case 34:
394
+ return "content-type"
395
+ case 35:
396
+ return "*"
397
+ case 36:
398
+ return "max-age=0"
399
+ case 37:
400
+ return "max-age=2592000"
401
+ case 38:
402
+ return "max-age=604800"
403
+ case 39:
404
+ return "no-cache"
405
+ case 40:
406
+ return "no-store"
407
+ case 41:
408
+ return "public, max-age=31536000"
409
+ case 42:
410
+ return "br"
411
+ case 43:
412
+ return "gzip"
413
+ case 44:
414
+ return "application/dns-message"
415
+ case 45:
416
+ return "application/javascript"
417
+ case 46:
418
+ return "application/json"
419
+ case 47:
420
+ return "application/x-www-form-urlencoded"
421
+ case 48:
422
+ return "image/gif"
423
+ case 49:
424
+ return "image/jpeg"
425
+ case 50:
426
+ return "image/png"
427
+ case 51:
428
+ return "text/css"
429
+ case 52:
430
+ return "text/html; charset=utf-8"
431
+ case 53:
432
+ return "text/plain"
433
+ case 54:
434
+ return "text/plain;charset=utf-8"
435
+ case 55:
436
+ return "bytes=0-"
437
+ case 56:
438
+ return "max-age=31536000"
439
+ case 57:
440
+ return "max-age=31536000; includesubdomains"
441
+ case 58:
442
+ return "max-age=31536000; includesubdomains; preload"
443
+ case 59:
444
+ return "accept-encoding"
445
+ case 60:
446
+ return "origin"
447
+ case 61:
448
+ return "nosniff"
449
+ case 62:
450
+ return "1; mode=block"
451
+ case 63:
452
+ return "100"
453
+ case 64:
454
+ return "204"
455
+ case 65:
456
+ return "206"
457
+ case 66:
458
+ return "302"
459
+ case 67:
460
+ return "400"
461
+ case 68:
462
+ return "403"
463
+ case 69:
464
+ return "421"
465
+ case 70:
466
+ return "425"
467
+ case 71:
468
+ return "500"
469
+ case 73:
470
+ return "FALSE"
471
+ case 74:
472
+ return "TRUE"
473
+ case 75:
474
+ return "*"
475
+ case 76:
476
+ return "get"
477
+ case 77:
478
+ return "get, post, options"
479
+ case 78:
480
+ return "options"
481
+ case 79:
482
+ return "content-length"
483
+ case 80:
484
+ return "content-type"
485
+ case 81:
486
+ return "get"
487
+ case 82:
488
+ return "post"
489
+ case 83:
490
+ return "clear"
491
+ case 85:
492
+ return "script-src 'none'; object-src 'none'; base-uri 'none'"
493
+ case 86:
494
+ return "1"
495
+ case 91:
496
+ return "prefetch"
497
+ case 93:
498
+ return "*"
499
+ case 94:
500
+ return "1"
501
+ case 97:
502
+ return "deny"
503
+ case 98:
504
+ return "sameorigin"
505
+ default:
506
+ return ""
507
+ }
508
+ }
509
+ /** Panics unless `buf[off, off + len)` lies inside `buf`. */
510
+ const qpackCheckWindow = (where: string, buf: u8[], off: i32, len: i32): void => {
511
+ if (off < 0 || len < 0 || toI64(off) + toI64(len) > toI64(buf.length)) {
512
+ panic(`${where}: the window [${off}, ${off} + ${len}) is outside a buffer of ${buf.length} bytes`)
513
+ }
514
+ }
515
+
516
+ /** Whether `buf[off, off + len)` spells `text`, which is ASCII. */
517
+ const qpackSameText = (buf: u8[], off: i32, len: i32, text: string): boolean => {
518
+ if (len !== toI32(text.length)) {
519
+ return false
520
+ }
521
+ for (let i: i32 = 0; i < len && i < toI32(text.length); i += 1) {
522
+ const at: i32 = off + i
523
+ if (at < 0 || at >= toI32(buf.length) || toI32(buf[at]) !== toI32(text.charCodeAt(i))) {
524
+ return false
525
+ }
526
+ }
527
+ return true
528
+ }
529
+
530
+ /** Appends the bytes of `text`, which is ASCII. */
531
+ const qpackPushText = (out: u8[], text: string): void => {
532
+ const n: i32 = toI32(text.length)
533
+ for (let i: i32 = 0; i < n; i += 1) {
534
+ out.push(toU8(text.charCodeAt(i)))
535
+ }
536
+ }
537
+
538
+ /** Appends `src[off, off + len)` to `out`. */
539
+ const qpackPushBytes = (out: u8[], src: u8[], off: i32, len: i32): void => {
540
+ const end: i32 = off + len
541
+ for (let i: i32 = off; i >= 0 && i < end && i < toI32(src.length); i += 1) {
542
+ out.push(src[i])
543
+ }
544
+ }
545
+
546
+ /**
547
+ * Appends `value` as a §4.1.1 integer with a `prefixBits`-bit prefix, the first
548
+ * byte's high bits being `flags`. A value outside 0 to 2^62 - 1, or a prefix
549
+ * outside 1 to 8 bits, panics: both are the program's choice.
550
+ */
551
+ const qpackPushInteger = (out: u8[], flags: i32, prefixBits: i32, value: i64): void => {
552
+ if (prefixBits < 1 || prefixBits > 8 || value < 0 || value > QPACK_MAX_INTEGER) {
553
+ panic(`qpackPushInteger: a value of ${value} with a ${prefixBits}-bit prefix`)
554
+ }
555
+ const max: i32 = (1 << prefixBits) - 1
556
+ if (value < toI64(max)) {
557
+ out.push(toU8(flags | toI32(value)))
558
+ return
559
+ }
560
+ out.push(toU8(flags | max))
561
+ let rest: i64 = value - toI64(max)
562
+ while (rest >= toI64(128)) {
563
+ out.push(toU8(toI32(rest & toI64(127)) | 128))
564
+ rest = rest >> toI64(7)
565
+ }
566
+ out.push(toU8(toI32(rest)))
567
+ }
568
+
569
+ /**
570
+ * Appends `src[off, off + len)` as a §4.1.2 string literal with a
571
+ * `prefixBits`-bit prefix: the Huffman flag is the prefix's top bit, the
572
+ * length the `prefixBits - 1` bits below it, and `flags` the bits above.
573
+ * When `huffman` is true the string is Huffman-coded if that makes it
574
+ * shorter, and sent raw otherwise, as an empty string always is.
575
+ */
576
+ const qpackPushString = (
577
+ h: HpackHuffman,
578
+ out: u8[],
579
+ flags: i32,
580
+ prefixBits: i32,
581
+ src: u8[],
582
+ off: i32,
583
+ len: i32,
584
+ huffman: boolean
585
+ ): void => {
586
+ const lengthBits: i32 = prefixBits - 1
587
+ const coded: i32 = huffman ? hpackHuffmanLength(h, src, off, len) : len
588
+ if (coded < len) {
589
+ qpackPushInteger(out, flags | (1 << lengthBits), lengthBits, toI64(coded))
590
+ hpackHuffmanEncode(h, src, off, len, out)
591
+ return
592
+ }
593
+ qpackPushInteger(out, flags, lengthBits, toI64(len))
594
+ qpackPushBytes(out, src, off, len)
595
+ }
596
+
597
+ /**
598
+ * Appends a Stream Cancellation for `streamId` (§4.4.2), the one instruction a
599
+ * decoder of capacity 0 may send on its decoder stream, and may also omit.
600
+ * A stream ID outside 0 to 2^62 - 1 panics.
601
+ */
602
+ export const qpackPushStreamCancellation = (out: u8[], streamId: i64): void => {
603
+ qpackPushInteger(out, 64, 6, streamId)
604
+ }
605
+
606
+ /** Whether the section being decoded has a byte left at `d.at`. */
607
+ const qpackHasByte = (d: QpackDecoder, src: u8[]): boolean =>
608
+ d.at >= 0 && d.at < d.end && d.at < toI32(src.length)
609
+
610
+ /** The `QPACK_REASON_*` for a negative `qpackReadInteger` answer. */
611
+ const qpackIntegerReason = (status: i64): i32 =>
612
+ status === QPACK_INTEGER_TRUNCATED ? QPACK_REASON_TRUNCATED : QPACK_REASON_INTEGER
613
+
614
+ /**
615
+ * Reads a §4.1.1 integer whose prefix is the low `prefixBits` bits of the
616
+ * byte at `d.at`, and answers it, or `QPACK_INTEGER_TRUNCATED` or
617
+ * `QPACK_INTEGER_OVERFLOW`. Nine continuation bytes carry 63 bits, so a
618
+ * tenth is refused even when its bits are zero.
619
+ */
620
+ const qpackReadInteger = (d: QpackDecoder, src: u8[], prefixBits: i32): i64 => {
621
+ if (!qpackHasByte(d, src)) {
622
+ return QPACK_INTEGER_TRUNCATED
623
+ }
624
+ const max: i32 = (1 << prefixBits) - 1
625
+ const first: i32 = toI32(src[d.at]) & max
626
+ d.at += 1
627
+ if (first < max) {
628
+ return toI64(first)
629
+ }
630
+ let value: i64 = toI64(max)
631
+ for (let shift: i32 = 0; shift <= 56; shift += 7) {
632
+ if (!qpackHasByte(d, src)) {
633
+ return QPACK_INTEGER_TRUNCATED
634
+ }
635
+ const b: i32 = toI32(src[d.at])
636
+ d.at += 1
637
+ const digit: i64 = toI64(b & 127)
638
+ if (digit > (QPACK_MAX_INTEGER - value) >> toI64(shift)) {
639
+ return QPACK_INTEGER_OVERFLOW
640
+ }
641
+ value += digit << toI64(shift)
642
+ if ((b & 128) === 0) {
643
+ return value
644
+ }
645
+ }
646
+ return QPACK_INTEGER_OVERFLOW
647
+ }
648
+
649
+ /**
650
+ * How many symbols the Huffman code in `src[off, off + len)` spells, found by
651
+ * walking `h`'s canonical tables the way `hpackHuffmanDecode` does but writing
652
+ * nothing. An EOS inside the string is counted as a symbol, so the answer is
653
+ * never below what the decode will write; whether the string is well formed
654
+ * is the decode's to say.
655
+ */
656
+ const qpackHuffmanSymbols = (h: HpackHuffman, src: u8[], off: i32, len: i32): i64 => {
657
+ let code: i32 = 0
658
+ let bits: i32 = 0
659
+ let count: i64 = 0
660
+ const end: i32 = off + len
661
+ for (let i: i32 = off; i >= 0 && i < end && i < toI32(src.length); i += 1) {
662
+ const byte: i32 = toI32(src[i])
663
+ for (let k: i32 = 7; k >= 0; k -= 1) {
664
+ code = (code << 1) | ((byte >> k) & 1)
665
+ bits += 1
666
+ if (bits >= 5 && bits < toI32(h.firstCode.length)) {
667
+ const at: i32 = code - h.firstCode[bits]
668
+ if (at >= 0 && at < h.countAt[bits]) {
669
+ count += toI64(1)
670
+ code = 0
671
+ bits = 0
672
+ }
673
+ }
674
+ }
675
+ }
676
+ return count
677
+ }
678
+
679
+ /**
680
+ * Reads a §4.1.2 string literal with a `prefixBits`-bit prefix at `d.at` and
681
+ * appends its octets to `d.bytes`, answering a `QPACK_REASON_*`, `NONE` when
682
+ * it read one, or `QPACK_STRING_PAST_LIMIT`. The length is checked against
683
+ * what the section has left, and the octets against `d.room`, before anything
684
+ * is appended: a Huffman string decodes to at most 8/5 of its length, and only
685
+ * one that could pass the room is counted first, so `bytes` never grows past
686
+ * the field-section limit however a peer spells its strings.
687
+ */
688
+ const qpackReadString = (d: QpackDecoder, src: u8[], prefixBits: i32): i32 => {
689
+ if (!qpackHasByte(d, src)) {
690
+ return QPACK_REASON_TRUNCATED
691
+ }
692
+ const lengthBits: i32 = prefixBits - 1
693
+ const huffman: boolean = (toI32(src[d.at]) & (1 << lengthBits)) !== 0
694
+ const n: i64 = qpackReadInteger(d, src, lengthBits)
695
+ if (n < 0) {
696
+ return qpackIntegerReason(n)
697
+ }
698
+ if (n > toI64(d.end - d.at)) {
699
+ return QPACK_REASON_TRUNCATED
700
+ }
701
+ const start: i32 = d.at
702
+ const length: i32 = toI32(n)
703
+ d.at = start + length
704
+ let octets: i64 = n
705
+ if (huffman) {
706
+ octets = (n * toI64(8)) / toI64(5)
707
+ if (octets > d.room) {
708
+ octets = qpackHuffmanSymbols(d.huffman, src, start, length)
709
+ }
710
+ }
711
+ if (octets > d.room) {
712
+ return QPACK_STRING_PAST_LIMIT
713
+ }
714
+ const before: i32 = toI32(d.bytes.length)
715
+ if (huffman) {
716
+ if (hpackHuffmanDecode(d.huffman, src, start, length, d.bytes) !== HPACK_OK) {
717
+ return QPACK_REASON_HUFFMAN
718
+ }
719
+ } else {
720
+ qpackPushBytes(d.bytes, src, start, length)
721
+ }
722
+ d.room -= toI64(d.bytes.length) - toI64(before)
723
+ return QPACK_REASON_NONE
724
+ }
725
+
726
+ /**
727
+ * One HTTP/3 connection's receiving side: the field sections the peer's
728
+ * encoder sends on request streams, and the peer's encoder stream.
729
+ *
730
+ * `decode` answers a section's fields as windows into `bytes`: field `i`'s
731
+ * name is `bytes[nameStart[i], + nameLength[i])`, its value likewise, and
732
+ * `neverIndexed[i]` is its N bit, which an intermediary forwarding it must
733
+ * keep (§4.5.4). All of them are replaced by every call.
734
+ */
735
+ export class QpackDecoder {
736
+ huffman: HpackHuffman
737
+ /** The connection error a call met, which every later call answers; `QPACK_OK` until then. */
738
+ failed: i64
739
+ bytes: u8[]
740
+ nameStart: i32[]
741
+ nameLength: i32[]
742
+ valueStart: i32[]
743
+ valueLength: i32[]
744
+ neverIndexed: boolean[]
745
+ /** SETTINGS_MAX_FIELD_SECTION_SIZE as RFC 9114 §4.2.2 counts it. */
746
+ maxFieldSectionSize: i32
747
+ /** Which rule `failed` broke, a `QPACK_REASON_*`. */
748
+ reason: i32
749
+ /** How many Set Dynamic Table Capacity 0 instructions the encoder stream carried, saturating at 2^31 - 1. */
750
+ capacityInstructions: i32
751
+ /** How many fields the last `decode` answered. */
752
+ count: i32
753
+ /**
754
+ * What the section being decoded may still add to `bytes` and its fields'
755
+ * overhead before it is past `maxFieldSectionSize`: every append is checked
756
+ * against it first, so a section past the limit never grows `bytes`.
757
+ */
758
+ room: i64
759
+ /** Where the next read of the section being decoded starts, and where it ends. */
760
+ at: i32
761
+ end: i32
762
+
763
+ constructor(maxFieldSectionSize: i32) {
764
+ if (maxFieldSectionSize < 0) {
765
+ panic(`QpackDecoder: a field-section limit of ${maxFieldSectionSize}`)
766
+ }
767
+ this.huffman = new HpackHuffman()
768
+ this.maxFieldSectionSize = maxFieldSectionSize
769
+ this.failed = QPACK_OK
770
+ this.reason = QPACK_REASON_NONE
771
+ this.capacityInstructions = 0
772
+ this.bytes = []
773
+ this.nameStart = []
774
+ this.nameLength = []
775
+ this.valueStart = []
776
+ this.valueLength = []
777
+ this.neverIndexed = []
778
+ this.count = 0
779
+ this.room = 0
780
+ this.at = 0
781
+ this.end = 0
782
+ }
783
+
784
+ /**
785
+ * Decodes one whole field section, `section[off, off + len)`, and answers
786
+ * `QPACK_OK`, `QPACK_SECTION_TOO_LARGE` or QPACK_DECOMPRESSION_FAILED; the
787
+ * module comment lists what each means. A decoder whose encoder stream was
788
+ * refused answers QPACK_ENCODER_STREAM_ERROR again instead, since the
789
+ * connection is closing. Anything but `QPACK_OK` leaves no fields.
790
+ */
791
+ decode(section: u8[], off: i32, len: i32): i64 {
792
+ qpackCheckWindow("QpackDecoder.decode", section, off, len)
793
+ this.clear()
794
+ if (this.failed !== QPACK_OK) {
795
+ return this.failed
796
+ }
797
+ this.at = off
798
+ this.end = off + len
799
+ const result: i64 = this.decodeLines(section)
800
+ if (result !== QPACK_OK) {
801
+ this.clear()
802
+ }
803
+ if (result === QPACK_DECOMPRESSION_FAILED) {
804
+ this.failed = result
805
+ }
806
+ return result
807
+ }
808
+
809
+ /**
810
+ * Reads the encoder stream's next bytes, `buf[off, off + len)`, and answers
811
+ * `QPACK_OK` or QPACK_ENCODER_STREAM_ERROR (or, once a section was refused,
812
+ * QPACK_DECOMPRESSION_FAILED again). At capacity 0 the one
813
+ * instruction a peer may send is Set Dynamic Table Capacity 0, the single
814
+ * byte `0x20`, so every other first byte is refused on sight: its top bits
815
+ * already say which instruction it begins, and none of them can be valid.
816
+ */
817
+ receiveEncoderStream(buf: u8[], off: i32, len: i32): i64 {
818
+ qpackCheckWindow("QpackDecoder.receiveEncoderStream", buf, off, len)
819
+ if (this.failed !== QPACK_OK) {
820
+ return this.failed
821
+ }
822
+ const end: i32 = off + len
823
+ for (let i: i32 = off; i >= 0 && i < end && i < toI32(buf.length); i += 1) {
824
+ const b: i32 = toI32(buf[i])
825
+ if (b === 32) {
826
+ if (this.capacityInstructions < QPACK_COUNT_MAX) {
827
+ this.capacityInstructions += 1
828
+ }
829
+ } else {
830
+ if ((b & 192) !== 0) {
831
+ this.reason = QPACK_REASON_INSERT
832
+ } else if ((b & 32) !== 0) {
833
+ this.reason = QPACK_REASON_CAPACITY
834
+ } else {
835
+ this.reason = QPACK_REASON_DUPLICATE
836
+ }
837
+ this.failed = QPACK_ENCODER_STREAM_ERROR
838
+ return this.failed
839
+ }
840
+ }
841
+ return QPACK_OK
842
+ }
843
+
844
+ /** Empties the fields, keeping the arrays' storage for the next section. */
845
+ clear(): void {
846
+ while (this.bytes.length > 0) {
847
+ this.bytes.pop()
848
+ }
849
+ while (this.nameStart.length > 0) {
850
+ this.nameStart.pop()
851
+ }
852
+ while (this.nameLength.length > 0) {
853
+ this.nameLength.pop()
854
+ }
855
+ while (this.valueStart.length > 0) {
856
+ this.valueStart.pop()
857
+ }
858
+ while (this.valueLength.length > 0) {
859
+ this.valueLength.pop()
860
+ }
861
+ while (this.neverIndexed.length > 0) {
862
+ this.neverIndexed.pop()
863
+ }
864
+ this.count = 0
865
+ }
866
+
867
+ /**
868
+ * Takes `cost` from what the section has room for and answers true, or
869
+ * answers false and takes nothing when the section would pass its limit.
870
+ */
871
+ take(cost: i64): boolean {
872
+ if (cost > this.room) {
873
+ return false
874
+ }
875
+ this.room -= cost
876
+ return true
877
+ }
878
+
879
+ /** The answer for a string read: `QPACK_OK`, past the limit, or the refusal `status` names. */
880
+ stringResult(status: i32): i64 {
881
+ if (status === QPACK_STRING_PAST_LIMIT) {
882
+ return QPACK_SECTION_TOO_LARGE
883
+ }
884
+ return status === QPACK_REASON_NONE ? QPACK_OK : this.refuse(status)
885
+ }
886
+
887
+ /** Records `reason` and answers QPACK_DECOMPRESSION_FAILED. */
888
+ refuse(reason: i32): i64 {
889
+ this.reason = reason
890
+ return QPACK_DECOMPRESSION_FAILED
891
+ }
892
+
893
+ /** The refusal for a negative `qpackReadInteger` answer. */
894
+ refuseInteger(status: i64): i64 {
895
+ return this.refuse(qpackIntegerReason(status))
896
+ }
897
+
898
+ /** Records a field whose name and value end where `bytes` ends now. */
899
+ addField(nameStart: i32, valueStart: i32, neverIndexed: boolean): void {
900
+ this.nameStart.push(nameStart)
901
+ this.nameLength.push(valueStart - nameStart)
902
+ this.valueStart.push(valueStart)
903
+ this.valueLength.push(toI32(this.bytes.length) - valueStart)
904
+ this.neverIndexed.push(neverIndexed)
905
+ this.count += 1
906
+ }
907
+
908
+ /**
909
+ * The body of `decode`: the prefix, then one pass over the field lines,
910
+ * each told apart by its first byte's pattern (§4.5.2 to §4.5.6).
911
+ */
912
+ decodeLines(src: u8[]): i64 {
913
+ const insertCount: i64 = qpackReadInteger(this, src, 8)
914
+ if (insertCount < 0) {
915
+ return this.refuseInteger(insertCount)
916
+ }
917
+ if (insertCount !== toI64(0)) {
918
+ return this.refuse(QPACK_REASON_INSERT_COUNT)
919
+ }
920
+ if (!qpackHasByte(this, src)) {
921
+ return this.refuse(QPACK_REASON_TRUNCATED)
922
+ }
923
+ // With a Required Insert Count of 0, a Sign bit of 1 makes the Base
924
+ // 0 - Delta Base - 1, which is negative whatever Delta Base is. A Base of
925
+ // 0 or more is accepted at any value, since nothing may use it (§4.5.1.2).
926
+ const negative: boolean = (toI32(src[this.at]) & 128) !== 0
927
+ const deltaBase: i64 = qpackReadInteger(this, src, 7)
928
+ if (deltaBase < 0) {
929
+ return this.refuseInteger(deltaBase)
930
+ }
931
+ if (negative) {
932
+ return this.refuse(QPACK_REASON_BASE)
933
+ }
934
+ this.room = toI64(this.maxFieldSectionSize)
935
+ while (qpackHasByte(this, src)) {
936
+ const b: i32 = toI32(src[this.at])
937
+ let neverIndexed: boolean = false
938
+ const nameStart: i32 = toI32(this.bytes.length)
939
+ // Where the value starts once it is in `bytes`; -1 while it is still to
940
+ // be read as a string literal, which every form but the indexed one has.
941
+ let valueStart: i32 = -1
942
+ if ((b & 128) !== 0) {
943
+ // Indexed Field Line (§4.5.2): `1 T index(6+)`.
944
+ if ((b & 64) === 0) {
945
+ return this.refuse(QPACK_REASON_DYNAMIC)
946
+ }
947
+ const index: i64 = qpackReadInteger(this, src, 6)
948
+ if (index < 0) {
949
+ return this.refuseInteger(index)
950
+ }
951
+ if (index >= toI64(QPACK_STATIC_LENGTH)) {
952
+ return this.refuse(QPACK_REASON_STATIC_INDEX)
953
+ }
954
+ const name: string = qpackStaticName(toI32(index))
955
+ const value: string = qpackStaticValue(toI32(index))
956
+ if (!this.take(toI64(QPACK_FIELD_OVERHEAD) + toI64(name.length) + toI64(value.length))) {
957
+ return QPACK_SECTION_TOO_LARGE
958
+ }
959
+ qpackPushText(this.bytes, name)
960
+ valueStart = toI32(this.bytes.length)
961
+ qpackPushText(this.bytes, value)
962
+ } else if ((b & 64) !== 0) {
963
+ // Literal Field Line with Name Reference (§4.5.4): `01 N T index(4+)`.
964
+ if ((b & 16) === 0) {
965
+ return this.refuse(QPACK_REASON_DYNAMIC)
966
+ }
967
+ neverIndexed = (b & 32) !== 0
968
+ const index: i64 = qpackReadInteger(this, src, 4)
969
+ if (index < 0) {
970
+ return this.refuseInteger(index)
971
+ }
972
+ if (index >= toI64(QPACK_STATIC_LENGTH)) {
973
+ return this.refuse(QPACK_REASON_STATIC_INDEX)
974
+ }
975
+ const name: string = qpackStaticName(toI32(index))
976
+ if (!this.take(toI64(QPACK_FIELD_OVERHEAD) + toI64(name.length))) {
977
+ return QPACK_SECTION_TOO_LARGE
978
+ }
979
+ qpackPushText(this.bytes, name)
980
+ } else if ((b & 32) !== 0) {
981
+ // Literal Field Line with Literal Name (§4.5.6): `001 N H length(3+)`.
982
+ neverIndexed = (b & 16) !== 0
983
+ if (!this.take(toI64(QPACK_FIELD_OVERHEAD))) {
984
+ return QPACK_SECTION_TOO_LARGE
985
+ }
986
+ const name: i64 = this.stringResult(qpackReadString(this, src, 4))
987
+ if (name !== QPACK_OK) {
988
+ return name
989
+ }
990
+ } else {
991
+ // `0001` is an Indexed Field Line with Post-Base Index (§4.5.3) and
992
+ // `0000` a Literal Field Line with Post-Base Name Reference (§4.5.5):
993
+ // both name the dynamic table.
994
+ return this.refuse(QPACK_REASON_DYNAMIC)
995
+ }
996
+ if (valueStart < 0) {
997
+ valueStart = toI32(this.bytes.length)
998
+ const value: i64 = this.stringResult(qpackReadString(this, src, 8))
999
+ if (value !== QPACK_OK) {
1000
+ return value
1001
+ }
1002
+ }
1003
+ this.addField(nameStart, valueStart, neverIndexed)
1004
+ }
1005
+ return QPACK_OK
1006
+ }
1007
+ }
1008
+
1009
+ /**
1010
+ * One HTTP/3 connection's sending side: field sections that reference the
1011
+ * static table alone, and the peer's decoder stream.
1012
+ *
1013
+ * It never inserts, so it needs no state for the peer's table capacity or its
1014
+ * blocked-streams limit, whatever the peer's SETTINGS allow, and it never sends
1015
+ * anything on its own encoder stream once the stream type is written.
1016
+ */
1017
+ export class QpackEncoder {
1018
+ huffman: HpackHuffman
1019
+ /** The connection error the decoder stream met, which every later call answers. */
1020
+ failed: i64
1021
+ /** Which rule `failed` broke, a `QPACK_REASON_*`. */
1022
+ reason: i32
1023
+ /** How many Stream Cancellations the decoder stream carried, saturating at 2^31 - 1, and the last one's stream. */
1024
+ cancellations: i32
1025
+ lastCancelled: i64
1026
+ /** The stream ID of a Stream Cancellation still being read, and its next shift; -1 between instructions. */
1027
+ pendingId: i64
1028
+ pendingShift: i32
1029
+
1030
+ constructor() {
1031
+ this.huffman = new HpackHuffman()
1032
+ this.failed = QPACK_OK
1033
+ this.reason = QPACK_REASON_NONE
1034
+ this.cancellations = 0
1035
+ this.lastCancelled = -1
1036
+ this.pendingId = 0
1037
+ this.pendingShift = -1
1038
+ }
1039
+
1040
+ /**
1041
+ * Appends the prefix every field section of this encoder begins with: a
1042
+ * Required Insert Count of 0 and a Base of 0, the bytes `00 00`.
1043
+ */
1044
+ beginSection(out: u8[]): void {
1045
+ out.push(0)
1046
+ out.push(0)
1047
+ }
1048
+
1049
+ /**
1050
+ * Appends one field line, `name[nameOff, + nameLen)` and
1051
+ * `value[valueOff, + valueLen)`, to the section in `out`. A field the static
1052
+ * table holds whole is one Indexed Field Line; otherwise it is a literal,
1053
+ * with its name by static index when the table has the name, the lowest
1054
+ * index first, and each of its strings Huffman-coded when `huffman` is
1055
+ * true and the code is the shorter spelling.
1056
+ *
1057
+ * A field marked `neverIndexed` is always a literal with the N bit set, even
1058
+ * when the static table holds it, so that an intermediary re-encoding it
1059
+ * keeps it out of every dynamic table downstream (§4.5.4, §7.1.3).
1060
+ */
1061
+ encodeField(
1062
+ out: u8[],
1063
+ name: u8[],
1064
+ nameOff: i32,
1065
+ nameLen: i32,
1066
+ value: u8[],
1067
+ valueOff: i32,
1068
+ valueLen: i32,
1069
+ neverIndexed: boolean,
1070
+ huffman: boolean
1071
+ ): void {
1072
+ qpackCheckWindow("QpackEncoder.encodeField", name, nameOff, nameLen)
1073
+ qpackCheckWindow("QpackEncoder.encodeField", value, valueOff, valueLen)
1074
+ let nameIndex: i32 = -1
1075
+ for (let i: i32 = 0; i < QPACK_STATIC_LENGTH; i += 1) {
1076
+ if (qpackSameText(name, nameOff, nameLen, qpackStaticName(i))) {
1077
+ if (!neverIndexed && qpackSameText(value, valueOff, valueLen, qpackStaticValue(i))) {
1078
+ qpackPushInteger(out, 192, 6, toI64(i))
1079
+ return
1080
+ }
1081
+ if (nameIndex < 0) {
1082
+ nameIndex = i
1083
+ }
1084
+ if (neverIndexed) {
1085
+ // A never-indexed field is a literal whatever the table holds, so
1086
+ // the lowest index with its name is all there is to find.
1087
+ break
1088
+ }
1089
+ }
1090
+ }
1091
+ // `01 N 1` for a static name reference, `001 N` for a literal name.
1092
+ if (nameIndex >= 0) {
1093
+ qpackPushInteger(out, neverIndexed ? 112 : 80, 4, toI64(nameIndex))
1094
+ } else {
1095
+ qpackPushString(this.huffman, out, neverIndexed ? 48 : 32, 4, name, nameOff, nameLen, huffman)
1096
+ }
1097
+ qpackPushString(this.huffman, out, 0, 8, value, valueOff, valueLen, huffman)
1098
+ }
1099
+
1100
+ /**
1101
+ * Reads the decoder stream's next bytes, `buf[off, off + len)`, and answers
1102
+ * `QPACK_OK` or QPACK_DECODER_STREAM_ERROR. A Stream Cancellation is
1103
+ * accepted and counted, its stream ID read across calls when the bytes
1104
+ * split it. A Section Acknowledgment and an Insert Count Increment are
1105
+ * refused on their first byte: this encoder sent no section that needs
1106
+ * acknowledging and inserted nothing to count.
1107
+ */
1108
+ receiveDecoderStream(buf: u8[], off: i32, len: i32): i64 {
1109
+ qpackCheckWindow("QpackEncoder.receiveDecoderStream", buf, off, len)
1110
+ if (this.failed !== QPACK_OK) {
1111
+ return this.failed
1112
+ }
1113
+ const end: i32 = off + len
1114
+ for (let i: i32 = off; i >= 0 && i < end && i < toI32(buf.length); i += 1) {
1115
+ const b: i32 = toI32(buf[i])
1116
+ if (this.pendingShift < 0) {
1117
+ if ((b & 128) !== 0) {
1118
+ return this.refuse(QPACK_REASON_ACKNOWLEDGMENT)
1119
+ }
1120
+ if ((b & 64) === 0) {
1121
+ return this.refuse(QPACK_REASON_INCREMENT)
1122
+ }
1123
+ if ((b & 63) < 63) {
1124
+ this.cancelled(toI64(b & 63))
1125
+ } else {
1126
+ this.pendingId = 63
1127
+ this.pendingShift = 0
1128
+ }
1129
+ } else {
1130
+ const digit: i64 = toI64(b & 127)
1131
+ if (
1132
+ this.pendingShift > 56 ||
1133
+ digit > (QPACK_MAX_INTEGER - this.pendingId) >> toI64(this.pendingShift)
1134
+ ) {
1135
+ return this.refuse(QPACK_REASON_INTEGER)
1136
+ }
1137
+ this.pendingId += digit << toI64(this.pendingShift)
1138
+ if ((b & 128) === 0) {
1139
+ this.cancelled(this.pendingId)
1140
+ this.pendingShift = -1
1141
+ } else {
1142
+ this.pendingShift += 7
1143
+ }
1144
+ }
1145
+ }
1146
+ return QPACK_OK
1147
+ }
1148
+
1149
+ /** Counts a Stream Cancellation for `streamId`. */
1150
+ cancelled(streamId: i64): void {
1151
+ if (this.cancellations < QPACK_COUNT_MAX) {
1152
+ this.cancellations += 1
1153
+ }
1154
+ this.lastCancelled = streamId
1155
+ }
1156
+
1157
+ /** Records `reason` and answers QPACK_DECODER_STREAM_ERROR, which sticks. */
1158
+ refuse(reason: i32): i64 {
1159
+ this.reason = reason
1160
+ this.failed = QPACK_DECODER_STREAM_ERROR
1161
+ return this.failed
1162
+ }
1163
+ }