blob: 4275d301afa98c082c7d5ca31c72f2a8bc1a2a8f [file] [log] [blame]
// Copyright 2018 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/services/secure_channel/multiplexed_channel_impl.h"
#include <iterator>
#include <memory>
#include <unordered_map>
#include <utility>
#include <vector>
#include "base/stl_util.h"
#include "base/test/scoped_task_environment.h"
#include "chromeos/services/secure_channel/connection_details.h"
#include "chromeos/services/secure_channel/connection_medium.h"
#include "chromeos/services/secure_channel/fake_authenticated_channel.h"
#include "chromeos/services/secure_channel/fake_client_connection_parameters.h"
#include "chromeos/services/secure_channel/fake_connection_delegate.h"
#include "chromeos/services/secure_channel/fake_multiplexed_channel.h"
#include "chromeos/services/secure_channel/fake_single_client_message_proxy.h"
#include "chromeos/services/secure_channel/single_client_message_proxy_impl.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace secure_channel {
namespace {
const char kTestDeviceId[] = "testDeviceId";
const char kTestFeature[] = "testFeature";
class FakeSingleClientMessageProxyImplFactory
: public SingleClientMessageProxyImpl::Factory {
public:
FakeSingleClientMessageProxyImplFactory() = default;
~FakeSingleClientMessageProxyImplFactory() override = default;
const SingleClientMessageProxy::Delegate* expected_delegate() {
return expected_delegate_;
}
// Contains all created FakeSingleClientMessageProxy pointers which have not
// yet been deleted.
std::unordered_map<base::UnguessableToken,
FakeSingleClientMessageProxy*,
base::UnguessableTokenHash>&
id_to_active_proxy_map() {
return id_to_active_proxy_map_;
}
private:
std::unique_ptr<SingleClientMessageProxy> BuildInstance(
SingleClientMessageProxy::Delegate* delegate,
std::unique_ptr<ClientConnectionParameters> client_connection_parameters)
override {
EXPECT_EQ(kTestFeature, client_connection_parameters->feature());
EXPECT_TRUE(client_connection_parameters->IsClientWaitingForResponse());
if (!expected_delegate_)
expected_delegate_ = delegate;
// Each call should have the same delegate.
EXPECT_EQ(expected_delegate_, delegate);
std::unique_ptr<SingleClientMessageProxy> proxy = std::make_unique<
FakeSingleClientMessageProxy>(
delegate,
base::BindOnce(
&FakeSingleClientMessageProxyImplFactory::OnCreatedInstanceDeleted,
base::Unretained(this)));
FakeSingleClientMessageProxy* proxy_raw =
static_cast<FakeSingleClientMessageProxy*>(proxy.get());
id_to_active_proxy_map_[proxy->GetProxyId()] = proxy_raw;
return proxy;
}
void OnCreatedInstanceDeleted(
const base::UnguessableToken& deleted_proxy_id) {
size_t num_deleted = id_to_active_proxy_map_.erase(deleted_proxy_id);
EXPECT_EQ(1u, num_deleted);
}
SingleClientMessageProxy::Delegate* expected_delegate_ = nullptr;
std::unordered_map<base::UnguessableToken,
FakeSingleClientMessageProxy*,
base::UnguessableTokenHash>
id_to_active_proxy_map_;
DISALLOW_COPY_AND_ASSIGN(FakeSingleClientMessageProxyImplFactory);
};
} // namespace
class SecureChannelMultiplexedChannelImplTest : public testing::Test {
protected:
SecureChannelMultiplexedChannelImplTest() = default;
~SecureChannelMultiplexedChannelImplTest() override = default;
void SetUp() override {
fake_proxy_factory_ =
std::make_unique<FakeSingleClientMessageProxyImplFactory>();
SingleClientMessageProxyImpl::Factory::SetInstanceForTesting(
fake_proxy_factory_.get());
fake_delegate_ = std::make_unique<FakeMultiplexedChannelDelegate>();
// The default list contains one client.
initial_client_list_.push_back(
std::make_unique<FakeClientConnectionParameters>(kTestFeature));
}
void TearDown() override {
SingleClientMessageProxyImpl::Factory::SetInstanceForTesting(nullptr);
}
void CreateChannel() {
auto fake_authenticated_channel =
std::make_unique<FakeAuthenticatedChannel>();
fake_authenticated_channel_ = fake_authenticated_channel.get();
multiplexed_channel_ =
MultiplexedChannelImpl::Factory::Get()->BuildInstance(
std::move(fake_authenticated_channel), fake_delegate_.get(),
ConnectionDetails(kTestDeviceId,
ConnectionMedium::kBluetoothLowEnergy),
&initial_client_list_);
// Once BuildInstance() has finished, |fake_proxy_factory_| is expected to
// have already created one or more instances. Verify that the delegate
// passed to the factory is actually |multiplexed_channel_|.
EXPECT_EQ(static_cast<MultiplexedChannelImpl*>(multiplexed_channel_.get()),
fake_proxy_factory_->expected_delegate());
}
// If |complete_sending| is true, the "on sent" callback is invoked.
int SendMessageAndVerifyState(FakeSingleClientMessageProxy* sending_proxy,
const std::string& feature,
const std::string& payload,
bool complete_sending = true) {
size_t num_sent_message_before_call = sent_messages().size();
int message_counter = next_send_message_counter_++;
sending_proxy->NotifySendMessageRequested(
feature, payload,
base::BindOnce(&SecureChannelMultiplexedChannelImplTest::OnMessageSent,
base::Unretained(this), message_counter));
EXPECT_EQ(num_sent_message_before_call + 1u, sent_messages().size());
EXPECT_EQ(feature, std::get<0>(sent_messages().back()));
EXPECT_EQ(payload, std::get<1>(sent_messages().back()));
if (complete_sending) {
EXPECT_FALSE(HasMessageBeenSent(message_counter));
std::move(std::get<2>(sent_messages().back())).Run();
EXPECT_TRUE(HasMessageBeenSent(message_counter));
}
return message_counter;
}
void ReceiveMessageAndVerifyState(const std::string& feature,
const std::string& payload) {
std::unordered_map<base::UnguessableToken, size_t,
base::UnguessableTokenHash>
proxy_id_to_num_processed_messages_before_call_map;
for (auto& map_entry : id_to_active_proxy_map()) {
proxy_id_to_num_processed_messages_before_call_map[map_entry.first] =
map_entry.second->processed_messages().size();
}
fake_authenticated_channel_->NotifyMessageReceived(feature, payload);
for (auto& map_entry : id_to_active_proxy_map()) {
EXPECT_EQ(
proxy_id_to_num_processed_messages_before_call_map[map_entry.first] +
1u,
map_entry.second->processed_messages().size());
EXPECT_EQ(feature, map_entry.second->processed_messages().back().first);
EXPECT_EQ(payload, map_entry.second->processed_messages().back().second);
}
}
bool HasMessageBeenSent(int message_counter) {
return base::ContainsKey(sent_message_counters_, message_counter);
}
void DisconnectClientAndVerifyState(
FakeSingleClientMessageProxy* sending_proxy,
bool expected_to_be_last_client) {
// If this is the last client left, disconnecting it should result in the
// underlying channel becoming disconnected.
bool is_last_client = id_to_active_proxy_map().size() == 1u;
EXPECT_EQ(is_last_client, expected_to_be_last_client);
base::UnguessableToken proxy_id = sending_proxy->GetProxyId();
// All relevant parties should still indicate that the connection is valid.
EXPECT_TRUE(base::ContainsKey(id_to_active_proxy_map(), proxy_id));
EXPECT_FALSE(
fake_authenticated_channel_->has_disconnection_been_requested());
EXPECT_FALSE(multiplexed_channel_->IsDisconnecting());
EXPECT_FALSE(multiplexed_channel_->IsDisconnected());
EXPECT_FALSE(fake_delegate_->disconnected_connection_details());
// Disconnecting the client should result in the proxy being deleted.
sending_proxy->NotifyClientDisconnected();
EXPECT_FALSE(base::ContainsKey(id_to_active_proxy_map(), proxy_id));
if (!is_last_client)
return;
// Since this was the last client, |multiplexed_channel_| should have
// started the disconnection flow. Because disconnection is asynchronous,
// it should have not yet completed yet.
EXPECT_TRUE(multiplexed_channel_->IsDisconnecting());
EXPECT_FALSE(multiplexed_channel_->IsDisconnected());
EXPECT_TRUE(
fake_authenticated_channel_->has_disconnection_been_requested());
EXPECT_FALSE(fake_delegate_->disconnected_connection_details());
// Complete asynchronous disconnection.
fake_authenticated_channel_->NotifyDisconnected();
// Verify that all relevant parties have been notified of the disconnection.
EXPECT_EQ(multiplexed_channel_->connection_details(),
fake_delegate_->disconnected_connection_details());
EXPECT_FALSE(multiplexed_channel_->IsDisconnecting());
EXPECT_TRUE(multiplexed_channel_->IsDisconnected());
}
void DisconnectRemoteDeviceAndVerifyState() {
// Before the disconnection, all relevant parties should still indicate that
// the connection is valid.
EXPECT_FALSE(multiplexed_channel_->IsDisconnecting());
EXPECT_FALSE(multiplexed_channel_->IsDisconnected());
EXPECT_FALSE(fake_delegate_->disconnected_connection_details());
for (auto& map_entry : id_to_active_proxy_map())
EXPECT_FALSE(map_entry.second->was_remote_device_disconnection_handled());
fake_authenticated_channel_->NotifyDisconnected();
// Verify that the above preconditions have now been changed.
EXPECT_FALSE(multiplexed_channel_->IsDisconnecting());
EXPECT_TRUE(multiplexed_channel_->IsDisconnected());
EXPECT_EQ(multiplexed_channel_->connection_details(),
fake_delegate_->disconnected_connection_details());
for (auto& map_entry : id_to_active_proxy_map())
EXPECT_TRUE(map_entry.second->was_remote_device_disconnection_handled());
}
void AddClientToChannel(const std::string& feature) {
bool success = multiplexed_channel_->AddClientToChannel(
std::make_unique<FakeClientConnectionParameters>(feature));
EXPECT_TRUE(success);
}
void CallGetConnectionMetadataFromDelegate(
FakeSingleClientMessageProxy* proxy) {
proxy->GetConnectionMetadataFromDelegate(base::BindOnce(
&SecureChannelMultiplexedChannelImplTest::OnGetConnectionMetadata,
base::Unretained(this)));
}
void OnGetConnectionMetadata(
mojom::ConnectionMetadataPtr connection_metadata) {
connection_metadata_ = std::move(connection_metadata);
}
std::vector<std::unique_ptr<ClientConnectionParameters>>&
initial_client_list() {
return initial_client_list_;
}
std::unordered_map<base::UnguessableToken,
FakeSingleClientMessageProxy*,
base::UnguessableTokenHash>&
id_to_active_proxy_map() {
return fake_proxy_factory_->id_to_active_proxy_map();
}
std::vector<std::tuple<std::string, std::string, base::OnceClosure>>&
sent_messages() {
return fake_authenticated_channel_->sent_messages();
}
FakeAuthenticatedChannel* fake_authenticated_channel() {
return fake_authenticated_channel_;
}
mojom::ConnectionMetadataPtr connection_metadata_;
private:
void OnMessageSent(int message_counter) {
sent_message_counters_.insert(message_counter);
}
base::test::ScopedTaskEnvironment scoped_task_environment_;
int next_send_message_counter_ = 0;
std::unordered_set<int> sent_message_counters_;
std::unique_ptr<FakeSingleClientMessageProxyImplFactory> fake_proxy_factory_;
std::vector<std::unique_ptr<ClientConnectionParameters>> initial_client_list_;
FakeAuthenticatedChannel* fake_authenticated_channel_ = nullptr;
std::unique_ptr<FakeMultiplexedChannelDelegate> fake_delegate_;
std::unique_ptr<MultiplexedChannel> multiplexed_channel_;
DISALLOW_COPY_AND_ASSIGN(SecureChannelMultiplexedChannelImplTest);
};
TEST_F(SecureChannelMultiplexedChannelImplTest, ConnectionMetadata) {
CreateChannel();
EXPECT_EQ(1u, id_to_active_proxy_map().size());
std::vector<mojom::ConnectionCreationDetail> creation_details{
mojom::ConnectionCreationDetail::
REMOTE_DEVICE_USED_BACKGROUND_BLE_ADVERTISING};
// Set connection metadata on |fake_authenticated_channel_|.
mojom::ConnectionMetadataPtr metadata = mojom::ConnectionMetadata::New(
creation_details,
mojom::BluetoothConnectionMetadata::New(-24 /* current_rssi */),
"channel_binding_data_1");
fake_authenticated_channel()->set_connection_metadata_for_next_call(
std::move(metadata));
// Retrieving the metadata through the proxy should cause
// |fake_authenticated_channel_|'s metadata to be passed through
// |multiplexed_channel_|.
CallGetConnectionMetadataFromDelegate(
id_to_active_proxy_map().begin()->second);
EXPECT_EQ(creation_details, connection_metadata_->creation_details);
EXPECT_EQ(-24,
connection_metadata_->bluetooth_connection_metadata->current_rssi);
EXPECT_EQ("channel_binding_data_1",
connection_metadata_->channel_binding_data);
// Now, change the values and set them on |fake_authenticated_channel_|.
creation_details.clear();
metadata = mojom::ConnectionMetadata::New(
creation_details,
mojom::BluetoothConnectionMetadata::New(-8 /* current_rssi */),
"channel_binding_data_2");
fake_authenticated_channel()->set_connection_metadata_for_next_call(
std::move(metadata));
// The new updates should be available.
CallGetConnectionMetadataFromDelegate(
id_to_active_proxy_map().begin()->second);
EXPECT_EQ(creation_details, connection_metadata_->creation_details);
EXPECT_EQ(-8,
connection_metadata_->bluetooth_connection_metadata->current_rssi);
EXPECT_EQ("channel_binding_data_2",
connection_metadata_->channel_binding_data);
DisconnectClientAndVerifyState(id_to_active_proxy_map().begin()->second,
true /* expected_to_be_last_client */);
}
TEST_F(SecureChannelMultiplexedChannelImplTest,
OneClient_SendReceive_DisconnectFromClient) {
CreateChannel();
EXPECT_EQ(1u, id_to_active_proxy_map().size());
SendMessageAndVerifyState(
id_to_active_proxy_map().begin()->second /* sending_proxy */, "feature1",
"payload1");
ReceiveMessageAndVerifyState("feature2", "payload2");
DisconnectClientAndVerifyState(id_to_active_proxy_map().begin()->second,
true /* expected_to_be_last_client */);
}
TEST_F(SecureChannelMultiplexedChannelImplTest,
OneClient_SendAsync_DisconnectFromRemoteDevice) {
CreateChannel();
EXPECT_EQ(1u, id_to_active_proxy_map().size());
// Start sending two messages, but do not complete the asynchronous sending.
int counter1 = SendMessageAndVerifyState(
id_to_active_proxy_map().begin()->second /* sending_proxy */, "feature1",
"payload1", false /* complete_sending */);
int counter2 = SendMessageAndVerifyState(
id_to_active_proxy_map().begin()->second /* sending_proxy */, "feature2",
"payload2", false /* complete_sending */);
EXPECT_FALSE(HasMessageBeenSent(counter1));
EXPECT_FALSE(HasMessageBeenSent(counter2));
EXPECT_EQ(2u, sent_messages().size());
// Finish sending the first message.
std::move(std::get<2>(sent_messages()[0])).Run();
EXPECT_TRUE(HasMessageBeenSent(counter1));
EXPECT_FALSE(HasMessageBeenSent(counter2));
// Before the second message is sent successfully, disconnect.
DisconnectRemoteDeviceAndVerifyState();
}
TEST_F(SecureChannelMultiplexedChannelImplTest,
TwoInitialClient_OneAdded_DisconnectFromClient) {
// Add a second initial client.
initial_client_list().push_back(
std::make_unique<FakeClientConnectionParameters>(kTestFeature));
CreateChannel();
EXPECT_EQ(2u, id_to_active_proxy_map().size());
// Send a message from the first client.
SendMessageAndVerifyState(
id_to_active_proxy_map().begin()->second /* sending_proxy */, "feature1",
"payload1");
// Receive a message (both clients should receive).
ReceiveMessageAndVerifyState("feature2", "payload2");
// Send a message from the second client.
auto it = id_to_active_proxy_map().begin();
std::advance(it, 1);
SendMessageAndVerifyState(it->second /* sending_proxy */, "feature3",
"payload3");
// Receive a message (both clients should receive).
ReceiveMessageAndVerifyState("feature4", "payload4");
// Create a third client and add it to the channel.
AddClientToChannel(kTestFeature);
EXPECT_EQ(3u, id_to_active_proxy_map().size());
// Send a message from the third client.
it = id_to_active_proxy_map().begin();
std::advance(it, 2);
SendMessageAndVerifyState(it->second, "feature5", "payload5");
// Receive a message (both clients should receive).
ReceiveMessageAndVerifyState("feature6", "payload6");
// Disconnect the clients, one by one. Only the last client should actually
// trigger |fake_authenticated_channel_| to disconnect.
DisconnectClientAndVerifyState(id_to_active_proxy_map().begin()->second,
false /* expected_to_be_last_client */);
EXPECT_EQ(2u, id_to_active_proxy_map().size());
DisconnectClientAndVerifyState(id_to_active_proxy_map().begin()->second,
false /* expected_to_be_last_client */);
EXPECT_EQ(1u, id_to_active_proxy_map().size());
DisconnectClientAndVerifyState(id_to_active_proxy_map().begin()->second,
true /* expected_to_be_last_client */);
}
} // namespace secure_channel
} // namespace chromeos