/*
 *  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 {

constexpr DurationMs kMaxRtt = DurationMs(8'000);
constexpr DurationMs kInitialRto = DurationMs(200);
constexpr DurationMs kMaxRto = DurationMs(800);
constexpr DurationMs kMinRto = DurationMs(120);

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

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

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

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

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

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

TEST(RetransmissionTimeoutTest, CalculatesRtoForStableRtt) {
  RetransmissionTimeout rto_(MakeOptions());
  rto_.ObserveRTT(DurationMs(124));
  EXPECT_EQ(*rto_.rto(), 372);
  rto_.ObserveRTT(DurationMs(128));
  EXPECT_EQ(*rto_.rto(), 314);
  rto_.ObserveRTT(DurationMs(123));
  EXPECT_EQ(*rto_.rto(), 268);
  rto_.ObserveRTT(DurationMs(125));
  EXPECT_EQ(*rto_.rto(), 233);
  rto_.ObserveRTT(DurationMs(127));
  EXPECT_EQ(*rto_.rto(), 208);
}

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

TEST(RetransmissionTimeoutTest, WillStabilizeAfterAWhile) {
  RetransmissionTimeout rto_(MakeOptions());
  rto_.ObserveRTT(DurationMs(124));
  rto_.ObserveRTT(DurationMs(402));
  rto_.ObserveRTT(DurationMs(728));
  rto_.ObserveRTT(DurationMs(89));
  rto_.ObserveRTT(DurationMs(126));
  EXPECT_EQ(*rto_.rto(), 800);
  rto_.ObserveRTT(DurationMs(124));
  EXPECT_EQ(*rto_.rto(), 800);
  rto_.ObserveRTT(DurationMs(122));
  EXPECT_EQ(*rto_.rto(), 709);
  rto_.ObserveRTT(DurationMs(123));
  EXPECT_EQ(*rto_.rto(), 630);
  rto_.ObserveRTT(DurationMs(124));
  EXPECT_EQ(*rto_.rto(), 561);
  rto_.ObserveRTT(DurationMs(122));
  EXPECT_EQ(*rto_.rto(), 504);
  rto_.ObserveRTT(DurationMs(124));
  EXPECT_EQ(*rto_.rto(), 453);
  rto_.ObserveRTT(DurationMs(124));
  EXPECT_EQ(*rto_.rto(), 409);
  rto_.ObserveRTT(DurationMs(124));
  EXPECT_EQ(*rto_.rto(), 372);
  rto_.ObserveRTT(DurationMs(124));
  EXPECT_EQ(*rto_.rto(), 339);
}

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 < 100; ++i) {
    rto_.ObserveRTT(DurationMs(124));
  }
  EXPECT_GT(*rto_.rto(), 124);
}

}  // namespace
}  // namespace dcsctp
