blob: 14f812339190ba1fcb2fcec1f266464b0541b16c [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/connection_attempt_base.h"
#include <algorithm>
#include <memory>
#include "base/bind.h"
#include "base/test/scoped_task_environment.h"
#include "base/test/test_simple_task_runner.h"
#include "chromeos/services/secure_channel/ble_initiator_failure_type.h"
#include "chromeos/services/secure_channel/connection_attempt_details.h"
#include "chromeos/services/secure_channel/connection_details.h"
#include "chromeos/services/secure_channel/connection_medium.h"
#include "chromeos/services/secure_channel/connection_role.h"
#include "chromeos/services/secure_channel/device_id_pair.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_connect_to_device_operation.h"
#include "chromeos/services/secure_channel/fake_connection_attempt_delegate.h"
#include "chromeos/services/secure_channel/fake_connection_delegate.h"
#include "chromeos/services/secure_channel/fake_pending_connection_request.h"
#include "chromeos/services/secure_channel/pending_connection_request_delegate.h"
#include "chromeos/services/secure_channel/public/cpp/shared/connection_priority.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace secure_channel {
namespace {
const char kTestRemoteDeviceId[] = "testRemoteDeviceId";
const char kTestLocalDeviceId[] = "testLocalDeviceId";
// Since ConnectionAttemptBase is templatized, a concrete implementation
// is needed for its test.
class TestConnectionAttempt
: public ConnectionAttemptBase<BleInitiatorFailureType> {
public:
TestConnectionAttempt(FakeConnectionAttemptDelegate* delegate)
: ConnectionAttemptBase<BleInitiatorFailureType>(
delegate,
ConnectionAttemptDetails(kTestRemoteDeviceId,
kTestLocalDeviceId,
ConnectionMedium::kBluetoothLowEnergy,
ConnectionRole::kListenerRole)) {}
~TestConnectionAttempt() override = default;
FakeConnectToDeviceOperation<BleInitiatorFailureType>* fake_operation() {
return fake_operation_;
}
private:
// ConnectionAttemptBase<BleInitiatorFailureType>:
std::unique_ptr<ConnectToDeviceOperation<BleInitiatorFailureType>>
CreateConnectToDeviceOperation(
const DeviceIdPair& device_id_pair,
ConnectionPriority connection_priority,
ConnectToDeviceOperation<
BleInitiatorFailureType>::ConnectionSuccessCallback success_callback,
const ConnectToDeviceOperation<BleInitiatorFailureType>::
ConnectionFailedCallback& failure_callback) override {
EXPECT_FALSE(fake_operation_);
auto fake_operation =
std::make_unique<FakeConnectToDeviceOperation<BleInitiatorFailureType>>(
std::move(success_callback), failure_callback, connection_priority);
fake_operation_ = fake_operation.get();
return std::move(fake_operation);
}
FakeConnectToDeviceOperation<BleInitiatorFailureType>* fake_operation_ =
nullptr;
DISALLOW_COPY_AND_ASSIGN(TestConnectionAttempt);
};
} // namespace
class SecureChannelConnectionAttemptBaseTest : public testing::Test {
protected:
SecureChannelConnectionAttemptBaseTest() = default;
~SecureChannelConnectionAttemptBaseTest() override = default;
void SetUp() override {
fake_authenticated_channel_ = std::make_unique<FakeAuthenticatedChannel>();
fake_delegate_ = std::make_unique<FakeConnectionAttemptDelegate>();
connection_attempt_ =
std::make_unique<TestConnectionAttempt>(fake_delegate_.get());
}
void TearDown() override {
// ExtractClientConnectionParameters() tests destroy |connection_attempt_|,
// so no additional verifications should be performed.
if (is_extract_client_data_test_)
return;
// If the operation did not complete successfully, the operation should be
// canceled.
bool should_operation_be_canceled_in_destructor =
fake_delegate_->authenticated_channel() == nullptr;
if (should_operation_be_canceled_in_destructor) {
EXPECT_FALSE(fake_operation()->canceled());
EXPECT_FALSE(was_operation_canceled_in_tear_down_);
fake_operation()->set_cancel_callback(
base::BindOnce(&SecureChannelConnectionAttemptBaseTest::
OnOperationCanceledInTeardown,
base::Unretained(this)));
}
connection_attempt_.reset();
if (should_operation_be_canceled_in_destructor)
EXPECT_TRUE(was_operation_canceled_in_tear_down_);
}
FakePendingConnectionRequest<BleInitiatorFailureType>* AddNewRequest(
ConnectionPriority connection_priority) {
auto request =
std::make_unique<FakePendingConnectionRequest<BleInitiatorFailureType>>(
connection_attempt_.get(), connection_priority);
FakePendingConnectionRequest<BleInitiatorFailureType>* request_raw =
request.get();
active_requests_.insert(request_raw);
ConnectionAttempt<BleInitiatorFailureType>* connection_attempt =
connection_attempt_.get();
connection_attempt->AddPendingConnectionRequest(std::move(request));
return request_raw;
}
void FinishRequestWithoutConnection(
FakePendingConnectionRequest<BleInitiatorFailureType>* request,
PendingConnectionRequestDelegate::FailedConnectionReason reason) {
request->NotifyRequestFinishedWithoutConnection(reason);
EXPECT_EQ(1u, active_requests_.erase(request));
}
void FailOperation() {
// Before failing the operation, check to see how many failure details each
// request has been passed.
std::unordered_map<base::UnguessableToken, size_t,
base::UnguessableTokenHash>
id_to_num_details_map;
for (const auto* request : active_requests_) {
id_to_num_details_map[request->GetRequestId()] =
request->handled_failure_details().size();
}
fake_operation()->OnFailedConnectionAttempt(
BleInitiatorFailureType::kAuthenticationError);
// Now, ensure that each active request had one additional failure detail
// added, and verify that it was kAuthenticationError.
for (const auto* request : active_requests_) {
EXPECT_EQ(id_to_num_details_map[request->GetRequestId()] + 1,
request->handled_failure_details().size());
EXPECT_EQ(BleInitiatorFailureType::kAuthenticationError,
request->handled_failure_details().back());
}
}
void FinishOperationSuccessfully() {
EXPECT_TRUE(fake_authenticated_channel_);
auto* fake_authenticated_channel_raw = fake_authenticated_channel_.get();
fake_operation()->OnSuccessfulConnectionAttempt(
std::move(fake_authenticated_channel_));
// |fake_delegate_|'s delegate should have received the
// AuthenticatedChannel.
EXPECT_TRUE(connection_attempt_->connection_attempt_details()
.CorrespondsToConnectionDetails(
*fake_delegate_->connection_details()));
EXPECT_EQ(fake_authenticated_channel_raw,
fake_delegate_->authenticated_channel());
}
void VerifyDelegateNotNotified() {
EXPECT_FALSE(fake_delegate_->connection_details());
EXPECT_FALSE(fake_delegate_->connection_attempt_details());
}
void VerifyDelegateNotifiedOfFailure() {
// |fake_delegate_| should have received the failing attempt's ID but no
// AuthenticatedChannel.
EXPECT_EQ(connection_attempt_->connection_attempt_details(),
fake_delegate_->connection_attempt_details());
EXPECT_FALSE(fake_delegate_->connection_details());
EXPECT_FALSE(fake_delegate_->authenticated_channel());
}
std::vector<std::unique_ptr<ClientConnectionParameters>>
ExtractClientConnectionParameters() {
is_extract_client_data_test_ = true;
return ConnectionAttempt<BleInitiatorFailureType>::
ExtractClientConnectionParameters(std::move(connection_attempt_));
}
FakeConnectToDeviceOperation<BleInitiatorFailureType>* fake_operation() {
return connection_attempt_->fake_operation();
}
private:
void OnOperationCanceledInTeardown() {
was_operation_canceled_in_tear_down_ = true;
}
base::test::ScopedTaskEnvironment scoped_task_environment_;
std::unique_ptr<FakeConnectionAttemptDelegate> fake_delegate_;
std::unique_ptr<FakeAuthenticatedChannel> fake_authenticated_channel_;
std::set<FakePendingConnectionRequest<BleInitiatorFailureType>*>
active_requests_;
bool was_operation_canceled_in_tear_down_ = false;
bool is_extract_client_data_test_ = false;
std::unique_ptr<TestConnectionAttempt> connection_attempt_;
DISALLOW_COPY_AND_ASSIGN(SecureChannelConnectionAttemptBaseTest);
};
TEST_F(SecureChannelConnectionAttemptBaseTest, SingleRequest_Success) {
AddNewRequest(ConnectionPriority::kLow);
EXPECT_EQ(ConnectionPriority::kLow, fake_operation()->connection_priority());
FinishOperationSuccessfully();
}
TEST_F(SecureChannelConnectionAttemptBaseTest, SingleRequest_Fails) {
FakePendingConnectionRequest<BleInitiatorFailureType>* request =
AddNewRequest(ConnectionPriority::kLow);
EXPECT_EQ(ConnectionPriority::kLow, fake_operation()->connection_priority());
// Fail the operation; the delegate should not have been notified since no
// request has yet indicated failure.
FailOperation();
VerifyDelegateNotNotified();
FinishRequestWithoutConnection(
request,
PendingConnectionRequestDelegate::FailedConnectionReason::kRequestFailed);
VerifyDelegateNotifiedOfFailure();
}
TEST_F(SecureChannelConnectionAttemptBaseTest, SingleRequest_Canceled) {
// Simulate the request being canceled.
FakePendingConnectionRequest<BleInitiatorFailureType>* request =
AddNewRequest(ConnectionPriority::kLow);
FinishRequestWithoutConnection(
request, PendingConnectionRequestDelegate::FailedConnectionReason::
kRequestCanceledByClient);
VerifyDelegateNotifiedOfFailure();
}
TEST_F(SecureChannelConnectionAttemptBaseTest, SingleRequest_FailThenSuccess) {
AddNewRequest(ConnectionPriority::kLow);
EXPECT_EQ(ConnectionPriority::kLow, fake_operation()->connection_priority());
// Fail the operation; the delegate should not have been notified since no
// request has yet indicated failure.
FailOperation();
VerifyDelegateNotNotified();
EXPECT_EQ(ConnectionPriority::kLow, fake_operation()->connection_priority());
FinishOperationSuccessfully();
}
TEST_F(SecureChannelConnectionAttemptBaseTest, TwoRequests_Success) {
AddNewRequest(ConnectionPriority::kLow);
EXPECT_EQ(ConnectionPriority::kLow, fake_operation()->connection_priority());
// Add a second request; the first operation should still be active.
AddNewRequest(ConnectionPriority::kLow);
FinishOperationSuccessfully();
}
TEST_F(SecureChannelConnectionAttemptBaseTest, TwoRequests_Fails) {
FakePendingConnectionRequest<BleInitiatorFailureType>* request1 =
AddNewRequest(ConnectionPriority::kLow);
EXPECT_EQ(ConnectionPriority::kLow, fake_operation()->connection_priority());
// Add a second request.
FakePendingConnectionRequest<BleInitiatorFailureType>* request2 =
AddNewRequest(ConnectionPriority::kLow);
// Fail the operation; the delegate should not have been notified since no
// request has yet indicated failure.
FailOperation();
VerifyDelegateNotNotified();
// Finish the first request; since a second request remains, the delegate
// should not have been notified.
FinishRequestWithoutConnection(
request1,
PendingConnectionRequestDelegate::FailedConnectionReason::kRequestFailed);
VerifyDelegateNotNotified();
// Finish the second request, which should cause the delegate to be notified.
FinishRequestWithoutConnection(
request2,
PendingConnectionRequestDelegate::FailedConnectionReason::kRequestFailed);
VerifyDelegateNotifiedOfFailure();
}
TEST_F(SecureChannelConnectionAttemptBaseTest, TwoRequests_Canceled) {
FakePendingConnectionRequest<BleInitiatorFailureType>* request1 =
AddNewRequest(ConnectionPriority::kLow);
FakePendingConnectionRequest<BleInitiatorFailureType>* request2 =
AddNewRequest(ConnectionPriority::kLow);
FinishRequestWithoutConnection(
request1, PendingConnectionRequestDelegate::FailedConnectionReason::
kRequestCanceledByClient);
VerifyDelegateNotNotified();
FinishRequestWithoutConnection(
request2, PendingConnectionRequestDelegate::FailedConnectionReason::
kRequestCanceledByClient);
VerifyDelegateNotifiedOfFailure();
}
TEST_F(SecureChannelConnectionAttemptBaseTest, TwoRequests_FailThenSuccess) {
FakePendingConnectionRequest<BleInitiatorFailureType>* request1 =
AddNewRequest(ConnectionPriority::kLow);
EXPECT_EQ(ConnectionPriority::kLow, fake_operation()->connection_priority());
// Fail the operation.
FailOperation();
VerifyDelegateNotNotified();
EXPECT_EQ(ConnectionPriority::kLow, fake_operation()->connection_priority());
// Add a second request.
AddNewRequest(ConnectionPriority::kLow);
FailOperation();
VerifyDelegateNotNotified();
// Simulate the first request finishing due to failures; since a second
// request remains, the delegate should not have been notified.
FinishRequestWithoutConnection(
request1,
PendingConnectionRequestDelegate::FailedConnectionReason::kRequestFailed);
VerifyDelegateNotNotified();
EXPECT_EQ(ConnectionPriority::kLow, fake_operation()->connection_priority());
FinishOperationSuccessfully();
}
TEST_F(SecureChannelConnectionAttemptBaseTest, ManyRequests_UpdatePriority) {
AddNewRequest(ConnectionPriority::kLow);
EXPECT_EQ(ConnectionPriority::kLow, fake_operation()->connection_priority());
// Add a medium-priority request. This should update the operation's priority
// as well.
FakePendingConnectionRequest<BleInitiatorFailureType>* request2 =
AddNewRequest(ConnectionPriority::kMedium);
EXPECT_EQ(ConnectionPriority::kMedium,
fake_operation()->connection_priority());
// Add a high-priority request and verify that the operation is updated.
FakePendingConnectionRequest<BleInitiatorFailureType>* request3 =
AddNewRequest(ConnectionPriority::kHigh);
EXPECT_EQ(ConnectionPriority::kHigh, fake_operation()->connection_priority());
// Remote the high-priority request; the operation should go back to medium.
FinishRequestWithoutConnection(
request3, PendingConnectionRequestDelegate::FailedConnectionReason::
kRequestCanceledByClient);
EXPECT_EQ(ConnectionPriority::kMedium,
fake_operation()->connection_priority());
// Remote the medium-priority request; the operation should go back to low.
FinishRequestWithoutConnection(
request2, PendingConnectionRequestDelegate::FailedConnectionReason::
kRequestCanceledByClient);
EXPECT_EQ(ConnectionPriority::kLow, fake_operation()->connection_priority());
FinishOperationSuccessfully();
}
TEST_F(SecureChannelConnectionAttemptBaseTest,
ExtractClientConnectionParameters) {
FakePendingConnectionRequest<BleInitiatorFailureType>* request1 =
AddNewRequest(ConnectionPriority::kLow);
auto fake_parameters_1 =
std::make_unique<FakeClientConnectionParameters>("request1Feature");
auto* fake_parameters_1_raw = fake_parameters_1.get();
request1->set_client_data_for_extraction(std::move(fake_parameters_1));
FakePendingConnectionRequest<BleInitiatorFailureType>* request2 =
AddNewRequest(ConnectionPriority::kLow);
auto fake_parameters_2 =
std::make_unique<FakeClientConnectionParameters>("request2Feature");
auto* fake_parameters_2_raw = fake_parameters_2.get();
request2->set_client_data_for_extraction(std::move(fake_parameters_2));
auto extracted_client_data = ExtractClientConnectionParameters();
EXPECT_EQ(2u, extracted_client_data.size());
// The extracted client data may not be returned in the same order that as the
// associated requests were added to |conenction_attempt_|, since
// ConnectionAttemptBase internally utilizes a std::unordered_map. Sort the
// data before making verifications to ensure correctness.
std::sort(extracted_client_data.begin(), extracted_client_data.end(),
[](const auto& ptr_1, const auto& ptr_2) {
return ptr_1->feature() < ptr_2->feature();
});
EXPECT_EQ(fake_parameters_1_raw, extracted_client_data[0].get());
EXPECT_EQ(fake_parameters_2_raw, extracted_client_data[1].get());
}
} // namespace secure_channel
} // namespace chromeos