@amritk/nish-x86_64-linux 0.16.0 → 0.18.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 -34
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +117 -8
- package/runtime/nish.h +50 -13
- package/runtime/nish.mjs +71 -7
- package/runtime/runtime-host.c +43 -24
- package/runtime/runtime-net.c +57 -10
- package/runtime/runtime-os.c +62 -20
- package/runtime/runtime-simd.c +203 -0
- package/runtime/runtime-wasm.c +12 -0
- package/runtime/runtime.c +66 -18
- package/runtime/shim.mjs +114 -5
- package/scripts/build.sh +14 -12
- package/std/README.md +168 -36
- package/std/crypto/aes.ts +51 -8
- package/std/crypto/base64url.ts +25 -12
- package/std/crypto/chacha20poly1305.ts +43 -29
- package/std/crypto/ct.ts +6 -1
- package/std/crypto/hkdf.ts +364 -3
- package/std/crypto/hmac.ts +307 -7
- package/std/crypto/p256.ts +618 -34
- package/std/crypto/sha1.ts +147 -0
- package/std/crypto/sha256.ts +3 -2
- package/std/crypto/x25519.ts +88 -36
- package/std/crypto/x509.ts +130 -47
- package/std/json.ts +297 -41
- package/std/net/hpack.ts +1094 -0
- package/std/net/http-fields.ts +604 -0
- package/std/net/http1-server.ts +1389 -0
- package/std/net/http1.ts +1492 -0
- package/std/net/http2-frame.ts +745 -0
- package/std/net/http2-tls.ts +216 -0
- package/std/net/http2.ts +1433 -0
- package/std/net/http3-frame.ts +457 -0
- package/std/net/http3-server.ts +911 -0
- package/std/net/http3.ts +2145 -0
- package/std/net/qpack.ts +1163 -0
- package/std/net/quic-conn-ack.ts +206 -0
- package/std/net/quic-conn-cid.ts +425 -0
- package/std/net/quic-conn-params.ts +462 -0
- package/std/net/quic-datagram.ts +130 -0
- package/std/net/quic-frame.ts +1206 -0
- package/std/net/quic-listener.ts +929 -0
- package/std/net/quic-packet.ts +1698 -0
- package/std/net/quic-recovery.ts +849 -0
- package/std/net/quic-stream.ts +1609 -0
- package/std/net/quic.ts +3578 -0
- package/std/net/tls/codec.ts +1089 -0
- package/std/net/tls/record-server.ts +1173 -0
- package/std/net/tls/record.ts +621 -0
- package/std/net/tls/schedule.ts +429 -0
- package/std/net/tls-tcp.ts +414 -0
- package/std/net/tls.ts +1437 -0
- package/std/net/websocket.ts +823 -0
- package/std/net/webtransport.ts +1334 -0
- package/std/secret.ts +62 -0
- package/std/text.ts +77 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/net/quic-conn-ack` — the packet numbers one QUIC packet number space
|
|
3
|
+
* has received, kept as ranges, which is what duplicate detection reads
|
|
4
|
+
* (RFC 9000 §12.3) and what an ACK frame is written from (§13.2, §19.3).
|
|
5
|
+
*
|
|
6
|
+
* import { QuicAckRanges } from "nish/net/quic-conn-ack";
|
|
7
|
+
*
|
|
8
|
+
* const received = new QuicAckRanges();
|
|
9
|
+
* if (!received.record(packetNumber, ackEliciting)) { … a duplicate: drop it … }
|
|
10
|
+
* if (received.ackPending) { received.pushAck(payload, 0); }
|
|
11
|
+
*
|
|
12
|
+
* **Bounded.** At most `QUIC_ACK_MAX_RANGES` ranges are kept, highest first.
|
|
13
|
+
* A new range past that drops the lowest, and everything at or below what it
|
|
14
|
+
* covered becomes the `floor`: a packet number there is answered as already
|
|
15
|
+
* seen, because this side can no longer tell, and RFC 9000 §12.3 says a
|
|
16
|
+
* receiver must discard a packet it cannot be sure it has not processed. So a
|
|
17
|
+
* peer that sends packet numbers full of holes costs a fixed amount of memory
|
|
18
|
+
* and loses only very late packets.
|
|
19
|
+
*
|
|
20
|
+
* **When to acknowledge** is the connection's choice; this module records
|
|
21
|
+
* whether something ack-eliciting arrived since the last ACK it wrote
|
|
22
|
+
* (`ackPending`). ACK frames carry an ACK Delay the caller supplies, and no
|
|
23
|
+
* ECN counts.
|
|
24
|
+
*
|
|
25
|
+
* Written from RFC 9000 §12.3, §13.2 and §19.3, not ported from another
|
|
26
|
+
* implementation. Private names carry the `quicAck` prefix
|
|
27
|
+
* (`docs/wp26-stdlib.md` §3e).
|
|
28
|
+
*/
|
|
29
|
+
import { QUIC_MAX_VARINT } from "nish/net/quic-packet"
|
|
30
|
+
import { quicPushAck, quicPutAck } from "nish/net/quic-frame"
|
|
31
|
+
|
|
32
|
+
/** The most disjoint ranges a `QuicAckRanges` keeps, and so the most an ACK frame it writes lists. */
|
|
33
|
+
export const QUIC_ACK_MAX_RANGES: i32 = 32
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The packet numbers received in one space. `ranges` holds `count` pairs
|
|
37
|
+
* `[smallest, largest]`, highest first and never touching; `largest` is the
|
|
38
|
+
* largest packet number received, or -1 before the first.
|
|
39
|
+
*/
|
|
40
|
+
export class QuicAckRanges {
|
|
41
|
+
/** The largest packet number received, or -1. */
|
|
42
|
+
largest: i64 = -1
|
|
43
|
+
/** Every packet number at or below this is answered as already received; -1 until a range is dropped. */
|
|
44
|
+
floor: i64 = -1
|
|
45
|
+
ranges: i64[]
|
|
46
|
+
count: i32 = 0
|
|
47
|
+
/** Whether an ack-eliciting packet arrived since the last `pushAck`. */
|
|
48
|
+
ackPending: boolean = false
|
|
49
|
+
|
|
50
|
+
constructor() {
|
|
51
|
+
this.ranges = new Array<i64>(QUIC_ACK_MAX_RANGES * 2)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The smallest packet number of range `k`, which the caller keeps below `count`. */
|
|
55
|
+
low(k: i32): i64 {
|
|
56
|
+
const at: i32 = k * 2
|
|
57
|
+
if (at < 0 || at >= toI32(this.ranges.length)) {
|
|
58
|
+
return -1
|
|
59
|
+
}
|
|
60
|
+
return this.ranges[at]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The largest packet number of range `k`. */
|
|
64
|
+
high(k: i32): i64 {
|
|
65
|
+
const at: i32 = k * 2 + 1
|
|
66
|
+
if (at < 0 || at >= toI32(this.ranges.length)) {
|
|
67
|
+
return -1
|
|
68
|
+
}
|
|
69
|
+
return this.ranges[at]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Sets range `k` to `[low, high]`. */
|
|
73
|
+
set(k: i32, low: i64, high: i64): void {
|
|
74
|
+
const at: i32 = k * 2
|
|
75
|
+
const next: i32 = at + 1
|
|
76
|
+
if (at < 0 || at >= toI32(this.ranges.length) || next < 0 || next >= toI32(this.ranges.length)) {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
this.ranges[at] = low
|
|
80
|
+
this.ranges[next] = high
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Whether `pn` was received, or is at or below the floor and so cannot be told apart from one that was. */
|
|
84
|
+
contains(pn: i64): boolean {
|
|
85
|
+
if (pn <= this.floor) {
|
|
86
|
+
return true
|
|
87
|
+
}
|
|
88
|
+
for (let k: i32 = 0; k < this.count; k += 1) {
|
|
89
|
+
if (pn > this.high(k)) {
|
|
90
|
+
return false
|
|
91
|
+
}
|
|
92
|
+
if (pn >= this.low(k)) {
|
|
93
|
+
return true
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Records `pn` as received, `ackEliciting` saying whether its frames ask for
|
|
101
|
+
* an acknowledgement. Answers `false`, changing nothing, for a packet number
|
|
102
|
+
* already received (or at or below the floor), or one that is not a packet
|
|
103
|
+
* number at all.
|
|
104
|
+
*/
|
|
105
|
+
record(pn: i64, ackEliciting: boolean): boolean {
|
|
106
|
+
if (pn < 0 || pn > QUIC_MAX_VARINT || this.contains(pn)) {
|
|
107
|
+
return false
|
|
108
|
+
}
|
|
109
|
+
if (pn > this.largest) {
|
|
110
|
+
this.largest = pn
|
|
111
|
+
}
|
|
112
|
+
if (ackEliciting) {
|
|
113
|
+
this.ackPending = true
|
|
114
|
+
}
|
|
115
|
+
// Find the first range whose largest is below `pn`: `pn` goes above it.
|
|
116
|
+
let k: i32 = 0
|
|
117
|
+
while (k < this.count && this.low(k) > pn) {
|
|
118
|
+
k += 1
|
|
119
|
+
}
|
|
120
|
+
const joinsAbove: boolean = k > 0 && this.low(k - 1) === pn + 1
|
|
121
|
+
const joinsBelow: boolean = k < this.count && this.high(k) === pn - 1
|
|
122
|
+
if (joinsAbove && joinsBelow) {
|
|
123
|
+
// `pn` closes the hole between two ranges: merge them into the upper one.
|
|
124
|
+
this.set(k - 1, this.low(k), this.high(k - 1))
|
|
125
|
+
this.remove(k)
|
|
126
|
+
return true
|
|
127
|
+
}
|
|
128
|
+
if (joinsAbove) {
|
|
129
|
+
this.set(k - 1, pn, this.high(k - 1))
|
|
130
|
+
return true
|
|
131
|
+
}
|
|
132
|
+
if (joinsBelow) {
|
|
133
|
+
this.set(k, this.low(k), pn)
|
|
134
|
+
return true
|
|
135
|
+
}
|
|
136
|
+
this.insert(k, pn)
|
|
137
|
+
return true
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Removes range `k`, moving the lower ones up. */
|
|
141
|
+
remove(k: i32): void {
|
|
142
|
+
for (let j: i32 = k; j + 1 < this.count; j += 1) {
|
|
143
|
+
this.set(j, this.low(j + 1), this.high(j + 1))
|
|
144
|
+
}
|
|
145
|
+
this.count = this.count - 1
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Inserts the one-number range `[pn, pn]` at position `k`. When every slot
|
|
150
|
+
* is taken the lowest range is dropped first and the floor raised over it;
|
|
151
|
+
* if `pn` itself would be that lowest range, it is the one dropped.
|
|
152
|
+
*/
|
|
153
|
+
insert(k: i32, pn: i64): void {
|
|
154
|
+
if (this.count === QUIC_ACK_MAX_RANGES) {
|
|
155
|
+
if (k === this.count) {
|
|
156
|
+
this.floor = pn
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
this.floor = this.high(this.count - 1)
|
|
160
|
+
this.count = this.count - 1
|
|
161
|
+
}
|
|
162
|
+
for (let j: i32 = this.count; j > k; j -= 1) {
|
|
163
|
+
this.set(j, this.low(j - 1), this.high(j - 1))
|
|
164
|
+
}
|
|
165
|
+
this.set(k, pn, pn)
|
|
166
|
+
this.count = this.count + 1
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Appends an ACK frame for every range kept, with `ackDelay` (already
|
|
171
|
+
* scaled by this endpoint's ACK delay exponent), and clears `ackPending`.
|
|
172
|
+
* Answers `false`, appending nothing, when nothing has been received.
|
|
173
|
+
*/
|
|
174
|
+
pushAck(out: u8[], ackDelay: i64): boolean {
|
|
175
|
+
if (this.count === 0 || !quicPushAck(out, this.ranges, this.count, ackDelay)) {
|
|
176
|
+
return false
|
|
177
|
+
}
|
|
178
|
+
this.ackPending = false
|
|
179
|
+
return true
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Writes the ACK frame `pushAck` appends into `buf` at `at`, before `end`,
|
|
184
|
+
* and clears `ackPending`. Answers the offset past it, or -1, writing
|
|
185
|
+
* nothing and leaving `ackPending` as it was, when nothing has been
|
|
186
|
+
* received or the frame does not fit.
|
|
187
|
+
*/
|
|
188
|
+
putAck(buf: u8[], at: i32, end: i32, ackDelay: i64): i32 {
|
|
189
|
+
if (this.count === 0) {
|
|
190
|
+
return -1
|
|
191
|
+
}
|
|
192
|
+
const next: i32 = quicPutAck(buf, at, end, this.ranges, this.count, ackDelay)
|
|
193
|
+
if (next >= 0) {
|
|
194
|
+
this.ackPending = false
|
|
195
|
+
}
|
|
196
|
+
return next
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Forgets every packet number, for a connection slot reused for another peer. */
|
|
200
|
+
clear(): void {
|
|
201
|
+
this.largest = -1
|
|
202
|
+
this.floor = -1
|
|
203
|
+
this.count = 0
|
|
204
|
+
this.ackPending = false
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/net/quic-conn-cid` — a QUIC connection's connection-ID table (RFC 9000
|
|
3
|
+
* §5.1): the IDs this endpoint issued, which a datagram is routed by, and the
|
|
4
|
+
* ones the peer issued, which this endpoint sends to.
|
|
5
|
+
*
|
|
6
|
+
* import { QuicCidTable } from "nish/net/quic-conn-cid";
|
|
7
|
+
*
|
|
8
|
+
* const ids = new QuicCidTable(4, 4); // the active_connection_id_limit advertised; local slots
|
|
9
|
+
* ids.addLocal(serverScid, token); // sequence 0
|
|
10
|
+
* ids.addPeer(0, 0, clientScid, none); // the client's first SCID, sequence 0
|
|
11
|
+
* const error: i64 = ids.retireLocal(frame.value, packetDcid);
|
|
12
|
+
*
|
|
13
|
+
* **Local IDs** are the ones this side handed out: sequence 0 is the Source
|
|
14
|
+
* Connection ID of its first packet, and each later one goes out in a
|
|
15
|
+
* NEW_CONNECTION_ID frame (`nextUnannounced` answers the next one still to). The peer
|
|
16
|
+
* retires one with RETIRE_CONNECTION_ID: a sequence number never issued, or
|
|
17
|
+
* the one the retiring packet was itself sent to, is a PROTOCOL_VIOLATION
|
|
18
|
+
* (§19.16).
|
|
19
|
+
*
|
|
20
|
+
* **Peer IDs** come from the peer's first packet and its NEW_CONNECTION_ID
|
|
21
|
+
* frames (§19.15). A sequence number seen before must carry the same ID and
|
|
22
|
+
* token, or it is a PROTOCOL_VIOLATION; an ID already given another sequence
|
|
23
|
+
* number is one too. More active IDs than the limit this side advertised is
|
|
24
|
+
* a CONNECTION_ID_LIMIT_ERROR (§5.1.1). Retire Prior To retires every older ID
|
|
25
|
+
* at once: each is queued in `retirePending`, for this side to send
|
|
26
|
+
* RETIRE_CONNECTION_ID for, and `current` moves to the oldest still active.
|
|
27
|
+
* Queued retirements past twice the limit are a CONNECTION_ID_LIMIT_ERROR too
|
|
28
|
+
* (§5.1.2), which bounds what a peer can make this side owe it.
|
|
29
|
+
*
|
|
30
|
+
* **Fixed slots.** The table is sized when it is made — `limit` slots for the
|
|
31
|
+
* peer's IDs, each with room for a 20-byte ID and its token, `localCapacity`
|
|
32
|
+
* for this side's, and a ring of twice the limit for owed retirements — and
|
|
33
|
+
* every ID is copied into a slot, so nothing a peer sends allocates: a frame
|
|
34
|
+
* reads straight from the packet through `addPeerAt`, `ownsLocalAt` and
|
|
35
|
+
* `retireLocal`, and `reset` empties the table for a connection slot reused
|
|
36
|
+
* for another peer. Connection IDs are compared in constant time.
|
|
37
|
+
*
|
|
38
|
+
* Written from RFC 9000 §5.1, §19.15 and §19.16, not ported from another
|
|
39
|
+
* implementation. Private names carry the `quicCid` prefix
|
|
40
|
+
* (`docs/wp26-stdlib.md` §3e).
|
|
41
|
+
*/
|
|
42
|
+
import { timingSafeEqualAt } from "nish/crypto/ct"
|
|
43
|
+
import {
|
|
44
|
+
QUIC_ERROR_CONNECTION_ID_LIMIT,
|
|
45
|
+
QUIC_ERROR_NO_ERROR,
|
|
46
|
+
QUIC_ERROR_PROTOCOL_VIOLATION,
|
|
47
|
+
QUIC_RESET_TOKEN_SIZE,
|
|
48
|
+
} from "nish/net/quic-frame"
|
|
49
|
+
import { QUIC_MAX_CID_LENGTH } from "nish/net/quic-packet"
|
|
50
|
+
|
|
51
|
+
/** A typed zero: a bare literal is an `f64` under `--number-mode f64`. */
|
|
52
|
+
const QUIC_CID_ZERO: i32 = 0
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* One slot of the table: a connection ID and what came with it. `cid` has
|
|
56
|
+
* room for the longest ID, and its first `length` bytes are this one; `used`
|
|
57
|
+
* says whether the slot holds an ID at all.
|
|
58
|
+
*/
|
|
59
|
+
export class QuicCidEntry {
|
|
60
|
+
sequence: i64 = 0
|
|
61
|
+
cid: u8[]
|
|
62
|
+
/** The stateless reset token issued with it (RFC 9000 §10.3); `hasToken` is false for a peer's sequence 0. */
|
|
63
|
+
resetToken: u8[]
|
|
64
|
+
length: i32 = 0
|
|
65
|
+
used: boolean = false
|
|
66
|
+
hasToken: boolean = false
|
|
67
|
+
/** For a local ID: whether a NEW_CONNECTION_ID frame announced it yet. Sequence 0 needs none. */
|
|
68
|
+
announced: boolean = false
|
|
69
|
+
|
|
70
|
+
constructor() {
|
|
71
|
+
this.cid = new Array<u8>(QUIC_MAX_CID_LENGTH)
|
|
72
|
+
this.resetToken = new Array<u8>(QUIC_RESET_TOKEN_SIZE)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Copies `length` bytes of `from` at `at` into the start of `to`, and zeroes the rest of `to`. */
|
|
77
|
+
export const quicCidCopy = (to: u8[], from: u8[], at: i32, length: i32): void => {
|
|
78
|
+
for (let k: i32 = 0; k < toI32(to.length); k += 1) {
|
|
79
|
+
to[k] = k < length && at + k >= 0 && at + k < toI32(from.length) ? from[at + k] : toU8(0)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The index of the used entry with the smallest sequence number above `after`, only unannounced ones when `unannounced`, or -1. */
|
|
84
|
+
const quicCidOldest = (entries: QuicCidEntry[], after: i64, unannounced: boolean): i32 => {
|
|
85
|
+
let best: i32 = -1
|
|
86
|
+
let bestSequence: i64 = -1
|
|
87
|
+
for (let k: i32 = 0; k < toI32(entries.length); k += 1) {
|
|
88
|
+
const entry: QuicCidEntry = entries[k]
|
|
89
|
+
if (
|
|
90
|
+
entry.used &&
|
|
91
|
+
entry.sequence > after &&
|
|
92
|
+
(!unannounced || !entry.announced) &&
|
|
93
|
+
(bestSequence < 0 || entry.sequence < bestSequence)
|
|
94
|
+
) {
|
|
95
|
+
best = k
|
|
96
|
+
bestSequence = entry.sequence
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return best
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The index of the first unused entry, or -1 when every one is taken. */
|
|
103
|
+
const quicCidFree = (entries: QuicCidEntry[]): i32 => {
|
|
104
|
+
for (let k: i32 = 0; k < toI32(entries.length); k += 1) {
|
|
105
|
+
if (!entries[k].used) {
|
|
106
|
+
return k
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return -1
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Empties one slot: its sequence and flags reset and its bytes zeroed, the token among them. */
|
|
113
|
+
const quicCidClear = (entry: QuicCidEntry): void => {
|
|
114
|
+
entry.used = false
|
|
115
|
+
entry.hasToken = false
|
|
116
|
+
entry.announced = false
|
|
117
|
+
entry.sequence = 0
|
|
118
|
+
entry.length = 0
|
|
119
|
+
secureZero(entry.cid)
|
|
120
|
+
secureZero(entry.resetToken)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** `count` empty slots. */
|
|
124
|
+
const quicCidSlots = (count: i32): QuicCidEntry[] => {
|
|
125
|
+
const out: QuicCidEntry[] = []
|
|
126
|
+
for (let k: i32 = 0; k < count; k += 1) {
|
|
127
|
+
out.push(new QuicCidEntry())
|
|
128
|
+
}
|
|
129
|
+
return out
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Both sides of one connection's IDs. `limit` is the
|
|
134
|
+
* `active_connection_id_limit` this side advertised, which bounds the peer's.
|
|
135
|
+
*/
|
|
136
|
+
export class QuicCidTable {
|
|
137
|
+
local: QuicCidEntry[]
|
|
138
|
+
peer: QuicCidEntry[]
|
|
139
|
+
/** Peer sequence numbers this side must send RETIRE_CONNECTION_ID for, oldest first, as a ring of `retireCount` from `retireHead`. */
|
|
140
|
+
retirePending: i64[]
|
|
141
|
+
/** The next local sequence number to issue. */
|
|
142
|
+
nextLocal: i64 = 0
|
|
143
|
+
/** The peer's highest Retire Prior To so far. */
|
|
144
|
+
peerRetirePriorTo: i64 = 0
|
|
145
|
+
limit: i64 = 2
|
|
146
|
+
/** The index in `peer` of the ID this side sends to, or -1 before the peer gave one. */
|
|
147
|
+
current: i32 = -1
|
|
148
|
+
retireHead: i32 = 0
|
|
149
|
+
retireCount: i32 = 0
|
|
150
|
+
|
|
151
|
+
/** A table for an advertised `limit` (2 to 8 in practice) with `localCapacity` slots for this side's IDs. */
|
|
152
|
+
constructor(limit: i64, localCapacity: i32) {
|
|
153
|
+
const peerSlots: i32 = limit < 1 ? 1 : toI32(limit)
|
|
154
|
+
this.local = quicCidSlots(localCapacity)
|
|
155
|
+
this.peer = quicCidSlots(peerSlots)
|
|
156
|
+
this.retirePending = new Array<i64>(peerSlots * 2)
|
|
157
|
+
this.limit = limit
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Empties the table, for a connection slot reused for another peer; the slots and their bytes are kept, zeroed. */
|
|
161
|
+
reset(): void {
|
|
162
|
+
for (const entry of this.local) {
|
|
163
|
+
quicCidClear(entry)
|
|
164
|
+
}
|
|
165
|
+
for (const entry of this.peer) {
|
|
166
|
+
quicCidClear(entry)
|
|
167
|
+
}
|
|
168
|
+
this.nextLocal = 0
|
|
169
|
+
this.peerRetirePriorTo = 0
|
|
170
|
+
this.current = -1
|
|
171
|
+
this.retireHead = 0
|
|
172
|
+
this.retireCount = 0
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Issues the first `length` bytes of `cid` as the next local ID, with its
|
|
177
|
+
* 16-byte reset token (or none, empty, for sequence 0), and answers its
|
|
178
|
+
* sequence number; -1 when every local slot is taken.
|
|
179
|
+
*/
|
|
180
|
+
addLocal(cid: u8[], resetToken: u8[]): i64 {
|
|
181
|
+
const k: i32 = quicCidFree(this.local)
|
|
182
|
+
if (k < 0) {
|
|
183
|
+
return -1
|
|
184
|
+
}
|
|
185
|
+
const entry: QuicCidEntry = this.local[k]
|
|
186
|
+
const sequence: i64 = this.nextLocal
|
|
187
|
+
const length: i32 = toI32(cid.length) > QUIC_MAX_CID_LENGTH ? QUIC_MAX_CID_LENGTH : toI32(cid.length)
|
|
188
|
+
quicCidCopy(entry.cid, cid, 0, length)
|
|
189
|
+
entry.length = length
|
|
190
|
+
entry.hasToken = toI32(resetToken.length) === QUIC_RESET_TOKEN_SIZE
|
|
191
|
+
quicCidCopy(entry.resetToken, resetToken, 0, entry.hasToken ? QUIC_RESET_TOKEN_SIZE : 0)
|
|
192
|
+
entry.sequence = sequence
|
|
193
|
+
entry.used = true
|
|
194
|
+
// Sequence 0 travels in the long header's SCID, never in a frame.
|
|
195
|
+
entry.announced = sequence === 0
|
|
196
|
+
this.nextLocal = sequence + 1
|
|
197
|
+
return sequence
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** How many local IDs are active: a retired one leaves its slot. */
|
|
201
|
+
activeLocal(): i32 {
|
|
202
|
+
let n: i32 = 0
|
|
203
|
+
for (const entry of this.local) {
|
|
204
|
+
if (entry.used) {
|
|
205
|
+
n += 1
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return n
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Whether `dcid` is an active local ID: whether a packet sent to it is this connection's. */
|
|
212
|
+
ownsLocal(dcid: u8[]): boolean {
|
|
213
|
+
return this.ownsLocalAt(dcid, QUIC_CID_ZERO, toI32(dcid.length))
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Whether `buf[at .. at + length)` is an active local ID. Every slot is compared, each in constant time. */
|
|
217
|
+
ownsLocalAt(buf: u8[], at: i32, length: i32): boolean {
|
|
218
|
+
let owned: boolean = false
|
|
219
|
+
for (const entry of this.local) {
|
|
220
|
+
if (entry.used && entry.length === length && timingSafeEqualAt(entry.cid, 0, buf, at, length)) {
|
|
221
|
+
owned = true
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return owned
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* The peer retires local ID `sequence` (RETIRE_CONNECTION_ID), in a packet
|
|
229
|
+
* sent to `buf[at .. at + length)`. Answers `QUIC_ERROR_NO_ERROR`, or
|
|
230
|
+
* `QUIC_ERROR_PROTOCOL_VIOLATION` for a sequence number never issued or the
|
|
231
|
+
* ID the packet itself was sent to (§19.16). Retiring one twice is fine. A
|
|
232
|
+
* retired ID leaves its slot, so a peer that retires one ID after another
|
|
233
|
+
* cannot grow the table.
|
|
234
|
+
*/
|
|
235
|
+
retireLocal(sequence: i64, buf: u8[], at: i32, length: i32): i64 {
|
|
236
|
+
if (sequence < 0 || sequence >= this.nextLocal) {
|
|
237
|
+
return QUIC_ERROR_PROTOCOL_VIOLATION
|
|
238
|
+
}
|
|
239
|
+
for (const entry of this.local) {
|
|
240
|
+
if (entry.used && entry.sequence === sequence) {
|
|
241
|
+
if (entry.length === length && timingSafeEqualAt(entry.cid, 0, buf, at, length)) {
|
|
242
|
+
return QUIC_ERROR_PROTOCOL_VIOLATION
|
|
243
|
+
}
|
|
244
|
+
quicCidClear(entry)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return QUIC_ERROR_NO_ERROR
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** How many peer IDs are active: retired ones leave their slots. */
|
|
251
|
+
activePeer(): i32 {
|
|
252
|
+
let n: i32 = 0
|
|
253
|
+
for (const entry of this.peer) {
|
|
254
|
+
if (entry.used) {
|
|
255
|
+
n += 1
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return n
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** `addPeerAt` of whole arrays: `resetToken` empty for none. */
|
|
262
|
+
addPeer(sequence: i64, retirePriorTo: i64, cid: u8[], resetToken: u8[]): i64 {
|
|
263
|
+
return this.addPeerAt(
|
|
264
|
+
sequence,
|
|
265
|
+
retirePriorTo,
|
|
266
|
+
cid,
|
|
267
|
+
QUIC_CID_ZERO,
|
|
268
|
+
toI32(cid.length),
|
|
269
|
+
resetToken,
|
|
270
|
+
QUIC_CID_ZERO,
|
|
271
|
+
toI32(resetToken.length) === QUIC_RESET_TOKEN_SIZE
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Takes a peer ID, `cid[cidAt .. cidAt + cidLength)`, with the 16-byte token
|
|
277
|
+
* at `token[tokenAt]` when `hasToken` (its first packet's SCID is sequence
|
|
278
|
+
* 0, with none; a NEW_CONNECTION_ID frame's has one). Answers
|
|
279
|
+
* `QUIC_ERROR_NO_ERROR`, `QUIC_ERROR_PROTOCOL_VIOLATION` for a sequence
|
|
280
|
+
* number reused with another ID or token or an ID reused under another
|
|
281
|
+
* sequence number, or `QUIC_ERROR_CONNECTION_ID_LIMIT` when the active IDs
|
|
282
|
+
* or the queued retirements would pass what §5.1 allows, in which case the
|
|
283
|
+
* table is left as it was. The tokens are compared in constant time, since
|
|
284
|
+
* a token is what a stateless reset is checked against.
|
|
285
|
+
*/
|
|
286
|
+
addPeerAt(
|
|
287
|
+
sequence: i64,
|
|
288
|
+
retirePriorTo: i64,
|
|
289
|
+
cid: u8[],
|
|
290
|
+
cidAt: i32,
|
|
291
|
+
cidLength: i32,
|
|
292
|
+
token: u8[],
|
|
293
|
+
tokenAt: i32,
|
|
294
|
+
hasToken: boolean
|
|
295
|
+
): i64 {
|
|
296
|
+
if (cidLength < 0 || cidLength > QUIC_MAX_CID_LENGTH) {
|
|
297
|
+
return QUIC_ERROR_PROTOCOL_VIOLATION
|
|
298
|
+
}
|
|
299
|
+
for (const entry of this.peer) {
|
|
300
|
+
if (!entry.used) {
|
|
301
|
+
continue
|
|
302
|
+
}
|
|
303
|
+
const sameId: boolean =
|
|
304
|
+
entry.length === cidLength && timingSafeEqualAt(entry.cid, 0, cid, cidAt, cidLength)
|
|
305
|
+
if (entry.sequence === sequence) {
|
|
306
|
+
const sameToken: boolean =
|
|
307
|
+
entry.hasToken === hasToken &&
|
|
308
|
+
(!hasToken || timingSafeEqualAt(entry.resetToken, 0, token, tokenAt, QUIC_RESET_TOKEN_SIZE))
|
|
309
|
+
return sameId && sameToken ? QUIC_ERROR_NO_ERROR : QUIC_ERROR_PROTOCOL_VIOLATION
|
|
310
|
+
}
|
|
311
|
+
if (sameId) {
|
|
312
|
+
return QUIC_ERROR_PROTOCOL_VIOLATION
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// Count what the frame would leave before changing anything, so a frame
|
|
316
|
+
// refused for a limit leaves the table as it was.
|
|
317
|
+
const priorTo: i64 = retirePriorTo > this.peerRetirePriorTo ? retirePriorTo : this.peerRetirePriorTo
|
|
318
|
+
let active: i64 = sequence < priorTo ? 0 : 1
|
|
319
|
+
let pending: i64 = toI64(this.retireCount) + (sequence < priorTo ? 1 : 0)
|
|
320
|
+
for (const old of this.peer) {
|
|
321
|
+
if (!old.used) {
|
|
322
|
+
continue
|
|
323
|
+
}
|
|
324
|
+
if (old.sequence < priorTo) {
|
|
325
|
+
pending += 1
|
|
326
|
+
} else {
|
|
327
|
+
active += 1
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (
|
|
331
|
+
active > this.limit ||
|
|
332
|
+
pending > this.limit * 2 ||
|
|
333
|
+
pending > toI64(toI32(this.retirePending.length))
|
|
334
|
+
) {
|
|
335
|
+
return QUIC_ERROR_CONNECTION_ID_LIMIT
|
|
336
|
+
}
|
|
337
|
+
this.peerRetirePriorTo = priorTo
|
|
338
|
+
// §19.15: every ID below Retire Prior To is retired, oldest first, and
|
|
339
|
+
// one that arrives already below it is retired as it arrives.
|
|
340
|
+
let k: i32 = quicCidOldest(this.peer, -1, false)
|
|
341
|
+
while (k >= 0 && k < toI32(this.peer.length) && this.peer[k].sequence < priorTo) {
|
|
342
|
+
const old: QuicCidEntry = this.peer[k]
|
|
343
|
+
this.queueRetire(old.sequence)
|
|
344
|
+
const after: i64 = old.sequence
|
|
345
|
+
quicCidClear(old)
|
|
346
|
+
k = quicCidOldest(this.peer, after, false)
|
|
347
|
+
}
|
|
348
|
+
if (sequence < priorTo) {
|
|
349
|
+
this.queueRetire(sequence)
|
|
350
|
+
} else {
|
|
351
|
+
const free: i32 = quicCidFree(this.peer)
|
|
352
|
+
if (free >= 0 && free < toI32(this.peer.length)) {
|
|
353
|
+
const entry: QuicCidEntry = this.peer[free]
|
|
354
|
+
quicCidCopy(entry.cid, cid, cidAt, cidLength)
|
|
355
|
+
entry.length = cidLength
|
|
356
|
+
entry.hasToken = hasToken
|
|
357
|
+
quicCidCopy(entry.resetToken, token, tokenAt, hasToken ? QUIC_RESET_TOKEN_SIZE : 0)
|
|
358
|
+
entry.sequence = sequence
|
|
359
|
+
entry.used = true
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
this.prune()
|
|
363
|
+
return QUIC_ERROR_NO_ERROR
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** Points `current` at the oldest active peer ID, or -1 when none is. */
|
|
367
|
+
prune(): void {
|
|
368
|
+
this.current = quicCidOldest(this.peer, -1, false)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** The peer ID this side sends to: its slot, or `null` before the peer gave any. */
|
|
372
|
+
currentPeerEntry(): QuicCidEntry | null {
|
|
373
|
+
const at: i32 = this.current
|
|
374
|
+
return at >= 0 && at < toI32(this.peer.length) ? this.peer[at] : null
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** A copy of the peer ID this side sends to, or empty before the peer gave any. */
|
|
378
|
+
currentPeer(): u8[] {
|
|
379
|
+
const entry: QuicCidEntry | null = this.currentPeerEntry()
|
|
380
|
+
const length: i32 = entry !== null ? entry.length : 0
|
|
381
|
+
const out: u8[] = new Array<u8>(length)
|
|
382
|
+
if (entry !== null) {
|
|
383
|
+
quicCidCopy(out, entry.cid, 0, length)
|
|
384
|
+
}
|
|
385
|
+
return out
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** Queues a RETIRE_CONNECTION_ID for peer sequence `sequence`, unless it is queued already or the ring is full. */
|
|
389
|
+
queueRetire(sequence: i64): void {
|
|
390
|
+
const size: i32 = toI32(this.retirePending.length)
|
|
391
|
+
for (let k: i32 = 0; k < this.retireCount; k += 1) {
|
|
392
|
+
const at: i32 = (this.retireHead + k) % size
|
|
393
|
+
if (at >= 0 && at < size && this.retirePending[at] === sequence) {
|
|
394
|
+
return
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (this.retireCount >= size || size === 0) {
|
|
398
|
+
return
|
|
399
|
+
}
|
|
400
|
+
const at: i32 = (this.retireHead + this.retireCount) % size
|
|
401
|
+
if (at >= 0 && at < size) {
|
|
402
|
+
this.retirePending[at] = sequence
|
|
403
|
+
this.retireCount = this.retireCount + 1
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** The oldest queued retirement, or -1 when none is queued. Taking it removes it. */
|
|
408
|
+
takeRetire(): i64 {
|
|
409
|
+
const size: i32 = toI32(this.retirePending.length)
|
|
410
|
+
if (this.retireCount === 0 || size === 0) {
|
|
411
|
+
return -1
|
|
412
|
+
}
|
|
413
|
+
const at: i32 = this.retireHead
|
|
414
|
+
const first: i64 = at >= 0 && at < size ? this.retirePending[at] : -1
|
|
415
|
+
this.retireHead = (at + 1) % size
|
|
416
|
+
this.retireCount = this.retireCount - 1
|
|
417
|
+
return first
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** The first local ID still to be announced in a NEW_CONNECTION_ID frame, oldest first, or `null`. */
|
|
421
|
+
nextUnannounced(): QuicCidEntry | null {
|
|
422
|
+
const k: i32 = quicCidOldest(this.local, -1, true)
|
|
423
|
+
return k >= 0 && k < toI32(this.local.length) ? this.local[k] : null
|
|
424
|
+
}
|
|
425
|
+
}
|