dcsctp: Allow specifying minimum RTT variance

This is mentioned in
https://datatracker.ietf.org/doc/html/rfc4960#section-6.3.1 and further
described in https://datatracker.ietf.org/doc/html/rfc6298#section-4.

The TCP RFCs mentioned G as the clock granularity, but in SCTP it should
be set much higher, to account for the delayed ack timeout (ATO) of the
peer (as that can be seen as a very high clock granularity). That one is
set to 200ms by default in many clients, so a reasonable default limit
could be set to 220 ms.

If the measured variance is higher, it will be used instead.

Bug: webrtc:12943
Change-Id: Ifc217daa390850520da8b3beb0ef214181ff8c4e
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/232614
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#35068}
diff --git a/net/dcsctp/public/dcsctp_options.h b/net/dcsctp/public/dcsctp_options.h
index 92bb3a3..2ac2aff 100644
--- a/net/dcsctp/public/dcsctp_options.h
+++ b/net/dcsctp/public/dcsctp_options.h
@@ -128,6 +128,21 @@
   // unacknowledged packet. Whatever is smallest of RTO/2 and this will be used.
   DurationMs delayed_ack_max_timeout = DurationMs(200);
 
+  // The minimum limit for the measured RTT variance
+  //
+  // Setting this below the expected delayed ack timeout (+ margin) of the peer
+  // might result in unnecessary retransmissions, as the maximum time it takes
+  // to ACK a DATA chunk is typically RTT + ATO (delayed ack timeout), and when
+  // the SCTP channel is quite idle, and heartbeats dominate the source of RTT
+  // measurement, the RTO would converge with the smoothed RTT (SRTT). The
+  // default ATO is 200ms in usrsctp, and a 20ms (10%) margin would include the
+  // processing time of received packets and the clock granularity when setting
+  // the delayed ack timer on the peer.
+  //
+  // This is described for TCP in
+  // https://datatracker.ietf.org/doc/html/rfc6298#section-4.
+  DurationMs min_rtt_variance = DurationMs(220);
+
   // Do slow start as TCP - double cwnd instead of increasing it by MTU.
   bool slow_start_tcp_style = false;
 
diff --git a/net/dcsctp/tx/retransmission_timeout.cc b/net/dcsctp/tx/retransmission_timeout.cc
index ee7c48b..aa2863f 100644
--- a/net/dcsctp/tx/retransmission_timeout.cc
+++ b/net/dcsctp/tx/retransmission_timeout.cc
@@ -20,6 +20,7 @@
     : min_rto_(*options.rto_min),
       max_rto_(*options.rto_max),
       max_rtt_(*options.rtt_max),
+      min_rtt_variance_(*options.min_rtt_variance),
       rto_(*options.rto_initial) {}
 
 void RetransmissionTimeout::ObserveRTT(DurationMs measured_rtt) {
@@ -48,12 +49,12 @@
     rtt_diff -= (scaled_rtt_var_ >> kRttVarShift);
     scaled_rtt_var_ += rtt_diff;
   }
-  rto_ = (scaled_srtt_ >> kRttShift) + scaled_rtt_var_;
 
-  // If the RTO becomes smaller or equal to RTT, expiration timers will be
-  // scheduled at the same time as packets are expected. Only happens in
-  // extremely stable RTTs, i.e. in simulations.
-  rto_ = std::max(rto_, rtt + 1);
+  if (scaled_rtt_var_ < min_rtt_variance_) {
+    scaled_rtt_var_ = min_rtt_variance_;
+  }
+
+  rto_ = (scaled_srtt_ >> kRttShift) + scaled_rtt_var_;
 
   // Clamp RTO between min and max.
   rto_ = std::min(std::max(rto_, min_rto_), max_rto_);
diff --git a/net/dcsctp/tx/retransmission_timeout.h b/net/dcsctp/tx/retransmission_timeout.h
index f3a9553..7cbcc6f 100644
--- a/net/dcsctp/tx/retransmission_timeout.h
+++ b/net/dcsctp/tx/retransmission_timeout.h
@@ -44,6 +44,7 @@
   const int32_t min_rto_;
   const int32_t max_rto_;
   const int32_t max_rtt_;
