@amritk/nish-x86_64-linux 0.17.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 +231 -20
- 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 +203 -0
- package/runtime/runtime-wasm.c +16 -3
- package/runtime/runtime.c +89 -23
- package/runtime/shim.mjs +71 -1
- package/scripts/build.sh +225 -20
- package/std/README.md +86 -55
- 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 +1376 -42
- 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 +1412 -70
- 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 +231 -45
- package/std/net/http3.ts +187 -28
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-conn-params.ts +86 -16
- package/std/net/quic-listener.ts +576 -219
- package/std/net/quic-packet.ts +443 -58
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +434 -28
- package/std/net/quic.ts +922 -206
- 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 +204 -57
- package/std/net/tls.ts +1132 -305
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/text.ts +77 -0
- package/std/threads.ts +438 -14
package/std/net/http3-server.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* // quicConfig.alpn is ["h3"]; pollAdd(loop, fd, 1, 0)
|
|
10
10
|
* // each time round the loop, with `now` in milliseconds:
|
|
11
11
|
* pollWait(loop, ready, server.timeout(now));
|
|
12
|
+
* server.clock(micros); // the same instant in microseconds, for the pacer
|
|
12
13
|
* server.receive(now, key); // every datagram waiting; signs handshakes with `key`
|
|
13
14
|
* server.tick(now); // the connections' timers
|
|
14
15
|
* let slot: i32 = server.ready();
|
|
@@ -19,6 +20,8 @@
|
|
|
19
20
|
* slot = server.ready();
|
|
20
21
|
* }
|
|
21
22
|
* server.flush(now); // what every touched slot has to send
|
|
23
|
+
* // at shutdown:
|
|
24
|
+
* server.stop(); // releases every slot, wipes the static keys
|
|
22
25
|
*
|
|
23
26
|
* **Routing.** A datagram goes to the slot that owns its first packet's
|
|
24
27
|
* Destination Connection ID, found through a hash index of every slot's
|
|
@@ -38,6 +41,10 @@
|
|
|
38
41
|
* **Sending.** `flush` writes each touched slot's datagrams with
|
|
39
42
|
* `quicListenerTakeFlight`, a paced flight at a time in one `udpSendTo` with
|
|
40
43
|
* GSO where the platform has it and one datagram at a time where it does not.
|
|
44
|
+
* The pacer counts the microseconds `clock` gives it, so on a path whose
|
|
45
|
+
* round trip is under a millisecond it neither waits for the next
|
|
46
|
+
* millisecond to earn credit nor holds a slot back for a whole one; without
|
|
47
|
+
* `clock` it counts whole milliseconds.
|
|
41
48
|
* A slot whose HTTP/3 side sent GOAWAY and whose requests have all finished,
|
|
42
49
|
* every byte acknowledged, is closed with H3_NO_ERROR; a closed slot is freed
|
|
43
50
|
* once its CONNECTION_CLOSE is out.
|
|
@@ -51,11 +58,21 @@
|
|
|
51
58
|
*
|
|
52
59
|
* **Caps, fixed at start-up**: the slots, and inside each the QUIC
|
|
53
60
|
* configuration's streams and buffers and the `Http3Config`'s field section,
|
|
54
|
-
* body chunk and DATA frame.
|
|
61
|
+
* body chunk and DATA frame. A slot's connections and address are built on
|
|
62
|
+
* the first Initial accepted into it and kept, so a pool sized for its peak
|
|
63
|
+
* costs at start-up only its per-slot tables (the index, the wheel, the
|
|
64
|
+
* queues), and holds at most `size` connections once the peak has come;
|
|
55
65
|
* a datagram an established connection reads, and every datagram it sends,
|
|
56
|
-
* allocates nothing
|
|
57
|
-
*
|
|
58
|
-
*
|
|
66
|
+
* allocates nothing; nor does a datagram no slot owns, which the listener
|
|
67
|
+
* reads where it lies and answers into scratch made once, nor a handshake
|
|
68
|
+
* into a slot built before, whose state is the slot's —
|
|
69
|
+
* `docs/security/http3.md` (H3-1, H3-3).
|
|
70
|
+
*
|
|
71
|
+
* **The Date.** Every response carries one `date` field (RFC 9110 §6.6.1)
|
|
72
|
+
* from the pool's `HttpDate`, which formats the IMF-fixdate at most once a
|
|
73
|
+
* second for every slot; trailers carry none, and a program that sends its
|
|
74
|
+
* own `date` sends that one instead. `server.date.pin(unixMs)` fixes it, for
|
|
75
|
+
* a test that compares bytes.
|
|
59
76
|
*
|
|
60
77
|
* Written from RFC 9114 §3 and RFC 9000 §5.2, not ported from another
|
|
61
78
|
* implementation. Private names carry the `http3Server` prefix
|
|
@@ -63,6 +80,8 @@
|
|
|
63
80
|
*/
|
|
64
81
|
import { udpRecvFrom, udpSendTo } from "nish:net"
|
|
65
82
|
import { Secret } from "nish:secret"
|
|
83
|
+
import { BufferPool } from "nish/net/buffer-pool"
|
|
84
|
+
import { HttpDate } from "nish/net/http1-server"
|
|
66
85
|
import { Http3Config, Http3Connection } from "nish/net/http3"
|
|
67
86
|
import { H3_NO_ERROR, h3Count } from "nish/net/http3-frame"
|
|
68
87
|
import {
|
|
@@ -70,21 +89,25 @@ import {
|
|
|
70
89
|
QUIC_CONN_DATAGRAM_SIZE,
|
|
71
90
|
QUIC_CONN_ENTROPY_SIZE,
|
|
72
91
|
QUIC_CONN_LOCAL_CIDS,
|
|
92
|
+
QUIC_CONN_MAX_STREAM_DATA,
|
|
93
|
+
QUIC_CONN_MAX_STREAMS,
|
|
73
94
|
QuicConnection,
|
|
74
95
|
QuicServerConfig,
|
|
75
96
|
} from "nish/net/quic"
|
|
76
97
|
import { QUIC_MAX_CID_LENGTH } from "nish/net/quic-packet"
|
|
98
|
+
import { QuicStreamPools, quicStreamGrowFree } from "nish/net/quic-stream"
|
|
77
99
|
import {
|
|
78
100
|
QUIC_LISTEN_ACCEPT,
|
|
101
|
+
QUIC_LISTENER_ANSWER_SIZE,
|
|
79
102
|
QUIC_LISTENER_FLIGHT_MAX,
|
|
80
103
|
QUIC_LISTENER_MIN_RESET,
|
|
81
104
|
QuicFlight,
|
|
82
105
|
QuicListener,
|
|
83
106
|
QuicListenerAnswer,
|
|
84
|
-
|
|
107
|
+
quicListenerFlightTime,
|
|
85
108
|
quicListenerTakeFlight,
|
|
86
109
|
} from "nish/net/quic-listener"
|
|
87
|
-
import {
|
|
110
|
+
import { TlsP256Signer } from "nish/net/tls"
|
|
88
111
|
|
|
89
112
|
/** The timer wheel's buckets, one millisecond each: its horizon. */
|
|
90
113
|
export const H3_SERVER_WHEEL: i32 = 2048
|
|
@@ -396,12 +419,29 @@ export class Http3Wheel {
|
|
|
396
419
|
*/
|
|
397
420
|
export class Http3Server {
|
|
398
421
|
quicConfig: QuicServerConfig
|
|
422
|
+
/** The HTTP/3 configuration each slot's `Http3Connection` is built with. */
|
|
423
|
+
config: Http3Config
|
|
399
424
|
listener: QuicListener
|
|
425
|
+
/** What the listener answers into, made once: its reply and IDs are refilled in their own room. */
|
|
426
|
+
answer: QuicListenerAnswer
|
|
427
|
+
/** The slots built so far, 0 up: each built on the first Initial accepted into it, or the first ask for it. */
|
|
400
428
|
quics: QuicConnection[]
|
|
401
429
|
conns: Http3Connection[]
|
|
430
|
+
/**
|
|
431
|
+
* The `date` every slot's responses carry, formatted once a second for all
|
|
432
|
+
* of them: each slot's `Http3Connection.date` is its bytes, refreshed by
|
|
433
|
+
* `connection` and `ready`. `date.pin` fixes its clock.
|
|
434
|
+
*/
|
|
435
|
+
date: HttpDate
|
|
402
436
|
index: Http3CidIndex
|
|
403
437
|
wheel: Http3Wheel
|
|
404
|
-
/**
|
|
438
|
+
/**
|
|
439
|
+
* Each slot's client address, as its first datagram came from. Not
|
|
440
|
+
* validated until `quic(slot).addressValidated` is set (RFC 9000 §8.1): a
|
|
441
|
+
* Retry token the listener checked, with `retry` on, or a Handshake packet
|
|
442
|
+
* from the client. Before then it is the source of one UDP datagram, which
|
|
443
|
+
* anyone can forge, so a cap or an allow-list keyed on it waits for that.
|
|
444
|
+
*/
|
|
405
445
|
addresses: u8[][]
|
|
406
446
|
/** What `receive` reads into, and where `flush` writes a flight. */
|
|
407
447
|
rx: u8[]
|
|
@@ -410,23 +450,33 @@ export class Http3Server {
|
|
|
410
450
|
meta: i32[]
|
|
411
451
|
entropy: u8[]
|
|
412
452
|
flight: QuicFlight
|
|
453
|
+
/** Signs every slot's CertificateVerify in `serve`, one for the server, keeping nothing per signature. */
|
|
454
|
+
signer: TlsP256Signer
|
|
413
455
|
/** Each slot's connection-ID index signature, so it is refiled only when its IDs changed. */
|
|
414
456
|
signatures: i64[]
|
|
415
457
|
state: i32[]
|
|
416
458
|
/** The slots with datagrams for the program, a ring, and the slots with something to send, a stack. */
|
|
417
459
|
readyRing: i32[]
|
|
418
460
|
dirtyStack: i32[]
|
|
419
|
-
/**
|
|
461
|
+
/** Released slots, a stack: a slot never built is taken from `used` instead. */
|
|
420
462
|
free: i32[]
|
|
421
463
|
/** Per slot: 1 while on the ready ring, 2 while on the dirty stack. */
|
|
422
464
|
queued: i32[]
|
|
423
465
|
/** When the answer budget was last topped up, -1 before the first answer. */
|
|
424
466
|
answerRefilled: i64 = -1
|
|
467
|
+
/** The caller's clock in microseconds, from `clock`, or -1: what each connection's pacer counts. */
|
|
468
|
+
micros: i64 = -1
|
|
425
469
|
fd: i32 = -1
|
|
426
470
|
readyHead: i32 = 0
|
|
427
471
|
readyCount: i32 = 0
|
|
428
472
|
dirtyCount: i32 = 0
|
|
429
473
|
freeCount: i32 = 0
|
|
474
|
+
/** How many slots the pool may hold, which `size` answers. */
|
|
475
|
+
slots: i32 = 1
|
|
476
|
+
/** The slots ever accepted into, 0 up: one past it has never held a connection, so is free without being on `free`. */
|
|
477
|
+
used: i32 = 0
|
|
478
|
+
/** Set by `stop`: a slot built after it is released as it is made, so it holds no keys. */
|
|
479
|
+
stopped: boolean = false
|
|
430
480
|
/** What is left of the answer budget (see `answerRefilled`). */
|
|
431
481
|
answerBudget: i32 = 0
|
|
432
482
|
/** Counters for a log or a test, saturating: connections accepted, Initials dropped for want of a slot, datagrams past the answer budget, sends that failed. */
|
|
@@ -434,59 +484,137 @@ export class Http3Server {
|
|
|
434
484
|
refused: i32 = 0
|
|
435
485
|
limited: i32 = 0
|
|
436
486
|
sendErrors: i32 = 0
|
|
487
|
+
/**
|
|
488
|
+
* The stream buffers every slot's connection borrows from, made by the
|
|
489
|
+
* constructor for the stream table `quicConfig` gives every slot: `null`
|
|
490
|
+
* when its streams have none. Each pool makes a buffer only when a stream
|
|
491
|
+
* first needs one, so an idle server holds none (QUIC-3). It is made even
|
|
492
|
+
* for a configuration `quicConnConfigFits` refuses; `build` shares it only
|
|
493
|
+
* with a connection whose stream buffers are its size.
|
|
494
|
+
*/
|
|
495
|
+
streamPools: QuicStreamPools | null = null
|
|
496
|
+
/** The `bodyChunk`-byte read buffers every slot's `Http3Connection` borrows, one at most each. */
|
|
497
|
+
dataPool: BufferPool
|
|
498
|
+
/** The `maxFieldSectionSize`-byte HEADERS buffers every slot's `Http3Connection` borrows around a decode. */
|
|
499
|
+
sectionPool: BufferPool
|
|
437
500
|
|
|
438
501
|
/**
|
|
439
502
|
* A pool of `size` slots serving HTTP/3 under `config` over QUIC under
|
|
440
503
|
* `quicConfig` on the UDP socket `fd`. `listenerEntropy` is the
|
|
441
504
|
* listener's `QUIC_LISTENER_ENTROPY_SIZE` random bytes, whose first eight
|
|
442
505
|
* also key the connection-ID index (wiped in the caller's array, as the
|
|
443
|
-
* listener does).
|
|
506
|
+
* listener does). No slot's connections are made here: each slot's are
|
|
507
|
+
* built on the first Initial accepted into it. From here on
|
|
508
|
+
* `quicConfig`'s `statelessResetKey` and `retryTokenKey` are the server's:
|
|
509
|
+
* `stop` wipes them, so the configuration is this server's alone.
|
|
444
510
|
*/
|
|
445
511
|
constructor(quicConfig: QuicServerConfig, config: Http3Config, fd: i32, size: i32, listenerEntropy: u8[]) {
|
|
446
512
|
if (size < 1 || size > 65536) {
|
|
447
513
|
panic(`Http3Server: ${size} slots, outside 1 to 65536`)
|
|
448
514
|
}
|
|
449
515
|
this.quicConfig = quicConfig
|
|
516
|
+
this.config = config
|
|
450
517
|
this.fd = fd
|
|
518
|
+
this.slots = size
|
|
451
519
|
this.index = new Http3CidIndex(size, listenerEntropy)
|
|
452
520
|
this.listener = new QuicListener(quicConfig, listenerEntropy)
|
|
521
|
+
this.answer = new QuicListenerAnswer(QUIC_LISTENER_ANSWER_SIZE)
|
|
453
522
|
this.wheel = new Http3Wheel(size)
|
|
454
523
|
this.quics = []
|
|
455
524
|
this.conns = []
|
|
525
|
+
this.date = new HttpDate()
|
|
526
|
+
// Formatted once now, so no slot ever sends the bytes before a first format.
|
|
527
|
+
this.date.current()
|
|
456
528
|
this.addresses = []
|
|
457
529
|
this.entropy = new Array<u8>(QUIC_CONN_ENTROPY_SIZE)
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
530
|
+
this.dataPool = new BufferPool("Http3Server.data", config.bodyChunk, size)
|
|
531
|
+
this.sectionPool = new BufferPool("Http3Server.section", config.maxFieldSectionSize, size)
|
|
532
|
+
// Room for every buffer each pool can lend, so that giving one back never grows the arena.
|
|
533
|
+
const none: u8[] = []
|
|
534
|
+
quicStreamGrowFree(this.dataPool.free, size, none)
|
|
535
|
+
quicStreamGrowFree(this.sectionPool.free, size, none)
|
|
536
|
+
// The stream table every slot's connection will have, read from the
|
|
537
|
+
// configuration as `QuicConnection` reads it: a shape outside its bounds
|
|
538
|
+
// gets no pools, and a connection whose configuration does not fit has no
|
|
539
|
+
// table to share them with.
|
|
540
|
+
const streamData: i64 = quicConfig.maxStreamData
|
|
541
|
+
const bidi: i64 = quicConfig.maxStreamsBidi
|
|
542
|
+
const uni: i64 = quicConfig.maxStreamsUni
|
|
543
|
+
const local: i64 = quicConfig.localStreams
|
|
544
|
+
if (
|
|
545
|
+
streamData >= 1 &&
|
|
546
|
+
streamData <= QUIC_CONN_MAX_STREAM_DATA &&
|
|
547
|
+
bidi >= 0 &&
|
|
548
|
+
bidi <= QUIC_CONN_MAX_STREAMS &&
|
|
549
|
+
uni >= 0 &&
|
|
550
|
+
uni <= QUIC_CONN_MAX_STREAMS &&
|
|
551
|
+
local >= 0 &&
|
|
552
|
+
local <= QUIC_CONN_MAX_STREAMS &&
|
|
553
|
+
bidi + uni + local > 0
|
|
554
|
+
) {
|
|
555
|
+
this.streamPools = new QuicStreamPools(toI32(streamData), toI32(bidi + uni + local), size)
|
|
464
556
|
}
|
|
465
557
|
this.rx = new Array<u8>(H3_SERVER_RECEIVE_SIZE)
|
|
466
558
|
this.tx = new Array<u8>(QUIC_LISTENER_FLIGHT_MAX * QUIC_CONN_DATAGRAM_SIZE)
|
|
467
559
|
this.from = new Array<u8>(H3_SERVER_ADDRESS)
|
|
468
560
|
this.meta = [H3_SERVER_ZERO, H3_SERVER_ZERO]
|
|
469
561
|
this.flight = new QuicFlight()
|
|
562
|
+
this.signer = new TlsP256Signer()
|
|
470
563
|
this.signatures = new Array<i64>(size)
|
|
471
564
|
this.state = new Array<i32>(size)
|
|
472
565
|
this.readyRing = new Array<i32>(size)
|
|
473
566
|
this.dirtyStack = new Array<i32>(size)
|
|
567
|
+
// Released slots only: one never used is taken at `used`.
|
|
474
568
|
this.free = new Array<i32>(size)
|
|
475
569
|
this.queued = new Array<i32>(size)
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Builds the next slot, `quics.length`: its QUIC connection under fresh
|
|
574
|
+
* entropy, sharing `streamPools` when its stream table is the one they
|
|
575
|
+
* were made for, its HTTP/3 connection on the pool's buffers and `date`,
|
|
576
|
+
* and its address.
|
|
577
|
+
*/
|
|
578
|
+
build(): void {
|
|
579
|
+
crypto.getRandomValues(this.entropy)
|
|
580
|
+
const quic = new QuicConnection(this.quicConfig, this.entropy)
|
|
581
|
+
this.quics.push(quic)
|
|
582
|
+
const pools: QuicStreamPools | null = this.streamPools
|
|
583
|
+
if (pools !== null && pools.data.size === quic.streams.bufferSize && quic.streams.slots.length > 0) {
|
|
584
|
+
quic.streams.share(pools)
|
|
585
|
+
}
|
|
586
|
+
const conn: Http3Connection = new Http3Connection(this.config, quic)
|
|
587
|
+
conn.share(this.dataPool, this.sectionPool)
|
|
588
|
+
conn.date = this.date.current()
|
|
589
|
+
this.conns.push(conn)
|
|
590
|
+
this.addresses.push(new Array<u8>(H3_SERVER_ADDRESS))
|
|
591
|
+
if (this.stopped) {
|
|
592
|
+
conn.release()
|
|
479
593
|
}
|
|
480
594
|
}
|
|
481
595
|
|
|
482
|
-
/**
|
|
596
|
+
/** `slot` when it is one of the pool's, built with every slot before it if it was not; 0 when it is out of range. */
|
|
597
|
+
slotAt(slot: i32): i32 {
|
|
598
|
+
const at: i32 = slot >= 0 && slot < this.slots ? slot : H3_SERVER_ZERO
|
|
599
|
+
while (toI32(this.quics.length) <= at) {
|
|
600
|
+
this.build()
|
|
601
|
+
}
|
|
602
|
+
return at
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/** Whether every slot holds a connection: none released, and none left never used. */
|
|
606
|
+
full(): boolean {
|
|
607
|
+
return this.freeCount <= 0 && this.used >= this.slots
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/** How many slots the pool has, built or not. */
|
|
483
611
|
size(): i32 {
|
|
484
|
-
return
|
|
612
|
+
return this.slots
|
|
485
613
|
}
|
|
486
614
|
|
|
487
615
|
/** How many slots hold a connection. */
|
|
488
616
|
busy(): i32 {
|
|
489
|
-
return this.
|
|
617
|
+
return this.used - this.freeCount
|
|
490
618
|
}
|
|
491
619
|
|
|
492
620
|
/** Whether `slot` holds a connection. */
|
|
@@ -494,16 +622,20 @@ export class Http3Server {
|
|
|
494
622
|
return slot >= 0 && slot < toI32(this.state.length) && this.state[slot] === H3_SERVER_BUSY
|
|
495
623
|
}
|
|
496
624
|
|
|
497
|
-
/**
|
|
625
|
+
/**
|
|
626
|
+
* The HTTP/3 connection of `slot`, its `date` brought up to now; a slot out
|
|
627
|
+
* of range names the first. A slot not built yet is built here, with every
|
|
628
|
+
* slot before it, so a program that binds something to each slot's
|
|
629
|
+
* connection up front gets the one its slot keeps.
|
|
630
|
+
*/
|
|
498
631
|
connection(slot: i32): Http3Connection {
|
|
499
|
-
|
|
500
|
-
return this.conns[
|
|
632
|
+
this.date.current()
|
|
633
|
+
return this.conns[this.slotAt(slot)]
|
|
501
634
|
}
|
|
502
635
|
|
|
503
|
-
/** The QUIC connection of `slot
|
|
636
|
+
/** The QUIC connection of `slot`, built as `connection` builds it; a slot out of range names the first. */
|
|
504
637
|
quic(slot: i32): QuicConnection {
|
|
505
|
-
|
|
506
|
-
return this.quics[at]
|
|
638
|
+
return this.quics[this.slotAt(slot)]
|
|
507
639
|
}
|
|
508
640
|
|
|
509
641
|
/** Puts `slot` on the ready ring and the dirty stack, unless it is on them. */
|
|
@@ -531,6 +663,7 @@ export class Http3Server {
|
|
|
531
663
|
|
|
532
664
|
/** The next slot with news for the program, or -1: call its connection's `next` until it has none. */
|
|
533
665
|
ready(): i32 {
|
|
666
|
+
this.date.current()
|
|
534
667
|
while (this.readyCount > 0) {
|
|
535
668
|
const slot: i32 = this.readyRing[this.readyHead]
|
|
536
669
|
this.readyHead = (this.readyHead + 1) % toI32(this.readyRing.length)
|
|
@@ -605,28 +738,36 @@ export class Http3Server {
|
|
|
605
738
|
this.unowned(buf, at, len, address, now, key)
|
|
606
739
|
}
|
|
607
740
|
|
|
608
|
-
/**
|
|
741
|
+
/**
|
|
742
|
+
* A datagram no slot owns: the listener decides, reading it where it lies
|
|
743
|
+
* in `buf` and answering into `answer`, and may make a connection in a
|
|
744
|
+
* free slot, which reads the same window. Nothing is copied (H3-3).
|
|
745
|
+
*/
|
|
609
746
|
unowned(buf: u8[], at: i32, len: i32, address: u8[], now: i64, key: Secret<u8[]>): void {
|
|
610
747
|
if (!this.worthListening(buf, at, len, now)) {
|
|
611
748
|
return
|
|
612
749
|
}
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
datagram[k] = buf[at + k]
|
|
616
|
-
}
|
|
617
|
-
const answer: QuicListenerAnswer = this.listener.handle(datagram, address, now)
|
|
618
|
-
if (answer.kind !== QUIC_LISTEN_ACCEPT) {
|
|
750
|
+
const answer: QuicListenerAnswer = this.answer
|
|
751
|
+
if (this.listener.handleWindow(buf, at, len, address, now, answer) !== QUIC_LISTEN_ACCEPT) {
|
|
619
752
|
const reply: i32 = toI32(answer.reply.length)
|
|
620
753
|
if (reply > 0) {
|
|
621
754
|
this.send(answer.reply, H3_SERVER_ZERO, reply, address, H3_SERVER_ZERO)
|
|
622
755
|
}
|
|
623
756
|
return
|
|
624
757
|
}
|
|
625
|
-
|
|
758
|
+
// A slot released before is taken first, then the next never used, so the
|
|
759
|
+
// first accepts take slots 0, 1, 2 ... and a pool that never fills never
|
|
760
|
+
// builds its last slots.
|
|
761
|
+
let slot: i32 = 0
|
|
762
|
+
if (this.freeCount > 0) {
|
|
763
|
+
this.freeCount = this.freeCount - 1
|
|
764
|
+
slot = this.free[this.freeCount]
|
|
765
|
+
} else if (this.used < this.slots) {
|
|
766
|
+
slot = this.slotAt(this.used)
|
|
767
|
+
this.used = this.used + 1
|
|
768
|
+
} else {
|
|
626
769
|
return
|
|
627
770
|
}
|
|
628
|
-
this.freeCount = this.freeCount - 1
|
|
629
|
-
const slot: i32 = this.free[this.freeCount]
|
|
630
771
|
crypto.getRandomValues(this.entropy)
|
|
631
772
|
const quic: QuicConnection = this.quics[slot]
|
|
632
773
|
quic.reset(this.entropy)
|
|
@@ -646,9 +787,8 @@ export class Http3Server {
|
|
|
646
787
|
|
|
647
788
|
/**
|
|
648
789
|
* Whether the listener could answer `buf[at .. at + len)` at all, read
|
|
649
|
-
* from its first bytes: what it would drop without a word
|
|
650
|
-
*
|
|
651
|
-
* (H3-3). That is a short header too short for a stateless reset; a long
|
|
790
|
+
* from its first bytes: what it would drop without a word is dropped
|
|
791
|
+
* here, before the listener parses it. That is a short header too short for a stateless reset; a long
|
|
652
792
|
* one shorter than a full Initial, which neither Version Negotiation nor a
|
|
653
793
|
* new connection answers (RFC 9000 §14.1, §6.1); version 0; a version 1
|
|
654
794
|
* packet that is not an Initial, or whose DCID is under 8 bytes (§7.2);
|
|
@@ -674,7 +814,7 @@ export class Http3Server {
|
|
|
674
814
|
if ((first & 0x30) !== 0 || toI32(buf[at + 5]) < 8) {
|
|
675
815
|
return false
|
|
676
816
|
}
|
|
677
|
-
if (this.
|
|
817
|
+
if (this.full()) {
|
|
678
818
|
this.refused = h3Count(this.refused)
|
|
679
819
|
return false
|
|
680
820
|
}
|
|
@@ -686,7 +826,7 @@ export class Http3Server {
|
|
|
686
826
|
* `H3_SERVER_ANSWER_BURST` at once, then one per
|
|
687
827
|
* `H3_SERVER_ANSWER_INTERVAL` milliseconds — topping it up first. Past it
|
|
688
828
|
* the datagram is dropped before the listener sees it, so a flood of
|
|
689
|
-
* garbage costs
|
|
829
|
+
* garbage costs no answer and no MAC past the budget (H3-3); counted in `limited`.
|
|
690
830
|
*/
|
|
691
831
|
takeAnswer(now: i64): boolean {
|
|
692
832
|
if (this.answerRefilled < 0 || now < this.answerRefilled) {
|
|
@@ -707,13 +847,25 @@ export class Http3Server {
|
|
|
707
847
|
return true
|
|
708
848
|
}
|
|
709
849
|
|
|
850
|
+
/**
|
|
851
|
+
* Gives the server the caller's monotonic clock in microseconds: the
|
|
852
|
+
* instant the `now` of the calls that follow names, `monotonicNanos() /
|
|
853
|
+
* 1000` where `now` is `monotonicNanos() / 1000000`. The pacer counts it;
|
|
854
|
+
* every timer still counts `now`. One that is not inside `now`'s
|
|
855
|
+
* millisecond is not read.
|
|
856
|
+
*/
|
|
857
|
+
clock(micros: i64): void {
|
|
858
|
+
this.micros = micros
|
|
859
|
+
}
|
|
860
|
+
|
|
710
861
|
/** Hands `slot`'s connection a datagram, signs when the handshake asks, and refiles its IDs and timer. */
|
|
711
862
|
serve(slot: i32, buf: u8[], at: i32, len: i32, now: i64, key: Secret<u8[]>): void {
|
|
712
863
|
const quic: QuicConnection = this.quics[slot]
|
|
864
|
+
quic.recovery.micros = this.micros
|
|
713
865
|
quic.receiveWindow(buf, at, len, now)
|
|
714
866
|
const input: u8[] | null = quic.signatureInput()
|
|
715
867
|
if (input !== null) {
|
|
716
|
-
const signature: u8[] | null =
|
|
868
|
+
const signature: u8[] | null = this.signer.sign(key, input)
|
|
717
869
|
if (signature !== null) {
|
|
718
870
|
quic.sign(signature)
|
|
719
871
|
}
|
|
@@ -795,6 +947,7 @@ export class Http3Server {
|
|
|
795
947
|
/** `flush` of one slot. */
|
|
796
948
|
flushSlot(slot: i32, now: i64): void {
|
|
797
949
|
const quic: QuicConnection = this.quics[slot]
|
|
950
|
+
quic.recovery.micros = this.micros
|
|
798
951
|
if (this.conns[slot].isDone() && !quic.closed()) {
|
|
799
952
|
quic.close(H3_NO_ERROR)
|
|
800
953
|
}
|
|
@@ -837,7 +990,7 @@ export class Http3Server {
|
|
|
837
990
|
// The pacer answers a time, not a delay: the slot wakes at it when it
|
|
838
991
|
// comes before the connection's own deadline.
|
|
839
992
|
let due: i64 = quic.deadline()
|
|
840
|
-
const paced: i64 =
|
|
993
|
+
const paced: i64 = quicListenerFlightTime(quic, now)
|
|
841
994
|
if (paced > now && (due < 0 || paced < due)) {
|
|
842
995
|
due = paced
|
|
843
996
|
}
|
|
@@ -856,7 +1009,7 @@ export class Http3Server {
|
|
|
856
1009
|
release(slot: i32): void {
|
|
857
1010
|
this.unindex(slot)
|
|
858
1011
|
this.wheel.unfile(slot)
|
|
859
|
-
this.
|
|
1012
|
+
this.conns[slot].release()
|
|
860
1013
|
this.state[slot] = H3_SERVER_FREE
|
|
861
1014
|
if (this.freeCount < toI32(this.free.length)) {
|
|
862
1015
|
this.free[this.freeCount] = slot
|
|
@@ -864,6 +1017,39 @@ export class Http3Server {
|
|
|
864
1017
|
}
|
|
865
1018
|
}
|
|
866
1019
|
|
|
1020
|
+
/**
|
|
1021
|
+
* Stops the server for good and wipes the keys it holds: every built slot's
|
|
1022
|
+
* connection is released (`QuicConnection.release`, which sends nothing),
|
|
1023
|
+
* as is one built after, by `connection` or `quic`; the listener is
|
|
1024
|
+
* released; and the configuration's `statelessResetKey` and
|
|
1025
|
+
* `retryTokenKey`, the index's salt, the connections' entropy scratch
|
|
1026
|
+
* and the listener's last answer (a Retry's or a reset's token) are
|
|
1027
|
+
* zeroed. From then on every datagram is dropped and no connection is
|
|
1028
|
+
* accepted. The configuration's two keys are the server's once it is made
|
|
1029
|
+
* with them: a program that keeps a copy of either, to start the next
|
|
1030
|
+
* server with, wipes its own copy. The keys are zeroed in place, so give
|
|
1031
|
+
* each server a `QuicServerConfig` of its own: any other listener,
|
|
1032
|
+
* connection or server made with the same one would go on MACing Retry
|
|
1033
|
+
* tokens and deriving stateless reset tokens under an all-zero key, which
|
|
1034
|
+
* anyone can forge.
|
|
1035
|
+
*/
|
|
1036
|
+
stop(): void {
|
|
1037
|
+
this.stopped = true
|
|
1038
|
+
for (let slot: i32 = 0; slot < toI32(this.quics.length); slot++) {
|
|
1039
|
+
if (this.holds(slot)) {
|
|
1040
|
+
this.release(slot)
|
|
1041
|
+
} else {
|
|
1042
|
+
this.conns[slot].release()
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
this.listener.release()
|
|
1046
|
+
secureZero(this.quicConfig.statelessResetKey)
|
|
1047
|
+
secureZero(this.quicConfig.retryTokenKey)
|
|
1048
|
+
secureZero(this.index.salt)
|
|
1049
|
+
secureZero(this.entropy)
|
|
1050
|
+
this.answer.wipe()
|
|
1051
|
+
}
|
|
1052
|
+
|
|
867
1053
|
/** Runs the timers due by `now`, and marks each slot it ran for `flush`. */
|
|
868
1054
|
tick(now: i64): void {
|
|
869
1055
|
const wheel: Http3Wheel = this.wheel
|