blob: 05f0cf176f5359b64b52945e12ed9ebb2a2476c3 [file] [log] [blame]
// Copyright 2020 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 "chrome/browser/sharing/webrtc/sharing_webrtc_connection_host.h"
#include <algorithm>
#include "base/bind_helpers.h"
#include "base/test/bind_test_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/time/time.h"
#include "chrome/browser/sharing/fake_device_info.h"
#include "chrome/browser/sharing/fake_sharing_handler_registry.h"
#include "chrome/browser/sharing/mock_sharing_message_handler.h"
#include "chrome/browser/sharing/proto/sharing_message.pb.h"
#include "chrome/browser/sharing/sharing_constants.h"
#include "chrome/browser/sharing/webrtc/webrtc_signalling_host_fcm.h"
#include "chrome/services/sharing/public/mojom/webrtc.mojom.h"
#include "components/gcm_driver/crypto/gcm_decryption_result.h"
#include "components/gcm_driver/crypto/gcm_encryption_result.h"
#include "components/gcm_driver/fake_gcm_driver.h"
#include "content/public/test/browser_task_environment.h"
#include "services/network/public/mojom/p2p_trusted.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
class MockSharingMojoService : public sharing::mojom::SharingWebRtcConnection,
public network::mojom::P2PTrustedSocketManager {
public:
MockSharingMojoService() = default;
~MockSharingMojoService() override = default;
// sharing::mojom::SharingWebRtcConnection:
MOCK_METHOD2(SendMessage,
void(const std::vector<uint8_t>&, SendMessageCallback));
// network::mojom::P2PTrustedSocketManager:
void StartRtpDump(bool incoming, bool outgoing) override {}
void StopRtpDump(bool incoming, bool outgoing) override {}
mojo::Remote<sharing::mojom::SharingWebRtcConnectionDelegate> delegate;
mojo::Receiver<sharing::mojom::SharingWebRtcConnection> connection{this};
mojo::Remote<network::mojom::P2PTrustedSocketManagerClient>
socket_manager_client;
mojo::Receiver<network::mojom::P2PTrustedSocketManager> socket_manager{this};
};
class MockSignallingHost : public WebRtcSignallingHostFCM {
public:
MockSignallingHost()
: WebRtcSignallingHostFCM(
mojo::PendingReceiver<sharing::mojom::SignallingSender>(),
mojo::PendingRemote<sharing::mojom::SignallingReceiver>(),
/*message_sender=*/nullptr,
CreateFakeDeviceInfo("id", "name")) {}
~MockSignallingHost() override = default;
// WebRtcSignallingHostFCM:
MOCK_METHOD2(SendOffer, void(const std::string&, SendOfferCallback));
MOCK_METHOD1(SendIceCandidates,
void(std::vector<sharing::mojom::IceCandidatePtr>));
MOCK_METHOD2(OnOfferReceived, void(const std::string&, SendOfferCallback));
MOCK_METHOD1(OnIceCandidatesReceived,
void(std::vector<sharing::mojom::IceCandidatePtr>));
};
// Implementation of GCMDriver that does not encrypt / decrypt messages.
class CustomFakeGCMDriver : public gcm::FakeGCMDriver {
public:
CustomFakeGCMDriver() = default;
~CustomFakeGCMDriver() override = default;
void EncryptMessage(const std::string& app_id,
const std::string& authorized_entity,
const std::string& p256dh,
const std::string& auth_secret,
const std::string& message,
EncryptMessageCallback callback) override {
std::string result = message;
if (reverse_data_)
std::reverse(result.begin(), result.end());
std::move(callback).Run(fail_
? gcm::GCMEncryptionResult::ENCRYPTION_FAILED
: gcm::GCMEncryptionResult::ENCRYPTED_DRAFT_08,
std::move(result));
}
void DecryptMessage(const std::string& app_id,
const std::string& authorized_entity,
const std::string& message,
DecryptMessageCallback callback) override {
std::string result = message;
if (reverse_data_)
std::reverse(result.begin(), result.end());
std::move(callback).Run(
fail_ ? gcm::GCMDecryptionResult::INVALID_ENCRYPTION_HEADER
: gcm::GCMDecryptionResult::DECRYPTED_DRAFT_08,
std::move(result));
}
void set_reverse_data(bool reverse_data) { reverse_data_ = reverse_data; }
void set_fail(bool fail) { fail_ = fail; }
private:
bool reverse_data_ = false;
bool fail_ = false;
};
chrome_browser_sharing::WebRtcMessage CreateMessage() {
chrome_browser_sharing::WebRtcMessage message;
message.set_message_guid("guid");
chrome_browser_sharing::SharingMessage* sharing_message =
message.mutable_message();
chrome_browser_sharing::SharedClipboardMessage* shared_clipboard_message =
sharing_message->mutable_shared_clipboard_message();
shared_clipboard_message->set_text("text");
return message;
}
chrome_browser_sharing::WebRtcMessage CreateAckMessage() {
chrome_browser_sharing::WebRtcMessage message;
chrome_browser_sharing::SharingMessage* sharing_message =
message.mutable_message();
chrome_browser_sharing::AckMessage* ack_message =
sharing_message->mutable_ack_message();
ack_message->set_original_message_id("original_message_id");
return message;
}
std::vector<uint8_t> SerializeMessage(
const chrome_browser_sharing::WebRtcMessage& message) {
std::vector<uint8_t> serialized_message(message.ByteSize());
message.SerializeToArray(serialized_message.data(),
serialized_message.size());
return serialized_message;
}
// Metric names
const char kWebRtcTimeout[] = "Sharing.WebRtc.Timeout";
} // namespace
class SharingWebRtcConnectionHostTest : public testing::Test {
public:
SharingWebRtcConnectionHostTest() {
handler_registry_.SetSharingHandler(
chrome_browser_sharing::SharingMessage::kSharedClipboardMessage,
&message_handler_);
handler_registry_.SetSharingHandler(
chrome_browser_sharing::SharingMessage::kAckMessage,
&ack_message_handler_);
auto signalling_host = std::make_unique<MockSignallingHost>();
signalling_host_ = signalling_host.get();
SharingWebRtcConnectionHost::EncryptionInfo encryption_info{
"authorized_entity", "p256dh", "auth_secret"};
host_ = std::make_unique<SharingWebRtcConnectionHost>(
std::move(signalling_host), &handler_registry_, &fake_gcm_driver_,
std::move(encryption_info),
base::BindOnce(&SharingWebRtcConnectionHostTest::ConnectionClosed,
base::Unretained(this)),
mock_service_.delegate.BindNewPipeAndPassReceiver(),
mock_service_.connection.BindNewPipeAndPassRemote(),
mock_service_.socket_manager_client.BindNewPipeAndPassReceiver(),
mock_service_.socket_manager.BindNewPipeAndPassRemote());
}
MOCK_METHOD0(ConnectionClosed, void());
void ExpectOnMessage(MockSharingMessageHandler* handler) {
EXPECT_CALL(*handler, OnMessage(testing::_, testing::_))
.WillOnce(testing::Invoke(
[&](const chrome_browser_sharing::SharingMessage& message,
SharingMessageHandler::DoneCallback done_callback) {
std::move(done_callback).Run(/*response=*/nullptr);
}));
}
void ExpectSendMessage() {
EXPECT_CALL(mock_service_, SendMessage(testing::_, testing::_))
.WillOnce(testing::Invoke(
[&](const std::vector<uint8_t>& data,
sharing::mojom::SharingWebRtcConnection::SendMessageCallback
callback) {
std::move(callback).Run(
sharing::mojom::SendMessageResult::kSuccess);
}));
}
void WaitForConnectionClosed() {
base::RunLoop run_loop;
EXPECT_CALL(*this, ConnectionClosed()).WillOnce(testing::Invoke([&]() {
run_loop.Quit();
}));
run_loop.Run();
}
void WaitForMojoDisconnect() {
base::RunLoop run_loop;
mock_service_.delegate.set_disconnect_handler(run_loop.QuitClosure());
run_loop.Run();
EXPECT_FALSE(mock_service_.delegate.is_connected());
}
protected:
content::BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
MockSharingMessageHandler message_handler_;
MockSharingMessageHandler ack_message_handler_;
MockSharingMojoService mock_service_;
FakeSharingHandlerRegistry handler_registry_;
CustomFakeGCMDriver fake_gcm_driver_;
MockSignallingHost* signalling_host_;
std::unique_ptr<SharingWebRtcConnectionHost> host_;
base::HistogramTester histograms_;
};
TEST_F(SharingWebRtcConnectionHostTest, OnMessageReceived) {
EXPECT_TRUE(mock_service_.delegate.is_connected());
// Expect the message handler to be called.
ExpectOnMessage(&message_handler_);
// Expect that an Ack message is sent after the message handler is done.
ExpectSendMessage();
// Expect that sending the Ack message closes the connection.
base::RunLoop run_loop;
mock_service_.delegate.set_disconnect_handler(run_loop.QuitClosure());
host_->OnMessageReceived(SerializeMessage(CreateMessage()));
run_loop.Run();
EXPECT_FALSE(mock_service_.delegate.is_connected());
}
TEST_F(SharingWebRtcConnectionHostTest, DecryptMessage_Success) {
fake_gcm_driver_.set_reverse_data(true);
ExpectOnMessage(&message_handler_);
ExpectSendMessage();
base::RunLoop run_loop;
mock_service_.delegate.set_disconnect_handler(run_loop.QuitClosure());
auto message = SerializeMessage(CreateMessage());
// Reverse data to simulate encrypted message being received.
std::reverse(message.begin(), message.end());
host_->OnMessageReceived(message);
run_loop.Run();
}
TEST_F(SharingWebRtcConnectionHostTest, DecryptMessage_Fail) {
fake_gcm_driver_.set_fail(true);
// Expect not to handle the message as decryption failed.
EXPECT_CALL(message_handler_, OnMessage(testing::_, testing::_)).Times(0);
auto message = SerializeMessage(CreateMessage());
// Reverse data to simulate encrypted message being received.
std::reverse(message.begin(), message.end());
host_->OnMessageReceived(message);
WaitForConnectionClosed();
}
TEST_F(SharingWebRtcConnectionHostTest, DecryptMessage_FailNotEncrypted) {
fake_gcm_driver_.set_reverse_data(true);
// Expect not to handle the message as decryption failed.
EXPECT_CALL(message_handler_, OnMessage(testing::_, testing::_)).Times(0);
auto message = SerializeMessage(CreateMessage());
// Do not reverse data to check if we actually try to decrypt.
host_->OnMessageReceived(message);
WaitForConnectionClosed();
}
TEST_F(SharingWebRtcConnectionHostTest, OnAckMessageReceived) {
EXPECT_TRUE(mock_service_.delegate.is_connected());
// Expect the Ack message handler to be called.
ExpectOnMessage(&ack_message_handler_);
// Expect that handling the Ack message closes the connection.
base::RunLoop run_loop;
mock_service_.delegate.set_disconnect_handler(run_loop.QuitClosure());
host_->OnMessageReceived(SerializeMessage(CreateAckMessage()));
run_loop.Run();
EXPECT_FALSE(mock_service_.delegate.is_connected());
}
TEST_F(SharingWebRtcConnectionHostTest, SendMessage) {
// Expect the message to be sent to the service.
ExpectSendMessage();
base::RunLoop run_loop;
host_->SendMessage(
CreateMessage(),
base::BindLambdaForTesting([&](sharing::mojom::SendMessageResult result) {
EXPECT_EQ(sharing::mojom::SendMessageResult::kSuccess, result);
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(SharingWebRtcConnectionHostTest, EncryptMessage_Success) {
fake_gcm_driver_.set_reverse_data(true);
auto message = CreateMessage();
auto serialized_message = SerializeMessage(message);
std::reverse(serialized_message.begin(), serialized_message.end());
EXPECT_CALL(mock_service_, SendMessage(testing::_, testing::_))
.WillOnce(testing::Invoke(
[&](const std::vector<uint8_t>& data,
sharing::mojom::SharingWebRtcConnection::SendMessageCallback
callback) {
EXPECT_EQ(serialized_message, data);
std::move(callback).Run(
sharing::mojom::SendMessageResult::kSuccess);
}));
base::RunLoop run_loop;
host_->SendMessage(
std::move(message),
base::BindLambdaForTesting([&](sharing::mojom::SendMessageResult result) {
EXPECT_EQ(sharing::mojom::SendMessageResult::kSuccess, result);
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(SharingWebRtcConnectionHostTest, EncryptMessage_Fail) {
fake_gcm_driver_.set_fail(true);
EXPECT_CALL(mock_service_, SendMessage(testing::_, testing::_)).Times(0);
host_->SendMessage(
CreateMessage(),
base::BindLambdaForTesting([&](sharing::mojom::SendMessageResult result) {
EXPECT_EQ(sharing::mojom::SendMessageResult::kError, result);
}));
WaitForConnectionClosed();
}
TEST_F(SharingWebRtcConnectionHostTest, OnOfferReceived) {
EXPECT_CALL(*signalling_host_, OnOfferReceived("offer", testing::_))
.WillOnce(testing::Invoke(
[&](const std::string& offer,
base::OnceCallback<void(const std::string&)> callback) {
EXPECT_EQ("offer", offer);
std::move(callback).Run("answer");
}));
base::RunLoop run_loop;
host_->OnOfferReceived(
"offer", base::BindLambdaForTesting([&](const std::string& answer) {
EXPECT_EQ("answer", answer);
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(SharingWebRtcConnectionHostTest, OnIceCandidatesReceived) {
base::RunLoop run_loop;
EXPECT_CALL(*signalling_host_, OnIceCandidatesReceived(testing::_))
.WillOnce(testing::Invoke(
[&](std::vector<sharing::mojom::IceCandidatePtr> ice_candidates) {
EXPECT_EQ(1u, ice_candidates.size());
run_loop.Quit();
}));
std::vector<sharing::mojom::IceCandidatePtr> ice_candidates;
ice_candidates.push_back(sharing::mojom::IceCandidate::New());
host_->OnIceCandidatesReceived(std::move(ice_candidates));
run_loop.Run();
}
TEST_F(SharingWebRtcConnectionHostTest, ConnectionClosed) {
base::RunLoop run_loop;
EXPECT_CALL(*this, ConnectionClosed()).WillOnce(testing::Invoke([&]() {
run_loop.Quit();
}));
// Expect the connection to force close if the network service connection is
// lost. This also happens if the Sharing service closes the connection.
mock_service_.socket_manager.reset();
run_loop.Run();
}
TEST_F(SharingWebRtcConnectionHostTest, ConnectionTimeout) {
base::TimeTicks start_time = task_environment_.NowTicks();
// Simply wait for the connection to be force closed.
WaitForConnectionClosed();
base::TimeTicks end_time = task_environment_.NowTicks();
EXPECT_EQ(start_time + kSharingWebRtcTimeout, end_time);
histograms_.ExpectUniqueSample(kWebRtcTimeout,
sharing::WebRtcTimeoutState::kConnecting, 1);
}
TEST_F(SharingWebRtcConnectionHostTest, ConnectionTimeout_OnMessageReceived) {
// Wait for some time before receiving the first message.
task_environment_.FastForwardBy(kSharingWebRtcTimeout / 2);
base::TimeTicks start_time = task_environment_.NowTicks();
// Expect the message handler to be called but don't call the callback.
EXPECT_CALL(message_handler_, OnMessage(testing::_, testing::_));
host_->OnMessageReceived(SerializeMessage(CreateMessage()));
// The call to OnMessageReceived should have reset the timer.
WaitForConnectionClosed();
base::TimeTicks end_time = task_environment_.NowTicks();
EXPECT_EQ(start_time + kSharingWebRtcTimeout, end_time);
histograms_.ExpectUniqueSample(
kWebRtcTimeout, sharing::WebRtcTimeoutState::kMessageReceived, 1);
}
TEST_F(SharingWebRtcConnectionHostTest, ConnectionTimeout_OnConnectionClosing) {
// Wait for some time before receiving the first message.
task_environment_.FastForwardBy(kSharingWebRtcTimeout / 2);
// Receive an Ack message that will start closing the connection.
ExpectOnMessage(&ack_message_handler_);
host_->OnMessageReceived(SerializeMessage(CreateAckMessage()));
// The timer should reset when closing the connection.
base::TimeTicks start_time = task_environment_.NowTicks();
WaitForConnectionClosed();
base::TimeTicks end_time = task_environment_.NowTicks();
histograms_.ExpectUniqueSample(
kWebRtcTimeout, sharing::WebRtcTimeoutState::kDisconnecting, 1);
EXPECT_EQ(start_time + kSharingWebRtcTimeout, end_time);
}
TEST_F(SharingWebRtcConnectionHostTest, ConnectionTimeout_SendMessage) {
// Wait for some time before sending the first message.
task_environment_.FastForwardBy(kSharingWebRtcTimeout / 2);
base::TimeTicks start_time = task_environment_.NowTicks();
// Hold on to the SendMessageCallback without calling it.
sharing::mojom::SharingWebRtcConnection::SendMessageCallback send_callback;
EXPECT_CALL(mock_service_, SendMessage(testing::_, testing::_))
.WillOnce(testing::Invoke(
[&](const std::vector<uint8_t>& data,
sharing::mojom::SharingWebRtcConnection::SendMessageCallback
callback) { send_callback = std::move(callback); }));
// The NullCallback will not be called.
host_->SendMessage(CreateMessage(), base::NullCallback());
// The call to SendMessage should have reset the timer.
WaitForConnectionClosed();
base::TimeTicks end_time = task_environment_.NowTicks();
EXPECT_EQ(start_time + kSharingWebRtcTimeout, end_time);
histograms_.ExpectUniqueSample(kWebRtcTimeout,
sharing::WebRtcTimeoutState::kMessageSent, 1);
WaitForMojoDisconnect();
}