@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.
Files changed (58) hide show
  1. package/INSTALL.md +43 -34
  2. package/bin/nish +0 -0
  3. package/package.json +1 -1
  4. package/runtime/nish.d.ts +117 -8
  5. package/runtime/nish.h +50 -13
  6. package/runtime/nish.mjs +71 -7
  7. package/runtime/runtime-host.c +43 -24
  8. package/runtime/runtime-net.c +57 -10
  9. package/runtime/runtime-os.c +62 -20
  10. package/runtime/runtime-simd.c +203 -0
  11. package/runtime/runtime-wasm.c +12 -0
  12. package/runtime/runtime.c +66 -18
  13. package/runtime/shim.mjs +114 -5
  14. package/scripts/build.sh +14 -12
  15. package/std/README.md +168 -36
  16. package/std/crypto/aes.ts +51 -8
  17. package/std/crypto/base64url.ts +25 -12
  18. package/std/crypto/chacha20poly1305.ts +43 -29
  19. package/std/crypto/ct.ts +6 -1
  20. package/std/crypto/hkdf.ts +364 -3
  21. package/std/crypto/hmac.ts +307 -7
  22. package/std/crypto/p256.ts +618 -34
  23. package/std/crypto/sha1.ts +147 -0
  24. package/std/crypto/sha256.ts +3 -2
  25. package/std/crypto/x25519.ts +88 -36
  26. package/std/crypto/x509.ts +130 -47
  27. package/std/json.ts +297 -41
  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 +911 -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 +462 -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 +929 -0
  45. package/std/net/quic-packet.ts +1698 -0
  46. package/std/net/quic-recovery.ts +849 -0
  47. package/std/net/quic-stream.ts +1609 -0
  48. package/std/net/quic.ts +3578 -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 +414 -0
  54. package/std/net/tls.ts +1437 -0
  55. package/std/net/websocket.ts +823 -0
  56. package/std/net/webtransport.ts +1334 -0
  57. package/std/secret.ts +62 -0
  58. package/std/text.ts +77 -0
