@amritk/nish-x86_64-linux 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/INSTALL.md +43 -34
  2. package/bin/nish +0 -0
  3. package/package.json +1 -1
  4. package/runtime/nish.d.ts +117 -8
  5. package/runtime/nish.h +30 -7
  6. package/runtime/nish.mjs +71 -7
  7. package/runtime/runtime-host.c +43 -24
  8. package/runtime/runtime-net.c +57 -10
  9. package/runtime/runtime-os.c +62 -20
  10. package/runtime/runtime-wasm.c +1 -0
  11. package/runtime/runtime.c +46 -8
  12. package/runtime/shim.mjs +114 -5
  13. package/scripts/build.sh +1 -3
  14. package/std/README.md +166 -35
  15. package/std/crypto/aes.ts +51 -8
  16. package/std/crypto/base64url.ts +25 -12
  17. package/std/crypto/chacha20poly1305.ts +43 -29
  18. package/std/crypto/ct.ts +6 -1
  19. package/std/crypto/hkdf.ts +364 -3
  20. package/std/crypto/hmac.ts +307 -7
  21. package/std/crypto/p256.ts +148 -27
  22. package/std/crypto/sha1.ts +147 -0
  23. package/std/crypto/sha256.ts +3 -2
  24. package/std/crypto/x25519.ts +88 -36
  25. package/std/crypto/x509.ts +130 -47
  26. package/std/net/hpack.ts +1094 -0
  27. package/std/net/http-fields.ts +604 -0
  28. package/std/net/http1-server.ts +1389 -0
  29. package/std/net/http1.ts +1492 -0
  30. package/std/net/http2-frame.ts +745 -0
  31. package/std/net/http2-tls.ts +216 -0
  32. package/std/net/http2.ts +1433 -0
  33. package/std/net/http3-frame.ts +457 -0
  34. package/std/net/http3-server.ts +905 -0
  35. package/std/net/http3.ts +2145 -0
  36. package/std/net/qpack.ts +1163 -0
  37. package/std/net/quic-conn-ack.ts +206 -0
  38. package/std/net/quic-conn-cid.ts +425 -0
  39. package/std/net/quic-conn-params.ts +392 -0
  40. package/std/net/quic-datagram.ts +130 -0
  41. package/std/net/quic-frame.ts +1206 -0
  42. package/std/net/quic-listener.ts +653 -0
  43. package/std/net/quic-packet.ts +1474 -0
  44. package/std/net/quic-recovery.ts +849 -0
  45. package/std/net/quic-stream.ts +1603 -0
  46. package/std/net/quic.ts +3214 -0
  47. package/std/net/tls/codec.ts +1089 -0
  48. package/std/net/tls/record-server.ts +1173 -0
  49. package/std/net/tls/record.ts +621 -0
  50. package/std/net/tls/schedule.ts +429 -0
  51. package/std/net/tls-tcp.ts +410 -0
  52. package/std/net/tls.ts +1368 -0
  53. package/std/net/websocket.ts +823 -0
  54. package/std/net/webtransport.ts +1334 -0
  55. package/std/secret.ts +62 -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
+ }