@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.
- package/INSTALL.md +58 -40
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +117 -8
- package/runtime/nish.h +67 -10
- package/runtime/nish.mjs +71 -7
- package/runtime/runtime-host.c +72 -26
- package/runtime/runtime-net.c +72 -19
- package/runtime/runtime-os.c +127 -45
- package/runtime/runtime-parallel.c +5 -1
- package/runtime/runtime-wasm.c +26 -2
- package/runtime/runtime.c +86 -14
- package/runtime/shim.mjs +114 -5
- package/scripts/build.sh +1 -3
- package/std/README.md +192 -36
- package/std/crypto/aes.ts +104 -21
- package/std/crypto/base64url.ts +43 -17
- package/std/crypto/chacha20poly1305.ts +52 -31
- package/std/crypto/ct.ts +14 -3
- package/std/crypto/hkdf.ts +385 -8
- package/std/crypto/hmac.ts +325 -9
- package/std/crypto/p256.ts +153 -27
- package/std/crypto/sha1.ts +147 -0
- package/std/crypto/sha256.ts +15 -3
- package/std/crypto/sha512.ts +16 -2
- package/std/crypto/x25519.ts +90 -37
- package/std/crypto/x509.ts +265 -65
- package/std/net/hpack.ts +1094 -0
- package/std/net/http-fields.ts +604 -0
- package/std/net/http1-server.ts +1389 -0
- package/std/net/http1.ts +1492 -0
- package/std/net/http2-frame.ts +745 -0
- package/std/net/http2-tls.ts +216 -0
- package/std/net/http2.ts +1433 -0
- package/std/net/http3-frame.ts +457 -0
- package/std/net/http3-server.ts +905 -0
- package/std/net/http3.ts +2145 -0
- package/std/net/qpack.ts +1163 -0
- package/std/net/quic-conn-ack.ts +206 -0
- package/std/net/quic-conn-cid.ts +425 -0
- package/std/net/quic-conn-params.ts +392 -0
- package/std/net/quic-datagram.ts +130 -0
- package/std/net/quic-frame.ts +1206 -0
- package/std/net/quic-listener.ts +653 -0
- package/std/net/quic-packet.ts +1474 -0
- package/std/net/quic-recovery.ts +849 -0
- package/std/net/quic-stream.ts +1603 -0
- package/std/net/quic.ts +3214 -0
- package/std/net/tls/codec.ts +1089 -0
- package/std/net/tls/record-server.ts +1173 -0
- package/std/net/tls/record.ts +621 -0
- package/std/net/tls/schedule.ts +429 -0
- package/std/net/tls-tcp.ts +410 -0
- package/std/net/tls.ts +1368 -0
- package/std/net/websocket.ts +823 -0
- package/std/net/webtransport.ts +1334 -0
- 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
|
+
}
|