@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,1603 @@
1
+ /**
2
+ * `nish/net/quic-stream` — the streams of one QUIC version 1 connection
3
+ * (RFC 9000 §2-§4): stream IDs and their four types, the sending and
4
+ * receiving state machines, reassembly, flow control per stream and for the
5
+ * connection, and the stream limits. Sans-IO, like the rest of `nish/net`:
6
+ * `nish/net/quic`'s `QuicConnection` hands it every stream frame it reads,
7
+ * asks it for the frames to send, and reports which of them a packet that
8
+ * was acknowledged or lost carried. An application reaches it through the
9
+ * connection's stream calls (`openStream`, `streamWrite`, `streamRead`,
10
+ * `nextStreamEvent` and the rest), not directly.
11
+ *
12
+ * **IDs** (§2.1). The low bit says who opened a stream (0 the client, 1 the
13
+ * server) and the next one whether it carries data both ways (0) or only
14
+ * from its opener (1). This side is a server: it opens IDs 1 and 3 modulo 4,
15
+ * and the client 0 and 2. A frame for a stream of the client's that is past
16
+ * the limit this side advertised is STREAM_LIMIT_ERROR; for one of the
17
+ * server's that it has not opened, STREAM_STATE_ERROR; and a frame that only
18
+ * makes sense on a side the stream does not have (STREAM, RESET_STREAM or
19
+ * STREAM_DATA_BLOCKED on the server's unidirectional stream; MAX_STREAM_DATA
20
+ * or STOP_SENDING on the client's) is STREAM_STATE_ERROR (§19.4-§19.13). A
21
+ * client's stream opens every lower one of its type with it (§3.2).
22
+ *
23
+ * **Fixed slots.** The table is sized once, from the configuration: one slot
24
+ * per stream that may be open at once — as many client-opened bidirectional
25
+ * and unidirectional streams as this side lets the client have, and
26
+ * `localStreams` of its own — each with a receive buffer and a send buffer
27
+ * of `bufferSize` bytes and a bit per byte beside each. A stream lives in
28
+ * its slot from the frame (or `open`) that starts it until both of its
29
+ * sides are done, and then the slot is free for the next. A stream is found
30
+ * by its ID through a hash index, never by a scan. Nothing a peer sends, and
31
+ * nothing the application writes, allocates.
32
+ *
33
+ * **Receiving** (§3.2, §2.2). Data lands in the receive buffer at its offset
34
+ * modulo the buffer's size, in any order and overlapping, with a bit set per
35
+ * byte; what continues the stream from the last byte read is what
36
+ * `read` hands out. The credit this side gives a stream (MAX_STREAM_DATA) is
37
+ * never more than what the buffer can hold past the last byte read, so a
38
+ * byte beyond it is FLOW_CONTROL_ERROR and the buffer cannot overflow; it is
39
+ * raised by a whole buffer once the application has read half of one. The
40
+ * connection's credit (MAX_DATA) is raised the same way, by `windowData`
41
+ * once half of it is read. A FIN or RESET_STREAM fixes the final size, and
42
+ * a frame that disagrees with it, or a size below what already arrived, is
43
+ * FINAL_SIZE_ERROR (§4.5). A reset stream's bytes count as read for the
44
+ * connection's credit.
45
+ *
46
+ * **Sending** (§3.1). The application writes into the send buffer as far as
47
+ * it has room, a chunk at a time: what `write` takes is its back-pressure,
48
+ * and the stream's event fires again once acknowledgements free room. Bytes
49
+ * stay in the buffer until acknowledged: the acknowledged bits move the
50
+ * buffer's start, and a range a lost packet carried is sent again from it.
51
+ * New bytes go out only within both the stream's credit and the
52
+ * connection's; at either limit the stream owes STREAM_DATA_BLOCKED or the
53
+ * connection DATA_BLOCKED, once per limit. A STOP_SENDING from the peer is
54
+ * answered with RESET_STREAM (§3.5), and RESET_STREAM from the application
55
+ * gives up what was not sent; either is sent until acknowledged.
56
+ *
57
+ * **Limits** (§4.6). The client may open as many streams of each type as
58
+ * the configuration allows at once; MAX_STREAMS raises its limit as its
59
+ * streams finish, once half of the configured number have. The server opens
60
+ * streams within the client's limits and its own `localStreams`; past the
61
+ * client's limit it owes STREAMS_BLOCKED.
62
+ *
63
+ * Written from RFC 9000 §2-§4 and §19, in this module's own structure;
64
+ * nothing here is ported from another implementation. Private names carry
65
+ * the `quicStream` prefix (`docs/wp26-stdlib.md` §3e).
66
+ */
67
+ import {
68
+ QUIC_ERROR_FINAL_SIZE,
69
+ QUIC_ERROR_FLOW_CONTROL,
70
+ QUIC_ERROR_NO_ERROR,
71
+ QUIC_ERROR_STREAM_LIMIT,
72
+ QUIC_ERROR_STREAM_STATE,
73
+ QUIC_FRAME_DATA_BLOCKED,
74
+ QUIC_FRAME_MAX_DATA,
75
+ QUIC_FRAME_MAX_STREAM_DATA,
76
+ QUIC_FRAME_MAX_STREAMS_BIDI,
77
+ QUIC_FRAME_MAX_STREAMS_UNI,
78
+ QUIC_FRAME_STREAM_DATA_BLOCKED,
79
+ QUIC_FRAME_STREAMS_BLOCKED_BIDI,
80
+ QUIC_FRAME_STREAMS_BLOCKED_UNI,
81
+ quicPutStream,
82
+ quicPutStreamError,
83
+ quicPutStreamValue,
84
+ quicPutValue,
85
+ quicStreamOverhead,
86
+ } from "nish/net/quic-frame"
87
+
88
+ // ---- What the stream calls answer -----------------------------------------------
89
+
90
+ /** Done: the data was taken, or the frame queued. */
91
+ export const QUIC_STREAM_OK: i32 = 0
92
+ /** No such stream: never opened, or already finished both ways and gone. */
93
+ export const QUIC_STREAM_ERR_UNKNOWN: i32 = -1
94
+ /** The stream's sending side is finished: a FIN was written, or the stream was reset. */
95
+ export const QUIC_STREAM_ERR_FINISHED: i32 = -2
96
+ /** The data would pass the credit the peer gave, or the room in the send buffer (all-or-nothing `writeStream`). */
97
+ export const QUIC_STREAM_ERR_FLOW: i32 = -3
98
+ /** The connection is not connected. */
99
+ export const QUIC_STREAM_ERR_STATE: i32 = -4
100
+ /** The stream has no such side: a write to a stream only the peer sends on, or a read of one only this side sends on. */
101
+ export const QUIC_STREAM_ERR_DIRECTION: i32 = -5
102
+ /** No stream can be opened now: the peer's MAX_STREAMS is reached (STREAMS_BLOCKED is sent), or every local slot is taken. */
103
+ export const QUIC_STREAM_ERR_LIMIT: i32 = -6
104
+ /** A read past the end: every byte up to the FIN has been read. */
105
+ export const QUIC_STREAM_END: i32 = -7
106
+ /** The peer reset the stream (RESET_STREAM): `resetCode` holds its error code. */
107
+ export const QUIC_STREAM_ERR_RESET: i32 = -8
108
+ /** The peer asked this side to stop sending (STOP_SENDING), which reset the stream: `stopCode` holds its code. */
109
+ export const QUIC_STREAM_ERR_STOPPED: i32 = -9
110
+
111
+ // ---- The states of RFC 9000 §3 --------------------------------------------------
112
+
113
+ /** A sending side with nothing written yet (§3.1 "Ready"). */
114
+ export const QUIC_SEND_READY: i32 = 0
115
+ /** Data written, not all of it sent with the FIN (§3.1 "Send"). */
116
+ export const QUIC_SEND_SEND: i32 = 1
117
+ /** Every byte and the FIN sent, not all acknowledged (§3.1 "Data Sent"). */
118
+ export const QUIC_SEND_DATA_SENT: i32 = 2
119
+ /** Every byte and the FIN acknowledged: the side is done (§3.1 "Data Recvd"). */
120
+ export const QUIC_SEND_DATA_RECVD: i32 = 3
121
+ /** RESET_STREAM sent, not yet acknowledged (§3.1 "Reset Sent"). */
122
+ export const QUIC_SEND_RESET_SENT: i32 = 4
123
+ /** RESET_STREAM acknowledged: the side is done (§3.1 "Reset Recvd"). */
124
+ export const QUIC_SEND_RESET_RECVD: i32 = 5
125
+ /** The stream has no sending side here: a unidirectional stream the peer opened. */
126
+ export const QUIC_SEND_NONE: i32 = 6
127
+
128
+ /** Receiving, the final size not known yet (§3.2 "Recv"). */
129
+ export const QUIC_RECV_RECV: i32 = 0
130
+ /** The FIN fixed the final size; bytes are still missing (§3.2 "Size Known"). */
131
+ export const QUIC_RECV_SIZE_KNOWN: i32 = 1
132
+ /** Every byte up to the final size arrived (§3.2 "Data Recvd"). */
133
+ export const QUIC_RECV_DATA_RECVD: i32 = 2
134
+ /** Every byte was read: the side is done (§3.2 "Data Read"). */
135
+ export const QUIC_RECV_DATA_READ: i32 = 3
136
+ /** The peer reset the stream (§3.2 "Reset Recvd"). */
137
+ export const QUIC_RECV_RESET_RECVD: i32 = 4
138
+ /** The application saw the reset: the side is done (§3.2 "Reset Read"). */
139
+ export const QUIC_RECV_RESET_READ: i32 = 5
140
+ /** The stream has no receiving side here: a unidirectional stream this side opened. */
141
+ export const QUIC_RECV_NONE: i32 = 6
142
+
143
+ // ---- The control frames a packet's record names ---------------------------------
144
+
145
+ /** MAX_DATA, the connection's credit. */
146
+ export const QUIC_STREAM_CONTROL_MAX_DATA: i32 = 3
147
+ /** MAX_STREAM_DATA for the stream the record's value names. */
148
+ export const QUIC_STREAM_CONTROL_MAX_STREAM_DATA: i32 = 4
149
+ /** MAX_STREAMS for bidirectional streams. */
150
+ export const QUIC_STREAM_CONTROL_MAX_STREAMS_BIDI: i32 = 5
151
+ /** MAX_STREAMS for unidirectional streams. */
152
+ export const QUIC_STREAM_CONTROL_MAX_STREAMS_UNI: i32 = 6
153
+ /** DATA_BLOCKED. */
154
+ export const QUIC_STREAM_CONTROL_DATA_BLOCKED: i32 = 7
155
+ /** STREAM_DATA_BLOCKED for the stream the value names. */
156
+ export const QUIC_STREAM_CONTROL_STREAM_DATA_BLOCKED: i32 = 8
157
+ /** STREAMS_BLOCKED for bidirectional streams. */
158
+ export const QUIC_STREAM_CONTROL_STREAMS_BLOCKED_BIDI: i32 = 9
159
+ /** STREAMS_BLOCKED for unidirectional streams. */
160
+ export const QUIC_STREAM_CONTROL_STREAMS_BLOCKED_UNI: i32 = 10
161
+ /** RESET_STREAM for the stream the value names. */
162
+ export const QUIC_STREAM_CONTROL_RESET: i32 = 11
163
+ /** STOP_SENDING for the stream the value names. */
164
+ export const QUIC_STREAM_CONTROL_STOP_SENDING: i32 = 12
165
+
166
+ /** The ceiling of a stream count, 2^60 (§4.6). A product, because an `i64` literal past 2^53 is refused. */
167
+ const QUIC_STREAM_MAX_COUNT: i64 = 1073741824 * 1073741824
168
+
169
+ /** A typed zero and one, since a bare literal is an `f64` under `--number-mode f64`. */
170
+ const QUIC_STREAM_ZERO: i64 = 0
171
+ const QUIC_STREAM_ONE: i32 = 1
172
+ const QUIC_STREAM_NO_BYTES: i32 = 0
173
+
174
+ /** Whether `id` is a stream this side, the server, opens: its low bit is 1. */
175
+ export const quicStreamIsLocal = (id: i64): boolean => (id & 1) !== 0
176
+
177
+ /** Whether `id` is a unidirectional stream: its second bit is 1. */
178
+ export const quicStreamIsUni = (id: i64): boolean => (id & 2) !== 0
179
+
180
+ /** Whether bit `k` of `bits` is set; false outside the array. */
181
+ export const quicStreamBit = (bits: u8[], k: i32): boolean => {
182
+ const at: i32 = k >> 3
183
+ return at >= 0 && at < toI32(bits.length) && (toI32(bits[at]) & (QUIC_STREAM_ONE << (k & 7))) !== 0
184
+ }
185
+
186
+ /** Sets or clears bit `k` of `bits`. */
187
+ export const quicStreamSetBit = (bits: u8[], k: i32, on: boolean): void => {
188
+ const at: i32 = k >> 3
189
+ if (at < 0 || at >= toI32(bits.length)) {
190
+ return
191
+ }
192
+ const mask: i32 = QUIC_STREAM_ONE << (k & 7)
193
+ const old: i32 = toI32(bits[at])
194
+ bits[at] = toU8(on ? old | mask : old & ~mask)
195
+ }
196
+
197
+ /** The smaller of two `i64`s. */
198
+ const quicStreamMin = (a: i64, b: i64): i64 => (a < b ? a : b)
199
+
200
+ /**
201
+ * One stream slot. `id` is -1 while the slot is free. The fields are
202
+ * readable — the states, `resetCode` once the peer reset it, `stopCode` once
203
+ * it asked this side to stop — and written only by `QuicStreams`.
204
+ */
205
+ export class QuicStream {
206
+ id: i64 = -1
207
+ /** The receive buffer: byte `o` of the stream at `o % size`, its bit in `recvHave` set while unread. */
208
+ recvBuf: u8[]
209
+ recvHave: u8[]
210
+ /** How far the application has read. */
211
+ recvRead: i64 = 0
212
+ /** Every byte below this has arrived. */
213
+ recvContiguous: i64 = 0
214
+ /** The highest offset any frame reached, which flow control counts. */
215
+ recvHighest: i64 = 0
216
+ /** The final size once a FIN or RESET_STREAM fixed it, or -1. */
217
+ recvFinal: i64 = -1
218
+ /** The credit this side gave: no byte at or past this offset may arrive. */
219
+ recvLimit: i64 = 0
220
+ /** The peer's RESET_STREAM error code, or -1. */
221
+ resetCode: i64 = -1
222
+ /** The STOP_SENDING error code this side asked with, or -1. */
223
+ stopSendingCode: i64 = -1
224
+ /** The send buffer: byte `o` at `o % size`, its bit in `sendAcked` set once acknowledged and not yet released. */
225
+ sendBuf: u8[]
226
+ sendAcked: u8[]
227
+ /** Every byte below this was acknowledged, and its room released. */
228
+ sendBase: i64 = 0
229
+ /** The next byte never sent: the stream's credit and the connection's count up to here. */
230
+ sendNext: i64 = 0
231
+ /** One past the last byte written. */
232
+ sendEnd: i64 = 0
233
+ /** The credit the peer gave: no byte at or past this offset may be sent. */
234
+ sendLimit: i64 = 0
235
+ /** Bytes a lost packet carried, `[resendLow, resendHigh)`, or -1; `resendFin` when the FIN was among them. */
236
+ resendLow: i64 = -1
237
+ resendHigh: i64 = -1
238
+ /** The error code of this side's RESET_STREAM, and its final size. */
239
+ resetSentCode: i64 = 0
240
+ resetFinal: i64 = 0
241
+ /** The peer's STOP_SENDING error code, or -1. */
242
+ stopCode: i64 = -1
243
+ /** The limit the last STREAM_DATA_BLOCKED named, or -1. */
244
+ blockedAt: i64 = -1
245
+ sendState: i32 = 0
246
+ recvState: i32 = 0
247
+ /** Whether the application wrote the FIN, a frame carried it, and the peer acknowledged it. */
248
+ finQueued: boolean = false
249
+ finSent: boolean = false
250
+ finAcked: boolean = false
251
+ resendFin: boolean = false
252
+ /** What this stream owes the peer: MAX_STREAM_DATA, STREAM_DATA_BLOCKED, RESET_STREAM, STOP_SENDING. */
253
+ maxDataOwed: boolean = false
254
+ blockedOwed: boolean = false
255
+ resetOwed: boolean = false
256
+ stopOwed: boolean = false
257
+ /** Whether a write was cut short for room, so freed room is an event. */
258
+ wantsWrite: boolean = false
259
+ /** Whether the slot is in the event ring. */
260
+ queued: boolean = false
261
+
262
+ constructor(bufferSize: i32) {
263
+ this.recvBuf = new Array<u8>(bufferSize)
264
+ this.recvHave = new Array<u8>((bufferSize + 7) >> 3)
265
+ this.sendBuf = new Array<u8>(bufferSize)
266
+ this.sendAcked = new Array<u8>((bufferSize + 7) >> 3)
267
+ }
268
+
269
+ /** The buffer index of stream offset `offset`. */
270
+ at(offset: i64): i32 {
271
+ const size: i32 = toI32(this.recvBuf.length)
272
+ return size > 0 ? toI32(offset % toI64(size)) : 0
273
+ }
274
+
275
+ /** How many bytes can be read now. */
276
+ readable(): i64 {
277
+ return this.recvContiguous - this.recvRead
278
+ }
279
+
280
+ /** How many more bytes the send buffer takes. */
281
+ room(): i64 {
282
+ return toI64(toI32(this.sendBuf.length)) - (this.sendEnd - this.sendBase)
283
+ }
284
+
285
+ /** Whether the sending side has a frame to go: lost bytes, new bytes, or a FIN not yet sent. */
286
+ wantsToSend(): boolean {
287
+ if (this.sendState === QUIC_SEND_NONE || this.sendState >= QUIC_SEND_DATA_RECVD) {
288
+ return false
289
+ }
290
+ return (
291
+ this.resendLow >= 0 ||
292
+ this.resendFin ||
293
+ this.sendNext < this.sendEnd ||
294
+ (this.finQueued && !this.finSent)
295
+ )
296
+ }
297
+
298
+ /** Whether both sides are done, so the slot can be freed (§3.3). */
299
+ finished(): boolean {
300
+ const sendDone: boolean =
301
+ this.sendState === QUIC_SEND_NONE ||
302
+ this.sendState === QUIC_SEND_DATA_RECVD ||
303
+ this.sendState === QUIC_SEND_RESET_RECVD
304
+ const recvDone: boolean =
305
+ this.recvState === QUIC_RECV_NONE ||
306
+ this.recvState === QUIC_RECV_DATA_READ ||
307
+ this.recvState === QUIC_RECV_RESET_READ
308
+ return sendDone && recvDone
309
+ }
310
+
311
+ /** Empties the slot for stream `id`, with `recvLimit` and `sendLimit` its first credits; the buffers' bits are cleared. */
312
+ open(id: i64, recvLimit: i64, sendLimit: i64): void {
313
+ this.id = id
314
+ this.recvHave.fill(toU8(0))
315
+ this.sendAcked.fill(toU8(0))
316
+ this.recvRead = 0
317
+ this.recvContiguous = 0
318
+ this.recvHighest = 0
319
+ this.recvFinal = -1
320
+ this.recvLimit = recvLimit
321
+ this.resetCode = -1
322
+ this.stopSendingCode = -1
323
+ this.sendBase = 0
324
+ this.sendNext = 0
325
+ this.sendEnd = 0
326
+ this.sendLimit = sendLimit
327
+ this.resendLow = -1
328
+ this.resendHigh = -1
329
+ this.resetSentCode = 0
330
+ this.resetFinal = 0
331
+ this.stopCode = -1
332
+ this.blockedAt = -1
333
+ // A unidirectional stream has one side (§2.1).
334
+ const uni: boolean = quicStreamIsUni(id)
335
+ const local: boolean = quicStreamIsLocal(id)
336
+ this.sendState = uni && !local ? QUIC_SEND_NONE : QUIC_SEND_READY
337
+ this.recvState = uni && local ? QUIC_RECV_NONE : QUIC_RECV_RECV
338
+ this.finQueued = false
339
+ this.finSent = false
340
+ this.finAcked = false
341
+ this.resendFin = false
342
+ this.maxDataOwed = false
343
+ this.blockedOwed = false
344
+ this.resetOwed = false
345
+ this.stopOwed = false
346
+ this.wantsWrite = false
347
+ }
348
+
349
+ /** Merges `[offset, offset + length)` into the range to send again, with the FIN when it was lost too. */
350
+ resend(offset: i64, length: i32, fin: boolean): void {
351
+ if (this.sendState === QUIC_SEND_NONE || this.sendState >= QUIC_SEND_DATA_RECVD) {
352
+ return
353
+ }
354
+ // Bytes below the buffer's start were acknowledged and their room reused.
355
+ const low: i64 = offset < this.sendBase ? this.sendBase : offset
356
+ const high: i64 = offset + toI64(length)
357
+ if (high > low) {
358
+ if (this.resendLow < 0) {
359
+ this.resendLow = low
360
+ this.resendHigh = high
361
+ } else {
362
+ if (low < this.resendLow) {
363
+ this.resendLow = low
364
+ }
365
+ if (high > this.resendHigh) {
366
+ this.resendHigh = high
367
+ }
368
+ }
369
+ }
370
+ // A FIN the peer acknowledged through another copy needs no resend.
371
+ this.resendFin = this.resendFin || (fin && !this.finAcked)
372
+ }
373
+ }
374
+
375
+ /**
376
+ * The streams of one connection: their slots, the index by ID, the limits
377
+ * both ways, the connection's flow control both ways, and the ring of
378
+ * streams with something new for the application (`nextEvent`). The
379
+ * counters are readable for a test or a log.
380
+ */
381
+ export class QuicStreams {
382
+ slots: QuicStream[]
383
+ /** Open addressing by ID: `index[h]` is a slot plus one, 0 for empty. */
384
+ index: i32[]
385
+ /** Slots with news for the application, `eventCount` of them from `eventHead`. */
386
+ events: i32[]
387
+ /**
388
+ * The free slots, a stack of `freeCount`, so a stream opens without a
389
+ * scan: a frame naming a high stream ID opens every lower one (§3.2), and
390
+ * each of those costs one pop, not a pass over the table.
391
+ */
392
+ free: i32[]
393
+ /** The configured limits on the client's streams at once and this side's own. */
394
+ maxBidi: i64 = 0
395
+ maxUni: i64 = 0
396
+ localStreams: i64 = 0
397
+ /** How many of each type the client opened, the limit advertised, and how many of them finished. */
398
+ peerBidiOpened: i64 = 0
399
+ peerUniOpened: i64 = 0
400
+ peerBidiLimit: i64 = 0
401
+ peerUniLimit: i64 = 0
402
+ peerBidiClosed: i64 = 0
403
+ peerUniClosed: i64 = 0
404
+ /** How many of each type this side opened, the client's limits, and how many of this side's are open. */
405
+ localBidiOpened: i64 = 0
406
+ localUniOpened: i64 = 0
407
+ localBidiLimit: i64 = 0
408
+ localUniLimit: i64 = 0
409
+ localOpen: i64 = 0
410
+ /** The peer's credit for each stream type as it opens (its transport parameters). */
411
+ peerBidiLocalCredit: i64 = 0
412
+ peerBidiRemoteCredit: i64 = 0
413
+ peerUniCredit: i64 = 0
414
+ /** The connection's receive credit: what was advertised, its window, what arrived, and what was read. */
415
+ recvMaxData: i64 = 0
416
+ windowData: i64 = 0
417
+ recvTotal: i64 = 0
418
+ recvConsumed: i64 = 0
419
+ /** The connection's send credit from the peer, what was sent against it, and what is written but not sent. */
420
+ sendMaxData: i64 = 0
421
+ sendTotal: i64 = 0
422
+ writtenTotal: i64 = 0
423
+ /** The transport error `locate` answered -2 for. */
424
+ locateError: i64 = 0
425
+ /** The limit the last DATA_BLOCKED named, or -1. */
426
+ dataBlockedAt: i64 = -1
427
+ /** The limit each STREAMS_BLOCKED named, or -1. */
428
+ streamsBlockedBidiAt: i64 = -1
429
+ streamsBlockedUniAt: i64 = -1
430
+ /** What the last `putNextChunk` or `putNextControl` wrote, for the packet's record. */
431
+ lastId: i64 = 0
432
+ lastOffset: i64 = 0
433
+ lastValue: i64 = 0
434
+ /** The bytes of each stream's buffer, each way. */
435
+ bufferSize: i32 = 0
436
+ lastLength: i32 = 0
437
+ lastKind: i32 = 0
438
+ eventHead: i32 = 0
439
+ eventCount: i32 = 0
440
+ /** How many slots `free` holds. */
441
+ freeCount: i32 = 0
442
+ /** Where the next packet's search for a stream to send starts, and how many it has looked at. */
443
+ cursor: i32 = 0
444
+ scanned: i32 = 0
445
+ /** Whether the last `putNextChunk` carried the FIN, for the packet's record. */
446
+ lastFin: boolean = false
447
+ /** Whether some stream may owe a control frame, so `putNextControl` looks at the slots at all. */
448
+ streamOwes: boolean = false
449
+ /** What the connection owes: MAX_DATA, DATA_BLOCKED, MAX_STREAMS and STREAMS_BLOCKED of each type. */
450
+ maxDataOwed: boolean = false
451
+ dataBlockedOwed: boolean = false
452
+ maxStreamsBidiOwed: boolean = false
453
+ maxStreamsUniOwed: boolean = false
454
+ streamsBlockedBidiOwed: boolean = false
455
+ streamsBlockedUniOwed: boolean = false
456
+
457
+ /**
458
+ * The table for a connection that lets the client open `maxBidi`
459
+ * bidirectional and `maxUni` unidirectional streams at once and opens
460
+ * `localStreams` of its own, each stream with `bufferSize` bytes each way,
461
+ * and a connection receive window of `windowData` bytes.
462
+ */
463
+ constructor(maxBidi: i64, maxUni: i64, localStreams: i64, bufferSize: i32, windowData: i64) {
464
+ const count: i32 = toI32(maxBidi + maxUni + localStreams)
465
+ this.slots = []
466
+ for (let k: i32 = 0; k < count; k += 1) {
467
+ this.slots.push(new QuicStream(bufferSize))
468
+ }
469
+ let size: i32 = 4
470
+ while (size < count * 2) {
471
+ size = size * 2
472
+ }
473
+ this.index = new Array<i32>(size)
474
+ this.events = new Array<i32>(count > 0 ? count : QUIC_STREAM_ONE)
475
+ this.free = new Array<i32>(count > 0 ? count : QUIC_STREAM_ONE)
476
+ this.bufferSize = bufferSize
477
+ this.maxBidi = maxBidi
478
+ this.maxUni = maxUni
479
+ this.localStreams = localStreams
480
+ this.windowData = windowData
481
+ this.reset()
482
+ }
483
+
484
+ /** Frees every slot and puts every counter back, for a connection slot reused for another peer. */
485
+ reset(): void {
486
+ for (const stream of this.slots) {
487
+ stream.id = -1
488
+ stream.queued = false
489
+ }
490
+ // Slot 0 on top, so a fresh connection fills the table in order.
491
+ const count: i32 = toI32(this.slots.length)
492
+ this.freeCount = 0
493
+ for (let k: i32 = count - 1; k >= 0; k -= 1) {
494
+ this.pushFree(k)
495
+ }
496
+ this.index.fill(0)
497
+ this.eventHead = 0
498
+ this.eventCount = 0
499
+ this.peerBidiOpened = 0
500
+ this.peerUniOpened = 0
501
+ this.peerBidiLimit = this.maxBidi
502
+ this.peerUniLimit = this.maxUni
503
+ this.peerBidiClosed = 0
504
+ this.peerUniClosed = 0
505
+ this.localBidiOpened = 0
506
+ this.localUniOpened = 0
507
+ this.localBidiLimit = 0
508
+ this.localUniLimit = 0
509
+ this.localOpen = 0
510
+ this.peerBidiLocalCredit = 0
511
+ this.peerBidiRemoteCredit = 0
512
+ this.peerUniCredit = 0
513
+ this.recvMaxData = this.windowData
514
+ this.recvTotal = 0
515
+ this.recvConsumed = 0
516
+ this.sendMaxData = 0
517
+ this.sendTotal = 0
518
+ this.writtenTotal = 0
519
+ this.dataBlockedAt = -1
520
+ this.streamsBlockedBidiAt = -1
521
+ this.streamsBlockedUniAt = -1
522
+ this.cursor = 0
523
+ this.scanned = 0
524
+ this.streamOwes = false
525
+ this.maxDataOwed = false
526
+ this.dataBlockedOwed = false
527
+ this.maxStreamsBidiOwed = false
528
+ this.maxStreamsUniOwed = false
529
+ this.streamsBlockedBidiOwed = false
530
+ this.streamsBlockedUniOwed = false
531
+ }
532
+
533
+ /**
534
+ * Takes the peer's transport parameters (RFC 9000 §18.2): its limits on
535
+ * the streams this side opens, the credit each kind of stream starts with,
536
+ * and the connection's.
537
+ */
538
+ setPeerLimits(
539
+ maxData: i64,
540
+ bidiLocal: i64,
541
+ bidiRemote: i64,
542
+ uni: i64,
543
+ maxStreamsBidi: i64,
544
+ maxStreamsUni: i64
545
+ ): void {
546
+ this.sendMaxData = maxData
547
+ this.peerBidiLocalCredit = bidiLocal
548
+ this.peerBidiRemoteCredit = bidiRemote
549
+ this.peerUniCredit = uni
550
+ this.localBidiLimit = maxStreamsBidi
551
+ this.localUniLimit = maxStreamsUni
552
+ }
553
+
554
+ /** The hash bucket `id` starts its probe at. */
555
+ bucket(id: i64): i32 {
556
+ const mixed: i64 = (id >> 2) ^ (id << 3) ^ (id >> 13)
557
+ return toI32(mixed & toI64(toI32(this.index.length) - 1))
558
+ }
559
+
560
+ /** The slot of stream `id`, or -1 when it is not open. */
561
+ slotOf(id: i64): i32 {
562
+ const size: i32 = toI32(this.index.length)
563
+ let h: i32 = this.bucket(id)
564
+ for (let probe: i32 = 0; probe < size; probe += 1) {
565
+ if (h < 0 || h >= size) {
566
+ return -1
567
+ }
568
+ const entry: i32 = this.index[h] - 1
569
+ if (entry < 0) {
570
+ return -1
571
+ }
572
+ if (entry < toI32(this.slots.length) && this.slots[entry].id === id) {
573
+ return entry
574
+ }
575
+ h = (h + 1) & (size - 1)
576
+ }
577
+ return -1
578
+ }
579
+
580
+ /** The stream `id`, or `null` when it is not open. */
581
+ find(id: i64): QuicStream | null {
582
+ const k: i32 = this.slotOf(id)
583
+ return k >= 0 && k < toI32(this.slots.length) ? this.slots[k] : null
584
+ }
585
+
586
+ /** Puts slot `k` in the index under its stream's ID. */
587
+ indexInsert(k: i32, id: i64): void {
588
+ const size: i32 = toI32(this.index.length)
589
+ let h: i32 = this.bucket(id)
590
+ for (let probe: i32 = 0; probe < size; probe += 1) {
591
+ if (h < 0 || h >= size) {
592
+ return
593
+ }
594
+ if (this.index[h] === 0) {
595
+ this.index[h] = k + 1
596
+ return
597
+ }
598
+ h = (h + 1) & (size - 1)
599
+ }
600
+ }
601
+
602
+ /** Takes slot `k`, holding `id`, out of the index, moving later entries of its probe run back so none is lost. */
603
+ indexRemove(k: i32, id: i64): void {
604
+ const size: i32 = toI32(this.index.length)
605
+ const mask: i32 = size - 1
606
+ let h: i32 = this.bucket(id)
607
+ let found: i32 = -1
608
+ for (let probe: i32 = 0; probe < size && h >= 0 && h < size; probe += 1) {
609
+ if (this.index[h] === 0) {
610
+ return
611
+ }
612
+ if (this.index[h] === k + 1) {
613
+ found = h
614
+ break
615
+ }
616
+ h = (h + 1) & mask
617
+ }
618
+ if (found < 0) {
619
+ return
620
+ }
621
+ let hole: i32 = found
622
+ let next: i32 = (found + 1) & mask
623
+ for (
624
+ let probe: i32 = 0;
625
+ probe < size && next >= 0 && next < size && hole >= 0 && hole < size;
626
+ probe += 1
627
+ ) {
628
+ const entry: i32 = this.index[next] - 1
629
+ if (entry < 0) {
630
+ break
631
+ }
632
+ const home: i32 = entry < toI32(this.slots.length) ? this.bucket(this.slots[entry].id) : next
633
+ // The entry may move into the hole when its home is not between the hole and it.
634
+ const distanceNext: i32 = (next - home) & mask
635
+ const distanceHole: i32 = (hole - home) & mask
636
+ if (distanceHole < distanceNext) {
637
+ this.index[hole] = this.index[next]
638
+ hole = next
639
+ }
640
+ next = (next + 1) & mask
641
+ }
642
+ if (hole >= 0 && hole < size) {
643
+ this.index[hole] = 0
644
+ }
645
+ }
646
+
647
+ /** Puts slot `k` on the event ring, unless it is there already. */
648
+ queue(k: i32): void {
649
+ const size: i32 = toI32(this.events.length)
650
+ if (k < 0 || k >= toI32(this.slots.length) || this.slots[k].queued || this.eventCount >= size) {
651
+ return
652
+ }
653
+ const at: i32 = (this.eventHead + this.eventCount) % size
654
+ if (at >= 0 && at < size) {
655
+ this.events[at] = k
656
+ this.eventCount = this.eventCount + 1
657
+ this.slots[k].queued = true
658
+ }
659
+ }
660
+
661
+ /**
662
+ * The ID of the next stream with something new for the application — a
663
+ * stream the peer opened, bytes or a FIN to read, a reset, a STOP_SENDING,
664
+ * or room to write again after a short write — or -1 when there is none.
665
+ * Each stream comes up once however much happened to it; read and write it
666
+ * until it has nothing more to say.
667
+ */
668
+ nextEvent(): i64 {
669
+ const size: i32 = toI32(this.events.length)
670
+ while (this.eventCount > 0 && size > 0) {
671
+ const at: i32 = this.eventHead
672
+ this.eventHead = (at + 1) % size
673
+ this.eventCount = this.eventCount - 1
674
+ const k: i32 = at >= 0 && at < size ? this.events[at] : -1
675
+ if (k >= 0 && k < toI32(this.slots.length)) {
676
+ const stream: QuicStream = this.slots[k]
677
+ stream.queued = false
678
+ if (stream.id >= 0) {
679
+ return stream.id
680
+ }
681
+ }
682
+ }
683
+ return -1
684
+ }
685
+
686
+ /** Puts slot `k` on the free stack. */
687
+ pushFree(k: i32): void {
688
+ const at: i32 = this.freeCount
689
+ if (at >= 0 && at < toI32(this.free.length)) {
690
+ this.free[at] = k
691
+ this.freeCount = at + 1
692
+ }
693
+ }
694
+
695
+ /** Takes a free slot off the stack, or -1 when every slot is in use. */
696
+ takeFree(): i32 {
697
+ const at: i32 = this.freeCount - 1
698
+ if (at < 0 || at >= toI32(this.free.length)) {
699
+ return -1
700
+ }
701
+ this.freeCount = at
702
+ return this.free[at]
703
+ }
704
+
705
+ /** Opens `id` in a free slot with its first credits; answers the slot, or -1 when none is free. */
706
+ openSlot(id: i64): i32 {
707
+ const k: i32 = this.takeFree()
708
+ if (k < 0 || k >= toI32(this.slots.length)) {
709
+ return -1
710
+ }
711
+ const local: boolean = quicStreamIsLocal(id)
712
+ const uni: boolean = quicStreamIsUni(id)
713
+ const buffer: i64 = toI64(this.bufferSize)
714
+ // This side's credit: a whole buffer, for every stream it receives on,
715
+ // and none on its own unidirectional streams, which it only sends on.
716
+ const recvLimit: i64 = local && uni ? QUIC_STREAM_ZERO : buffer
717
+ let sendLimit: i64 = this.peerBidiLocalCredit
718
+ if (local) {
719
+ sendLimit = uni ? this.peerUniCredit : this.peerBidiRemoteCredit
720
+ }
721
+ this.slots[k].open(id, recvLimit, sendLimit)
722
+ this.indexInsert(k, id)
723
+ return k
724
+ }
725
+
726
+ /**
727
+ * Frees slot `k` once both of its stream's sides are done (§3.3). A stream
728
+ * the peer opened counts toward raising its limit: MAX_STREAMS is owed
729
+ * once half the configured number have finished since the last one.
730
+ */
731
+ release(k: i32): void {
732
+ if (k < 0 || k >= toI32(this.slots.length)) {
733
+ return
734
+ }
735
+ const stream: QuicStream = this.slots[k]
736
+ if (stream.id < 0 || !stream.finished()) {
737
+ return
738
+ }
739
+ const id: i64 = stream.id
740
+ this.indexRemove(k, id)
741
+ stream.id = -1
742
+ this.pushFree(k)
743
+ if (quicStreamIsLocal(id)) {
744
+ this.localOpen = this.localOpen - 1
745
+ return
746
+ }
747
+ if (quicStreamIsUni(id)) {
748
+ this.peerUniClosed = this.peerUniClosed + 1
749
+ if (this.peerUniClosed + this.maxUni - this.peerUniLimit >= (this.maxUni + 1) / 2) {
750
+ this.peerUniLimit = this.peerUniClosed + this.maxUni
751
+ this.maxStreamsUniOwed = true
752
+ }
753
+ return
754
+ }
755
+ this.peerBidiClosed = this.peerBidiClosed + 1
756
+ if (this.peerBidiClosed + this.maxBidi - this.peerBidiLimit >= (this.maxBidi + 1) / 2) {
757
+ this.peerBidiLimit = this.peerBidiClosed + this.maxBidi
758
+ this.maxStreamsBidiOwed = true
759
+ }
760
+ }
761
+
762
+ /**
763
+ * The slot a frame for stream `id` refers to (§3.2, §4.6, §19.8): opening
764
+ * it, and every lower stream of its type, when it is the peer's and new.
765
+ * `receiving` says whether the frame is about the stream's receiving half
766
+ * (STREAM, RESET_STREAM, STREAM_DATA_BLOCKED) or its sending half
767
+ * (STOP_SENDING, MAX_STREAM_DATA). Answers the slot; -1 for a stream that
768
+ * finished and is gone, whose frames are ignored; or -2 with `locateError`
769
+ * holding STREAM_LIMIT_ERROR for the peer's stream past the limit, or
770
+ * STREAM_STATE_ERROR for one of this side's that it has not opened or a
771
+ * unidirectional stream without the half the frame is about (§19.4-§19.13).
772
+ */
773
+ locate(id: i64, receiving: boolean): i32 {
774
+ if (quicStreamIsUni(id) && quicStreamIsLocal(id) === receiving) {
775
+ this.locateError = QUIC_ERROR_STREAM_STATE
776
+ return -2
777
+ }
778
+ const sequence: i64 = id >> 2
779
+ const uni: boolean = quicStreamIsUni(id)
780
+ if (quicStreamIsLocal(id)) {
781
+ const opened: i64 = uni ? this.localUniOpened : this.localBidiOpened
782
+ if (sequence >= opened) {
783
+ this.locateError = QUIC_ERROR_STREAM_STATE
784
+ return -2
785
+ }
786
+ return this.slotOf(id)
787
+ }
788
+ const limit: i64 = uni ? this.peerUniLimit : this.peerBidiLimit
789
+ if (sequence >= limit) {
790
+ this.locateError = QUIC_ERROR_STREAM_LIMIT
791
+ return -2
792
+ }
793
+ let opened: i64 = uni ? this.peerUniOpened : this.peerBidiOpened
794
+ if (sequence < opened) {
795
+ return this.slotOf(id)
796
+ }
797
+ // §3.2: a stream opens every lower one of its type with it.
798
+ let found: i32 = -1
799
+ while (opened <= sequence) {
800
+ const next: i64 = (opened << 2) | (id & 3)
801
+ const k: i32 = this.openSlot(next)
802
+ opened = opened + 1
803
+ if (k >= 0) {
804
+ this.queue(k)
805
+ found = k
806
+ }
807
+ }
808
+ if (uni) {
809
+ this.peerUniOpened = opened
810
+ } else {
811
+ this.peerBidiOpened = opened
812
+ }
813
+ return found
814
+ }
815
+
816
+ /**
817
+ * Counts the peer's bytes on `stream` up to `end` against the credit this
818
+ * side gave, for the stream and for the connection (§4.1): past either it
819
+ * is FLOW_CONTROL_ERROR. Answers 0 or that.
820
+ */
821
+ creditReceived(stream: QuicStream, end: i64): i64 {
822
+ if (end > stream.recvLimit) {
823
+ return QUIC_ERROR_FLOW_CONTROL
824
+ }
825
+ if (end > stream.recvHighest) {
826
+ this.recvTotal = this.recvTotal + (end - stream.recvHighest)
827
+ stream.recvHighest = end
828
+ if (this.recvTotal > this.recvMaxData) {
829
+ return QUIC_ERROR_FLOW_CONTROL
830
+ }
831
+ }
832
+ return QUIC_ERROR_NO_ERROR
833
+ }
834
+
835
+ /**
836
+ * A STREAM frame: `length` bytes of `buf` from `from`, at stream offset
837
+ * `offset`, and the FIN when `fin`. Answers 0, or the transport error that
838
+ * closes the connection: the stream's ID (see `locate`), a stream only this
839
+ * side sends on, the final size, or the credit.
840
+ */
841
+ onStream(id: i64, offset: i64, buf: u8[], from: i32, length: i32, fin: boolean): i64 {
842
+ const k: i32 = this.locate(id, true)
843
+ if (k === -2) {
844
+ return this.locateError
845
+ }
846
+ if (k < 0 || k >= toI32(this.slots.length)) {
847
+ return QUIC_ERROR_NO_ERROR
848
+ }
849
+ const stream: QuicStream = this.slots[k]
850
+ const end: i64 = offset + toI64(length)
851
+ if (stream.recvFinal >= 0 && (end > stream.recvFinal || (fin && end !== stream.recvFinal))) {
852
+ return QUIC_ERROR_FINAL_SIZE
853
+ }
854
+ if (fin && end < stream.recvHighest) {
855
+ return QUIC_ERROR_FINAL_SIZE
856
+ }
857
+ const credit: i64 = this.creditReceived(stream, end)
858
+ if (credit !== QUIC_ERROR_NO_ERROR) {
859
+ return credit
860
+ }
861
+ if (stream.recvState >= QUIC_RECV_DATA_RECVD) {
862
+ // Everything arrived already, or the stream was reset: nothing new.
863
+ return QUIC_ERROR_NO_ERROR
864
+ }
865
+ if (fin) {
866
+ stream.recvFinal = end
867
+ stream.recvState = QUIC_RECV_SIZE_KNOWN
868
+ }
869
+ const before: i64 = stream.recvContiguous
870
+ const size: i32 = toI32(stream.recvBuf.length)
871
+ for (let k2: i32 = 0; k2 < length; k2 += 1) {
872
+ const position: i64 = offset + toI64(k2)
873
+ const src: i32 = from + k2
874
+ if (position >= stream.recvContiguous && src >= 0 && src < toI32(buf.length)) {
875
+ const slot: i32 = stream.at(position)
876
+ if (slot >= 0 && slot < size) {
877
+ stream.recvBuf[slot] = buf[src]
878
+ quicStreamSetBit(stream.recvHave, slot, true)
879
+ }
880
+ }
881
+ }
882
+ while (
883
+ stream.recvContiguous < stream.recvHighest &&
884
+ quicStreamBit(stream.recvHave, stream.at(stream.recvContiguous))
885
+ ) {
886
+ stream.recvContiguous = stream.recvContiguous + 1
887
+ }
888
+ if (stream.recvFinal >= 0 && stream.recvContiguous === stream.recvFinal) {
889
+ stream.recvState = QUIC_RECV_DATA_RECVD
890
+ }
891
+ if (stream.recvContiguous > before || fin) {
892
+ this.queue(k)
893
+ }
894
+ return QUIC_ERROR_NO_ERROR
895
+ }
896
+
897
+ /** RESET_STREAM (§19.4): the peer abandons the stream at `finalSize` with `code`. Answers 0 or the transport error. */
898
+ onReset(id: i64, code: i64, finalSize: i64): i64 {
899
+ const k: i32 = this.locate(id, true)
900
+ if (k === -2) {
901
+ return this.locateError
902
+ }
903
+ if (k < 0 || k >= toI32(this.slots.length)) {
904
+ return QUIC_ERROR_NO_ERROR
905
+ }
906
+ const stream: QuicStream = this.slots[k]
907
+ if ((stream.recvFinal >= 0 && stream.recvFinal !== finalSize) || finalSize < stream.recvHighest) {
908
+ return QUIC_ERROR_FINAL_SIZE
909
+ }
910
+ const credit: i64 = this.creditReceived(stream, finalSize)
911
+ if (credit !== QUIC_ERROR_NO_ERROR) {
912
+ return credit
913
+ }
914
+ stream.recvFinal = finalSize
915
+ if (stream.recvState === QUIC_RECV_DATA_READ || stream.recvState >= QUIC_RECV_RESET_RECVD) {
916
+ return QUIC_ERROR_NO_ERROR
917
+ }
918
+ // §4.5: a reset stream's bytes count as consumed for the connection.
919
+ this.consume(finalSize - stream.recvRead)
920
+ stream.recvRead = finalSize
921
+ stream.recvContiguous = finalSize
922
+ stream.recvHave.fill(toU8(0))
923
+ stream.recvState = QUIC_RECV_RESET_RECVD
924
+ stream.resetCode = code
925
+ stream.maxDataOwed = false
926
+ stream.stopOwed = false
927
+ this.queue(k)
928
+ return QUIC_ERROR_NO_ERROR
929
+ }
930
+
931
+ /**
932
+ * STOP_SENDING (§19.5): the peer wants no more of the stream, which this
933
+ * side answers with RESET_STREAM carrying the same code (§3.5). Answers 0
934
+ * or the transport error.
935
+ */
936
+ onStopSending(id: i64, code: i64): i64 {
937
+ const k: i32 = this.locate(id, false)
938
+ if (k === -2) {
939
+ return this.locateError
940
+ }
941
+ if (k < 0 || k >= toI32(this.slots.length)) {
942
+ return QUIC_ERROR_NO_ERROR
943
+ }
944
+ const stream: QuicStream = this.slots[k]
945
+ if (stream.stopCode < 0) {
946
+ stream.stopCode = code
947
+ this.resetSlot(k, code)
948
+ this.queue(k)
949
+ }
950
+ return QUIC_ERROR_NO_ERROR
951
+ }
952
+
953
+ /** MAX_STREAM_DATA (§19.10): more credit for the stream. Answers 0 or the transport error. */
954
+ onMaxStreamData(id: i64, limit: i64): i64 {
955
+ const k: i32 = this.locate(id, false)
956
+ if (k === -2) {
957
+ return this.locateError
958
+ }
959
+ if (k >= 0 && k < toI32(this.slots.length)) {
960
+ const stream: QuicStream = this.slots[k]
961
+ if (limit > stream.sendLimit) {
962
+ stream.sendLimit = limit
963
+ stream.blockedOwed = false
964
+ if (stream.wantsWrite || stream.sendNext < stream.sendEnd) {
965
+ this.queue(k)
966
+ }
967
+ }
968
+ }
969
+ return QUIC_ERROR_NO_ERROR
970
+ }
971
+
972
+ /** STREAM_DATA_BLOCKED (§19.13): only the ID is checked; credit follows reading, not asking. */
973
+ onStreamDataBlocked(id: i64): i64 {
974
+ const k: i32 = this.locate(id, true)
975
+ return k === -2 ? this.locateError : QUIC_ERROR_NO_ERROR
976
+ }
977
+
978
+ /** MAX_DATA (§19.9): more credit for the connection. */
979
+ onMaxData(limit: i64): void {
980
+ if (limit > this.sendMaxData) {
981
+ this.sendMaxData = limit
982
+ this.dataBlockedOwed = false
983
+ }
984
+ }
985
+
986
+ /** MAX_STREAMS (§19.11): the peer lets this side open more streams of a type. */
987
+ onMaxStreams(uni: boolean, limit: i64): void {
988
+ if (uni && limit > this.localUniLimit) {
989
+ this.localUniLimit = limit
990
+ this.streamsBlockedUniOwed = false
991
+ } else if (!uni && limit > this.localBidiLimit) {
992
+ this.localBidiLimit = limit
993
+ this.streamsBlockedBidiOwed = false
994
+ }
995
+ }
996
+
997
+ /** Counts `bytes` read (or abandoned by a reset) toward the connection's credit, raising it once half the window is used. */
998
+ consume(bytes: i64): void {
999
+ this.recvConsumed = this.recvConsumed + bytes
1000
+ if (
1001
+ this.recvConsumed + this.windowData - this.recvMaxData >= this.windowData / 2 &&
1002
+ this.windowData > 0
1003
+ ) {
1004
+ this.recvMaxData = this.recvConsumed + this.windowData
1005
+ this.maxDataOwed = true
1006
+ }
1007
+ }
1008
+
1009
+ /**
1010
+ * Opens a stream of this side's (§2.1): bidirectional when `bidi`, else
1011
+ * unidirectional. Answers its ID, or `QUIC_STREAM_ERR_LIMIT` when the
1012
+ * peer's limit is reached (STREAMS_BLOCKED is then owed) or every slot for
1013
+ * this side's streams is taken.
1014
+ */
1015
+ open(bidi: boolean): i64 {
1016
+ const opened: i64 = bidi ? this.localBidiOpened : this.localUniOpened
1017
+ const limit: i64 = bidi ? this.localBidiLimit : this.localUniLimit
1018
+ if (opened >= limit) {
1019
+ if (bidi && this.streamsBlockedBidiAt !== limit) {
1020
+ this.streamsBlockedBidiOwed = true
1021
+ } else if (!bidi && this.streamsBlockedUniAt !== limit) {
1022
+ this.streamsBlockedUniOwed = true
1023
+ }
1024
+ return toI64(QUIC_STREAM_ERR_LIMIT)
1025
+ }
1026
+ if (this.localOpen >= this.localStreams || opened >= QUIC_STREAM_MAX_COUNT) {
1027
+ return toI64(QUIC_STREAM_ERR_LIMIT)
1028
+ }
1029
+ const id: i64 = (opened << 2) | (bidi ? 1 : 3)
1030
+ if (this.openSlot(id) < 0) {
1031
+ return toI64(QUIC_STREAM_ERR_LIMIT)
1032
+ }
1033
+ if (bidi) {
1034
+ this.localBidiOpened = opened + 1
1035
+ } else {
1036
+ this.localUniOpened = opened + 1
1037
+ }
1038
+ this.localOpen = this.localOpen + 1
1039
+ return id
1040
+ }
1041
+
1042
+ /**
1043
+ * Writes up to `length` bytes of `buf` from `from` to stream `id`, and its
1044
+ * FIN when `fin` and every byte fit. Answers how many bytes it took — fewer
1045
+ * than `length` when the send buffer is full, and the stream's event fires
1046
+ * once acknowledgements free room — or a `QUIC_STREAM_ERR_*`.
1047
+ */
1048
+ write(id: i64, buf: u8[], from: i32, length: i32, fin: boolean): i32 {
1049
+ const k: i32 = this.slotOf(id)
1050
+ if (
1051
+ k < 0 ||
1052
+ k >= toI32(this.slots.length) ||
1053
+ from < 0 ||
1054
+ length < 0 ||
1055
+ from > toI32(buf.length) - length
1056
+ ) {
1057
+ return QUIC_STREAM_ERR_UNKNOWN
1058
+ }
1059
+ const stream: QuicStream = this.slots[k]
1060
+ if (stream.sendState === QUIC_SEND_NONE) {
1061
+ return QUIC_STREAM_ERR_DIRECTION
1062
+ }
1063
+ if (stream.stopCode >= 0) {
1064
+ return QUIC_STREAM_ERR_STOPPED
1065
+ }
1066
+ if (stream.finQueued || stream.sendState >= QUIC_SEND_RESET_SENT) {
1067
+ return QUIC_STREAM_ERR_FINISHED
1068
+ }
1069
+ const room: i64 = stream.room()
1070
+ const n: i32 = toI64(length) < room ? length : toI32(room)
1071
+ const size: i32 = toI32(stream.sendBuf.length)
1072
+ for (let j: i32 = 0; j < n; j += 1) {
1073
+ const slot: i32 = stream.at(stream.sendEnd + toI64(j))
1074
+ if (slot >= 0 && slot < size && from + j >= 0 && from + j < toI32(buf.length)) {
1075
+ stream.sendBuf[slot] = buf[from + j]
1076
+ }
1077
+ }
1078
+ stream.sendEnd = stream.sendEnd + toI64(n)
1079
+ this.writtenTotal = this.writtenTotal + toI64(n)
1080
+ stream.wantsWrite = n < length
1081
+ if (fin && n === length) {
1082
+ stream.finQueued = true
1083
+ }
1084
+ if (n > 0 || stream.finQueued) {
1085
+ stream.sendState = QUIC_SEND_SEND
1086
+ }
1087
+ return n
1088
+ }
1089
+
1090
+ /**
1091
+ * Reads up to `length` bytes of stream `id` into `buf` from `at`. Answers
1092
+ * how many it read; 0 when nothing is there yet; `QUIC_STREAM_END` once
1093
+ * every byte up to the FIN was read; `QUIC_STREAM_ERR_RESET` once the peer
1094
+ * reset it; or another `QUIC_STREAM_ERR_*`. Reading half a buffer gives the
1095
+ * peer more credit. A stream's receiving side is done when a read answers
1096
+ * `QUIC_STREAM_END` or `QUIC_STREAM_ERR_RESET`, so read until one does.
1097
+ */
1098
+ read(id: i64, buf: u8[], at: i32, length: i32): i32 {
1099
+ const k: i32 = this.slotOf(id)
1100
+ if (k < 0 || k >= toI32(this.slots.length) || at < 0 || length < 0 || at > toI32(buf.length) - length) {
1101
+ return QUIC_STREAM_ERR_UNKNOWN
1102
+ }
1103
+ const stream: QuicStream = this.slots[k]
1104
+ if (stream.recvState === QUIC_RECV_NONE) {
1105
+ return QUIC_STREAM_ERR_DIRECTION
1106
+ }
1107
+ if (stream.recvState >= QUIC_RECV_RESET_RECVD) {
1108
+ stream.recvState = QUIC_RECV_RESET_READ
1109
+ this.release(k)
1110
+ return QUIC_STREAM_ERR_RESET
1111
+ }
1112
+ const available: i64 = stream.readable()
1113
+ const n: i32 = toI64(length) < available ? length : toI32(available)
1114
+ const size: i32 = toI32(stream.recvBuf.length)
1115
+ for (let j: i32 = 0; j < n; j += 1) {
1116
+ const slot: i32 = stream.at(stream.recvRead + toI64(j))
1117
+ if (slot >= 0 && slot < size && at + j >= 0 && at + j < toI32(buf.length)) {
1118
+ buf[at + j] = stream.recvBuf[slot]
1119
+ quicStreamSetBit(stream.recvHave, slot, false)
1120
+ }
1121
+ }
1122
+ stream.recvRead = stream.recvRead + toI64(n)
1123
+ if (n > 0) {
1124
+ this.consume(toI64(n))
1125
+ }
1126
+ if (
1127
+ stream.recvState === QUIC_RECV_RECV &&
1128
+ stream.recvRead + toI64(size) - stream.recvLimit >= toI64(size / 2)
1129
+ ) {
1130
+ stream.recvLimit = stream.recvRead + toI64(size)
1131
+ stream.maxDataOwed = true
1132
+ this.streamOwes = true
1133
+ }
1134
+ // The read that finds nothing left past the FIN is the one that answers
1135
+ // the end, and only then is the side done: a slot is never freed under a
1136
+ // reader that has not seen the end yet.
1137
+ if (n === 0 && stream.recvState === QUIC_RECV_DATA_RECVD && stream.recvRead === stream.recvFinal) {
1138
+ stream.recvState = QUIC_RECV_DATA_READ
1139
+ this.release(k)
1140
+ return QUIC_STREAM_END
1141
+ }
1142
+ return n
1143
+ }
1144
+
1145
+ /** Abandons the sending side of slot `k` with `code` (§3.1, §19.4): what was not sent is dropped and RESET_STREAM is owed. */
1146
+ resetSlot(k: i32, code: i64): void {
1147
+ if (k < 0 || k >= toI32(this.slots.length)) {
1148
+ return
1149
+ }
1150
+ const stream: QuicStream = this.slots[k]
1151
+ if (stream.sendState === QUIC_SEND_NONE || stream.sendState >= QUIC_SEND_DATA_RECVD) {
1152
+ return
1153
+ }
1154
+ this.writtenTotal = this.writtenTotal - (stream.sendEnd - stream.sendNext)
1155
+ stream.sendEnd = stream.sendNext
1156
+ stream.sendState = QUIC_SEND_RESET_SENT
1157
+ stream.resetSentCode = code
1158
+ stream.resetFinal = stream.sendNext
1159
+ stream.resetOwed = true
1160
+ this.streamOwes = true
1161
+ stream.resendLow = -1
1162
+ stream.resendHigh = -1
1163
+ stream.resendFin = false
1164
+ stream.blockedOwed = false
1165
+ }
1166
+
1167
+ /** Resets stream `id`'s sending side with `code`. Answers `QUIC_STREAM_OK` or a `QUIC_STREAM_ERR_*`. */
1168
+ resetStream(id: i64, code: i64): i32 {
1169
+ const k: i32 = this.slotOf(id)
1170
+ if (k < 0 || k >= toI32(this.slots.length)) {
1171
+ return QUIC_STREAM_ERR_UNKNOWN
1172
+ }
1173
+ const stream: QuicStream = this.slots[k]
1174
+ if (stream.sendState === QUIC_SEND_NONE) {
1175
+ return QUIC_STREAM_ERR_DIRECTION
1176
+ }
1177
+ if (stream.sendState >= QUIC_SEND_DATA_RECVD) {
1178
+ return QUIC_STREAM_ERR_FINISHED
1179
+ }
1180
+ this.resetSlot(k, code)
1181
+ return QUIC_STREAM_OK
1182
+ }
1183
+
1184
+ /** Asks the peer to stop sending on stream `id`, with `code` (STOP_SENDING, §3.5). Answers `QUIC_STREAM_OK` or a `QUIC_STREAM_ERR_*`. */
1185
+ stopSending(id: i64, code: i64): i32 {
1186
+ const k: i32 = this.slotOf(id)
1187
+ if (k < 0 || k >= toI32(this.slots.length)) {
1188
+ return QUIC_STREAM_ERR_UNKNOWN
1189
+ }
1190
+ const stream: QuicStream = this.slots[k]
1191
+ if (stream.recvState === QUIC_RECV_NONE) {
1192
+ return QUIC_STREAM_ERR_DIRECTION
1193
+ }
1194
+ if (stream.recvState >= QUIC_RECV_DATA_RECVD) {
1195
+ return QUIC_STREAM_ERR_FINISHED
1196
+ }
1197
+ if (stream.stopSendingCode < 0) {
1198
+ stream.stopSendingCode = code
1199
+ stream.stopOwed = true
1200
+ this.streamOwes = true
1201
+ }
1202
+ return QUIC_STREAM_OK
1203
+ }
1204
+
1205
+ /** Starts a packet: the next `putNextChunk` calls look at each stream at most once. */
1206
+ beginPacket(): void {
1207
+ this.scanned = 0
1208
+ }
1209
+
1210
+ /**
1211
+ * Writes one STREAM frame into `buf[at .. end)` for the next stream, in
1212
+ * turn, that has one to send: its lost bytes first, else as many new ones
1213
+ * as fit and both credits allow, with the FIN when the rest fits. Answers
1214
+ * the offset past it, with `lastId`, `lastOffset`, `lastLength` and
1215
+ * `lastFin` describing it for the packet's record; or `at` when no stream
1216
+ * has a frame that fits.
1217
+ */
1218
+ putNextChunk(buf: u8[], at: i32, end: i32): i32 {
1219
+ const count: i32 = toI32(this.slots.length)
1220
+ while (this.scanned < count && count > 0) {
1221
+ const k: i32 = this.cursor % count
1222
+ this.cursor = (k + 1) % count
1223
+ this.scanned = this.scanned + 1
1224
+ if (k >= 0 && k < count) {
1225
+ const stream: QuicStream = this.slots[k]
1226
+ if (stream.id >= 0 && stream.wantsToSend()) {
1227
+ const next: i32 =
1228
+ stream.resendLow >= 0 || stream.resendFin
1229
+ ? this.putResend(stream, buf, at, end)
1230
+ : this.putNew(stream, k, buf, at, end)
1231
+ if (next > at) {
1232
+ return next
1233
+ }
1234
+ }
1235
+ }
1236
+ }
1237
+ return at
1238
+ }
1239
+
1240
+ /**
1241
+ * One STREAM frame of what `stream` lost: its lost bytes, skipping those
1242
+ * acknowledged since, with the FIN only when they run to the final size
1243
+ * (§4.5); or, with no bytes left to send again, a lost FIN alone, as a
1244
+ * frame of no bytes at the final size.
1245
+ */
1246
+ putResend(stream: QuicStream, buf: u8[], at: i32, end: i32): i32 {
1247
+ if (stream.resendLow >= 0) {
1248
+ let low: i64 = stream.resendLow < stream.sendBase ? stream.sendBase : stream.resendLow
1249
+ while (low < stream.resendHigh && quicStreamBit(stream.sendAcked, stream.at(low))) {
1250
+ low = low + 1
1251
+ }
1252
+ const want: i32 = toI32(stream.resendHigh - low)
1253
+ if (want <= 0) {
1254
+ stream.resendLow = -1
1255
+ stream.resendHigh = -1
1256
+ } else {
1257
+ const room: i32 = end - at - quicStreamOverhead(stream.id, low, want)
1258
+ if (room <= 0) {
1259
+ return at
1260
+ }
1261
+ const n: i32 = room < want ? room : want
1262
+ const fin: boolean = stream.resendFin && n === want && low + toI64(n) === stream.sendEnd
1263
+ const next: i32 = quicPutStream(buf, at, end, stream.id, low, stream.sendBuf, stream.at(low), n, fin)
1264
+ if (next < 0) {
1265
+ return at
1266
+ }
1267
+ this.noteChunk(stream.id, low, n, fin)
1268
+ if (n === want) {
1269
+ stream.resendLow = -1
1270
+ stream.resendHigh = -1
1271
+ } else {
1272
+ stream.resendLow = low + toI64(n)
1273
+ }
1274
+ if (fin) {
1275
+ stream.resendFin = false
1276
+ }
1277
+ return next
1278
+ }
1279
+ }
1280
+ if (!stream.resendFin) {
1281
+ return at
1282
+ }
1283
+ const next: i32 = quicPutStream(
1284
+ buf,
1285
+ at,
1286
+ end,
1287
+ stream.id,
1288
+ stream.sendEnd,
1289
+ stream.sendBuf,
1290
+ QUIC_STREAM_NO_BYTES,
1291
+ QUIC_STREAM_NO_BYTES,
1292
+ true
1293
+ )
1294
+ if (next < 0) {
1295
+ return at
1296
+ }
1297
+ this.noteChunk(stream.id, stream.sendEnd, QUIC_STREAM_NO_BYTES, true)
1298
+ stream.resendFin = false
1299
+ return next
1300
+ }
1301
+
1302
+ /** Describes the STREAM frame just written, for the packet's record. */
1303
+ noteChunk(id: i64, offset: i64, length: i32, fin: boolean): void {
1304
+ this.lastId = id
1305
+ this.lastOffset = offset
1306
+ this.lastLength = length
1307
+ this.lastFin = fin
1308
+ }
1309
+
1310
+ /**
1311
+ * One STREAM frame of `stream`'s new bytes within both credits. A stream
1312
+ * held back by a credit owes the matching BLOCKED frame, once per limit.
1313
+ */
1314
+ putNew(stream: QuicStream, k: i32, buf: u8[], at: i32, end: i32): i32 {
1315
+ const unsent: i64 = stream.sendEnd - stream.sendNext
1316
+ const streamCredit: i64 = stream.sendLimit - stream.sendNext
1317
+ const connectionCredit: i64 = this.sendMaxData - this.sendTotal
1318
+ let allowed: i64 = quicStreamMin(unsent, quicStreamMin(streamCredit, connectionCredit))
1319
+ if (allowed < 0) {
1320
+ allowed = 0
1321
+ }
1322
+ if (allowed < unsent) {
1323
+ if (streamCredit < unsent && stream.blockedAt !== stream.sendLimit) {
1324
+ stream.blockedOwed = true
1325
+ this.streamOwes = true
1326
+ }
1327
+ if (connectionCredit < unsent && this.dataBlockedAt !== this.sendMaxData) {
1328
+ this.dataBlockedOwed = true
1329
+ }
1330
+ }
1331
+ const want: i32 = toI32(allowed)
1332
+ const finNow: boolean = stream.finQueued && !stream.finSent && allowed === unsent
1333
+ if (want === 0 && !finNow) {
1334
+ return at
1335
+ }
1336
+ const room: i32 = end - at - quicStreamOverhead(stream.id, stream.sendNext, want)
1337
+ if (room < 0 || (room === 0 && want > 0)) {
1338
+ return at
1339
+ }
1340
+ const n: i32 = room < want ? room : want
1341
+ const fin: boolean = finNow && n === want
1342
+ const next: i32 = quicPutStream(
1343
+ buf,
1344
+ at,
1345
+ end,
1346
+ stream.id,
1347
+ stream.sendNext,
1348
+ stream.sendBuf,
1349
+ stream.at(stream.sendNext),
1350
+ n,
1351
+ fin
1352
+ )
1353
+ if (next < 0) {
1354
+ return at
1355
+ }
1356
+ this.noteChunk(stream.id, stream.sendNext, n, fin)
1357
+ stream.sendNext = stream.sendNext + toI64(n)
1358
+ this.sendTotal = this.sendTotal + toI64(n)
1359
+ if (fin) {
1360
+ stream.finSent = true
1361
+ stream.sendState = QUIC_SEND_DATA_SENT
1362
+ }
1363
+ if (k >= 0 && stream.wantsWrite && stream.room() > 0) {
1364
+ this.queue(k)
1365
+ }
1366
+ return next
1367
+ }
1368
+
1369
+ /**
1370
+ * Writes the next control frame owed, if it fits in `buf[at .. end)`:
1371
+ * MAX_DATA, the MAX_STREAMS and BLOCKED frames, then each stream's
1372
+ * MAX_STREAM_DATA, STREAM_DATA_BLOCKED, RESET_STREAM and STOP_SENDING.
1373
+ * Answers the offset past it, with `lastKind` (a
1374
+ * `QUIC_STREAM_CONTROL_*`) and `lastValue` (the stream it names, or 0)
1375
+ * for the packet's record; or `at` when nothing owed fits.
1376
+ */
1377
+ putNextControl(buf: u8[], at: i32, end: i32): i32 {
1378
+ this.lastValue = 0
1379
+ if (this.maxDataOwed) {
1380
+ const next: i32 = quicPutValue(buf, at, end, QUIC_FRAME_MAX_DATA, this.recvMaxData)
1381
+ if (next > at) {
1382
+ this.maxDataOwed = false
1383
+ this.lastKind = QUIC_STREAM_CONTROL_MAX_DATA
1384
+ return next
1385
+ }
1386
+ }
1387
+ if (this.maxStreamsBidiOwed) {
1388
+ const next: i32 = quicPutValue(buf, at, end, QUIC_FRAME_MAX_STREAMS_BIDI, this.peerBidiLimit)
1389
+ if (next > at) {
1390
+ this.maxStreamsBidiOwed = false
1391
+ this.lastKind = QUIC_STREAM_CONTROL_MAX_STREAMS_BIDI
1392
+ return next
1393
+ }
1394
+ }
1395
+ if (this.maxStreamsUniOwed) {
1396
+ const next: i32 = quicPutValue(buf, at, end, QUIC_FRAME_MAX_STREAMS_UNI, this.peerUniLimit)
1397
+ if (next > at) {
1398
+ this.maxStreamsUniOwed = false
1399
+ this.lastKind = QUIC_STREAM_CONTROL_MAX_STREAMS_UNI
1400
+ return next
1401
+ }
1402
+ }
1403
+ if (this.dataBlockedOwed) {
1404
+ const next: i32 = quicPutValue(buf, at, end, QUIC_FRAME_DATA_BLOCKED, this.sendMaxData)
1405
+ if (next > at) {
1406
+ this.dataBlockedOwed = false
1407
+ this.dataBlockedAt = this.sendMaxData
1408
+ this.lastKind = QUIC_STREAM_CONTROL_DATA_BLOCKED
1409
+ return next
1410
+ }
1411
+ }
1412
+ if (this.streamsBlockedBidiOwed) {
1413
+ const next: i32 = quicPutValue(buf, at, end, QUIC_FRAME_STREAMS_BLOCKED_BIDI, this.localBidiLimit)
1414
+ if (next > at) {
1415
+ this.streamsBlockedBidiOwed = false
1416
+ this.streamsBlockedBidiAt = this.localBidiLimit
1417
+ this.lastKind = QUIC_STREAM_CONTROL_STREAMS_BLOCKED_BIDI
1418
+ return next
1419
+ }
1420
+ }
1421
+ if (this.streamsBlockedUniOwed) {
1422
+ const next: i32 = quicPutValue(buf, at, end, QUIC_FRAME_STREAMS_BLOCKED_UNI, this.localUniLimit)
1423
+ if (next > at) {
1424
+ this.streamsBlockedUniOwed = false
1425
+ this.streamsBlockedUniAt = this.localUniLimit
1426
+ this.lastKind = QUIC_STREAM_CONTROL_STREAMS_BLOCKED_UNI
1427
+ return next
1428
+ }
1429
+ }
1430
+ if (!this.streamOwes) {
1431
+ return at
1432
+ }
1433
+ let fits: boolean = true
1434
+ for (const stream of this.slots) {
1435
+ if (
1436
+ stream.id >= 0 &&
1437
+ (stream.maxDataOwed || stream.blockedOwed || stream.resetOwed || stream.stopOwed)
1438
+ ) {
1439
+ const next: i32 = this.putStreamControl(stream, buf, at, end)
1440
+ if (next > at) {
1441
+ this.lastValue = stream.id
1442
+ return next
1443
+ }
1444
+ fits = false
1445
+ }
1446
+ }
1447
+ // Only a look that found nothing owed at all clears the hint; one that
1448
+ // found frames too large for the room leaves it for the next packet.
1449
+ this.streamOwes = !fits
1450
+ return at
1451
+ }
1452
+
1453
+ /** The next control frame `stream` owes, if it fits; sets `lastKind`. */
1454
+ putStreamControl(stream: QuicStream, buf: u8[], at: i32, end: i32): i32 {
1455
+ if (stream.maxDataOwed) {
1456
+ const next: i32 = quicPutStreamValue(
1457
+ buf,
1458
+ at,
1459
+ end,
1460
+ QUIC_FRAME_MAX_STREAM_DATA,
1461
+ stream.id,
1462
+ stream.recvLimit
1463
+ )
1464
+ if (next > at) {
1465
+ stream.maxDataOwed = false
1466
+ this.lastKind = QUIC_STREAM_CONTROL_MAX_STREAM_DATA
1467
+ return next
1468
+ }
1469
+ }
1470
+ if (stream.blockedOwed) {
1471
+ const next: i32 = quicPutStreamValue(
1472
+ buf,
1473
+ at,
1474
+ end,
1475
+ QUIC_FRAME_STREAM_DATA_BLOCKED,
1476
+ stream.id,
1477
+ stream.sendLimit
1478
+ )
1479
+ if (next > at) {
1480
+ stream.blockedOwed = false
1481
+ stream.blockedAt = stream.sendLimit
1482
+ this.lastKind = QUIC_STREAM_CONTROL_STREAM_DATA_BLOCKED
1483
+ return next
1484
+ }
1485
+ }
1486
+ if (stream.resetOwed) {
1487
+ const next: i32 = quicPutStreamError(buf, at, end, stream.id, stream.resetSentCode, stream.resetFinal)
1488
+ if (next > at) {
1489
+ stream.resetOwed = false
1490
+ this.lastKind = QUIC_STREAM_CONTROL_RESET
1491
+ return next
1492
+ }
1493
+ }
1494
+ if (stream.stopOwed) {
1495
+ const next: i32 = quicPutStreamError(buf, at, end, stream.id, stream.stopSendingCode, -1)
1496
+ if (next > at) {
1497
+ stream.stopOwed = false
1498
+ this.lastKind = QUIC_STREAM_CONTROL_STOP_SENDING
1499
+ return next
1500
+ }
1501
+ }
1502
+ return at
1503
+ }
1504
+
1505
+ /**
1506
+ * A packet that carried bytes `[offset, offset + length)` of stream `id`,
1507
+ * and its FIN when `fin`, was acknowledged: the bytes are marked, the
1508
+ * buffer's start moves past every acknowledged one, and once all of them
1509
+ * and the FIN are, the sending side is done (§3.1 "Data Recvd").
1510
+ */
1511
+ chunkAcked(id: i64, offset: i64, length: i32, fin: boolean): void {
1512
+ const k: i32 = this.slotOf(id)
1513
+ if (k < 0 || k >= toI32(this.slots.length)) {
1514
+ return
1515
+ }
1516
+ const stream: QuicStream = this.slots[k]
1517
+ const top: i64 = offset + toI64(length) < stream.sendEnd ? offset + toI64(length) : stream.sendEnd
1518
+ for (let o: i64 = offset < stream.sendBase ? stream.sendBase : offset; o < top; o += 1) {
1519
+ quicStreamSetBit(stream.sendAcked, stream.at(o), true)
1520
+ }
1521
+ const before: i64 = stream.sendBase
1522
+ while (stream.sendBase < stream.sendNext && quicStreamBit(stream.sendAcked, stream.at(stream.sendBase))) {
1523
+ quicStreamSetBit(stream.sendAcked, stream.at(stream.sendBase), false)
1524
+ stream.sendBase = stream.sendBase + 1
1525
+ }
1526
+ if (fin) {
1527
+ stream.finAcked = true
1528
+ }
1529
+ if (stream.sendState === QUIC_SEND_DATA_SENT && stream.finAcked && stream.sendBase === stream.sendEnd) {
1530
+ stream.sendState = QUIC_SEND_DATA_RECVD
1531
+ this.release(k)
1532
+ return
1533
+ }
1534
+ if (stream.sendBase > before && stream.wantsWrite) {
1535
+ this.queue(k)
1536
+ }
1537
+ }
1538
+
1539
+ /** A packet that carried those bytes was lost: they are queued to go again, unless the side is done or reset. */
1540
+ chunkLost(id: i64, offset: i64, length: i32, fin: boolean): void {
1541
+ const stream: QuicStream | null = this.find(id)
1542
+ if (stream !== null) {
1543
+ stream.resend(offset, length, fin)
1544
+ }
1545
+ }
1546
+
1547
+ /**
1548
+ * A packet that carried control frame `kind` (for stream `value`, or 0)
1549
+ * was acknowledged: a RESET_STREAM acknowledged ends its sending side
1550
+ * (§3.1 "Reset Recvd").
1551
+ */
1552
+ controlAcked(kind: i32, value: i64): void {
1553
+ if (kind !== QUIC_STREAM_CONTROL_RESET) {
1554
+ return
1555
+ }
1556
+ const k: i32 = this.slotOf(value)
1557
+ if (k >= 0 && k < toI32(this.slots.length) && this.slots[k].sendState === QUIC_SEND_RESET_SENT) {
1558
+ this.slots[k].sendState = QUIC_SEND_RESET_RECVD
1559
+ this.release(k)
1560
+ }
1561
+ }
1562
+
1563
+ /**
1564
+ * A packet that carried control frame `kind` was lost: it is owed again
1565
+ * with the current value, while it still says something (§13.3) — a
1566
+ * BLOCKED frame only while the limit it named still holds.
1567
+ */
1568
+ controlLost(kind: i32, value: i64): void {
1569
+ if (kind === QUIC_STREAM_CONTROL_MAX_DATA) {
1570
+ this.maxDataOwed = true
1571
+ } else if (kind === QUIC_STREAM_CONTROL_MAX_STREAMS_BIDI) {
1572
+ this.maxStreamsBidiOwed = true
1573
+ } else if (kind === QUIC_STREAM_CONTROL_MAX_STREAMS_UNI) {
1574
+ this.maxStreamsUniOwed = true
1575
+ } else if (kind === QUIC_STREAM_CONTROL_DATA_BLOCKED) {
1576
+ this.dataBlockedOwed = this.dataBlockedAt === this.sendMaxData && this.sendTotal >= this.sendMaxData
1577
+ } else if (kind === QUIC_STREAM_CONTROL_STREAMS_BLOCKED_BIDI) {
1578
+ this.streamsBlockedBidiOwed = this.streamsBlockedBidiAt === this.localBidiLimit
1579
+ } else if (kind === QUIC_STREAM_CONTROL_STREAMS_BLOCKED_UNI) {
1580
+ this.streamsBlockedUniOwed = this.streamsBlockedUniAt === this.localUniLimit
1581
+ } else {
1582
+ this.streamControlLost(kind, value)
1583
+ }
1584
+ }
1585
+
1586
+ /** A lost control frame of stream `id`. */
1587
+ streamControlLost(kind: i32, id: i64): void {
1588
+ const stream: QuicStream | null = this.find(id)
1589
+ if (stream === null) {
1590
+ return
1591
+ }
1592
+ this.streamOwes = true
1593
+ if (kind === QUIC_STREAM_CONTROL_MAX_STREAM_DATA) {
1594
+ stream.maxDataOwed = stream.recvState === QUIC_RECV_RECV
1595
+ } else if (kind === QUIC_STREAM_CONTROL_STREAM_DATA_BLOCKED) {
1596
+ stream.blockedOwed = stream.blockedAt === stream.sendLimit && stream.sendNext >= stream.sendLimit
1597
+ } else if (kind === QUIC_STREAM_CONTROL_RESET) {
1598
+ stream.resetOwed = stream.sendState === QUIC_SEND_RESET_SENT
1599
+ } else if (kind === QUIC_STREAM_CONTROL_STOP_SENDING) {
1600
+ stream.stopOwed = stream.recvState < QUIC_RECV_DATA_RECVD
1601
+ }
1602
+ }
1603
+ }