/*
 *  Copyright 2011 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 "p2p/base/pseudo_tcp.h"

#include <string.h>

#include <algorithm>
#include <cstddef>
#include <string>
#include <utility>
#include <vector>

#include "api/task_queue/pending_task_safety_flag.h"
#include "api/task_queue/task_queue_base.h"
#include "api/units/time_delta.h"
#include "rtc_base/gunit.h"
#include "rtc_base/helpers.h"
#include "rtc_base/logging.h"
#include "rtc_base/memory_stream.h"
#include "rtc_base/time_utils.h"
#include "test/gtest.h"

using ::cricket::PseudoTcp;
using ::webrtc::ScopedTaskSafety;
using ::webrtc::TaskQueueBase;
using ::webrtc::TimeDelta;

static const int kConnectTimeoutMs = 10000;  // ~3 * default RTO of 3000ms
static const int kTransferTimeoutMs = 15000;
static const int kBlockSize = 4096;

class PseudoTcpForTest : public cricket::PseudoTcp {
 public:
  PseudoTcpForTest(cricket::IPseudoTcpNotify* notify, uint32_t conv)
      : PseudoTcp(notify, conv) {}

  bool isReceiveBufferFull() const { return PseudoTcp::isReceiveBufferFull(); }

  void disableWindowScale() { PseudoTcp::disableWindowScale(); }
};

class PseudoTcpTestBase : public ::testing::Test,
                          public cricket::IPseudoTcpNotify {
 public:
  PseudoTcpTestBase()
      : local_(this, 1),
        remote_(this, 1),
        have_connected_(false),
        have_disconnected_(false),
        local_mtu_(65535),
        remote_mtu_(65535),
        delay_(0),
        loss_(0) {
    // Set use of the test RNG to get predictable loss patterns. Otherwise,
    // this test would occasionally get really unlucky loss and time out.
    rtc::SetRandomTestMode(true);
  }
  ~PseudoTcpTestBase() {
    // Put it back for the next test.
    rtc::SetRandomTestMode(false);
  }
  // If true, both endpoints will send the "connect" segment simultaneously,
  // rather than `local_` sending it followed by a response from `remote_`.
  // Note that this is what chromoting ends up doing.
  void SetSimultaneousOpen(bool enabled) { simultaneous_open_ = enabled; }
  void SetLocalMtu(int mtu) {
    local_.NotifyMTU(mtu);
    local_mtu_ = mtu;
  }
  void SetRemoteMtu(int mtu) {
    remote_.NotifyMTU(mtu);
    remote_mtu_ = mtu;
  }
  void SetDelay(int delay) { delay_ = delay; }
  void SetLoss(int percent) { loss_ = percent; }
  // Used to cause the initial "connect" segment to be lost, needed for a
  // regression test.
  void DropNextPacket() { drop_next_packet_ = true; }
  void SetOptNagling(bool enable_nagles) {
    local_.SetOption(PseudoTcp::OPT_NODELAY, !enable_nagles);
    remote_.SetOption(PseudoTcp::OPT_NODELAY, !enable_nagles);
  }
  void SetOptAckDelay(int ack_delay) {
    local_.SetOption(PseudoTcp::OPT_ACKDELAY, ack_delay);
    remote_.SetOption(PseudoTcp::OPT_ACKDELAY, ack_delay);
  }
  void SetOptSndBuf(int size) {
    local_.SetOption(PseudoTcp::OPT_SNDBUF, size);
    remote_.SetOption(PseudoTcp::OPT_SNDBUF, size);
  }
  void SetRemoteOptRcvBuf(int size) {
    remote_.SetOption(PseudoTcp::OPT_RCVBUF, size);
  }
  void SetLocalOptRcvBuf(int size) {
    local_.SetOption(PseudoTcp::OPT_RCVBUF, size);
  }
  void DisableRemoteWindowScale() { remote_.disableWindowScale(); }
  void DisableLocalWindowScale() { local_.disableWindowScale(); }

 protected:
  int Connect() {
    int ret = local_.Connect();
    if (ret == 0) {
      UpdateLocalClock();
    }
    if (simultaneous_open_) {
      ret = remote_.Connect();
      if (ret == 0) {
        UpdateRemoteClock();
      }
    }
    return ret;
  }
  void Close() {
    local_.Close(false);
    UpdateLocalClock();
  }

  virtual void OnTcpOpen(PseudoTcp* tcp) {
    // Consider ourselves connected when the local side gets OnTcpOpen.
    // OnTcpWriteable isn't fired at open, so we trigger it now.
    RTC_LOG(LS_VERBOSE) << "Opened";
    if (tcp == &local_) {
      have_connected_ = true;
      OnTcpWriteable(tcp);
    }
  }
  // Test derived from the base should override
  //   virtual void OnTcpReadable(PseudoTcp* tcp)
  // and
  //   virtual void OnTcpWritable(PseudoTcp* tcp)
  virtual void OnTcpClosed(PseudoTcp* tcp, uint32_t error) {
    // Consider ourselves closed when the remote side gets OnTcpClosed.
    // TODO(?): OnTcpClosed is only ever notified in case of error in
    // the current implementation.  Solicited close is not (yet) supported.
    RTC_LOG(LS_VERBOSE) << "Closed";
    EXPECT_EQ(0U, error);
    if (tcp == &remote_) {
      have_disconnected_ = true;
    }
  }
  virtual WriteResult TcpWritePacket(PseudoTcp* tcp,
                                     const char* buffer,
                                     size_t len) {
    // Drop a packet if the test called DropNextPacket.
    if (drop_next_packet_) {
      drop_next_packet_ = false;
      RTC_LOG(LS_VERBOSE) << "Dropping packet due to DropNextPacket, size="
                          << len;
      return WR_SUCCESS;
    }
    // Randomly drop the desired percentage of packets.
    if (rtc::CreateRandomId() % 100 < static_cast<uint32_t>(loss_)) {
      RTC_LOG(LS_VERBOSE) << "Randomly dropping packet, size=" << len;
      return WR_SUCCESS;
    }
    // Also drop packets that are larger than the configured MTU.
    if (len > static_cast<size_t>(std::min(local_mtu_, remote_mtu_))) {
      RTC_LOG(LS_VERBOSE) << "Dropping packet that exceeds path MTU, size="
                          << len;
      return WR_SUCCESS;
    }
    PseudoTcp* other;
    ScopedTaskSafety* timer;
    if (tcp == &local_) {
      other = &remote_;
      timer = &remote_timer_;
    } else {
      other = &local_;
      timer = &local_timer_;
    }
    std::string packet(buffer, len);
    ++packets_in_flight_;
    TaskQueueBase::Current()->PostDelayedTask(
        [other, timer, packet = std::move(packet), this] {
          --packets_in_flight_;
          other->NotifyPacket(packet.c_str(), packet.size());
          UpdateClock(*other, *timer);
        },
        TimeDelta::Millis(delay_));
    return WR_SUCCESS;
  }

  void UpdateLocalClock() { UpdateClock(local_, local_timer_); }
  void UpdateRemoteClock() { UpdateClock(remote_, remote_timer_); }
  static void UpdateClock(PseudoTcp& tcp, ScopedTaskSafety& timer) {
    long interval = 0;  // NOLINT
    tcp.GetNextClock(PseudoTcp::Now(), interval);
    interval = std::max<int>(interval, 0L);  // sometimes interval is < 0
    timer.reset();
    TaskQueueBase::Current()->PostDelayedTask(
        SafeTask(timer.flag(),
                 [&tcp, &timer] {
                   tcp.NotifyClock(PseudoTcp::Now());
                   UpdateClock(tcp, timer);
                 }),
        TimeDelta::Millis(interval));
  }

  rtc::AutoThread main_thread_;
  PseudoTcpForTest local_;
  PseudoTcpForTest remote_;
  ScopedTaskSafety local_timer_;
  ScopedTaskSafety remote_timer_;
  rtc::MemoryStream send_stream_;
  rtc::MemoryStream recv_stream_;
  bool have_connected_;
  bool have_disconnected_;
  int local_mtu_;
  int remote_mtu_;
  int delay_;
  int loss_;
  bool drop_next_packet_ = false;
  bool simultaneous_open_ = false;
  int packets_in_flight_ = 0;
};

class PseudoTcpTest : public PseudoTcpTestBase {
 public:
  void TestTransfer(int size) {
    uint32_t start;
    int32_t elapsed;
    size_t received;
    // Create some dummy data to send.
    send_stream_.ReserveSize(size);
    for (int i = 0; i < size; ++i) {
      char ch = static_cast<char>(i);
      send_stream_.Write(&ch, 1, NULL, NULL);
    }
    send_stream_.Rewind();
    // Prepare the receive stream.
    recv_stream_.ReserveSize(size);
    // Connect and wait until connected.
    start = rtc::Time32();
    EXPECT_EQ(0, Connect());
    EXPECT_TRUE_WAIT(have_connected_, kConnectTimeoutMs);
    // Sending will start from OnTcpWriteable and complete when all data has
    // been received.
    EXPECT_TRUE_WAIT(have_disconnected_, kTransferTimeoutMs);
    elapsed = rtc::Time32() - start;
    recv_stream_.GetSize(&received);
    // Ensure we closed down OK and we got the right data.
    // TODO(?): Ensure the errors are cleared properly.
    // EXPECT_EQ(0, local_.GetError());
    // EXPECT_EQ(0, remote_.GetError());
    EXPECT_EQ(static_cast<size_t>(size), received);
    EXPECT_EQ(0,
              memcmp(send_stream_.GetBuffer(), recv_stream_.GetBuffer(), size));
    RTC_LOG(LS_INFO) << "Transferred " << received << " bytes in " << elapsed
                     << " ms (" << size * 8 / elapsed << " Kbps)";
  }

 private:
  // IPseudoTcpNotify interface

  virtual void OnTcpReadable(PseudoTcp* tcp) {
    // Stream bytes to the recv stream as they arrive.
    if (tcp == &remote_) {
      ReadData();

      // TODO(?): OnTcpClosed() is currently only notified on error -
      // there is no on-the-wire equivalent of TCP FIN.
      // So we fake the notification when all the data has been read.
      size_t received, required;
      recv_stream_.GetPosition(&received);
      send_stream_.GetSize(&required);
      if (received == required)
        OnTcpClosed(&remote_, 0);
    }
  }
  virtual void OnTcpWriteable(PseudoTcp* tcp) {
    // Write bytes from the send stream when we can.
    // Shut down when we've sent everything.
    if (tcp == &local_) {
      RTC_LOG(LS_VERBOSE) << "Flow Control Lifted";
      bool done;
      WriteData(&done);
      if (done) {
        Close();
      }
    }
  }

  void ReadData() {
    char block[kBlockSize];
    size_t position;
    int rcvd;
    do {
      rcvd = remote_.Recv(block, sizeof(block));
      if (rcvd != -1) {
        recv_stream_.Write(block, rcvd, NULL, NULL);
        recv_stream_.GetPosition(&position);
        RTC_LOG(LS_VERBOSE) << "Received: " << position;
      }
    } while (rcvd > 0);
  }
  void WriteData(bool* done) {
    size_t position, tosend;
    int sent;
    char block[kBlockSize];
    do {
      send_stream_.GetPosition(&position);
      if (send_stream_.Read(block, sizeof(block), &tosend, NULL) !=
          rtc::SR_EOS) {
        sent = local_.Send(block, tosend);
        UpdateLocalClock();
        if (sent != -1) {
          send_stream_.SetPosition(position + sent);
          RTC_LOG(LS_VERBOSE) << "Sent: " << position + sent;
        } else {
          send_stream_.SetPosition(position);
          RTC_LOG(LS_VERBOSE) << "Flow Controlled";
        }
      } else {
        sent = static_cast<int>(tosend = 0);
      }
    } while (sent > 0);
    *done = (tosend == 0);
  }

 private:
  rtc::MemoryStream send_stream_;
  rtc::MemoryStream recv_stream_;
};

class PseudoTcpTestPingPong : public PseudoTcpTestBase {
 public:
  PseudoTcpTestPingPong()
      : iterations_remaining_(0),
        sender_(NULL),
        receiver_(NULL),
        bytes_per_send_(0) {}
  void SetBytesPerSend(int bytes) { bytes_per_send_ = bytes; }
  void TestPingPong(int size, int iterations) {
    uint32_t start, elapsed;
    iterations_remaining_ = iterations;
    receiver_ = &remote_;
    sender_ = &local_;
    // Create some dummy data to send.
    send_stream_.ReserveSize(size);
    for (int i = 0; i < size; ++i) {
      char ch = static_cast<char>(i);
      send_stream_.Write(&ch, 1, NULL, NULL);
    }
    send_stream_.Rewind();
    // Prepare the receive stream.
    recv_stream_.ReserveSize(size);
    // Connect and wait until connected.
    start = rtc::Time32();
    EXPECT_EQ(0, Connect());
    EXPECT_TRUE_WAIT(have_connected_, kConnectTimeoutMs);
    // Sending will start from OnTcpWriteable and stop when the required
    // number of iterations have completed.
    EXPECT_TRUE_WAIT(have_disconnected_, kTransferTimeoutMs);
    elapsed = rtc::TimeSince(start);
    RTC_LOG(LS_INFO) << "Performed " << iterations << " pings in " << elapsed
                     << " ms";
  }

 private:
  // IPseudoTcpNotify interface

  virtual void OnTcpReadable(PseudoTcp* tcp) {
    if (tcp != receiver_) {
      RTC_LOG_F(LS_ERROR) << "unexpected OnTcpReadable";
      return;
    }
    // Stream bytes to the recv stream as they arrive.
    ReadData();
    // If we've received the desired amount of data, rewind things
    // and send it back the other way!
    size_t position, desired;
    recv_stream_.GetPosition(&position);
    send_stream_.GetSize(&desired);
    if (position == desired) {
      if (receiver_ == &local_ && --iterations_remaining_ == 0) {
        Close();
        // TODO(?): Fake OnTcpClosed() on the receiver for now.
        OnTcpClosed(&remote_, 0);
        return;
      }
      PseudoTcp* tmp = receiver_;
      receiver_ = sender_;
      sender_ = tmp;
      recv_stream_.Rewind();
      send_stream_.Rewind();
      OnTcpWriteable(sender_);
    }
  }
  virtual void OnTcpWriteable(PseudoTcp* tcp) {
    if (tcp != sender_)
      return;
    // Write bytes from the send stream when we can.
    // Shut down when we've sent everything.
    RTC_LOG(LS_VERBOSE) << "Flow Control Lifted";
    WriteData();
  }

  void ReadData() {
    char block[kBlockSize];
    size_t position;
    int rcvd;
    do {
      rcvd = receiver_->Recv(block, sizeof(block));
      if (rcvd != -1) {
        recv_stream_.Write(block, rcvd, NULL, NULL);
        recv_stream_.GetPosition(&position);
        RTC_LOG(LS_VERBOSE) << "Received: " << position;
      }
    } while (rcvd > 0);
  }
  void WriteData() {
    size_t position, tosend;
    int sent;
    char block[kBlockSize];
    do {
      send_stream_.GetPosition(&position);
      tosend = bytes_per_send_ ? bytes_per_send_ : sizeof(block);
      if (send_stream_.Read(block, tosend, &tosend, NULL) != rtc::SR_EOS) {
        sent = sender_->Send(block, tosend);
        UpdateLocalClock();
        if (sent != -1) {
          send_stream_.SetPosition(position + sent);
          RTC_LOG(LS_VERBOSE) << "Sent: " << position + sent;
        } else {
          send_stream_.SetPosition(position);
          RTC_LOG(LS_VERBOSE) << "Flow Controlled";
        }
      } else {
        sent = static_cast<int>(tosend = 0);
      }
    } while (sent > 0);
  }

 private:
  int iterations_remaining_;
  PseudoTcp* sender_;
  PseudoTcp* receiver_;
  int bytes_per_send_;
};

// Fill the receiver window until it is full, drain it and then
// fill it with the same amount. This is to test that receiver window
// contracts and enlarges correctly.
class PseudoTcpTestReceiveWindow : public PseudoTcpTestBase {
 public:
  // Not all the data are transfered, `size` just need to be big enough
  // to fill up the receiver window twice.
  void TestTransfer(int size) {
    // Create some dummy data to send.
    send_stream_.ReserveSize(size);
    for (int i = 0; i < size; ++i) {
      char ch = static_cast<char>(i);
      send_stream_.Write(&ch, 1, NULL, NULL);
    }
    send_stream_.Rewind();

    // Prepare the receive stream.
    recv_stream_.ReserveSize(size);

    // Connect and wait until connected.
    EXPECT_EQ(0, Connect());
    EXPECT_TRUE_WAIT(have_connected_, kConnectTimeoutMs);

    TaskQueueBase::Current()->PostTask([this] { WriteData(); });
    EXPECT_TRUE_WAIT(have_disconnected_, kTransferTimeoutMs);

    ASSERT_EQ(2u, send_position_.size());
    ASSERT_EQ(2u, recv_position_.size());

    const size_t estimated_recv_window = EstimateReceiveWindowSize();

    // The difference in consecutive send positions should equal the
    // receive window size or match very closely. This verifies that receive
    // window is open after receiver drained all the data.
    const size_t send_position_diff = send_position_[1] - send_position_[0];
    EXPECT_GE(1024u, estimated_recv_window - send_position_diff);

    // Receiver drained the receive window twice.
    EXPECT_EQ(2 * estimated_recv_window, recv_position_[1]);
  }

  uint32_t EstimateReceiveWindowSize() const {
    return static_cast<uint32_t>(recv_position_[0]);
  }

  uint32_t EstimateSendWindowSize() const {
    return static_cast<uint32_t>(send_position_[0] - recv_position_[0]);
  }

 private:
  // IPseudoTcpNotify interface
  virtual void OnTcpReadable(PseudoTcp* tcp) {}

  virtual void OnTcpWriteable(PseudoTcp* tcp) {}

  void ReadUntilIOPending() {
    char block[kBlockSize];
    size_t position;
    int rcvd;

    do {
      rcvd = remote_.Recv(block, sizeof(block));
      if (rcvd != -1) {
        recv_stream_.Write(block, rcvd, NULL, NULL);
        recv_stream_.GetPosition(&position);
        RTC_LOG(LS_VERBOSE) << "Received: " << position;
      }
    } while (rcvd > 0);

    recv_stream_.GetPosition(&position);
    recv_position_.push_back(position);

    // Disconnect if we have done two transfers.
    if (recv_position_.size() == 2u) {
      Close();
      OnTcpClosed(&remote_, 0);
    } else {
      WriteData();
    }
  }

  void WriteData() {
    size_t position, tosend;
    int sent;
    char block[kBlockSize];
    do {
      send_stream_.GetPosition(&position);
      if (send_stream_.Read(block, sizeof(block), &tosend, NULL) !=
          rtc::SR_EOS) {
        sent = local_.Send(block, tosend);
        UpdateLocalClock();
        if (sent != -1) {
          send_stream_.SetPosition(position + sent);
          RTC_LOG(LS_VERBOSE) << "Sent: " << position + sent;
        } else {
          send_stream_.SetPosition(position);
          RTC_LOG(LS_VERBOSE) << "Flow Controlled";
        }
      } else {
        sent = static_cast<int>(tosend = 0);
      }
    } while (sent > 0);
    // At this point, we've filled up the available space in the send queue.

    if (packets_in_flight_ > 0) {
      // If there are packet tasks, attempt to continue sending after giving
      // those packets time to process, which should free up the send buffer.
      rtc::Thread::Current()->PostDelayedTask([this] { WriteData(); },
                                              TimeDelta::Millis(10));
    } else {
      if (!remote_.isReceiveBufferFull()) {
        RTC_LOG(LS_ERROR) << "This shouldn't happen - the send buffer is full, "
                             "the receive buffer is not, and there are no "
                             "remaining messages to process.";
      }
      send_stream_.GetPosition(&position);
      send_position_.push_back(position);

      // Drain the receiver buffer.
      ReadUntilIOPending();
    }
  }

 private:
  rtc::MemoryStream send_stream_;
  rtc::MemoryStream recv_stream_;

  std::vector<size_t> send_position_;
  std::vector<size_t> recv_position_;
};

// Basic end-to-end data transfer tests

// Test the normal case of sending data from one side to the other.
TEST_F(PseudoTcpTest, TestSend) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  TestTransfer(1000000);
}

// Test sending data with a 50 ms RTT. Transmission should take longer due
// to a slower ramp-up in send rate.
TEST_F(PseudoTcpTest, TestSendWithDelay) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetDelay(50);
  TestTransfer(1000000);
}

// Test sending data with packet loss. Transmission should take much longer due
// to send back-off when loss occurs.
TEST_F(PseudoTcpTest, TestSendWithLoss) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetLoss(10);
  TestTransfer(100000);  // less data so test runs faster
}

// Test sending data with a 50 ms RTT and 10% packet loss. Transmission should
// take much longer due to send back-off and slower detection of loss.
TEST_F(PseudoTcpTest, TestSendWithDelayAndLoss) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetDelay(50);
  SetLoss(10);
  TestTransfer(100000);  // less data so test runs faster
}

// Test sending data with 10% packet loss and Nagling disabled.  Transmission
// should take about the same time as with Nagling enabled.
TEST_F(PseudoTcpTest, TestSendWithLossAndOptNaglingOff) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetLoss(10);
  SetOptNagling(false);
  TestTransfer(100000);  // less data so test runs faster
}

// Regression test for bugs.webrtc.org/9208.
//
// This bug resulted in corrupted data if a "connect" segment was received after
// a data segment. This is only possible if:
//
// * The initial "connect" segment is lost, and retransmitted later.
// * Both sides send "connect"s simultaneously, such that the local side thinks
//   a connection is established even before its "connect" has been
//   acknowledged.
// * Nagle algorithm disabled, allowing a data segment to be sent before the
//   "connect" has been acknowledged.
TEST_F(PseudoTcpTest,
       TestSendWhenFirstPacketLostWithOptNaglingOffAndSimultaneousOpen) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  DropNextPacket();
  SetOptNagling(false);
  SetSimultaneousOpen(true);
  TestTransfer(10000);
}

// Test sending data with 10% packet loss and Delayed ACK disabled.
// Transmission should be slightly faster than with it enabled.
TEST_F(PseudoTcpTest, TestSendWithLossAndOptAckDelayOff) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetLoss(10);
  SetOptAckDelay(0);
  TestTransfer(100000);
}

// Test sending data with 50ms delay and Nagling disabled.
TEST_F(PseudoTcpTest, TestSendWithDelayAndOptNaglingOff) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetDelay(50);
  SetOptNagling(false);
  TestTransfer(100000);  // less data so test runs faster
}

// Test sending data with 50ms delay and Delayed ACK disabled.
TEST_F(PseudoTcpTest, TestSendWithDelayAndOptAckDelayOff) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetDelay(50);
  SetOptAckDelay(0);
  TestTransfer(100000);  // less data so test runs faster
}

// Test a large receive buffer with a sender that doesn't support scaling.
TEST_F(PseudoTcpTest, TestSendRemoteNoWindowScale) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetLocalOptRcvBuf(100000);
  DisableRemoteWindowScale();
  TestTransfer(1000000);
}

// Test a large sender-side receive buffer with a receiver that doesn't support
// scaling.
TEST_F(PseudoTcpTest, TestSendLocalNoWindowScale) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetRemoteOptRcvBuf(100000);
  DisableLocalWindowScale();
  TestTransfer(1000000);
}

// Test when both sides use window scaling.
TEST_F(PseudoTcpTest, TestSendBothUseWindowScale) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetRemoteOptRcvBuf(100000);
  SetLocalOptRcvBuf(100000);
  TestTransfer(1000000);
}

// Test using a large window scale value.
TEST_F(PseudoTcpTest, TestSendLargeInFlight) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetRemoteOptRcvBuf(100000);
  SetLocalOptRcvBuf(100000);
  SetOptSndBuf(150000);
  TestTransfer(1000000);
}

TEST_F(PseudoTcpTest, TestSendBothUseLargeWindowScale) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetRemoteOptRcvBuf(1000000);
  SetLocalOptRcvBuf(1000000);
  TestTransfer(10000000);
}

// Test using a small receive buffer.
TEST_F(PseudoTcpTest, TestSendSmallReceiveBuffer) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetRemoteOptRcvBuf(10000);
  SetLocalOptRcvBuf(10000);
  TestTransfer(1000000);
}

// Test using a very small receive buffer.
TEST_F(PseudoTcpTest, TestSendVerySmallReceiveBuffer) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetRemoteOptRcvBuf(100);
  SetLocalOptRcvBuf(100);
  TestTransfer(100000);
}

// Ping-pong (request/response) tests

// Test sending <= 1x MTU of data in each ping/pong.  Should take <10ms.
TEST_F(PseudoTcpTestPingPong, TestPingPong1xMtu) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  TestPingPong(100, 100);
}

// Test sending 2x-3x MTU of data in each ping/pong.  Should take <10ms.
TEST_F(PseudoTcpTestPingPong, TestPingPong3xMtu) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  TestPingPong(400, 100);
}

// Test sending 1x-2x MTU of data in each ping/pong.
// Should take ~1s, due to interaction between Nagling and Delayed ACK.
TEST_F(PseudoTcpTestPingPong, TestPingPong2xMtu) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  TestPingPong(2000, 5);
}

// Test sending 1x-2x MTU of data in each ping/pong with Delayed ACK off.
// Should take <10ms.
TEST_F(PseudoTcpTestPingPong, TestPingPong2xMtuWithAckDelayOff) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetOptAckDelay(0);
  TestPingPong(2000, 100);
}

// Test sending 1x-2x MTU of data in each ping/pong with Nagling off.
// Should take <10ms.
TEST_F(PseudoTcpTestPingPong, TestPingPong2xMtuWithNaglingOff) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetOptNagling(false);
  TestPingPong(2000, 5);
}

// Test sending a ping as pair of short (non-full) segments.
// Should take ~1s, due to Delayed ACK interaction with Nagling.
TEST_F(PseudoTcpTestPingPong, TestPingPongShortSegments) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetOptAckDelay(5000);
  SetBytesPerSend(50);  // i.e. two Send calls per payload
  TestPingPong(100, 5);
}

// Test sending ping as a pair of short (non-full) segments, with Nagling off.
// Should take <10ms.
TEST_F(PseudoTcpTestPingPong, TestPingPongShortSegmentsWithNaglingOff) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetOptNagling(false);
  SetBytesPerSend(50);  // i.e. two Send calls per payload
  TestPingPong(100, 5);
}

// Test sending <= 1x MTU of data ping/pong, in two segments, no Delayed ACK.
// Should take ~1s.
TEST_F(PseudoTcpTestPingPong, TestPingPongShortSegmentsWithAckDelayOff) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetBytesPerSend(50);  // i.e. two Send calls per payload
  SetOptAckDelay(0);
  TestPingPong(100, 5);
}

// Test that receive window expands and contract correctly.
TEST_F(PseudoTcpTestReceiveWindow, TestReceiveWindow) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetOptNagling(false);
  SetOptAckDelay(0);
  TestTransfer(1024 * 1000);
}

// Test setting send window size to a very small value.
TEST_F(PseudoTcpTestReceiveWindow, TestSetVerySmallSendWindowSize) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetOptNagling(false);
  SetOptAckDelay(0);
  SetOptSndBuf(900);
  TestTransfer(1024 * 1000);
  EXPECT_EQ(900u, EstimateSendWindowSize());
}

// Test setting receive window size to a value other than default.
TEST_F(PseudoTcpTestReceiveWindow, TestSetReceiveWindowSize) {
  SetLocalMtu(1500);
  SetRemoteMtu(1500);
  SetOptNagling(false);
  SetOptAckDelay(0);
  SetRemoteOptRcvBuf(100000);
  SetLocalOptRcvBuf(100000);
  TestTransfer(1024 * 1000);
  EXPECT_EQ(100000u, EstimateReceiveWindowSize());
}

/* Test sending data with mismatched MTUs. We should detect this and reduce
// our packet size accordingly.
// TODO(?): This doesn't actually work right now. The current code
// doesn't detect if the MTU is set too high on either side.
TEST_F(PseudoTcpTest, TestSendWithMismatchedMtus) {
  SetLocalMtu(1500);
  SetRemoteMtu(1280);
  TestTransfer(1000000);
}
*/
