@amritk/nish-x86_64-linux 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +43 -34
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +117 -8
- package/runtime/nish.h +50 -13
- package/runtime/nish.mjs +71 -7
- package/runtime/runtime-host.c +43 -24
- package/runtime/runtime-net.c +57 -10
- package/runtime/runtime-os.c +62 -20
- package/runtime/runtime-simd.c +203 -0
- package/runtime/runtime-wasm.c +12 -0
- package/runtime/runtime.c +66 -18
- package/runtime/shim.mjs +114 -5
- package/scripts/build.sh +14 -12
- package/std/README.md +168 -36
- package/std/crypto/aes.ts +51 -8
- package/std/crypto/base64url.ts +25 -12
- package/std/crypto/chacha20poly1305.ts +43 -29
- package/std/crypto/ct.ts +6 -1
- package/std/crypto/hkdf.ts +364 -3
- package/std/crypto/hmac.ts +307 -7
- package/std/crypto/p256.ts +618 -34
- package/std/crypto/sha1.ts +147 -0
- package/std/crypto/sha256.ts +3 -2
- package/std/crypto/x25519.ts +88 -36
- package/std/crypto/x509.ts +130 -47
- package/std/json.ts +297 -41
- package/std/net/hpack.ts +1094 -0
- package/std/net/http-fields.ts +604 -0
- package/std/net/http1-server.ts +1389 -0
- package/std/net/http1.ts +1492 -0
- package/std/net/http2-frame.ts +745 -0
- package/std/net/http2-tls.ts +216 -0
- package/std/net/http2.ts +1433 -0
- package/std/net/http3-frame.ts +457 -0
- package/std/net/http3-server.ts +911 -0
- package/std/net/http3.ts +2145 -0
- package/std/net/qpack.ts +1163 -0
- package/std/net/quic-conn-ack.ts +206 -0
- package/std/net/quic-conn-cid.ts +425 -0
- package/std/net/quic-conn-params.ts +462 -0
- package/std/net/quic-datagram.ts +130 -0
- package/std/net/quic-frame.ts +1206 -0
- package/std/net/quic-listener.ts +929 -0
- package/std/net/quic-packet.ts +1698 -0
- package/std/net/quic-recovery.ts +849 -0
- package/std/net/quic-stream.ts +1609 -0
- package/std/net/quic.ts +3578 -0
- package/std/net/tls/codec.ts +1089 -0
- package/std/net/tls/record-server.ts +1173 -0
- package/std/net/tls/record.ts +621 -0
- package/std/net/tls/schedule.ts +429 -0
- package/std/net/tls-tcp.ts +414 -0
- package/std/net/tls.ts +1437 -0
- package/std/net/websocket.ts +823 -0
- package/std/net/webtransport.ts +1334 -0
- package/std/secret.ts +62 -0
- package/std/text.ts +77 -0
package/std/net/qpack.ts
ADDED
|
@@ -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
|
+
}
|