+  const int32_t min_rtt_variance_;
   // If this is the first measurement
   bool first_measurement_ = true;
   // Smoothed Round-Trip Time, shifted by kRttShift
diff --git a/net/dcsctp/tx/retransmission_timeout_test.cc b/net/dcsctp/tx/retransmission_timeout_test.cc
index d2d0719..f3b20a8 100644
--- a/net/dcsctp/tx/retransmission_timeout_test.cc
+++ b/net/dcsctp/tx/retransmission_timeout_test.cc
@@ -20,6 +20,7 @@
 constexpr DurationMs kInitialRto = DurationMs(200);
 constexpr DurationMs kMaxRto = DurationMs(800);
 constexpr DurationMs kMinRto = DurationMs(120);
+constexpr DurationMs kMinRttVariance = DurationMs(220);
 
 DcSctpOptions MakeOptions() {
   DcSctpOptions options;
@@ -27,6 +28,7 @@
   options.rto_initial = kInitialRto;
   options.rto_max = kMaxRto;
   options.rto_min = kMinRto;
+  options.min_rtt_variance = kMinRttVariance;
   return options;
 }
 
@@ -82,13 +84,13 @@
   rto_.ObserveRTT(DurationMs(124));
   EXPECT_EQ(*rto_.rto(), 372);
   rto_.ObserveRTT(DurationMs(128));
-  EXPECT_EQ(*rto_.rto(), 314);
+  EXPECT_EQ(*rto_.rto(), 344);
   rto_.ObserveRTT(DurationMs(123));
-  EXPECT_EQ(*rto_.rto(), 268);
+  EXPECT_EQ(*rto_.rto(), 344);
   rto_.ObserveRTT(DurationMs(125));
-  EXPECT_EQ(*rto_.rto(), 233);
+  EXPECT_EQ(*rto_.rto(), 344);
   rto_.ObserveRTT(DurationMs(127));
-  EXPECT_EQ(*rto_.rto(), 209);
+  EXPECT_EQ(*rto_.rto(), 344);
 }
 
 TEST(RetransmissionTimeoutTest, CalculatesRtoForUnstableRtt) {
@@ -130,7 +132,7 @@
   rto_.ObserveRTT(DurationMs(124));
   EXPECT_EQ(*rto_.rto(), 372);
   rto_.ObserveRTT(DurationMs(124));
-  EXPECT_EQ(*rto_.rto(), 340);
+  EXPECT_EQ(*rto_.rto(), 367);
 }
 
 TEST(RetransmissionTimeoutTest, WillAlwaysStayAboveRTT) {
@@ -141,10 +143,32 @@
   // any jitter will increase the RTO.
   RetransmissionTimeout rto_(MakeOptions());
 
-  for (int i = 0; i < 100; ++i) {
+  for (int i = 0; i < 1000; ++i) {
     rto_.ObserveRTT(DurationMs(124));
   }
-  EXPECT_GT(*rto_.rto(), 124);
+  EXPECT_EQ(*rto_.rto(), 344);
+}
+
+TEST(RetransmissionTimeoutTest, CanSpecifySmallerMinimumRttVariance) {
+  DcSctpOptions options = MakeOptions();
+  options.min_rtt_variance = kMinRttVariance - DurationMs(100);
+  RetransmissionTimeout rto_(options);
+
+  for (int i = 0; i < 1000; ++i) {
+    rto_.ObserveRTT(DurationMs(124));
+  }
+  EXPECT_EQ(*rto_.rto(), 244);
+}
+
+TEST(RetransmissionTimeoutTest, CanSpecifyLargerMinimumRttVariance) {
+  DcSctpOptions options = MakeOptions();
+  options.min_rtt_variance = kMinRttVariance + DurationMs(100);
+  RetransmissionTimeout rto_(options);
+
+  for (int i = 0; i < 1000; ++i) {
+    rto_.ObserveRTT(DurationMs(124));
+  }
+  EXPECT_EQ(*rto_.rto(), 444);
 }
 
 }  // namespace