blob: 59659c395cc0cff4a8d92a034de366a857cd26a5 [file] [log] [blame]
// Copyright (c) 2012 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 "remoting/protocol/jingle_session.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/test/test_timeouts.h"
#include "base/time/time.h"
#include "net/socket/socket.h"
#include "net/socket/stream_socket.h"
#include "net/url_request/url_request_context_getter.h"
#include "remoting/base/constants.h"
#include "remoting/protocol/authenticator.h"
#include "remoting/protocol/channel_authenticator.h"
#include "remoting/protocol/chromium_port_allocator_factory.h"
#include "remoting/protocol/connection_tester.h"
#include "remoting/protocol/fake_authenticator.h"
#include "remoting/protocol/jingle_session_manager.h"
#include "remoting/protocol/network_settings.h"
#include "remoting/protocol/session_plugin.h"
#include "remoting/protocol/transport.h"
#include "remoting/protocol/transport_context.h"
#include "remoting/signaling/fake_signal_strategy.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/libjingle_xmpp/xmpp/constants.h"
using testing::_;
using testing::AtLeast;
using testing::AtMost;
using testing::DeleteArg;
using testing::DoAll;
using testing::InSequence;
using testing::Invoke;
using testing::InvokeWithoutArgs;
using testing::Return;
using testing::SaveArg;
using testing::SetArgPointee;
using testing::WithArg;
namespace remoting {
namespace protocol {
namespace {
const char kHostJid[] = "Host@gmail.com/123";
const char kClientJid[] = "Client@gmail.com/321";
// kHostJid the way it would be stored in the directory.
const char kNormalizedHostJid[] = "host@gmail.com/123";
class MockSessionManagerListener {
public:
MOCK_METHOD2(OnIncomingSession,
void(Session*,
SessionManager::IncomingSessionResponse*));
};
class MockSessionEventHandler : public Session::EventHandler {
public:
MOCK_METHOD1(OnSessionStateChange, void(Session::State));
MOCK_METHOD2(OnSessionRouteChange, void(const std::string& channel_name,
const TransportRoute& route));
};
class FakeTransport : public Transport {
public:
SendTransportInfoCallback send_transport_info_callback() {
return send_transport_info_callback_;
}
const std::vector<std::unique_ptr<buzz::XmlElement>>& received_messages() {
return received_messages_;
}
void set_on_message_callback(const base::Closure& on_message_callback) {
on_message_callback_ = on_message_callback;
}
// Transport interface.
void Start(Authenticator* authenticator,
SendTransportInfoCallback send_transport_info_callback) override {
send_transport_info_callback_ = send_transport_info_callback;
}
bool ProcessTransportInfo(buzz::XmlElement* transport_info) override {
received_messages_.push_back(
std::make_unique<buzz::XmlElement>(*transport_info));
if (!on_message_callback_.is_null())
on_message_callback_.Run();
return true;
}
private:
SendTransportInfoCallback send_transport_info_callback_;
std::vector<std::unique_ptr<buzz::XmlElement>> received_messages_;
base::Closure on_message_callback_;
};
class FakePlugin : public SessionPlugin {
public:
std::unique_ptr<buzz::XmlElement> GetNextMessage() override {
std::string tag_name = "test-tag-";
tag_name += base::IntToString(outgoing_messages_.size());
std::unique_ptr<buzz::XmlElement> new_message(new buzz::XmlElement(
buzz::QName("test-namespace", tag_name)));
outgoing_messages_.push_back(*new_message);
return new_message;
}
void OnIncomingMessage(const buzz::XmlElement& attachments) override {
for (const buzz::XmlElement* it = attachments.FirstElement();
it != nullptr;
it = it->NextElement()) {
incoming_messages_.push_back(*it);
}
}
const std::vector<buzz::XmlElement>& outgoing_messages() const {
return outgoing_messages_;
}
const std::vector<buzz::XmlElement>& incoming_messages() const {
return incoming_messages_;
}
void Clear() {
outgoing_messages_.clear();
incoming_messages_.clear();
}
private:
std::vector<buzz::XmlElement> outgoing_messages_;
std::vector<buzz::XmlElement> incoming_messages_;
};
std::unique_ptr<buzz::XmlElement> CreateTransportInfo(const std::string& id) {
std::unique_ptr<buzz::XmlElement> result(
buzz::XmlElement::ForStr("<transport xmlns='google:remoting:ice'/>"));
result->AddAttr(buzz::QN_ID, id);
return result;
}
} // namespace
class JingleSessionTest : public testing::Test {
public:
JingleSessionTest() {
message_loop_.reset(new base::MessageLoopForIO());
network_settings_ =
NetworkSettings(NetworkSettings::NAT_TRAVERSAL_OUTGOING);
}
// Helper method that handles OnIncomingSession().
void SetHostSession(Session* session) {
DCHECK(session);
host_session_.reset(session);
host_session_->SetEventHandler(&host_session_event_handler_);
host_session_->SetTransport(&host_transport_);
host_session_->AddPlugin(&host_plugin_);
}
void DeleteHostSession() { host_session_.reset(); }
void DeleteClientSession() { client_session_.reset(); }
protected:
void TearDown() override {
CloseSessions();
CloseSessionManager();
base::RunLoop().RunUntilIdle();
}
void CloseSessions() {
host_session_.reset();
client_session_.reset();
}
void CreateSessionManagers(FakeAuthenticator::Config auth_config,
int messages_till_start) {
host_signal_strategy_ =
std::make_unique<FakeSignalStrategy>(SignalingAddress(kHostJid));
client_signal_strategy_ =
std::make_unique<FakeSignalStrategy>(SignalingAddress(kClientJid));
FakeSignalStrategy::Connect(host_signal_strategy_.get(),
client_signal_strategy_.get());
host_server_.reset(new JingleSessionManager(host_signal_strategy_.get()));
host_server_->AcceptIncoming(
base::Bind(&MockSessionManagerListener::OnIncomingSession,
base::Unretained(&host_server_listener_)));
std::unique_ptr<AuthenticatorFactory> factory(
new FakeHostAuthenticatorFactory(messages_till_start, auth_config));
host_server_->set_authenticator_factory(std::move(factory));
client_server_.reset(
new JingleSessionManager(client_signal_strategy_.get()));
}
void CreateSessionManagers(FakeAuthenticator::Config auth_config) {
CreateSessionManagers(auth_config, 0);
}
void CloseSessionManager() {
host_server_.reset();
client_server_.reset();
host_signal_strategy_.reset();
client_signal_strategy_.reset();
}
void SetHostExpectation(bool expect_fail) {
EXPECT_CALL(host_server_listener_, OnIncomingSession(_, _))
.WillOnce(
DoAll(WithArg<0>(Invoke(this, &JingleSessionTest::SetHostSession)),
SetArgPointee<1>(protocol::SessionManager::ACCEPT)));
{
InSequence dummy;
EXPECT_CALL(host_session_event_handler_,
OnSessionStateChange(Session::ACCEPTED))
.Times(AtMost(1));
EXPECT_CALL(host_session_event_handler_,
OnSessionStateChange(Session::AUTHENTICATING))
.Times(AtMost(1));
if (expect_fail) {
EXPECT_CALL(host_session_event_handler_,
OnSessionStateChange(Session::FAILED))
.Times(1);
} else {
EXPECT_CALL(host_session_event_handler_,
OnSessionStateChange(Session::AUTHENTICATED))
.Times(1);
// Expect that the connection will be closed eventually.
EXPECT_CALL(host_session_event_handler_,
OnSessionStateChange(Session::CLOSED))
.Times(AtMost(1));
}
}
}
void SetClientExpectation(bool expect_fail) {
InSequence dummy;
EXPECT_CALL(client_session_event_handler_,
OnSessionStateChange(Session::ACCEPTED))
.Times(AtMost(1));
EXPECT_CALL(client_session_event_handler_,
OnSessionStateChange(Session::AUTHENTICATING))
.Times(AtMost(1));
if (expect_fail) {
EXPECT_CALL(client_session_event_handler_,
OnSessionStateChange(Session::FAILED))
.Times(1);
} else {
EXPECT_CALL(client_session_event_handler_,
OnSessionStateChange(Session::AUTHENTICATED))
.Times(1);
// Expect that the connection will be closed eventually.
EXPECT_CALL(client_session_event_handler_,
OnSessionStateChange(Session::CLOSED))
.Times(AtMost(1));
}
}
void ConnectClient(std::unique_ptr<Authenticator> authenticator) {
client_session_ = client_server_->Connect(
SignalingAddress(kNormalizedHostJid), std::move(authenticator));
client_session_->SetEventHandler(&client_session_event_handler_);
client_session_->SetTransport(&client_transport_);
client_session_->AddPlugin(&client_plugin_);
base::RunLoop().RunUntilIdle();
}
void ConnectClient(FakeAuthenticator::Config auth_config) {
ConnectClient(std::make_unique<FakeAuthenticator>(
FakeAuthenticator::CLIENT, auth_config,
client_signal_strategy_->GetLocalAddress().id(), kNormalizedHostJid));
}
void InitiateConnection(FakeAuthenticator::Config auth_config,
bool expect_fail) {
SetHostExpectation(expect_fail);
SetClientExpectation(expect_fail);
ConnectClient(auth_config);
}
void ExpectRouteChange(const std::string& channel_name) {
EXPECT_CALL(host_session_event_handler_,
OnSessionRouteChange(channel_name, _))
.Times(AtLeast(1));
EXPECT_CALL(client_session_event_handler_,
OnSessionRouteChange(channel_name, _))
.Times(AtLeast(1));
}
void ExpectPluginMessagesEqual() const {
ASSERT_EQ(client_plugin_.outgoing_messages().size(),
host_plugin_.incoming_messages().size());
for (size_t i = 0; i < client_plugin_.outgoing_messages().size(); i++) {
ASSERT_EQ(client_plugin_.outgoing_messages()[i].Str(),
host_plugin_.incoming_messages()[i].Str());
}
ASSERT_EQ(client_plugin_.incoming_messages().size(),
host_plugin_.outgoing_messages().size());
for (size_t i = 0; i < client_plugin_.incoming_messages().size(); i++) {
ASSERT_EQ(client_plugin_.incoming_messages()[i].Str(),
host_plugin_.outgoing_messages()[i].Str());
}
}
std::unique_ptr<base::MessageLoopForIO> message_loop_;
NetworkSettings network_settings_;
std::unique_ptr<FakeSignalStrategy> host_signal_strategy_;
std::unique_ptr<FakeSignalStrategy> client_signal_strategy_;
std::unique_ptr<JingleSessionManager> host_server_;
MockSessionManagerListener host_server_listener_;
std::unique_ptr<JingleSessionManager> client_server_;
std::unique_ptr<Session> host_session_;
MockSessionEventHandler host_session_event_handler_;
FakeTransport host_transport_;
std::unique_ptr<Session> client_session_;
MockSessionEventHandler client_session_event_handler_;
FakeTransport client_transport_;
FakePlugin host_plugin_;
FakePlugin client_plugin_;
};
// Verify that we can create and destroy session managers without a
// connection.
TEST_F(JingleSessionTest, CreateAndDestoy) {
CreateSessionManagers(FakeAuthenticator::Config(FakeAuthenticator::ACCEPT));
}
// Verify that an incoming session can be rejected, and that the
// status of the connection is set to FAILED in this case.
TEST_F(JingleSessionTest, RejectConnection) {
CreateSessionManagers(FakeAuthenticator::Config(FakeAuthenticator::ACCEPT));
// Reject incoming session.
EXPECT_CALL(host_server_listener_, OnIncomingSession(_, _))
.WillOnce(SetArgPointee<1>(protocol::SessionManager::DECLINE));
{
InSequence dummy;
EXPECT_CALL(client_session_event_handler_,
OnSessionStateChange(Session::FAILED))
.Times(1);
}
ConnectClient(FakeAuthenticator::Config(FakeAuthenticator::ACCEPT));
client_session_->SetEventHandler(&client_session_event_handler_);
base::RunLoop().RunUntilIdle();
}
// Verify that we can connect two endpoints with single-step authentication.
TEST_F(JingleSessionTest, Connect) {
CreateSessionManagers(FakeAuthenticator::Config(FakeAuthenticator::ACCEPT));
InitiateConnection(FakeAuthenticator::Config(), false);
// Verify that the client specified correct initiator value.
ASSERT_GT(host_signal_strategy_->received_messages().size(), 0U);
const buzz::XmlElement* initiate_xml =
host_signal_strategy_->received_messages().front().get();
const buzz::XmlElement* jingle_element =
initiate_xml->FirstNamed(buzz::QName("urn:xmpp:jingle:1", "jingle"));
ASSERT_TRUE(jingle_element);
ASSERT_EQ(client_signal_strategy_->GetLocalAddress().id(),
jingle_element->Attr(buzz::QName(std::string(), "initiator")));
}
// Verify that we can connect two endpoints with multi-step authentication.
TEST_F(JingleSessionTest, ConnectWithMultistep) {
const int kAuthRoundtrips = 3;
FakeAuthenticator::Config auth_config(kAuthRoundtrips,
FakeAuthenticator::ACCEPT, true);
CreateSessionManagers(auth_config);
InitiateConnection(auth_config, false);
}
TEST_F(JingleSessionTest, ConnectWithOutOfOrderIqs) {
CreateSessionManagers(FakeAuthenticator::Config(FakeAuthenticator::ACCEPT));
InitiateConnection(FakeAuthenticator::Config(), false);
client_signal_strategy_->SimulateMessageReordering();
// Verify that out of order transport messages are received correctly.
host_transport_.send_transport_info_callback().Run(CreateTransportInfo("1"));
host_transport_.send_transport_info_callback().Run(CreateTransportInfo("2"));
base::RunLoop().RunUntilIdle();
ASSERT_EQ(client_transport_.received_messages().size(), 2U);
EXPECT_EQ("1", client_transport_.received_messages()[0]->Attr(buzz::QN_ID));
EXPECT_EQ("2", client_transport_.received_messages()[1]->Attr(buzz::QN_ID));
}
// Verify that out-of-order messages are handled correctly when the session is
// torn down after the first message.
TEST_F(JingleSessionTest, ConnectWithOutOfOrderIqsDestroyOnFirstMessage) {
CreateSessionManagers(FakeAuthenticator::Config(FakeAuthenticator::ACCEPT));
InitiateConnection(FakeAuthenticator::Config(), false);
client_signal_strategy_->SimulateMessageReordering();
// Verify that out of order transport messages are received correctly.
host_transport_.send_transport_info_callback().Run(CreateTransportInfo("1"));
host_transport_.send_transport_info_callback().Run(CreateTransportInfo("2"));
// Destroy the session as soon as the first message is received.
client_transport_.set_on_message_callback(base::Bind(
&JingleSessionTest::DeleteClientSession, base::Unretained(this)));
base::RunLoop().RunUntilIdle();
ASSERT_EQ(client_transport_.received_messages().size(), 1U);
EXPECT_EQ("1", client_transport_.received_messages()[0]->Attr(buzz::QN_ID));
}
// Verify that connection is terminated when single-step auth fails.
TEST_F(JingleSessionTest, ConnectWithBadAuth) {
CreateSessionManagers(FakeAuthenticator::Config(FakeAuthenticator::REJECT));
InitiateConnection(FakeAuthenticator::Config(), true);
}
// Verify that connection is terminated when multi-step auth fails.
TEST_F(JingleSessionTest, ConnectWithBadMultistepAuth) {
const int kAuthRoundtrips = 3;
CreateSessionManagers(FakeAuthenticator::Config(
kAuthRoundtrips, FakeAuthenticator::REJECT, false));
InitiateConnection(FakeAuthenticator::Config(
kAuthRoundtrips, FakeAuthenticator::ACCEPT, false),
true);
}
// Verify that incompatible protocol configuration is handled properly.
TEST_F(JingleSessionTest, TestIncompatibleProtocol) {
CreateSessionManagers(FakeAuthenticator::Config(FakeAuthenticator::ACCEPT));
EXPECT_CALL(host_server_listener_, OnIncomingSession(_, _)).Times(0);
EXPECT_CALL(client_session_event_handler_,
OnSessionStateChange(Session::FAILED))
.Times(1);
std::unique_ptr<CandidateSessionConfig> config =
CandidateSessionConfig::CreateDefault();
// Disable all video codecs so the host will reject connection.
config->mutable_video_configs()->clear();
client_server_->set_protocol_config(std::move(config));
ConnectClient(FakeAuthenticator::Config(FakeAuthenticator::ACCEPT));
EXPECT_EQ(INCOMPATIBLE_PROTOCOL, client_session_->error());
EXPECT_FALSE(host_session_);
}
// Verify that GICE-only client is rejected with an appropriate error code.
TEST_F(JingleSessionTest, TestLegacyIceConnection) {
CreateSessionManagers(FakeAuthenticator::Config(FakeAuthenticator::ACCEPT));
EXPECT_CALL(host_server_listener_, OnIncomingSession(_, _)).Times(0);
EXPECT_CALL(client_session_event_handler_,
OnSessionStateChange(Session::FAILED))
.Times(1);
std::unique_ptr<CandidateSessionConfig> config =
CandidateSessionConfig::CreateDefault();
config->set_ice_supported(false);
client_server_->set_protocol_config(std::move(config));
ConnectClient(FakeAuthenticator::Config(FakeAuthenticator::ACCEPT));
EXPECT_EQ(INCOMPATIBLE_PROTOCOL, client_session_->error());
EXPECT_FALSE(host_session_);
}
TEST_F(JingleSessionTest, DeleteSessionOnIncomingConnection) {
const int kAuthRoundtrips = 3;
FakeAuthenticator::Config auth_config(kAuthRoundtrips,
FakeAuthenticator::ACCEPT, true);
CreateSessionManagers(auth_config);
EXPECT_CALL(host_server_listener_, OnIncomingSession(_, _))
.WillOnce(
DoAll(WithArg<0>(Invoke(this, &JingleSessionTest::SetHostSession)),
SetArgPointee<1>(protocol::SessionManager::ACCEPT)));
EXPECT_CALL(host_session_event_handler_,
OnSessionStateChange(Session::ACCEPTED))
.Times(AtMost(1));
EXPECT_CALL(host_session_event_handler_,
OnSessionStateChange(Session::AUTHENTICATING))
.WillOnce(InvokeWithoutArgs(this, &JingleSessionTest::DeleteHostSession));
ConnectClient(auth_config);
}
TEST_F(JingleSessionTest, DeleteSessionOnAuth) {
// Same as the previous test, but set messages_till_started to 2 in
// CreateSessionManagers so that the session will goes into the
// AUTHENTICATING state after two message exchanges.
const int kMessagesTillStarted = 2;
const int kAuthRoundtrips = 3;
FakeAuthenticator::Config auth_config(kAuthRoundtrips,
FakeAuthenticator::ACCEPT, true);
CreateSessionManagers(auth_config, kMessagesTillStarted);
EXPECT_CALL(host_server_listener_, OnIncomingSession(_, _))
.WillOnce(
DoAll(WithArg<0>(Invoke(this, &JingleSessionTest::SetHostSession)),
SetArgPointee<1>(protocol::SessionManager::ACCEPT)));
EXPECT_CALL(host_session_event_handler_,
OnSessionStateChange(Session::ACCEPTED))
.Times(AtMost(1));
EXPECT_CALL(host_session_event_handler_,
OnSessionStateChange(Session::AUTHENTICATING))
.WillOnce(InvokeWithoutArgs(this, &JingleSessionTest::DeleteHostSession));
ConnectClient(auth_config);
}
// Verify that incoming transport-info messages are handled correctly while in
// AUTHENTICATING state.
TEST_F(JingleSessionTest, TransportInfoDuringAuthentication) {
const int kAuthRoundtrips = 2;
FakeAuthenticator::Config auth_config(kAuthRoundtrips,
FakeAuthenticator::ACCEPT, true);
CreateSessionManagers(auth_config);
SetHostExpectation(false);
{
InSequence dummy;
EXPECT_CALL(client_session_event_handler_,
OnSessionStateChange(Session::ACCEPTED))
.Times(AtMost(1));
EXPECT_CALL(client_session_event_handler_,
OnSessionStateChange(Session::AUTHENTICATING))
.Times(AtMost(1));
}
// Create connection and pause it before authentication is finished.
FakeAuthenticator* authenticator = new FakeAuthenticator(
FakeAuthenticator::CLIENT, auth_config,
client_signal_strategy_->GetLocalAddress().id(), kNormalizedHostJid);
authenticator->set_pause_message_index(4);
ConnectClient(base::WrapUnique(authenticator));
// Send 2 transport messages.
host_transport_.send_transport_info_callback().Run(CreateTransportInfo("1"));
host_transport_.send_transport_info_callback().Run(CreateTransportInfo("2"));
base::RunLoop().RunUntilIdle();
// The transport-info messages should not be received here because
// authentication hasn't finished.
EXPECT_TRUE(client_transport_.received_messages().empty());
// Destroy the session as soon as the first message is received.
client_transport_.set_on_message_callback(base::Bind(
&JingleSessionTest::DeleteClientSession, base::Unretained(this)));
// Resume authentication.
authenticator->Resume();
base::RunLoop().RunUntilIdle();
// Verify that transport-info that the first transport-info message was
// received.
ASSERT_EQ(client_transport_.received_messages().size(), 1U);
EXPECT_EQ("1", client_transport_.received_messages()[0]->Attr(buzz::QN_ID));
}
TEST_F(JingleSessionTest, TestSessionPlugin) {
host_plugin_.Clear();
client_plugin_.Clear();
const int kAuthRoundtrips = 3;
FakeAuthenticator::Config auth_config(kAuthRoundtrips,
FakeAuthenticator::ACCEPT, true);
CreateSessionManagers(auth_config);
ASSERT_NO_FATAL_FAILURE(InitiateConnection(auth_config, false));
ExpectPluginMessagesEqual();
}
TEST_F(JingleSessionTest, SessionPluginShouldNotBeInvolvedInSessionTerminate) {
host_plugin_.Clear();
client_plugin_.Clear();
CreateSessionManagers(FakeAuthenticator::Config(FakeAuthenticator::REJECT));
InitiateConnection(FakeAuthenticator::Config(), true);
// It's expected the client sends one more plugin message than host, the host
// won't send plugin message in the SESSION_TERMINATE message.
ASSERT_EQ(client_plugin_.outgoing_messages().size() - 1,
client_plugin_.incoming_messages().size());
ExpectPluginMessagesEqual();
}
TEST_F(JingleSessionTest, ImmediatelyCloseSessionAfterConnect) {
const int kAuthRoundtrips = 3;
FakeAuthenticator::Config auth_config(kAuthRoundtrips,
FakeAuthenticator::ACCEPT, true);
CreateSessionManagers(auth_config);
client_session_ = client_server_->Connect(
SignalingAddress(kNormalizedHostJid),
std::make_unique<FakeAuthenticator>(
FakeAuthenticator::CLIENT, auth_config,
client_signal_strategy_->GetLocalAddress().id(), kNormalizedHostJid));
client_session_->Close(HOST_OVERLOAD);
base::RunLoop().RunUntilIdle();
// We should only send a SESSION_TERMINATE message if the session has been
// closed before SESSION_INITIATE message.
ASSERT_EQ(1U, host_signal_strategy_->received_messages().size());
}
} // namespace protocol
} // namespace remoting