blob: a76d5abe7a70e6ce7e9894372925ca3daecc759c [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert to safer constructs.
// This test code was moved from WebRTC which predates unsafe buffer checks.
// The unsafe buffer usage is in existing test utilities that directly
// manipulate network packets for testing the TCP protocol implementation.
#pragma allow_unsafe_buffers
#endif
#include "remoting/protocol/pseudo_tcp.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <string>
#include <utility>
#include <vector>
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/rand_util.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
// Remove unused using declaration
using ::remoting::protocol::PseudoTcp;
static const int kConnectTimeoutMs =
60000; // Much higher timeout for mock time
static const int kTransferTimeoutMs = 240000; // Increased for tests with loss
static const int kBlockSize = 4096;
class PseudoTcpForTest : public remoting::protocol::PseudoTcp {
public:
PseudoTcpForTest(remoting::protocol::IPseudoTcpNotify* notify, uint32_t conv)
: remoting::protocol::PseudoTcp(notify, conv) {}
bool isReceiveBufferFull() const {
return remoting::protocol::PseudoTcp::isReceiveBufferFull();
}
void disableWindowScale() {
remoting::protocol::PseudoTcp::disableWindowScale();
}
};
class PseudoTcpTestBase : public ::testing::Test,
public remoting::protocol::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.
// Note: WebRTC's SetRandomTestMode doesn't exist in Chromium, so we rely
// on base::RandUint64() for randomness which is already suitable for
// testing.
}
~PseudoTcpTestBase() override = default;
// 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();
}
void OnTcpOpen(PseudoTcp* tcp) override {
// Consider ourselves connected when the local side gets OnTcpOpen.
// OnTcpWriteable isn't fired at open, so we trigger it now.
VLOG(1) << "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)
void OnTcpClosed(PseudoTcp* tcp, uint32_t error) override {
// 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.
VLOG(1) << "Closed";
EXPECT_EQ(0U, error);
if (tcp == &remote_) {
have_disconnected_ = true;
if (transfer_complete_callback_) {
std::move(transfer_complete_callback_).Run();
}
}
}
WriteResult TcpWritePacket(PseudoTcp* tcp,
const char* buffer,
size_t len) override {
// Drop a packet if the test called DropNextPacket.
if (drop_next_packet_) {
drop_next_packet_ = false;
VLOG(1) << "Dropping packet due to DropNextPacket, size=" << len;
return WR_SUCCESS;
}
// Randomly drop the desired percentage of packets.
if (base::RandUint64() % 100 < static_cast<uint32_t>(loss_)) {
VLOG(1) << "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_))) {
VLOG(1) << "Dropping packet that exceeds path MTU, size=" << len;
return WR_SUCCESS;
}
PseudoTcp* other;
if (tcp == &local_) {
other = &remote_;
} else {
other = &local_;
}
std::string packet(buffer, len);
++packets_in_flight_;
// Post delayed task using Chromium's task scheduling
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(
[](PseudoTcp* other, std::string packet, PseudoTcpTestBase* test) {
--test->packets_in_flight_;
other->NotifyPacket(packet.c_str(), packet.size());
test->UpdateClock(*other);
},
other, std::move(packet), this),
base::Milliseconds(delay_));
return WR_SUCCESS;
}
void UpdateLocalClock() { local_.NotifyClock(PseudoTcp::Now()); }
void UpdateRemoteClock() { remote_.NotifyClock(PseudoTcp::Now()); }
void UpdateClock(PseudoTcp& tcp) { tcp.NotifyClock(PseudoTcp::Now()); }
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
PseudoTcpForTest local_;
PseudoTcpForTest remote_;
std::vector<uint8_t> send_buffer_;
std::vector<uint8_t> recv_buffer_;
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;
base::OnceClosure transfer_complete_callback_;
};
class PseudoTcpTest : public PseudoTcpTestBase {
public:
void TestTransfer(int size) {
base::TimeTicks start;
base::TimeDelta elapsed;
size_t received;
// Create some dummy data to send.
send_buffer_.reserve(size);
for (int i = 0; i < size; ++i) {
send_buffer_.push_back(static_cast<uint8_t>(i));
}
send_stream_pos_ = 0;
// Prepare the receive buffer.
recv_buffer_.reserve(size);
// Connect and wait until connected.
start = base::TimeTicks::Now();
EXPECT_EQ(0, Connect());
// Wait for connection - use bounded loop instead of time-based
for (int i = 0; i < kConnectTimeoutMs / 100 && !have_connected_; i++) {
UpdateLocalClock();
UpdateRemoteClock();
task_environment_.FastForwardBy(base::Milliseconds(100));
if (i % 100 == 0) {
VLOG(1) << "Connect attempt " << i
<< ", connected: " << have_connected_;
}
}
EXPECT_TRUE(have_connected_)
<< "Failed to connect after " << kConnectTimeoutMs << "ms";
// Sending will start from OnTcpWriteable and complete when all data has
// been received.
base::RunLoop transfer_loop;
transfer_complete_callback_ = transfer_loop.QuitClosure();
// Run transfer with periodic clock updates
for (int i = 0;
i < kTransferTimeoutMs / 100 &&
recv_buffer_.size() < send_buffer_.size() && !have_disconnected_;
i++) {
UpdateLocalClock();
UpdateRemoteClock();
task_environment_.FastForwardBy(base::Milliseconds(100));
if (i % 100 == 0) {
VLOG(1) << "Transfer attempt " << i << ", recv: " << recv_buffer_.size()
<< "/" << send_buffer_.size();
}
}
// If not yet complete, wait for completion with timeout
if (!have_disconnected_) {
// In mock time environment, we need to continue advancing time
// while waiting for completion
bool transfer_complete = false;
int timeout_iterations = kTransferTimeoutMs / 100;
for (int i = 0;
i < timeout_iterations && !have_disconnected_ && !transfer_complete;
i++) {
// More frequent clock updates for tests with loss
UpdateLocalClock();
UpdateRemoteClock();
task_environment_.FastForwardBy(base::Milliseconds(50));
UpdateLocalClock();
UpdateRemoteClock();
task_environment_.FastForwardBy(base::Milliseconds(50));
// Check if transfer is complete
if (recv_buffer_.size() == send_buffer_.size()) {
transfer_complete = true;
OnTcpClosed(&remote_, 0); // Manually trigger completion
}
if (i % 100 == 0) {
VLOG(1) << "Final transfer attempt " << i
<< ", recv: " << recv_buffer_.size() << "/"
<< send_buffer_.size();
}
}
}
EXPECT_EQ(recv_buffer_.size(), send_buffer_.size());
// Clean up connection
Close();
elapsed = base::TimeTicks::Now() - start;
received = recv_buffer_.size();
// 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(send_buffer_, recv_buffer_);
VLOG(1) << "Transferred " << received << " bytes in "
<< elapsed.InMilliseconds() << " ms ("
<< size * 8 / elapsed.InMilliseconds() << " Kbps)";
}
private:
// IPseudoTcpNotify interface
void OnTcpReadable(PseudoTcp* tcp) override {
// Stream bytes to the recv buffer 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 = recv_buffer_.size();
size_t required = send_buffer_.size();
if (received == required) {
OnTcpClosed(&remote_, 0);
}
}
}
void OnTcpWriteable(PseudoTcp* tcp) override {
// Write bytes from the send buffer when we can.
// Shut down when we've sent everything.
if (tcp == &local_) {
VLOG(1) << "Flow Control Lifted";
bool done;
WriteData(&done);
if (done) {
Close();
}
}
}
void ReadData() {
char block[kBlockSize];
int received;
do {
received = remote_.Recv(block, sizeof(block));
if (received != -1) {
recv_buffer_.insert(recv_buffer_.end(), block, block + received);
if (recv_buffer_.size() % 50000 == 0) {
VLOG(1) << "Received: " << recv_buffer_.size();
}
}
} while (received > 0);
}
void WriteData(bool* done) {
int sent;
char block[kBlockSize];
do {
size_t tosend = std::min(static_cast<size_t>(kBlockSize),
send_buffer_.size() - send_stream_pos_);
if (tosend > 0) {
memcpy(block, send_buffer_.data() + send_stream_pos_, tosend);
sent = local_.Send(block, tosend);
UpdateLocalClock();
if (sent != -1) {
send_stream_pos_ += sent;
if (send_stream_pos_ % 50000 == 0) {
VLOG(1) << "Sent: " << send_stream_pos_;
}
} else {
VLOG(1) << "Flow Controlled";
}
} else {
sent = 0;
tosend = 0;
}
} while (sent > 0);
*done = (send_stream_pos_ >= send_buffer_.size());
}
private:
size_t send_stream_pos_ = 0;
};
class PseudoTcpTestPingPong : public PseudoTcpTestBase {
public:
PseudoTcpTestPingPong()
: iterations_remaining_(0),
sender_(nullptr),
receiver_(nullptr),
bytes_per_send_(0) {}
void SetBytesPerSend(int bytes) { bytes_per_send_ = bytes; }
void TestPingPong(int size, int iterations) {
base::TimeTicks start;
base::TimeDelta elapsed;
iterations_remaining_ = iterations;
receiver_ = &remote_;
sender_ = &local_;
// Create some dummy data to send.
send_buffer_.reserve(size);
for (int i = 0; i < size; ++i) {
send_buffer_.push_back(static_cast<uint8_t>(i));
}
send_stream_pos_ = 0;
// Prepare the receive buffer.
recv_buffer_.reserve(size);
// Connect and wait until connected.
start = base::TimeTicks::Now();
EXPECT_EQ(0, Connect());
// Wait for connection
for (int i = 0; i < kConnectTimeoutMs / 100 && !have_connected_; i++) {
UpdateLocalClock();
UpdateRemoteClock();
task_environment_.FastForwardBy(base::Milliseconds(100));
}
EXPECT_TRUE(have_connected_);
// Sending will start from OnTcpWriteable and stop when the required
// number of iterations have completed.
base::RunLoop pingpong_loop;
transfer_complete_callback_ = pingpong_loop.QuitClosure();
for (int i = 0; i < kTransferTimeoutMs / 100 && !have_disconnected_ &&
iterations_remaining_ > 0;
i++) {
UpdateLocalClock();
UpdateRemoteClock();
task_environment_.FastForwardBy(base::Milliseconds(100));
if (i % 100 == 0) {
VLOG(1) << "PingPong attempt " << i
<< ", iterations_remaining: " << iterations_remaining_
<< ", have_disconnected: " << have_disconnected_;
}
}
// If not yet complete, wait for completion with timeout
if (!have_disconnected_ && iterations_remaining_ > 0) {
// Post a timeout task to quit the loop if transfer doesn't complete
auto timeout_callback = base::BindOnce(
[](base::RunLoop* loop) { loop->Quit(); }, &pingpong_loop);
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, std::move(timeout_callback),
base::Milliseconds(kTransferTimeoutMs));
pingpong_loop.Run();
// Check if we can complete anyway after timeout
if (!have_disconnected_ && iterations_remaining_ > 0) {
UpdateLocalClock();
UpdateRemoteClock();
}
}
EXPECT_TRUE(have_disconnected_ || iterations_remaining_ == 0);
elapsed = base::TimeTicks::Now() - start;
VLOG(1) << "Performed " << iterations << " pings in "
<< elapsed.InMilliseconds() << " ms";
}
private:
// IPseudoTcpNotify interface
void OnTcpReadable(PseudoTcp* tcp) override {
if (tcp != receiver_) {
LOG(ERROR) << "unexpected OnTcpReadable";
return;
}
// Stream bytes to the recv buffer 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 = recv_buffer_.size();
size_t desired = send_buffer_.size();
if (position == desired) {
if (receiver_ == &local_ && --iterations_remaining_ == 0) {
Close();
// TODO: Fake OnTcpClosed() on the receiver for now.
have_disconnected_ = true; // Set this directly for immediate detection
OnTcpClosed(&remote_, 0);
return;
}
PseudoTcp* tmp = receiver_;
receiver_ = sender_;
sender_ = tmp;
recv_buffer_.clear();
send_stream_pos_ = 0;
OnTcpWriteable(sender_);
}
}
void OnTcpWriteable(PseudoTcp* tcp) override {
if (tcp != sender_) {
return;
}
// Write bytes from the send buffer when we can.
// Shut down when we've sent everything.
VLOG(1) << "Flow Control Lifted";
WriteData();
}
void ReadData() {
char block[kBlockSize];
int received;
do {
received = receiver_->Recv(block, sizeof(block));
if (received != -1) {
recv_buffer_.insert(recv_buffer_.end(), block, block + received);
if (recv_buffer_.size() % 50000 == 0) {
VLOG(1) << "Received: " << recv_buffer_.size();
}
}
} while (received > 0);
}
void WriteData() {
int sent;
char block[kBlockSize];
do {
size_t tosend = bytes_per_send_
? std::min(static_cast<size_t>(bytes_per_send_),
send_buffer_.size() - send_stream_pos_)
: std::min(static_cast<size_t>(kBlockSize),
send_buffer_.size() - send_stream_pos_);
if (tosend > 0) {
memcpy(block, send_buffer_.data() + send_stream_pos_, tosend);
sent = sender_->Send(block, tosend);
UpdateLocalClock();
if (sent != -1) {
send_stream_pos_ += sent;
if (send_stream_pos_ % 50000 == 0) {
VLOG(1) << "Sent: " << send_stream_pos_;
}
} else {
// Flow control - position stays the same
VLOG(1) << "Flow Controlled";
}
} else {
sent = 0;
}
} while (sent > 0);
}
private:
int iterations_remaining_;
raw_ptr<PseudoTcp> sender_;
raw_ptr<PseudoTcp> receiver_;
int bytes_per_send_;
size_t send_stream_pos_ = 0;
};
// 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_buffer_.reserve(size);
for (int i = 0; i < size; ++i) {
send_buffer_.push_back(static_cast<uint8_t>(i));
}
send_stream_pos_ = 0;
// Prepare the receive buffer.
recv_buffer_.reserve(size);
// Connect and wait until connected.
EXPECT_EQ(0, Connect());
base::TimeTicks start = base::TimeTicks::Now();
while (!have_connected_ && (base::TimeTicks::Now() - start) <
base::Milliseconds(kConnectTimeoutMs)) {
task_environment_.FastForwardBy(base::Milliseconds(10));
}
EXPECT_TRUE(have_connected_);
WriteData();
for (int i = 0; i < kTransferTimeoutMs / 100 && !have_disconnected_; i++) {
task_environment_.FastForwardBy(base::Milliseconds(100));
}
EXPECT_TRUE(have_disconnected_);
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
void OnTcpReadable(PseudoTcp* /* tcp */) override {}
void OnTcpWriteable(PseudoTcp* /* tcp */) override {}
void ReadUntilIOPending() {
char block[kBlockSize];
int received;
do {
received = remote_.Recv(block, sizeof(block));
if (received != -1) {
recv_buffer_.insert(recv_buffer_.end(), block, block + received);
if (recv_buffer_.size() % 50000 == 0) {
VLOG(1) << "Received: " << recv_buffer_.size();
}
}
} while (received > 0);
size_t position = recv_buffer_.size();
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() {
int sent;
char block[kBlockSize];
do {
size_t tosend = std::min(static_cast<size_t>(kBlockSize),
send_buffer_.size() - send_stream_pos_);
if (tosend > 0) {
memcpy(block, send_buffer_.data() + send_stream_pos_, tosend);
sent = local_.Send(block, tosend);
UpdateLocalClock();
if (sent != -1) {
send_stream_pos_ += sent;
if (send_stream_pos_ % 50000 == 0) {
VLOG(1) << "Sent: " << send_stream_pos_;
}
} else {
VLOG(1) << "Flow Controlled";
}
} else {
sent = 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.
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&PseudoTcpTestReceiveWindow::WriteData,
base::Unretained(this)),
base::Milliseconds(10));
} else {
if (!remote_.isReceiveBufferFull()) {
LOG(ERROR) << "This shouldn't happen - the send buffer is full, "
"the receive buffer is not, and there are no "
"remaining messages to process.";
}
size_t position = send_stream_pos_;
send_position_.push_back(position);
// Drain the receiver buffer.
ReadUntilIOPending();
}
}
private:
size_t send_stream_pos_ = 0;
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);
}
*/