1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
//! an implementation of draft-ietf-quic-recovery-13
use packet::Frame;
use std::cmp::{max, min};
use std::collections::HashMap;
use config;
// 3.5.1. Loss Detection Settings
/// Maximum number of RTOs before connection is considered unrecoverable
const MAX_RTOS: u16 = 4;
/// Maximum number of tail loss probes before an RTO fires
const MAX_TLPS: u16 = 2;
/// Maximum reordering in packet number space before FACK style loss detection considers a packet lost.
const REORDERING_THRESHOLD: u64 = 3;
/// Maximum reordering in time space before time based loss detection considers a packet lost. In fraction of an RTT.
const TIME_REORDERING_FRACTION: f64 = 0.125;
/// Whether time based loss detection is in use. If false, uses FACK style loss detection.
const USING_TIME_LOSS_DETECTION: bool = false;
/// Minimum time in the future a tail loss probe alarm may be set for.
const MIN_TLP_TIMEOUT: u64 = 10;
/// Minimum time in the future an RTO alarm may be set for.
const MIN_RTO_TIMEOUT: u64 = 200;
// 4.8.1. Congestion Control Settings
/// The max packet size is used for calculating initial and minimum congestion windows.
const INITIAL_MSS: u64 = 1460;
/// Limit on the initial amount of outstanding data in bytes.
const INITIAL_WINDOW: u64 = 10 * INITIAL_MSS;
/// Minimum congestion window in bytes.
const MINIMUM_WINDOW: u64 = 2 * INITIAL_MSS;
/// Reduction in congestion window when a new loss event is detected.
const LOSS_REDUCTION_FACTOR: f64 = 0.5;
#[derive(Default)]
pub struct Pkt {
seq: u64,
ackonly: bool,
time_sent: u64,
bytes: usize,
frames: Vec<Frame>,
}
#[derive(PartialEq, Debug)]
pub enum LossDetection {
None,
Lost(Vec<Frame>),
TailLossProbe(Vec<Frame>),
RetransmissionTimeout(Vec<Frame>),
Unrecoverable,
}
pub struct QuicRecovery {
config: config::Protocol,
// Loss Detection
/// a future in time space when on_loss_detection_alarm must be called
loss_detection_alarm: Option<u64>,
/// The number of times a tail loss probe has been sent without receiving an ack.
tlp_count: u16,
/// The number of times an rto has been sent without receiving an ack.
rto_count: u16,
/// The last packet number sent prior to the first retransmission timeout.
largest_sent_before_rto: u64,
/// The time the most recent retransmittable packet was sent.
time_of_last_sent_retransmittable_packet: u64,
/// The packet number of the most recently sent packet.
pub(crate) largest_sent_packet: u64,
/// Accumulative amount of packets ever declared lost
pub(crate) total_lost_packet_count: u64,
/// The packet number of the most recently sent packet.
/// NOT PART OF SPEC
largest_sent_retransmittable_packet: u64,
/// The largest packet number acknowledged in an ACK frame.
largest_acked_packet: u64,
/// The time at which the next packet will be considered lost
/// based on early transmit or exceeding the reordering window in time.
loss_time: u64,
/// An association of packet numbers to information about
/// them, including a number field indicating the packet number, a
/// time field indicating the time a packet was sent, a boolean
/// indicating whether the packet is ack only, and a bytes field
/// indicating the packet's size. sent_packets is ordered by packet
/// number, and packets remain in sent_packets until acknowledged or
/// lost. A sent_packets data structure is maintained per packet
/// number space, and ACK processing only applies to a single space.
sent_packets: HashMap<u64, Pkt>,
/// The most recent RTT measurement made when receiving an ack for a previously unacked packet.
latest_rtt: u64,
/// The smoothed RTT of the connection, computed as described in [RFC6298]
pub(crate) smoothed_rtt: u64,
/// The RTT variance, computed as described in [RFC6298]
pub(crate) rttvar: f64,
/// The minimum RTT seen in the connection, ignoring ack delay.
min_rtt: u64,
/// The maximum ack delay in an incoming ACK frame for
/// this connection. Excludes ack delays for ack only packets and
/// those that create an RTT sample less than min_rtt.
pub(crate) max_ack_delay: u64,
// Congestion Control
/// The sum of the size in bytes of all sent packets
/// that contain at least one retransmittable frame, and have not been
/// acked or declared lost. The size does not include IP or UDP
/// overhead. Packets only containing ACK frames do not count towards
/// bytes_in_flight to ensure congestion control does not impede
/// congestion feedback.
bytes_in_flight: usize,
/// Maximum number of bytes-in-flight that may be sent.
congestion_window: u64,
/// The largest packet number sent when QUIC detects a loss.
/// When a larger packet is acknowledged, QUIC exits recovery.
end_of_recovery: u64,
/// Slow start threshold in bytes. When the congestion window
/// is below ssthresh, the mode is slow start and the window grows by
/// the number of bytes acknowledged.
ssthresh: u64,
}
impl QuicRecovery {
pub fn new(config: config::Protocol) -> Self {
Self {
config,
loss_detection_alarm: None,
tlp_count: 0,
rto_count: 0,
largest_sent_before_rto: 0,
time_of_last_sent_retransmittable_packet: 0,
largest_sent_packet: 0,
total_lost_packet_count: 0,
largest_sent_retransmittable_packet: 0,
largest_acked_packet: 0,
loss_time: 0,
sent_packets: HashMap::default(),
latest_rtt: 0,
smoothed_rtt: 0,
rttvar: 0.0,
min_rtt: <u64>::max_value(),
max_ack_delay: 0,
bytes_in_flight: 0,
congestion_window: INITIAL_WINDOW,
end_of_recovery: 0,
ssthresh: <u64>::max_value(),
}
}
/// current free space in sending window
pub fn window(&self) -> (usize, u64) {
if self.largest_acked_packet + 20 < self.largest_sent_retransmittable_packet {
// TODO: not part of the spec.
// But if we dont do this, loss_detection_alarm keeps getting reset on send even we get no ack
// https://github.com/quicwg/base-drafts/issues/1718
// this should be removed when we have a test suite that captures this case
// because the new idle timer should actually fix this properly
trace!("no send window because we're loosing too many packets");
return (0, self.congestion_window);
}
(
if self.bytes_in_flight > self.congestion_window as usize {
0
} else {
self.congestion_window as usize - self.bytes_in_flight
},
self.congestion_window
)
}
/// current free space in sending window
pub fn bytes_in_flight(&self) -> usize {
self.bytes_in_flight
}
pub fn loss_detection_alarm(&self) -> Option<u64> {
self.loss_detection_alarm
}
/// 3.5.4. Loss Detection
pub fn on_packet_sent(&mut self, seq: u64, frames: Vec<Frame>, version: u8, now: u64) {
let (bytes, ackonly) = frames.iter().fold((0, true), |(bytes, ackonly), frame| {
let ackonly = ackonly && frame.is_ack();
(bytes + if frame.is_ack() { 0 } else { frame.len(version) }, ackonly)
});
let pkt = Pkt {
seq,
ackonly,
time_sent: now,
bytes,
frames,
};
assert!(
self.largest_sent_packet < seq,
"cannot send packet older than last one"
);
self.largest_sent_packet = seq;
if !ackonly {
self.largest_sent_retransmittable_packet = seq;
}
let old = self.sent_packets.insert(seq, pkt);
assert!(old.is_none(), "cannot send packets with packet id already in flight");
if !ackonly {
self.time_of_last_sent_retransmittable_packet = now;
self.on_packet_sent_cc(bytes);
self.set_loss_detection_alarm();
}
}
/// 3.5.5. Loss Detection
///
/// returns packets that are lost
pub fn on_ack_received(&mut self, delay: u64, acked: Vec<u64>, now: u64) -> LossDetection {
if let Some(largest) = acked.get(0) {
//TODO how does this behave when we get an older ack?
self.largest_acked_packet = *largest;
if self.sent_packets.contains_key(largest) {
self.latest_rtt = now - self.sent_packets[largest].time_sent;
if let Some(min) = self.config.min_latency {
if self.latest_rtt < min {
self.latest_rtt = min;
}
}
self.update_rtt(delay.into());
}
}
// Find all newly acked packets.
for acked in acked {
self.on_packet_acked(acked);
}
let loss = self.detect_lost_packets(now);
self.set_loss_detection_alarm();
loss
// TODO
// Process ECN information if present.
// if (ACK frame contains ECN information):
// ProcessECN(ack)
// TODO: this isnt done in their pseudocode, but its mentioned in the text:
// 3.3.3. Retransmission Timeout
// When an acknowledgment is received for a packet sent on an RTO event,
// any unacknowledged packets with lower packet numbers than those
// acknowledged MUST be marked as lost.
}
/// 3.5.5. Loss Detection
fn update_rtt(&mut self, ack_delay: u64) {
// min_rtt ignores ack delay.
self.min_rtt = min(self.min_rtt, self.latest_rtt);
// Adjust for ack delay if it's plausible.
if self.latest_rtt - self.min_rtt > ack_delay {
self.latest_rtt -= ack_delay;
// Only save into max ack delay if it's used
// for rtt calculation and is not ack only.
if !self.sent_packets[&self.largest_acked_packet].ackonly {
self.max_ack_delay = max(self.max_ack_delay, ack_delay);
}
}
// Based on {{RFC6298}}
if self.smoothed_rtt == 0 {
self.smoothed_rtt = self.latest_rtt;
self.rttvar = self.latest_rtt as f64 / 2.0;
} else {
let rttvar_sample = if self.smoothed_rtt > self.latest_rtt {
self.smoothed_rtt - self.latest_rtt
} else {
self.latest_rtt - self.smoothed_rtt
};
self.rttvar = 3.0 / 4.0 * self.rttvar + 1.0 / 4.0 * rttvar_sample as f64;
self.smoothed_rtt = (7.0 / 8.0 * self.smoothed_rtt as f64 + 1.0 / 8.0 * self.latest_rtt as f64) as u64;
}
}
/// 3.5.6. On Packet Acknowledgment
fn on_packet_acked(&mut self, acked_packet: u64) {
match self.sent_packets.remove(&acked_packet) {
Some(v) => {
if !v.ackonly {
self.on_packet_acked_cc(v)
}
}
None => {
// dup'd ack
}
};
// If a packet sent prior to RTO was acked, then the RTO
// was spurious. Otherwise, inform congestion control.
if self.rto_count > 0 && acked_packet > self.largest_sent_before_rto {
self.on_retransmission_timeout_verified();
}
self.tlp_count = 0;
self.rto_count = 0;
}
//
// TODO this loss detection triggers too early on poorly ordered networks.
// See 3.2.1. Fast Retransmit. It's unclear if this is a problem for our use case.
//
// TODO: this is significantly worse on lossy networks than tcp, wtf is up with that?
//
/// 3.5.9. Loss Detection | DetectLostPackets
///
/// 4.8.8 Congestion Control | OnPacketsLost inlined here
fn detect_lost_packets(&mut self, now: u64) -> LossDetection {
self.loss_time = 0;
let mut lost_packets = Vec::new();
let delay_until_lost = if self.config.time_loss_detection.unwrap_or(USING_TIME_LOSS_DETECTION) {
(1.0 + TIME_REORDERING_FRACTION) * max(self.latest_rtt, self.smoothed_rtt) as f64
} else if self.largest_acked_packet == self.largest_sent_packet {
// Early retransmit alarm.
5.0 / 4.0 * max(self.latest_rtt, self.smoothed_rtt) as f64
} else {
0.0
}
.round() as u64;
for unacked in self.sent_packets.values() {
if unacked.seq >= self.largest_acked_packet {
continue;
}
let time_since_sent = now - unacked.time_sent;
let delta = self.largest_acked_packet - unacked.seq;
if (delay_until_lost != 0 && time_since_sent > delay_until_lost)
|| (!self.config.time_loss_detection.unwrap_or(USING_TIME_LOSS_DETECTION) && delta > self.config.reordering_threshold.unwrap_or(REORDERING_THRESHOLD))
{
if !unacked.ackonly {
lost_packets.push(unacked.seq);
}
} else if delay_until_lost != 0 {
self.loss_time = min(self.loss_time, now + delay_until_lost - time_since_sent);
}
}
// Inform the congestion controller of lost packets and
// lets it decide whether to retransmit immediately.
lost_packets.sort_unstable();
let mut largest_lost_packet = 0;
let mut lost_frames = Vec::new();
for seq in lost_packets {
self.total_lost_packet_count += 1;
let mut pkt = self.sent_packets.remove(&seq).unwrap();
// OnPacketsLost
// Remove lost packets from bytes_in_flight.
self.bytes_in_flight -= pkt.bytes;
largest_lost_packet = max(largest_lost_packet, pkt.seq);
lost_frames.append(&mut pkt.frames);
}
if lost_frames.len() > 0 {
self.congestion_event(largest_lost_packet);
LossDetection::Lost(lost_frames)
} else {
LossDetection::None
}
}
/// 3.5.7.4. Loss Detection Alarm
fn set_loss_detection_alarm(&mut self) {
// Don't arm the alarm if there are no packets with
// retransmittable data in flight.
if self.bytes_in_flight == 0 {
self.loss_detection_alarm = None;
return;
}
let alarm_duration = if self.loss_time != 0 {
// Early retransmit timer or time loss detection.
self.loss_time - self.time_of_last_sent_retransmittable_packet
} else {
// RTO or TLP alarm
// Calculate RTO duration
let mut alarm_duration = self.smoothed_rtt + (4.0 * self.rttvar) as u64 + self.max_ack_delay;
alarm_duration = max(alarm_duration, self.config.min_rto_timeout.unwrap_or(MIN_RTO_TIMEOUT));
alarm_duration = alarm_duration * 2_u64.pow(self.rto_count as u32);
if self.tlp_count < self.config.max_tlps.unwrap_or(MAX_TLPS) {
// Tail Loss Probe
let tlp_alarm_duration = 1.5 * (self.smoothed_rtt + self.max_ack_delay) as f64;
let tlp_alarm_duration = max(tlp_alarm_duration.floor() as u64, self.config.min_tlp_timeout.unwrap_or(MIN_TLP_TIMEOUT));
alarm_duration = min(tlp_alarm_duration, alarm_duration);
}
alarm_duration
};
self.loss_detection_alarm = Some(self.time_of_last_sent_retransmittable_packet + alarm_duration);
}
//3.3.3. Retransmission Timeout
//TODO
//
//> Since this packet is sent as a probe into the network prior to establishing
//> any packet loss, prior unacknowledged packets SHOULD NOT be marked as lost.
//
// so instead of taking them out, we empty them, making them placeholders
// that still can be used for RTT measurement
fn retransmit_n_packets(&mut self, mut n: usize) -> Vec<Frame> {
let mut r = Vec::new();
let mut keys: Vec<u64> = self.sent_packets.keys().cloned().collect();
keys.sort_unstable();
for key in keys {
let mut pkt = self.sent_packets.get_mut(&key).unwrap();
if pkt.ackonly {
continue;
}
if pkt.frames.is_empty() {
continue;
}
self.bytes_in_flight -= pkt.bytes;
pkt.bytes = 0;
r.append(&mut pkt.frames);
n -= 1;
if n < 1 {
break;
}
}
r
}
/// 3.5.8. On Alarm Firing
///
/// returns packets that are lost
pub fn on_loss_detection_alarm(&mut self, now: u64) -> LossDetection {
let loss = if self.loss_time != 0 {
// Early retransmit or Time Loss Detection
self.detect_lost_packets(now)
} else if self.tlp_count < self.config.max_tlps.unwrap_or(MAX_TLPS) {
//send one packet
self.tlp_count += 1;
let retransmit = self.retransmit_n_packets(1);
LossDetection::TailLossProbe(retransmit)
} else if self.rto_count > self.config.max_rtos.unwrap_or(MAX_RTOS) {
LossDetection::Unrecoverable
} else {
// RTO.
if self.rto_count == 0 {
self.largest_sent_before_rto = self.largest_sent_packet
}
//send two packets
self.rto_count += 1;
let retransmit = self.retransmit_n_packets(2);
LossDetection::RetransmissionTimeout(retransmit)
};
self.set_loss_detection_alarm();
loss
}
/// 4.8.4. Congestion Control
fn on_packet_sent_cc(&mut self, bytes: usize) {
self.bytes_in_flight += bytes;
}
/// 4.8.5. Congestion Control
fn in_recovery(&self, packet_number: u64) -> bool {
packet_number <= self.end_of_recovery
}
/// 4.8.5. Congestion Control
fn on_packet_acked_cc(&mut self, acked_packet: Pkt) {
self.bytes_in_flight -= acked_packet.bytes;
if self.in_recovery(acked_packet.seq) {
// Do not increase congestion window in recovery period.
return;
}
if self.congestion_window < self.ssthresh {
// Slow start.
self.congestion_window += acked_packet.bytes as u64;
} else {
// Congestion avoidance.
self.congestion_window += INITIAL_MSS * acked_packet.bytes as u64 / self.congestion_window;
}
}
/// 4.8.6 On New Congestion Event
fn congestion_event(&mut self, packet_number: u64) {
if !self.in_recovery(packet_number) {
self.end_of_recovery = self.largest_sent_packet;
self.congestion_window = (self.congestion_window as f64 * LOSS_REDUCTION_FACTOR) as u64;
self.congestion_window = max(self.congestion_window, MINIMUM_WINDOW);
self.ssthresh = self.congestion_window
}
}
// 4.8.9. On Retransmission Timeout Verified
// QUIC decreases the congestion window to the minimum value once the
// retransmission timeout has been verified.
fn on_retransmission_timeout_verified(&mut self) {
self.congestion_window = MINIMUM_WINDOW;
}
}
#[test]
fn lossless() {
let mut qr = QuicRecovery::new();
let mut seq = 0;
let mut clock = 0;
// ------
// sent 10 packets, each takes 1ms to prepare
for _ in 1..11 {
clock += 1;
seq += 1;
let frame = Frame::Stream {
order: seq,
payload: vec![],
stream: 1,
};
qr.on_packet_sent(seq, vec![frame], super::packet::LATEST_VERSION, clock);
}
assert_eq!(clock, 10);
assert_eq!(qr.largest_sent_packet, 10);
// -------
// at 15ms ack everything
clock = 15;
let loss = qr.on_ack_received(10, vec![10, 9, 8, 7, 6, 5, 4, 3, 2, 1], clock);
assert_eq!(loss, LossDetection::None);
assert_eq!(qr.loss_detection_alarm, None, "no alarm should be set");
}
#[test]
fn high_latency() {
let mut qr = QuicRecovery::new();
let mut seq = 0;
let mut clock = 0;
// ------
// sent 10 packets, each takes 1ms to prepare
for _ in 1..11 {
clock += 1;
seq += 1;
let frame = Frame::Stream {
order: seq,
payload: vec![],
stream: 1,
};
qr.on_packet_sent(seq, vec![frame], super::packet::LATEST_VERSION, clock);
}
assert_eq!(clock, 10);
assert_eq!(qr.largest_sent_packet, 10);
assert_eq!(qr.latest_rtt, 0, "RTT cannot be calculated yet");
assert_eq!(qr.smoothed_rtt, 0, "RTT cannot be calculated yet");
assert_eq!(
qr.loss_detection_alarm,
Some(20),
"alarm should be 10 (last packet sent) + MIN_TLP_TIMEOUT);"
);
// ------
// At 20ms TLP should be triggered
clock = 20;
let loss = qr.on_loss_detection_alarm(clock);
let _re = if let LossDetection::TailLossProbe(re) = loss {
re
} else {
panic!("expected TLP")
};
assert_eq!(qr.rto_count, 0);
assert_eq!(qr.tlp_count, 1);
assert_eq!(qr.latest_rtt, 0, "RTT cannot be calculated yet");
assert_eq!(qr.smoothed_rtt, 0, "RTT cannot be calculated yet");
seq += 1;
qr.on_packet_sent(seq, vec![Frame::Ping], super::packet::LATEST_VERSION, clock);
assert_eq!(
qr.loss_detection_alarm,
Some(30),
"alarm should be 20 (last packet sent) + MIN_TLP_TIMEOUT);"
);
// ------
// At 30ms we get another TLP
clock = 30;
let loss = qr.on_loss_detection_alarm(clock);
let _re = if let LossDetection::TailLossProbe(re) = loss {
re
} else {
panic!("expected TLP")
};
assert_eq!(qr.rto_count, 0);
assert_eq!(qr.tlp_count, 2);
assert_eq!(qr.latest_rtt, 0, "RTT cannot be calculated yet");
assert_eq!(qr.smoothed_rtt, 0, "RTT cannot be calculated yet");
assert_eq!(qr.max_ack_delay, 0, "ack delay cannot be calculated yet");
seq += 1;
qr.on_packet_sent(seq, vec![Frame::Ping], super::packet::LATEST_VERSION, clock);
assert_eq!(
qr.loss_detection_alarm,
Some(230),
"alarm should be 230 (2 ^ rtocount * MIN_RTO_TIMEOUT) + 30 (last sent)"
);
// ------
// At 30+210ms we're getting a RTO
clock = 30 + 201;
let loss = qr.on_loss_detection_alarm(clock);
let _re = if let LossDetection::RetransmissionTimeout(re) = loss {
re
} else {
panic!("expected RTO")
};
assert_eq!(qr.rto_count, 1);
assert_eq!(qr.tlp_count, 2);
seq += 1;
qr.on_packet_sent(seq, vec![Frame::Ping], super::packet::LATEST_VERSION, clock);
assert_eq!(
qr.loss_detection_alarm,
Some(631),
"alarm should be 631 (2 ^ rtocount * MIN_RTO_TIMEOUT) + 231 (last sent)"
);
// ---------
// 300ms, finally getting an ACK for a package
clock = 300;
// no loss
let loss = qr.on_ack_received(8, vec![1], clock);
assert_eq!(loss, LossDetection::None);
/* TODO: i broke those tests because RTO now removes the pkts
* we need to resubmit them above
assert_eq!(qr.tlp_count, 0, "TLP count should reset even on ack");
assert_eq!(qr.latest_rtt, 297, "rtt must be 300(now) - 2 (ack delay) - 1 (sent)");
assert_eq!(qr.max_ack_delay, 2);
assert_eq!(qr.smoothed_rtt, qr.latest_rtt);
assert_eq!(qr.rttvar, qr.latest_rtt / 2);
assert_eq!(qr.loss_time, 0);
assert_eq!(
qr.loss_detection_alarm,
Some(231 + (1.5 * (297 + 2) as f64) as u64),
"alarm should be last sent + (1.5 * (rttvar + max_ack_delay))"
);
*/
}
#[test]
fn low_latency_high_loss() {
let mut qr = QuicRecovery::new();
let mut seq = 0;
let mut clock = 0;
// ------
// sent 10 packets, each takes 1ms to prepare
for _ in 1..11 {
clock += 1;
seq += 1;
let frame = Frame::Stream {
order: seq,
payload: vec![0; 1000],
stream: 1,
};
qr.on_packet_sent(seq, vec![frame], super::packet::LATEST_VERSION, clock);
}
assert_eq!(clock, 10);
assert_eq!(qr.largest_sent_packet, 10);
assert_eq!(qr.latest_rtt, 0, "RTT cannot be calculated yet");
assert_eq!(qr.smoothed_rtt, 0, "RTT cannot be calculated yet");
assert_eq!(qr.congestion_window, INITIAL_WINDOW);
assert_eq!(
qr.loss_detection_alarm,
Some(20),
"alarm should be 10 (last packet sent) + MIN_TLP_TIMEOUT);"
);
// --------
// packet 2 ack
clock = 5;
// no loss
let loss = qr.on_ack_received(1, vec![2], clock);
assert_eq!(
qr.congestion_window,
INITIAL_WINDOW + 1015,
"congestion windows should increase by ack'd frame"
);
assert_eq!(loss, LossDetection::None);
assert_eq!(
qr.loss_detection_alarm,
Some(20),
"alarm should be 10 (last packet sent) + MIN_TLP_TIMEOUT);"
);
// --------
// packet 4 ack
clock = 10;
// no loss
let loss = qr.on_ack_received(1, vec![4], clock);
assert_eq!(
qr.congestion_window,
INITIAL_WINDOW + 2030,
"congestion windows should increase by ack'd frame"
);
assert_eq!(loss, LossDetection::None);
assert_eq!(
qr.loss_detection_alarm,
Some(20),
"alarm should be 10 (last packet sent) + MIN_TLP_TIMEOUT);"
);
// --------
// packet 6 ack
clock = 15;
// lost packet 1
let loss = qr.on_ack_received(1, vec![6], clock);
let frames = if let LossDetection::Lost(frames) = loss {
assert_eq!(frames.len(), 1);
frames
} else {
panic!("expected lost frames");
};
assert_eq!(
qr.congestion_window,
(INITIAL_WINDOW + 3045) / 2,
"congestion windows should be halfed on loss"
);
//retransmit packet 1
seq += 1;
qr.on_packet_sent(seq, frames, super::packet::LATEST_VERSION, clock);
// --------
// ack finally arriving
clock = 20;
let loss = qr.on_ack_received(1, vec![5, 4, 3, 2], clock);
assert_eq!(loss, LossDetection::None);
assert_eq!(qr.congestion_window, 8822, "congestion windows should recover");
}
// when using the original quic pseudocode,
// TLP is being reset on every sent packet, even if we're still waiting for acks
// so there's never a timeout if the packets are sent faster than TLP.
#[test]
fn tlp_vs_ping() {
let mut qr = QuicRecovery::new();
let mut seq = 0;
let mut clock = 0;
// prime the RTT calculator to 100ms
clock += 100;
seq += 1;
let frame = Frame::Stream {
order: seq,
payload: vec![0; 1000],
stream: 1,
};
qr.on_packet_sent(seq, vec![frame], super::packet::LATEST_VERSION, clock);
assert_eq!(
qr.loss_detection_alarm,
Some(110),
"alarm should be 110 (last packet sent) + MIN_TLP_TIMEOUT);"
);
clock += 100;
let loss = qr.on_ack_received(1, vec![1], clock);
assert_eq!(loss, LossDetection::None);
assert_eq!(qr.latest_rtt, 100);
// send every 100ms
for _ in 0..1000 {
clock += 100;
seq += 1;
let frame = Frame::Stream {
order: seq,
payload: vec![0; 1000],
stream: 1,
};
qr.on_packet_sent(seq, vec![frame], super::packet::LATEST_VERSION, clock);
//FIXME this is the issue. TLP is now a moving goalpost
assert_eq!(qr.loss_detection_alarm, Some(clock + 100 + 50));
}
}