blob: 016124bfc6d4ac471e6bdb442c5f173cd10ea1e1 [file] [log] [blame]
// Copyright 2017 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 "chromeos/components/tether/message_transfer_operation.h"
#include <memory>
#include "base/memory/ptr_util.h"
#include "base/timer/mock_timer.h"
#include "chromeos/components/multidevice/remote_device_test_util.h"
#include "chromeos/components/tether/message_wrapper.h"
#include "chromeos/components/tether/proto_test_util.h"
#include "chromeos/components/tether/test_timer_factory.h"
#include "chromeos/services/device_sync/public/cpp/fake_device_sync_client.h"
#include "chromeos/services/secure_channel/public/cpp/client/fake_client_channel.h"
#include "chromeos/services/secure_channel/public/cpp/client/fake_connection_attempt.h"
#include "chromeos/services/secure_channel/public/cpp/client/fake_secure_channel_client.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace tether {
namespace {
// Arbitrarily chosen value. The MessageType used in this test does not matter
// except that it must be consistent throughout the test.
const MessageType kTestMessageType = MessageType::TETHER_AVAILABILITY_REQUEST;
const uint32_t kTestTimeoutSeconds = 5;
const char kTetherFeature[] = "magic_tether";
// A test double for MessageTransferOperation is needed because
// MessageTransferOperation has pure virtual methods which must be overridden in
// order to create a concrete instantiation of the class.
class TestOperation : public MessageTransferOperation {
public:
TestOperation(const multidevice::RemoteDeviceRefList& devices_to_connect,
device_sync::DeviceSyncClient* device_sync_client,
secure_channel::SecureChannelClient* secure_channel_client)
: MessageTransferOperation(devices_to_connect,
secure_channel::ConnectionPriority::kLow,
device_sync_client,
secure_channel_client) {}
~TestOperation() override = default;
bool HasDeviceAuthenticated(multidevice::RemoteDeviceRef remote_device) {
const auto iter = device_map_.find(remote_device);
if (iter == device_map_.end())
return false;
return iter->second.has_device_authenticated;
}
std::vector<std::shared_ptr<MessageWrapper>> GetReceivedMessages(
multidevice::RemoteDeviceRef remote_device) {
const auto iter = device_map_.find(remote_device);
if (iter == device_map_.end())
return std::vector<std::shared_ptr<MessageWrapper>>();
return iter->second.received_messages;
}
// MessageTransferOperation:
void OnDeviceAuthenticated(
multidevice::RemoteDeviceRef remote_device) override {
device_map_[remote_device].has_device_authenticated = true;
}
void OnMessageReceived(std::unique_ptr<MessageWrapper> message_wrapper,
multidevice::RemoteDeviceRef remote_device) override {
device_map_[remote_device].received_messages.push_back(
std::move(message_wrapper));
if (should_unregister_device_on_message_received_)
UnregisterDevice(remote_device);
}
void OnOperationStarted() override { has_operation_started_ = true; }
void OnOperationFinished() override { has_operation_finished_ = true; }
MessageType GetMessageTypeForConnection() override {
return kTestMessageType;
}
void OnMessageSent(int sequence_number) override {
last_sequence_number_ = sequence_number;
}
uint32_t GetMessageTimeoutSeconds() override { return timeout_seconds_; }
void set_timeout_seconds(uint32_t timeout_seconds) {
timeout_seconds_ = timeout_seconds;
}
void set_should_unregister_device_on_message_received(
bool should_unregister_device_on_message_received) {
should_unregister_device_on_message_received_ =
should_unregister_device_on_message_received;
}
bool has_operation_started() { return has_operation_started_; }
bool has_operation_finished() { return has_operation_finished_; }
base::Optional<int> last_sequence_number() { return last_sequence_number_; }
private:
struct DeviceMapValue {
DeviceMapValue() = default;
~DeviceMapValue() = default;
bool has_device_authenticated;
std::vector<std::shared_ptr<MessageWrapper>> received_messages;
};
base::flat_map<multidevice::RemoteDeviceRef, DeviceMapValue> device_map_;
uint32_t timeout_seconds_ = kTestTimeoutSeconds;
bool should_unregister_device_on_message_received_ = false;
bool has_operation_started_ = false;
bool has_operation_finished_ = false;
base::Optional<int> last_sequence_number_;
};
TetherAvailabilityResponse CreateTetherAvailabilityResponse() {
TetherAvailabilityResponse response;
response.set_response_code(
TetherAvailabilityResponse_ResponseCode::
TetherAvailabilityResponse_ResponseCode_TETHER_AVAILABLE);
response.mutable_device_status()->CopyFrom(
CreateDeviceStatusWithFakeFields());
return response;
}
} // namespace
class MessageTransferOperationTest : public testing::Test {
protected:
MessageTransferOperationTest()
: test_local_device_(multidevice::RemoteDeviceRefBuilder()
.SetPublicKey("local device")
.Build()),
test_devices_(multidevice::CreateRemoteDeviceRefListForTest(4)) {}
void SetUp() override {
fake_device_sync_client_ =
std::make_unique<device_sync::FakeDeviceSyncClient>();
fake_device_sync_client_->set_local_device_metadata(test_local_device_);
fake_secure_channel_client_ =
std::make_unique<secure_channel::FakeSecureChannelClient>();
}
void ConstructOperation(multidevice::RemoteDeviceRefList remote_devices) {
test_timer_factory_ = new TestTimerFactory();
for (auto remote_device : remote_devices) {
// Prepare for connection timeout timers to be made for each remote
// device.
test_timer_factory_->set_device_id_for_next_timer(
remote_device.GetDeviceId());
auto fake_connection_attempt =
std::make_unique<secure_channel::FakeConnectionAttempt>();
remote_device_to_fake_connection_attempt_map_[remote_device] =
fake_connection_attempt.get();
fake_secure_channel_client_->set_next_listen_connection_attempt(
remote_device, test_local_device_,
std::move(fake_connection_attempt));
}
operation_ = base::WrapUnique(
new TestOperation(remote_devices, fake_device_sync_client_.get(),
fake_secure_channel_client_.get()));
operation_->SetTimerFactoryForTest(base::WrapUnique(test_timer_factory_));
VerifyOperationStartedAndFinished(false /* has_started */,
false /* has_finished */);
}
void InitializeOperation() {
VerifyOperationStartedAndFinished(false /* has_started */,
false /* has_finished */);
operation_->Initialize();
for (const auto* arguments :
fake_secure_channel_client_
->last_listen_for_connection_request_arguments_list()) {
EXPECT_EQ(kTetherFeature, arguments->feature);
}
VerifyOperationStartedAndFinished(true /* has_started */,
false /* has_finished */);
for (const auto& remote_device : operation_->remote_devices())
VerifyConnectionTimerCreatedForDevice(remote_device);
}
void VerifyOperationStartedAndFinished(bool has_started, bool has_finished) {
EXPECT_EQ(has_started, operation_->has_operation_started());
EXPECT_EQ(has_finished, operation_->has_operation_finished());
}
void CreateAuthenticatedChannelForDevice(
multidevice::RemoteDeviceRef remote_device) {
test_timer_factory_->set_device_id_for_next_timer(
remote_device.GetDeviceId());
auto fake_client_channel =
std::make_unique<secure_channel::FakeClientChannel>();
remote_device_to_fake_client_channel_map_[remote_device] =
fake_client_channel.get();
remote_device_to_fake_connection_attempt_map_[remote_device]
->NotifyConnection(std::move(fake_client_channel));
}
base::MockOneShotTimer* GetTimerForDevice(
multidevice::RemoteDeviceRef remote_device) {
return test_timer_factory_->GetTimerForDeviceId(
remote_device.GetDeviceId());
}
void VerifyDefaultTimerCreatedForDevice(
multidevice::RemoteDeviceRef remote_device) {
VerifyTimerCreatedForDevice(remote_device, kTestTimeoutSeconds);
}
void VerifyConnectionTimerCreatedForDevice(
multidevice::RemoteDeviceRef remote_device) {
VerifyTimerCreatedForDevice(
remote_device, MessageTransferOperation::kConnectionTimeoutSeconds);
}
void VerifyTimerCreatedForDevice(multidevice::RemoteDeviceRef remote_device,
uint32_t timeout_seconds) {
EXPECT_TRUE(GetTimerForDevice(remote_device));
EXPECT_EQ(base::TimeDelta::FromSeconds(timeout_seconds),
GetTimerForDevice(remote_device)->GetCurrentDelay());
}
int SendMessageToDevice(multidevice::RemoteDeviceRef remote_device,
std::unique_ptr<MessageWrapper> message_wrapper) {
return operation_->SendMessageToDevice(test_devices_[0],
std::move(message_wrapper));
}
const multidevice::RemoteDeviceRef test_local_device_;
const multidevice::RemoteDeviceRefList test_devices_;
base::flat_map<multidevice::RemoteDeviceRef,
secure_channel::FakeConnectionAttempt*>
remote_device_to_fake_connection_attempt_map_;
base::flat_map<multidevice::RemoteDeviceRef,
secure_channel::FakeClientChannel*>
remote_device_to_fake_client_channel_map_;
std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
std::unique_ptr<secure_channel::FakeSecureChannelClient>
fake_secure_channel_client_;
TestTimerFactory* test_timer_factory_;
std::unique_ptr<TestOperation> operation_;
private:
DISALLOW_COPY_AND_ASSIGN(MessageTransferOperationTest);
};
TEST_F(MessageTransferOperationTest, TestFailedConnection) {
ConstructOperation(multidevice::RemoteDeviceRefList{test_devices_[0]});
InitializeOperation();
remote_device_to_fake_connection_attempt_map_[test_devices_[0]]
->NotifyConnectionAttemptFailure(
secure_channel::mojom::ConnectionAttemptFailureReason::
AUTHENTICATION_ERROR);
VerifyOperationStartedAndFinished(true /* has_started */,
true /* has_finished */);
EXPECT_FALSE(operation_->HasDeviceAuthenticated(test_devices_[0]));
EXPECT_TRUE(operation_->GetReceivedMessages(test_devices_[0]).empty());
}
TEST_F(MessageTransferOperationTest,
TestSuccessfulConnectionSendAndReceiveMessage) {
ConstructOperation(multidevice::RemoteDeviceRefList{test_devices_[0]});
InitializeOperation();
// Simulate how subclasses behave after a successful response: unregister the
// device.
operation_->set_should_unregister_device_on_message_received(true);
CreateAuthenticatedChannelForDevice(test_devices_[0]);
EXPECT_TRUE(operation_->HasDeviceAuthenticated(test_devices_[0]));
VerifyDefaultTimerCreatedForDevice(test_devices_[0]);
auto message_wrapper =
std::make_unique<MessageWrapper>(TetherAvailabilityRequest());
std::string expected_payload = message_wrapper->ToRawMessage();
int sequence_number =
SendMessageToDevice(test_devices_[0], std::move(message_wrapper));
std::vector<std::pair<std::string, base::OnceClosure>>& sent_messages =
remote_device_to_fake_client_channel_map_[test_devices_[0]]
->sent_messages();
EXPECT_EQ(1u, sent_messages.size());
EXPECT_EQ(expected_payload, sent_messages[0].first);
EXPECT_FALSE(operation_->last_sequence_number());
std::move(sent_messages[0].second).Run();
EXPECT_EQ(sequence_number, operation_->last_sequence_number());
remote_device_to_fake_client_channel_map_[test_devices_[0]]
->NotifyMessageReceived(
MessageWrapper(CreateTetherAvailabilityResponse()).ToRawMessage());
EXPECT_EQ(1u, operation_->GetReceivedMessages(test_devices_[0]).size());
std::shared_ptr<MessageWrapper> message =
operation_->GetReceivedMessages(test_devices_[0])[0];
EXPECT_EQ(MessageType::TETHER_AVAILABILITY_RESPONSE,
message->GetMessageType());
EXPECT_EQ(CreateTetherAvailabilityResponse().SerializeAsString(),
message->GetProto()->SerializeAsString());
}
TEST_F(MessageTransferOperationTest, TestTimesOutBeforeAuthentication) {
ConstructOperation(multidevice::RemoteDeviceRefList{test_devices_[0]});
InitializeOperation();
GetTimerForDevice(test_devices_[0])->Fire();
EXPECT_TRUE(operation_->has_operation_finished());
}
TEST_F(MessageTransferOperationTest, TestAuthenticatesButThenTimesOut) {
ConstructOperation(multidevice::RemoteDeviceRefList{test_devices_[0]});
InitializeOperation();
CreateAuthenticatedChannelForDevice(test_devices_[0]);
EXPECT_TRUE(operation_->HasDeviceAuthenticated(test_devices_[0]));
VerifyDefaultTimerCreatedForDevice(test_devices_[0]);
GetTimerForDevice(test_devices_[0])->Fire();
EXPECT_TRUE(operation_->has_operation_finished());
}
TEST_F(MessageTransferOperationTest, TestRepeatedInputDevice) {
// Construct with two copies of the same device.
ConstructOperation(
multidevice::RemoteDeviceRefList{test_devices_[0], test_devices_[0]});
InitializeOperation();
CreateAuthenticatedChannelForDevice(test_devices_[0]);
EXPECT_TRUE(operation_->HasDeviceAuthenticated(test_devices_[0]));
VerifyDefaultTimerCreatedForDevice(test_devices_[0]);
remote_device_to_fake_client_channel_map_[test_devices_[0]]
->NotifyMessageReceived(
MessageWrapper(CreateTetherAvailabilityResponse()).ToRawMessage());
// Should still have received only one message even though the device was
// repeated twice in the constructor.
EXPECT_EQ(1u, operation_->GetReceivedMessages(test_devices_[0]).size());
std::shared_ptr<MessageWrapper> message =
operation_->GetReceivedMessages(test_devices_[0])[0];
EXPECT_EQ(MessageType::TETHER_AVAILABILITY_RESPONSE,
message->GetMessageType());
EXPECT_EQ(CreateTetherAvailabilityResponse().SerializeAsString(),
message->GetProto()->SerializeAsString());
}
TEST_F(MessageTransferOperationTest, MultipleDevices) {
ConstructOperation(test_devices_);
InitializeOperation();
for (const auto& remote_device : test_devices_)
test_timer_factory_->ClearTimerForDeviceId(remote_device.GetDeviceId());
// Authenticate |test_devices_[0]|'s channel.
CreateAuthenticatedChannelForDevice(test_devices_[0]);
EXPECT_TRUE(operation_->HasDeviceAuthenticated(test_devices_[0]));
VerifyDefaultTimerCreatedForDevice(test_devices_[0]);
// Fail to connect to |test_devices_[1]|.
test_timer_factory_->set_device_id_for_next_timer(
test_devices_[1].GetDeviceId());
remote_device_to_fake_connection_attempt_map_[test_devices_[1]]
->NotifyConnectionAttemptFailure(
secure_channel::mojom::ConnectionAttemptFailureReason::
GATT_CONNECTION_ERROR);
EXPECT_FALSE(operation_->HasDeviceAuthenticated(test_devices_[1]));
EXPECT_FALSE(GetTimerForDevice(test_devices_[1]));
// Authenticate |test_devices_[2]|'s channel.
CreateAuthenticatedChannelForDevice(test_devices_[2]);
EXPECT_TRUE(operation_->HasDeviceAuthenticated(test_devices_[2]));
VerifyDefaultTimerCreatedForDevice(test_devices_[2]);
// Fail to connect to |test_devices_[3]|.
test_timer_factory_->set_device_id_for_next_timer(
test_devices_[3].GetDeviceId());
remote_device_to_fake_connection_attempt_map_[test_devices_[3]]
->NotifyConnectionAttemptFailure(
secure_channel::mojom::ConnectionAttemptFailureReason::
GATT_CONNECTION_ERROR);
EXPECT_FALSE(operation_->HasDeviceAuthenticated(test_devices_[3]));
EXPECT_FALSE(GetTimerForDevice(test_devices_[3]));
}
} // namespace tether
} // namespace chromeos