dcsctp: Specify the max timer backoff duration

By allowing the max timer backoff duration to be limited, a socket can
fast recover in case of intermittent network issues. Before this CL, the
exponential backoff algorithm could result in very long retry durations
(in the order of minutes), when connection has been lost or been flaky
for a long while.

Note that limiting the maximum backoff duration might require
compensating the maximum retransmission limit to avoid closing the
socket prematurely due to reaching the maximum retransmission limit much
faster than previously.

Bug: webrtc:13129
Change-Id: Ib94030d666433e3fa1a2c8ef69750a1afab8ef94
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/230702
Reviewed-by: Florent Castelli <orphis@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#34913}
diff --git a/net/dcsctp/public/dcsctp_options.h b/net/dcsctp/public/dcsctp_options.h
index a966115..6e3c233 100644
--- a/net/dcsctp/public/dcsctp_options.h
+++ b/net/dcsctp/public/dcsctp_options.h
@@ -113,6 +113,14 @@
   // T2-shutdown timeout.
   DurationMs t2_shutdown_timeout = DurationMs(1000);
 
+  // For t1-init, t1-cookie, t2-shutdown, t3-rtx, this value - if set - will be
+  // the upper bound on how large the exponentially backed off timeout can
+  // become. The lower the duration, the faster the connection can recover on
+  // transient network issues. Setting this value may require changing
+  // `max_retransmissions` and `max_init_retransmits` to ensure that the
+  // connection is not closed too quickly.
+  absl::optional<DurationMs> max_timer_backoff_duration = absl::nullopt;
+
   // Hearbeat interval (on idle connections only). Set to zero to disable.
   DurationMs heartbeat_interval = DurationMs(30000);
 
