/*
 *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */
#include "net/dcsctp/tx/retransmission_timeout.h"

#include "net/dcsctp/public/dcsctp_options.h"
#include "rtc_base/gunit.h"
#include "test/gmock.h"

namespace dcsctp {
namespace {
using ::webrtc::TimeDelta;

constexpr TimeDelta kMaxRtt = TimeDelta::Millis(8'000);
constexpr TimeDelta kInitialRto = TimeDelta::Millis(200);
constexpr TimeDelta kMaxRto = TimeDelta::Millis(800);
constexpr TimeDelta kMinRto = TimeDelta::Millis(120);
constexpr TimeDelta kMinRttVariance = TimeDelta::Millis(220);

DcSctpOptions MakeOptions() {
  DcSctpOptions options;
  options.rtt_max = DurationMs(kMaxRtt);
  options.rto_initial = DurationMs(kInitialRto);
  options.rto_max = DurationMs(kMaxRto);
  options.rto_min = DurationMs(kMinRto);
  options.min_rtt_variance = DurationMs(kMinRttVariance);
  return options;
}

TEST(RetransmissionTimeoutTest, HasValidInitialRto) {
  RetransmissionTimeout rto_(MakeOptions());
  EXPECT_EQ(rto_.rto(), kInitialRto);
}

TEST(RetransmissionTimeoutTest, HasValidInitialSrtt) {
  RetransmissionTimeout rto_(MakeOptions());
  EXPECT_EQ(rto_.srtt(), kInitialRto);
}

TEST(RetransmissionTimeoutTest, NegativeValuesDoNotAffectRTO) {
  RetransmissionTimeout rto_(MakeOptions());
  // Initial negative value
  rto_.ObserveRTT(TimeDelta::Millis(-10));
  EXPECT_EQ(rto_.rto(), kInitialRto);
  rto_.ObserveRTT(TimeDelta::Millis(124));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372));
  // Subsequent negative value
  rto_.ObserveRTT(TimeDelta::Millis(-10));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372));
}

TEST(RetransmissionTimeoutTest, TooLargeValuesDoNotAffectRTO) {
  RetransmissionTimeout rto_(MakeOptions());
  // Initial too large value
  rto_.ObserveRTT(kMaxRtt + TimeDelta::Millis(100));
  EXPECT_EQ(rto_.rto(), kInitialRto);
  rto_.ObserveRTT(TimeDelta::Millis(124));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372));
  // Subsequent too large value
  rto_.ObserveRTT(kMaxRtt + TimeDelta::Millis(100));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372));
}

TEST(RetransmissionTimeoutTest, WillNeverGoBelowMinimumRto) {
  RetransmissionTimeout rto_(MakeOptions());
  for (int i = 0; i < 1000; ++i) {
    rto_.ObserveRTT(TimeDelta::Millis(1));
  }
  EXPECT_GE(rto_.rto(), kMinRto);
}

TEST(RetransmissionTimeoutTest, WillNeverGoAboveMaximumRto) {
  RetransmissionTimeout rto_(MakeOptions());
  for (int i = 0; i < 1000; ++i) {
    rto_.ObserveRTT(kMaxRtt - TimeDelta::Millis(1));
    // Adding jitter, which would make it RTO be well above RTT.
    rto_.ObserveRTT(kMaxRtt - TimeDelta::Millis(100));
  }
  EXPECT_LE(rto_.rto(), kMaxRto);
}

TEST(RetransmissionTimeoutTest, CalculatesRtoForStableRtt) {
  RetransmissionTimeout rto_(MakeOptions());
  rto_.ObserveRTT(TimeDelta::Millis(124));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372));
  rto_.ObserveRTT(TimeDelta::Millis(128));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(344));
  rto_.ObserveRTT(TimeDelta::Millis(123));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(344));
  rto_.ObserveRTT(TimeDelta::Millis(125));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(344));
  rto_.ObserveRTT(TimeDelta::Millis(127));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(344));
}

TEST(RetransmissionTimeoutTest, CalculatesRtoForUnstableRtt) {
  RetransmissionTimeout rto_(MakeOptions());
  rto_.ObserveRTT(TimeDelta::Millis(124));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372));
  rto_.ObserveRTT(TimeDelta::Millis(402));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(622));
  rto_.ObserveRTT(TimeDelta::Millis(728));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(800));
  rto_.ObserveRTT(TimeDelta::Millis(89));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(800));
  rto_.ObserveRTT(TimeDelta::Millis(126));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(800));
}

TEST(RetransmissionTimeoutTest, WillStabilizeAfterAWhile) {
  RetransmissionTimeout rto_(MakeOptions());
  rto_.ObserveRTT(TimeDelta::Millis(124));
  rto_.ObserveRTT(TimeDelta::Millis(402));
  rto_.ObserveRTT(TimeDelta::Millis(728));
  rto_.ObserveRTT(TimeDelta::Millis(89));
  rto_.ObserveRTT(TimeDelta::Millis(126));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(800));
  rto_.ObserveRTT(TimeDelta::Millis(124));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(800));
  rto_.ObserveRTT(TimeDelta::Millis(122));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(710));
  rto_.ObserveRTT(TimeDelta::Millis(123));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(631));
  rto_.ObserveRTT(TimeDelta::Millis(124));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(562));
  rto_.ObserveRTT(TimeDelta::Millis(122));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(505));
  rto_.ObserveRTT(TimeDelta::Millis(124));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(454));
  rto_.ObserveRTT(TimeDelta::Millis(124));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(410));
  rto_.ObserveRTT(TimeDelta::Millis(124));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(372));
  rto_.ObserveRTT(TimeDelta::Millis(124));
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(367));
}

TEST(RetransmissionTimeoutTest, WillAlwaysStayAboveRTT) {
  // In simulations, it's quite common to have a very stable RTT, and having an
  // RTO at the same value will cause issues as expiry timers will be scheduled
  // to be expire exactly when a packet is supposed to arrive. The RTO must be
  // larger than the RTT. In non-simulated environments, this is a non-issue as
  // any jitter will increase the RTO.
  RetransmissionTimeout rto_(MakeOptions());

  for (int i = 0; i < 1000; ++i) {
    rto_.ObserveRTT(TimeDelta::Millis(124));
  }
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(344));
}

TEST(RetransmissionTimeoutTest, CanSpecifySmallerMinimumRttVariance) {
  DcSctpOptions options = MakeOptions();
  options.min_rtt_variance =
      DurationMs(kMinRttVariance - TimeDelta::Millis(100));
  RetransmissionTimeout rto_(options);

  for (int i = 0; i < 1000; ++i) {
    rto_.ObserveRTT(TimeDelta::Millis(124));
  }
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(244));
}

TEST(RetransmissionTimeoutTest, CanSpecifyLargerMinimumRttVariance) {
  DcSctpOptions options = MakeOptions();
  options.min_rtt_variance =
      DurationMs(kMinRttVariance + TimeDelta::Millis(100));
  RetransmissionTimeout rto_(options);

  for (int i = 0; i < 1000; ++i) {
    rto_.ObserveRTT(TimeDelta::Millis(124));
  }
  EXPECT_EQ(rto_.rto(), TimeDelta::Millis(444));
}

}  // namespace
}  // namespace dcsctp
