@amritk/nish-x86_64-linux 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/INSTALL.md +58 -40
  2. package/bin/nish +0 -0
  3. package/package.json +1 -1
  4. package/runtime/nish.d.ts +117 -8
  5. package/runtime/nish.h +67 -10
  6. package/runtime/nish.mjs +71 -7
  7. package/runtime/runtime-host.c +72 -26
  8. package/runtime/runtime-net.c +72 -19
  9. package/runtime/runtime-os.c +127 -45
  10. package/runtime/runtime-parallel.c +5 -1
  11. package/runtime/runtime-wasm.c +26 -2
  12. package/runtime/runtime.c +86 -14
  13. package/runtime/shim.mjs +114 -5
  14. package/scripts/build.sh +1 -3
  15. package/std/README.md +192 -36
  16. package/std/crypto/aes.ts +104 -21
  17. package/std/crypto/base64url.ts +43 -17
  18. package/std/crypto/chacha20poly1305.ts +52 -31
  19. package/std/crypto/ct.ts +14 -3
  20. package/std/crypto/hkdf.ts +385 -8
  21. package/std/crypto/hmac.ts +325 -9
  22. package/std/crypto/p256.ts +153 -27
  23. package/std/crypto/sha1.ts +147 -0
  24. package/std/crypto/sha256.ts +15 -3
  25. package/std/crypto/sha512.ts +16 -2
  26. package/std/crypto/x25519.ts +90 -37
  27. package/std/crypto/x509.ts +265 -65
  28. package/std/net/hpack.ts +1094 -0
  29. package/std/net/http-fields.ts +604 -0
  30. package/std/net/http1-server.ts +1389 -0
  31. package/std/net/http1.ts +1492 -0
  32. package/std/net/http2-frame.ts +745 -0
  33. package/std/net/http2-tls.ts +216 -0
  34. package/std/net/http2.ts +1433 -0
  35. package/std/net/http3-frame.ts +457 -0
  36. package/std/net/http3-server.ts +905 -0
  37. package/std/net/http3.ts +2145 -0
  38. package/std/net/qpack.ts +1163 -0
  39. package/std/net/quic-conn-ack.ts +206 -0
  40. package/std/net/quic-conn-cid.ts +425 -0
  41. package/std/net/quic-conn-params.ts +392 -0
  42. package/std/net/quic-datagram.ts +130 -0
  43. package/std/net/quic-frame.ts +1206 -0
  44. package/std/net/quic-listener.ts +653 -0
  45. package/std/net/quic-packet.ts +1474 -0
  46. package/std/net/quic-recovery.ts +849 -0
  47. package/std/net/quic-stream.ts +1603 -0
  48. package/std/net/quic.ts +3214 -0
  49. package/std/net/tls/codec.ts +1089 -0
  50. package/std/net/tls/record-server.ts +1173 -0
  51. package/std/net/tls/record.ts +621 -0
  52. package/std/net/tls/schedule.ts +429 -0
  53. package/std/net/tls-tcp.ts +410 -0
  54. package/std/net/tls.ts +1368 -0
  55. package/std/net/websocket.ts +823 -0
  56. package/std/net/webtransport.ts +1334 -0
  57. package/std/secret.ts +62 -0