@@ -0,0 +1,2145 @@
1
+ /**
2
+ * `nish/net/http3` — the server side of an HTTP/3 connection (RFC 9114) on
3
+ * the streams of a `QuicConnection` (`nish/net/quic`): the control stream
4
+ * and SETTINGS both ways, the QPACK encoder and decoder streams (opened, at
5
+ * dynamic table capacity 0), requests and responses on bidirectional streams
6
+ * with their bodies streamed both ways, GOAWAY, and RFC 9114's error codes.
7
+ * Field sections are encoded and decoded by `nish/net/qpack` and checked by
8
+ * `nish/net/http-fields`.
9
+ *
10
+ * It is sans-IO in the way the QUIC connection under it is: the carrier
11
+ * hands the `QuicConnection` its datagrams and takes the ones it sends, and
12
+ * this module reads and writes that connection's streams. `nish/net/http3-server`
13
+ * runs it over a UDP socket with ALPN `h3`.
14
+ *
15
+ * import { Http3Config, Http3Connection, H3_NEED_MORE, H3_ERROR, H3_REQUEST, H3_DATA, H3_END } from "nish/net/http3";
16
+ *
17
+ * const h3 = new Http3Connection(new Http3Config(), quic); // once per slot; restart() per connection
18
+ * // after quic.receive(...):
19
+ * let event: i32 = h3.next();
20
+ * while (event !== H3_NEED_MORE && event !== H3_ERROR) {
21
+ * // H3_REQUEST: h3.stream, h3.fields (method, path, get("accept"))
22
+ * // H3_DATA: h3.data[h3.dataStart .. h3.dataStart + h3.dataLength)
23
+ * // H3_END: the request is whole; respond
24
+ * event = h3.next();
25
+ * }
26
+ * h3.respond(stream, 200, names, values, false);
27
+ * h3.writeData(stream, body, 0, n, true); // answers how much it took
28
+ *
29
+ * **Events.** `next()` reads the streams the QUIC connection has news for and
30
+ * answers the first thing that means something to the program: `H3_REQUEST`
31
+ * (a request's field section, in `fields`), `H3_DATA` (a piece of its body, a
32
+ * window onto `data` valid until the next call), `H3_TRAILERS`, `H3_END` (the
33
+ * request ended, and its `content-length`, if any, matched), `H3_RESET` (the
34
+ * stream is gone: the client reset it or stopped it, or it broke a rule that
35
+ * resets it — `errorCode` says which, `resetByPeer` who), `H3_WRITABLE` (a
36
+ * stream whose write was held back can take more), `H3_GOAWAY` (the client is
37
+ * going away; `lastStreamId` is its push ID), and `H3_ERROR`, final: the
38
+ * connection broke a rule, it is closed with `errorCode`, and every later call
39
+ * answers it again. `H3_NEED_MORE` asks for more datagrams. A request's events
40
+ * come in order — REQUEST, DATA…, TRAILERS, END — and after `H3_RESET` its
41
+ * stream has no more.
42
+ *
43
+ * **Back-pressure** is QUIC's flow control. A body is read only as the
44
+ * program calls `next()`, `bodyChunk` bytes at most at a time, and QUIC gives
45
+ * the client more credit only as its receive buffer is read. `writeData`
46
+ * takes as much as the stream's send buffer has room for and answers how much;
47
+ * when it took less, `H3_WRITABLE` names the stream once acknowledgements free
48
+ * room. HEADERS and trailers go whole or not at all (`H3_AGAIN`).
49
+ *
50
+ * **ALPN.** A QUIC connection whose handshake chose anything but `h3` is
51
+ * closed with CRYPTO_ERROR no_application_protocol (0x0178) on the first
52
+ * `next()` after it connects, before any HTTP/3 byte (§3.1).
53
+ *
54
+ * **Streams** (§6). The connection opens its control stream, sends SETTINGS
55
+ * first on it (SETTINGS_QPACK_MAX_TABLE_CAPACITY 0, SETTINGS_QPACK_BLOCKED_STREAMS
56
+ * 0 and SETTINGS_MAX_FIELD_SECTION_SIZE), and opens its QPACK encoder and
57
+ * decoder streams, on which it sends nothing but their types. The client's
58
+ * encoder and decoder streams go to `nish/net/qpack`'s capacity-0 handling.
59
+ * A unidirectional stream of a type it does not know is asked to stop
60
+ * (H3_STREAM_CREATION_ERROR) and read to its end (§6.2).
61
+ *
62
+ * **What it refuses.** A connection error closes the QUIC connection with
63
+ * the code: a second control, encoder or decoder stream, or a push stream
64
+ * from a client (H3_STREAM_CREATION_ERROR, §6.2.1, §6.2.2); a control stream
65
+ * whose first frame is not SETTINGS (H3_MISSING_SETTINGS); the end, reset or
66
+ * STOP_SENDING of any critical stream, either side's (H3_CLOSED_CRITICAL_STREAM);
67
+ * a frame where it may not be — DATA or HEADERS on the control stream, a
68
+ * control frame on a request stream, a second SETTINGS, DATA before HEADERS,
69
+ * anything but the end after trailers, any PUSH_PROMISE from a client, and an
70
+ * HTTP/2 type HTTP/3 reserves (H3_FRAME_UNEXPECTED, §7.2.8); a frame cut off
71
+ * by its stream's end or a payload of the wrong length (H3_FRAME_ERROR, §7.1);
72
+ * a SETTINGS frame `nish/net/http3-frame` refuses (H3_SETTINGS_ERROR); a
73
+ * GOAWAY whose push ID rose, a MAX_PUSH_ID that fell, a CANCEL_PUSH past the
74
+ * push IDs allowed (H3_ID_ERROR, §5.2, §7.2.7, §7.2.3); a control frame past
75
+ * `H3_CONTROL_FRAME_MAX` (H3_EXCESSIVE_LOAD); and QPACK's three. A stream
76
+ * error resets one request, both ways, and the connection lives: a field
77
+ * section `nish/net/http-fields` refuses, and a body that disagrees with
78
+ * `content-length` (H3_MESSAGE_ERROR, §4.1.2); a request stream that ends
79
+ * before its HEADERS (H3_REQUEST_INCOMPLETE, §4.1.1); a request on a stream at
80
+ * or past the ID of the GOAWAY this side sent (H3_REQUEST_REJECTED, §5.2).
81
+ * A request whose field section is longer than `maxFieldSectionSize` — the
82
+ * frame, or the section once decoded — is answered with a 431, never reaches
83
+ * the program, and its stream is asked to stop with H3_NO_ERROR (§4.2.2,
84
+ * §4.1.2).
85
+ *
86
+ * **No server push.** It never sends PUSH_PROMISE; a client's MAX_PUSH_ID is
87
+ * allowed, checked and ignored.
88
+ *
89
+ * **Caps are start-up numbers**, all in `Http3Config`, and every buffer is
90
+ * made by the constructor: the stream state for every QUIC stream slot, the
91
+ * field section, the body chunk and the control frame. A field section's
92
+ * names and values are copied into arrays the connection keeps, which grow to
93
+ * the largest section seen and are then reused, so a warm connection
94
+ * allocates nothing per request (`docs/security/http3.md`, H3-2). A peer's
95
+ * counters saturate, and the lookup of a stream's state is the QUIC
96
+ * connection's own hash index (`QuicStreams.slotOf`), never a scan.
97
+ *
98
+ * **Extended CONNECT and WebTransport.** With `extendedConnect` the
99
+ * connection sends SETTINGS_ENABLE_CONNECT_PROTOCOL 1 and a request may carry
100
+ * `:protocol` (RFC 9220); without it `:protocol` is malformed,
101
+ * H3_MESSAGE_ERROR. `webtransportSessions` above 0 turns that on and also
102
+ * sends SETTINGS_H3_DATAGRAM 1, draft-02's SETTINGS_ENABLE_WEBTRANSPORT 1 and
103
+ * draft-07's SETTINGS_WEBTRANSPORT_MAX_SESSIONS, and `nish/net/webtransport`
104
+ * runs its sessions on top: a request then waits for the client's SETTINGS
105
+ * (draft-02 §3.1), and a stream that opens with WebTransport's unidirectional
106
+ * type (0x54) or bidirectional signal (0x41) is read as far as its session ID,
107
+ * held unread and named in `H3_STREAM` until the program calls
108
+ * `acceptStream` (its bytes then come as H3_DATA, H3_END, H3_RESET and
109
+ * H3_STOPPED, and `writeStream` writes them as they are) or `refuseStream`.
110
+ * `openStream` opens one of this side's; `writeDataWhole` writes a capsule;
111
+ * a GOAWAY still lets a session's new stream in, and `H3_DROPPED` names a
112
+ * request stream that ended before its request reached the program. The
113
+ * client's SETTINGS_ENABLE_CONNECT_PROTOCOL and SETTINGS_H3_DATAGRAM must be
114
+ * 0 or 1 (H3_SETTINGS_ERROR), a session ID that is no client-initiated
115
+ * bidirectional stream is H3_ID_ERROR, and the signal anywhere but a stream's
116
+ * start is H3_FRAME_ERROR. Holding every request, not only an extended
117
+ * CONNECT, until the client's SETTINGS keeps a decoded field section from
118
+ * having to be held per stream; it costs a request only when the client's
119
+ * control stream is lost or reordered.
120
+ *
121
+ * Private helpers share the importing program's flat symbol namespace
122
+ * (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
123
+ *
124
+ * Written from RFC 9114 and RFC 9204, not ported from another implementation.
125
+ */
126
+ import {
127
+ HTTP_FIELDS_OK,
128
+ HttpFields,
129
+ httpFieldBytes,
130
+ httpFieldSensitive,
131
+ httpFieldsCheckOutgoing,
132
+ } from "nish/net/http-fields"
133
+ import {
134
+ QPACK_FIELD_OVERHEAD,
135
+ QPACK_OK,
136
+ QPACK_SECTION_TOO_LARGE,
137
+ QpackDecoder,
138
+ QpackEncoder,
139
+ } from "nish/net/qpack"
140
+ import {
141
+ H3_CLOSED_CRITICAL_STREAM,
142
+ H3_EXCESSIVE_LOAD,
143
+ H3_FRAME_WEBTRANSPORT_STREAM,
144
+ H3_FRAME_CANCEL_PUSH,
145
+ H3_FRAME_DATA,
146
+ H3_FRAME_ERROR,
147
+ H3_FRAME_GOAWAY,
148
+ H3_FRAME_HEADERS,
149
+ H3_FRAME_MAX_PUSH_ID,
150
+ H3_FRAME_PUSH_PROMISE,
151
+ H3_FRAME_SETTINGS,
152
+ H3_FRAME_UNEXPECTED,
153
+ H3_ID_ERROR,
154
+ H3_INTERNAL_ERROR,
155
+ H3_MESSAGE_ERROR,
156
+ H3_MISSING_SETTINGS,
157
+ H3_NO_ERROR,
158
+ H3_REQUEST_CANCELLED,
159
+ H3_REQUEST_INCOMPLETE,
160
+ H3_REQUEST_REJECTED,
161
+ H3_SETTINGS_ENABLE_CONNECT_PROTOCOL,
162
+ H3_SETTINGS_ENABLE_WEBTRANSPORT,
163
+ H3_SETTINGS_ERROR,
164
+ H3_SETTINGS_H3_DATAGRAM,
165
+ H3_SETTINGS_MAX_FIELD_SECTION_SIZE,
166
+ H3_SETTINGS_OK,
167
+ H3_SETTINGS_QPACK_BLOCKED_STREAMS,
168
+ H3_SETTINGS_QPACK_MAX_TABLE_CAPACITY,
169
+ H3_SETTINGS_WEBTRANSPORT_MAX_SESSIONS,
170
+ H3_STREAM_CONTROL,
171
+ H3_STREAM_CREATION_ERROR,
172
+ H3_STREAM_PUSH,
173
+ H3_STREAM_QPACK_DECODER,
174
+ H3_STREAM_QPACK_ENCODER,
175
+ H3_STREAM_WEBTRANSPORT,
176
+ H3_WT_SESSION_GONE,
177
+ Http3FrameHeader,
178
+ Http3Settings,
179
+ h3CheckWindow,
180
+ h3Count,
181
+ h3FrameHeaderSize,
182
+ h3PutFrameHeader,
183
+ h3PutIdFrame,
184
+ h3PutSettings,
185
+ h3PutVarint,
186
+ h3ReadFrameHeader,
187
+ h3ReadIdPayload,
188
+ h3ReadSettings,
189
+ h3ReadVarint,
190
+ h3ReservedHttp2Frame,
191
+ h3VarintLength,
192
+ } from "nish/net/http3-frame"
193
+ import { QUIC_STATE_CONNECTED, QuicConnection } from "nish/net/quic"
194
+ import { QUIC_STREAM_END, QUIC_STREAM_ERR_RESET, QuicStream } from "nish/net/quic-stream"
195
+ import { quicVarintPut } from "nish/net/quic-packet"
196
+
197
+ /** The ALPN protocol identifier of HTTP/3 (RFC 9114 §3.1). */
198
+ export const H3_ALPN: string = "h3"
199
+
200
+ /** `next` has nothing more until more datagrams arrive. */
201
+ export const H3_NEED_MORE: i32 = 0
202
+ /** A request's field section: `stream`, `fields`. */
203
+ export const H3_REQUEST: i32 = 1
204
+ /** Request body bytes: `stream`, `data[dataStart .. dataStart + dataLength)`. */
205
+ export const H3_DATA: i32 = 2
206
+ /** A trailer section: `stream`, `fields`. */
207
+ export const H3_TRAILERS: i32 = 3
208
+ /** The request ended whole: `stream`. */
209
+ export const H3_END: i32 = 4
210
+ /** The stream is gone: `stream`, `errorCode`, `resetByPeer`. */
211
+ export const H3_RESET: i32 = 5
212
+ /** A stream whose write was held back can take more: `stream`. */
213
+ export const H3_WRITABLE: i32 = 6
214
+ /** The client sent GOAWAY: `lastStreamId`, its push ID. */
215
+ export const H3_GOAWAY: i32 = 7
216
+ /** The connection failed with `errorCode` and is closed. Final. */
217
+ export const H3_ERROR: i32 = 8
218
+ /**
219
+ * A WebTransport stream arrived (only with `webtransportSessions` on):
220
+ * `stream`, and `session`, the session ID its first bytes named. It is held,
221
+ * unread, until the program calls `acceptStream` or `refuseStream`.
222
+ */
223
+ export const H3_STREAM: i32 = 9
224
+ /** The client sent STOP_SENDING for a WebTransport stream this side writes, and QUIC reset it: `stream`, `errorCode`. */
225
+ export const H3_STOPPED: i32 = 10
226
+ /**
227
+ * A request stream ended before its request reached the program (only with
228
+ * `webtransportSessions` on): `stream`. Nothing is owed the program; a
229
+ * WebTransport layer lets go of the streams that waited for it as a session.
230
+ */
231
+ export const H3_DROPPED: i32 = 11
232
+
233
+ /** A write that can take nothing yet: Linux's EAGAIN, as `nish:net` spells it. */
234
+ export const H3_AGAIN: i32 = -11
235
+ /** A write of fields or a status the program may not send, or out of order: Linux's EINVAL. */
236
+ export const H3_INVALID: i32 = -22
237
+ /** A write to a stream that is not an open request: Linux's EPIPE. */
238
+ export const H3_CLOSED: i32 = -32
239
+ /** A field section past what the client takes, or past a stream's buffer: Linux's EMSGSIZE. */
240
+ export const H3_TOO_LARGE: i32 = -90
241
+
242
+ /** CRYPTO_ERROR with TLS's no_application_protocol alert (RFC 9001 §4.8): a handshake that did not choose `h3`. */
243
+ export const H3_NO_APPLICATION_PROTOCOL: i64 = 0x0178
244
+
245
+ /** The largest control-stream frame read whole: SETTINGS, GOAWAY, MAX_PUSH_ID, CANCEL_PUSH. */
246
+ export const H3_CONTROL_FRAME_MAX: i32 = 1024
247
+
248
+ /** The most WebTransport sessions a configuration may advertise. */
249
+ export const H3_MAX_SESSIONS: i32 = 256
250
+
251
+ /** The request streams' phases, and the unidirectional streams'. */
252
+ const H3_PHASE_HEADERS: i32 = 0
253
+ const H3_PHASE_BODY: i32 = 1
254
+ const H3_PHASE_TRAILED: i32 = 2
255
+ const H3_PHASE_SKIP: i32 = 3
256
+ const H3_PHASE_DISCARD: i32 = 4
257
+ const H3_PHASE_DONE: i32 = 5
258
+ const H3_PHASE_UNI_TYPE: i32 = 6
259
+ const H3_PHASE_CONTROL: i32 = 7
260
+ const H3_PHASE_ENCODER: i32 = 8
261
+ const H3_PHASE_DECODER: i32 = 9
262
+ /** A WebTransport unidirectional stream whose session ID is being read, one held for the program, and one read raw. */
263
+ const H3_PHASE_WT_SESSION: i32 = 10
264
+ const H3_PHASE_WT_HELD: i32 = 11
265
+ const H3_PHASE_WT: i32 = 12
266
+
267
+ /** A stream slot's flags. */
268
+ const H3_FLAG_DELIVERED: i32 = 1
269
+ const H3_FLAG_HEADERS_SENT: i32 = 2
270
+ const H3_FLAG_SEND_DONE: i32 = 4
271
+ const H3_FLAG_RECV_DONE: i32 = 8
272
+ const H3_FLAG_BLOCKED: i32 = 32
273
+ const H3_FLAG_COUNTED: i32 = 64
274
+ /** On the ring of streams `next` steps again before asking QUIC, or waiting for the client's SETTINGS. */
275
+ const H3_FLAG_AGAIN: i32 = 128
276
+ const H3_FLAG_WAITING: i32 = 16
277
+ /** A WebTransport stream: its bytes are the session's, read and written raw. */
278
+ const H3_FLAG_WT: i32 = 256
279
+ /** A request stream past this side's GOAWAY, refused unless its first bytes make it a WebTransport stream. */
280
+ const H3_FLAG_LATE: i32 = 512
281
+
282
+ /** The connection's states. */
283
+ const H3_STATE_OPEN: i32 = 0
284
+ const H3_STATE_FAILED: i32 = 1
285
+
286
+ /** What `requestFrame` answers when it read a frame header and the stream has more to read. */
287
+ const H3_CONTINUE: i32 = -1
288
+
289
+ /** A frame type no frame has, for a payload being skipped. */
290
+ const H3_SKIP_TYPE: i64 = -1
291
+
292
+ /** A reserved frame type (0x1f * 0 + 0x21) the connection pads a stream's send buffer with to be told when it drains. */
293
+ const H3_FILLER_TYPE: i32 = 0x21
294
+
295
+ /** Typed constants, since a bare literal is an `f64` under `--number-mode f64`. */
296
+ const H3_ZERO: i32 = 0
297
+ const H3_ONE: i32 = 1
298
+ const H3_NONE: i64 = -1
299
+ const H3_NONE32: i32 = -1
300
+ const H3_STATUS_TOO_LARGE: i32 = 431
301
+ const H3_DIGIT_ZERO: i32 = 48
302
+
303
+ /** The largest body chunk or DATA frame a configuration may ask for. */
304
+ const H3_MAX_CHUNK: i32 = 1048576
305
+
306
+ /** The size classes of a field's copy: up to 2^23 bytes, past any stream buffer QUIC allows. */
307
+ const H3_FIELD_CLASSES: i32 = 24
308
+
309
+ /** The bytes a slot keeps for a frame header being read or written. */
310
+ const H3_HEAD: i32 = 16
311
+
312
+ /**
313
+ * The connection's caps, fixed when it is made. `maxFieldSectionSize` is
314
+ * advertised in SETTINGS and is the largest HEADERS frame read and the
315
+ * largest section decoded; `bodyChunk` the most body handed to the program at
316
+ * once; `writeChunk` the largest DATA frame written.
317
+ */
318
+ export class Http3Config {
319
+ /** SETTINGS_MAX_FIELD_SECTION_SIZE (§4.2.2): past it a request is answered with a 431. */
320
+ maxFieldSectionSize: i32 = 16384
321
+ /** The body buffer: the largest `H3_DATA` window. */
322
+ bodyChunk: i32 = 16384
323
+ /** The largest DATA frame `writeData` starts. */
324
+ writeChunk: i32 = 16384
325
+ /**
326
+ * The WebTransport sessions this side takes at once
327
+ * (`nish/net/webtransport`), 0 for none. Above 0 it turns on extended
328
+ * CONNECT and sends SETTINGS_H3_DATAGRAM, SETTINGS_ENABLE_WEBTRANSPORT and
329
+ * SETTINGS_WEBTRANSPORT_MAX_SESSIONS, and the QUIC connection must take
330
+ * DATAGRAM frames; at 0 it must not (RFC 9297 §2.1.1).
331
+ */
332
+ webtransportSessions: i32 = 0
333
+ /**
334
+ * Whether `:protocol` is accepted (RFC 9220's extended CONNECT), and
335
+ * SETTINGS_ENABLE_CONNECT_PROTOCOL sent. WebTransport turns it on.
336
+ */
337
+ extendedConnect: boolean = false
338
+ }
339
+
340
+ /** Panics unless `value` lies in `[low, high]`: a cap out of range is the program's mistake. */
341
+ const http3CheckCap = (what: string, value: i32, low: i32, high: i32): void => {
342
+ if (value < low || value > high) {
343
+ panic(`Http3Connection: ${what} of ${value}, outside ${low} to ${high}`)
344
+ }
345
+ }
346
+
347
+ /** Empties `bytes`, keeping its storage for the next fill. */
348
+ const http3Empty = (bytes: u8[]): void => {
349
+ while (toI32(bytes.length) > 0) {
350
+ bytes.pop()
351
+ }
352
+ }
353
+
354
+ /** Empties `list`, keeping its storage for the next fill. */
355
+ const http3EmptyList = (list: u8[][]): void => {
356
+ while (toI32(list.length) > 0) {
357
+ list.pop()
358
+ }
359
+ }
360
+
361
+ /** Whether `id` is a stream the client opened carrying data both ways: a request stream. */
362
+ const http3IsRequest = (id: i64): boolean => (id & 3) === 0
363
+
364
+ /** The smaller of two `i64`s. */
365
+ const http3Min = (a: i64, b: i64): i64 => (a < b ? a : b)
366
+
367
+ /**
368
+ * One HTTP/3 connection, server side, over `quic`. The fields after the
369
+ * buffers are the last event's; the module comment says which event sets
370
+ * which.
371
+ */
372
+ export class Http3Connection {
373
+ // Pointers and 64-bit fields first, then 32-bit ones, then flags: the
374
+ // order the layout packs without padding.
375
+ config: Http3Config
376
+ quic: QuicConnection
377
+ decoder: QpackDecoder
378
+ encoder: QpackEncoder
379
+ /** The last request's or trailers' fields, valid until the next of either. */
380
+ fields: HttpFields
381
+ /** The client's SETTINGS. */
382
+ peer: Http3Settings
383
+ header: Http3FrameHeader
384
+ /** H3_DATA's bytes, and where a payload being skipped is read to. */
385
+ data: u8[]
386
+ /** A HEADERS payload read whole, for the decoder. */
387
+ section: u8[]
388
+ /** A control frame's payload, read whole. */
389
+ control: u8[]
390
+ /** Frame headers, SETTINGS and GOAWAY on their way out. */
391
+ scratch: u8[]
392
+ /** Zeros, the payload of a filler frame. */
393
+ zeros: u8[]
394
+ /** A response's field section, encoded, emptied and reused. */
395
+ encoded: u8[]
396
+ /** Copies of the decoded names and values, in arrays by size class (`copyOf`), and the lists handed to `fields`. */
397
+ pools: u8[][][]
398
+ poolUsed: i32[]
399
+ nameList: u8[][]
400
+ valueList: u8[][]
401
+ statusName: u8[]
402
+ statusValue: u8[]
403
+ /** The SETTINGS this side sends. */
404
+ settingIds: i64[]
405
+ settingValues: i64[]
406
+ /** Per QUIC stream slot: the stream the state is for, or -1. */
407
+ slotId: i64[]
408
+ /** The frame being read: its type, and what is left of its payload (-1 between frames). */
409
+ frameType: i64[]
410
+ frameLeft: i64[]
411
+ /** DATA bytes received on the request, and its `content-length` or -1. */
412
+ dataTotal: i64[]
413
+ contentLength: i64[]
414
+ /** Bytes still owed on the stream: a filler's payload, and the DATA frame's payload the program is writing. */
415
+ fillLeft: i64[]
416
+ dataLeft: i64[]
417
+ phase: i32[]
418
+ flags: i32[]
419
+ /** A frame header (or a stream type) being read, `headLen[k]` bytes of `head[k * 16 ..]`. */
420
+ headLen: i32[]
421
+ head: u8[]
422
+ /** Frame-header bytes owed before anything else, `pendLen[k]` of `pend[k * 16 + pendAt[k] ..]`. */
423
+ pendAt: i32[]
424
+ pendLen: i32[]
425
+ pend: u8[]
426
+ /**
427
+ * Streams `next` steps again before asking QUIC for news, a ring of
428
+ * `againCount` from `againHead`: WebTransport streams the program let go,
429
+ * and requests that waited for the client's SETTINGS. And those waiting,
430
+ * `waitCount` of them. A stream is on each at most once (its flags say),
431
+ * so neither needs more room than there are stream slots.
432
+ */
433
+ again: i64[]
434
+ waiting: i64[]
435
+ /** This side's streams, and the client's critical ones; -1 until there. */
436
+ ownControl: i64 = -1
437
+ ownEncoder: i64 = -1
438
+ ownDecoder: i64 = -1
439
+ peerControl: i64 = -1
440
+ peerEncoder: i64 = -1
441
+ peerDecoder: i64 = -1
442
+ /** The stream `next` is reading, or -1. */
443
+ active: i64 = -1
444
+ /** The ID this side's GOAWAY named, or -1; the client's last GOAWAY push ID, and its MAX_PUSH_ID. */
445
+ goawayId: i64 = -1
446
+ peerGoaway: i64 = -1
447
+ maxPushId: i64 = -1
448
+ /** The last event's stream. */
449
+ stream: i64 = -1
450
+ /** H3_RESET's and H3_ERROR's code. */
451
+ errorCode: i64 = 0
452
+ /** H3_GOAWAY's push ID. */
453
+ lastStreamId: i64 = 0
454
+ /** H3_STREAM's session ID. */
455
+ session: i64 = -1
456
+ /** The QUIC stream slot of the last event's stream: still right when the read that made the event freed it. */
457
+ slot: i32 = -1
458
+ /** H3_DATA's window onto `data`. */
459
+ dataStart: i32 = 0
460
+ dataLength: i32 = 0
461
+ /** How much of `control` is filled. */
462
+ controlFill: i32 = 0
463
+ state: i32 = 0
464
+ /** Requests open: taken and not yet finished both ways. */
465
+ live: i32 = 0
466
+ /** Counters for a log or a test, each saturating: requests refused after GOAWAY, answered 431, and reset for a rule. */
467
+ rejected: i32 = 0
468
+ tooLarge: i32 = 0
469
+ streamErrors: i32 = 0
470
+ againHead: i32 = 0
471
+ againCount: i32 = 0
472
+ waitCount: i32 = 0
473
+ /** How many times `restart` ran: a layer above that keeps state per connection compares it to know when to forget its own. */
474
+ generation: i32 = 0
475
+ /** Whether the client's SETTINGS have arrived. */
476
+ settingsSeen: boolean = false
477
+ /** Whether H3_RESET came from the client. */
478
+ resetByPeer: boolean = false
479
+ /** Whether extended CONNECT is on, and WebTransport. */
480
+ connect: boolean = false
481
+ webtransport: boolean = false
482
+
483
+ /**
484
+ * A connection under `config` over `quic`, whose configuration must let a
485
+ * HEADERS frame of `maxFieldSectionSize` fit half a stream's buffer, and
486
+ * one on every request stream fit half the connection's window, and let
487
+ * the client open the three unidirectional streams it needs and this side
488
+ * open its own three (§6.2). A cap out of range panics.
489
+ */
490
+ constructor(config: Http3Config, quic: QuicConnection) {
491
+ const buffer: i32 = quic.streams.bufferSize
492
+ // A HEADERS frame is read once all of it is in the stream's buffer, and QUIC
493
+ // gives more credit only once half a buffer has been read, so the frame
494
+ // may be at most half a buffer, its header aside, or it could stall.
495
+ http3CheckCap("maxFieldSectionSize", config.maxFieldSectionSize, 1, buffer / 2 - H3_HEAD)
496
+ // The connection's credit too: every request stream may hold a HEADERS
497
+ // frame not yet whole, and QUIC raises MAX_DATA once half the window is read.
498
+ const held: i64 = quic.streams.maxBidi * toI64(config.maxFieldSectionSize + H3_HEAD) * toI64(2)
499
+ if (quic.streams.windowData < held) {
500
+ panic(
501
+ `Http3Connection: a connection window of ${quic.streams.windowData}, under the ${held} that ${quic.streams.maxBidi} request streams' field sections need`
502
+ )
503
+ }
504
+ http3CheckCap("bodyChunk", config.bodyChunk, 1, H3_MAX_CHUNK)
505
+ http3CheckCap("writeChunk", config.writeChunk, 1, H3_MAX_CHUNK)
506
+ http3CheckCap("the QUIC unidirectional stream limit", toI32(quic.streams.maxUni), 3, 1024)
507
+ http3CheckCap("the QUIC local stream limit", toI32(quic.streams.localStreams), 3, 1024)
508
+ http3CheckCap("webtransportSessions", config.webtransportSessions, 0, H3_MAX_SESSIONS)
509
+ // RFC 9297 §2.1.1: QUIC datagrams carry HTTP datagrams, which only WebTransport uses here.
510
+ const datagrams: boolean = quic.datagramsIn.capacity() > 0
511
+ if (config.webtransportSessions > 0 && !datagrams) {
512
+ panic("Http3Connection: WebTransport needs QUIC datagrams, and the QUIC maxDatagramFrameSize is 0")
513
+ }
514
+ if (config.webtransportSessions === 0 && datagrams) {
515
+ panic("Http3Connection: QUIC datagrams advertised with WebTransport off (webtransportSessions 0)")
516
+ }
517
+ this.webtransport = config.webtransportSessions > 0
518
+ this.connect = config.extendedConnect || this.webtransport
519
+ this.config = config
520
+ this.quic = quic
521
+ this.decoder = new QpackDecoder(config.maxFieldSectionSize)
522
+ this.encoder = new QpackEncoder()
523
+ this.fields = new HttpFields()
524
+ this.peer = new Http3Settings()
525
+ this.header = new Http3FrameHeader()
526
+ this.data = new Array<u8>(config.bodyChunk)
527
+ this.section = new Array<u8>(config.maxFieldSectionSize)
528
+ this.control = new Array<u8>(H3_CONTROL_FRAME_MAX)
529
+ this.scratch = new Array<u8>(64)
530
+ this.zeros = new Array<u8>(256)
531
+ this.encoded = []
532
+ this.pools = []
533
+ for (let j: i32 = 0; j < H3_FIELD_CLASSES; j++) {
534
+ const pool: u8[][] = []
535
+ this.pools.push(pool)
536
+ }
537
+ this.poolUsed = new Array<i32>(H3_FIELD_CLASSES)
538
+ this.nameList = []
539
+ this.valueList = []
540
+ this.statusName = httpFieldBytes(":status")
541
+ this.statusValue = [48, 48, 48]
542
+ this.settingIds = [
543
+ H3_SETTINGS_QPACK_MAX_TABLE_CAPACITY,
544
+ H3_SETTINGS_QPACK_BLOCKED_STREAMS,
545
+ H3_SETTINGS_MAX_FIELD_SECTION_SIZE,
546
+ ]
547
+ this.settingValues = [toI64(0), toI64(0), toI64(config.maxFieldSectionSize)]
548
+ if (this.connect) {
549
+ this.settingIds.push(H3_SETTINGS_ENABLE_CONNECT_PROTOCOL)
550
+ this.settingValues.push(toI64(1))
551
+ }
552
+ if (this.webtransport) {
553
+ this.settingIds.push(H3_SETTINGS_H3_DATAGRAM)
554
+ this.settingValues.push(toI64(1))
555
+ this.settingIds.push(H3_SETTINGS_ENABLE_WEBTRANSPORT)
556
+ this.settingValues.push(toI64(1))
557
+ this.settingIds.push(H3_SETTINGS_WEBTRANSPORT_MAX_SESSIONS)
558
+ this.settingValues.push(toI64(config.webtransportSessions))
559
+ }
560
+ const n: i32 = toI32(quic.streams.slots.length)
561
+ this.slotId = new Array<i64>(n)
562
+ this.frameType = new Array<i64>(n)
563
+ this.frameLeft = new Array<i64>(n)
564
+ this.dataTotal = new Array<i64>(n)
565
+ this.contentLength = new Array<i64>(n)
566
+ this.fillLeft = new Array<i64>(n)
567
+ this.dataLeft = new Array<i64>(n)
568
+ this.phase = new Array<i32>(n)
569
+ this.flags = new Array<i32>(n)
570
+ this.headLen = new Array<i32>(n)
571
+ this.head = new Array<u8>(n * H3_HEAD)
572
+ this.pendAt = new Array<i32>(n)
573
+ this.pendLen = new Array<i32>(n)
574
+ this.pend = new Array<u8>(n * H3_HEAD)
575
+ this.again = new Array<i64>(n > 0 ? n : H3_ONE)
576
+ this.waiting = new Array<i64>(n > 0 ? n : H3_ONE)
577
+ this.restart()
578
+ }
579
+
580
+ /**
581
+ * Puts the connection back as the constructor left it, for the QUIC
582
+ * connection's next peer (call it after `quic.reset`): no stream known,
583
+ * QPACK's state and the client's SETTINGS forgotten. It allocates nothing.
584
+ */
585
+ restart(): void {
586
+ this.slotId.fill(H3_NONE)
587
+ this.flags.fill(H3_ZERO)
588
+ this.headLen.fill(H3_ZERO)
589
+ this.pendLen.fill(H3_ZERO)
590
+ this.fillLeft.fill(toI64(0))
591
+ this.dataLeft.fill(toI64(0))
592
+ this.decoder.failed = QPACK_OK
593
+ this.decoder.reason = 0
594
+ this.decoder.capacityInstructions = 0
595
+ this.encoder.failed = QPACK_OK
596
+ this.encoder.reason = 0
597
+ this.encoder.cancellations = 0
598
+ this.encoder.lastCancelled = -1
599
+ this.encoder.pendingId = 0
600
+ this.encoder.pendingShift = -1
601
+ this.fields.clear()
602
+ this.peer.reset()
603
+ this.ownControl = -1
604
+ this.ownEncoder = -1
605
+ this.ownDecoder = -1
606
+ this.peerControl = -1
607
+ this.peerEncoder = -1
608
+ this.peerDecoder = -1
609
+ this.active = -1
610
+ this.goawayId = -1
611
+ this.peerGoaway = -1
612
+ this.maxPushId = -1
613
+ this.stream = -1
614
+ this.errorCode = 0
615
+ this.lastStreamId = 0
616
+ this.session = -1
617
+ this.slot = -1
618
+ this.againHead = 0
619
+ this.againCount = 0
620
+ this.waitCount = 0
621
+ this.generation = this.generation < 2147483647 ? this.generation + 1 : H3_ONE
622
+ this.dataStart = 0
623
+ this.dataLength = 0
624
+ this.controlFill = 0
625
+ this.state = H3_STATE_OPEN
626
+ this.live = 0
627
+ this.rejected = 0
628
+ this.tooLarge = 0
629
+ this.streamErrors = 0
630
+ this.settingsSeen = false
631
+ this.resetByPeer = false
632
+ }
633
+
634
+ // ---- The connection -----------------------------------------------------------
635
+
636
+ /** Closes the connection with `code` (a connection error) and answers H3_ERROR. */
637
+ fail(code: i64): i32 {
638
+ if (this.state !== H3_STATE_FAILED) {
639
+ this.state = H3_STATE_FAILED
640
+ this.errorCode = code
641
+ this.quic.close(code)
642
+ }
643
+ return H3_ERROR
644
+ }
645
+
646
+ /**
647
+ * Whether this side sent GOAWAY and every request it took has finished
648
+ * both ways, every byte of each response acknowledged: the carrier may
649
+ * close the connection.
650
+ */
651
+ isDone(): boolean {
652
+ const streams = this.quic.streams
653
+ return this.goawayId >= 0 && this.live === 0 && streams.peerBidiClosed >= streams.peerBidiOpened
654
+ }
655
+
656
+ /** Writes `buf[0 .. n)` to this side's new stream `id` whole, as its first bytes; answers whether it took them. */
657
+ writeNew(id: i64, buf: u8[], n: i32): boolean {
658
+ return n > 0 && this.quic.streamWrite(id, buf, H3_ZERO, n, false) === n
659
+ }
660
+
661
+ /**
662
+ * Opens this side's control stream with its type and SETTINGS, and its
663
+ * QPACK encoder and decoder streams with their types (§6.2.1, RFC 9204
664
+ * §4.2). A stream the client's MAX_STREAMS does not allow yet is opened on
665
+ * a later call.
666
+ */
667
+ start(): void {
668
+ const end: i32 = toI32(this.scratch.length)
669
+ if (this.ownControl < 0) {
670
+ const id: i64 = this.quic.openStream(false)
671
+ if (id < 0) {
672
+ return
673
+ }
674
+ this.ownControl = id
675
+ const p: i32 = h3PutVarint(this.scratch, H3_ZERO, end, H3_STREAM_CONTROL)
676
+ const q: i32 = h3PutSettings(this.scratch, p, end, this.settingIds, this.settingValues)
677
+ if (!this.writeNew(id, this.scratch, q)) {
678
+ this.fail(H3_INTERNAL_ERROR)
679
+ return
680
+ }
681
+ }
682
+ if (this.ownEncoder < 0) {
683
+ const id: i64 = this.quic.openStream(false)
684
+ if (id < 0) {
685
+ return
686
+ }
687
+ this.ownEncoder = id
688
+ if (
689
+ !this.writeNew(id, this.scratch, h3PutVarint(this.scratch, H3_ZERO, end, H3_STREAM_QPACK_ENCODER))
690
+ ) {
691
+ this.fail(H3_INTERNAL_ERROR)
692
+ return
693
+ }
694
+ }
695
+ if (this.ownDecoder < 0) {
696
+ const id: i64 = this.quic.openStream(false)
697
+ if (id < 0) {
698
+ return
699
+ }
700
+ this.ownDecoder = id
701
+ if (
702
+ !this.writeNew(id, this.scratch, h3PutVarint(this.scratch, H3_ZERO, end, H3_STREAM_QPACK_DECODER))
703
+ ) {
704
+ this.fail(H3_INTERNAL_ERROR)
705
+ return
706
+ }
707
+ }
708
+ }
709
+
710
+ /**
711
+ * Reads what the QUIC connection has for the program and answers the next
712
+ * event (see the module comment). Call it after every datagram the QUIC
713
+ * connection takes, until it answers H3_NEED_MORE or H3_ERROR.
714
+ */
715
+ next(): i32 {
716
+ if (this.state === H3_STATE_FAILED) {
717
+ return H3_ERROR
718
+ }
719
+ if (this.quic.closed()) {
720
+ this.state = H3_STATE_FAILED
721
+ this.errorCode = this.quic.error
722
+ return H3_ERROR
723
+ }
724
+ if (this.quic.state !== QUIC_STATE_CONNECTED) {
725
+ return H3_NEED_MORE
726
+ }
727
+ if (this.ownDecoder < 0) {
728
+ // RFC 9114 §3.1: HTTP/3 only where the handshake chose it.
729
+ if (this.quic.alpn !== H3_ALPN) {
730
+ this.quic.fail(H3_NO_APPLICATION_PROTOCOL, toI64(0))
731
+ return this.fail(H3_NO_APPLICATION_PROTOCOL)
732
+ }
733
+ this.start()
734
+ }
735
+ while (this.state !== H3_STATE_FAILED) {
736
+ if (this.active < 0) {
737
+ this.active = this.againCount > 0 ? this.takeAgain() : this.quic.nextStreamEvent()
738
+ if (this.active < 0) {
739
+ return H3_NEED_MORE
740
+ }
741
+ }
742
+ const event: i32 = this.step(this.active)
743
+ if (event !== H3_NEED_MORE) {
744
+ return event
745
+ }
746
+ this.active = -1
747
+ }
748
+ return H3_ERROR
749
+ }
750
+
751
+ /** The oldest stream on the `again` ring, taken off it. */
752
+ takeAgain(): i64 {
753
+ const size: i32 = toI32(this.again.length)
754
+ const id: i64 = this.again[this.againHead]
755
+ this.againHead = (this.againHead + 1) % size
756
+ this.againCount = this.againCount - 1
757
+ const k: i32 = this.quic.streams.slotOf(id)
758
+ if (k >= 0 && k < toI32(this.flags.length) && this.slotId[k] === id) {
759
+ this.flags[k] = this.flags[k] & ~H3_FLAG_AGAIN
760
+ }
761
+ return id
762
+ }
763
+
764
+ /** Puts stream `id`, slot `k`, on the `again` ring, unless it is there. */
765
+ stepAgain(k: i32, id: i64): void {
766
+ const size: i32 = toI32(this.again.length)
767
+ if ((this.flags[k] & H3_FLAG_AGAIN) !== 0 || this.againCount >= size) {
768
+ return
769
+ }
770
+ this.again[(this.againHead + this.againCount) % size] = id
771
+ this.againCount = this.againCount + 1
772
+ this.flags[k] = this.flags[k] | H3_FLAG_AGAIN
773
+ }
774
+
775
+ /** The next event stream `id` has, or H3_NEED_MORE when it has nothing more to say now. */
776
+ step(id: i64): i32 {
777
+ const k: i32 = this.quic.streams.slotOf(id)
778
+ if (k < 0 || k >= toI32(this.slotId.length)) {
779
+ return H3_NEED_MORE
780
+ }
781
+ this.slot = k
782
+ if (this.slotId[k] === id && (this.flags[k] & H3_FLAG_WT) !== 0) {
783
+ return this.wtStep(k, id)
784
+ }
785
+ const kind: i64 = id & 3
786
+ if (kind === 3) {
787
+ return this.ownStream(k)
788
+ }
789
+ if (kind === 1) {
790
+ return H3_NEED_MORE
791
+ }
792
+ if (this.slotId[k] !== id) {
793
+ this.open(k, id)
794
+ }
795
+ const phase: i32 = this.phase[k]
796
+ if (phase >= H3_PHASE_UNI_TYPE) {
797
+ return this.uniStep(k, id)
798
+ }
799
+ return this.requestStep(k, id)
800
+ }
801
+
802
+ /** The state of a stream the client just opened in slot `k`. */
803
+ open(k: i32, id: i64): void {
804
+ this.slotId[k] = id
805
+ this.frameType[k] = H3_SKIP_TYPE
806
+ this.frameLeft[k] = -1
807
+ this.dataTotal[k] = 0
808
+ this.contentLength[k] = -1
809
+ this.fillLeft[k] = 0
810
+ this.dataLeft[k] = 0
811
+ this.flags[k] = 0
812
+ this.headLen[k] = 0
813
+ this.pendAt[k] = 0
814
+ this.pendLen[k] = 0
815
+ if (!http3IsRequest(id)) {
816
+ this.phase[k] = H3_PHASE_UNI_TYPE
817
+ return
818
+ }
819
+ this.phase[k] = H3_PHASE_HEADERS
820
+ if (this.goawayId >= 0 && id >= this.goawayId) {
821
+ if (this.webtransport) {
822
+ // A session's new stream may still come after GOAWAY: its first bytes decide.
823
+ this.flags[k] = H3_FLAG_LATE
824
+ return
825
+ }
826
+ this.reject(k, id)
827
+ return
828
+ }
829
+ this.flags[k] = H3_FLAG_COUNTED
830
+ this.live = this.live + 1
831
+ }
832
+
833
+ /** §5.2: a request past the GOAWAY this side sent is refused, both ways, unprocessed. */
834
+ reject(k: i32, id: i64): void {
835
+ this.quic.streamReset(id, H3_REQUEST_REJECTED)
836
+ this.quic.streamStopSending(id, H3_REQUEST_REJECTED)
837
+ this.flags[k] = H3_FLAG_SEND_DONE
838
+ this.phase[k] = H3_PHASE_DISCARD
839
+ this.rejected = h3Count(this.rejected)
840
+ }
841
+
842
+ /** One of this side's own streams has news: the client may not stop it (§6.2.1, RFC 9204 §4.2). */
843
+ ownStream(k: i32): i32 {
844
+ const stream: QuicStream = this.quic.streams.slots[k]
845
+ if (stream.stopCode >= 0) {
846
+ return this.fail(H3_CLOSED_CRITICAL_STREAM)
847
+ }
848
+ return H3_NEED_MORE
849
+ }
850
+
851
+ /** Lets a slot's request count as finished once both of its sides are done. */
852
+ settle(k: i32): void {
853
+ const done: i32 = H3_FLAG_SEND_DONE | H3_FLAG_RECV_DONE | H3_FLAG_COUNTED
854
+ if ((this.flags[k] & done) === done) {
855
+ this.flags[k] = this.flags[k] & ~H3_FLAG_COUNTED
856
+ this.live = this.live - 1
857
+ }
858
+ }
859
+
860
+ /** Marks flags on slot `k` and settles it. */
861
+ mark(k: i32, bits: i32): void {
862
+ this.flags[k] = this.flags[k] | bits
863
+ this.settle(k)
864
+ }
865
+
866
+ /**
867
+ * Reads a frame header of stream `id` into slot `k`'s `head`, a varint at
868
+ * a time — its first byte, which says its size, then the rest — so a
869
+ * header split across STREAM frames costs nothing to keep. Answers 1 with
870
+ * `header` filled, 0 when more is needed, or the `QUIC_STREAM_*` answer
871
+ * that ended the read.
872
+ */
873
+ readHeader(k: i32, id: i64): i32 {
874
+ const base: i32 = k * H3_HEAD
875
+ while (this.headLen[k] < H3_HEAD) {
876
+ const have: i32 = this.headLen[k]
877
+ // The type's size from its first byte, then the length's from its.
878
+ let need: i32 = 1
879
+ if (have > 0) {
880
+ const typeSize: i32 = h3VarintLength(this.head, base)
881
+ need = have < typeSize ? typeSize - have : 1
882
+ if (have > typeSize) {
883
+ need = typeSize + h3VarintLength(this.head, base + typeSize) - have
884
+ }
885
+ }
886
+ const n: i32 = this.quic.streamRead(id, this.head, base + have, need)
887
+ if (n <= 0) {
888
+ return n
889
+ }
890
+ this.headLen[k] = have + n
891
+ if (h3ReadFrameHeader(this.header, this.head, base, this.headLen[k]) > 0) {
892
+ this.headLen[k] = 0
893
+ return 1
894
+ }
895
+ }
896
+ return H3_ZERO
897
+ }
898
+
899
+ // ---- Unidirectional streams ------------------------------------------------------
900
+
901
+ /** The next event of the client's unidirectional stream `id` in slot `k`. */
902
+ uniStep(k: i32, id: i64): i32 {
903
+ while (this.state !== H3_STATE_FAILED) {
904
+ const phase: i32 = this.phase[k]
905
+ if (phase === H3_PHASE_UNI_TYPE) {
906
+ const base: i32 = k * H3_HEAD
907
+ const n: i32 = this.quic.streamRead(id, this.head, base + this.headLen[k], H3_ONE)
908
+ if (n !== 1) {
909
+ // §6.2: a stream may end or be reset before its type; nothing is owed.
910
+ if (n < 0) {
911
+ this.phase[k] = H3_PHASE_DONE
912
+ }
913
+ return H3_NEED_MORE
914
+ }
915
+ this.headLen[k] = this.headLen[k] + 1
916
+ const type: i64 = h3ReadVarint(this.head, base, base + this.headLen[k])
917
+ if (type >= 0) {
918
+ this.headLen[k] = 0
919
+ const event: i32 = this.typed(k, id, type)
920
+ if (event !== H3_NEED_MORE) {
921
+ return event
922
+ }
923
+ }
924
+ } else if (phase === H3_PHASE_CONTROL) {
925
+ return this.controlStep(k, id)
926
+ } else if (phase === H3_PHASE_ENCODER || phase === H3_PHASE_DECODER) {
927
+ const n: i32 = this.quic.streamRead(id, this.data, H3_ZERO, toI32(this.data.length))
928
+ if (n === 0) {
929
+ return H3_NEED_MORE
930
+ }
931
+ if (n < 0) {
932
+ return this.fail(H3_CLOSED_CRITICAL_STREAM)
933
+ }
934
+ const result: i64 =
935
+ phase === H3_PHASE_ENCODER
936
+ ? this.decoder.receiveEncoderStream(this.data, H3_ZERO, n)
937
+ : this.encoder.receiveDecoderStream(this.data, H3_ZERO, n)
938
+ if (result !== QPACK_OK) {
939
+ return this.fail(result)
940
+ }
941
+ } else if (phase === H3_PHASE_DISCARD) {
942
+ return this.discard(k, id)
943
+ } else {
944
+ return H3_NEED_MORE
945
+ }
946
+ }
947
+ return H3_ERROR
948
+ }
949
+
950
+ /** The client's unidirectional stream `id` turned out to be of `type` (§6.2). */
951
+ typed(k: i32, id: i64, type: i64): i32 {
952
+ if (type === H3_STREAM_CONTROL) {
953
+ if (this.peerControl >= 0) {
954
+ return this.fail(H3_STREAM_CREATION_ERROR)
955
+ }
956
+ this.peerControl = id
957
+ this.phase[k] = H3_PHASE_CONTROL
958
+ return H3_NEED_MORE
959
+ }
960
+ if (type === H3_STREAM_QPACK_ENCODER) {
961
+ if (this.peerEncoder >= 0) {
962
+ return this.fail(H3_STREAM_CREATION_ERROR)
963
+ }
964
+ this.peerEncoder = id
965
+ this.phase[k] = H3_PHASE_ENCODER
966
+ return H3_NEED_MORE
967
+ }
968
+ if (type === H3_STREAM_QPACK_DECODER) {
969
+ if (this.peerDecoder >= 0) {
970
+ return this.fail(H3_STREAM_CREATION_ERROR)
971
+ }
972
+ this.peerDecoder = id
973
+ this.phase[k] = H3_PHASE_DECODER
974
+ return H3_NEED_MORE
975
+ }
976
+ if (type === H3_STREAM_PUSH) {
977
+ // §6.2.2: only a server pushes.
978
+ return this.fail(H3_STREAM_CREATION_ERROR)
979
+ }
980
+ if (type === H3_STREAM_WEBTRANSPORT && this.webtransport) {
981
+ // draft-02 §4.1: the session ID follows the type. The stream has no side for this one to write.
982
+ this.flags[k] = H3_FLAG_WT | H3_FLAG_SEND_DONE
983
+ this.phase[k] = H3_PHASE_WT_SESSION
984
+ return this.wtStep(k, id)
985
+ }
986
+ // §6.2: a type this side does not know is read and dropped, and its sender asked to stop.
987
+ this.quic.streamStopSending(id, H3_STREAM_CREATION_ERROR)
988
+ this.phase[k] = H3_PHASE_DISCARD
989
+ return H3_NEED_MORE
990
+ }
991
+
992
+ /** Frames on the client's control stream (§6.2.1, §7.2). */
993
+ controlStep(k: i32, id: i64): i32 {
994
+ while (this.state !== H3_STATE_FAILED) {
995
+ if (this.frameLeft[k] < 0) {
996
+ const got: i32 = this.readHeader(k, id)
997
+ if (got === 0) {
998
+ return H3_NEED_MORE
999
+ }
1000
+ if (got < 0) {
1001
+ return this.fail(H3_CLOSED_CRITICAL_STREAM)
1002
+ }
1003
+ const type: i64 = this.header.type
1004
+ const length: i64 = this.header.length
1005
+ if (!this.settingsSeen && type !== H3_FRAME_SETTINGS) {
1006
+ return this.fail(H3_MISSING_SETTINGS)
1007
+ }
1008
+ if (
1009
+ type === H3_FRAME_DATA ||
1010
+ type === H3_FRAME_HEADERS ||
1011
+ type === H3_FRAME_PUSH_PROMISE ||
1012
+ h3ReservedHttp2Frame(type) ||
1013
+ (type === H3_FRAME_SETTINGS && this.settingsSeen)
1014
+ ) {
1015
+ return this.fail(H3_FRAME_UNEXPECTED)
1016
+ }
1017
+ const whole: boolean =
1018
+ type === H3_FRAME_SETTINGS ||
1019
+ type === H3_FRAME_GOAWAY ||
1020
+ type === H3_FRAME_MAX_PUSH_ID ||
1021
+ type === H3_FRAME_CANCEL_PUSH
1022
+ if (whole && length > toI64(H3_CONTROL_FRAME_MAX)) {
1023
+ return this.fail(type === H3_FRAME_SETTINGS ? H3_EXCESSIVE_LOAD : H3_FRAME_ERROR)
1024
+ }
1025
+ this.frameType[k] = whole ? type : H3_SKIP_TYPE
1026
+ this.frameLeft[k] = length
1027
+ this.controlFill = 0
1028
+ }
1029
+ if (this.frameLeft[k] > 0) {
1030
+ const into: u8[] = this.frameType[k] === H3_SKIP_TYPE ? this.data : this.control
1031
+ const at: i32 = this.frameType[k] === H3_SKIP_TYPE ? H3_ZERO : this.controlFill
1032
+ const want: i32 = toI32(http3Min(this.frameLeft[k], toI64(toI32(into.length) - at)))
1033
+ const n: i32 = this.quic.streamRead(id, into, at, want)
1034
+ if (n === 0) {
1035
+ return H3_NEED_MORE
1036
+ }
1037
+ if (n < 0) {
1038
+ return this.fail(H3_CLOSED_CRITICAL_STREAM)
1039
+ }
1040
+ this.frameLeft[k] = this.frameLeft[k] - toI64(n)
1041
+ if (this.frameType[k] !== H3_SKIP_TYPE) {
1042
+ this.controlFill = this.controlFill + n
1043
+ }
1044
+ }
1045
+ if (this.frameLeft[k] === 0) {
1046
+ this.frameLeft[k] = -1
1047
+ const event: i32 = this.controlFrame(this.frameType[k])
1048
+ if (event !== H3_NEED_MORE) {
1049
+ return event
1050
+ }
1051
+ }
1052
+ }
1053
+ return H3_ERROR
1054
+ }
1055
+
1056
+ /** Acts on a whole control frame of `type` in `control[0 .. controlFill)`. */
1057
+ controlFrame(type: i64): i32 {
1058
+ if (type === H3_FRAME_SETTINGS) {
1059
+ const result: i64 = h3ReadSettings(this.peer, this.control, H3_ZERO, this.controlFill)
1060
+ if (result !== H3_SETTINGS_OK) {
1061
+ return this.fail(result)
1062
+ }
1063
+ // RFC 9220 §5 and RFC 9297 §2.1.1: each is 0 or 1.
1064
+ if (this.peer.enableConnectProtocol > 1 || this.peer.h3Datagram > 1) {
1065
+ return this.fail(H3_SETTINGS_ERROR)
1066
+ }
1067
+ this.settingsSeen = true
1068
+ // The requests that waited for these SETTINGS are stepped again.
1069
+ for (let j: i32 = 0; j < this.waitCount && j < toI32(this.waiting.length); j++) {
1070
+ const id: i64 = this.waiting[j]
1071
+ const w: i32 = this.quic.streams.slotOf(id)
1072
+ if (w >= 0 && w < toI32(this.flags.length) && this.slotId[w] === id) {
1073
+ this.flags[w] = this.flags[w] & ~H3_FLAG_WAITING
1074
+ this.stepAgain(w, id)
1075
+ }
1076
+ }
1077
+ this.waitCount = 0
1078
+ return H3_NEED_MORE
1079
+ }
1080
+ if (type === H3_SKIP_TYPE) {
1081
+ return H3_NEED_MORE
1082
+ }
1083
+ const value: i64 = h3ReadIdPayload(this.control, H3_ZERO, this.controlFill)
1084
+ if (value < 0) {
1085
+ return this.fail(H3_FRAME_ERROR)
1086
+ }
1087
+ if (type === H3_FRAME_GOAWAY) {
1088
+ // §5.2: a client's GOAWAY names a push ID, which may only fall.
1089
+ if (this.peerGoaway >= 0 && value > this.peerGoaway) {
1090
+ return this.fail(H3_ID_ERROR)
1091
+ }
1092
+ this.peerGoaway = value
1093
+ this.lastStreamId = value
1094
+ return H3_GOAWAY
1095
+ }
1096
+ if (type === H3_FRAME_MAX_PUSH_ID) {
1097
+ // §7.2.7: it may not fall. This side never pushes, so that is all it means.
1098
+ if (value < this.maxPushId) {
1099
+ return this.fail(H3_ID_ERROR)
1100
+ }
1101
+ this.maxPushId = value
1102
+ return H3_NEED_MORE
1103
+ }
1104
+ // CANCEL_PUSH (§7.2.3): no push was ever promised, so only one past the
1105
+ // push IDs allowed is an error; any other is a no-op.
1106
+ return value > this.maxPushId ? this.fail(H3_ID_ERROR) : H3_NEED_MORE
1107
+ }
1108
+
1109
+ /** Reads and drops what is left of stream `id`, slot `k`, to its end or reset. */
1110
+ discard(k: i32, id: i64): i32 {
1111
+ const n: i32 = this.drop(id)
1112
+ if (n < 0) {
1113
+ this.phase[k] = H3_PHASE_DONE
1114
+ this.mark(k, H3_FLAG_RECV_DONE)
1115
+ }
1116
+ return this.writable(k, id)
1117
+ }
1118
+
1119
+ /** Reads stream `id` into `data` until nothing more is there; answers 0, or the `QUIC_STREAM_*` that ended it. */
1120
+ drop(id: i64): i32 {
1121
+ let n: i32 = this.quic.streamRead(id, this.data, H3_ZERO, toI32(this.data.length))
1122
+ while (n > 0) {
1123
+ n = this.quic.streamRead(id, this.data, H3_ZERO, toI32(this.data.length))
1124
+ }
1125
+ return n
1126
+ }
1127
+
1128
+ // ---- WebTransport streams ----------------------------------------------------------
1129
+
1130
+ /**
1131
+ * Stream `id`, slot `k`, named session `session` in its first bytes: it is
1132
+ * held, unread, and the program told with H3_STREAM. A session ID that is
1133
+ * not a client-initiated bidirectional stream is H3_ID_ERROR (draft-02 §4).
1134
+ */
1135
+ held(k: i32, id: i64, session: i64): i32 {
1136
+ if (!http3IsRequest(session)) {
1137
+ return this.fail(H3_ID_ERROR)
1138
+ }
1139
+ if (this.goawayId >= 0 && session >= this.goawayId) {
1140
+ // A session at or past this side's GOAWAY can never come (its CONNECT is refused), so its stream is gone.
1141
+ this.abandon(k, id, H3_WT_SESSION_GONE)
1142
+ this.stopReading(k, id, H3_WT_SESSION_GONE)
1143
+ this.rejected = h3Count(this.rejected)
1144
+ return H3_NEED_MORE
1145
+ }
1146
+ this.phase[k] = H3_PHASE_WT_HELD
1147
+ this.frameLeft[k] = -1
1148
+ this.stream = id
1149
+ this.session = session
1150
+ return H3_STREAM
1151
+ }
1152
+
1153
+ /**
1154
+ * The next event of WebTransport stream `id` in slot `k`: its session ID
1155
+ * (a unidirectional stream's), then its bytes as H3_DATA, H3_END at its
1156
+ * FIN and H3_RESET at the client's RESET_STREAM; H3_STOPPED once for the
1157
+ * client's STOP_SENDING; and H3_WRITABLE.
1158
+ */
1159
+ wtStep(k: i32, id: i64): i32 {
1160
+ const stream: QuicStream = this.quic.streams.slots[k]
1161
+ if (stream.stopCode >= 0 && (this.flags[k] & H3_FLAG_SEND_DONE) === 0) {
1162
+ this.mark(k, H3_FLAG_SEND_DONE)
1163
+ this.stream = id
1164
+ this.errorCode = stream.stopCode
1165
+ return H3_STOPPED
1166
+ }
1167
+ const phase: i32 = this.phase[k]
1168
+ if (phase === H3_PHASE_WT_SESSION) {
1169
+ const base: i32 = k * H3_HEAD
1170
+ while (this.headLen[k] < 8) {
1171
+ const n: i32 = this.quic.streamRead(id, this.head, base + this.headLen[k], H3_ONE)
1172
+ if (n !== 1) {
1173
+ // Ended or reset before its session ID: nothing is owed (§6.2).
1174
+ if (n < 0) {
1175
+ this.phase[k] = H3_PHASE_DONE
1176
+ this.mark(k, H3_FLAG_RECV_DONE)
1177
+ }
1178
+ return H3_NEED_MORE
1179
+ }
1180
+ this.headLen[k] = this.headLen[k] + 1
1181
+ const session: i64 = h3ReadVarint(this.head, base, base + this.headLen[k])
1182
+ if (session >= 0) {
1183
+ this.headLen[k] = 0
1184
+ return this.held(k, id, session)
1185
+ }
1186
+ }
1187
+ return H3_NEED_MORE
1188
+ }
1189
+ if (phase === H3_PHASE_WT) {
1190
+ if ((this.flags[k] & H3_FLAG_RECV_DONE) === 0) {
1191
+ const resetCode: i64 = stream.resetCode
1192
+ const n: i32 = this.quic.streamRead(id, this.data, H3_ZERO, toI32(this.data.length))
1193
+ if (n > 0) {
1194
+ this.stream = id
1195
+ this.dataStart = 0
1196
+ this.dataLength = n
1197
+ return H3_DATA
1198
+ }
1199
+ if (n < 0) {
1200
+ this.phase[k] = H3_PHASE_DONE
1201
+ this.mark(k, H3_FLAG_RECV_DONE)
1202
+ if (n === QUIC_STREAM_END) {
1203
+ this.stream = id
1204
+ return H3_END
1205
+ }
1206
+ if (n === QUIC_STREAM_ERR_RESET) {
1207
+ return this.resetEvent(id, resetCode, true)
1208
+ }
1209
+ }
1210
+ }
1211
+ return this.writable(k, id)
1212
+ }
1213
+ if (phase === H3_PHASE_DISCARD) {
1214
+ return this.discard(k, id)
1215
+ }
1216
+ return phase === H3_PHASE_DONE ? this.writable(k, id) : H3_NEED_MORE
1217
+ }
1218
+
1219
+ /** The slot of WebTransport stream `id` when it is known here, else -1. */
1220
+ wtSlot(id: i64): i32 {
1221
+ if (this.state === H3_STATE_FAILED) {
1222
+ return -1
1223
+ }
1224
+ const k: i32 = this.quic.streams.slotOf(id)
1225
+ if (
1226
+ k < 0 ||
1227
+ k >= toI32(this.slotId.length) ||
1228
+ this.slotId[k] !== id ||
1229
+ (this.flags[k] & H3_FLAG_WT) === 0
1230
+ ) {
1231
+ return -1
1232
+ }
1233
+ return k
1234
+ }
1235
+
1236
+ /**
1237
+ * Lets a stream H3_STREAM held go: its bytes come as H3_DATA from the next
1238
+ * `next()`, and the program may write it. Answers 0, or H3_CLOSED for a
1239
+ * stream that is not one held.
1240
+ */
1241
+ acceptStream(id: i64): i32 {
1242
+ const k: i32 = this.wtSlot(id)
1243
+ if (k < 0 || this.phase[k] !== H3_PHASE_WT_HELD) {
1244
+ return H3_CLOSED
1245
+ }
1246
+ this.phase[k] = H3_PHASE_WT
1247
+ this.stepAgain(k, id)
1248
+ return 0
1249
+ }
1250
+
1251
+ /**
1252
+ * Refuses WebTransport stream `id` with `code`: its side here is reset,
1253
+ * the client's asked to stop, and what it sent read and dropped. Answers 0,
1254
+ * or H3_CLOSED for a stream that is not a WebTransport one.
1255
+ */
1256
+ refuseStream(id: i64, code: i64): i32 {
1257
+ const k: i32 = this.wtSlot(id)
1258
+ if (k < 0) {
1259
+ return H3_CLOSED
1260
+ }
1261
+ this.abandon(k, id, code)
1262
+ this.stopReading(k, id, code)
1263
+ return 0
1264
+ }
1265
+
1266
+ /** Asks the client to stop stream `id`, slot `k`, with `code`, and drops what it still sends, unless its side is over or being dropped. */
1267
+ stopReading(k: i32, id: i64, code: i64): void {
1268
+ if (this.phase[k] !== H3_PHASE_DONE && this.phase[k] !== H3_PHASE_DISCARD) {
1269
+ this.quic.streamStopSending(id, code)
1270
+ this.phase[k] = H3_PHASE_DISCARD
1271
+ this.stepAgain(k, id)
1272
+ }
1273
+ }
1274
+
1275
+ /**
1276
+ * Opens a WebTransport stream of this side's for `session` (draft-02
1277
+ * §4.1, §4.2): unidirectional with the type 0x54, or bidirectional with
1278
+ * the signal 0x41, and the session ID, which go before anything written.
1279
+ * Answers its ID, H3_AGAIN while the client's MAX_STREAMS allows no more,
1280
+ * or H3_CLOSED with WebTransport off or the connection failed.
1281
+ */
1282
+ openStream(session: i64, bidirectional: boolean): i64 {
1283
+ if (!this.webtransport || this.state === H3_STATE_FAILED || !http3IsRequest(session) || session < 0) {
1284
+ return toI64(H3_CLOSED)
1285
+ }
1286
+ const id: i64 = this.quic.openStream(bidirectional)
1287
+ const k: i32 = id >= 0 ? this.quic.streams.slotOf(id) : H3_NONE32
1288
+ if (k < 0 || k >= toI32(this.slotId.length)) {
1289
+ return toI64(H3_AGAIN)
1290
+ }
1291
+ this.open(k, id)
1292
+ // A unidirectional stream of this side's has no side for the client to write.
1293
+ this.flags[k] = bidirectional ? H3_FLAG_WT : H3_FLAG_WT | H3_FLAG_RECV_DONE
1294
+ this.phase[k] = bidirectional ? H3_PHASE_WT : H3_PHASE_DONE
1295
+ const end: i32 = toI32(this.scratch.length)
1296
+ const p: i32 = h3PutVarint(
1297
+ this.scratch,
1298
+ H3_ZERO,
1299
+ end,
1300
+ bidirectional ? H3_FRAME_WEBTRANSPORT_STREAM : H3_STREAM_WEBTRANSPORT
1301
+ )
1302
+ this.owe(k, this.scratch, h3PutVarint(this.scratch, p, end, session))
1303
+ if (!this.drain(k, id)) {
1304
+ this.mark(k, H3_FLAG_BLOCKED)
1305
+ }
1306
+ return id
1307
+ }
1308
+
1309
+ /**
1310
+ * Writes up to `len` bytes of `buf` from `off` to WebTransport stream `id`,
1311
+ * as they are, and its FIN when `fin` and every byte was taken. Answers how
1312
+ * many it took — fewer only when the send buffer filled, and H3_WRITABLE
1313
+ * then names the stream — or H3_AGAIN when it took none, or H3_CLOSED for
1314
+ * a stream with no side here to write, finished, reset, held or stopped.
1315
+ */
1316
+ writeStream(id: i64, buf: u8[], off: i32, len: i32, fin: boolean): i32 {
1317
+ h3CheckWindow("Http3Connection.writeStream", buf, off, len)
1318
+ const k: i32 = this.wtSlot(id)
1319
+ if (
1320
+ k < 0 ||
1321
+ (this.flags[k] & H3_FLAG_SEND_DONE) !== 0 ||
1322
+ this.phase[k] === H3_PHASE_WT_HELD ||
1323
+ this.quic.streams.slots[k].stopCode >= 0
1324
+ ) {
1325
+ return H3_CLOSED
1326
+ }
1327
+ if (!this.drain(k, id)) {
1328
+ this.mark(k, H3_FLAG_BLOCKED)
1329
+ return H3_AGAIN
1330
+ }
1331
+ const n: i32 = this.quic.streamWrite(id, buf, off, len, fin)
1332
+ if (n < 0) {
1333
+ return H3_CLOSED
1334
+ }
1335
+ if (n < len) {
1336
+ this.mark(k, H3_FLAG_BLOCKED)
1337
+ } else if (fin) {
1338
+ this.mark(k, H3_FLAG_SEND_DONE)
1339
+ }
1340
+ return n > 0 || (fin && len === 0) ? n : H3_AGAIN
1341
+ }
1342
+
1343
+ /** Resets this side of WebTransport stream `id` with `code`; the client's side goes on. Answers 0 or H3_CLOSED. */
1344
+ resetStream(id: i64, code: i64): i32 {
1345
+ const k: i32 = this.wtSlot(id)
1346
+ if (k < 0) {
1347
+ return H3_CLOSED
1348
+ }
1349
+ this.abandon(k, id, code)
1350
+ return 0
1351
+ }
1352
+
1353
+ /**
1354
+ * Asks the client to stop its side of WebTransport stream `id` with
1355
+ * `code`, and drops what it still sends; this side's goes on. Answers 0 or
1356
+ * H3_CLOSED.
1357
+ */
1358
+ stopStream(id: i64, code: i64): i32 {
1359
+ const k: i32 = this.wtSlot(id)
1360
+ if (k < 0) {
1361
+ return H3_CLOSED
1362
+ }
1363
+ this.stopReading(k, id, code)
1364
+ return 0
1365
+ }
1366
+
1367
+ // ---- Request streams ------------------------------------------------------------
1368
+
1369
+ /** The next event of request stream `id` in slot `k`. */
1370
+ requestStep(k: i32, id: i64): i32 {
1371
+ const stream: QuicStream = this.quic.streams.slots[k]
1372
+ while (this.state !== H3_STATE_FAILED) {
1373
+ const phase: i32 = this.phase[k]
1374
+ if (stream.stopCode >= 0 && (this.flags[k] & H3_FLAG_SEND_DONE) === 0) {
1375
+ // The client stopped the response (QUIC answered with RESET_STREAM, §3.5 of RFC 9000).
1376
+ const told: boolean = (this.flags[k] & H3_FLAG_DELIVERED) !== 0
1377
+ this.mark(k, H3_FLAG_SEND_DONE)
1378
+ if (told) {
1379
+ if (phase !== H3_PHASE_DONE) {
1380
+ this.phase[k] = H3_PHASE_DISCARD
1381
+ }
1382
+ return this.resetEvent(id, stream.stopCode, true)
1383
+ }
1384
+ }
1385
+ if (phase === H3_PHASE_DONE) {
1386
+ return this.writable(k, id)
1387
+ }
1388
+ if (phase === H3_PHASE_DISCARD) {
1389
+ return this.discard(k, id)
1390
+ }
1391
+ if (this.webtransport && !this.settingsSeen && phase === H3_PHASE_HEADERS && this.headLen[k] === 0) {
1392
+ // draft-02 §3.1: with WebTransport on, a request waits for the client's SETTINGS, which say whether it may be a session.
1393
+ if ((this.flags[k] & H3_FLAG_WAITING) === 0 && this.waitCount < toI32(this.waiting.length)) {
1394
+ this.waiting[this.waitCount] = id
1395
+ this.waitCount = this.waitCount + 1
1396
+ this.flags[k] = this.flags[k] | H3_FLAG_WAITING
1397
+ }
1398
+ return H3_NEED_MORE
1399
+ }
1400
+ if (this.frameLeft[k] < 0) {
1401
+ const event: i32 = this.requestFrame(k, id)
1402
+ if (event === H3_CONTINUE) {
1403
+ continue
1404
+ }
1405
+ if (event !== H3_NEED_MORE) {
1406
+ return event
1407
+ }
1408
+ // The header is not all in yet, unless the stream moved on to another phase.
1409
+ if (this.phase[k] === phase) {
1410
+ return this.writable(k, id)
1411
+ }
1412
+ continue
1413
+ }
1414
+ const type: i64 = this.frameType[k]
1415
+ if (type === H3_FRAME_HEADERS) {
1416
+ // A field section is decoded whole: wait until all of it is in.
1417
+ if (stream.resetCode < 0 && stream.readable() < this.frameLeft[k]) {
1418
+ if (stream.recvFinal >= 0 && stream.recvRead + this.frameLeft[k] > stream.recvFinal) {
1419
+ return this.fail(H3_FRAME_ERROR)
1420
+ }
1421
+ return this.writable(k, id)
1422
+ }
1423
+ const length: i32 = toI32(this.frameLeft[k])
1424
+ const n: i32 = this.quic.streamRead(id, this.section, H3_ZERO, length)
1425
+ if (n < 0) {
1426
+ return this.ended(k, id, n, stream.resetCode)
1427
+ }
1428
+ if (n !== length) {
1429
+ return this.writable(k, id)
1430
+ }
1431
+ this.frameLeft[k] = -1
1432
+ const event: i32 = this.fieldSection(k, id, length)
1433
+ if (event !== H3_NEED_MORE) {
1434
+ return event
1435
+ }
1436
+ continue
1437
+ }
1438
+ // DATA, a payload being skipped, or an unknown frame's.
1439
+ const want: i32 = toI32(http3Min(this.frameLeft[k], toI64(toI32(this.data.length))))
1440
+ const resetCode: i64 = stream.resetCode
1441
+ const n: i32 = this.quic.streamRead(id, this.data, H3_ZERO, want)
1442
+ if (n === 0) {
1443
+ return this.writable(k, id)
1444
+ }
1445
+ if (n < 0) {
1446
+ return n === QUIC_STREAM_END ? this.fail(H3_FRAME_ERROR) : this.ended(k, id, n, resetCode)
1447
+ }
1448
+ this.frameLeft[k] = this.frameLeft[k] - toI64(n)
1449
+ if (type === H3_FRAME_DATA) {
1450
+ if (this.frameLeft[k] === 0) {
1451
+ this.frameLeft[k] = -1
1452
+ }
1453
+ this.dataTotal[k] = this.dataTotal[k] + toI64(n)
1454
+ if (this.contentLength[k] >= 0 && this.dataTotal[k] > this.contentLength[k]) {
1455
+ return this.streamError(k, id, H3_MESSAGE_ERROR)
1456
+ }
1457
+ this.stream = id
1458
+ this.dataStart = 0
1459
+ this.dataLength = n
1460
+ return H3_DATA
1461
+ }
1462
+ if (this.frameLeft[k] === 0) {
1463
+ this.frameLeft[k] = -1
1464
+ if (this.phase[k] === H3_PHASE_SKIP) {
1465
+ return this.answerTooLarge(k, id)
1466
+ }
1467
+ }
1468
+ }
1469
+ return H3_ERROR
1470
+ }
1471
+
1472
+ /**
1473
+ * Reads the next frame header of request stream `id` and decides what its
1474
+ * payload is (§4.1, §7.2): DATA after HEADERS, HEADERS first or as
1475
+ * trailers, and anything else unknown and skipped, or refused where
1476
+ * RFC 9114 says so. A stream that ends here ends the request.
1477
+ */
1478
+ requestFrame(k: i32, id: i64): i32 {
1479
+ const stream: QuicStream = this.quic.streams.slots[k]
1480
+ const resetCode: i64 = stream.resetCode
1481
+ const got: i32 = this.readHeader(k, id)
1482
+ if (got === 0) {
1483
+ return H3_NEED_MORE
1484
+ }
1485
+ if (got < 0) {
1486
+ if (got === QUIC_STREAM_END && this.headLen[k] > 0) {
1487
+ return this.fail(H3_FRAME_ERROR)
1488
+ }
1489
+ if ((this.flags[k] & H3_FLAG_LATE) !== 0) {
1490
+ this.reject(k, id)
1491
+ return this.dropped(id)
1492
+ }
1493
+ return this.ended(k, id, got, resetCode)
1494
+ }
1495
+ const type: i64 = this.header.type
1496
+ const length: i64 = this.header.length
1497
+ const phase: i32 = this.phase[k]
1498
+ if (type === H3_FRAME_WEBTRANSPORT_STREAM && this.webtransport) {
1499
+ if (phase !== H3_PHASE_HEADERS) {
1500
+ // draft-02 §4.2: the signal opens a stream, and is nothing anywhere else.
1501
+ return this.fail(H3_FRAME_ERROR)
1502
+ }
1503
+ // Its "length" is the session ID. A stream past this side's GOAWAY counts once it is held, for a session below it.
1504
+ this.flags[k] = this.flags[k] | H3_FLAG_WT
1505
+ const event: i32 = this.held(k, id, length)
1506
+ if (event === H3_STREAM && (this.flags[k] & H3_FLAG_LATE) !== 0) {
1507
+ this.flags[k] = (this.flags[k] & ~H3_FLAG_LATE) | H3_FLAG_COUNTED
1508
+ this.live = this.live + 1
1509
+ }
1510
+ return event
1511
+ }
1512
+ if ((this.flags[k] & H3_FLAG_LATE) !== 0) {
1513
+ this.reject(k, id)
1514
+ return this.dropped(id)
1515
+ }
1516
+ if (type === H3_FRAME_DATA) {
1517
+ if (phase !== H3_PHASE_BODY) {
1518
+ return this.fail(H3_FRAME_UNEXPECTED)
1519
+ }
1520
+ this.frameType[k] = type
1521
+ this.frameLeft[k] = length > 0 ? length : -1
1522
+ return H3_CONTINUE
1523
+ }
1524
+ if (type === H3_FRAME_HEADERS) {
1525
+ if (phase === H3_PHASE_TRAILED) {
1526
+ return this.fail(H3_FRAME_UNEXPECTED)
1527
+ }
1528
+ if (length > toI64(this.config.maxFieldSectionSize)) {
1529
+ if (phase === H3_PHASE_HEADERS) {
1530
+ // §4.2.2: the section is skipped and answered with a 431.
1531
+ this.phase[k] = H3_PHASE_SKIP
1532
+ this.frameType[k] = H3_SKIP_TYPE
1533
+ this.frameLeft[k] = length
1534
+ return H3_CONTINUE
1535
+ }
1536
+ return this.streamError(k, id, H3_EXCESSIVE_LOAD)
1537
+ }
1538
+ this.frameType[k] = type
1539
+ this.frameLeft[k] = length
1540
+ if (length === 0) {
1541
+ // An empty section has no prefix, which QPACK refuses.
1542
+ this.frameLeft[k] = -1
1543
+ return this.fieldSection(k, id, H3_ZERO)
1544
+ }
1545
+ return H3_CONTINUE
1546
+ }
1547
+ if (
1548
+ type === H3_FRAME_SETTINGS ||
1549
+ type === H3_FRAME_GOAWAY ||
1550
+ type === H3_FRAME_MAX_PUSH_ID ||
1551
+ type === H3_FRAME_CANCEL_PUSH ||
1552
+ type === H3_FRAME_PUSH_PROMISE ||
1553
+ h3ReservedHttp2Frame(type)
1554
+ ) {
1555
+ return this.fail(H3_FRAME_UNEXPECTED)
1556
+ }
1557
+ // §9: an unknown frame type is skipped, wherever it comes.
1558
+ this.frameType[k] = H3_SKIP_TYPE
1559
+ this.frameLeft[k] = length > 0 ? length : -1
1560
+ return H3_CONTINUE
1561
+ }
1562
+
1563
+ /**
1564
+ * A whole HEADERS payload of `length` bytes is in `section`: the request's
1565
+ * field section, or its trailers. Decodes it, copies it into `fields` and
1566
+ * checks it there.
1567
+ */
1568
+ fieldSection(k: i32, id: i64, length: i32): i32 {
1569
+ const result: i64 = this.decoder.decode(this.section, H3_ZERO, length)
1570
+ const first: boolean = this.phase[k] === H3_PHASE_HEADERS
1571
+ if (result === QPACK_SECTION_TOO_LARGE) {
1572
+ return first ? this.answerTooLarge(k, id) : this.streamError(k, id, H3_EXCESSIVE_LOAD)
1573
+ }
1574
+ if (result !== QPACK_OK) {
1575
+ return this.fail(result)
1576
+ }
1577
+ this.copyFields()
1578
+ if (!first) {
1579
+ if (this.fields.readTrailers(this.nameList, this.valueList) !== HTTP_FIELDS_OK) {
1580
+ return this.streamError(k, id, H3_MESSAGE_ERROR)
1581
+ }
1582
+ this.phase[k] = H3_PHASE_TRAILED
1583
+ this.stream = id
1584
+ return H3_TRAILERS
1585
+ }
1586
+ if (this.fields.readRequest(this.nameList, this.valueList, this.connect) !== HTTP_FIELDS_OK) {
1587
+ return this.streamError(k, id, H3_MESSAGE_ERROR)
1588
+ }
1589
+ // An extended CONNECT reaches the program like any request, `fields.protocol` set:
1590
+ // `nish/net/webtransport` makes a session of one whose protocol is `webtransport`.
1591
+ this.contentLength[k] = this.fields.contentLength
1592
+ this.phase[k] = H3_PHASE_BODY
1593
+ this.flags[k] = this.flags[k] | H3_FLAG_DELIVERED
1594
+ this.stream = id
1595
+ return H3_REQUEST
1596
+ }
1597
+
1598
+ /** Copies the decoder's fields into the connection's own arrays and the lists `fields` reads. */
1599
+ copyFields(): void {
1600
+ const d: QpackDecoder = this.decoder
1601
+ http3EmptyList(this.nameList)
1602
+ http3EmptyList(this.valueList)
1603
+ this.poolUsed.fill(H3_ZERO)
1604
+ for (let i: i32 = 0; i < d.count; i++) {
1605
+ this.nameList.push(this.copyOf(d, d.nameStart[i], d.nameLength[i]))
1606
+ this.valueList.push(this.copyOf(d, d.valueStart[i], d.valueLength[i]))
1607
+ }
1608
+ }
1609
+
1610
+ /**
1611
+ * A copy of the decoder's bytes `[start, start + length)`, in an array of
1612
+ * the size class `length` falls in: class `j` holds arrays made with room
1613
+ * for 2^j bytes, for strings shorter than that and at least half as long.
1614
+ * A section takes the arrays of each class in order and the next takes the
1615
+ * same ones again, so a class grows only when one section needs more of its
1616
+ * size than any before it, and the total stays bounded by the field-section
1617
+ * cap (`docs/security/http3.md`, H3-2).
1618
+ */
1619
+ copyOf(d: QpackDecoder, start: i32, length: i32): u8[] {
1620
+ let j: i32 = 0
1621
+ while (j < H3_FIELD_CLASSES - 1 && 1 << j <= length) {
1622
+ j++
1623
+ }
1624
+ const pool: u8[][] = this.pools[j]
1625
+ const used: i32 = this.poolUsed[j]
1626
+ if (used >= toI32(pool.length)) {
1627
+ // Made with room for the class, then emptied below like any reused one.
1628
+ pool.push(new Array<u8>(1 << j))
1629
+ }
1630
+ this.poolUsed[j] = used + 1
1631
+ const out: u8[] = pool[used]
1632
+ http3Empty(out)
1633
+ const from: i32 = start > 0 ? start : H3_ZERO
1634
+ const end: i32 = start + length < toI32(d.bytes.length) ? start + length : toI32(d.bytes.length)
1635
+ for (let k: i32 = from; k < end; k++) {
1636
+ out.push(d.bytes[k])
1637
+ }
1638
+ return out
1639
+ }
1640
+
1641
+ /**
1642
+ * The request stream ended, cleanly (`n` is `QUIC_STREAM_END`) or by the
1643
+ * client's RESET_STREAM with `resetCode`, at a frame boundary.
1644
+ */
1645
+ ended(k: i32, id: i64, n: i32, resetCode: i64): i32 {
1646
+ const phase: i32 = this.phase[k]
1647
+ this.phase[k] = H3_PHASE_DONE
1648
+ this.mark(k, H3_FLAG_RECV_DONE)
1649
+ const delivered: boolean = (this.flags[k] & H3_FLAG_DELIVERED) !== 0
1650
+ if (n === QUIC_STREAM_ERR_RESET) {
1651
+ // §4.1.1: a response to a request that will not finish is abandoned too.
1652
+ this.abandon(k, id, resetCode === H3_REQUEST_CANCELLED ? H3_REQUEST_CANCELLED : H3_REQUEST_INCOMPLETE)
1653
+ return delivered ? this.resetEvent(id, resetCode, true) : this.dropped(id)
1654
+ }
1655
+ if (n !== QUIC_STREAM_END) {
1656
+ return H3_NEED_MORE
1657
+ }
1658
+ if (phase === H3_PHASE_HEADERS || phase === H3_PHASE_SKIP) {
1659
+ // §4.1.1: the stream ended before a whole request.
1660
+ this.abandon(k, id, H3_REQUEST_INCOMPLETE)
1661
+ this.streamErrors = h3Count(this.streamErrors)
1662
+ return this.dropped(id)
1663
+ }
1664
+ if (this.contentLength[k] >= 0 && this.dataTotal[k] !== this.contentLength[k]) {
1665
+ // §4.1.2: the body is shorter than its content-length.
1666
+ this.abandon(k, id, H3_MESSAGE_ERROR)
1667
+ this.streamErrors = h3Count(this.streamErrors)
1668
+ return this.resetEvent(id, H3_MESSAGE_ERROR, false)
1669
+ }
1670
+ this.stream = id
1671
+ return H3_END
1672
+ }
1673
+
1674
+ /** Resets this side's half of request stream `id` with `code`, unless it is done already. */
1675
+ abandon(k: i32, id: i64, code: i64): void {
1676
+ if ((this.flags[k] & H3_FLAG_SEND_DONE) === 0) {
1677
+ this.quic.streamReset(id, code)
1678
+ }
1679
+ this.mark(k, H3_FLAG_SEND_DONE)
1680
+ }
1681
+
1682
+ /** A stream error (§8): the request is reset both ways with `code`, and the program told when it knew the request. */
1683
+ streamError(k: i32, id: i64, code: i64): i32 {
1684
+ this.streamErrors = h3Count(this.streamErrors)
1685
+ this.abandon(k, id, code)
1686
+ this.quic.streamStopSending(id, code)
1687
+ this.phase[k] = H3_PHASE_DISCARD
1688
+ if ((this.flags[k] & H3_FLAG_DELIVERED) !== 0) {
1689
+ return this.resetEvent(id, code, false)
1690
+ }
1691
+ return this.dropped(id)
1692
+ }
1693
+
1694
+ /** H3_DROPPED for request stream `id` with WebTransport on, which waits on such a stream; H3_NEED_MORE without. */
1695
+ dropped(id: i64): i32 {
1696
+ if (!this.webtransport) {
1697
+ return H3_NEED_MORE
1698
+ }
1699
+ this.stream = id
1700
+ return H3_DROPPED
1701
+ }
1702
+
1703
+ /** H3_RESET for `id` with `code`. */
1704
+ resetEvent(id: i64, code: i64, byPeer: boolean): i32 {
1705
+ this.stream = id
1706
+ this.errorCode = code
1707
+ this.resetByPeer = byPeer
1708
+ return H3_RESET
1709
+ }
1710
+
1711
+ /**
1712
+ * A request whose field section is past `maxFieldSectionSize`: answered
1713
+ * with a 431 and its stream asked to stop with H3_NO_ERROR (§4.2.2,
1714
+ * §4.1.2). The program never sees it.
1715
+ */
1716
+ answerTooLarge(k: i32, id: i64): i32 {
1717
+ this.tooLarge = h3Count(this.tooLarge)
1718
+ const empty: u8[][] = this.nameList
1719
+ http3EmptyList(empty)
1720
+ if (this.writeHead(k, id, H3_STATUS_TOO_LARGE, empty, empty, true) !== 0) {
1721
+ this.abandon(k, id, H3_EXCESSIVE_LOAD)
1722
+ }
1723
+ this.quic.streamStopSending(id, H3_NO_ERROR)
1724
+ this.phase[k] = H3_PHASE_DISCARD
1725
+ return this.dropped(id)
1726
+ }
1727
+
1728
+ /**
1729
+ * The stream's write side, when a write was held back: owed bytes are
1730
+ * written, and once the buffer has room again the program is told with
1731
+ * H3_WRITABLE. Answers that, or H3_NEED_MORE.
1732
+ */
1733
+ writable(k: i32, id: i64): i32 {
1734
+ if ((this.flags[k] & H3_FLAG_BLOCKED) === 0 || (this.flags[k] & H3_FLAG_SEND_DONE) !== 0) {
1735
+ return H3_NEED_MORE
1736
+ }
1737
+ if (!this.drain(k, id) || this.quic.streams.slots[k].room() <= 0) {
1738
+ return H3_NEED_MORE
1739
+ }
1740
+ this.flags[k] = this.flags[k] & ~H3_FLAG_BLOCKED
1741
+ this.stream = id
1742
+ return H3_WRITABLE
1743
+ }
1744
+
1745
+ // ---- Writing ----------------------------------------------------------------------
1746
+
1747
+ /** The slot of request stream `id` when the program may write to it, else -1. */
1748
+ writeSlot(id: i64): i32 {
1749
+ if (this.state === H3_STATE_FAILED || !http3IsRequest(id)) {
1750
+ return -1
1751
+ }
1752
+ const k: i32 = this.quic.streams.slotOf(id)
1753
+ if (k < 0 || k >= toI32(this.slotId.length) || this.slotId[k] !== id) {
1754
+ return -1
1755
+ }
1756
+ const flags: i32 = this.flags[k]
1757
+ if ((flags & H3_FLAG_DELIVERED) === 0 || (flags & H3_FLAG_SEND_DONE) !== 0) {
1758
+ return -1
1759
+ }
1760
+ return this.quic.streams.slots[k].stopCode >= 0 ? -1 : k
1761
+ }
1762
+
1763
+ /**
1764
+ * Writes the bytes owed on stream `id` before anything else — the rest of
1765
+ * a frame header, a filler's payload — as far as the send buffer takes
1766
+ * them. Answers whether none are owed now.
1767
+ */
1768
+ drain(k: i32, id: i64): boolean {
1769
+ const base: i32 = k * H3_HEAD
1770
+ while (this.pendLen[k] > 0) {
1771
+ const n: i32 = this.quic.streamWrite(id, this.pend, base + this.pendAt[k], this.pendLen[k], false)
1772
+ if (n <= 0) {
1773
+ return false
1774
+ }
1775
+ this.pendAt[k] = this.pendAt[k] + n
1776
+ this.pendLen[k] = this.pendLen[k] - n
1777
+ }
1778
+ while (this.fillLeft[k] > 0) {
1779
+ const want: i32 = toI32(http3Min(this.fillLeft[k], toI64(toI32(this.zeros.length))))
1780
+ const n: i32 = this.quic.streamWrite(id, this.zeros, H3_ZERO, want, false)
1781
+ if (n <= 0) {
1782
+ return false
1783
+ }
1784
+ this.fillLeft[k] = this.fillLeft[k] - toI64(n)
1785
+ }
1786
+ return true
1787
+ }
1788
+
1789
+ /** Queues `buf[0 .. n)`, a frame header, as the bytes owed first on slot `k`. */
1790
+ owe(k: i32, buf: u8[], n: i32): void {
1791
+ const base: i32 = k * H3_HEAD
1792
+ for (let j: i32 = 0; j < n && j < H3_HEAD && j < toI32(buf.length); j++) {
1793
+ this.pend[base + j] = buf[j]
1794
+ }
1795
+ this.pendAt[k] = 0
1796
+ this.pendLen[k] = n < H3_HEAD ? n : H3_HEAD
1797
+ }
1798
+
1799
+ /**
1800
+ * A frame of `need` bytes must go whole and the send buffer has less room:
1801
+ * the room left is filled with a reserved frame the client skips (§7.2.8),
1802
+ * so that QUIC names the stream again once acknowledgements free room.
1803
+ */
1804
+ arm(k: i32, id: i64): void {
1805
+ const stream: QuicStream = this.quic.streams.slots[k]
1806
+ const room: i64 = stream.room()
1807
+ this.mark(k, H3_FLAG_BLOCKED)
1808
+ if (room > 0) {
1809
+ // A reserved frame of exactly `room` bytes: its type, its length in
1810
+ // the varint size that makes the sum come out (RFC 9000 §16 lets a
1811
+ // varint be longer than it needs), and zeros. One byte of room cannot
1812
+ // hold a frame, so it takes the type and owes the length.
1813
+ let size: i32 = 1
1814
+ if (room > 65) {
1815
+ size = 2
1816
+ }
1817
+ if (room > 16386) {
1818
+ size = 4
1819
+ }
1820
+ const payload: i64 = room > 1 ? room - toI64(1 + size) : toI64(0)
1821
+ this.scratch[0] = toU8(H3_FILLER_TYPE)
1822
+ const n: i32 = quicVarintPut(this.scratch, H3_ONE, payload, size)
1823
+ this.owe(k, this.scratch, n)
1824
+ this.fillLeft[k] = payload
1825
+ if (!this.drain(k, id)) {
1826
+ return
1827
+ }
1828
+ }
1829
+ // The room is filled: a write that takes nothing asks QUIC for the event.
1830
+ if (stream.room() === toI64(0)) {
1831
+ this.quic.streamWrite(id, this.zeros, H3_ZERO, H3_ONE, false)
1832
+ }
1833
+ }
1834
+
1835
+ /**
1836
+ * Encodes `:status` (unless it is 0, for trailers) and `names`/`values` into a HEADERS
1837
+ * frame and writes it whole to stream `id`, with the FIN when `fin`.
1838
+ * Answers 0, H3_AGAIN, or H3_TOO_LARGE.
1839
+ */
1840
+ writeHead(k: i32, id: i64, status: i32, names: u8[][], values: u8[][], fin: boolean): i32 {
1841
+ const out: u8[] = this.encoded
1842
+ http3Empty(out)
1843
+ this.encoder.beginSection(out)
1844
+ let size: i64 = 0
1845
+ if (status > 0) {
1846
+ const ten: i32 = 10
1847
+ this.statusValue[0] = toU8(H3_DIGIT_ZERO + status / (ten * ten))
1848
+ this.statusValue[1] = toU8(H3_DIGIT_ZERO + ((status / ten) % ten))
1849
+ this.statusValue[2] = toU8(H3_DIGIT_ZERO + (status % ten))
1850
+ const nameLength: i32 = toI32(this.statusName.length)
1851
+ const valueLength: i32 = toI32(this.statusValue.length)
1852
+ this.encoder.encodeField(
1853
+ out,
1854
+ this.statusName,
1855
+ H3_ZERO,
1856
+ nameLength,
1857
+ this.statusValue,
1858
+ H3_ZERO,
1859
+ valueLength,
1860
+ false,
1861
+ true
1862
+ )
1863
+ size = toI64(nameLength + valueLength + QPACK_FIELD_OVERHEAD)
1864
+ }
1865
+ const n: i32 = toI32(names.length)
1866
+ for (let j: i32 = 0; j < n && j < toI32(names.length) && j < toI32(values.length); j++) {
1867
+ const name: u8[] = names[j]
1868
+ const value: u8[] = values[j]
1869
+ const nl: i32 = toI32(name.length)
1870
+ const vl: i32 = toI32(value.length)
1871
+ this.encoder.encodeField(out, name, H3_ZERO, nl, value, H3_ZERO, vl, httpFieldSensitive(name), true)
1872
+ size = size + toI64(nl + vl + QPACK_FIELD_OVERHEAD)
1873
+ }
1874
+ const limit: i64 = this.peer.maxFieldSectionSize
1875
+ const length: i64 = toI64(toI32(out.length))
1876
+ const total: i64 = toI64(h3FrameHeaderSize(H3_FRAME_HEADERS, length)) + length
1877
+ if ((limit >= 0 && size > limit) || total > toI64(this.quic.streams.bufferSize)) {
1878
+ return H3_TOO_LARGE
1879
+ }
1880
+ if (!this.drain(k, id)) {
1881
+ this.mark(k, H3_FLAG_BLOCKED)
1882
+ return H3_AGAIN
1883
+ }
1884
+ if (this.quic.streams.slots[k].room() < total) {
1885
+ this.arm(k, id)
1886
+ return H3_AGAIN
1887
+ }
1888
+ const hp: i32 = h3PutFrameHeader(
1889
+ this.scratch,
1890
+ H3_ZERO,
1891
+ toI32(this.scratch.length),
1892
+ H3_FRAME_HEADERS,
1893
+ length
1894
+ )
1895
+ this.quic.streamWrite(id, this.scratch, H3_ZERO, hp, false)
1896
+ this.quic.streamWrite(id, out, H3_ZERO, toI32(length), fin)
1897
+ this.flags[k] = this.flags[k] | H3_FLAG_HEADERS_SENT
1898
+ if (fin) {
1899
+ this.mark(k, H3_FLAG_SEND_DONE)
1900
+ }
1901
+ return 0
1902
+ }
1903
+
1904
+ /**
1905
+ * Sends the response head on request stream `id`: `status` (200 to 599)
1906
+ * and the fields `names`/`values` (lowercase names, no pseudo-headers),
1907
+ * ending the stream when `endStream`. Answers 0; H3_CLOSED for a stream
1908
+ * that is not an open request; H3_INVALID for a second head, a status out
1909
+ * of range or a field `httpFieldsCheckOutgoing` refuses; H3_TOO_LARGE for a
1910
+ * section past the client's SETTINGS_MAX_FIELD_SECTION_SIZE; or H3_AGAIN.
1911
+ */
1912
+ respond(id: i64, status: i32, names: u8[][], values: u8[][], endStream: boolean): i32 {
1913
+ const k: i32 = this.writeSlot(id)
1914
+ if (k < 0) {
1915
+ return H3_CLOSED
1916
+ }
1917
+ if (
1918
+ (this.flags[k] & H3_FLAG_HEADERS_SENT) !== 0 ||
1919
+ status < 200 ||
1920
+ status > 599 ||
1921
+ httpFieldsCheckOutgoing(names, values) !== HTTP_FIELDS_OK
1922
+ ) {
1923
+ return H3_INVALID
1924
+ }
1925
+ return this.writeHead(k, id, status, names, values, endStream)
1926
+ }
1927
+
1928
+ /**
1929
+ * Writes a trailer section on request stream `id` after its body, which
1930
+ * ends the stream. Answers as `respond` does; H3_INVALID before the head
1931
+ * or inside a DATA frame `writeData` has not finished.
1932
+ */
1933
+ writeTrailers(id: i64, names: u8[][], values: u8[][]): i32 {
1934
+ const k: i32 = this.writeSlot(id)
1935
+ if (k < 0) {
1936
+ return H3_CLOSED
1937
+ }
1938
+ if (
1939
+ (this.flags[k] & H3_FLAG_HEADERS_SENT) === 0 ||
1940
+ this.dataLeft[k] > 0 ||
1941
+ httpFieldsCheckOutgoing(names, values) !== HTTP_FIELDS_OK
1942
+ ) {
1943
+ return H3_INVALID
1944
+ }
1945
+ return this.writeHead(k, id, H3_ZERO, names, values, true)
1946
+ }
1947
+
1948
+ /**
1949
+ * Writes up to `len` bytes of `buf` from `off` as the response body of
1950
+ * stream `id`, in DATA frames of at most `writeChunk`, and ends the stream
1951
+ * when `endStream` and every byte was taken. Answers how many bytes it
1952
+ * took — fewer than `len` only when the send buffer filled, and
1953
+ * H3_WRITABLE then names the stream once it has room — or H3_AGAIN when it
1954
+ * took none;
1955
+ * H3_CLOSED, or H3_INVALID before the head or when `endStream` would cut
1956
+ * short the DATA frame the last call started.
1957
+ */
1958
+ writeData(id: i64, buf: u8[], off: i32, len: i32, endStream: boolean): i32 {
1959
+ h3CheckWindow("Http3Connection.writeData", buf, off, len)
1960
+ const k: i32 = this.writeSlot(id)
1961
+ if (k < 0) {
1962
+ return H3_CLOSED
1963
+ }
1964
+ if ((this.flags[k] & H3_FLAG_HEADERS_SENT) === 0 || (endStream && toI64(len) < this.dataLeft[k])) {
1965
+ return H3_INVALID
1966
+ }
1967
+ if (!this.drain(k, id)) {
1968
+ this.mark(k, H3_FLAG_BLOCKED)
1969
+ return H3_AGAIN
1970
+ }
1971
+ if (len === 0) {
1972
+ if (endStream && this.dataLeft[k] === 0) {
1973
+ this.quic.streamWrite(id, this.zeros, H3_ZERO, H3_ZERO, true)
1974
+ this.mark(k, H3_FLAG_SEND_DONE)
1975
+ }
1976
+ return 0
1977
+ }
1978
+ // DATA frame after DATA frame, until every byte is taken or the send buffer is full.
1979
+ let total: i32 = 0
1980
+ while (total < len) {
1981
+ if (this.dataLeft[k] === 0) {
1982
+ const rest: i32 = len - total
1983
+ const frame: i64 = toI64(rest < this.config.writeChunk ? rest : this.config.writeChunk)
1984
+ const n: i32 = h3PutFrameHeader(
1985
+ this.scratch,
1986
+ H3_ZERO,
1987
+ toI32(this.scratch.length),
1988
+ H3_FRAME_DATA,
1989
+ frame
1990
+ )
1991
+ this.owe(k, this.scratch, n)
1992
+ this.dataLeft[k] = frame
1993
+ if (!this.drain(k, id)) {
1994
+ this.mark(k, H3_FLAG_BLOCKED)
1995
+ break
1996
+ }
1997
+ }
1998
+ const take: i32 = toI32(http3Min(toI64(len - total), this.dataLeft[k]))
1999
+ const fin: boolean = endStream && total + take === len && toI64(take) === this.dataLeft[k]
2000
+ const n: i32 = this.quic.streamWrite(id, buf, off + total, take, fin)
2001
+ if (n < 0) {
2002
+ return total > 0 ? total : H3_CLOSED
2003
+ }
2004
+ this.dataLeft[k] = this.dataLeft[k] - toI64(n)
2005
+ total = total + n
2006
+ if (n < take) {
2007
+ this.mark(k, H3_FLAG_BLOCKED)
2008
+ break
2009
+ }
2010
+ if (fin) {
2011
+ this.mark(k, H3_FLAG_SEND_DONE)
2012
+ }
2013
+ }
2014
+ return total > 0 ? total : H3_AGAIN
2015
+ }
2016
+
2017
+ /**
2018
+ * Writes `buf[off .. off + len)` as one DATA frame on request stream `id`,
2019
+ * whole or not at all, with the FIN when `fin`: a capsule (RFC 9297 §3.2)
2020
+ * goes this way, so the client never reads half of one. Answers 0;
2021
+ * H3_AGAIN while the send buffer lacks room, and H3_WRITABLE then names the
2022
+ * stream; or as `writeData` refuses, and H3_TOO_LARGE for a frame past the
2023
+ * stream's buffer.
2024
+ */
2025
+ writeDataWhole(id: i64, buf: u8[], off: i32, len: i32, fin: boolean): i32 {
2026
+ h3CheckWindow("Http3Connection.writeDataWhole", buf, off, len)
2027
+ const k: i32 = this.writeSlot(id)
2028
+ if (k < 0) {
2029
+ return H3_CLOSED
2030
+ }
2031
+ if ((this.flags[k] & H3_FLAG_HEADERS_SENT) === 0 || this.dataLeft[k] > 0) {
2032
+ return H3_INVALID
2033
+ }
2034
+ const length: i64 = toI64(len)
2035
+ const total: i64 = toI64(h3FrameHeaderSize(H3_FRAME_DATA, length)) + length
2036
+ if (total > toI64(this.quic.streams.bufferSize)) {
2037
+ return H3_TOO_LARGE
2038
+ }
2039
+ if (!this.drain(k, id)) {
2040
+ this.mark(k, H3_FLAG_BLOCKED)
2041
+ return H3_AGAIN
2042
+ }
2043
+ if (this.quic.streams.slots[k].room() < total) {
2044
+ this.arm(k, id)
2045
+ return H3_AGAIN
2046
+ }
2047
+ const hp: i32 = h3PutFrameHeader(this.scratch, H3_ZERO, toI32(this.scratch.length), H3_FRAME_DATA, length)
2048
+ this.quic.streamWrite(id, this.scratch, H3_ZERO, hp, false)
2049
+ this.quic.streamWrite(id, buf, off, len, fin)
2050
+ if (fin) {
2051
+ this.mark(k, H3_FLAG_SEND_DONE)
2052
+ }
2053
+ return 0
2054
+ }
2055
+
2056
+ /**
2057
+ * Resets request stream `id` both ways with `code` — H3_REQUEST_CANCELLED,
2058
+ * or H3_REQUEST_REJECTED for one the program did not process (§4.1.1).
2059
+ * Answers 0 or H3_CLOSED.
2060
+ */
2061
+ reset(id: i64, code: i64): i32 {
2062
+ const k: i32 = this.writeSlot(id)
2063
+ if (k < 0) {
2064
+ return H3_CLOSED
2065
+ }
2066
+ this.abandon(k, id, code)
2067
+ if (this.phase[k] !== H3_PHASE_DONE) {
2068
+ this.quic.streamStopSending(id, code)
2069
+ this.phase[k] = H3_PHASE_DISCARD
2070
+ }
2071
+ return 0
2072
+ }
2073
+
2074
+ /**
2075
+ * Whether request stream `id` has not yet had its field section read:
2076
+ * not opened, opened by a later stream's frame (RFC 9000 §3.2), or still
2077
+ * waiting for its HEADERS. A WebTransport stream that names it as its
2078
+ * session may wait for it.
2079
+ */
2080
+ awaitsRequest(id: i64): boolean {
2081
+ if (!http3IsRequest(id) || id < 0 || (this.goawayId >= 0 && id >= this.goawayId)) {
2082
+ return false
2083
+ }
2084
+ const k: i32 = this.quic.streams.slotOf(id)
2085
+ if (k < 0 || k >= toI32(this.slotId.length)) {
2086
+ // Not opened yet, and one the client's MAX_STREAMS lets it open now.
2087
+ const streams = this.quic.streams
2088
+ return id >= streams.peerBidiOpened << 2 && id < streams.peerBidiLimit << 2
2089
+ }
2090
+ if (this.slotId[k] !== id) {
2091
+ return true
2092
+ }
2093
+ return this.phase[k] === H3_PHASE_HEADERS && (this.flags[k] & H3_FLAG_WT) === 0
2094
+ }
2095
+
2096
+ /**
2097
+ * Stops reading request stream `id`: the client is asked to stop with
2098
+ * `code` and what it still sends is dropped unseen, whether or not this
2099
+ * side has finished its response — a refused WebTransport session's
2100
+ * stream, after its status. Answers 0, or H3_CLOSED for a stream that is
2101
+ * not a request here.
2102
+ */
2103
+ discardRequest(id: i64, code: i64): i32 {
2104
+ if (this.state === H3_STATE_FAILED || !http3IsRequest(id)) {
2105
+ return H3_CLOSED
2106
+ }
2107
+ const k: i32 = this.quic.streams.slotOf(id)
2108
+ if (k < 0 || k >= toI32(this.slotId.length) || this.slotId[k] !== id) {
2109
+ return H3_CLOSED
2110
+ }
2111
+ this.stopReading(k, id, code)
2112
+ return 0
2113
+ }
2114
+
2115
+ /**
2116
+ * Sends GOAWAY (§5.2): the client's requests on streams below the ID it
2117
+ * names — every stream the client has opened so far — are processed, and
2118
+ * any it opens from there on is refused with H3_REQUEST_REJECTED. A second
2119
+ * call never raises the ID, and sends nothing when the ID has not fallen.
2120
+ * Answers 0, or H3_AGAIN before this side's control stream is open or while
2121
+ * it has no room for the frame.
2122
+ */
2123
+ goaway(): i32 {
2124
+ if (this.ownControl < 0 || this.state === H3_STATE_FAILED) {
2125
+ return H3_AGAIN
2126
+ }
2127
+ let id: i64 = this.quic.streams.peerBidiOpened << 2
2128
+ if (this.goawayId >= 0 && id > this.goawayId) {
2129
+ id = this.goawayId
2130
+ }
2131
+ if (id === this.goawayId) {
2132
+ // The client has this ID already: nothing new to say.
2133
+ return 0
2134
+ }
2135
+ const n: i32 = h3PutIdFrame(this.scratch, H3_ZERO, toI32(this.scratch.length), H3_FRAME_GOAWAY, id)
2136
+ const k: i32 = this.quic.streams.slotOf(this.ownControl)
2137
+ // Whole or not at all: half a GOAWAY on the control stream would be a frame error.
2138
+ if (k < 0 || k >= toI32(this.quic.streams.slots.length) || this.quic.streams.slots[k].room() < toI64(n)) {
2139
+ return H3_AGAIN
2140
+ }
2141
+ this.quic.streamWrite(this.ownControl, this.scratch, H3_ZERO, n, false)
2142
+ this.goawayId = id
2143
+ return 0
2144
+ }
2145
+ }