@amritk/nish-x86_64-linux 0.17.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/INSTALL.md +43 -25
  2. package/bin/nish +0 -0
  3. package/package.json +2 -2
  4. package/runtime/nish.d.ts +55 -5
  5. package/runtime/nish.h +231 -20
  6. package/runtime/nish.mjs +16 -0
  7. package/runtime/runtime-aes.c +490 -0
  8. package/runtime/runtime-cross.c +366 -0
  9. package/runtime/runtime-host.c +7 -6
  10. package/runtime/runtime-net.c +93 -34
  11. package/runtime/runtime-os.c +213 -15
  12. package/runtime/runtime-parallel.c +590 -107
  13. package/runtime/runtime-simd.c +203 -0
  14. package/runtime/runtime-wasm.c +16 -3
  15. package/runtime/runtime.c +89 -23
  16. package/runtime/shim.mjs +71 -1
  17. package/scripts/build.sh +225 -20
  18. package/std/README.md +86 -55
  19. package/std/bytes.ts +163 -0
  20. package/std/crypto/LICENSE-libsecp256k1 +19 -0
  21. package/std/crypto/aes.ts +238 -76
  22. package/std/crypto/chacha20poly1305.ts +43 -9
  23. package/std/crypto/ed25519.ts +840 -0
  24. package/std/crypto/hkdf.ts +304 -12
  25. package/std/crypto/hmac.ts +245 -53
  26. package/std/crypto/p256.ts +1376 -42
  27. package/std/crypto/x25519.ts +55 -37
  28. package/std/crypto/x509-random.ts +71 -0
  29. package/std/crypto/x509.ts +9 -9
  30. package/std/json-tokenizer.ts +1296 -0
  31. package/std/json-typed.ts +1014 -0
  32. package/std/json-value.ts +1163 -0
  33. package/std/json.ts +1412 -70
  34. package/std/net/buffer-pool.ts +151 -0
  35. package/std/net/hpack.ts +416 -112
  36. package/std/net/http-fields.ts +93 -0
  37. package/std/net/http1-server.ts +417 -55
  38. package/std/net/http1.ts +31 -6
  39. package/std/net/http2-tls.ts +112 -24
  40. package/std/net/http2.ts +333 -75
  41. package/std/net/http3-server.ts +231 -45
  42. package/std/net/http3.ts +187 -28
  43. package/std/net/prefork.ts +412 -0
  44. package/std/net/quic-conn-params.ts +86 -16
  45. package/std/net/quic-listener.ts +576 -219
  46. package/std/net/quic-packet.ts +443 -58
  47. package/std/net/quic-recovery.ts +361 -30
  48. package/std/net/quic-stream.ts +434 -28
  49. package/std/net/quic.ts +922 -206
  50. package/std/net/router.ts +676 -0
  51. package/std/net/tls/codec.ts +426 -27
  52. package/std/net/tls/record-server.ts +478 -132
  53. package/std/net/tls/record.ts +133 -42
  54. package/std/net/tls/schedule.ts +464 -55
  55. package/std/net/tls/ticket.ts +610 -0
  56. package/std/net/tls-tcp.ts +204 -57
  57. package/std/net/tls.ts +1132 -305
  58. package/std/net/url.ts +571 -0
  59. package/std/random.ts +94 -0
  60. package/std/text.ts +77 -0
  61. package/std/threads.ts +438 -14
@@ -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, and nothing reads ECN
37
- * (§7.1, B.7). A congestion window that the sender does not use is not
38
- * grown (§7.8): an acknowledgement grows it only if, when it arrived, the
39
- * bytes in flight were at least half the window.
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 (s < 0 || s >= toI32(sp.pn.length) || s >= toI32(sp.timeSent.length) || s >= toI32(sp.size.length)) {
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 (largest > sp.largestAcked) {
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
- this.updateRtt(space === QUIC_RECOVERY_INITIAL ? none : quicRecoveryMax(ackDelay, none), now)
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
- let delay: i64 = ackDelay
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 window per
805
- * smoothed RTT, and holds at most the initial window, which is the largest
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
- if (this.pacerTime < 0 || now < this.pacerTime) {
810
- this.pacerTime = now
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 = now - this.pacerTime
815
- if (elapsed > 60000) {
816
- elapsed = 60000
1128
+ let elapsed: i64 = at - this.pacerTime
1129
+ if (elapsed > 60000000) {
1130
+ elapsed = 60000000
817
1131
  }
818
- const rtt: i64 = quicRecoveryMax(this.smoothedRtt, QUIC_RECOVERY_GRANULARITY)
819
- const earned: i64 = (elapsed * this.congestionWindow * 5) / (rtt * 4)
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 = now
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 at least 1. The rate is 5/4 of the
830
- * window per smoothed RTT (§7.7's N of 1.25).
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.smoothedRtt, QUIC_RECOVERY_GRANULARITY)
839
- const rate: i64 = quicRecoveryMax(this.congestionWindow * 5, 1)
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`. */