| /* |
| * Copyright 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "hci/acl_manager/classic_acl_connection.h" |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include <chrono> |
| #include <cstdint> |
| #include <future> |
| #include <list> |
| #include <memory> |
| #include <mutex> |
| #include <queue> |
| #include <vector> |
| |
| #include "hci/acl_connection_interface.h" |
| #include "hci/acl_manager/connection_management_callbacks.h" |
| #include "hci/address.h" |
| #include "hci/hci_packets.h" |
| #include "os/handler.h" |
| #include "os/log.h" |
| #include "os/thread.h" |
| |
| using namespace bluetooth; |
| using namespace std::chrono_literals; |
| |
| namespace { |
| constexpr char kAddress[] = "00:11:22:33:44:55"; |
| constexpr uint16_t kConnectionHandle = 123; |
| constexpr size_t kQueueSize = 10; |
| |
| std::vector<hci::DisconnectReason> disconnect_reason_vector = { |
| hci::DisconnectReason::AUTHENTICATION_FAILURE, |
| hci::DisconnectReason::REMOTE_USER_TERMINATED_CONNECTION, |
| hci::DisconnectReason::REMOTE_DEVICE_TERMINATED_CONNECTION_LOW_RESOURCES, |
| hci::DisconnectReason::REMOTE_DEVICE_TERMINATED_CONNECTION_POWER_OFF, |
| hci::DisconnectReason::UNSUPPORTED_REMOTE_FEATURE, |
| hci::DisconnectReason::PAIRING_WITH_UNIT_KEY_NOT_SUPPORTED, |
| hci::DisconnectReason::UNACCEPTABLE_CONNECTION_PARAMETERS, |
| }; |
| |
| std::vector<hci::ErrorCode> error_code_vector = { |
| hci::ErrorCode::SUCCESS, |
| hci::ErrorCode::UNKNOWN_HCI_COMMAND, |
| hci::ErrorCode::UNKNOWN_CONNECTION, |
| hci::ErrorCode::HARDWARE_FAILURE, |
| hci::ErrorCode::PAGE_TIMEOUT, |
| hci::ErrorCode::AUTHENTICATION_FAILURE, |
| hci::ErrorCode::PIN_OR_KEY_MISSING, |
| hci::ErrorCode::MEMORY_CAPACITY_EXCEEDED, |
| hci::ErrorCode::CONNECTION_TIMEOUT, |
| hci::ErrorCode::CONNECTION_LIMIT_EXCEEDED, |
| hci::ErrorCode::SYNCHRONOUS_CONNECTION_LIMIT_EXCEEDED, |
| hci::ErrorCode::CONNECTION_ALREADY_EXISTS, |
| hci::ErrorCode::COMMAND_DISALLOWED, |
| hci::ErrorCode::CONNECTION_REJECTED_LIMITED_RESOURCES, |
| hci::ErrorCode::CONNECTION_REJECTED_SECURITY_REASONS, |
| hci::ErrorCode::CONNECTION_REJECTED_UNACCEPTABLE_BD_ADDR, |
| hci::ErrorCode::CONNECTION_ACCEPT_TIMEOUT, |
| hci::ErrorCode::UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE, |
| hci::ErrorCode::INVALID_HCI_COMMAND_PARAMETERS, |
| hci::ErrorCode::REMOTE_USER_TERMINATED_CONNECTION, |
| hci::ErrorCode::REMOTE_DEVICE_TERMINATED_CONNECTION_LOW_RESOURCES, |
| hci::ErrorCode::REMOTE_DEVICE_TERMINATED_CONNECTION_POWER_OFF, |
| hci::ErrorCode::CONNECTION_TERMINATED_BY_LOCAL_HOST, |
| hci::ErrorCode::REPEATED_ATTEMPTS, |
| hci::ErrorCode::PAIRING_NOT_ALLOWED, |
| hci::ErrorCode::UNKNOWN_LMP_PDU, |
| hci::ErrorCode::UNSUPPORTED_REMOTE_OR_LMP_FEATURE, |
| hci::ErrorCode::SCO_OFFSET_REJECTED, |
| hci::ErrorCode::SCO_INTERVAL_REJECTED, |
| hci::ErrorCode::SCO_AIR_MODE_REJECTED, |
| hci::ErrorCode::INVALID_LMP_OR_LL_PARAMETERS, |
| hci::ErrorCode::UNSPECIFIED_ERROR, |
| hci::ErrorCode::UNSUPPORTED_LMP_OR_LL_PARAMETER, |
| hci::ErrorCode::ROLE_CHANGE_NOT_ALLOWED, |
| hci::ErrorCode::TRANSACTION_RESPONSE_TIMEOUT, |
| hci::ErrorCode::LINK_LAYER_COLLISION, |
| hci::ErrorCode::ENCRYPTION_MODE_NOT_ACCEPTABLE, |
| hci::ErrorCode::ROLE_SWITCH_FAILED, |
| hci::ErrorCode::CONTROLLER_BUSY, |
| hci::ErrorCode::ADVERTISING_TIMEOUT, |
| hci::ErrorCode::CONNECTION_FAILED_ESTABLISHMENT, |
| hci::ErrorCode::LIMIT_REACHED, |
| hci::ErrorCode::STATUS_UNKNOWN, |
| }; |
| |
| // Generic template for all commands |
| template <typename T, typename U> |
| T CreateCommand(U u) { |
| T command; |
| return command; |
| } |
| |
| template <> |
| hci::DisconnectView CreateCommand(std::shared_ptr<std::vector<uint8_t>> bytes) { |
| return hci::DisconnectView::Create( |
| hci::AclCommandView::Create(hci::CommandView::Create(hci::PacketView<hci::kLittleEndian>(bytes)))); |
| } |
| |
| } // namespace |
| |
| class TestAclConnectionInterface : public hci::AclConnectionInterface { |
| private: |
| void EnqueueCommand( |
| std::unique_ptr<hci::AclCommandBuilder> command, |
| common::ContextualOnceCallback<void(hci::CommandStatusView)> on_status) override { |
| const std::lock_guard<std::mutex> lock(command_queue_mutex_); |
| command_queue_.push(std::move(command)); |
| command_status_callbacks.push_back(std::move(on_status)); |
| if (command_promise_ != nullptr) { |
| std::promise<void>* prom = command_promise_.release(); |
| prom->set_value(); |
| delete prom; |
| } |
| } |
| |
| void EnqueueCommand( |
| std::unique_ptr<hci::AclCommandBuilder> command, |
| common::ContextualOnceCallback<void(hci::CommandCompleteView)> on_complete) override { |
| const std::lock_guard<std::mutex> lock(command_queue_mutex_); |
| command_queue_.push(std::move(command)); |
| command_complete_callbacks.push_back(std::move(on_complete)); |
| if (command_promise_ != nullptr) { |
| std::promise<void>* prom = command_promise_.release(); |
| prom->set_value(); |
| delete prom; |
| } |
| } |
| |
| public: |
| virtual ~TestAclConnectionInterface() = default; |
| |
| std::unique_ptr<hci::CommandBuilder> DequeueCommand() { |
| const std::lock_guard<std::mutex> lock(command_queue_mutex_); |
| auto packet = std::move(command_queue_.front()); |
| command_queue_.pop(); |
| return std::move(packet); |
| } |
| |
| std::shared_ptr<std::vector<uint8_t>> DequeueCommandBytes() { |
| auto command = DequeueCommand(); |
| auto bytes = std::make_shared<std::vector<uint8_t>>(); |
| packet::BitInserter bi(*bytes); |
| command->Serialize(bi); |
| return bytes; |
| } |
| |
| bool IsPacketQueueEmpty() const { |
| const std::lock_guard<std::mutex> lock(command_queue_mutex_); |
| return command_queue_.empty(); |
| } |
| |
| size_t NumberOfQueuedCommands() const { |
| const std::lock_guard<std::mutex> lock(command_queue_mutex_); |
| return command_queue_.size(); |
| } |
| |
| private: |
| std::list<common::ContextualOnceCallback<void(hci::CommandCompleteView)>> command_complete_callbacks; |
| std::list<common::ContextualOnceCallback<void(hci::CommandStatusView)>> command_status_callbacks; |
| std::queue<std::unique_ptr<hci::CommandBuilder>> command_queue_; |
| mutable std::mutex command_queue_mutex_; |
| std::unique_ptr<std::promise<void>> command_promise_; |
| std::unique_ptr<std::future<void>> command_future_; |
| }; |
| |
| class TestConnectionManagementCallbacks : public hci::acl_manager::ConnectionManagementCallbacks { |
| public: |
| ~TestConnectionManagementCallbacks() = default; |
| void OnConnectionPacketTypeChanged(uint16_t packet_type) override {} |
| void OnAuthenticationComplete(hci::ErrorCode hci_status) override {} |
| void OnEncryptionChange(hci::EncryptionEnabled enabled) override {} |
| void OnChangeConnectionLinkKeyComplete() override {} |
| void OnReadClockOffsetComplete(uint16_t clock_offset) override {} |
| void OnModeChange(hci::ErrorCode status, hci::Mode current_mode, uint16_t interval) override {} |
| void OnSniffSubrating( |
| hci::ErrorCode hci_status, |
| uint16_t maximum_transmit_latency, |
| uint16_t maximum_receive_latency, |
| uint16_t minimum_remote_timeout, |
| uint16_t minimum_local_timeout) override {} |
| void OnQosSetupComplete( |
| hci::ServiceType service_type, |
| uint32_t token_rate, |
| uint32_t peak_bandwidth, |
| uint32_t latency, |
| uint32_t delay_variation) override {} |
| void OnFlowSpecificationComplete( |
| hci::FlowDirection flow_direction, |
| hci::ServiceType service_type, |
| uint32_t token_rate, |
| uint32_t token_bucket_size, |
| uint32_t peak_bandwidth, |
| uint32_t access_latency) override {} |
| void OnFlushOccurred() override {} |
| void OnRoleDiscoveryComplete(hci::Role current_role) override {} |
| void OnReadLinkPolicySettingsComplete(uint16_t link_policy_settings) override {} |
| void OnReadAutomaticFlushTimeoutComplete(uint16_t flush_timeout) override {} |
| void OnReadTransmitPowerLevelComplete(uint8_t transmit_power_level) override {} |
| void OnReadLinkSupervisionTimeoutComplete(uint16_t link_supervision_timeout) override {} |
| void OnReadFailedContactCounterComplete(uint16_t failed_contact_counter) override {} |
| void OnReadLinkQualityComplete(uint8_t link_quality) override {} |
| void OnReadAfhChannelMapComplete(hci::AfhMode afh_mode, std::array<uint8_t, 10> afh_channel_map) override {} |
| void OnReadRssiComplete(uint8_t rssi) override {} |
| void OnReadClockComplete(uint32_t clock, uint16_t accuracy) override {} |
| void OnCentralLinkKeyComplete(hci::KeyFlag key_flag) override {} |
| void OnRoleChange(hci::ErrorCode hci_status, hci::Role new_role) override {} |
| void OnDisconnection(hci::ErrorCode reason) override { |
| on_disconnection_error_code_queue_.push(reason); |
| } |
| void OnReadRemoteVersionInformationComplete( |
| hci::ErrorCode hci_status, uint8_t lmp_version, uint16_t manufacturer_name, uint16_t sub_version) override {} |
| void OnReadRemoteSupportedFeaturesComplete(uint64_t features) override {} |
| void OnReadRemoteExtendedFeaturesComplete(uint8_t page_number, uint8_t max_page_number, uint64_t features) override {} |
| |
| std::queue<hci::ErrorCode> on_disconnection_error_code_queue_; |
| }; |
| |
| namespace bluetooth { |
| namespace hci { |
| namespace acl_manager { |
| |
| class ClassicAclConnectionTest : public ::testing::Test { |
| protected: |
| void SetUp() override { |
| ASSERT_TRUE(hci::Address::FromString(kAddress, address_)); |
| thread_ = new os::Thread("thread", os::Thread::Priority::NORMAL); |
| handler_ = new os::Handler(thread_); |
| queue_ = std::make_shared<hci::acl_manager::AclConnection::Queue>(kQueueSize); |
| sync_handler(); |
| } |
| |
| void TearDown() override { |
| handler_->Clear(); |
| delete handler_; |
| delete thread_; |
| } |
| |
| void sync_handler() { |
| ASSERT(handler_ != nullptr); |
| |
| auto promise = std::promise<void>(); |
| auto future = promise.get_future(); |
| handler_->BindOnceOn(&promise, &std::promise<void>::set_value).Invoke(); |
| auto status = future.wait_for(2s); |
| ASSERT_EQ(status, std::future_status::ready); |
| } |
| |
| Address address_; |
| os::Handler* handler_{nullptr}; |
| os::Thread* thread_{nullptr}; |
| std::shared_ptr<hci::acl_manager::AclConnection::Queue> queue_; |
| |
| TestAclConnectionInterface acl_connection_interface_; |
| TestConnectionManagementCallbacks callbacks_; |
| }; |
| |
| TEST_F(ClassicAclConnectionTest, simple) { |
| AclConnectionInterface* acl_connection_interface = nullptr; |
| ClassicAclConnection* connection = |
| new ClassicAclConnection(queue_, acl_connection_interface, kConnectionHandle, address_); |
| connection->RegisterCallbacks(&callbacks_, handler_); |
| |
| delete connection; |
| } |
| |
| class ClassicAclConnectionWithCallbacksTest : public ClassicAclConnectionTest { |
| protected: |
| void SetUp() override { |
| ClassicAclConnectionTest::SetUp(); |
| connection_ = |
| std::make_unique<ClassicAclConnection>(queue_, &acl_connection_interface_, kConnectionHandle, address_); |
| connection_->RegisterCallbacks(&callbacks_, handler_); |
| is_callbacks_registered_ = true; |
| connection_management_callbacks_ = |
| connection_->GetEventCallbacks([this](uint16_t hci_handle) { is_callbacks_invalidated_ = true; }); |
| is_callbacks_invalidated_ = false; |
| } |
| |
| void TearDown() override { |
| connection_.reset(); |
| ASSERT_TRUE(is_callbacks_invalidated_); |
| ClassicAclConnectionTest::TearDown(); |
| } |
| |
| protected: |
| std::unique_ptr<ClassicAclConnection> connection_; |
| ConnectionManagementCallbacks* connection_management_callbacks_; |
| bool is_callbacks_registered_{false}; |
| bool is_callbacks_invalidated_{false}; |
| }; |
| |
| TEST_F(ClassicAclConnectionWithCallbacksTest, Disconnect) { |
| for (const auto& reason : disconnect_reason_vector) { |
| ASSERT_TRUE(connection_->Disconnect(reason)); |
| } |
| |
| for (const auto& reason : disconnect_reason_vector) { |
| ASSERT_FALSE(acl_connection_interface_.IsPacketQueueEmpty()); |
| auto command = CreateCommand<DisconnectView>(acl_connection_interface_.DequeueCommandBytes()); |
| ASSERT_TRUE(command.IsValid()); |
| ASSERT_EQ(reason, command.GetReason()); |
| ASSERT_EQ(kConnectionHandle, command.GetConnectionHandle()); |
| } |
| ASSERT_TRUE(acl_connection_interface_.IsPacketQueueEmpty()); |
| } |
| |
| TEST_F(ClassicAclConnectionWithCallbacksTest, OnDisconnection) { |
| for (const auto& error_code : error_code_vector) { |
| connection_management_callbacks_->OnDisconnection(error_code); |
| } |
| |
| sync_handler(); |
| ASSERT_TRUE(!callbacks_.on_disconnection_error_code_queue_.empty()); |
| |
| for (const auto& error_code : error_code_vector) { |
| ASSERT_EQ(error_code, callbacks_.on_disconnection_error_code_queue_.front()); |
| callbacks_.on_disconnection_error_code_queue_.pop(); |
| } |
| } |
| |
| } // namespace acl_manager |
| } // namespace hci |
| } // namespace bluetooth |