28100cb388
BUG=N/A TBR=niklas.enbom@webrtc.org Review URL: https://webrtc-codereview.appspot.com/29829004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@7472 4adac7df-926f-26a2-2b94-8c16560cd09d
859 lines
26 KiB
C++
859 lines
26 KiB
C++
/*
|
|
* libjingle
|
|
* Copyright 2011 Google Inc.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
* 3. The name of the author may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <vector>
|
|
|
|
#include "talk/p2p/base/pseudotcp.h"
|
|
#include "webrtc/base/gunit.h"
|
|
#include "webrtc/base/helpers.h"
|
|
#include "webrtc/base/messagehandler.h"
|
|
#include "webrtc/base/stream.h"
|
|
#include "webrtc/base/thread.h"
|
|
#include "webrtc/base/timeutils.h"
|
|
|
|
using cricket::PseudoTcp;
|
|
|
|
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 conv)
|
|
: PseudoTcp(notify, conv) {
|
|
}
|
|
|
|
bool isReceiveBufferFull() const {
|
|
return PseudoTcp::isReceiveBufferFull();
|
|
}
|
|
|
|
void disableWindowScale() {
|
|
PseudoTcp::disableWindowScale();
|
|
}
|
|
};
|
|
|
|
class PseudoTcpTestBase : public testing::Test,
|
|
public rtc::MessageHandler,
|
|
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.
|
|
rtc::SetRandomTestMode(true);
|
|
}
|
|
~PseudoTcpTestBase() {
|
|
// Put it back for the next test.
|
|
rtc::SetRandomTestMode(false);
|
|
}
|
|
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;
|
|
}
|
|
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();
|
|
}
|
|
return ret;
|
|
}
|
|
void Close() {
|
|
local_.Close(false);
|
|
UpdateLocalClock();
|
|
}
|
|
|
|
enum { MSG_LPACKET, MSG_RPACKET, MSG_LCLOCK, MSG_RCLOCK, MSG_IOCOMPLETE,
|
|
MSG_WRITE};
|
|
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.
|
|
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 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.
|
|
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) {
|
|
// Randomly drop the desired percentage of packets.
|
|
// Also drop packets that are larger than the configured MTU.
|
|
if (rtc::CreateRandomId() % 100 < static_cast<uint32>(loss_)) {
|
|
LOG(LS_VERBOSE) << "Randomly dropping packet, size=" << len;
|
|
} else if (len > static_cast<size_t>(
|
|
rtc::_min(local_mtu_, remote_mtu_))) {
|
|
LOG(LS_VERBOSE) << "Dropping packet that exceeds path MTU, size=" << len;
|
|
} else {
|
|
int id = (tcp == &local_) ? MSG_RPACKET : MSG_LPACKET;
|
|
std::string packet(buffer, len);
|
|
rtc::Thread::Current()->PostDelayed(delay_, this, id,
|
|
rtc::WrapMessageData(packet));
|
|
}
|
|
return WR_SUCCESS;
|
|
}
|
|
|
|
void UpdateLocalClock() { UpdateClock(&local_, MSG_LCLOCK); }
|
|
void UpdateRemoteClock() { UpdateClock(&remote_, MSG_RCLOCK); }
|
|
void UpdateClock(PseudoTcp* tcp, uint32 message) {
|
|
long interval = 0; // NOLINT
|
|
tcp->GetNextClock(PseudoTcp::Now(), interval);
|
|
interval = rtc::_max<int>(interval, 0L); // sometimes interval is < 0
|
|
rtc::Thread::Current()->Clear(this, message);
|
|
rtc::Thread::Current()->PostDelayed(interval, this, message);
|
|
}
|
|
|
|
virtual void OnMessage(rtc::Message* message) {
|
|
switch (message->message_id) {
|
|
case MSG_LPACKET: {
|
|
const std::string& s(
|
|
rtc::UseMessageData<std::string>(message->pdata));
|
|
local_.NotifyPacket(s.c_str(), s.size());
|
|
UpdateLocalClock();
|
|
break;
|
|
}
|
|
case MSG_RPACKET: {
|
|
const std::string& s(
|
|
rtc::UseMessageData<std::string>(message->pdata));
|
|
remote_.NotifyPacket(s.c_str(), s.size());
|
|
UpdateRemoteClock();
|
|
break;
|
|
}
|
|
case MSG_LCLOCK:
|
|
local_.NotifyClock(PseudoTcp::Now());
|
|
UpdateLocalClock();
|
|
break;
|
|
case MSG_RCLOCK:
|
|
remote_.NotifyClock(PseudoTcp::Now());
|
|
UpdateRemoteClock();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
delete message->pdata;
|
|
}
|
|
|
|
PseudoTcpForTest local_;
|
|
PseudoTcpForTest remote_;
|
|
rtc::MemoryStream send_stream_;
|
|
rtc::MemoryStream recv_stream_;
|
|
bool have_connected_;
|
|
bool have_disconnected_;
|
|
int local_mtu_;
|
|
int remote_mtu_;
|
|
int delay_;
|
|
int loss_;
|
|
};
|
|
|
|
class PseudoTcpTest : public PseudoTcpTestBase {
|
|
public:
|
|
void TestTransfer(int size) {
|
|
uint32 start, 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::Time();
|
|
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::TimeSince(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));
|
|
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_) {
|
|
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);
|
|
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);
|
|
LOG(LS_VERBOSE) << "Sent: " << position + sent;
|
|
} else {
|
|
send_stream_.SetPosition(position);
|
|
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 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::Time();
|
|
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);
|
|
LOG(LS_INFO) << "Performed " << iterations << " pings in "
|
|
<< elapsed << " ms";
|
|
}
|
|
|
|
private:
|
|
// IPseudoTcpNotify interface
|
|
|
|
virtual void OnTcpReadable(PseudoTcp* tcp) {
|
|
if (tcp != receiver_) {
|
|
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.
|
|
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);
|
|
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);
|
|
LOG(LS_VERBOSE) << "Sent: " << position + sent;
|
|
} else {
|
|
send_stream_.SetPosition(position);
|
|
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);
|
|
|
|
rtc::Thread::Current()->Post(this, MSG_WRITE);
|
|
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]);
|
|
}
|
|
|
|
virtual void OnMessage(rtc::Message* message) {
|
|
int message_id = message->message_id;
|
|
PseudoTcpTestBase::OnMessage(message);
|
|
|
|
switch (message_id) {
|
|
case MSG_WRITE: {
|
|
WriteData();
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint32 EstimateReceiveWindowSize() const {
|
|
return static_cast<uint32>(recv_position_[0]);
|
|
}
|
|
|
|
uint32 EstimateSendWindowSize() const {
|
|
return static_cast<uint32>(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);
|
|
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);
|
|
LOG(LS_VERBOSE) << "Sent: " << position + sent;
|
|
} else {
|
|
send_stream_.SetPosition(position);
|
|
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.
|
|
|
|
int message_queue_size =
|
|
static_cast<int>(rtc::Thread::Current()->size());
|
|
// The message queue will always have at least 2 messages, an RCLOCK and
|
|
// an LCLOCK, since they are added back on the delay queue at the same time
|
|
// they are pulled off and therefore are never really removed.
|
|
if (message_queue_size > 2) {
|
|
// If there are non-clock messages remaining, attempt to continue sending
|
|
// after giving those messages time to process, which should free up the
|
|
// send buffer.
|
|
rtc::Thread::Current()->PostDelayed(10, this, MSG_WRITE);
|
|
} else {
|
|
if (!remote_.isReceiveBufferFull()) {
|
|
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
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
*/
|