@@ -0,0 +1,1433 @@
1
+ /**
2
+ * `nish/net/http2` — the server side of an HTTP/2 connection (RFC 9113):
3
+ * the connection preface, SETTINGS and their acknowledgement, the stream
4
+ * state machine of §5.1, flow control on the connection and on every stream
5
+ * with WINDOW_UPDATE, PING, RST_STREAM, GOAWAY with the last stream
6
+ * identifier, header blocks across CONTINUATION frames decoded by
7
+ * `nish/net/hpack`, and extended CONNECT (RFC 8441).
8
+ *
9
+ * It is sans-IO: bytes in, events and bytes out, and no socket anywhere.
10
+ * `nish/net/http2-tls` runs it over TLS with ALPN `h2`.
11
+ *
12
+ * import { Http2Config, Http2Connection, H2_NEED_MORE, H2_REQUEST, H2_DATA } from "nish/net/http2";
13
+ *
14
+ * const conn = new Http2Connection(new Http2Config());
15
+ * // send conn.output[conn.outputStart .. conn.outputEnd), then conn.consume(sent)
16
+ * conn.feed(received, 0, n);
17
+ * let event: i32 = conn.next();
18
+ * while (event !== H2_NEED_MORE && event !== H2_ERROR) {
19
+ * // H2_REQUEST: conn.stream, conn.fields (method, path, get("accept")), conn.endStream
20
+ * // H2_DATA: conn.data[conn.dataStart .. conn.dataStart + conn.dataLength), conn.endStream
21
+ * event = conn.next();
22
+ * }
23
+ * conn.respond(stream, 200, names, values, false);
24
+ * conn.writeData(stream, body, 0, n, true);
25
+ *
26
+ * **Events.** `next()` reads the frames `feed` handed it and answers the
27
+ * first that means something to the program: `H2_REQUEST` (a stream opened
28
+ * with a request's header section; `fields` holds it), `H2_DATA` (some of a
29
+ * request body, a window onto the input buffer that stays valid until the
30
+ * next `feed`), `H2_TRAILERS`, `H2_RESET` (the stream is gone: the peer reset
31
+ * it, or broke a rule that resets it — `errorCode` says which), `H2_WINDOW`
32
+ * (a send window that held `writeData` back has opened: `stream`, or 0 for the
33
+ * connection's), `H2_GOAWAY` (the peer is going away; `lastStreamId`), and
34
+ * `H2_ERROR`, final: the connection broke a rule, a GOAWAY with `errorCode`
35
+ * is queued, and every later call answers it again. `H2_NEED_MORE` asks for
36
+ * more input, or for the output to drain: a frame is read only while the
37
+ * output has room for whatever it makes the connection answer, so a peer
38
+ * that sends PINGs or SETTINGS and never reads the acknowledgements stops
39
+ * being read rather than growing a queue. Bodies move a frame at a time and
40
+ * nothing buffers a whole one.
41
+ *
42
+ * **What the connection refuses**, as RFC 9113 classes it. A connection error
43
+ * sends GOAWAY and ends the connection: a bad preface or a first frame that is
44
+ * not SETTINGS (PROTOCOL_ERROR, §3.4); every refusal of
45
+ * `nish/net/http2-frame`; a frame other than CONTINUATION inside a header
46
+ * block, or a CONTINUATION outside one (§6.10); a stream identifier that is
47
+ * even or does not increase (§5.1.1); DATA, WINDOW_UPDATE or RST_STREAM on an
48
+ * idle stream, and a PUSH_PROMISE from a client (PROTOCOL_ERROR); HEADERS on
49
+ * a closed stream (STREAM_CLOSED); a header block HPACK cannot decode
50
+ * (COMPRESSION_ERROR, RFC 7541 §2.3.2); DATA past the connection window, or a
51
+ * window pushed past 2^31 − 1 (FLOW_CONTROL_ERROR, §6.9); a setting
52
+ * `http2SettingError` refuses; a header block longer than `maxHeaderBlock`
53
+ * or cut into more than `H2_MAX_FRAGMENTS` frames, and more streams reset by
54
+ * the peer than `resetBudget` allows (ENHANCE_YOUR_CALM, §10.5). A stream
55
+ * error resets one stream and the connection lives: a malformed request — any
56
+ * refusal of `nish/net/http-fields`, trailers without END_STREAM, a body that
57
+ * disagrees with `content-length` (PROTOCOL_ERROR, §8.1.1); DATA or HEADERS
58
+ * after the peer ended the stream (STREAM_CLOSED, §5.1); DATA past the
59
+ * stream's window, or its window pushed past 2^31 − 1 (FLOW_CONTROL_ERROR);
60
+ * a stream past `maxStreams` (REFUSED_STREAM, §5.1.2); a stream that depends
61
+ * on itself and a PRIORITY of the wrong length. A request whose header list
62
+ * is longer than `maxHeaderListSize` is answered with a 431 and never reaches
63
+ * the program (§10.5.1).
64
+ *
65
+ * **Caps are start-up numbers**, all in `Http2Config`: the streams open at
66
+ * once, the windows, the largest frame and header block accepted, the header
67
+ * list, the output buffer and the reset budget. Every buffer and every
68
+ * stream slot is allocated by the constructor; reading DATA, writing DATA and
69
+ * every control frame allocate nothing. A header block does allocate, through
70
+ * `HpackDecoder` (`docs/security/http2.md`, H2-1).
71
+ *
72
+ * The server sends no PUSH_PROMISE and no PRIORITY, and reads PRIORITY only to
73
+ * check it: RFC 9113 §5.3.2 deprecates the scheme. Every field it sends is a
74
+ * literal without indexing, so its encoder's table stays empty and the peer's
75
+ * SETTINGS_HEADER_TABLE_SIZE only ever asks it for a size update; a field
76
+ * `httpFieldSensitive` names (`set-cookie`, `authorization`, …) is a
77
+ * never-indexed literal, which no intermediary may index either (RFC 7541
78
+ * §7.1.3).
79
+ *
80
+ * Private helpers share the importing program's flat symbol namespace
81
+ * (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
82
+ *
83
+ * Written from RFC 9113 and RFC 8441, not ported from another implementation.
84
+ */
85
+ import {
86
+ HPACK_DEFAULT_TABLE_SIZE,
87
+ HPACK_INDEX_NEVER,
88
+ HPACK_INDEX_WITHOUT,
89
+ HPACK_LIST_TOO_LARGE,
90
+ HpackDecoder,
91
+ HpackEncoder,
92
+ hpackHuffmanLength,
93
+ } from "nish/net/hpack"
94
+ import {
95
+ HTTP_FIELDS_OK,
96
+ HttpFields,
97
+ httpFieldBytes,
98
+ httpFieldSensitive,
99
+ httpFieldsCheckOutgoing,
100
+ } from "nish/net/http-fields"
101
+ import {
102
+ H2_COMPRESSION_ERROR,
103
+ H2_DEFAULT_MAX_FRAME,
104
+ H2_DEFAULT_WINDOW,
105
+ H2_ENHANCE_YOUR_CALM,
106
+ H2_FLAG_ACK,
107
+ H2_FLAG_END_HEADERS,
108
+ H2_FLAG_END_STREAM,
109
+ H2_FLOW_CONTROL_ERROR,
110
+ H2_FRAME_CONTINUATION,
111
+ H2_FRAME_DATA,
112
+ H2_FRAME_GOAWAY,
113
+ H2_FRAME_HEADER_SIZE,
114
+ H2_FRAME_HEADERS,
115
+ H2_FRAME_PING,
116
+ H2_FRAME_PRIORITY,
117
+ H2_FRAME_PUSH_PROMISE,
118
+ H2_FRAME_RST_STREAM,
119
+ H2_FRAME_SETTINGS,
120
+ H2_FRAME_SIZE_ERROR,
121
+ H2_FRAME_WINDOW_UPDATE,
122
+ H2_MAX_FRAME_LIMIT,
123
+ H2_MAX_WINDOW,
124
+ H2_NO_ERROR,
125
+ H2_PROTOCOL_ERROR,
126
+ H2_REFUSED_STREAM,
127
+ H2_SETTINGS_ENABLE_CONNECT_PROTOCOL,
128
+ H2_SETTINGS_HEADER_TABLE_SIZE,
129
+ H2_SETTINGS_INITIAL_WINDOW_SIZE,
130
+ H2_SETTINGS_MAX_CONCURRENT_STREAMS,
131
+ H2_SETTINGS_MAX_FRAME_SIZE,
132
+ H2_SETTINGS_MAX_HEADER_LIST_SIZE,
133
+ H2_STREAM_CLOSED,
134
+ Http2Frame,
135
+ http2ParseFrame,
136
+ http2ReadHeader,
137
+ http2SettingCount,
138
+ http2SettingError,
139
+ http2SettingId,
140
+ http2SettingValue,
141
+ http2WriteContinuation,
142
+ http2WriteData,
143
+ http2WriteGoaway,
144
+ http2WriteHeaders,
145
+ http2WritePing,
146
+ http2WriteRstStream,
147
+ http2WriteSettings,
148
+ http2WriteSettingsAck,
149
+ http2WriteWindowUpdate,
150
+ } from "nish/net/http2-frame"
151
+
152
+ /** `next` needs more input, or the output to drain. */
153
+ export const H2_NEED_MORE: i32 = 0
154
+ /** A stream opened with a request: `stream`, `fields`, `endStream`. */
155
+ export const H2_REQUEST: i32 = 1
156
+ /** Request body bytes: `stream`, `data[dataStart .. dataStart + dataLength)`, `endStream`. */
157
+ export const H2_DATA: i32 = 2
158
+ /** A trailer section, which ends the request: `stream`, `fields`. */
159
+ export const H2_TRAILERS: i32 = 3
160
+ /** The stream is gone, reset by the peer or for a rule it broke: `stream`, `errorCode`, `resetByPeer`. */
161
+ export const H2_RESET: i32 = 4
162
+ /** A send window that held `writeData` back has opened: `stream`, 0 for the connection's. */
163
+ export const H2_WINDOW: i32 = 5
164
+ /** The peer sent GOAWAY: `lastStreamId`, `errorCode`. */
165
+ export const H2_GOAWAY: i32 = 6
166
+ /** The connection failed with `errorCode` and a GOAWAY is queued. Final. */
167
+ export const H2_ERROR: i32 = 7
168
+
169
+ /** What `writeData` answers when nothing can be sent yet: Linux's EAGAIN, as `nish:net` spells it. */
170
+ export const H2_AGAIN: i32 = -11
171
+ /** What a write answers for fields or a status it may not send: Linux's EINVAL. */
172
+ export const H2_INVALID: i32 = -22
173
+ /** What a write answers for a stream it may not write to: Linux's EPIPE. */
174
+ export const H2_CLOSED: i32 = -32
175
+
176
+ /** The connection preface every client sends first (§3.4). */
177
+ const H2_PREFACE: string = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
178
+
179
+ /** The preface's length. */
180
+ const H2_PREFACE_SIZE: i32 = 24
181
+
182
+ /** How many frames one header block may be cut into. */
183
+ export const H2_MAX_FRAGMENTS: i32 = 64
184
+
185
+ /**
186
+ * Output kept free of DATA and HEADERS, so the control frames a read frame
187
+ * can make the connection send — two WINDOW_UPDATEs, a RST_STREAM, a 431, a
188
+ * PING or SETTINGS acknowledgement — and the GOAWAY after them always fit.
189
+ */
190
+ const H2_CONTROL_RESERVE: i32 = 128
191
+
192
+ /**
193
+ * How many stream identifiers the connection remembers having reset, and
194
+ * having closed, beyond `maxStreams`: enough for the program to reset every
195
+ * open stream at once and still ignore what the peer had in flight for each.
196
+ */
197
+ const H2_RECENT_RESETS: i32 = 16
198
+
199
+ /** The connection's states. */
200
+ const H2_STATE_PREFACE: i32 = 0
201
+ const H2_STATE_SETTINGS: i32 = 1
202
+ const H2_STATE_OPEN: i32 = 2
203
+ const H2_STATE_FAILED: i32 = 3
204
+
205
+ /** A typed zero: a bare literal is an `f64` under `--number-mode f64`. */
206
+ const H2_ZERO: i32 = 0
207
+
208
+ /** The character `0`, typed, which a status digit is added to. */
209
+ const H2_DIGIT_ZERO: i32 = 48
210
+
211
+ /** 431 Request Header Fields Too Large (RFC 6585 §5), typed. */
212
+ const H2_STATUS_TOO_LARGE: i32 = 431
213
+
214
+ /** A typed -1, for no padding. */
215
+ const H2_NO_PAD: i32 = -1
216
+
217
+ /**
218
+ * The connection's caps, fixed when it is made. The defaults are RFC 9113's
219
+ * where it has one; each is advertised in the server's SETTINGS where a
220
+ * setting names it.
221
+ */
222
+ export class Http2Config {
223
+ /** SETTINGS_MAX_CONCURRENT_STREAMS, and the stream slots the connection allocates. */
224
+ maxStreams: i32 = 100
225
+ /** SETTINGS_INITIAL_WINDOW_SIZE: what each stream may send before the server credits it. */
226
+ initialWindowSize: i32 = 65535
227
+ /** The connection's receive window, raised from 65,535 by a WINDOW_UPDATE at the start. */
228
+ connectionWindowSize: i32 = 1048576
229
+ /** SETTINGS_MAX_FRAME_SIZE: the largest frame payload read, which sizes the input buffer. */
230
+ maxFrameSize: i32 = 16384
231
+ /** SETTINGS_MAX_HEADER_LIST_SIZE: past it a request is answered with a 431. */
232
+ maxHeaderListSize: i32 = 16384
233
+ /** The largest header block, compressed, across HEADERS and CONTINUATION. */
234
+ maxHeaderBlock: i32 = 16384
235
+ /** SETTINGS_HEADER_TABLE_SIZE: the HPACK decoder's table. */
236
+ headerTableSize: i32 = 4096
237
+ /** SETTINGS_ENABLE_CONNECT_PROTOCOL (RFC 8441): whether `:protocol` is accepted. */
238
+ enableConnectProtocol: boolean = false
239
+ /** The output buffer, in bytes. */
240
+ outputSize: i32 = 65536
241
+ /** How many streams the peer may reset before finishing, net of the ones it lets finish. */
242
+ resetBudget: i32 = 100
243
+ }
244
+
245
+ /** One stream's slot. `id` is 0 while the slot is free. */
246
+ export class Http2Stream {
247
+ /** What the server may still send on the stream. */
248
+ sendWindow: i64 = 0
249
+ /** What the peer may still send on it. */
250
+ recvWindow: i64 = 0
251
+ /** The request's `content-length`, or -1. */
252
+ contentLength: i64 = -1
253
+ /** The body bytes received. */
254
+ received: i64 = 0
255
+ id: i32 = 0
256
+ /** What the program has been handed since the last WINDOW_UPDATE for it. */
257
+ recvUnacked: i32 = 0
258
+ /** The peer sent END_STREAM: half-closed (remote). */
259
+ remoteEnded: boolean = false
260
+ /** The server sent END_STREAM: half-closed (local). */
261
+ localEnded: boolean = false
262
+ /** A final (non-1xx) response head was sent. */
263
+ responded: boolean = false
264
+ /** `writeData` was held back by this stream's window. */
265
+ blocked: boolean = false
266
+ }
267
+
268
+ /** Whether `buf[off ..]` holds the preface. */
269
+ const http2IsPreface = (buf: u8[], off: i32): boolean => {
270
+ for (let k: i32 = 0; k < H2_PREFACE_SIZE && off + k < toI32(buf.length); k++) {
271
+ if (toI32(buf[off + k]) !== toI32(H2_PREFACE.charCodeAt(k))) {
272
+ return false
273
+ }
274
+ }
275
+ return true
276
+ }
277
+
278
+ /** Moves `buf[start, end)` to the front of `buf` and answers where it now ends. */
279
+ const http2Compact = (buf: u8[], start: i32, end: i32): i32 => {
280
+ const keep: i32 = end - start
281
+ if (start > 0) {
282
+ for (let k: i32 = 0; k < keep && k < toI32(buf.length) && start + k < toI32(buf.length); k++) {
283
+ buf[k] = buf[start + k]
284
+ }
285
+ }
286
+ return keep
287
+ }
288
+
289
+ /** Writes `id` at `at` in the ring `ring` and answers where the next one goes. */
290
+ const http2Remember = (ring: i32[], at: i32, id: i32): i32 => {
291
+ const slot: i32 = at % toI32(ring.length)
292
+ if (slot >= 0 && slot < toI32(ring.length)) {
293
+ ring[slot] = id
294
+ }
295
+ return slot + 1
296
+ }
297
+
298
+ /** Panics unless `[off, off + len)` lies inside `buf`. */
299
+ const http2CheckWindow = (what: string, buf: u8[], off: i32, len: i32): void => {
300
+ if (off < 0 || len < 0 || toI64(off) + toI64(len) > toI64(buf.length)) {
301
+ panic(`${what}: the window [${off}, ${off} + ${len}) is outside a buffer of ${buf.length} bytes`)
302
+ }
303
+ }
304
+
305
+ /** Panics unless `value` lies in `[low, high]`: a cap out of range is the program's mistake. */
306
+ const http2CheckCap = (what: string, value: i32, low: i32, high: i32): void => {
307
+ if (value < low || value > high) {
308
+ panic(`Http2Connection: ${what} of ${value}, outside ${low} to ${high}`)
309
+ }
310
+ }
311
+
312
+ /**
313
+ * One HTTP/2 connection, server side. The fields after the buffers are the
314
+ * last event's; the module comment says which event sets which.
315
+ */
316
+ export class Http2Connection {
317
+ // Pointers and 64-bit fields first, then 32-bit ones, then flags: the
318
+ // order the layout packs without padding.
319
+ config: Http2Config
320
+ frame: Http2Frame
321
+ /** Bytes fed and not read yet are `input[inputStart .. inputEnd)`. */
322
+ input: u8[]
323
+ /** Bytes to send are `output[outputStart .. outputEnd)`. */
324
+ output: u8[]
325
+ /** The header block being gathered across CONTINUATION frames. */
326
+ block: u8[]
327
+ /** Where a response's header block is encoded, emptied and reused. */
328
+ encoded: u8[]
329
+ decoder: HpackDecoder
330
+ encoder: HpackEncoder
331
+ /** The last request's or trailers' fields. */
332
+ fields: HttpFields
333
+ streams: Http2Stream[]
334
+ /** The identifiers the server reset most recently, a ring. */
335
+ resets: i32[]
336
+ /** The identifiers of the streams closed most recently, however they closed, a ring. */
337
+ closed: i32[]
338
+ /** The bytes of `:status` and of a three-digit status, reused for every response. */
339
+ statusName: u8[]
340
+ statusValue: u8[]
341
+ /** GOAWAY's debug data, which the server never sends: one empty array, made once. */
342
+ noDebug: u8[]
343
+ /** The server's SETTINGS, made once and sent by every `restart`. */
344
+ settingIds: i32[]
345
+ settingValues: i64[]
346
+ /** The peer's SETTINGS_INITIAL_WINDOW_SIZE. */
347
+ peerInitialWindow: i64 = 65535
348
+ /** The peer's SETTINGS_MAX_HEADER_LIST_SIZE, advisory; -1 until it sends one. */
349
+ peerMaxHeaderList: i64 = -1
350
+ /** The window a new stream starts with on the receiving side. */
351
+ localInitialWindow: i64 = 65535
352
+ /** The connection's send window. */
353
+ sendWindow: i64 = 65535
354
+ /** The connection's receive window, and what has been handed on since it was last credited. */
355
+ recvWindow: i64 = 65535
356
+ recvUnacked: i64 = 0
357
+ /** H2_DATA's bytes: the input buffer, valid until the next `feed`. */
358
+ data: u8[]
359
+ /** H2_RESET's, H2_GOAWAY's and H2_ERROR's code. */
360
+ errorCode: i64 = 0
361
+ inputStart: i32 = 0
362
+ inputEnd: i32 = 0
363
+ outputStart: i32 = 0
364
+ outputEnd: i32 = 0
365
+ blockLength: i32 = 0
366
+ /** The stream the open header block is on, or 0 when none is open. */
367
+ blockStream: i32 = 0
368
+ /** A stream error the HEADERS that opened the block carried. */
369
+ blockError: i32 = 0
370
+ blockFragments: i32 = 0
371
+ resetAt: i32 = 0
372
+ closedAt: i32 = 0
373
+ state: i32 = 0
374
+ /** The highest stream identifier the peer has opened. */
375
+ lastPeerStream: i32 = 0
376
+ /** Streams with a slot. */
377
+ active: i32 = 0
378
+ /** The peer's SETTINGS_MAX_FRAME_SIZE. */
379
+ peerMaxFrameSize: i32 = 16384
380
+ /** What is left of `config.resetBudget`. */
381
+ resetCredit: i32 = 0
382
+ /** The last event's stream. */
383
+ stream: i32 = 0
384
+ /** H2_DATA's window onto `data`. */
385
+ dataStart: i32 = 0
386
+ dataLength: i32 = 0
387
+ /** H2_GOAWAY's last stream identifier. */
388
+ lastStreamId: i32 = 0
389
+ /** The last stream identifier the server's first GOAWAY named, which no later one may raise (§6.8). */
390
+ sentLast: i32 = 0
391
+ blockEndStream: boolean = false
392
+ /** Whether the peer has acknowledged the server's SETTINGS. */
393
+ settingsAcked: boolean = false
394
+ /** `writeData` was held back by the connection's window. */
395
+ sendBlocked: boolean = false
396
+ goawaySent: boolean = false
397
+ goawayReceived: boolean = false
398
+ /** The peer's byte stream has ended (`endInput`). */
399
+ inputEnded: boolean = false
400
+ /** Whether the last H2_REQUEST, H2_DATA or H2_TRAILERS ended its stream. */
401
+ endStream: boolean = false
402
+ /** Whether H2_RESET came from the peer's RST_STREAM rather than a rule its stream broke. */
403
+ resetByPeer: boolean = false
404
+
405
+ /**
406
+ * A connection under `config`, with its SETTINGS — and the WINDOW_UPDATE
407
+ * that raises the connection's window, when `connectionWindowSize` asks
408
+ * for one — already in `output`, since a server's preface is its SETTINGS
409
+ * (§3.4). A cap outside what RFC 9113 allows panics.
410
+ */
411
+ constructor(config: Http2Config) {
412
+ http2CheckCap("maxStreams", config.maxStreams, 1, 65536)
413
+ http2CheckCap("initialWindowSize", config.initialWindowSize, 1, H2_MAX_WINDOW)
414
+ http2CheckCap("connectionWindowSize", config.connectionWindowSize, H2_DEFAULT_WINDOW, H2_MAX_WINDOW)
415
+ http2CheckCap("maxFrameSize", config.maxFrameSize, H2_DEFAULT_MAX_FRAME, H2_MAX_FRAME_LIMIT)
416
+ http2CheckCap("maxHeaderListSize", config.maxHeaderListSize, 0, H2_MAX_WINDOW)
417
+ http2CheckCap("maxHeaderBlock", config.maxHeaderBlock, 1, H2_MAX_WINDOW)
418
+ http2CheckCap("headerTableSize", config.headerTableSize, 0, H2_MAX_WINDOW)
419
+ http2CheckCap("outputSize", config.outputSize, 1024, H2_MAX_WINDOW)
420
+ http2CheckCap("resetBudget", config.resetBudget, 0, H2_MAX_WINDOW)
421
+ this.config = config
422
+ this.frame = new Http2Frame()
423
+ this.input = new Array<u8>(config.maxFrameSize + H2_FRAME_HEADER_SIZE)
424
+ this.output = new Array<u8>(config.outputSize)
425
+ this.block = new Array<u8>(config.maxHeaderBlock)
426
+ this.encoded = []
427
+ this.decoder = new HpackDecoder(HPACK_DEFAULT_TABLE_SIZE, config.maxHeaderListSize)
428
+ this.encoder = new HpackEncoder(HPACK_DEFAULT_TABLE_SIZE)
429
+ this.fields = new HttpFields()
430
+ this.streams = []
431
+ for (let k: i32 = 0; k < config.maxStreams; k++) {
432
+ this.streams.push(new Http2Stream())
433
+ }
434
+ this.resets = new Array<i32>(config.maxStreams + H2_RECENT_RESETS)
435
+ this.closed = new Array<i32>(config.maxStreams + H2_RECENT_RESETS)
436
+ this.statusName = httpFieldBytes(":status")
437
+ this.noDebug = []
438
+ this.statusValue = [48, 48, 48]
439
+ this.data = this.input
440
+ this.settingIds = [
441
+ H2_SETTINGS_HEADER_TABLE_SIZE,
442
+ H2_SETTINGS_MAX_CONCURRENT_STREAMS,
443
+ H2_SETTINGS_INITIAL_WINDOW_SIZE,
444
+ H2_SETTINGS_MAX_FRAME_SIZE,
445
+ H2_SETTINGS_MAX_HEADER_LIST_SIZE,
446
+ ]
447
+ this.settingValues = [
448
+ toI64(config.headerTableSize),
449
+ toI64(config.maxStreams),
450
+ toI64(config.initialWindowSize),
451
+ toI64(config.maxFrameSize),
452
+ toI64(config.maxHeaderListSize),
453
+ ]
454
+ if (config.enableConnectProtocol) {
455
+ this.settingIds.push(H2_SETTINGS_ENABLE_CONNECT_PROTOCOL)
456
+ this.settingValues.push(toI64(1))
457
+ }
458
+ this.restart()
459
+ }
460
+
461
+ /**
462
+ * Puts the connection back as the constructor left it, for the next peer:
463
+ * every stream closed, both HPACK tables empty, and the server's SETTINGS
464
+ * in `output` again. It allocates nothing, which is what lets a carrier
465
+ * reuse one connection per slot (WP34 N9).
466
+ */
467
+ restart(): void {
468
+ const config: Http2Config = this.config
469
+ this.inputStart = 0
470
+ this.inputEnd = 0
471
+ this.outputStart = 0
472
+ this.outputEnd = 0
473
+ this.blockLength = 0
474
+ this.blockStream = 0
475
+ this.blockEndStream = false
476
+ this.blockError = 0
477
+ this.blockFragments = 0
478
+ for (const s of this.streams) {
479
+ s.id = 0
480
+ }
481
+ this.resets.fill(0)
482
+ this.resetAt = 0
483
+ this.closed.fill(0)
484
+ this.closedAt = 0
485
+ this.decoder.table.resize(H2_ZERO)
486
+ this.decoder.table.resize(HPACK_DEFAULT_TABLE_SIZE)
487
+ this.decoder.settingsLimit = HPACK_DEFAULT_TABLE_SIZE
488
+ this.decoder.owedUpdate = -1
489
+ this.decoder.failed = 0
490
+ this.encoder.table.resize(H2_ZERO)
491
+ this.encoder.table.resize(HPACK_DEFAULT_TABLE_SIZE)
492
+ this.encoder.owedMinimum = -1
493
+ this.state = H2_STATE_PREFACE
494
+ this.lastPeerStream = 0
495
+ this.active = 0
496
+ this.peerInitialWindow = toI64(H2_DEFAULT_WINDOW)
497
+ this.peerMaxFrameSize = H2_DEFAULT_MAX_FRAME
498
+ this.peerMaxHeaderList = -1
499
+ this.settingsAcked = false
500
+ this.sendWindow = toI64(H2_DEFAULT_WINDOW)
501
+ this.recvUnacked = 0
502
+ this.sendBlocked = false
503
+ this.goawaySent = false
504
+ this.goawayReceived = false
505
+ this.inputEnded = false
506
+ this.stream = 0
507
+ this.endStream = false
508
+ this.dataStart = 0
509
+ this.dataLength = 0
510
+ this.errorCode = 0
511
+ this.resetByPeer = false
512
+ this.lastStreamId = 0
513
+ this.sentLast = 0
514
+ this.resetCredit = config.resetBudget
515
+ const big: i64 = toI64(H2_DEFAULT_WINDOW)
516
+ this.localInitialWindow = toI64(config.initialWindowSize) > big ? toI64(config.initialWindowSize) : big
517
+ this.recvWindow = toI64(config.connectionWindowSize)
518
+ this.outputEnd = http2WriteSettings(this.output, H2_ZERO, this.settingIds, this.settingValues)
519
+ const raise: i32 = config.connectionWindowSize - H2_DEFAULT_WINDOW
520
+ if (raise > 0) {
521
+ this.outputEnd = http2WriteWindowUpdate(this.output, this.outputEnd, H2_ZERO, raise)
522
+ }
523
+ }
524
+
525
+ // ---- Bytes in and out ---------------------------------------------------------
526
+
527
+ /** How many bytes `feed` would take now. */
528
+ inputRoom(): i32 {
529
+ return toI32(this.input.length) - (this.inputEnd - this.inputStart)
530
+ }
531
+
532
+ /**
533
+ * Takes as much of `buf[off, off + len)` as the input buffer has room for,
534
+ * and answers how much; the rest is the caller's to feed again once `next`
535
+ * has read what is there. Moves the unread bytes to the front of the
536
+ * buffer, which ends the last H2_DATA's window. Once the connection has
537
+ * failed it takes everything and keeps nothing.
538
+ */
539
+ feed(buf: u8[], off: i32, len: i32): i32 {
540
+ http2CheckWindow("Http2Connection.feed", buf, off, len)
541
+ if (this.state === H2_STATE_FAILED) {
542
+ return len
543
+ }
544
+ const room: i32 = this.inputTail()
545
+ const n: i32 = len < room ? len : room
546
+ for (let k: i32 = 0; k < n; k++) {
547
+ this.input[this.inputEnd + k] = buf[off + k]
548
+ }
549
+ this.inputEnd = this.inputEnd + n
550
+ return n
551
+ }
552
+
553
+ /**
554
+ * Moves the unread input to the front of its buffer, which ends the last
555
+ * H2_DATA's window, and answers the room after it: where a carrier may read
556
+ * straight into `input` from `inputEnd`, then `received` what it read.
557
+ */
558
+ inputTail(): i32 {
559
+ this.inputEnd = http2Compact(this.input, this.inputStart, this.inputEnd)
560
+ this.inputStart = 0
561
+ return toI32(this.input.length) - this.inputEnd
562
+ }
563
+
564
+ /** Takes `n` bytes a carrier read into `input` at `inputEnd`, after `inputTail` gave it the room. */
565
+ received(n: i32): void {
566
+ const room: i32 = toI32(this.input.length) - this.inputEnd
567
+ if (n < 0 || n > room) {
568
+ panic(`Http2Connection.received: ${n} bytes into ${room} of room`)
569
+ }
570
+ if (this.state !== H2_STATE_FAILED) {
571
+ this.inputEnd = this.inputEnd + n
572
+ }
573
+ }
574
+
575
+ /** Marks the first `n` bytes of the output sent. */
576
+ consume(n: i32): void {
577
+ const pending: i32 = this.outputEnd - this.outputStart
578
+ this.outputStart = this.outputStart + (n < pending ? n : pending)
579
+ if (this.outputStart === this.outputEnd) {
580
+ this.outputStart = 0
581
+ this.outputEnd = 0
582
+ }
583
+ }
584
+
585
+ /** Whether the output holds bytes to send. */
586
+ wantsWrite(): boolean {
587
+ return this.outputEnd > this.outputStart
588
+ }
589
+
590
+ /**
591
+ * Whether the connection is over: everything is sent, and it failed, or no
592
+ * stream is left after a GOAWAY went either way or after the peer's byte
593
+ * stream ended (`endInput`) with nothing of it left unread.
594
+ */
595
+ isDone(): boolean {
596
+ if (this.wantsWrite()) {
597
+ return false
598
+ }
599
+ const unread: boolean = this.inputEnd > this.inputStart
600
+ const over: boolean = this.goawaySent || this.goawayReceived || (this.inputEnded && !unread)
601
+ return this.state === H2_STATE_FAILED || (over && this.active === 0)
602
+ }
603
+
604
+ /**
605
+ * The peer's byte stream has ended, so nothing more will be fed: once the
606
+ * requests it already sent are answered, the connection is done.
607
+ */
608
+ endInput(): void {
609
+ this.inputEnded = true
610
+ }
611
+
612
+ /** The room left at the end of the output, once what was sent is moved out of the way. */
613
+ room(): i32 {
614
+ this.outputEnd = http2Compact(this.output, this.outputStart, this.outputEnd)
615
+ this.outputStart = 0
616
+ return toI32(this.output.length) - this.outputEnd
617
+ }
618
+
619
+ /** The room DATA and HEADERS may use: all but the control reserve. */
620
+ bodyRoom(): i32 {
621
+ const r: i32 = this.room() - H2_CONTROL_RESERVE
622
+ return r > 0 ? r : 0
623
+ }
624
+
625
+ /** Appends a frame a writer put at the end of the output, when it fit (`end >= 0`), and answers whether it did. */
626
+ wrote(end: i32): boolean {
627
+ if (end >= 0) {
628
+ this.outputEnd = end
629
+ }
630
+ return end >= 0
631
+ }
632
+
633
+ // ---- Streams ------------------------------------------------------------------
634
+
635
+ /** The slot holding stream `id`, or `null`. */
636
+ find(id: i32): Http2Stream | null {
637
+ if (id <= 0) {
638
+ return null
639
+ }
640
+ for (const s of this.streams) {
641
+ if (s.id === id) {
642
+ return s
643
+ }
644
+ }
645
+ return null
646
+ }
647
+
648
+ /** Opens stream `id` in a free slot; the caller has checked there is one. */
649
+ open(id: i32): Http2Stream {
650
+ for (const s of this.streams) {
651
+ if (s.id === 0) {
652
+ s.id = id
653
+ s.sendWindow = this.peerInitialWindow
654
+ s.recvWindow = this.localInitialWindow
655
+ s.recvUnacked = 0
656
+ s.contentLength = -1
657
+ s.received = 0
658
+ s.remoteEnded = false
659
+ s.localEnded = false
660
+ s.responded = false
661
+ s.blocked = false
662
+ this.active = this.active + 1
663
+ return s
664
+ }
665
+ }
666
+ panic("Http2Connection.open: no free slot")
667
+ }
668
+
669
+ /** Frees the slot of a stream that is closed. */
670
+ release(s: Http2Stream): void {
671
+ if (s.id !== 0) {
672
+ this.closedAt = http2Remember(this.closed, this.closedAt, s.id)
673
+ s.id = 0
674
+ this.active = this.active - 1
675
+ }
676
+ }
677
+
678
+ /** The peer ended stream `s`; closes it when the server had too. */
679
+ remoteEnd(s: Http2Stream): void {
680
+ s.remoteEnded = true
681
+ if (s.localEnded) {
682
+ this.release(s)
683
+ }
684
+ }
685
+
686
+ /** The server ended stream `s`; closes it when the peer had too, and credits the reset budget. */
687
+ localEnd(s: Http2Stream): void {
688
+ s.localEnded = true
689
+ if (this.resetCredit < this.config.resetBudget) {
690
+ this.resetCredit = this.resetCredit + 1
691
+ }
692
+ if (s.remoteEnded) {
693
+ this.release(s)
694
+ }
695
+ }
696
+
697
+ /** Whether the server reset stream `id` recently, so frames still in flight for it are ignored (§5.4.2). */
698
+ recentlyReset(id: i32): boolean {
699
+ return id > 0 && this.resets.indexOf(id) >= 0
700
+ }
701
+
702
+ /**
703
+ * Whether stream `id` is one the connection remembers closing. A HEADERS
704
+ * below the highest identifier on a stream it does not remember was never
705
+ * opened, and is the out-of-order identifier §5.1.1 calls PROTOCOL_ERROR
706
+ * rather than a frame on a closed stream (STREAM_CLOSED, §5.1).
707
+ */
708
+ recentlyClosed(id: i32): boolean {
709
+ return id > 0 && this.closed.indexOf(id) >= 0
710
+ }
711
+
712
+ /**
713
+ * Sends RST_STREAM for `id` with `code`, frees its slot if it has one, and
714
+ * remembers it; answers false, changing nothing, when the frame did not fit.
715
+ */
716
+ resetStream(id: i32, code: i32): boolean {
717
+ if (!this.wrote(http2WriteRstStream(this.output, this.outputEnd, id, toI64(code)))) {
718
+ return false
719
+ }
720
+ const s: Http2Stream | null = this.find(id)
721
+ if (s !== null) {
722
+ this.release(s)
723
+ }
724
+ this.resetAt = http2Remember(this.resets, this.resetAt, id)
725
+ return true
726
+ }
727
+
728
+ /** A stream error on `s`: resets it, and answers the H2_RESET that tells the program. */
729
+ streamError(s: Http2Stream, code: i32): i32 {
730
+ const id: i32 = s.id
731
+ this.resetStream(id, code)
732
+ this.stream = id
733
+ this.errorCode = toI64(code)
734
+ this.resetByPeer = false
735
+ return H2_RESET
736
+ }
737
+
738
+ /**
739
+ * Writes GOAWAY with `code`, naming the last stream the peer had opened when
740
+ * the server's first GOAWAY went: no later one may name a higher stream
741
+ * (§6.8). Answers whether it fit.
742
+ */
743
+ sendGoaway(code: i32): boolean {
744
+ const last: i32 = this.goawaySent ? this.sentLast : this.lastPeerStream
745
+ if (
746
+ !this.wrote(
747
+ http2WriteGoaway(this.output, this.outputEnd, last, toI64(code), this.noDebug, H2_ZERO, H2_ZERO)
748
+ )
749
+ ) {
750
+ return false
751
+ }
752
+ this.goawaySent = true
753
+ this.sentLast = last
754
+ return true
755
+ }
756
+
757
+ /** A connection error: queues GOAWAY with `code` and answers H2_ERROR. */
758
+ fail(code: i32): i32 {
759
+ if (this.state !== H2_STATE_FAILED) {
760
+ this.state = H2_STATE_FAILED
761
+ this.errorCode = toI64(code)
762
+ this.sendGoaway(code)
763
+ this.inputStart = 0
764
+ this.inputEnd = 0
765
+ this.blockStream = 0
766
+ }
767
+ return H2_ERROR
768
+ }
769
+
770
+ /**
771
+ * Hands `n` received bytes back to the connection's window, and to `s`'s
772
+ * when it is still open for receiving, with a WINDOW_UPDATE once half a
773
+ * window is owed.
774
+ */
775
+ credit(s: Http2Stream | null, n: i32): void {
776
+ this.recvUnacked = this.recvUnacked + toI64(n)
777
+ const half: i64 = toI64(this.config.connectionWindowSize / 2)
778
+ if (this.recvUnacked > toI64(0) && this.recvUnacked >= half) {
779
+ const inc: i32 = toI32(this.recvUnacked)
780
+ this.wrote(http2WriteWindowUpdate(this.output, this.outputEnd, H2_ZERO, inc))
781
+ this.recvWindow = this.recvWindow + this.recvUnacked
782
+ this.recvUnacked = 0
783
+ }
784
+ if (s !== null && !s.remoteEnded) {
785
+ s.recvUnacked = s.recvUnacked + n
786
+ if (s.recvUnacked > 0 && s.recvUnacked >= this.config.initialWindowSize / 2) {
787
+ this.wrote(http2WriteWindowUpdate(this.output, this.outputEnd, s.id, s.recvUnacked))
788
+ s.recvWindow = s.recvWindow + toI64(s.recvUnacked)
789
+ s.recvUnacked = 0
790
+ }
791
+ }
792
+ }
793
+
794
+ // ---- Reading ------------------------------------------------------------------
795
+
796
+ /** Reads frames until one is an event for the program, and answers it. */
797
+ next(): i32 {
798
+ while (true) {
799
+ if (this.state === H2_STATE_FAILED) {
800
+ return H2_ERROR
801
+ }
802
+ if (this.room() < H2_CONTROL_RESERVE) {
803
+ return H2_NEED_MORE
804
+ }
805
+ const available: i32 = this.inputEnd - this.inputStart
806
+ if (this.state === H2_STATE_PREFACE) {
807
+ if (available < H2_PREFACE_SIZE) {
808
+ return H2_NEED_MORE
809
+ }
810
+ if (!http2IsPreface(this.input, this.inputStart)) {
811
+ return this.fail(H2_PROTOCOL_ERROR)
812
+ }
813
+ this.inputStart = this.inputStart + H2_PREFACE_SIZE
814
+ this.state = H2_STATE_SETTINGS
815
+ continue
816
+ }
817
+ const f: Http2Frame = this.frame
818
+ if (!http2ReadHeader(f, this.input, this.inputStart, available)) {
819
+ return H2_NEED_MORE
820
+ }
821
+ if (f.length > this.config.maxFrameSize) {
822
+ return this.fail(H2_FRAME_SIZE_ERROR)
823
+ }
824
+ if (available < H2_FRAME_HEADER_SIZE + f.length) {
825
+ return H2_NEED_MORE
826
+ }
827
+ const error: i32 = http2ParseFrame(f, this.input, this.inputStart, this.config.maxFrameSize)
828
+ this.inputStart = this.inputStart + H2_FRAME_HEADER_SIZE + f.length
829
+ if (error !== H2_NO_ERROR) {
830
+ return this.fail(error)
831
+ }
832
+ const event: i32 = this.handleFrame(f)
833
+ if (event !== H2_NEED_MORE) {
834
+ return event
835
+ }
836
+ }
837
+ }
838
+
839
+ /** One frame that parsed: the rules no single frame type owns, then the type's own. */
840
+ handleFrame(f: Http2Frame): i32 {
841
+ if (this.blockStream !== 0 && (f.type !== H2_FRAME_CONTINUATION || f.streamId !== this.blockStream)) {
842
+ return this.fail(H2_PROTOCOL_ERROR)
843
+ }
844
+ if (this.state === H2_STATE_SETTINGS) {
845
+ if (f.type !== H2_FRAME_SETTINGS || f.has(H2_FLAG_ACK)) {
846
+ return this.fail(H2_PROTOCOL_ERROR)
847
+ }
848
+ this.state = H2_STATE_OPEN
849
+ }
850
+ switch (f.type) {
851
+ case H2_FRAME_DATA:
852
+ return this.handleData(f)
853
+ case H2_FRAME_HEADERS:
854
+ return this.handleHeaders(f)
855
+ case H2_FRAME_PRIORITY:
856
+ return this.handlePriority(f)
857
+ case H2_FRAME_RST_STREAM:
858
+ return this.handleRstStream(f)
859
+ case H2_FRAME_SETTINGS:
860
+ return this.handleSettings(f)
861
+ case H2_FRAME_PUSH_PROMISE:
862
+ return this.fail(H2_PROTOCOL_ERROR)
863
+ case H2_FRAME_PING:
864
+ if (!f.has(H2_FLAG_ACK)) {
865
+ this.wrote(http2WritePing(this.output, this.outputEnd, this.input, f.contentStart, true))
866
+ }
867
+ return H2_NEED_MORE
868
+ case H2_FRAME_GOAWAY:
869
+ this.goawayReceived = true
870
+ this.lastStreamId = f.lastStreamId
871
+ this.errorCode = f.errorCode
872
+ return H2_GOAWAY
873
+ case H2_FRAME_WINDOW_UPDATE:
874
+ return this.handleWindowUpdate(f)
875
+ case H2_FRAME_CONTINUATION:
876
+ return this.handleContinuation(f)
877
+ default:
878
+ return H2_NEED_MORE
879
+ }
880
+ }
881
+
882
+ /** Whether `id` names an idle stream: one the peer has not opened yet. */
883
+ isIdle(id: i32): boolean {
884
+ // An even identifier is the server's to open (§5.1.1), and this server
885
+ // opens none, so every even stream is idle for as long as it lives.
886
+ return id % 2 === 0 || id > this.lastPeerStream
887
+ }
888
+
889
+ /**
890
+ * Whether frames on stream `id`, which has no slot, are dropped without a
891
+ * word: the server reset it and these were in flight (§5.4.2), or it was
892
+ * opened after the server's GOAWAY and so never opened at all (§6.8).
893
+ */
894
+ discarded(id: i32): boolean {
895
+ return this.recentlyReset(id) || (this.goawaySent && id > this.sentLast)
896
+ }
897
+
898
+ /** DATA (§6.1): flow control first, on the whole payload, then the stream's state. */
899
+ handleData(f: Http2Frame): i32 {
900
+ const id: i32 = f.streamId
901
+ const size: i32 = f.length
902
+ const s: Http2Stream | null = this.find(id)
903
+ if (s === null && this.isIdle(id)) {
904
+ return this.fail(H2_PROTOCOL_ERROR)
905
+ }
906
+ if (toI64(size) > this.recvWindow) {
907
+ return this.fail(H2_FLOW_CONTROL_ERROR)
908
+ }
909
+ this.recvWindow = this.recvWindow - toI64(size)
910
+ if (s === null) {
911
+ this.credit(null, size)
912
+ if (!this.discarded(id)) {
913
+ this.resetStream(id, H2_STREAM_CLOSED)
914
+ }
915
+ return H2_NEED_MORE
916
+ }
917
+ const end: boolean = f.has(H2_FLAG_END_STREAM)
918
+ const received: i64 = s.received + toI64(f.contentLength)
919
+ const declared: boolean = s.contentLength >= toI64(0)
920
+ let error: i32 = H2_NO_ERROR
921
+ if (s.remoteEnded) {
922
+ error = H2_STREAM_CLOSED
923
+ } else if (toI64(size) > s.recvWindow) {
924
+ error = H2_FLOW_CONTROL_ERROR
925
+ } else if (declared && (received > s.contentLength || (end && received !== s.contentLength))) {
926
+ error = H2_PROTOCOL_ERROR
927
+ }
928
+ if (error !== H2_NO_ERROR) {
929
+ this.credit(null, size)
930
+ return this.streamError(s, error)
931
+ }
932
+ s.recvWindow = s.recvWindow - toI64(size)
933
+ s.received = received
934
+ this.credit(end ? null : s, size)
935
+ this.stream = id
936
+ this.endStream = end
937
+ this.data = this.input
938
+ this.dataStart = f.contentStart
939
+ this.dataLength = f.contentLength
940
+ if (end) {
941
+ this.remoteEnd(s)
942
+ } else if (f.contentLength === 0) {
943
+ return H2_NEED_MORE
944
+ }
945
+ return H2_DATA
946
+ }
947
+
948
+ /** Appends a fragment of a header block; a block past `maxHeaderBlock` or `H2_MAX_FRAGMENTS` ends the connection. */
949
+ gather(f: Http2Frame): boolean {
950
+ this.blockFragments = this.blockFragments + 1
951
+ if (
952
+ this.blockFragments > H2_MAX_FRAGMENTS ||
953
+ f.contentLength > toI32(this.block.length) - this.blockLength
954
+ ) {
955
+ return false
956
+ }
957
+ for (let k: i32 = 0; k < f.contentLength; k++) {
958
+ this.block[this.blockLength + k] = this.input[f.contentStart + k]
959
+ }
960
+ this.blockLength = this.blockLength + f.contentLength
961
+ return true
962
+ }
963
+
964
+ /** HEADERS (§6.2): starts a header block, and ends it when END_HEADERS says so. */
965
+ handleHeaders(f: Http2Frame): i32 {
966
+ this.blockStream = f.streamId
967
+ this.blockEndStream = f.has(H2_FLAG_END_STREAM)
968
+ this.blockError = f.streamError
969
+ this.blockLength = 0
970
+ this.blockFragments = 0
971
+ return this.handleContinuation(f)
972
+ }
973
+
974
+ /** CONTINUATION (§6.10): only inside a header block, which `handleFrame` has checked it continues. */
975
+ handleContinuation(f: Http2Frame): i32 {
976
+ if (this.blockStream === 0) {
977
+ return this.fail(H2_PROTOCOL_ERROR)
978
+ }
979
+ if (!this.gather(f)) {
980
+ return this.fail(H2_ENHANCE_YOUR_CALM)
981
+ }
982
+ return f.has(H2_FLAG_END_HEADERS) ? this.endBlock() : H2_NEED_MORE
983
+ }
984
+
985
+ /**
986
+ * A whole header block: decoded first, whatever becomes of its stream,
987
+ * because the decoder's table must stay in step with the peer's encoder
988
+ * (§4.3); then a new stream's request, or an open stream's trailers.
989
+ */
990
+ endBlock(): i32 {
991
+ const id: i32 = this.blockStream
992
+ this.blockStream = 0
993
+ const decoded: i32 = this.decoder.decode(this.block, H2_ZERO, this.blockLength)
994
+ if (decoded < 0) {
995
+ return this.fail(H2_COMPRESSION_ERROR)
996
+ }
997
+ const s: Http2Stream | null = this.find(id)
998
+ if (s !== null) {
999
+ return this.trailers(s, decoded)
1000
+ }
1001
+ if (!this.isIdle(id)) {
1002
+ if (this.discarded(id)) {
1003
+ return H2_NEED_MORE
1004
+ }
1005
+ return this.fail(this.recentlyClosed(id) ? H2_STREAM_CLOSED : H2_PROTOCOL_ERROR)
1006
+ }
1007
+ if (id % 2 === 0) {
1008
+ return this.fail(H2_PROTOCOL_ERROR)
1009
+ }
1010
+ this.lastPeerStream = id
1011
+ if (this.goawaySent) {
1012
+ return H2_NEED_MORE
1013
+ }
1014
+ if (this.blockError !== 0) {
1015
+ this.resetStream(id, this.blockError)
1016
+ return H2_NEED_MORE
1017
+ }
1018
+ if (decoded === HPACK_LIST_TOO_LARGE) {
1019
+ this.tooLarge(id)
1020
+ return H2_NEED_MORE
1021
+ }
1022
+ if (this.active >= this.config.maxStreams) {
1023
+ this.resetStream(id, H2_REFUSED_STREAM)
1024
+ return H2_NEED_MORE
1025
+ }
1026
+ const shape: i32 = this.fields.readRequest(
1027
+ this.decoder.names,
1028
+ this.decoder.values,
1029
+ this.config.enableConnectProtocol
1030
+ )
1031
+ if (shape !== HTTP_FIELDS_OK || (this.blockEndStream && this.fields.contentLength > toI64(0))) {
1032
+ this.resetStream(id, H2_PROTOCOL_ERROR)
1033
+ return H2_NEED_MORE
1034
+ }
1035
+ const opened: Http2Stream = this.open(id)
1036
+ opened.contentLength = this.fields.contentLength
1037
+ this.stream = id
1038
+ this.endStream = this.blockEndStream
1039
+ if (this.blockEndStream) {
1040
+ this.remoteEnd(opened)
1041
+ }
1042
+ return H2_REQUEST
1043
+ }
1044
+
1045
+ /** A header block on an open stream: trailers, which must end it (§8.1). */
1046
+ trailers(s: Http2Stream, decoded: i32): i32 {
1047
+ if (s.remoteEnded) {
1048
+ return this.streamError(s, H2_STREAM_CLOSED)
1049
+ }
1050
+ if (this.blockError !== 0) {
1051
+ return this.streamError(s, this.blockError)
1052
+ }
1053
+ if (
1054
+ !this.blockEndStream ||
1055
+ decoded === HPACK_LIST_TOO_LARGE ||
1056
+ this.fields.readTrailers(this.decoder.names, this.decoder.values) !== HTTP_FIELDS_OK ||
1057
+ (s.contentLength >= toI64(0) && s.received !== s.contentLength)
1058
+ ) {
1059
+ return this.streamError(s, H2_PROTOCOL_ERROR)
1060
+ }
1061
+ this.stream = s.id
1062
+ this.endStream = true
1063
+ this.remoteEnd(s)
1064
+ return H2_TRAILERS
1065
+ }
1066
+
1067
+ /**
1068
+ * A request whose header list passed `maxHeaderListSize`: a 431 with
1069
+ * END_STREAM, then a RST_STREAM with NO_ERROR when the peer is still
1070
+ * sending (§8.1), so the stream never reaches the program.
1071
+ */
1072
+ tooLarge(id: i32): void {
1073
+ this.encodeStatus(H2_STATUS_TOO_LARGE)
1074
+ const n: i32 = toI32(this.encoded.length)
1075
+ this.wrote(
1076
+ http2WriteHeaders(
1077
+ this.output,
1078
+ this.outputEnd,
1079
+ id,
1080
+ this.encoded,
1081
+ H2_ZERO,
1082
+ n,
1083
+ H2_FLAG_END_STREAM | H2_FLAG_END_HEADERS,
1084
+ H2_NO_PAD
1085
+ )
1086
+ )
1087
+ if (!this.blockEndStream) {
1088
+ this.resetStream(id, H2_NO_ERROR)
1089
+ }
1090
+ }
1091
+
1092
+ /** Empties `encoded` and starts a block with `:status`, or with nothing for 0 (trailers). */
1093
+ encodeStatus(status: i32): void {
1094
+ while (toI32(this.encoded.length) > 0) {
1095
+ this.encoded.pop()
1096
+ }
1097
+ if (status > 0) {
1098
+ this.statusValue[0] = toU8(status / 100 + H2_DIGIT_ZERO)
1099
+ this.statusValue[1] = toU8(((status / 10) % 10) + H2_DIGIT_ZERO)
1100
+ this.statusValue[2] = toU8((status % 10) + H2_DIGIT_ZERO)
1101
+ this.encoder.encodeField(this.encoded, this.statusName, this.statusValue, HPACK_INDEX_WITHOUT, false)
1102
+ }
1103
+ }
1104
+
1105
+ /** PRIORITY (§6.3): checked and otherwise ignored, and it opens nothing. */
1106
+ handlePriority(f: Http2Frame): i32 {
1107
+ if (f.streamError === 0) {
1108
+ return H2_NEED_MORE
1109
+ }
1110
+ const s: Http2Stream | null = this.find(f.streamId)
1111
+ if (s !== null) {
1112
+ return this.streamError(s, f.streamError)
1113
+ }
1114
+ this.resetStream(f.streamId, f.streamError)
1115
+ return H2_NEED_MORE
1116
+ }
1117
+
1118
+ /** RST_STREAM (§6.4): the stream is closed; on an idle one it is a connection error. */
1119
+ handleRstStream(f: Http2Frame): i32 {
1120
+ if (this.isIdle(f.streamId)) {
1121
+ return this.fail(H2_PROTOCOL_ERROR)
1122
+ }
1123
+ const s: Http2Stream | null = this.find(f.streamId)
1124
+ if (s === null) {
1125
+ return H2_NEED_MORE
1126
+ }
1127
+ this.release(s)
1128
+ this.resetCredit = this.resetCredit - 1
1129
+ if (this.resetCredit < 0) {
1130
+ return this.fail(H2_ENHANCE_YOUR_CALM)
1131
+ }
1132
+ this.stream = f.streamId
1133
+ this.errorCode = f.errorCode
1134
+ this.resetByPeer = true
1135
+ return H2_RESET
1136
+ }
1137
+
1138
+ /**
1139
+ * SETTINGS (§6.5): the peer's acknowledgement puts the server's own
1140
+ * settings in force; anything else is the peer's, each value checked, then
1141
+ * acknowledged. A new SETTINGS_INITIAL_WINDOW_SIZE moves every stream's send
1142
+ * window by the difference (§6.9.2), and opening one answers H2_WINDOW.
1143
+ */
1144
+ handleSettings(f: Http2Frame): i32 {
1145
+ if (f.has(H2_FLAG_ACK)) {
1146
+ if (!this.settingsAcked) {
1147
+ this.settingsAcked = true
1148
+ this.decoder.setSettingsLimit(this.config.headerTableSize)
1149
+ const delta: i64 = toI64(this.config.initialWindowSize) - this.localInitialWindow
1150
+ this.localInitialWindow = toI64(this.config.initialWindowSize)
1151
+ for (const s of this.streams) {
1152
+ if (s.id !== 0) {
1153
+ s.recvWindow = s.recvWindow + delta
1154
+ }
1155
+ }
1156
+ }
1157
+ return H2_NEED_MORE
1158
+ }
1159
+ let opened: boolean = false
1160
+ const n: i32 = http2SettingCount(f)
1161
+ for (let k: i32 = 0; k < n; k++) {
1162
+ const id: i32 = http2SettingId(f, this.input, k)
1163
+ const value: i64 = http2SettingValue(f, this.input, k)
1164
+ const error: i32 = http2SettingError(id, value)
1165
+ if (error !== H2_NO_ERROR) {
1166
+ return this.fail(error)
1167
+ }
1168
+ if (id === H2_SETTINGS_HEADER_TABLE_SIZE) {
1169
+ const size: i32 = toI32(Math.min(value, toI64(HPACK_DEFAULT_TABLE_SIZE)))
1170
+ if (size !== this.encoder.table.maxSize) {
1171
+ this.encoder.setMaxTableSize(size)
1172
+ }
1173
+ } else if (id === H2_SETTINGS_INITIAL_WINDOW_SIZE) {
1174
+ const delta: i64 = value - this.peerInitialWindow
1175
+ this.peerInitialWindow = value
1176
+ for (const s of this.streams) {
1177
+ if (s.id !== 0) {
1178
+ s.sendWindow = s.sendWindow + delta
1179
+ if (s.sendWindow > toI64(H2_MAX_WINDOW)) {
1180
+ return this.fail(H2_FLOW_CONTROL_ERROR)
1181
+ }
1182
+ if (delta > toI64(0) && s.blocked) {
1183
+ s.blocked = false
1184
+ opened = true
1185
+ }
1186
+ }
1187
+ }
1188
+ } else if (id === H2_SETTINGS_MAX_FRAME_SIZE) {
1189
+ this.peerMaxFrameSize = toI32(value)
1190
+ } else if (id === H2_SETTINGS_MAX_HEADER_LIST_SIZE) {
1191
+ this.peerMaxHeaderList = value
1192
+ }
1193
+ }
1194
+ this.wrote(http2WriteSettingsAck(this.output, this.outputEnd))
1195
+ if (opened) {
1196
+ this.stream = 0
1197
+ return H2_WINDOW
1198
+ }
1199
+ return H2_NEED_MORE
1200
+ }
1201
+
1202
+ /** WINDOW_UPDATE (§6.9): a send window grows, and a write it held back may go on. */
1203
+ handleWindowUpdate(f: Http2Frame): i32 {
1204
+ const inc: i64 = toI64(f.increment)
1205
+ if (f.streamId === 0) {
1206
+ this.sendWindow = this.sendWindow + inc
1207
+ if (this.sendWindow > toI64(H2_MAX_WINDOW)) {
1208
+ return this.fail(H2_FLOW_CONTROL_ERROR)
1209
+ }
1210
+ if (this.sendBlocked) {
1211
+ this.sendBlocked = false
1212
+ this.stream = 0
1213
+ return H2_WINDOW
1214
+ }
1215
+ return H2_NEED_MORE
1216
+ }
1217
+ const s: Http2Stream | null = this.find(f.streamId)
1218
+ if (s === null) {
1219
+ return this.isIdle(f.streamId) ? this.fail(H2_PROTOCOL_ERROR) : H2_NEED_MORE
1220
+ }
1221
+ if (f.streamError !== 0) {
1222
+ return this.streamError(s, f.streamError)
1223
+ }
1224
+ s.sendWindow = s.sendWindow + inc
1225
+ if (s.sendWindow > toI64(H2_MAX_WINDOW)) {
1226
+ return this.streamError(s, H2_FLOW_CONTROL_ERROR)
1227
+ }
1228
+ if (s.blocked) {
1229
+ s.blocked = false
1230
+ this.stream = s.id
1231
+ return H2_WINDOW
1232
+ }
1233
+ return H2_NEED_MORE
1234
+ }
1235
+
1236
+ // ---- Writing ------------------------------------------------------------------
1237
+
1238
+ /** The stream the program may still send on, or `null`. */
1239
+ writable(id: i32): Http2Stream | null {
1240
+ if (this.state === H2_STATE_FAILED) {
1241
+ return null
1242
+ }
1243
+ const s: Http2Stream | null = this.find(id)
1244
+ return s !== null && !s.localEnded ? s : null
1245
+ }
1246
+
1247
+ /**
1248
+ * Encodes `:status` and `names`/`values` and sends the block as HEADERS and
1249
+ * as many CONTINUATION frames as the peer's SETTINGS_MAX_FRAME_SIZE needs,
1250
+ * with END_STREAM when `endStream`. Answers 0, `H2_INVALID` for fields
1251
+ * `httpFieldsCheckOutgoing` refuses or a status outside 100 to 599,
1252
+ * `H2_AGAIN` when the output has no room for it yet, or `H2_CLOSED`.
1253
+ * Nothing is encoded until it is sure to be sent, so the HPACK state never
1254
+ * moves for a block that was not.
1255
+ */
1256
+ sendHead(s: Http2Stream, status: i32, names: u8[][], values: u8[][], endStream: boolean): i32 {
1257
+ if (httpFieldsCheckOutgoing(names, values) !== HTTP_FIELDS_OK) {
1258
+ return H2_INVALID
1259
+ }
1260
+ // A literal costs its strings and at most eleven bytes of prefixes; two
1261
+ // table size updates and `:status` are inside the forty-eight on top.
1262
+ let bound: i64 = toI64(48)
1263
+ for (let k: i32 = 0; k < toI32(names.length) && k < toI32(values.length); k++) {
1264
+ bound = bound + toI64(names[k].length) + toI64(values[k].length) + toI64(11)
1265
+ }
1266
+ const frames: i64 = bound / toI64(this.peerMaxFrameSize) + toI64(1)
1267
+ const need: i64 = bound + frames * toI64(H2_FRAME_HEADER_SIZE)
1268
+ if (need > toI64(this.bodyRoom())) {
1269
+ return toI64(this.output.length) < need + toI64(H2_CONTROL_RESERVE) ? H2_INVALID : H2_AGAIN
1270
+ }
1271
+ this.encodeStatus(status)
1272
+ for (let k: i32 = 0; k < toI32(names.length) && k < toI32(values.length); k++) {
1273
+ const value: u8[] = values[k]
1274
+ // One choice covers the name and the value, so Huffman is taken only
1275
+ // when it shortens the value and does not lengthen the name: neither
1276
+ // string is then longer than the bound above counted it.
1277
+ const name: u8[] = names[k]
1278
+ const huffman: boolean =
1279
+ hpackHuffmanLength(this.encoder.huffman, value, H2_ZERO, toI32(value.length)) < toI32(value.length) &&
1280
+ hpackHuffmanLength(this.encoder.huffman, name, H2_ZERO, toI32(name.length)) <= toI32(name.length)
1281
+ const indexing: i32 = httpFieldSensitive(name) ? HPACK_INDEX_NEVER : HPACK_INDEX_WITHOUT
1282
+ this.encoder.encodeField(this.encoded, name, value, indexing, huffman)
1283
+ }
1284
+ const total: i32 = toI32(this.encoded.length)
1285
+ let at: i32 = 0
1286
+ let first: boolean = true
1287
+ while (first || at < total) {
1288
+ const left: i32 = total - at
1289
+ const chunk: i32 = left < this.peerMaxFrameSize ? left : this.peerMaxFrameSize
1290
+ const last: boolean = at + chunk >= total
1291
+ const flags: i32 =
1292
+ (last ? H2_FLAG_END_HEADERS : H2_ZERO) | (first && endStream ? H2_FLAG_END_STREAM : H2_ZERO)
1293
+ this.wrote(
1294
+ first
1295
+ ? http2WriteHeaders(this.output, this.outputEnd, s.id, this.encoded, at, chunk, flags, H2_NO_PAD)
1296
+ : http2WriteContinuation(this.output, this.outputEnd, s.id, this.encoded, at, chunk, flags)
1297
+ )
1298
+ at = at + chunk
1299
+ first = false
1300
+ }
1301
+ if (endStream) {
1302
+ this.localEnd(s)
1303
+ }
1304
+ return 0
1305
+ }
1306
+
1307
+ /**
1308
+ * Answers stream `id` with `status` and the fields `names[k]`: `values[k]`,
1309
+ * lowercase and none of them connection-specific; END_STREAM when
1310
+ * `endStream`. A 1xx status but 101 may come before the final one and may
1311
+ * not end the stream. Answers as `sendHead` does.
1312
+ */
1313
+ respond(id: i32, status: i32, names: u8[][], values: u8[][], endStream: boolean): i32 {
1314
+ const s: Http2Stream | null = this.writable(id)
1315
+ if (s === null) {
1316
+ return H2_CLOSED
1317
+ }
1318
+ const interim: boolean = status >= 100 && status < 200
1319
+ // 101 switches protocols, which HTTP/2 does not do (§8.6).
1320
+ if (status < 100 || status > 599 || status === 101 || s.responded || (interim && endStream)) {
1321
+ return H2_INVALID
1322
+ }
1323
+ const result: i32 = this.sendHead(s, status, names, values, endStream)
1324
+ if (result === 0 && !interim) {
1325
+ s.responded = true
1326
+ }
1327
+ return result
1328
+ }
1329
+
1330
+ /** Ends stream `id` with a trailer section, after its response head (§8.1). Answers as `sendHead` does. */
1331
+ writeTrailers(id: i32, names: u8[][], values: u8[][]): i32 {
1332
+ const s: Http2Stream | null = this.writable(id)
1333
+ if (s === null) {
1334
+ return H2_CLOSED
1335
+ }
1336
+ if (!s.responded) {
1337
+ return H2_INVALID
1338
+ }
1339
+ return this.sendHead(s, H2_ZERO, names, values, true)
1340
+ }
1341
+
1342
+ /**
1343
+ * Sends as much of `buf[off, off + len)` on stream `id` as the stream's
1344
+ * window, the connection's window, and the output allow, in frames no
1345
+ * larger than the peer's SETTINGS_MAX_FRAME_SIZE, and answers how much.
1346
+ * END_STREAM goes on the last frame when `endStream` and every byte was
1347
+ * taken; `len` 0 with `endStream` sends an empty DATA that ends the stream.
1348
+ * Answers `H2_AGAIN` when nothing could be taken — after H2_WINDOW for the
1349
+ * stream or the connection, or once the output has drained, it may be
1350
+ * tried again — and `H2_CLOSED` for a stream with no final response head
1351
+ * or that has ended.
1352
+ */
1353
+ writeData(id: i32, buf: u8[], off: i32, len: i32, endStream: boolean): i32 {
1354
+ http2CheckWindow("Http2Connection.writeData", buf, off, len)
1355
+ const s: Http2Stream | null = this.writable(id)
1356
+ if (s === null || !s.responded) {
1357
+ return H2_CLOSED
1358
+ }
1359
+ let sent: i32 = 0
1360
+ while (sent < len) {
1361
+ const room: i32 = this.bodyRoom() - H2_FRAME_HEADER_SIZE
1362
+ let chunk: i64 = Math.min(toI64(len - sent), toI64(this.peerMaxFrameSize))
1363
+ chunk = Math.min(chunk, Math.min(s.sendWindow, this.sendWindow))
1364
+ chunk = Math.min(chunk, toI64(room))
1365
+ if (chunk <= toI64(0)) {
1366
+ if (s.sendWindow <= toI64(0)) {
1367
+ s.blocked = true
1368
+ }
1369
+ if (this.sendWindow <= toI64(0)) {
1370
+ this.sendBlocked = true
1371
+ }
1372
+ break
1373
+ }
1374
+ const n: i32 = toI32(chunk)
1375
+ const last: boolean = endStream && sent + n === len
1376
+ this.wrote(
1377
+ http2WriteData(
1378
+ this.output,
1379
+ this.outputEnd,
1380
+ id,
1381
+ buf,
1382
+ off + sent,
1383
+ n,
1384
+ last ? H2_FLAG_END_STREAM : 0,
1385
+ H2_NO_PAD
1386
+ )
1387
+ )
1388
+ s.sendWindow = s.sendWindow - chunk
1389
+ this.sendWindow = this.sendWindow - chunk
1390
+ sent = sent + n
1391
+ }
1392
+ if (len === 0 && endStream) {
1393
+ if (this.bodyRoom() < H2_FRAME_HEADER_SIZE) {
1394
+ return H2_AGAIN
1395
+ }
1396
+ this.wrote(
1397
+ http2WriteData(this.output, this.outputEnd, id, buf, off, H2_ZERO, H2_FLAG_END_STREAM, H2_NO_PAD)
1398
+ )
1399
+ }
1400
+ if (endStream && sent === len) {
1401
+ this.localEnd(s)
1402
+ }
1403
+ return sent === 0 && len > 0 ? H2_AGAIN : sent
1404
+ }
1405
+
1406
+ /**
1407
+ * Resets stream `id` with `code` (§6.4), from the program's side: CANCEL,
1408
+ * say. Answers 0, `H2_AGAIN` when the output has no room for the frame yet
1409
+ * (the stream stays open), or `H2_CLOSED`.
1410
+ */
1411
+ reset(id: i32, code: i32): i32 {
1412
+ if (this.state === H2_STATE_FAILED || this.find(id) === null) {
1413
+ return H2_CLOSED
1414
+ }
1415
+ this.room()
1416
+ return this.resetStream(id, code) ? 0 : H2_AGAIN
1417
+ }
1418
+
1419
+ /**
1420
+ * Starts a graceful close (§6.8): GOAWAY with NO_ERROR naming the last
1421
+ * stream the peer opened. Streams already open finish; newer ones are
1422
+ * ignored; `isDone` once the last has closed and everything is sent.
1423
+ * Answers 0, also when a GOAWAY was already sent or the connection has
1424
+ * failed, or `H2_AGAIN` when the output has no room for the frame yet.
1425
+ */
1426
+ goaway(): i32 {
1427
+ if (this.state === H2_STATE_FAILED || this.goawaySent) {
1428
+ return 0
1429
+ }
1430
+ this.room()
1431
+ return this.sendGoaway(H2_NO_ERROR) ? 0 : H2_AGAIN
1432
+ }
1433
+ }