@amritk/nish-x86_64-linux 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +43 -25
- package/bin/nish +0 -0
- package/package.json +2 -2
- package/runtime/nish.d.ts +55 -5
- package/runtime/nish.h +215 -18
- package/runtime/nish.mjs +16 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +7 -6
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +213 -15
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +4 -4
- package/runtime/runtime-wasm.c +5 -3
- package/runtime/runtime.c +69 -13
- package/runtime/shim.mjs +71 -1
- package/scripts/build.sh +223 -22
- package/std/README.md +81 -51
- package/std/bytes.ts +163 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/chacha20poly1305.ts +43 -9
- package/std/crypto/ed25519.ts +840 -0
- package/std/crypto/hkdf.ts +304 -12
- package/std/crypto/hmac.ts +245 -53
- package/std/crypto/p256.ts +932 -61
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +9 -9
- package/std/json-tokenizer.ts +1296 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1163 -0
- package/std/json.ts +1120 -34
- package/std/net/buffer-pool.ts +151 -0
- package/std/net/hpack.ts +416 -112
- package/std/net/http-fields.ts +93 -0
- package/std/net/http1-server.ts +417 -55
- package/std/net/http1.ts +31 -6
- package/std/net/http2-tls.ts +112 -24
- package/std/net/http2.ts +333 -75
- package/std/net/http3-server.ts +211 -31
- package/std/net/http3.ts +187 -28
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-listener.ts +88 -7
- package/std/net/quic-packet.ts +243 -82
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +424 -24
- package/std/net/quic.ts +519 -167
- package/std/net/router.ts +676 -0
- package/std/net/tls/codec.ts +426 -27
- package/std/net/tls/record-server.ts +478 -132
- package/std/net/tls/record.ts +133 -42
- package/std/net/tls/schedule.ts +464 -55
- package/std/net/tls/ticket.ts +610 -0
- package/std/net/tls-tcp.ts +196 -53
- package/std/net/tls.ts +1018 -260
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/threads.ts +438 -14
package/std/net/quic-stream.ts
CHANGED
|
@@ -23,12 +23,25 @@
|
|
|
23
23
|
* **Fixed slots.** The table is sized once, from the configuration: one slot
|
|
24
24
|
* per stream that may be open at once — as many client-opened bidirectional
|
|
25
25
|
* and unidirectional streams as this side lets the client have, and
|
|
26
|
-
* `localStreams` of its own
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
26
|
+
* `localStreams` of its own. A stream lives in its slot from the frame (or
|
|
27
|
+
* `open`) that starts it until both of its sides are done, and then the slot
|
|
28
|
+
* is free for the next. A stream is found by its ID through a hash index,
|
|
29
|
+
* never by a scan.
|
|
30
|
+
*
|
|
31
|
+
* **Lent buffers.** A slot holds no buffers of its own. A side borrows a
|
|
32
|
+
* `bufferSize`-byte buffer, and a bit per byte beside it, from the table's
|
|
33
|
+
* `QuicStreamPools` when it first needs one: the receive side on the first
|
|
34
|
+
* byte that lands, the send side on the first byte written. Each goes back,
|
|
35
|
+
* its used extent zeroed, once it is done with: the receive side once the
|
|
36
|
+
* application has read to the final size or the stream is reset, the send
|
|
37
|
+
* side once every byte up to the final size is acknowledged or the stream
|
|
38
|
+
* is reset, never before, since a lost byte is resent from the send buffer.
|
|
39
|
+
* The pools hold two buffers per slot at most, the reserve's included, so a
|
|
40
|
+
* borrow never fails, and
|
|
41
|
+
* a server shares one pool across all its connections (`share`), so what it
|
|
42
|
+
* holds follows the streams moving bytes, not the slots it was sized for.
|
|
43
|
+
* Once the pools are warm, nothing a peer sends and nothing the application
|
|
44
|
+
* writes allocates.
|
|
32
45
|
*
|
|
33
46
|
* **Receiving** (§3.2, §2.2). Data lands in the receive buffer at its offset
|
|
34
47
|
* modulo the buffer's size, in any order and overlapping, with a bit set per
|
|
@@ -84,6 +97,7 @@ import {
|
|
|
84
97
|
quicPutValue,
|
|
85
98
|
quicStreamOverhead,
|
|
86
99
|
} from "nish/net/quic-frame"
|
|
100
|
+
import { BufferPool } from "nish/net/buffer-pool"
|
|
87
101
|
|
|
88
102
|
// ---- What the stream calls answer -----------------------------------------------
|
|
89
103
|
|
|
@@ -197,6 +211,186 @@ export const quicStreamSetBit = (bits: u8[], k: i32, on: boolean): void => {
|
|
|
197
211
|
/** The smaller of two `i64`s. */
|
|
198
212
|
const quicStreamMin = (a: i64, b: i64): i64 => (a < b ? a : b)
|
|
199
213
|
|
|
214
|
+
/**
|
|
215
|
+
* The buffers a connection's streams borrow: `data` lends the receive and
|
|
216
|
+
* send buffers of `bufferSize` bytes, `bits` the bitmaps beside them, a bit
|
|
217
|
+
* per byte. One table makes a private set for itself; a server makes one set
|
|
218
|
+
* for every slot and hands it to each connection's table with
|
|
219
|
+
* `QuicStreams.share`, so the memory follows the streams moving bytes rather
|
|
220
|
+
* than the slots the server was sized for. Each pool's ceiling is two
|
|
221
|
+
* buffers for every stream it serves, one a side, which is what holding
|
|
222
|
+
* every buffer at once costs. The reserve below counts against it too, so a
|
|
223
|
+
* send side that finds the pools out takes the reserve's last buffer
|
|
224
|
+
* (`lendSend`): with that side holding none, not every buffer is lent, and
|
|
225
|
+
* the one left over is in the reserve.
|
|
226
|
+
*
|
|
227
|
+
* Frames are handled inside the arena scope a packet is opened in, where
|
|
228
|
+
* nothing may allocate, grow a list or push onto one. So the frames lend and
|
|
229
|
+
* take back receive buffers through a reserve the pools keep beside their
|
|
230
|
+
* free lists, by index alone: `lend` hands one out, `keep` takes one back
|
|
231
|
+
* zeroed. Before each scope `prime` trims the reserve to `hold` buffers,
|
|
232
|
+
* giving the rest to the pools, and tops it up to as many as the packet
|
|
233
|
+
* could start receive sides, taking them. `hold` is one connection's stream
|
|
234
|
+
* count, and the reserve three times that long, so a packet that lends
|
|
235
|
+
* nothing and takes back every buffer its connection held still fits. Once
|
|
236
|
+
* warm, the reserve mostly circulates on its own and `prime` costs a handful
|
|
237
|
+
* of comparisons, with no take or give. The pools count the reserve's
|
|
238
|
+
* buffers as lent. `prime` and the `drain` before each give-back outside a
|
|
239
|
+
* packet trim it to `hold` for the whole server, not each connection, but
|
|
240
|
+
* `keep` inside a packet can raise it to three times `hold`, and it stays
|
|
241
|
+
* there until the next `prime` or `drain`.
|
|
242
|
+
*/
|
|
243
|
+
export class QuicStreamPools {
|
|
244
|
+
data: BufferPool
|
|
245
|
+
bits: BufferPool
|
|
246
|
+
/** The reserve: `ready` buffers and their bitmaps, taken from the pools and not lent to a stream; the rest of each list is `none`. */
|
|
247
|
+
readyData: u8[][]
|
|
248
|
+
readyBits: u8[][]
|
|
249
|
+
/** The empty array a free reserve entry holds. */
|
|
250
|
+
none: u8[]
|
|
251
|
+
/** How many buffers the reserve holds. */
|
|
252
|
+
ready: i32 = 0
|
|
253
|
+
/** The most the reserve keeps from one packet to the next: one connection's stream count. */
|
|
254
|
+
hold: i32 = 0
|
|
255
|
+
/** How many buffers each pool's free list has been grown to hold, by `fit`. */
|
|
256
|
+
room: i32 = 0
|
|
257
|
+
|
|
258
|
+
/** Pools for `connections` connections of `streams` stream slots each, `bufferSize` bytes each way. */
|
|
259
|
+
constructor(bufferSize: i32, streams: i32, connections: i32) {
|
|
260
|
+
this.data = new BufferPool("QuicStreams.data", bufferSize, streams * 2 * connections)
|
|
261
|
+
this.bits = new BufferPool("QuicStreams.bits", (bufferSize + 7) >> 3, streams * 2 * connections)
|
|
262
|
+
this.none = []
|
|
263
|
+
this.hold = streams
|
|
264
|
+
this.readyData = []
|
|
265
|
+
this.readyBits = []
|
|
266
|
+
for (let k: i32 = 0; k < streams * 3; k += 1) {
|
|
267
|
+
this.readyData.push(this.none)
|
|
268
|
+
this.readyBits.push(this.none)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Readies the reserve for a packet that could start `need` receive sides:
|
|
274
|
+
* trims it to `hold` buffers, then tops it up to `need`. Takes and gives
|
|
275
|
+
* only when it is outside that window. Never inside an arena scope.
|
|
276
|
+
*/
|
|
277
|
+
prime(need: i32): void {
|
|
278
|
+
this.drain(this.hold)
|
|
279
|
+
while (this.ready < need && this.ready < toI32(this.readyData.length)) {
|
|
280
|
+
this.readyData[this.ready] = this.data.take()
|
|
281
|
+
this.readyBits[this.ready] = this.bits.take()
|
|
282
|
+
this.ready = this.ready + 1
|
|
283
|
+
}
|
|
284
|
+
this.fit()
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Gives the reserve's buffers past the first `most` back to the pools, each already zeroed by `keep`. Never inside an arena scope. */
|
|
288
|
+
drain(most: i32): void {
|
|
289
|
+
while (this.ready > most) {
|
|
290
|
+
this.ready = this.ready - 1
|
|
291
|
+
this.data.give(this.readyData[this.ready], QUIC_STREAM_NO_BYTES)
|
|
292
|
+
this.bits.give(this.readyBits[this.ready], QUIC_STREAM_NO_BYTES)
|
|
293
|
+
this.readyData[this.ready] = this.none
|
|
294
|
+
this.readyBits[this.ready] = this.none
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Grows each pool's free list until it can hold every buffer the pool has
|
|
300
|
+
* made, by pushing `none` and popping it straight off again, so that a
|
|
301
|
+
* give-back never grows it. A slot's `reset` gives back every buffer its
|
|
302
|
+
* connection held at once, more than the free list ever held before, and
|
|
303
|
+
* the arena must not move by it (QUIC-3). The room doubles, so the pushes
|
|
304
|
+
* cost O(1) for each buffer made.
|
|
305
|
+
*/
|
|
306
|
+
fit(): void {
|
|
307
|
+
const made: i32 = this.data.made
|
|
308
|
+
if (made <= this.room) {
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
let target: i32 = this.room + this.room
|
|
312
|
+
if (target < made) {
|
|
313
|
+
target = made
|
|
314
|
+
}
|
|
315
|
+
if (target > this.data.ceiling) {
|
|
316
|
+
target = this.data.ceiling
|
|
317
|
+
}
|
|
318
|
+
quicStreamGrowFree(this.data.free, target, this.none)
|
|
319
|
+
quicStreamGrowFree(this.bits.free, target, this.none)
|
|
320
|
+
this.room = target
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Lends the reserve's last buffer, into `stream`'s receive side. Panics when the reserve is empty: a packet handled without `prime`. */
|
|
324
|
+
lend(stream: QuicStream): void {
|
|
325
|
+
if (this.ready <= 0) {
|
|
326
|
+
panic("QuicStreams: a receive buffer was wanted with none primed")
|
|
327
|
+
}
|
|
328
|
+
this.ready = this.ready - 1
|
|
329
|
+
stream.recvBuf = this.readyData[this.ready]
|
|
330
|
+
stream.recvHave = this.readyBits[this.ready]
|
|
331
|
+
this.readyData[this.ready] = this.none
|
|
332
|
+
this.readyBits[this.ready] = this.none
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Lends `stream` a send buffer and bitmap: taken from the pools while they
|
|
337
|
+
* have one to give, else the reserve's last. The reserve counts against
|
|
338
|
+
* the same ceiling, so once the pools have made every buffer and lent them
|
|
339
|
+
* all, a send side wanting one means the reserve holds it. Never inside an
|
|
340
|
+
* arena scope, where a take would allocate.
|
|
341
|
+
*/
|
|
342
|
+
lendSend(stream: QuicStream): void {
|
|
343
|
+
if (this.data.free.length === 0 && this.data.made >= this.data.ceiling && this.ready > 0) {
|
|
344
|
+
this.ready = this.ready - 1
|
|
345
|
+
stream.sendBuf = this.readyData[this.ready]
|
|
346
|
+
stream.sendAcked = this.readyBits[this.ready]
|
|
347
|
+
this.readyData[this.ready] = this.none
|
|
348
|
+
this.readyBits[this.ready] = this.none
|
|
349
|
+
return
|
|
350
|
+
}
|
|
351
|
+
stream.sendBuf = this.data.take()
|
|
352
|
+
stream.sendAcked = this.bits.take()
|
|
353
|
+
this.fit()
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Takes `buf` and its bitmap `have` back into the reserve, zeroing the
|
|
358
|
+
* first `used` bytes of the buffer and the bits of them. Safe inside an
|
|
359
|
+
* arena scope. Panics when the reserve is full: more given back between
|
|
360
|
+
* two `drain`s than one connection holds, a table's bug.
|
|
361
|
+
*/
|
|
362
|
+
keep(buf: u8[], have: u8[], used: i32): void {
|
|
363
|
+
if (this.ready >= toI32(this.readyData.length)) {
|
|
364
|
+
panic(`QuicStreamPools: the reserve of ${this.readyData.length} buffers is full`)
|
|
365
|
+
}
|
|
366
|
+
buf.fill(0, 0, used)
|
|
367
|
+
have.fill(0, 0, (used + 7) >> 3)
|
|
368
|
+
this.readyData[this.ready] = buf
|
|
369
|
+
this.readyBits[this.ready] = have
|
|
370
|
+
this.ready = this.ready + 1
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** How many buffers and bitmaps are lent to streams now, the reserve's left out: 0 once every stream that borrowed has given back. */
|
|
374
|
+
lent(): i32 {
|
|
375
|
+
return this.data.lent() + this.bits.lent() - this.ready - this.ready
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Pushes `none` onto `free` until it is `target` long, then pops each one off
|
|
381
|
+
* again: the capacity stays, so giving back that many buffers to the pool the
|
|
382
|
+
* list belongs to never grows the arena.
|
|
383
|
+
*/
|
|
384
|
+
export const quicStreamGrowFree = (free: u8[][], target: i32, none: u8[]): void => {
|
|
385
|
+
const length: i32 = toI32(free.length)
|
|
386
|
+
while (toI32(free.length) < target) {
|
|
387
|
+
free.push(none)
|
|
388
|
+
}
|
|
389
|
+
while (toI32(free.length) > length) {
|
|
390
|
+
free.pop()
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
200
394
|
/**
|
|
201
395
|
* One stream slot. `id` is -1 while the slot is free. The fields are
|
|
202
396
|
* readable — the states, `resetCode` once the peer reset it, `stopCode` once
|
|
@@ -204,7 +398,11 @@ const quicStreamMin = (a: i64, b: i64): i64 => (a < b ? a : b)
|
|
|
204
398
|
*/
|
|
205
399
|
export class QuicStream {
|
|
206
400
|
id: i64 = -1
|
|
207
|
-
/**
|
|
401
|
+
/**
|
|
402
|
+
* The receive buffer: byte `o` of the stream at `o % size`, its bit in
|
|
403
|
+
* `recvHave` set while unread. Both are the table's empty array until the
|
|
404
|
+
* first byte arrives and after the side is done, and borrowed in between.
|
|
405
|
+
*/
|
|
208
406
|
recvBuf: u8[]
|
|
209
407
|
recvHave: u8[]
|
|
210
408
|
/** How far the application has read. */
|
|
@@ -221,7 +419,12 @@ export class QuicStream {
|
|
|
221
419
|
resetCode: i64 = -1
|
|
222
420
|
/** The STOP_SENDING error code this side asked with, or -1. */
|
|
223
421
|
stopSendingCode: i64 = -1
|
|
224
|
-
/**
|
|
422
|
+
/**
|
|
423
|
+
* The send buffer: byte `o` at `o % size`, its bit in `sendAcked` set once
|
|
424
|
+
* acknowledged and not yet released. Borrowed by the first write, and kept
|
|
425
|
+
* until every byte is acknowledged or the side is reset, since a lost
|
|
426
|
+
* packet is sent again from it.
|
|
427
|
+
*/
|
|
225
428
|
sendBuf: u8[]
|
|
226
429
|
sendAcked: u8[]
|
|
227
430
|
/** Every byte below this was acknowledged, and its room released. */
|
|
@@ -242,6 +445,8 @@ export class QuicStream {
|
|
|
242
445
|
stopCode: i64 = -1
|
|
243
446
|
/** The limit the last STREAM_DATA_BLOCKED named, or -1. */
|
|
244
447
|
blockedAt: i64 = -1
|
|
448
|
+
/** The bytes of each buffer once borrowed, which the offsets wrap at whether or not one is. */
|
|
449
|
+
size: i32 = 0
|
|
245
450
|
sendState: i32 = 0
|
|
246
451
|
recvState: i32 = 0
|
|
247
452
|
/** Whether the application wrote the FIN, a frame carried it, and the peer acknowledged it. */
|
|
@@ -259,17 +464,18 @@ export class QuicStream {
|
|
|
259
464
|
/** Whether the slot is in the event ring. */
|
|
260
465
|
queued: boolean = false
|
|
261
466
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
this.
|
|
265
|
-
this.
|
|
266
|
-
this.
|
|
467
|
+
/** A slot for buffers of `bufferSize` bytes, holding none: each side starts as `none`, the table's empty array. */
|
|
468
|
+
constructor(bufferSize: i32, none: u8[]) {
|
|
469
|
+
this.size = bufferSize
|
|
470
|
+
this.recvBuf = none
|
|
471
|
+
this.recvHave = none
|
|
472
|
+
this.sendBuf = none
|
|
473
|
+
this.sendAcked = none
|
|
267
474
|
}
|
|
268
475
|
|
|
269
476
|
/** The buffer index of stream offset `offset`. */
|
|
270
477
|
at(offset: i64): i32 {
|
|
271
|
-
|
|
272
|
-
return size > 0 ? toI32(offset % toI64(size)) : 0
|
|
478
|
+
return this.size > 0 ? toI32(offset % toI64(this.size)) : 0
|
|
273
479
|
}
|
|
274
480
|
|
|
275
481
|
/** How many bytes can be read now. */
|
|
@@ -279,7 +485,7 @@ export class QuicStream {
|
|
|
279
485
|
|
|
280
486
|
/** How many more bytes the send buffer takes. */
|
|
281
487
|
room(): i64 {
|
|
282
|
-
return toI64(
|
|
488
|
+
return toI64(this.size) - (this.sendEnd - this.sendBase)
|
|
283
489
|
}
|
|
284
490
|
|
|
285
491
|
/** Whether the sending side has a frame to go: lost bytes, new bytes, or a FIN not yet sent. */
|
|
@@ -308,11 +514,13 @@ export class QuicStream {
|
|
|
308
514
|
return sendDone && recvDone
|
|
309
515
|
}
|
|
310
516
|
|
|
311
|
-
/**
|
|
517
|
+
/**
|
|
518
|
+
* Empties the slot for stream `id`, with `recvLimit` and `sendLimit` its
|
|
519
|
+
* first credits. It holds no buffers here, and a borrowed bitmap comes
|
|
520
|
+
* back from its pool already zeroed.
|
|
521
|
+
*/
|
|
312
522
|
open(id: i64, recvLimit: i64, sendLimit: i64): void {
|
|
313
523
|
this.id = id
|
|
314
|
-
this.recvHave.fill(toU8(0))
|
|
315
|
-
this.sendAcked.fill(toU8(0))
|
|
316
524
|
this.recvRead = 0
|
|
317
525
|
this.recvContiguous = 0
|
|
318
526
|
this.recvHighest = 0
|
|
@@ -390,6 +598,16 @@ export class QuicStreams {
|
|
|
390
598
|
* each of those costs one pop, not a pass over the table.
|
|
391
599
|
*/
|
|
392
600
|
free: i32[]
|
|
601
|
+
/**
|
|
602
|
+
* Where the streams borrow their buffers: the table's own pools, or the
|
|
603
|
+
* set a server shares across its connections (`share`). `null` when the
|
|
604
|
+
* streams have no buffers at all (`bufferSize` 0, or no slots).
|
|
605
|
+
*/
|
|
606
|
+
pools: QuicStreamPools | null = null
|
|
607
|
+
/** The empty array a side holds while it has no buffer borrowed. */
|
|
608
|
+
none: u8[]
|
|
609
|
+
/** How many receive sides hold a buffer now: the rest are what a packet could start. */
|
|
610
|
+
recvLent: i32 = 0
|
|
393
611
|
/** The configured limits on the client's streams at once and this side's own. */
|
|
394
612
|
maxBidi: i64 = 0
|
|
395
613
|
maxUni: i64 = 0
|
|
@@ -462,9 +680,10 @@ export class QuicStreams {
|
|
|
462
680
|
*/
|
|
463
681
|
constructor(maxBidi: i64, maxUni: i64, localStreams: i64, bufferSize: i32, windowData: i64) {
|
|
464
682
|
const count: i32 = toI32(maxBidi + maxUni + localStreams)
|
|
683
|
+
this.none = []
|
|
465
684
|
this.slots = []
|
|
466
685
|
for (let k: i32 = 0; k < count; k += 1) {
|
|
467
|
-
this.slots.push(new QuicStream(bufferSize))
|
|
686
|
+
this.slots.push(new QuicStream(bufferSize, this.none))
|
|
468
687
|
}
|
|
469
688
|
let size: i32 = 4
|
|
470
689
|
while (size < count * 2) {
|
|
@@ -481,8 +700,154 @@ export class QuicStreams {
|
|
|
481
700
|
this.reset()
|
|
482
701
|
}
|
|
483
702
|
|
|
484
|
-
/**
|
|
703
|
+
/**
|
|
704
|
+
* Borrows the stream buffers from `pools` from now on, in place of the
|
|
705
|
+
* table's own: how a server's connections share one set. Call it before
|
|
706
|
+
* any stream has borrowed, or after `reset`. Panics when the pools' buffers
|
|
707
|
+
* are not `bufferSize` bytes.
|
|
708
|
+
*/
|
|
709
|
+
share(pools: QuicStreamPools): void {
|
|
710
|
+
if (pools.data.size !== this.bufferSize) {
|
|
711
|
+
panic(
|
|
712
|
+
`QuicStreams: pools of ${pools.data.size}-byte buffers shared with streams of ${this.bufferSize} bytes`
|
|
713
|
+
)
|
|
714
|
+
}
|
|
715
|
+
this.giveBack()
|
|
716
|
+
this.pools = pools
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* The pools, made for this table alone on first use when nothing shared
|
|
721
|
+
* one: a table that never moves a byte makes none. `null` when the
|
|
722
|
+
* streams have no buffers at all. Pools of one connection make every
|
|
723
|
+
* buffer the table could hold at once, there and then, the footprint the
|
|
724
|
+
* table had when each slot owned its buffers: how many it holds turns on
|
|
725
|
+
* when the peer acknowledges, so a lazily grown private pool would go on
|
|
726
|
+
* allocating after any warm-up. A server's shared pools grow lazily.
|
|
727
|
+
*/
|
|
728
|
+
ownPools(): QuicStreamPools | null {
|
|
729
|
+
const pools: QuicStreamPools | null = this.pools
|
|
730
|
+
if (pools !== null) {
|
|
731
|
+
return pools
|
|
732
|
+
}
|
|
733
|
+
const count: i32 = toI32(this.slots.length)
|
|
734
|
+
if (this.bufferSize <= 0 || count <= 0) {
|
|
735
|
+
return null
|
|
736
|
+
}
|
|
737
|
+
const made: QuicStreamPools = new QuicStreamPools(this.bufferSize, count, 1)
|
|
738
|
+
made.prime(made.data.ceiling)
|
|
739
|
+
made.drain(QUIC_STREAM_NO_BYTES)
|
|
740
|
+
this.pools = made
|
|
741
|
+
return made
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Readies the pools for a packet of `bytes` bytes, before the arena
|
|
746
|
+
* scope its frames are handled in: one buffer for every receive side it
|
|
747
|
+
* could start, which is no more than the sides holding none, nor than one
|
|
748
|
+
* per three bytes (the smallest STREAM frame carrying a byte, but for a
|
|
749
|
+
* last one without a length).
|
|
750
|
+
*
|
|
751
|
+
* The peer's remaining stream credit is no tighter bound: an ACK earlier
|
|
752
|
+
* in the same packet can finish a stream, and `release` then raises the
|
|
753
|
+
* limit before a later STREAM frame opens past the old one. A bound that
|
|
754
|
+
* allows for every such release is one buffer per side holding none, which
|
|
755
|
+
* `idle` already is. Once warm the buffers a packet gives back refill the
|
|
756
|
+
* reserve for the next one, so this costs a handful of comparisons, not a
|
|
757
|
+
* take per side.
|
|
758
|
+
*/
|
|
759
|
+
prime(bytes: i32): void {
|
|
760
|
+
const pools: QuicStreamPools | null = this.ownPools()
|
|
761
|
+
if (pools !== null) {
|
|
762
|
+
const idle: i32 = toI32(this.slots.length) - this.recvLent
|
|
763
|
+
const most: i32 = (bytes > 0 ? bytes : QUIC_STREAM_NO_BYTES) / 3 + 1
|
|
764
|
+
pools.prime(idle < most ? idle : most)
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/** Gives every slot's borrowed buffers back, each with its used extent zeroed. */
|
|
769
|
+
giveBack(): void {
|
|
770
|
+
const pools: QuicStreamPools | null = this.pools
|
|
771
|
+
if (pools !== null) {
|
|
772
|
+
pools.drain(pools.hold)
|
|
773
|
+
}
|
|
774
|
+
for (const stream of this.slots) {
|
|
775
|
+
this.giveRecv(stream)
|
|
776
|
+
this.giveSend(stream)
|
|
777
|
+
}
|
|
778
|
+
if (pools !== null) {
|
|
779
|
+
pools.drain(QUIC_STREAM_NO_BYTES)
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Lends `stream` a receive buffer and bitmap from the reserve `prime`
|
|
785
|
+
* filled, unless it holds them already or the streams have none. Panics
|
|
786
|
+
* when the reserve is empty: a packet handled without `prime`, where a
|
|
787
|
+
* take would allocate inside the scope.
|
|
788
|
+
*/
|
|
789
|
+
borrowRecv(stream: QuicStream): void {
|
|
790
|
+
const pools: QuicStreamPools | null = this.pools
|
|
791
|
+
if (stream.recvBuf.length > 0 || this.bufferSize <= 0) {
|
|
792
|
+
return
|
|
793
|
+
}
|
|
794
|
+
if (pools === null) {
|
|
795
|
+
panic("QuicStreams: a receive buffer was wanted with none primed")
|
|
796
|
+
}
|
|
797
|
+
pools.lend(stream)
|
|
798
|
+
this.recvLent = this.recvLent + 1
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Gives `stream`'s receive buffer and bitmap back to the pools, zeroing
|
|
803
|
+
* every byte a frame reached: below `recvHighest`, which past a whole
|
|
804
|
+
* buffer is all of it. Nothing when it holds none.
|
|
805
|
+
*/
|
|
806
|
+
giveRecv(stream: QuicStream): void {
|
|
807
|
+
const pools: QuicStreamPools | null = this.pools
|
|
808
|
+
if (pools !== null && stream.recvBuf.length > 0) {
|
|
809
|
+
pools.keep(
|
|
810
|
+
stream.recvBuf,
|
|
811
|
+
stream.recvHave,
|
|
812
|
+
toI32(quicStreamMin(stream.recvHighest, toI64(stream.size)))
|
|
813
|
+
)
|
|
814
|
+
stream.recvBuf = this.none
|
|
815
|
+
stream.recvHave = this.none
|
|
816
|
+
this.recvLent = this.recvLent - 1
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/** Borrows `stream`'s send buffer and bitmap from the pools, unless it holds them already or the streams have none. */
|
|
821
|
+
borrowSend(stream: QuicStream): void {
|
|
822
|
+
const pools: QuicStreamPools | null = this.ownPools()
|
|
823
|
+
if (pools !== null && stream.sendBuf.length === 0) {
|
|
824
|
+
pools.lendSend(stream)
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Gives `stream`'s send buffer and bitmap back to the pools, zeroing
|
|
830
|
+
* every byte a write reached: below `sendEnd`, which past a whole buffer is
|
|
831
|
+
* all of it. Called only once nothing can be sent again from it — every
|
|
832
|
+
* byte acknowledged, or the side reset — and before a reset moves
|
|
833
|
+
* `sendEnd` back.
|
|
834
|
+
*/
|
|
835
|
+
giveSend(stream: QuicStream): void {
|
|
836
|
+
const pools: QuicStreamPools | null = this.pools
|
|
837
|
+
if (pools !== null && stream.sendBuf.length > 0) {
|
|
838
|
+
pools.keep(stream.sendBuf, stream.sendAcked, toI32(quicStreamMin(stream.sendEnd, toI64(stream.size))))
|
|
839
|
+
stream.sendBuf = this.none
|
|
840
|
+
stream.sendAcked = this.none
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Frees every slot and puts every counter back, for a connection slot
|
|
846
|
+
* reused for another peer. Every buffer a stream still held goes back to
|
|
847
|
+
* its pool first.
|
|
848
|
+
*/
|
|
485
849
|
reset(): void {
|
|
850
|
+
this.giveBack()
|
|
486
851
|
for (const stream of this.slots) {
|
|
487
852
|
stream.id = -1
|
|
488
853
|
stream.queued = false
|
|
@@ -737,6 +1102,8 @@ export class QuicStreams {
|
|
|
737
1102
|
return
|
|
738
1103
|
}
|
|
739
1104
|
const id: i64 = stream.id
|
|
1105
|
+
this.giveRecv(stream)
|
|
1106
|
+
this.giveSend(stream)
|
|
740
1107
|
this.indexRemove(k, id)
|
|
741
1108
|
stream.id = -1
|
|
742
1109
|
this.pushFree(k)
|
|
@@ -867,6 +1234,9 @@ export class QuicStreams {
|
|
|
867
1234
|
stream.recvState = QUIC_RECV_SIZE_KNOWN
|
|
868
1235
|
}
|
|
869
1236
|
const before: i64 = stream.recvContiguous
|
|
1237
|
+
if (length > 0 && end > stream.recvContiguous) {
|
|
1238
|
+
this.borrowRecv(stream)
|
|
1239
|
+
}
|
|
870
1240
|
const size: i32 = toI32(stream.recvBuf.length)
|
|
871
1241
|
for (let k2: i32 = 0; k2 < length; k2 += 1) {
|
|
872
1242
|
const position: i64 = offset + toI64(k2)
|
|
@@ -919,7 +1289,8 @@ export class QuicStreams {
|
|
|
919
1289
|
this.consume(finalSize - stream.recvRead)
|
|
920
1290
|
stream.recvRead = finalSize
|
|
921
1291
|
stream.recvContiguous = finalSize
|
|
922
|
-
stream.
|
|
1292
|
+
// Nothing of a reset stream is read again (§3.2 "Reset Recvd").
|
|
1293
|
+
this.giveRecv(stream)
|
|
923
1294
|
stream.recvState = QUIC_RECV_RESET_RECVD
|
|
924
1295
|
stream.resetCode = code
|
|
925
1296
|
stream.maxDataOwed = false
|
|
@@ -1068,6 +1439,9 @@ export class QuicStreams {
|
|
|
1068
1439
|
}
|
|
1069
1440
|
const room: i64 = stream.room()
|
|
1070
1441
|
const n: i32 = toI64(length) < room ? length : toI32(room)
|
|
1442
|
+
if (n > 0) {
|
|
1443
|
+
this.borrowSend(stream)
|
|
1444
|
+
}
|
|
1071
1445
|
const size: i32 = toI32(stream.sendBuf.length)
|
|
1072
1446
|
for (let j: i32 = 0; j < n; j += 1) {
|
|
1073
1447
|
const slot: i32 = stream.at(stream.sendEnd + toI64(j))
|
|
@@ -1094,6 +1468,8 @@ export class QuicStreams {
|
|
|
1094
1468
|
* reset it; or another `QUIC_STREAM_ERR_*`. Reading half a buffer gives the
|
|
1095
1469
|
* peer more credit. A stream's receiving side is done when a read answers
|
|
1096
1470
|
* `QUIC_STREAM_END` or `QUIC_STREAM_ERR_RESET`, so read until one does.
|
|
1471
|
+
* After `QuicConnection.release`, a stream that still owed bytes answers
|
|
1472
|
+
* `QUIC_STREAM_END` and copies nothing: its buffer went back to the pool.
|
|
1097
1473
|
*/
|
|
1098
1474
|
read(id: i64, buf: u8[], at: i32, length: i32): i32 {
|
|
1099
1475
|
const k: i32 = this.slotOf(id)
|
|
@@ -1110,6 +1486,11 @@ export class QuicStreams {
|
|
|
1110
1486
|
return QUIC_STREAM_ERR_RESET
|
|
1111
1487
|
}
|
|
1112
1488
|
const available: i64 = stream.readable()
|
|
1489
|
+
// Bytes owed with no buffer left to hold them: the table was released
|
|
1490
|
+
// under the reader, and nothing more will arrive, so the side is over.
|
|
1491
|
+
if (available > 0 && stream.recvBuf.length === 0) {
|
|
1492
|
+
return QUIC_STREAM_END
|
|
1493
|
+
}
|
|
1113
1494
|
const n: i32 = toI64(length) < available ? length : toI32(available)
|
|
1114
1495
|
const size: i32 = toI32(stream.recvBuf.length)
|
|
1115
1496
|
for (let j: i32 = 0; j < n; j += 1) {
|
|
@@ -1123,11 +1504,13 @@ export class QuicStreams {
|
|
|
1123
1504
|
if (n > 0) {
|
|
1124
1505
|
this.consume(toI64(n))
|
|
1125
1506
|
}
|
|
1507
|
+
// The credit follows the buffer a stream borrows, not the one it holds now.
|
|
1508
|
+
const window: i32 = stream.size
|
|
1126
1509
|
if (
|
|
1127
1510
|
stream.recvState === QUIC_RECV_RECV &&
|
|
1128
|
-
stream.recvRead + toI64(
|
|
1511
|
+
stream.recvRead + toI64(window) - stream.recvLimit >= toI64(window / 2)
|
|
1129
1512
|
) {
|
|
1130
|
-
stream.recvLimit = stream.recvRead + toI64(
|
|
1513
|
+
stream.recvLimit = stream.recvRead + toI64(window)
|
|
1131
1514
|
stream.maxDataOwed = true
|
|
1132
1515
|
this.streamOwes = true
|
|
1133
1516
|
}
|
|
@@ -1136,6 +1519,13 @@ export class QuicStreams {
|
|
|
1136
1519
|
// reader that has not seen the end yet.
|
|
1137
1520
|
if (n === 0 && stream.recvState === QUIC_RECV_DATA_RECVD && stream.recvRead === stream.recvFinal) {
|
|
1138
1521
|
stream.recvState = QUIC_RECV_DATA_READ
|
|
1522
|
+
// Outside any packet's scope, so the reserve is trimmed first and the
|
|
1523
|
+
// buffer always has room in it.
|
|
1524
|
+
const pools: QuicStreamPools | null = this.pools
|
|
1525
|
+
if (pools !== null) {
|
|
1526
|
+
pools.drain(pools.hold)
|
|
1527
|
+
}
|
|
1528
|
+
this.giveRecv(stream)
|
|
1139
1529
|
this.release(k)
|
|
1140
1530
|
return QUIC_STREAM_END
|
|
1141
1531
|
}
|
|
@@ -1151,6 +1541,9 @@ export class QuicStreams {
|
|
|
1151
1541
|
if (stream.sendState === QUIC_SEND_NONE || stream.sendState >= QUIC_SEND_DATA_RECVD) {
|
|
1152
1542
|
return
|
|
1153
1543
|
}
|
|
1544
|
+
// Nothing is sent again once reset (§3.1), and the used extent is the
|
|
1545
|
+
// one written, so the buffer goes back before `sendEnd` moves.
|
|
1546
|
+
this.giveSend(stream)
|
|
1154
1547
|
this.writtenTotal = this.writtenTotal - (stream.sendEnd - stream.sendNext)
|
|
1155
1548
|
stream.sendEnd = stream.sendNext
|
|
1156
1549
|
stream.sendState = QUIC_SEND_RESET_SENT
|
|
@@ -1520,6 +1913,10 @@ export class QuicStreams {
|
|
|
1520
1913
|
return
|
|
1521
1914
|
}
|
|
1522
1915
|
const stream: QuicStream = this.slots[k]
|
|
1916
|
+
if (stream.sendState >= QUIC_SEND_DATA_RECVD) {
|
|
1917
|
+
// Done or reset: its buffer may be back in the pool, and nothing is owed.
|
|
1918
|
+
return
|
|
1919
|
+
}
|
|
1523
1920
|
const top: i64 = offset + toI64(length) < stream.sendEnd ? offset + toI64(length) : stream.sendEnd
|
|
1524
1921
|
for (let o: i64 = offset < stream.sendBase ? stream.sendBase : offset; o < top; o += 1) {
|
|
1525
1922
|
quicStreamSetBit(stream.sendAcked, stream.at(o), true)
|
|
@@ -1533,7 +1930,10 @@ export class QuicStreams {
|
|
|
1533
1930
|
stream.finAcked = true
|
|
1534
1931
|
}
|
|
1535
1932
|
if (stream.sendState === QUIC_SEND_DATA_SENT && stream.finAcked && stream.sendBase === stream.sendEnd) {
|
|
1933
|
+
// Every byte up to the final size is acknowledged: no frame reads the
|
|
1934
|
+
// buffer again, so only now does it go back.
|
|
1536
1935
|
stream.sendState = QUIC_SEND_DATA_RECVD
|
|
1936
|
+
this.giveSend(stream)
|
|
1537
1937
|
this.release(k)
|
|
1538
1938
|
return
|
|
1539
1939
|
}
|