diff --git a/net/dcsctp/socket/transmission_control_block.h b/net/dcsctp/socket/transmission_control_block.h
index 4510d83..c3766d1 100644
--- a/net/dcsctp/socket/transmission_control_block.h
+++ b/net/dcsctp/socket/transmission_control_block.h
@@ -66,7 +66,10 @@
         t3_rtx_(timer_manager_.CreateTimer(
             "t3-rtx",
             absl::bind_front(&TransmissionControlBlock::OnRtxTimerExpiry, this),
-            TimerOptions(options.rto_initial))),
+            TimerOptions(options.rto_initial,
+                         TimerBackoffAlgorithm::kExponential,
+                         /*max_restarts=*/absl::nullopt,
+                         options.max_timer_backoff_duration))),
         delayed_ack_timer_(timer_manager_.CreateTimer(
             "delayed-ack",
             absl::bind_front(&TransmissionControlBlock::OnDelayedAckTimerExpiry,
diff --git a/net/dcsctp/timer/timer.cc b/net/dcsctp/timer/timer.cc
index 2ad6d74..b3168e3 100644
--- a/net/dcsctp/timer/timer.cc
+++ b/net/dcsctp/timer/timer.cc
@@ -26,10 +26,10 @@
   return TimeoutID(static_cast<uint64_t>(*timer_id) << 32 | *generation);
 }
 
-DurationMs GetBackoffDuration(TimerBackoffAlgorithm algorithm,
+DurationMs GetBackoffDuration(const TimerOptions& options,
                               DurationMs base_duration,
                               int expiration_count) {
-  switch (algorithm) {
+  switch (options.backoff_algorithm) {
     case TimerBackoffAlgorithm::kFixed:
       return base_duration;
     case TimerBackoffAlgorithm::kExponential: {
@@ -38,6 +38,11 @@
       while (expiration_count > 0 && duration_ms < *Timer::kMaxTimerDuration) {
         duration_ms *= 2;
         --expiration_count;
+
+        if (options.max_backoff_duration.has_value() &&
+            duration_ms > **options.max_backoff_duration) {
+          return *options.max_backoff_duration;
+        }
       }
 
       return DurationMs(std::min(duration_ms, *Timer::kMaxTimerDuration));
@@ -99,8 +104,8 @@
       // timer. Note that it might be very quickly restarted again, if the
       // `on_expired_` callback returns a new duration.
       is_running_ = true;
-      DurationMs duration = GetBackoffDuration(options_.backoff_algorithm,
-                                               duration_, expiration_count_);
+      DurationMs duration =
+          GetBackoffDuration(options_, duration_, expiration_count_);
       generation_ = TimerGeneration(*generation_ + 1);
       timeout_->Start(duration, MakeTimeoutId(id_, generation_));
     }
@@ -112,8 +117,8 @@
         // Restart it with new duration.
         timeout_->Stop();
 
-        DurationMs duration = GetBackoffDuration(options_.backoff_algorithm,
-                                                 duration_, expiration_count_);
+        DurationMs duration =
+            GetBackoffDuration(options_, duration_, expiration_count_);
         generation_ = TimerGeneration(*generation_ + 1);
         timeout_->Start(duration, MakeTimeoutId(id_, generation_));
       }
diff --git a/net/dcsctp/timer/timer.h b/net/dcsctp/timer/timer.h
index ed072de..1d846b6 100644
--- a/net/dcsctp/timer/timer.h
+++ b/net/dcsctp/timer/timer.h
@@ -46,9 +46,16 @@
   TimerOptions(DurationMs duration,
                TimerBackoffAlgorithm backoff_algorithm,
                absl::optional<int> max_restarts)
+      : TimerOptions(duration, backoff_algorithm, max_restarts, absl::nullopt) {
+  }
+  TimerOptions(DurationMs duration,
+               TimerBackoffAlgorithm backoff_algorithm,
+               absl::optional<int> max_restarts,
+               absl::optional<DurationMs> max_backoff_duration)
       : duration(duration),
         backoff_algorithm(backoff_algorithm),
-        max_restarts(max_restarts) {}
+        max_restarts(max_restarts),
+        max_backoff_duration(max_backoff_duration) {}
 
   // The initial timer duration. Can be overridden with `set_duration`.
   const DurationMs duration;
@@ -58,6 +65,8 @@
   // The maximum number of times that the timer will be automatically restarted,
   // or absl::nullopt if there is no limit.
   const absl::optional<int> max_restarts;
+  // The maximum timeout value for exponential backoff.
+  const absl::optional<DurationMs> max_backoff_duration;
 };
 
 // A high-level timer (in contrast to the low-level `Timeout` class).
diff --git a/net/dcsctp/timer/timer_test.cc b/net/dcsctp/timer/timer_test.cc
index a403bb6..5550f7f 100644
--- a/net/dcsctp/timer/timer_test.cc
+++ b/net/dcsctp/timer/timer_test.cc
@@ -386,5 +386,42 @@
   AdvanceTimeAndRunTimers(DurationMs(1));
 }
 
+TEST_F(TimerTest, DurationStaysWithinMaxTimerBackOffDuration) {
+  std::unique_ptr<Timer> t1 = manager_.CreateTimer(
+      "t1", on_expired_.AsStdFunction(),
+      TimerOptions(DurationMs(1000), TimerBackoffAlgorithm::kExponential,
+                   /*max_restarts=*/absl::nullopt, DurationMs(5000)));
+
+  t1->Start();
+
+  // Initial timeout, 1000 ms
+  EXPECT_CALL(on_expired_, Call).Times(1);
+  AdvanceTimeAndRunTimers(DurationMs(1000));
+
+  // Exponential backoff -> 2000 ms
+  EXPECT_CALL(on_expired_, Call).Times(0);
+  AdvanceTimeAndRunTimers(DurationMs(1999));
+  EXPECT_CALL(on_expired_, Call).Times(1);
+  AdvanceTimeAndRunTimers(DurationMs(1));
+
+  // Exponential backoff -> 4000 ms
+  EXPECT_CALL(on_expired_, Call).Times(0);
+  AdvanceTimeAndRunTimers(DurationMs(3999));
+  EXPECT_CALL(on_expired_, Call).Times(1);
+  AdvanceTimeAndRunTimers(DurationMs(1));
+
+  // Limited backoff -> 5000ms
+  EXPECT_CALL(on_expired_, Call).Times(0);
+  AdvanceTimeAndRunTimers(DurationMs(4999));
+  EXPECT_CALL(on_expired_, Call).Times(1);
+  AdvanceTimeAndRunTimers(DurationMs(1));
+
+  // ... where it plateaus
+  EXPECT_CALL(on_expired_, Call).Times(0);
+  AdvanceTimeAndRunTimers(DurationMs(4999));
+  EXPECT_CALL(on_expired_, Call).Times(1);
+  AdvanceTimeAndRunTimers(DurationMs(1));
+}
+
 }  // namespace
 }  // namespace dcsctp