@amritk/nish-x86_64-linux 0.18.0 → 0.21.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 -28
- package/bin/nish +0 -0
- package/docs/AI.md +2759 -0
- package/package.json +3 -2
- package/runtime/browser.d.ts +14 -0
- package/runtime/browser.mjs +88 -0
- package/runtime/core/arrays.d.ts +18 -0
- package/runtime/core/arrays.mjs +125 -0
- package/runtime/core/checks.mjs +20 -0
- package/runtime/core/host.d.ts +70 -0
- package/runtime/core/host.mjs +311 -0
- package/runtime/core/numbers.d.ts +53 -0
- package/runtime/core/numbers.mjs +316 -0
- package/runtime/core/result.d.ts +41 -0
- package/runtime/core/result.mjs +127 -0
- package/runtime/core/strings.d.ts +14 -0
- package/runtime/core/strings.mjs +169 -0
- package/runtime/core.d.ts +9 -0
- package/runtime/core.mjs +55 -0
- package/runtime/host.d.ts +6 -0
- package/runtime/nish.d.ts +99 -5
- package/runtime/nish.h +289 -19
- package/runtime/nish.mjs +39 -3
- package/runtime/node.mjs +383 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-bytes.c +401 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +8 -7
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +214 -16
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +235 -11
- package/runtime/runtime-term.c +504 -0
- package/runtime/runtime-wasm.c +5 -14
- package/runtime/runtime.c +257 -17
- package/runtime/shim.mjs +6 -915
- package/runtime/sqlite/LICENSE.md +85 -0
- package/runtime/sqlite/README.md +45 -0
- package/runtime/sqlite/nish-sqlite.c +359 -0
- package/runtime/sqlite/sqlite3.c +271131 -0
- package/runtime/sqlite/sqlite3.h +14447 -0
- package/scripts/build.sh +437 -28
- package/std/LICENSE-fast-float +23 -0
- package/std/LICENSE-fdlibm +8 -0
- package/std/README.md +108 -58
- package/std/base64.ts +116 -0
- package/std/bytes.ts +419 -0
- package/std/cli.ts +328 -0
- package/std/collections.ts +6 -5
- package/std/crypto/LICENSE-crypt_blowfish +26 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/base64url.ts +125 -0
- package/std/crypto/bcrypt.ts +760 -0
- 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 +382 -54
- 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 +20 -131
- package/std/json-tokenizer.ts +4237 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1182 -0
- package/std/json.ts +1202 -34
- package/std/math.ts +608 -0
- 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 +501 -62
- 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 +261 -43
- package/std/net/http3.ts +187 -28
- package/std/net/postgres.ts +1875 -0
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-listener.ts +108 -11
- 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/regex.ts +4274 -0
- package/std/sqlite.ts +391 -0
- package/std/term-io.ts +978 -0
- package/std/term-pixels.ts +874 -0
- package/std/term-probe.ts +459 -0
- package/std/term-rate.ts +993 -0
- package/std/term.ts +2453 -0
- package/std/text.ts +90 -7
- package/std/threads.ts +438 -14
- package/std/yaml-lines.ts +141 -0
- package/std/yaml.ts +4675 -0
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,18 @@
|
|
|
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
|
+
* Each flight goes with the ECN codepoint its connection marked it with
|
|
45
|
+
* (`QuicFlight.ecn`, RFC 9000 §13.4), so the peer's ACK_ECN counts are
|
|
46
|
+
* validated and a CE mark shrinks the window. Where the platform sends no
|
|
47
|
+
* ECN (Darwin, whose `udpSendTo` answers -95 to a marked send), the first
|
|
48
|
+
* marked send goes again unmarked and no slot marks after it. A -95 is also
|
|
49
|
+
* what a platform without segmentation offload answers, so a refused GSO
|
|
50
|
+
* send goes again one datagram at a time, still marked, and only a -95 to
|
|
51
|
+
* one of those turns marking off.
|
|
52
|
+
* The pacer counts the microseconds `clock` gives it, so on a path whose
|
|
53
|
+
* round trip is under a millisecond it neither waits for the next
|
|
54
|
+
* millisecond to earn credit nor holds a slot back for a whole one; without
|
|
55
|
+
* `clock` it counts whole milliseconds.
|
|
41
56
|
* A slot whose HTTP/3 side sent GOAWAY and whose requests have all finished,
|
|
42
57
|
* every byte acknowledged, is closed with H3_NO_ERROR; a closed slot is freed
|
|
43
58
|
* once its CONNECTION_CLOSE is out.
|
|
@@ -51,11 +66,21 @@
|
|
|
51
66
|
*
|
|
52
67
|
* **Caps, fixed at start-up**: the slots, and inside each the QUIC
|
|
53
68
|
* configuration's streams and buffers and the `Http3Config`'s field section,
|
|
54
|
-
* body chunk and DATA frame.
|
|
69
|
+
* body chunk and DATA frame. A slot's connections and address are built on
|
|
70
|
+
* the first Initial accepted into it and kept, so a pool sized for its peak
|
|
71
|
+
* costs at start-up only its per-slot tables (the index, the wheel, the
|
|
72
|
+
* queues), and holds at most `size` connections once the peak has come;
|
|
55
73
|
* a datagram an established connection reads, and every datagram it sends,
|
|
56
74
|
* allocates nothing; nor does a datagram no slot owns, which the listener
|
|
57
|
-
* reads where it lies and answers into scratch made once, nor a handshake
|
|
58
|
-
* whose state is the slot's —
|
|
75
|
+
* reads where it lies and answers into scratch made once, nor a handshake
|
|
76
|
+
* into a slot built before, whose state is the slot's —
|
|
77
|
+
* `docs/security/http3.md` (H3-1, H3-3).
|
|
78
|
+
*
|
|
79
|
+
* **The Date.** Every response carries one `date` field (RFC 9110 §6.6.1)
|
|
80
|
+
* from the pool's `HttpDate`, which formats the IMF-fixdate at most once a
|
|
81
|
+
* second for every slot; trailers carry none, and a program that sends its
|
|
82
|
+
* own `date` sends that one instead. `server.date.pin(unixMs)` fixes it, for
|
|
83
|
+
* a test that compares bytes.
|
|
59
84
|
*
|
|
60
85
|
* Written from RFC 9114 §3 and RFC 9000 §5.2, not ported from another
|
|
61
86
|
* implementation. Private names carry the `http3Server` prefix
|
|
@@ -63,6 +88,8 @@
|
|
|
63
88
|
*/
|
|
64
89
|
import { udpRecvFrom, udpSendTo } from "nish:net"
|
|
65
90
|
import { Secret } from "nish:secret"
|
|
91
|
+
import { BufferPool } from "nish/net/buffer-pool"
|
|
92
|
+
import { HttpDate } from "nish/net/http1-server"
|
|
66
93
|
import { Http3Config, Http3Connection } from "nish/net/http3"
|
|
67
94
|
import { H3_NO_ERROR, h3Count } from "nish/net/http3-frame"
|
|
68
95
|
import {
|
|
@@ -70,10 +97,13 @@ import {
|
|
|
70
97
|
QUIC_CONN_DATAGRAM_SIZE,
|
|
71
98
|
QUIC_CONN_ENTROPY_SIZE,
|
|
72
99
|
QUIC_CONN_LOCAL_CIDS,
|
|
100
|
+
QUIC_CONN_MAX_STREAM_DATA,
|
|
101
|
+
QUIC_CONN_MAX_STREAMS,
|
|
73
102
|
QuicConnection,
|
|
74
103
|
QuicServerConfig,
|
|
75
104
|
} from "nish/net/quic"
|
|
76
105
|
import { QUIC_MAX_CID_LENGTH } from "nish/net/quic-packet"
|
|
106
|
+
import { QuicStreamPools, quicStreamGrowFree } from "nish/net/quic-stream"
|
|
77
107
|
import {
|
|
78
108
|
QUIC_LISTEN_ACCEPT,
|
|
79
109
|
QUIC_LISTENER_ANSWER_SIZE,
|
|
@@ -82,7 +112,7 @@ import {
|
|
|
82
112
|
QuicFlight,
|
|
83
113
|
QuicListener,
|
|
84
114
|
QuicListenerAnswer,
|
|
85
|
-
|
|
115
|
+
quicListenerFlightTime,
|
|
86
116
|
quicListenerTakeFlight,
|
|
87
117
|
} from "nish/net/quic-listener"
|
|
88
118
|
import { TlsP256Signer } from "nish/net/tls"
|
|
@@ -100,8 +130,8 @@ export const H3_SERVER_ANSWER_INTERVAL: i64 = 100
|
|
|
100
130
|
/** The address form `nish:net` reads and writes. */
|
|
101
131
|
const H3_SERVER_ADDRESS: i32 = 18
|
|
102
132
|
|
|
103
|
-
/** What `udpSendTo` answers where the platform has no segmentation offload. */
|
|
104
|
-
const
|
|
133
|
+
/** What `udpSendTo` answers where the platform has no segmentation offload, or sends no ECN. */
|
|
134
|
+
const H3_SERVER_UNSUPPORTED: i32 = -95
|
|
105
135
|
|
|
106
136
|
/** The largest datagram read: a GRO receive of up to 64 KB. */
|
|
107
137
|
const H3_SERVER_RECEIVE_SIZE: i32 = 65536
|
|
@@ -397,14 +427,29 @@ export class Http3Wheel {
|
|
|
397
427
|
*/
|
|
398
428
|
export class Http3Server {
|
|
399
429
|
quicConfig: QuicServerConfig
|
|
430
|
+
/** The HTTP/3 configuration each slot's `Http3Connection` is built with. */
|
|
431
|
+
config: Http3Config
|
|
400
432
|
listener: QuicListener
|
|
401
433
|
/** What the listener answers into, made once: its reply and IDs are refilled in their own room. */
|
|
402
434
|
answer: QuicListenerAnswer
|
|
435
|
+
/** The slots built so far, 0 up: each built on the first Initial accepted into it, or the first ask for it. */
|
|
403
436
|
quics: QuicConnection[]
|
|
404
437
|
conns: Http3Connection[]
|
|
438
|
+
/**
|
|
439
|
+
* The `date` every slot's responses carry, formatted once a second for all
|
|
440
|
+
* of them: each slot's `Http3Connection.date` is its bytes, refreshed by
|
|
441
|
+
* `connection` and `ready`. `date.pin` fixes its clock.
|
|
442
|
+
*/
|
|
443
|
+
date: HttpDate
|
|
405
444
|
index: Http3CidIndex
|
|
406
445
|
wheel: Http3Wheel
|
|
407
|
-
/**
|
|
446
|
+
/**
|
|
447
|
+
* Each slot's client address, as its first datagram came from. Not
|
|
448
|
+
* validated until `quic(slot).addressValidated` is set (RFC 9000 §8.1): a
|
|
449
|
+
* Retry token the listener checked, with `retry` on, or a Handshake packet
|
|
450
|
+
* from the client. Before then it is the source of one UDP datagram, which
|
|
451
|
+
* anyone can forge, so a cap or an allow-list keyed on it waits for that.
|
|
452
|
+
*/
|
|
408
453
|
addresses: u8[][]
|
|
409
454
|
/** What `receive` reads into, and where `flush` writes a flight. */
|
|
410
455
|
rx: u8[]
|
|
@@ -421,17 +466,25 @@ export class Http3Server {
|
|
|
421
466
|
/** The slots with datagrams for the program, a ring, and the slots with something to send, a stack. */
|
|
422
467
|
readyRing: i32[]
|
|
423
468
|
dirtyStack: i32[]
|
|
424
|
-
/**
|
|
469
|
+
/** Released slots, a stack: a slot never built is taken from `used` instead. */
|
|
425
470
|
free: i32[]
|
|
426
471
|
/** Per slot: 1 while on the ready ring, 2 while on the dirty stack. */
|
|
427
472
|
queued: i32[]
|
|
428
473
|
/** When the answer budget was last topped up, -1 before the first answer. */
|
|
429
474
|
answerRefilled: i64 = -1
|
|
475
|
+
/** The caller's clock in microseconds, from `clock`, or -1: what each connection's pacer counts. */
|
|
476
|
+
micros: i64 = -1
|
|
430
477
|
fd: i32 = -1
|
|
431
478
|
readyHead: i32 = 0
|
|
432
479
|
readyCount: i32 = 0
|
|
433
480
|
dirtyCount: i32 = 0
|
|
434
481
|
freeCount: i32 = 0
|
|
482
|
+
/** How many slots the pool may hold, which `size` answers. */
|
|
483
|
+
slots: i32 = 1
|
|
484
|
+
/** The slots ever accepted into, 0 up: one past it has never held a connection, so is free without being on `free`. */
|
|
485
|
+
used: i32 = 0
|
|
486
|
+
/** Set by `stop`: a slot built after it is released as it is made, so it holds no keys. */
|
|
487
|
+
stopped: boolean = false
|
|
435
488
|
/** What is left of the answer budget (see `answerRefilled`). */
|
|
436
489
|
answerBudget: i32 = 0
|
|
437
490
|
/** 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. */
|
|
@@ -439,34 +492,77 @@ export class Http3Server {
|
|
|
439
492
|
refused: i32 = 0
|
|
440
493
|
limited: i32 = 0
|
|
441
494
|
sendErrors: i32 = 0
|
|
495
|
+
/** Whether the platform sends ECN marks: true until a marked send answers -95, and every slot's `ecnMarking` with it. */
|
|
496
|
+
ecn: boolean = true
|
|
497
|
+
/**
|
|
498
|
+
* The stream buffers every slot's connection borrows from, made by the
|
|
499
|
+
* constructor for the stream table `quicConfig` gives every slot: `null`
|
|
500
|
+
* when its streams have none. Each pool makes a buffer only when a stream
|
|
501
|
+
* first needs one, so an idle server holds none (QUIC-3). It is made even
|
|
502
|
+
* for a configuration `quicConnConfigFits` refuses; `build` shares it only
|
|
503
|
+
* with a connection whose stream buffers are its size.
|
|
504
|
+
*/
|
|
505
|
+
streamPools: QuicStreamPools | null = null
|
|
506
|
+
/** The `bodyChunk`-byte read buffers every slot's `Http3Connection` borrows, one at most each. */
|
|
507
|
+
dataPool: BufferPool
|
|
508
|
+
/** The `maxFieldSectionSize`-byte HEADERS buffers every slot's `Http3Connection` borrows around a decode. */
|
|
509
|
+
sectionPool: BufferPool
|
|
442
510
|
|
|
443
511
|
/**
|
|
444
512
|
* A pool of `size` slots serving HTTP/3 under `config` over QUIC under
|
|
445
513
|
* `quicConfig` on the UDP socket `fd`. `listenerEntropy` is the
|
|
446
514
|
* listener's `QUIC_LISTENER_ENTROPY_SIZE` random bytes, whose first eight
|
|
447
515
|
* also key the connection-ID index (wiped in the caller's array, as the
|
|
448
|
-
* listener does).
|
|
516
|
+
* listener does). No slot's connections are made here: each slot's are
|
|
517
|
+
* built on the first Initial accepted into it. From here on
|
|
518
|
+
* `quicConfig`'s `statelessResetKey` and `retryTokenKey` are the server's:
|
|
519
|
+
* `stop` wipes them, so the configuration is this server's alone.
|
|
449
520
|
*/
|
|
450
521
|
constructor(quicConfig: QuicServerConfig, config: Http3Config, fd: i32, size: i32, listenerEntropy: u8[]) {
|
|
451
522
|
if (size < 1 || size > 65536) {
|
|
452
523
|
panic(`Http3Server: ${size} slots, outside 1 to 65536`)
|
|
453
524
|
}
|
|
454
525
|
this.quicConfig = quicConfig
|
|
526
|
+
this.config = config
|
|
455
527
|
this.fd = fd
|
|
528
|
+
this.slots = size
|
|
456
529
|
this.index = new Http3CidIndex(size, listenerEntropy)
|
|
457
530
|
this.listener = new QuicListener(quicConfig, listenerEntropy)
|
|
458
531
|
this.answer = new QuicListenerAnswer(QUIC_LISTENER_ANSWER_SIZE)
|
|
459
532
|
this.wheel = new Http3Wheel(size)
|
|
460
533
|
this.quics = []
|
|
461
534
|
this.conns = []
|
|
535
|
+
this.date = new HttpDate()
|
|
536
|
+
// Formatted once now, so no slot ever sends the bytes before a first format.
|
|
537
|
+
this.date.current()
|
|
462
538
|
this.addresses = []
|
|
463
539
|
this.entropy = new Array<u8>(QUIC_CONN_ENTROPY_SIZE)
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
540
|
+
this.dataPool = new BufferPool("Http3Server.data", config.bodyChunk, size)
|
|
541
|
+
this.sectionPool = new BufferPool("Http3Server.section", config.maxFieldSectionSize, size)
|
|
542
|
+
// Room for every buffer each pool can lend, so that giving one back never grows the arena.
|
|
543
|
+
const none: u8[] = []
|
|
544
|
+
quicStreamGrowFree(this.dataPool.free, size, none)
|
|
545
|
+
quicStreamGrowFree(this.sectionPool.free, size, none)
|
|
546
|
+
// The stream table every slot's connection will have, read from the
|
|
547
|
+
// configuration as `QuicConnection` reads it: a shape outside its bounds
|
|
548
|
+
// gets no pools, and a connection whose configuration does not fit has no
|
|
549
|
+
// table to share them with.
|
|
550
|
+
const streamData: i64 = quicConfig.maxStreamData
|
|
551
|
+
const bidi: i64 = quicConfig.maxStreamsBidi
|
|
552
|
+
const uni: i64 = quicConfig.maxStreamsUni
|
|
553
|
+
const local: i64 = quicConfig.localStreams
|
|
554
|
+
if (
|
|
555
|
+
streamData >= 1 &&
|
|
556
|
+
streamData <= QUIC_CONN_MAX_STREAM_DATA &&
|
|
557
|
+
bidi >= 0 &&
|
|
558
|
+
bidi <= QUIC_CONN_MAX_STREAMS &&
|
|
559
|
+
uni >= 0 &&
|
|
560
|
+
uni <= QUIC_CONN_MAX_STREAMS &&
|
|
561
|
+
local >= 0 &&
|
|
562
|
+
local <= QUIC_CONN_MAX_STREAMS &&
|
|
563
|
+
bidi + uni + local > 0
|
|
564
|
+
) {
|
|
565
|
+
this.streamPools = new QuicStreamPools(toI32(streamData), toI32(bidi + uni + local), size)
|
|
470
566
|
}
|
|
471
567
|
this.rx = new Array<u8>(H3_SERVER_RECEIVE_SIZE)
|
|
472
568
|
this.tx = new Array<u8>(QUIC_LISTENER_FLIGHT_MAX * QUIC_CONN_DATAGRAM_SIZE)
|
|
@@ -478,22 +574,58 @@ export class Http3Server {
|
|
|
478
574
|
this.state = new Array<i32>(size)
|
|
479
575
|
this.readyRing = new Array<i32>(size)
|
|
480
576
|
this.dirtyStack = new Array<i32>(size)
|
|
577
|
+
// Released slots only: one never used is taken at `used`.
|
|
481
578
|
this.free = new Array<i32>(size)
|
|
482
579
|
this.queued = new Array<i32>(size)
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Builds the next slot, `quics.length`: its QUIC connection under fresh
|
|
584
|
+
* entropy, sharing `streamPools` when its stream table is the one they
|
|
585
|
+
* were made for, its HTTP/3 connection on the pool's buffers and `date`,
|
|
586
|
+
* and its address.
|
|
587
|
+
*/
|
|
588
|
+
build(): void {
|
|
589
|
+
crypto.getRandomValues(this.entropy)
|
|
590
|
+
const quic = new QuicConnection(this.quicConfig, this.entropy)
|
|
591
|
+
quic.ecnMarking = this.ecn
|
|
592
|
+
this.quics.push(quic)
|
|
593
|
+
const pools: QuicStreamPools | null = this.streamPools
|
|
594
|
+
if (pools !== null && pools.data.size === quic.streams.bufferSize && quic.streams.slots.length > 0) {
|
|
595
|
+
quic.streams.share(pools)
|
|
596
|
+
}
|
|
597
|
+
const conn: Http3Connection = new Http3Connection(this.config, quic)
|
|
598
|
+
conn.share(this.dataPool, this.sectionPool)
|
|
599
|
+
conn.date = this.date.current()
|
|
600
|
+
this.conns.push(conn)
|
|
601
|
+
this.addresses.push(new Array<u8>(H3_SERVER_ADDRESS))
|
|
602
|
+
if (this.stopped) {
|
|
603
|
+
conn.release()
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/** `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. */
|
|
608
|
+
slotAt(slot: i32): i32 {
|
|
609
|
+
const at: i32 = slot >= 0 && slot < this.slots ? slot : H3_SERVER_ZERO
|
|
610
|
+
while (toI32(this.quics.length) <= at) {
|
|
611
|
+
this.build()
|
|
486
612
|
}
|
|
613
|
+
return at
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/** Whether every slot holds a connection: none released, and none left never used. */
|
|
617
|
+
full(): boolean {
|
|
618
|
+
return this.freeCount <= 0 && this.used >= this.slots
|
|
487
619
|
}
|
|
488
620
|
|
|
489
|
-
/** How many slots the pool has. */
|
|
621
|
+
/** How many slots the pool has, built or not. */
|
|
490
622
|
size(): i32 {
|
|
491
|
-
return
|
|
623
|
+
return this.slots
|
|
492
624
|
}
|
|
493
625
|
|
|
494
626
|
/** How many slots hold a connection. */
|
|
495
627
|
busy(): i32 {
|
|
496
|
-
return this.
|
|
628
|
+
return this.used - this.freeCount
|
|
497
629
|
}
|
|
498
630
|
|
|
499
631
|
/** Whether `slot` holds a connection. */
|
|
@@ -501,16 +633,20 @@ export class Http3Server {
|
|
|
501
633
|
return slot >= 0 && slot < toI32(this.state.length) && this.state[slot] === H3_SERVER_BUSY
|
|
502
634
|
}
|
|
503
635
|
|
|
504
|
-
/**
|
|
636
|
+
/**
|
|
637
|
+
* The HTTP/3 connection of `slot`, its `date` brought up to now; a slot out
|
|
638
|
+
* of range names the first. A slot not built yet is built here, with every
|
|
639
|
+
* slot before it, so a program that binds something to each slot's
|
|
640
|
+
* connection up front gets the one its slot keeps.
|
|
641
|
+
*/
|
|
505
642
|
connection(slot: i32): Http3Connection {
|
|
506
|
-
|
|
507
|
-
return this.conns[
|
|
643
|
+
this.date.current()
|
|
644
|
+
return this.conns[this.slotAt(slot)]
|
|
508
645
|
}
|
|
509
646
|
|
|
510
|
-
/** The QUIC connection of `slot
|
|
647
|
+
/** The QUIC connection of `slot`, built as `connection` builds it; a slot out of range names the first. */
|
|
511
648
|
quic(slot: i32): QuicConnection {
|
|
512
|
-
|
|
513
|
-
return this.quics[at]
|
|
649
|
+
return this.quics[this.slotAt(slot)]
|
|
514
650
|
}
|
|
515
651
|
|
|
516
652
|
/** Puts `slot` on the ready ring and the dirty stack, unless it is on them. */
|
|
@@ -538,6 +674,7 @@ export class Http3Server {
|
|
|
538
674
|
|
|
539
675
|
/** The next slot with news for the program, or -1: call its connection's `next` until it has none. */
|
|
540
676
|
ready(): i32 {
|
|
677
|
+
this.date.current()
|
|
541
678
|
while (this.readyCount > 0) {
|
|
542
679
|
const slot: i32 = this.readyRing[this.readyHead]
|
|
543
680
|
this.readyHead = (this.readyHead + 1) % toI32(this.readyRing.length)
|
|
@@ -625,15 +762,23 @@ export class Http3Server {
|
|
|
625
762
|
if (this.listener.handleWindow(buf, at, len, address, now, answer) !== QUIC_LISTEN_ACCEPT) {
|
|
626
763
|
const reply: i32 = toI32(answer.reply.length)
|
|
627
764
|
if (reply > 0) {
|
|
628
|
-
this.send(answer.reply, H3_SERVER_ZERO, reply, address, H3_SERVER_ZERO)
|
|
765
|
+
this.send(answer.reply, H3_SERVER_ZERO, reply, address, H3_SERVER_ZERO, H3_SERVER_ZERO)
|
|
629
766
|
}
|
|
630
767
|
return
|
|
631
768
|
}
|
|
632
|
-
|
|
769
|
+
// A slot released before is taken first, then the next never used, so the
|
|
770
|
+
// first accepts take slots 0, 1, 2 ... and a pool that never fills never
|
|
771
|
+
// builds its last slots.
|
|
772
|
+
let slot: i32 = 0
|
|
773
|
+
if (this.freeCount > 0) {
|
|
774
|
+
this.freeCount = this.freeCount - 1
|
|
775
|
+
slot = this.free[this.freeCount]
|
|
776
|
+
} else if (this.used < this.slots) {
|
|
777
|
+
slot = this.slotAt(this.used)
|
|
778
|
+
this.used = this.used + 1
|
|
779
|
+
} else {
|
|
633
780
|
return
|
|
634
781
|
}
|
|
635
|
-
this.freeCount = this.freeCount - 1
|
|
636
|
-
const slot: i32 = this.free[this.freeCount]
|
|
637
782
|
crypto.getRandomValues(this.entropy)
|
|
638
783
|
const quic: QuicConnection = this.quics[slot]
|
|
639
784
|
quic.reset(this.entropy)
|
|
@@ -680,7 +825,7 @@ export class Http3Server {
|
|
|
680
825
|
if ((first & 0x30) !== 0 || toI32(buf[at + 5]) < 8) {
|
|
681
826
|
return false
|
|
682
827
|
}
|
|
683
|
-
if (this.
|
|
828
|
+
if (this.full()) {
|
|
684
829
|
this.refused = h3Count(this.refused)
|
|
685
830
|
return false
|
|
686
831
|
}
|
|
@@ -713,9 +858,21 @@ export class Http3Server {
|
|
|
713
858
|
return true
|
|
714
859
|
}
|
|
715
860
|
|
|
861
|
+
/**
|
|
862
|
+
* Gives the server the caller's monotonic clock in microseconds: the
|
|
863
|
+
* instant the `now` of the calls that follow names, `monotonicNanos() /
|
|
864
|
+
* 1000` where `now` is `monotonicNanos() / 1000000`. The pacer counts it;
|
|
865
|
+
* every timer still counts `now`. One that is not inside `now`'s
|
|
866
|
+
* millisecond is not read.
|
|
867
|
+
*/
|
|
868
|
+
clock(micros: i64): void {
|
|
869
|
+
this.micros = micros
|
|
870
|
+
}
|
|
871
|
+
|
|
716
872
|
/** Hands `slot`'s connection a datagram, signs when the handshake asks, and refiles its IDs and timer. */
|
|
717
873
|
serve(slot: i32, buf: u8[], at: i32, len: i32, now: i64, key: Secret<u8[]>): void {
|
|
718
874
|
const quic: QuicConnection = this.quics[slot]
|
|
875
|
+
quic.recovery.micros = this.micros
|
|
719
876
|
quic.receiveWindow(buf, at, len, now)
|
|
720
877
|
const input: u8[] | null = quic.signatureInput()
|
|
721
878
|
if (input !== null) {
|
|
@@ -758,21 +915,40 @@ export class Http3Server {
|
|
|
758
915
|
}
|
|
759
916
|
}
|
|
760
917
|
|
|
761
|
-
/**
|
|
762
|
-
|
|
763
|
-
|
|
918
|
+
/**
|
|
919
|
+
* Sends `buf[at .. at + len)` to `to` with the ECN codepoint `ecn`, cut
|
|
920
|
+
* into `segment`-byte datagrams when it is above 0; answers whether it went.
|
|
921
|
+
*/
|
|
922
|
+
send(buf: u8[], at: i32, len: i32, to: u8[], segment: i32, ecn: i32): boolean {
|
|
923
|
+
const sent: i32 = udpSendTo(this.fd, buf, at, len, to, segment, ecn)
|
|
764
924
|
if (sent >= 0) {
|
|
765
925
|
return true
|
|
766
926
|
}
|
|
767
|
-
if (sent !==
|
|
927
|
+
if (sent !== H3_SERVER_UNSUPPORTED || (segment <= 0 && ecn === 0)) {
|
|
768
928
|
this.sendErrors = h3Count(this.sendErrors)
|
|
769
929
|
return false
|
|
770
930
|
}
|
|
771
|
-
//
|
|
931
|
+
// -95: no segmentation offload, or no ECN, or both. One datagram at a
|
|
932
|
+
// time, still marked: a -95 to a datagram with no segment is the mark's.
|
|
933
|
+
let mark: i32 = ecn
|
|
934
|
+
if (segment <= 0) {
|
|
935
|
+
this.unmark()
|
|
936
|
+
mark = H3_SERVER_ZERO
|
|
937
|
+
}
|
|
938
|
+
const step: i32 = segment > 0 ? segment : len
|
|
772
939
|
let p: i32 = at
|
|
773
940
|
while (p < at + len) {
|
|
774
|
-
const n: i32 = at + len - p <
|
|
775
|
-
|
|
941
|
+
const n: i32 = at + len - p < step ? at + len - p : step
|
|
942
|
+
let one: i32 = udpSendTo(this.fd, buf, p, n, to, H3_SERVER_ZERO, mark)
|
|
943
|
+
if (one === H3_SERVER_UNSUPPORTED && mark !== 0) {
|
|
944
|
+
// No ECN here: this datagram and the rest go unmarked and no slot
|
|
945
|
+
// marks again. The packets just counted as marked fail their
|
|
946
|
+
// connection's validation.
|
|
947
|
+
this.unmark()
|
|
948
|
+
mark = H3_SERVER_ZERO
|
|
949
|
+
one = udpSendTo(this.fd, buf, p, n, to, H3_SERVER_ZERO, mark)
|
|
950
|
+
}
|
|
951
|
+
if (one < 0) {
|
|
776
952
|
this.sendErrors = h3Count(this.sendErrors)
|
|
777
953
|
return false
|
|
778
954
|
}
|
|
@@ -781,6 +957,14 @@ export class Http3Server {
|
|
|
781
957
|
return true
|
|
782
958
|
}
|
|
783
959
|
|
|
960
|
+
/** The platform sends no ECN: no slot's connection marks from now on. */
|
|
961
|
+
unmark(): void {
|
|
962
|
+
this.ecn = false
|
|
963
|
+
for (let slot: i32 = 0; slot < toI32(this.quics.length); slot++) {
|
|
964
|
+
this.quics[slot].ecnMarking = false
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
784
968
|
/**
|
|
785
969
|
* Sends what every touched slot has, as far as its pacer lets it, closes a
|
|
786
970
|
* slot whose HTTP/3 side is done after GOAWAY, frees a slot whose
|
|
@@ -801,6 +985,7 @@ export class Http3Server {
|
|
|
801
985
|
/** `flush` of one slot. */
|
|
802
986
|
flushSlot(slot: i32, now: i64): void {
|
|
803
987
|
const quic: QuicConnection = this.quics[slot]
|
|
988
|
+
quic.recovery.micros = this.micros
|
|
804
989
|
if (this.conns[slot].isDone() && !quic.closed()) {
|
|
805
990
|
quic.close(H3_NO_ERROR)
|
|
806
991
|
}
|
|
@@ -825,7 +1010,7 @@ export class Http3Server {
|
|
|
825
1010
|
break
|
|
826
1011
|
}
|
|
827
1012
|
const segment: i32 = this.flight.count > 1 ? this.flight.segment : H3_SERVER_ZERO
|
|
828
|
-
if (!this.send(this.tx, H3_SERVER_ZERO, n, to, segment)) {
|
|
1013
|
+
if (!this.send(this.tx, H3_SERVER_ZERO, n, to, segment, this.flight.ecn)) {
|
|
829
1014
|
break
|
|
830
1015
|
}
|
|
831
1016
|
retried = false
|
|
@@ -834,7 +1019,7 @@ export class Http3Server {
|
|
|
834
1019
|
// The CONNECTION_CLOSE goes whatever the pacer says: nothing follows it.
|
|
835
1020
|
const n: i32 = quic.takeDatagramInto(this.tx, H3_SERVER_ZERO, now)
|
|
836
1021
|
if (n > 0) {
|
|
837
|
-
this.send(this.tx, H3_SERVER_ZERO, n, to, H3_SERVER_ZERO)
|
|
1022
|
+
this.send(this.tx, H3_SERVER_ZERO, n, to, H3_SERVER_ZERO, quic.datagramEcn)
|
|
838
1023
|
}
|
|
839
1024
|
this.release(slot)
|
|
840
1025
|
return
|
|
@@ -843,7 +1028,7 @@ export class Http3Server {
|
|
|
843
1028
|
// The pacer answers a time, not a delay: the slot wakes at it when it
|
|
844
1029
|
// comes before the connection's own deadline.
|
|
845
1030
|
let due: i64 = quic.deadline()
|
|
846
|
-
const paced: i64 =
|
|
1031
|
+
const paced: i64 = quicListenerFlightTime(quic, now)
|
|
847
1032
|
if (paced > now && (due < 0 || paced < due)) {
|
|
848
1033
|
due = paced
|
|
849
1034
|
}
|
|
@@ -862,7 +1047,7 @@ export class Http3Server {
|
|
|
862
1047
|
release(slot: i32): void {
|
|
863
1048
|
this.unindex(slot)
|
|
864
1049
|
this.wheel.unfile(slot)
|
|
865
|
-
this.
|
|
1050
|
+
this.conns[slot].release()
|
|
866
1051
|
this.state[slot] = H3_SERVER_FREE
|
|
867
1052
|
if (this.freeCount < toI32(this.free.length)) {
|
|
868
1053
|
this.free[this.freeCount] = slot
|
|
@@ -870,6 +1055,39 @@ export class Http3Server {
|
|
|
870
1055
|
}
|
|
871
1056
|
}
|
|
872
1057
|
|
|
1058
|
+
/**
|
|
1059
|
+
* Stops the server for good and wipes the keys it holds: every built slot's
|
|
1060
|
+
* connection is released (`QuicConnection.release`, which sends nothing),
|
|
1061
|
+
* as is one built after, by `connection` or `quic`; the listener is
|
|
1062
|
+
* released; and the configuration's `statelessResetKey` and
|
|
1063
|
+
* `retryTokenKey`, the index's salt, the connections' entropy scratch
|
|
1064
|
+
* and the listener's last answer (a Retry's or a reset's token) are
|
|
1065
|
+
* zeroed. From then on every datagram is dropped and no connection is
|
|
1066
|
+
* accepted. The configuration's two keys are the server's once it is made
|
|
1067
|
+
* with them: a program that keeps a copy of either, to start the next
|
|
1068
|
+
* server with, wipes its own copy. The keys are zeroed in place, so give
|
|
1069
|
+
* each server a `QuicServerConfig` of its own: any other listener,
|
|
1070
|
+
* connection or server made with the same one would go on MACing Retry
|
|
1071
|
+
* tokens and deriving stateless reset tokens under an all-zero key, which
|
|
1072
|
+
* anyone can forge.
|
|
1073
|
+
*/
|
|
1074
|
+
stop(): void {
|
|
1075
|
+
this.stopped = true
|
|
1076
|
+
for (let slot: i32 = 0; slot < toI32(this.quics.length); slot++) {
|
|
1077
|
+
if (this.holds(slot)) {
|
|
1078
|
+
this.release(slot)
|
|
1079
|
+
} else {
|
|
1080
|
+
this.conns[slot].release()
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
this.listener.release()
|
|
1084
|
+
secureZero(this.quicConfig.statelessResetKey)
|
|
1085
|
+
secureZero(this.quicConfig.retryTokenKey)
|
|
1086
|
+
secureZero(this.index.salt)
|
|
1087
|
+
secureZero(this.entropy)
|
|
1088
|
+
this.answer.wipe()
|
|
1089
|
+
}
|
|
1090
|
+
|
|
873
1091
|
/** Runs the timers due by `now`, and marks each slot it ran for `flush`. */
|
|
874
1092
|
tick(now: i64): void {
|
|
875
1093
|
const wheel: Http3Wheel = this.wheel
|