blob: 538ef05e340edf60057c560a50c8d9444ac4b748 [file] [log] [blame]
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/bind.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/path_service.h"
#include "base/time.h"
#include "base/test/test_timeouts.h"
#include "crypto/nss_util.h"
#include "net/base/completion_callback.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/socket/socket.h"
#include "remoting/protocol/jingle_session.h"
#include "remoting/protocol/jingle_session_manager.h"
#include "remoting/jingle_glue/jingle_thread.h"
#include "remoting/jingle_glue/fake_signal_strategy.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/libjingle/source/talk/p2p/client/basicportallocator.h"
using testing::_;
using testing::DeleteArg;
using testing::DoAll;
using testing::Invoke;
using testing::InvokeWithoutArgs;
using testing::Return;
using testing::SaveArg;
using testing::SetArgumentPointee;
using testing::WithArg;
namespace remoting {
namespace protocol {
class JingleSessionTest;
} // namespace protocol
} // namespace remoting
DISABLE_RUNNABLE_METHOD_REFCOUNT(remoting::protocol::JingleSessionTest);
namespace remoting {
namespace protocol {
namespace {
// Send 100 messages 1024 bytes each. UDP messages are sent with 10ms delay
// between messages (about 1 second for 100 messages).
const int kMessageSize = 1024;
const int kMessages = 100;
const int kTestDataSize = kMessages * kMessageSize;
const int kUdpWriteDelayMs = 10;
const char kTestToken[] = "a_dummy_token";
const char kHostJid[] = "[email protected]/123";
const char kClientJid[] = "[email protected]/321";
const char kTestHostPublicKey[] =
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3nk/8ILc0JBqHgOS0UCOIl4m"
"0GUd2FIiZ/6Fc9D/iiyUgli+FIY5dwsrSoNJ87sYGifVDh8a5fdZNV5y58CcrapI5fJI"
"FpXviSW4g8d/t1gcZkoz1ppmjzbgXm6ckw9Td0yRD0cHu732Ijs+eo8wT0pt4KiHkbyR"
"iAvjrvkNDlfiEk7tiY7YzD9zTi3146GX6KLz5GQAd/3I8I5QW3ftF1s/m93AHuc383GZ"
"A78Oi+IbcJf/jJUZO119VNnRKGiPsf5GZIoHyXX8O5OUQk5soKdQPeK1FwWkeZu6fuXl"
"QoU12I6podD6xMFa/PA/xefMwcpmuWTRhcso9bp10zVFGQIDAQAB";
void QuitCurrentThread() {
MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask());
}
void OnTimeoutTerminateThread(bool* timeout) {
*timeout = true;
QuitCurrentThread();
}
bool RunMessageLoopWithTimeout(int timeout_ms) {
bool timeout = false;
MessageLoop::current()->PostDelayedTask(
FROM_HERE, base::Bind(OnTimeoutTerminateThread, &timeout), timeout_ms);
MessageLoop::current()->Run();
return !timeout;
}
ACTION_P(QuitThreadOnCounter, counter) {
(*counter)--;
EXPECT_GE(*counter, 0);
if (*counter == 0)
QuitCurrentThread();
}
class MockSessionManagerCallback {
public:
MOCK_METHOD2(OnIncomingSession,
void(Session*,
SessionManager::IncomingSessionResponse*));
};
class MockSessionCallback {
public:
MOCK_METHOD1(OnStateChange, void(Session::State));
};
} // namespace
class JingleSessionTest : public testing::Test {
public:
JingleSessionTest()
: message_loop_(talk_base::Thread::Current()) {
}
// Helper method to copy to set value of client_connection_.
void SetHostSession(Session* session) {
DCHECK(session);
host_session_.reset(session);
host_session_->SetStateChangeCallback(
NewCallback(&host_connection_callback_,
&MockSessionCallback::OnStateChange));
session->set_config(SessionConfig::CreateDefault());
}
protected:
virtual void SetUp() {
}
virtual void TearDown() {
CloseSessions();
CloseSessionManager();
}
void CreateServerPair() {
// Sessions must be initialized on the jingle thread.
DoCreateServerPair();
}
void CloseSessions() {
if (host_session_.get()) {
host_session_->Close();
host_session_.reset();
}
if (client_session_.get()) {
client_session_->Close();
client_session_.reset();
}
}
void DoCreateServerPair() {
FilePath certs_dir;
PathService::Get(base::DIR_SOURCE_ROOT, &certs_dir);
certs_dir = certs_dir.AppendASCII("net");
certs_dir = certs_dir.AppendASCII("data");
certs_dir = certs_dir.AppendASCII("ssl");
certs_dir = certs_dir.AppendASCII("certificates");
FilePath cert_path = certs_dir.AppendASCII("unittest.selfsigned.der");
std::string cert_der;
ASSERT_TRUE(file_util::ReadFileToString(cert_path, &cert_der));
FilePath key_path = certs_dir.AppendASCII("unittest.key.bin");
std::string key_string;
ASSERT_TRUE(file_util::ReadFileToString(key_path, &key_string));
std::vector<uint8> key_vector(
reinterpret_cast<const uint8*>(key_string.data()),
reinterpret_cast<const uint8*>(key_string.data() +
key_string.length()));
scoped_ptr<crypto::RSAPrivateKey> private_key(
crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(key_vector));
host_signal_strategy_.reset(new FakeSignalStrategy(kHostJid));
client_signal_strategy_.reset(new FakeSignalStrategy(kClientJid));
FakeSignalStrategy::Connect(host_signal_strategy_.get(),
client_signal_strategy_.get());
host_server_.reset(JingleSessionManager::CreateNotSandboxed());
host_server_->set_allow_local_ips(true);
host_server_->Init(
kHostJid, host_signal_strategy_.get(),
NewCallback(&host_server_callback_,
&MockSessionManagerCallback::OnIncomingSession),
private_key.release(),
cert_der);
client_server_.reset(JingleSessionManager::CreateNotSandboxed());
client_server_->set_allow_local_ips(true);
client_server_->Init(
kClientJid, client_signal_strategy_.get(),
NewCallback(&client_server_callback_,
&MockSessionManagerCallback::OnIncomingSession),
NULL, "");
}
void CloseSessionManager() {
if (host_server_.get()) {
host_server_->Close();
host_server_.reset();
}
if (client_server_.get()) {
client_server_->Close();
client_server_.reset();
}
host_signal_strategy_.reset();
client_signal_strategy_.reset();
}
bool InitiateConnection() {
int not_connected_peers = 2;
EXPECT_CALL(host_server_callback_, OnIncomingSession(_, _))
.WillOnce(DoAll(
WithArg<0>(Invoke(
this, &JingleSessionTest::SetHostSession)),
SetArgumentPointee<1>(protocol::SessionManager::ACCEPT)));
base::WaitableEvent host_connected_event(true, false);
EXPECT_CALL(host_connection_callback_,
OnStateChange(Session::CONNECTING))
.Times(1);
EXPECT_CALL(host_connection_callback_,
OnStateChange(Session::CONNECTED))
.Times(1)
.WillOnce(QuitThreadOnCounter(&not_connected_peers));
// Expect that the connection will be closed eventually.
EXPECT_CALL(host_connection_callback_,
OnStateChange(Session::CLOSED))
.Times(1);
base::WaitableEvent client_connected_event(true, false);
EXPECT_CALL(client_connection_callback_,
OnStateChange(Session::CONNECTING))
.Times(1);
EXPECT_CALL(client_connection_callback_,
OnStateChange(Session::CONNECTED))
.Times(1)
.WillOnce(QuitThreadOnCounter(&not_connected_peers));
// Expect that the connection will be closed eventually.
EXPECT_CALL(client_connection_callback_,
OnStateChange(Session::CLOSED))
.Times(1);
client_session_.reset(client_server_->Connect(
kHostJid, kTestHostPublicKey, kTestToken,
CandidateSessionConfig::CreateDefault(),
NewCallback(&client_connection_callback_,
&MockSessionCallback::OnStateChange)));
return RunMessageLoopWithTimeout(TestTimeouts::action_max_timeout_ms());
}
static void DoNothing() { }
JingleThreadMessageLoop message_loop_;
scoped_ptr<FakeSignalStrategy> host_signal_strategy_;
scoped_ptr<FakeSignalStrategy> client_signal_strategy_;
scoped_ptr<JingleSessionManager> host_server_;
MockSessionManagerCallback host_server_callback_;
scoped_ptr<JingleSessionManager> client_server_;
MockSessionManagerCallback client_server_callback_;
scoped_ptr<Session> host_session_;
MockSessionCallback host_connection_callback_;
scoped_ptr<Session> client_session_;
MockSessionCallback client_connection_callback_;
};
class ChannelTesterBase : public base::RefCountedThreadSafe<ChannelTesterBase> {
public:
enum ChannelType {
CONTROL,
EVENT,
VIDEO,
VIDEO_RTP,
VIDEO_RTCP,
};
ChannelTesterBase(Session* host_session,
Session* client_session)
: host_session_(host_session),
client_session_(client_session),
done_(false) {
}
virtual ~ChannelTesterBase() { }
void Start(ChannelType channel) {
MessageLoop::current()->PostTask(
FROM_HERE, NewRunnableMethod(this, &ChannelTesterBase::DoStart,
channel));
}
bool WaitFinished() {
return RunMessageLoopWithTimeout(TestTimeouts::action_max_timeout_ms());
}
virtual void CheckResults() = 0;
protected:
void DoStart(ChannelType channel) {
socket_1_ = SelectChannel(host_session_, channel);
socket_2_ = SelectChannel(client_session_, channel);
InitBuffers();
DoRead();
DoWrite();
}
void Done() {
done_ = true;
MessageLoop::current()->PostTask(FROM_HERE, base::Bind(&QuitCurrentThread));
}
virtual void InitBuffers() = 0;
virtual void DoWrite() = 0;
virtual void DoRead() = 0;
net::Socket* SelectChannel(Session* session,
ChannelType channel) {
switch (channel) {
case CONTROL:
return session->control_channel();
case EVENT:
return session->event_channel();
case VIDEO:
return session->video_channel();
case VIDEO_RTP:
return session->video_rtp_channel();
case VIDEO_RTCP:
return session->video_rtcp_channel();
default:
NOTREACHED();
return NULL;
}
}
Session* host_session_;
Session* client_session_;
net::Socket* socket_1_;
net::Socket* socket_2_;
bool done_;
};
class TCPChannelTester : public ChannelTesterBase {
public:
TCPChannelTester(Session* host_session,
Session* client_session,
int message_size,
int message_count)
: ChannelTesterBase(host_session, client_session),
ALLOW_THIS_IN_INITIALIZER_LIST(
write_cb_(this, &TCPChannelTester::OnWritten)),
ALLOW_THIS_IN_INITIALIZER_LIST(
read_cb_(this, &TCPChannelTester::OnRead)),
write_errors_(0),
read_errors_(0),
message_size_(message_size),
message_count_(message_count),
test_data_size_(message_size * message_count) {
}
virtual ~TCPChannelTester() { }
virtual void CheckResults() {
EXPECT_EQ(0, write_errors_);
EXPECT_EQ(0, read_errors_);
ASSERT_EQ(test_data_size_, input_buffer_->offset());
output_buffer_->SetOffset(0);
ASSERT_EQ(test_data_size_, output_buffer_->size());
EXPECT_EQ(0, memcmp(output_buffer_->data(),
input_buffer_->StartOfBuffer(), test_data_size_));
}
protected:
virtual void InitBuffers() {
output_buffer_ = new net::DrainableIOBuffer(
new net::IOBuffer(test_data_size_), test_data_size_);
memset(output_buffer_->data(), 123, test_data_size_);
input_buffer_ = new net::GrowableIOBuffer();
}
virtual void DoWrite() {
int result = 1;
while (result > 0) {
if (output_buffer_->BytesRemaining() == 0)
break;
int bytes_to_write = std::min(output_buffer_->BytesRemaining(),
message_size_);
result = socket_1_->Write(output_buffer_, bytes_to_write, &write_cb_);
HandleWriteResult(result);
};
}
void OnWritten(int result) {
HandleWriteResult(result);
DoWrite();
}
void HandleWriteResult(int result) {
if (result <= 0 && result != net::ERR_IO_PENDING) {
LOG(ERROR) << "Received error " << result << " when trying to write";
write_errors_++;
Done();
} else if (result > 0) {
output_buffer_->DidConsume(result);
}
}
virtual void DoRead() {
int result = 1;
while (result > 0) {
input_buffer_->SetCapacity(input_buffer_->offset() + message_size_);
result = socket_2_->Read(input_buffer_, message_size_, &read_cb_);
HandleReadResult(result);
};
}
void OnRead(int result) {
HandleReadResult(result);
if (!done_)
DoRead(); // Don't try to read again when we are done reading.
}
void HandleReadResult(int result) {
if (result <= 0 && result != net::ERR_IO_PENDING) {
if (!done_) {
LOG(ERROR) << "Received error " << result << " when trying to read";
read_errors_++;
Done();
}
} else if (result > 0) {
// Allocate memory for the next read.
input_buffer_->set_offset(input_buffer_->offset() + result);
if (input_buffer_->offset() == test_data_size_)
Done();
}
}
scoped_refptr<net::DrainableIOBuffer> output_buffer_;
scoped_refptr<net::GrowableIOBuffer> input_buffer_;
net::CompletionCallbackImpl<TCPChannelTester> write_cb_;
net::CompletionCallbackImpl<TCPChannelTester> read_cb_;
int write_errors_;
int read_errors_;
int message_size_;
int message_count_;
int test_data_size_;
};
class ChannelSpeedTester : public TCPChannelTester {
public:
ChannelSpeedTester(Session* host_session,
Session* client_session,
int message_size)
: TCPChannelTester(host_session, client_session, message_size, 1) {
CHECK(message_size >= 8);
}
virtual ~ChannelSpeedTester() { }
virtual void CheckResults() {
}
base::TimeDelta GetElapsedTime() {
return base::Time::Now() - start_time_;
}
protected:
virtual void InitBuffers() {
TCPChannelTester::InitBuffers();
start_time_ = base::Time::Now();
}
base::Time start_time_;
};
class UDPChannelTester : public ChannelTesterBase {
public:
UDPChannelTester(Session* host_session,
Session* client_session)
: ChannelTesterBase(host_session, client_session),
ALLOW_THIS_IN_INITIALIZER_LIST(
write_cb_(this, &UDPChannelTester::OnWritten)),
ALLOW_THIS_IN_INITIALIZER_LIST(
read_cb_(this, &UDPChannelTester::OnRead)),
write_errors_(0),
read_errors_(0),
packets_sent_(0),
packets_received_(0),
broken_packets_(0) {
}
virtual ~UDPChannelTester() { }
virtual void CheckResults() {
EXPECT_EQ(0, write_errors_);
EXPECT_EQ(0, read_errors_);
EXPECT_EQ(0, broken_packets_);
// Verify that we've received at least one packet.
EXPECT_GT(packets_received_, 0);
LOG(INFO) << "Received " << packets_received_ << " packets out of "
<< kMessages;
}
protected:
virtual void InitBuffers() {
}
virtual void DoWrite() {
if (packets_sent_ >= kMessages) {
Done();
return;
}
scoped_refptr<net::IOBuffer> packet(new net::IOBuffer(kMessageSize));
memset(packet->data(), 123, kMessageSize);
sent_packets_[packets_sent_] = packet;
// Put index of this packet in the beginning of the packet body.
memcpy(packet->data(), &packets_sent_, sizeof(packets_sent_));
int result = socket_1_->Write(packet, kMessageSize, &write_cb_);
HandleWriteResult(result);
}
void OnWritten(int result) {
HandleWriteResult(result);
}
void HandleWriteResult(int result) {
if (result <= 0 && result != net::ERR_IO_PENDING) {
LOG(ERROR) << "Received error " << result << " when trying to write";
write_errors_++;
Done();
} else if (result > 0) {
EXPECT_EQ(kMessageSize, result);
packets_sent_++;
MessageLoop::current()->PostDelayedTask(
FROM_HERE, NewRunnableMethod(this, &UDPChannelTester::DoWrite),
kUdpWriteDelayMs);
}
}
virtual void DoRead() {
int result = 1;
while (result > 0) {
int kReadSize = kMessageSize * 2;
read_buffer_ = new net::IOBuffer(kReadSize);
result = socket_2_->Read(read_buffer_, kReadSize, &read_cb_);
HandleReadResult(result);
};
}
void OnRead(int result) {
HandleReadResult(result);
DoRead();
}
void HandleReadResult(int result) {
if (result <= 0 && result != net::ERR_IO_PENDING) {
// Error will be received after the socket is closed.
if (!done_) {
LOG(ERROR) << "Received error " << result << " when trying to read";
read_errors_++;
Done();
}
} else if (result > 0) {
packets_received_++;
if (kMessageSize != result) {
// Invalid packet size;
broken_packets_++;
} else {
// Validate packet body.
int packet_id;
memcpy(&packet_id, read_buffer_->data(), sizeof(packet_id));
if (packet_id < 0 || packet_id >= kMessages) {
broken_packets_++;
} else {
if (memcmp(read_buffer_->data(), sent_packets_[packet_id]->data(),
kMessageSize) != 0)
broken_packets_++;
}
}
}
}
private:
scoped_refptr<net::IOBuffer> sent_packets_[kMessages];
scoped_refptr<net::IOBuffer> read_buffer_;
net::CompletionCallbackImpl<UDPChannelTester> write_cb_;
net::CompletionCallbackImpl<UDPChannelTester> read_cb_;
int write_errors_;
int read_errors_;
int packets_sent_;
int packets_received_;
int broken_packets_;
};
// Verify that we can create and destory server objects without a connection.
TEST_F(JingleSessionTest, CreateAndDestoy) {
CreateServerPair();
}
// Verify that incoming session can be rejected, and that the status
// of the connection is set to CLOSED in this case.
TEST_F(JingleSessionTest, RejectConnection) {
CreateServerPair();
// Reject incoming session.
EXPECT_CALL(host_server_callback_, OnIncomingSession(_, _))
.WillOnce(SetArgumentPointee<1>(protocol::SessionManager::DECLINE));
EXPECT_CALL(client_connection_callback_,
OnStateChange(Session::CONNECTING))
.Times(1);
EXPECT_CALL(client_connection_callback_,
OnStateChange(Session::CLOSED))
.Times(1)
.WillOnce(InvokeWithoutArgs(&QuitCurrentThread));
client_session_.reset(client_server_->Connect(
kHostJid, kTestHostPublicKey, kTestToken,
CandidateSessionConfig::CreateDefault(),
NewCallback(&client_connection_callback_,
&MockSessionCallback::OnStateChange)));
ASSERT_TRUE(RunMessageLoopWithTimeout(TestTimeouts::action_max_timeout_ms()));
}
// Verify that we can connect two endpoints.
TEST_F(JingleSessionTest, Connect) {
CreateServerPair();
ASSERT_TRUE(InitiateConnection());
}
// Verify that data can be transmitted over the event channel.
TEST_F(JingleSessionTest, TestControlChannel) {
CreateServerPair();
ASSERT_TRUE(InitiateConnection());
scoped_refptr<TCPChannelTester> tester(
new TCPChannelTester(host_session_.get(), client_session_.get(),
kMessageSize, kMessages));
tester->Start(ChannelTesterBase::CONTROL);
ASSERT_TRUE(tester->WaitFinished());
tester->CheckResults();
// Connections must be closed while |tester| still exists.
CloseSessions();
}
// Verify that data can be transmitted over the video channel.
TEST_F(JingleSessionTest, TestVideoChannel) {
CreateServerPair();
ASSERT_TRUE(InitiateConnection());
scoped_refptr<TCPChannelTester> tester(
new TCPChannelTester(host_session_.get(), client_session_.get(),
kMessageSize, kMessageSize));
tester->Start(ChannelTesterBase::VIDEO);
ASSERT_TRUE(tester->WaitFinished());
tester->CheckResults();
// Connections must be closed while |tester| still exists.
CloseSessions();
}
// Verify that data can be transmitted over the event channel.
TEST_F(JingleSessionTest, TestEventChannel) {
CreateServerPair();
ASSERT_TRUE(InitiateConnection());
scoped_refptr<TCPChannelTester> tester(
new TCPChannelTester(host_session_.get(), client_session_.get(),
kMessageSize, kMessageSize));
tester->Start(ChannelTesterBase::EVENT);
ASSERT_TRUE(tester->WaitFinished());
tester->CheckResults();
// Connections must be closed while |tester| still exists.
CloseSessions();
}
// Verify that data can be transmitted over the video RTP channel.
TEST_F(JingleSessionTest, TestVideoRtpChannel) {
CreateServerPair();
ASSERT_TRUE(InitiateConnection());
scoped_refptr<UDPChannelTester> tester(
new UDPChannelTester(host_session_.get(), client_session_.get()));
tester->Start(ChannelTesterBase::VIDEO_RTP);
ASSERT_TRUE(tester->WaitFinished());
tester->CheckResults();
// Connections must be closed while |tester| still exists.
CloseSessions();
}
// Send packets of different size to get the latency for sending data
// using sockets from JingleSession.
TEST_F(JingleSessionTest, DISABLED_TestSpeed) {
CreateServerPair();
ASSERT_TRUE(InitiateConnection());
scoped_refptr<ChannelSpeedTester> tester;
tester = new ChannelSpeedTester(host_session_.get(),
client_session_.get(), 512);
tester->Start(ChannelTesterBase::VIDEO);
ASSERT_TRUE(tester->WaitFinished());
LOG(INFO) << "Time for 512 bytes "
<< tester->GetElapsedTime().InMilliseconds() << " ms.";
tester = new ChannelSpeedTester(host_session_.get(),
client_session_.get(), 1024);
tester->Start(ChannelTesterBase::VIDEO);
ASSERT_TRUE(tester->WaitFinished());
LOG(INFO) << "Time for 1024 bytes "
<< tester->GetElapsedTime().InMilliseconds() << " ms.";
tester = new ChannelSpeedTester(host_session_.get(),
client_session_.get(), 51200);
tester->Start(ChannelTesterBase::VIDEO);
ASSERT_TRUE(tester->WaitFinished());
LOG(INFO) << "Time for 50k bytes "
<< tester->GetElapsedTime().InMilliseconds() << " ms.";
tester = new ChannelSpeedTester(host_session_.get(),
client_session_.get(), 512000);
tester->Start(ChannelTesterBase::VIDEO);
ASSERT_TRUE(tester->WaitFinished());
LOG(INFO) << "Time for 500k bytes "
<< tester->GetElapsedTime().InMilliseconds() << " ms.";
// Connections must be closed while |tester| still exists.
CloseSessions();
}
} // namespace protocol
} // namespace remoting