@amritk/nish-x86_64-linux 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +43 -25
- package/bin/nish +0 -0
- package/package.json +2 -2
- package/runtime/nish.d.ts +55 -5
- package/runtime/nish.h +215 -18
- package/runtime/nish.mjs +16 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +7 -6
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +213 -15
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +4 -4
- package/runtime/runtime-wasm.c +5 -3
- package/runtime/runtime.c +69 -13
- package/runtime/shim.mjs +71 -1
- package/scripts/build.sh +223 -22
- package/std/README.md +81 -51
- package/std/bytes.ts +163 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/chacha20poly1305.ts +43 -9
- package/std/crypto/ed25519.ts +840 -0
- package/std/crypto/hkdf.ts +304 -12
- package/std/crypto/hmac.ts +245 -53
- package/std/crypto/p256.ts +932 -61
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +9 -9
- package/std/json-tokenizer.ts +1296 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1163 -0
- package/std/json.ts +1120 -34
- package/std/net/buffer-pool.ts +151 -0
- package/std/net/hpack.ts +416 -112
- package/std/net/http-fields.ts +93 -0
- package/std/net/http1-server.ts +417 -55
- package/std/net/http1.ts +31 -6
- package/std/net/http2-tls.ts +112 -24
- package/std/net/http2.ts +333 -75
- package/std/net/http3-server.ts +211 -31
- package/std/net/http3.ts +187 -28
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-listener.ts +88 -7
- package/std/net/quic-packet.ts +243 -82
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +424 -24
- package/std/net/quic.ts +519 -167
- package/std/net/router.ts +676 -0
- package/std/net/tls/codec.ts +426 -27
- package/std/net/tls/record-server.ts +478 -132
- package/std/net/tls/record.ts +133 -42
- package/std/net/tls/schedule.ts +464 -55
- package/std/net/tls/ticket.ts +610 -0
- package/std/net/tls-tcp.ts +196 -53
- package/std/net/tls.ts +1018 -260
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/threads.ts +438 -14
package/std/net/quic-recovery.ts
CHANGED
|
@@ -31,12 +31,39 @@
|
|
|
31
31
|
* adjusted_rtt) / 8`, `rttvar = (3 × rttvar + |smoothed_rtt −
|
|
32
32
|
* adjusted_rtt|) / 4`, each division truncating. Sizes are bytes.
|
|
33
33
|
*
|
|
34
|
+
* **The pacer's clock.** Pacing is not a timer, and on a path whose round
|
|
35
|
+
* trip is under a millisecond a pacer that counts whole milliseconds earns
|
|
36
|
+
* nothing until the next one starts, and then waits a whole one. So a
|
|
37
|
+
* caller with a finer clock sets `micros` to the same instant in
|
|
38
|
+
* microseconds before it calls in, and the pacer, its budget and its own
|
|
39
|
+
* smoothed RTT (`pacerRtt`) count microseconds; loss detection and the
|
|
40
|
+
* probe timeout still count milliseconds, at least
|
|
41
|
+
* `QUIC_RECOVERY_GRANULARITY`. A `micros` outside the millisecond `now`
|
|
42
|
+
* names, a stale one or none, is not read: the pacer counts `now × 1000`
|
|
43
|
+
* then, and paces as a millisecond clock always did.
|
|
44
|
+
*
|
|
34
45
|
* **What the server leaves out.** It is written for the server side of a
|
|
35
46
|
* connection: there is no client anti-deadlock probe (§6.2.2.1), since a
|
|
36
|
-
* server never has to send to unblock its peer
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
47
|
+
* server never has to send to unblock its peer. A congestion window that
|
|
48
|
+
* the sender does not use is not grown (§7.8): an acknowledgement grows it
|
|
49
|
+
* only if, when it arrived, the bytes in flight were at least half the
|
|
50
|
+
* window.
|
|
51
|
+
*
|
|
52
|
+
* **ECN** (§7.1, B.7; RFC 9000 §13.4.2 and its Appendix A.4). A caller that
|
|
53
|
+
* marks its packets ECT(0) asks `ecnCodepoint()` what to mark with, says so
|
|
54
|
+
* with `onEctSent` for every packet it marked, and hands an ACK_ECN frame's
|
|
55
|
+
* counts to `onAckEcn`. The counts are validated against the packets marked
|
|
56
|
+
* before they are used: an ACK that newly acknowledges a marked packet
|
|
57
|
+
* without counts, a count that falls, counts past the packets marked, or an
|
|
58
|
+
* increase in ECT(0) and ECN-CE short of the marked packets newly
|
|
59
|
+
* acknowledged fails validation, as does every packet of the testing period
|
|
60
|
+
* being lost, and a failed path is marked no more (`QUIC_RECOVERY_ECN_FAILED`).
|
|
61
|
+
* Only the first `QUIC_RECOVERY_ECN_TESTING_PACKETS` are marked until one
|
|
62
|
+
* validates. A rise in the ECN-CE count is a congestion event, from the
|
|
63
|
+
* largest packet newly acknowledged, so it halves the window once per
|
|
64
|
+
* recovery period as a loss does. Counts that arrive with an ACK that does
|
|
65
|
+
* not raise the largest acknowledged are neither validated nor used: they
|
|
66
|
+
* may be older than those already read.
|
|
40
67
|
*
|
|
41
68
|
* **What a peer cannot do.** Every input is checked: the ranges of an
|
|
42
69
|
* acknowledgement are read only up to the count given and the array's
|
|
@@ -110,6 +137,28 @@ const QUIC_RECOVERY_SENT: i32 = 1
|
|
|
110
137
|
const QUIC_RECOVERY_ACKED: i32 = 2
|
|
111
138
|
const QUIC_RECOVERY_LOST: i32 = 3
|
|
112
139
|
const QUIC_RECOVERY_LOST_NOW: i32 = 4
|
|
140
|
+
/** The bit of a slot's state byte that says its packet was sent marked ECT(0), kept beside the state so a slot costs no second byte. */
|
|
141
|
+
const QUIC_RECOVERY_MARKED: i32 = 128
|
|
142
|
+
|
|
143
|
+
/** The ECT(0) codepoint (RFC 3168), the only one this module asks a caller to mark with. */
|
|
144
|
+
export const QUIC_RECOVERY_ECT0: i32 = 2
|
|
145
|
+
/** How many packets are marked while ECN is tested, before a marked one is acknowledged (RFC 9000 A.4). */
|
|
146
|
+
export const QUIC_RECOVERY_ECN_TESTING_PACKETS: i32 = 10
|
|
147
|
+
/** ECN's states (RFC 9000 A.4): testing, marking the first packets; unknown, those sent and none validated yet; capable; and failed, for the connection's life. */
|
|
148
|
+
export const QUIC_RECOVERY_ECN_TESTING: i32 = 0
|
|
149
|
+
export const QUIC_RECOVERY_ECN_UNKNOWN: i32 = 1
|
|
150
|
+
export const QUIC_RECOVERY_ECN_CAPABLE: i32 = 2
|
|
151
|
+
export const QUIC_RECOVERY_ECN_FAILED: i32 = 3
|
|
152
|
+
|
|
153
|
+
/** Microseconds in a millisecond: the pacer's clock against the caller's `now`. */
|
|
154
|
+
const QUIC_RECOVERY_MICROS: i64 = 1000
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The most window the pacer earns by, in bytes: 4 GiB a round trip, faster
|
|
158
|
+
* than any path such a window could be on. It and the minute `pacerRefill`
|
|
159
|
+
* counts at most keep the product of the two inside an `i64`.
|
|
160
|
+
*/
|
|
161
|
+
const QUIC_RECOVERY_PACER_WINDOW: i64 = 4294967296
|
|
113
162
|
|
|
114
163
|
/** The larger of two times or durations. */
|
|
115
164
|
const quicRecoveryMax = (a: i64, b: i64): i64 => (a > b ? a : b)
|
|
@@ -125,7 +174,10 @@ const quicRecoveryMax = (a: i64, b: i64): i64 => (a > b ? a : b)
|
|
|
125
174
|
export class QuicSentPackets {
|
|
126
175
|
pn: i64[]
|
|
127
176
|
timeSent: i64[]
|
|
177
|
+
/** When each packet was sent on the pacer's clock, in microseconds. */
|
|
178
|
+
fineSent: i64[]
|
|
128
179
|
size: i32[]
|
|
180
|
+
/** Each slot's state, with `QUIC_RECOVERY_MARKED` set if its packet was sent marked ECT(0). */
|
|
129
181
|
state: u8[]
|
|
130
182
|
/** The slots the last `onAck` acknowledged, `ackedCount` of them, oldest first. */
|
|
131
183
|
acked: i32[]
|
|
@@ -148,11 +200,18 @@ export class QuicSentPackets {
|
|
|
148
200
|
inFlight: i32 = 0
|
|
149
201
|
ackedCount: i32 = 0
|
|
150
202
|
lostCount: i32 = 0
|
|
203
|
+
/** How many of this space's packets, recorded or not, were sent marked ECT(0). */
|
|
204
|
+
ectSent: i64 = 0
|
|
205
|
+
/** The ECN counts of the last ACK_ECN frame validated in this space (RFC 9000 §19.3.2). */
|
|
206
|
+
ect0: i64 = 0
|
|
207
|
+
ect1: i64 = 0
|
|
208
|
+
ce: i64 = 0
|
|
151
209
|
|
|
152
210
|
constructor(capacity: i32) {
|
|
153
211
|
this.capacity = capacity
|
|
154
212
|
this.pn = new Array<i64>(capacity)
|
|
155
213
|
this.timeSent = new Array<i64>(capacity)
|
|
214
|
+
this.fineSent = new Array<i64>(capacity)
|
|
156
215
|
this.size = new Array<i32>(capacity)
|
|
157
216
|
this.state = new Array<u8>(capacity)
|
|
158
217
|
this.acked = new Array<i32>(capacity)
|
|
@@ -169,13 +228,13 @@ export class QuicSentPackets {
|
|
|
169
228
|
if (s < 0 || s >= toI32(this.state.length)) {
|
|
170
229
|
return QUIC_RECOVERY_FREE
|
|
171
230
|
}
|
|
172
|
-
return toI32(this.state[s])
|
|
231
|
+
return toI32(this.state[s]) & (QUIC_RECOVERY_MARKED - 1)
|
|
173
232
|
}
|
|
174
233
|
|
|
175
234
|
/** Sets slot `s`'s state. */
|
|
176
235
|
setState(s: i32, value: i32): void {
|
|
177
236
|
if (s >= 0 && s < toI32(this.state.length)) {
|
|
178
|
-
this.state[s] = toU8(value)
|
|
237
|
+
this.state[s] = toU8((toI32(this.state[s]) & QUIC_RECOVERY_MARKED) | value)
|
|
179
238
|
}
|
|
180
239
|
}
|
|
181
240
|
|
|
@@ -189,11 +248,28 @@ export class QuicSentPackets {
|
|
|
189
248
|
return s >= 0 && s < toI32(this.timeSent.length) ? this.timeSent[s] : -1
|
|
190
249
|
}
|
|
191
250
|
|
|
251
|
+
/** When the packet in slot `s` was sent on the pacer's clock, in microseconds, or -1. */
|
|
252
|
+
fineAt(s: i32): i64 {
|
|
253
|
+
return s >= 0 && s < toI32(this.fineSent.length) ? this.fineSent[s] : -1
|
|
254
|
+
}
|
|
255
|
+
|
|
192
256
|
/** The size of the packet in slot `s`, or 0. */
|
|
193
257
|
sizeAt(s: i32): i32 {
|
|
194
258
|
return s >= 0 && s < toI32(this.size.length) ? this.size[s] : 0
|
|
195
259
|
}
|
|
196
260
|
|
|
261
|
+
/** Whether the packet in slot `s` was sent marked ECT(0). */
|
|
262
|
+
markedAt(s: i32): boolean {
|
|
263
|
+
return s >= 0 && s < toI32(this.state.length) && (toI32(this.state[s]) & QUIC_RECOVERY_MARKED) !== 0
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Sets whether the packet in slot `s` was sent marked ECT(0). */
|
|
267
|
+
setMarked(s: i32, value: boolean): void {
|
|
268
|
+
if (s >= 0 && s < toI32(this.state.length)) {
|
|
269
|
+
this.state[s] = toU8(this.stateAt(s) | (value ? QUIC_RECOVERY_MARKED : 0))
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
197
273
|
/** The `k`-th slot the last call acknowledged, or -1. */
|
|
198
274
|
ackedSlot(k: i32): i32 {
|
|
199
275
|
return k >= 0 && k < this.ackedCount && k < toI32(this.acked.length) ? this.acked[k] : -1
|
|
@@ -268,6 +344,10 @@ export class QuicSentPackets {
|
|
|
268
344
|
this.clear()
|
|
269
345
|
this.largestAcked = -1
|
|
270
346
|
this.lastPn = -1
|
|
347
|
+
this.ectSent = 0
|
|
348
|
+
this.ect0 = 0
|
|
349
|
+
this.ect1 = 0
|
|
350
|
+
this.ce = 0
|
|
271
351
|
}
|
|
272
352
|
}
|
|
273
353
|
|
|
@@ -313,9 +393,13 @@ export class QuicRecovery {
|
|
|
313
393
|
ssthresh: i64 = 0
|
|
314
394
|
/** congestion_recovery_start_time (B.2), or -1 outside recovery. */
|
|
315
395
|
recoveryStart: i64 = -1
|
|
316
|
-
/** The pacer's credit in bytes (§7.7), and the time it was last topped up, or -1. */
|
|
396
|
+
/** The pacer's credit in bytes (§7.7), and the time it was last topped up in microseconds, or -1. */
|
|
317
397
|
pacerBudget: i64 = 0
|
|
318
398
|
pacerTime: i64 = -1
|
|
399
|
+
/** The smoothed RTT the pacer's rate is taken from, in microseconds: `smoothedRtt` on the pacer's clock. */
|
|
400
|
+
pacerRtt: i64 = 0
|
|
401
|
+
/** The caller's clock in microseconds, which it sets before each call when it keeps one, or -1. */
|
|
402
|
+
micros: i64 = -1
|
|
319
403
|
/** pto_count (A.3): how many probe timeouts fired since the last acknowledgement. */
|
|
320
404
|
ptoCount: i32 = 0
|
|
321
405
|
/** The space the last `onTimeout` acted on, or -1. */
|
|
@@ -323,6 +407,14 @@ export class QuicRecovery {
|
|
|
323
407
|
/** How many congestion events reduced the window, and how many of them were persistent congestion. */
|
|
324
408
|
congestionEvents: i32 = 0
|
|
325
409
|
persistentCongestions: i32 = 0
|
|
410
|
+
/** How many congestion events a rise in the ECN-CE count caused (§7.1). */
|
|
411
|
+
ecnCongestionEvents: i32 = 0
|
|
412
|
+
/** ECN's state, `QUIC_RECOVERY_ECN_TESTING` until a marked packet's counts validate or fail. */
|
|
413
|
+
ecnState: i32 = 0
|
|
414
|
+
/** The packets marked while testing, and of those recorded, how many and how many of them were lost. */
|
|
415
|
+
ecnTestSent: i32 = 0
|
|
416
|
+
ecnTestRecorded: i32 = 0
|
|
417
|
+
ecnTestLost: i32 = 0
|
|
326
418
|
/** Whether the handshake is confirmed (RFC 9001 §4.1.2), which the caller sets: the peer's max_ack_delay then bounds its ACK delays, and the Application Data space has a probe timeout. */
|
|
327
419
|
handshakeConfirmed: boolean = false
|
|
328
420
|
|
|
@@ -355,10 +447,17 @@ export class QuicRecovery {
|
|
|
355
447
|
this.recoveryStart = -1
|
|
356
448
|
this.pacerBudget = QUIC_RECOVERY_INITIAL_WINDOW
|
|
357
449
|
this.pacerTime = -1
|
|
450
|
+
this.pacerRtt = QUIC_RECOVERY_INITIAL_RTT * QUIC_RECOVERY_MICROS
|
|
451
|
+
this.micros = -1
|
|
358
452
|
this.ptoCount = 0
|
|
359
453
|
this.timeoutSpace = -1
|
|
360
454
|
this.congestionEvents = 0
|
|
361
455
|
this.persistentCongestions = 0
|
|
456
|
+
this.ecnCongestionEvents = 0
|
|
457
|
+
this.ecnState = QUIC_RECOVERY_ECN_TESTING
|
|
458
|
+
this.ecnTestSent = 0
|
|
459
|
+
this.ecnTestRecorded = 0
|
|
460
|
+
this.ecnTestLost = 0
|
|
362
461
|
this.handshakeConfirmed = false
|
|
363
462
|
}
|
|
364
463
|
|
|
@@ -400,12 +499,20 @@ export class QuicRecovery {
|
|
|
400
499
|
return -1
|
|
401
500
|
}
|
|
402
501
|
const s: i32 = sp.slot(sp.count)
|
|
403
|
-
if (
|
|
502
|
+
if (
|
|
503
|
+
s < 0 ||
|
|
504
|
+
s >= toI32(sp.pn.length) ||
|
|
505
|
+
s >= toI32(sp.timeSent.length) ||
|
|
506
|
+
s >= toI32(sp.fineSent.length) ||
|
|
507
|
+
s >= toI32(sp.size.length)
|
|
508
|
+
) {
|
|
404
509
|
return -1
|
|
405
510
|
}
|
|
406
511
|
sp.pn[s] = pn
|
|
407
512
|
sp.timeSent[s] = now
|
|
513
|
+
sp.fineSent[s] = this.fineNow(now)
|
|
408
514
|
sp.size[s] = size
|
|
515
|
+
sp.setMarked(s, false)
|
|
409
516
|
sp.setState(s, QUIC_RECOVERY_SENT)
|
|
410
517
|
sp.count = sp.count + 1
|
|
411
518
|
sp.inFlight = sp.inFlight + 1
|
|
@@ -415,6 +522,45 @@ export class QuicRecovery {
|
|
|
415
522
|
return s
|
|
416
523
|
}
|
|
417
524
|
|
|
525
|
+
/**
|
|
526
|
+
* The ECN codepoint to mark the next packet with (RFC 9000 A.4): ECT(0)
|
|
527
|
+
* for the first `QUIC_RECOVERY_ECN_TESTING_PACKETS` and, once the path is
|
|
528
|
+
* capable, for every one; 0, Not-ECT, while the testing packets wait to be
|
|
529
|
+
* validated and once validation failed.
|
|
530
|
+
*/
|
|
531
|
+
ecnCodepoint(): i32 {
|
|
532
|
+
const testing: boolean =
|
|
533
|
+
this.ecnState === QUIC_RECOVERY_ECN_TESTING && this.ecnTestSent < QUIC_RECOVERY_ECN_TESTING_PACKETS
|
|
534
|
+
return testing || this.ecnState === QUIC_RECOVERY_ECN_CAPABLE ? QUIC_RECOVERY_ECT0 : 0
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* A packet of space `space` went out marked ECT(0): `slot` is the one
|
|
539
|
+
* `onPacketSent` answered for it, or -1 for a packet that elicits no
|
|
540
|
+
* acknowledgement and so was not recorded. Every packet of a datagram
|
|
541
|
+
* marked counts, whatever `ecnCodepoint()` answers by the last of them.
|
|
542
|
+
*/
|
|
543
|
+
onEctSent(space: i32, slot: i32): void {
|
|
544
|
+
const sp: QuicSentPackets | null = this.space(space)
|
|
545
|
+
if (sp === null) {
|
|
546
|
+
return
|
|
547
|
+
}
|
|
548
|
+
sp.ectSent = sp.ectSent + 1
|
|
549
|
+
const recorded: boolean = sp.inFlightAt(slot) && !sp.markedAt(slot)
|
|
550
|
+
if (recorded) {
|
|
551
|
+
sp.setMarked(slot, true)
|
|
552
|
+
}
|
|
553
|
+
if (this.ecnState === QUIC_RECOVERY_ECN_TESTING || this.ecnState === QUIC_RECOVERY_ECN_UNKNOWN) {
|
|
554
|
+
this.ecnTestSent = this.ecnTestSent + 1
|
|
555
|
+
if (recorded) {
|
|
556
|
+
this.ecnTestRecorded = this.ecnTestRecorded + 1
|
|
557
|
+
}
|
|
558
|
+
if (this.ecnTestSent >= QUIC_RECOVERY_ECN_TESTING_PACKETS) {
|
|
559
|
+
this.ecnState = QUIC_RECOVERY_ECN_UNKNOWN
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
418
564
|
/**
|
|
419
565
|
* An ACK frame for space `space` (A.7): `ranges` holds `count`
|
|
420
566
|
* `[smallest, largest]` pairs, highest first, as `QuicFrame.ackRanges`
|
|
@@ -429,24 +575,49 @@ export class QuicRecovery {
|
|
|
429
575
|
* elicit no acknowledgement, which are not recorded here.
|
|
430
576
|
*/
|
|
431
577
|
onAck(space: i32, ranges: i64[], count: i32, ackDelay: i64, now: i64): i32 {
|
|
578
|
+
const none: i64 = -1
|
|
579
|
+
return this.onAckEcn(space, ranges, count, ackDelay, now, none, none, none)
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* `onAck` for an ACK frame that carries ECN counts, `ect0`, `ect1` and
|
|
584
|
+
* `ce` as ACK_ECN gives them (RFC 9000 §19.3.2), or -1 each for one that
|
|
585
|
+
* carries none. The counts are validated (RFC 9000 §13.4.2.1) and a rise
|
|
586
|
+
* in ECN-CE is a congestion event (§7.1, B.7) before loss is detected, as
|
|
587
|
+
* A.7 orders it.
|
|
588
|
+
*/
|
|
589
|
+
onAckEcn(
|
|
590
|
+
space: i32,
|
|
591
|
+
ranges: i64[],
|
|
592
|
+
count: i32,
|
|
593
|
+
ackDelay: i64,
|
|
594
|
+
now: i64,
|
|
595
|
+
ect0: i64,
|
|
596
|
+
ect1: i64,
|
|
597
|
+
ce: i64
|
|
598
|
+
): i32 {
|
|
432
599
|
const sp: QuicSentPackets | null = this.space(space)
|
|
433
600
|
if (sp === null || count <= 0 || toI32(ranges.length) < 2) {
|
|
434
601
|
return -1
|
|
435
602
|
}
|
|
436
603
|
const largest: i64 = ranges[1]
|
|
604
|
+
const raised: boolean = largest > sp.largestAcked
|
|
437
605
|
sp.compact()
|
|
438
606
|
sp.clearResults()
|
|
439
|
-
if (
|
|
607
|
+
if (raised) {
|
|
440
608
|
sp.largestAcked = largest
|
|
441
609
|
}
|
|
442
610
|
// The bytes in flight before this ACK decide whether the window was in
|
|
443
611
|
// use, and so whether it may grow (§7.8).
|
|
444
612
|
const inUse: boolean = this.bytesInFlight() * 2 >= this.congestionWindow
|
|
445
613
|
let sampleAt: i32 = -1
|
|
614
|
+
// The send time of the first recorded packet past the largest acknowledged, or `now`: no earlier than the largest's own.
|
|
615
|
+
let later: i64 = now
|
|
446
616
|
for (let k: i32 = 0; k < sp.count; k += 1) {
|
|
447
617
|
const s: i32 = sp.slot(k)
|
|
448
618
|
// The ring is in packet-number order, so nothing past the largest acknowledged is.
|
|
449
619
|
if (sp.pnAt(s) > largest) {
|
|
620
|
+
later = sp.timeAt(s)
|
|
450
621
|
break
|
|
451
622
|
}
|
|
452
623
|
if (sp.stateAt(s) === QUIC_RECOVERY_SENT && quicRecoveryInRanges(ranges, count, sp.pnAt(s))) {
|
|
@@ -461,13 +632,25 @@ export class QuicRecovery {
|
|
|
461
632
|
}
|
|
462
633
|
}
|
|
463
634
|
if (sp.ackedCount === 0) {
|
|
635
|
+
// An ACK that raises the largest to a packet never recorded (one that
|
|
636
|
+
// elicits no ACK) still carries the newest counts. Skipping them here
|
|
637
|
+
// would hide a CE rise for good, since the next ACK raises nothing.
|
|
638
|
+
if (raised && this.ecnState !== QUIC_RECOVERY_ECN_FAILED) {
|
|
639
|
+
this.processEcn(sp, raised, ect0, ect1, ce, later, now)
|
|
640
|
+
}
|
|
464
641
|
return 0
|
|
465
642
|
}
|
|
466
643
|
if (sampleAt >= 0) {
|
|
467
644
|
this.latestRtt = quicRecoveryMax(now - sp.timeAt(sampleAt), 0)
|
|
468
645
|
// §5.3: the Initial space's ACKs are not delayed by the peer, so their delay is ignored.
|
|
469
646
|
const none: i64 = 0
|
|
470
|
-
|
|
647
|
+
const delay: i64 = space === QUIC_RECOVERY_INITIAL ? none : quicRecoveryMax(ackDelay, none)
|
|
648
|
+
const first: boolean = this.firstSampleTime < 0
|
|
649
|
+
this.updateRtt(delay, now)
|
|
650
|
+
this.updatePacerRtt(this.fineSample(now, sp.fineAt(sampleAt)), delay, first)
|
|
651
|
+
}
|
|
652
|
+
if (this.ecnState !== QUIC_RECOVERY_ECN_FAILED) {
|
|
653
|
+
this.processEcn(sp, raised, ect0, ect1, ce, later, now)
|
|
471
654
|
}
|
|
472
655
|
this.detectLoss(sp, now)
|
|
473
656
|
this.packetsLost(sp, now)
|
|
@@ -478,6 +661,72 @@ export class QuicRecovery {
|
|
|
478
661
|
return sp.ackedCount
|
|
479
662
|
}
|
|
480
663
|
|
|
664
|
+
/**
|
|
665
|
+
* ECN validation (RFC 9000 §13.4.2.1) and ProcessECN (B.7) for the ACK
|
|
666
|
+
* `onAckEcn` just settled in `sp`, `raised` saying whether it raised the
|
|
667
|
+
* largest acknowledged. A failure stops ECN for the connection and its
|
|
668
|
+
* counts are not used; a pass makes the path capable once a marked packet
|
|
669
|
+
* is among those acknowledged, and a rise in ECN-CE halves the window from
|
|
670
|
+
* the send time of the largest packet newly acknowledged. With none newly
|
|
671
|
+
* acknowledged it is timed from `later`, a send time no earlier than the
|
|
672
|
+
* largest acknowledged packet's, which recovery did not record.
|
|
673
|
+
*/
|
|
674
|
+
processEcn(
|
|
675
|
+
sp: QuicSentPackets,
|
|
676
|
+
raised: boolean,
|
|
677
|
+
ect0: i64,
|
|
678
|
+
ect1: i64,
|
|
679
|
+
ce: i64,
|
|
680
|
+
later: i64,
|
|
681
|
+
now: i64
|
|
682
|
+
): void {
|
|
683
|
+
let marked: i64 = 0
|
|
684
|
+
for (let k: i32 = 0; k < sp.ackedCount; k += 1) {
|
|
685
|
+
if (sp.markedAt(sp.ackedSlot(k))) {
|
|
686
|
+
marked = marked + 1
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
if (ect0 < 0 || ect1 < 0 || ce < 0) {
|
|
690
|
+
// An ACK without counts that newly acknowledges a marked packet: the path, or the peer, dropped them.
|
|
691
|
+
if (marked > 0) {
|
|
692
|
+
this.ecnState = QUIC_RECOVERY_ECN_FAILED
|
|
693
|
+
}
|
|
694
|
+
return
|
|
695
|
+
}
|
|
696
|
+
if (!raised) {
|
|
697
|
+
return
|
|
698
|
+
}
|
|
699
|
+
// A count only grows, and none of them, nor their sum, can pass the
|
|
700
|
+
// packets marked. Each is a varint, so the sum is taken by subtraction.
|
|
701
|
+
const sent: i64 = sp.ectSent
|
|
702
|
+
if (
|
|
703
|
+
ect0 < sp.ect0 ||
|
|
704
|
+
ect1 < sp.ect1 ||
|
|
705
|
+
ce < sp.ce ||
|
|
706
|
+
ect0 > sent ||
|
|
707
|
+
ect1 > sent - ect0 ||
|
|
708
|
+
ce > sent - ect0 - ect1 ||
|
|
709
|
+
ect0 - sp.ect0 + (ce - sp.ce) < marked
|
|
710
|
+
) {
|
|
711
|
+
this.ecnState = QUIC_RECOVERY_ECN_FAILED
|
|
712
|
+
return
|
|
713
|
+
}
|
|
714
|
+
if (marked > 0) {
|
|
715
|
+
this.ecnState = QUIC_RECOVERY_ECN_CAPABLE
|
|
716
|
+
}
|
|
717
|
+
const rose: boolean = ce > sp.ce
|
|
718
|
+
sp.ect0 = ect0
|
|
719
|
+
sp.ect1 = ect1
|
|
720
|
+
sp.ce = ce
|
|
721
|
+
if (rose) {
|
|
722
|
+
const events: i32 = this.congestionEvents
|
|
723
|
+
this.congestionEvent(sp.ackedCount > 0 ? sp.timeAt(sp.ackedSlot(sp.ackedCount - 1)) : later, now)
|
|
724
|
+
if (this.congestionEvents > events) {
|
|
725
|
+
this.ecnCongestionEvents = this.ecnCongestionEvents + 1
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
481
730
|
/** UpdateRtt (§5.3, A.7) with a sample in `latestRtt`, `ackDelay` the peer's delay in milliseconds. */
|
|
482
731
|
updateRtt(ackDelay: i64, now: i64): void {
|
|
483
732
|
const latest: i64 = this.latestRtt
|
|
@@ -491,10 +740,7 @@ export class QuicRecovery {
|
|
|
491
740
|
if (latest < this.minRtt) {
|
|
492
741
|
this.minRtt = latest
|
|
493
742
|
}
|
|
494
|
-
|
|
495
|
-
if (this.handshakeConfirmed && delay > this.maxAckDelay) {
|
|
496
|
-
delay = this.maxAckDelay
|
|
497
|
-
}
|
|
743
|
+
const delay: i64 = this.boundAckDelay(ackDelay)
|
|
498
744
|
// The delay is taken off only where that leaves the sample at or above min_rtt.
|
|
499
745
|
let adjusted: i64 = latest
|
|
500
746
|
if (latest >= this.minRtt + delay) {
|
|
@@ -505,6 +751,55 @@ export class QuicRecovery {
|
|
|
505
751
|
this.smoothedRtt = (7 * this.smoothedRtt + adjusted) / 8
|
|
506
752
|
}
|
|
507
753
|
|
|
754
|
+
/** The peer's ACK delay in milliseconds as §5.3 counts it: at most max_ack_delay once the handshake is confirmed. */
|
|
755
|
+
boundAckDelay(ackDelay: i64): i64 {
|
|
756
|
+
return this.handshakeConfirmed && ackDelay > this.maxAckDelay ? this.maxAckDelay : ackDelay
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* `now` on the pacer's clock: `micros` when it falls inside the
|
|
761
|
+
* millisecond `now` names, and `now × 1000` otherwise.
|
|
762
|
+
*/
|
|
763
|
+
fineNow(now: i64): i64 {
|
|
764
|
+
const coarse: i64 = now * QUIC_RECOVERY_MICROS
|
|
765
|
+
return this.micros >= coarse && this.micros < coarse + QUIC_RECOVERY_MICROS ? this.micros : coarse
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* The pacer's RTT sample, in microseconds, for a packet sent at `sent` on
|
|
770
|
+
* its clock: at least a microsecond on a fine clock, and at least
|
|
771
|
+
* `QUIC_RECOVERY_GRANULARITY` on a millisecond one, where a sample of 0
|
|
772
|
+
* says only that the round trip was under a millisecond. A packet sent
|
|
773
|
+
* while `micros` was stale is stamped `now × 1000`, so a fine sample for it
|
|
774
|
+
* runs long by up to a millisecond: only a carrier that calls `clock` on
|
|
775
|
+
* some sends and not others sees that.
|
|
776
|
+
*/
|
|
777
|
+
fineSample(now: i64, sent: i64): i64 {
|
|
778
|
+
const at: i64 = this.fineNow(now)
|
|
779
|
+
const floor: i64 = at === this.micros ? 1 : QUIC_RECOVERY_GRANULARITY * QUIC_RECOVERY_MICROS
|
|
780
|
+
return quicRecoveryMax(at - sent, floor)
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* UpdateRtt (§5.3) for the pacer's RTT, in microseconds: `sample` the
|
|
785
|
+
* sample, `ackDelay` the peer's delay in milliseconds, `first` whether it
|
|
786
|
+
* is the connection's first. min_rtt is the millisecond one, at most the
|
|
787
|
+
* true minimum, so a delay is taken off a little more readily than
|
|
788
|
+
* `updateRtt` takes it.
|
|
789
|
+
*/
|
|
790
|
+
updatePacerRtt(sample: i64, ackDelay: i64, first: boolean): void {
|
|
791
|
+
if (first) {
|
|
792
|
+
this.pacerRtt = sample
|
|
793
|
+
return
|
|
794
|
+
}
|
|
795
|
+
const delay: i64 = this.boundAckDelay(ackDelay) * QUIC_RECOVERY_MICROS
|
|
796
|
+
let adjusted: i64 = sample
|
|
797
|
+
if (sample >= this.minRtt * QUIC_RECOVERY_MICROS + delay) {
|
|
798
|
+
adjusted = sample - delay
|
|
799
|
+
}
|
|
800
|
+
this.pacerRtt = (7 * this.pacerRtt + adjusted) / 8
|
|
801
|
+
}
|
|
802
|
+
|
|
508
803
|
/** The loss delay (§6.1.2): 9/8 of the larger of the latest and the smoothed RTT, at least the granularity. */
|
|
509
804
|
lossDelay(): i64 {
|
|
510
805
|
const delay: i64 = (9 * quicRecoveryMax(this.latestRtt, this.smoothedRtt)) / 8
|
|
@@ -553,6 +848,17 @@ export class QuicRecovery {
|
|
|
553
848
|
let newest: i64 = -1
|
|
554
849
|
for (let k: i32 = 0; k < sp.lostCount; k += 1) {
|
|
555
850
|
newest = quicRecoveryMax(newest, sp.timeAt(sp.lostSlot(k)))
|
|
851
|
+
if (sp.markedAt(sp.lostSlot(k)) && this.ecnState !== QUIC_RECOVERY_ECN_CAPABLE) {
|
|
852
|
+
this.ecnTestLost = this.ecnTestLost + 1
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
// RFC 9000 A.4: every packet marked while testing that could be lost was.
|
|
856
|
+
if (
|
|
857
|
+
this.ecnState === QUIC_RECOVERY_ECN_UNKNOWN &&
|
|
858
|
+
this.ecnTestRecorded > 0 &&
|
|
859
|
+
this.ecnTestLost >= this.ecnTestRecorded
|
|
860
|
+
) {
|
|
861
|
+
this.ecnState = QUIC_RECOVERY_ECN_FAILED
|
|
556
862
|
}
|
|
557
863
|
this.congestionEvent(newest, now)
|
|
558
864
|
if (this.persistentCongestion(sp)) {
|
|
@@ -800,34 +1106,43 @@ export class QuicRecovery {
|
|
|
800
1106
|
this.ptoCount = 0
|
|
801
1107
|
}
|
|
802
1108
|
|
|
1109
|
+
/** The congestion window the pacer earns by: at most `QUIC_RECOVERY_PACER_WINDOW`. */
|
|
1110
|
+
pacerWindow(): i64 {
|
|
1111
|
+
return this.congestionWindow < QUIC_RECOVERY_PACER_WINDOW
|
|
1112
|
+
? this.congestionWindow
|
|
1113
|
+
: QUIC_RECOVERY_PACER_WINDOW
|
|
1114
|
+
}
|
|
1115
|
+
|
|
803
1116
|
/**
|
|
804
|
-
* Tops the pacer up to `now` (§7.7): it earns 5/4 of the
|
|
805
|
-
*
|
|
806
|
-
* burst it allows.
|
|
1117
|
+
* Tops the pacer up to `now` (§7.7), on its clock: it earns 5/4 of the
|
|
1118
|
+
* window per `pacerRtt`, and holds at most the initial window, which is the
|
|
1119
|
+
* largest burst it allows.
|
|
807
1120
|
*/
|
|
808
1121
|
pacerRefill(now: i64): void {
|
|
809
|
-
|
|
810
|
-
|
|
1122
|
+
const at: i64 = this.fineNow(now)
|
|
1123
|
+
if (this.pacerTime < 0 || at < this.pacerTime) {
|
|
1124
|
+
this.pacerTime = at
|
|
811
1125
|
return
|
|
812
1126
|
}
|
|
813
1127
|
// A minute is far past what fills the budget, and keeps the product in range.
|
|
814
|
-
let elapsed: i64 =
|
|
815
|
-
if (elapsed >
|
|
816
|
-
elapsed =
|
|
1128
|
+
let elapsed: i64 = at - this.pacerTime
|
|
1129
|
+
if (elapsed > 60000000) {
|
|
1130
|
+
elapsed = 60000000
|
|
817
1131
|
}
|
|
818
|
-
const rtt: i64 = quicRecoveryMax(this.
|
|
819
|
-
const earned: i64 = (elapsed * this.
|
|
1132
|
+
const rtt: i64 = quicRecoveryMax(this.pacerRtt, 1)
|
|
1133
|
+
const earned: i64 = (elapsed * this.pacerWindow() * 5) / (rtt * 4)
|
|
820
1134
|
this.pacerBudget = this.pacerBudget + earned
|
|
821
1135
|
if (this.pacerBudget > QUIC_RECOVERY_INITIAL_WINDOW) {
|
|
822
1136
|
this.pacerBudget = QUIC_RECOVERY_INITIAL_WINDOW
|
|
823
1137
|
}
|
|
824
|
-
this.pacerTime =
|
|
1138
|
+
this.pacerTime = at
|
|
825
1139
|
}
|
|
826
1140
|
|
|
827
1141
|
/**
|
|
828
1142
|
* How many milliseconds after `now` the pacer lets a datagram of `size`
|
|
829
|
-
* bytes go: 0 when it may go now, else
|
|
830
|
-
*
|
|
1143
|
+
* bytes go: 0 when it may go now, else its wait in microseconds rounded
|
|
1144
|
+
* up to whole milliseconds, so at least 1. The rate is 5/4 of the window per `pacerRtt`
|
|
1145
|
+
* (§7.7's N of 1.25).
|
|
831
1146
|
*/
|
|
832
1147
|
pacerDelay(now: i64, size: i32): i64 {
|
|
833
1148
|
this.pacerRefill(now)
|
|
@@ -835,10 +1150,26 @@ export class QuicRecovery {
|
|
|
835
1150
|
if (need <= 0) {
|
|
836
1151
|
return 0
|
|
837
1152
|
}
|
|
838
|
-
const rtt: i64 = quicRecoveryMax(this.
|
|
839
|
-
const rate: i64 = quicRecoveryMax(this.
|
|
1153
|
+
const rtt: i64 = quicRecoveryMax(this.pacerRtt, 1)
|
|
1154
|
+
const rate: i64 = quicRecoveryMax(this.pacerWindow() * 5, 1)
|
|
840
1155
|
const wait: i64 = (need * rtt * 4 + rate - 1) / rate
|
|
841
|
-
return quicRecoveryMax(wait, 1)
|
|
1156
|
+
return quicRecoveryMax((wait + QUIC_RECOVERY_MICROS - 1) / QUIC_RECOVERY_MICROS, 1)
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* The credit a paced flight waits for before it starts, in bytes: what the
|
|
1161
|
+
* pacer earns in a millisecond, at least `least` and at most `most`. A
|
|
1162
|
+
* pacer that earns by the microsecond would otherwise let a fast
|
|
1163
|
+
* connection out a datagram at a time, one send each; one that earns
|
|
1164
|
+
* slowly still lets a datagram go as soon as it has the credit for it.
|
|
1165
|
+
*/
|
|
1166
|
+
pacerQuantum(least: i32, most: i32): i32 {
|
|
1167
|
+
const rtt: i64 = quicRecoveryMax(this.pacerRtt, 1)
|
|
1168
|
+
const milli: i64 = (this.pacerWindow() * 5 * QUIC_RECOVERY_MICROS) / (rtt * 4)
|
|
1169
|
+
if (milli < toI64(least)) {
|
|
1170
|
+
return least
|
|
1171
|
+
}
|
|
1172
|
+
return milli > toI64(most) ? most : toI32(milli)
|
|
842
1173
|
}
|
|
843
1174
|
|
|
844
1175
|
/** Charges the pacer for a datagram of `size` bytes sent at `now`. */
|