| // Copyright 2018 The Fuchsia 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 "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/bredr_discovery_manager.h" |
| |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/peer_cache.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/controller_test.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/inspect.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/mock_controller.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_helpers.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_packets.h" |
| |
| namespace bt::gap { |
| namespace { |
| |
| using namespace inspect::testing; |
| |
| using bt::testing::CommandTransaction; |
| |
| using TestingBase = |
| bt::testing::FakeDispatcherControllerTest<bt::testing::MockController>; |
| |
| // clang-format off |
| #define COMMAND_COMPLETE_RSP(opcode) \ |
| StaticByteBuffer(hci_spec::kCommandCompleteEventCode, 0x04, 0xF0, \ |
| LowerBits((opcode)), UpperBits((opcode)), \ |
| pw::bluetooth::emboss::StatusCode::SUCCESS) |
| |
| #define COMMAND_STATUS_RSP(opcode, statuscode) \ |
| StaticByteBuffer( hci_spec::kCommandStatusEventCode, 0x04, \ |
| (statuscode), 0xF0, \ |
| LowerBits((opcode)), UpperBits((opcode))) |
| |
| |
| const StaticByteBuffer kWriteInquiryActivity( |
| LowerBits(hci_spec::kWriteInquiryScanActivity), UpperBits(hci_spec::kWriteInquiryScanActivity), |
| 0x04, // Param total size |
| LowerBits(kInquiryScanInterval), UpperBits(kInquiryScanInterval), |
| LowerBits(kInquiryScanWindow), UpperBits(kInquiryScanWindow) |
| ); |
| |
| const auto kWriteInquiryActivityRsp = COMMAND_COMPLETE_RSP(hci_spec::kWriteInquiryScanActivity); |
| |
| const StaticByteBuffer kWriteInquiryType( |
| LowerBits(hci_spec::kWriteInquiryScanType), UpperBits(hci_spec::kWriteInquiryScanType), |
| 0x01, // Param total size |
| 0x01 // Interlaced Inquiry Scan |
| ); |
| |
| const auto kWriteInquiryTypeRsp = COMMAND_COMPLETE_RSP(hci_spec::kWriteInquiryScanType); |
| // clang-format on |
| |
| class BrEdrDiscoveryManagerTest : public TestingBase { |
| public: |
| BrEdrDiscoveryManagerTest() = default; |
| ~BrEdrDiscoveryManagerTest() override = default; |
| |
| void SetUp() override { |
| TestingBase::SetUp(); |
| |
| NewDiscoveryManager(pw::bluetooth::emboss::InquiryMode::STANDARD); |
| } |
| |
| void TearDown() override { |
| discovery_manager_ = nullptr; |
| TestingBase::TearDown(); |
| } |
| |
| void NewDiscoveryManager(pw::bluetooth::emboss::InquiryMode mode) { |
| // We expect to set the Inquiry Scan and the Type when we start. |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), kWriteInquiryActivity, &kWriteInquiryActivityRsp); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), kWriteInquiryType, &kWriteInquiryTypeRsp); |
| |
| discovery_manager_ = std::make_unique<BrEdrDiscoveryManager>( |
| dispatcher(), |
| transport()->command_channel()->AsWeakPtr(), |
| mode, |
| &peer_cache_); |
| |
| RunUntilIdle(); |
| } |
| |
| void DestroyDiscoveryManager() { discovery_manager_.reset(); } |
| |
| PeerCache* peer_cache() { return &peer_cache_; } |
| |
| protected: |
| BrEdrDiscoveryManager* discovery_manager() const { |
| return discovery_manager_.get(); |
| } |
| |
| private: |
| PeerCache peer_cache_{dispatcher()}; |
| std::unique_ptr<BrEdrDiscoveryManager> discovery_manager_; |
| |
| BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(BrEdrDiscoveryManagerTest); |
| }; |
| |
| using GAP_BrEdrDiscoveryManagerTest = BrEdrDiscoveryManagerTest; |
| |
| // Suffix DeathTest has GoogleTest-specific behavior |
| using BrEdrDiscoveryManagerDeathTest = BrEdrDiscoveryManagerTest; |
| |
| // clang-format off |
| const StaticByteBuffer kInquiry( |
| LowerBits(hci_spec::kInquiry), UpperBits(hci_spec::kInquiry), |
| 0x05, // Paramreter total size |
| 0x33, 0x8B, 0x9E, // GIAC |
| 0x08, // hci_spec::kInquiryLengthDefault |
| 0x00 // Unlimited responses |
| ); |
| |
| const auto kWriteLocalNameRsp = |
| COMMAND_STATUS_RSP(hci_spec::kWriteLocalName, pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| const auto kWriteLocalNameRspError = |
| COMMAND_STATUS_RSP(hci_spec::kWriteLocalName, pw::bluetooth::emboss::StatusCode::HARDWARE_FAILURE); |
| |
| const auto kWriteExtendedInquiryResponseRsp = |
| COMMAND_STATUS_RSP(hci_spec::kWriteExtendedInquiryResponse, pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| const auto kWriteExtendedInquiryResponseRspError = |
| COMMAND_STATUS_RSP(hci_spec::kWriteExtendedInquiryResponse, pw::bluetooth::emboss::StatusCode::HARDWARE_FAILURE); |
| |
| const auto kInquiryRsp = COMMAND_STATUS_RSP(hci_spec::kInquiry, pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| const auto kInquiryRspError = COMMAND_STATUS_RSP(hci_spec::kInquiry, pw::bluetooth::emboss::StatusCode::HARDWARE_FAILURE); |
| |
| const StaticByteBuffer kInquiryComplete( |
| hci_spec::kInquiryCompleteEventCode, |
| 0x01, // parameter_total_size (1 bytes) |
| pw::bluetooth::emboss::StatusCode::SUCCESS |
| ); |
| |
| const StaticByteBuffer kInquiryCompleteError( |
| hci_spec::kInquiryCompleteEventCode, |
| 0x01, // parameter_total_size (1 bytes) |
| pw::bluetooth::emboss::StatusCode::HARDWARE_FAILURE |
| ); |
| |
| #define BD_ADDR(addr1) addr1, 0x00, 0x00, 0x00, 0x00, 0x00 |
| |
| const DeviceAddress kDeviceAddress1(DeviceAddress::Type::kBREDR, |
| {BD_ADDR(0x01)}); |
| const DeviceAddress kLeAliasAddress1(DeviceAddress::Type::kLEPublic, |
| kDeviceAddress1.value()); |
| const DeviceAddress kDeviceAddress2(DeviceAddress::Type::kBREDR, |
| {BD_ADDR(0x02)}); |
| const DeviceAddress kLeAliasAddress2(DeviceAddress::Type::kLEPublic, |
| kDeviceAddress2.value()); |
| const DeviceAddress kDeviceAddress3(DeviceAddress::Type::kBREDR, |
| {BD_ADDR(0x03)}); |
| const DeviceAddress kLeAliasAddress3(DeviceAddress::Type::kLEPublic, |
| kDeviceAddress3.value()); |
| |
| const StaticByteBuffer kInquiryResult( |
| hci_spec::kInquiryResultEventCode, |
| 0x0F, // parameter_total_size (15 bytes) |
| 0x01, // num_responses |
| BD_ADDR(0x01), // bd_addr[0] |
| 0x00, // page_scan_repetition_mode[0] (R0) |
| 0x00, // unused / reserved |
| 0x00, // unused / reserved |
| 0x00, 0x1F, 0x00, // class_of_device[0] (unspecified) |
| 0x00, 0x00 // clock_offset[0] |
| ); |
| |
| const StaticByteBuffer kInquiryResultIncompleteHeader( |
| hci_spec::kInquiryResultEventCode, |
| 0x00 // parameter_total_size (0 bytes) |
| // truncated |
| ); |
| |
| const StaticByteBuffer kInquiryResultMissingResponses( |
| hci_spec::kInquiryResultEventCode, |
| 0x1D, // parameter_total_size (29 bytes) |
| 0x03, // num_responses (only two responses are packed) |
| |
| // first response |
| BD_ADDR(0x01), // bd_addr[0] |
| 0x00, // page_scan_repetition_mode[0] (R0) |
| 0x00, // unused / reserved |
| 0x00, // unused / reserved |
| 0x00, 0x1F, 0x00, // class_of_device[0] (unspecified) |
| 0x00, 0x00, // clock_offset[0] |
| |
| // second response |
| BD_ADDR(0x02), // bd_addr[0] |
| 0x00, // page_scan_repetition_mode[0] (R0) |
| 0x00, // unused / reserved |
| 0x00, // unused / reserved |
| 0x00, 0x1F, 0x00, // class_of_device[0] (unspecified) |
| 0x00, 0x00 // clock_offset[0] |
| ); |
| |
| const StaticByteBuffer kInquiryResultIncompleteResponse( |
| hci_spec::kInquiryResultEventCode, |
| 0x15, // parameter_total_size (21 bytes) |
| 0x02, // num_responses |
| |
| // first response |
| BD_ADDR(0x01), // bd_addr[0] |
| 0x00, // page_scan_repetition_mode[0] (R0) |
| 0x00, // unused / reserved |
| 0x00, // unused / reserved |
| 0x00, 0x1F, 0x00, // class_of_device[0] (unspecified) |
| 0x00, 0x00, // clock_offset[0] |
| |
| // second response |
| BD_ADDR(0x02) // bd_addr[0] |
| // truncated |
| ); |
| |
| const StaticByteBuffer kRSSIInquiryResult( |
| hci_spec::kInquiryResultWithRSSIEventCode, |
| 0x0F, // parameter_total_size (15 bytes) |
| 0x01, // num_responses |
| BD_ADDR(0x02), // bd_addr[0] |
| 0x00, // page_scan_repetition_mode[0] (R0) |
| 0x00, // unused / reserved |
| 0x00, 0x1F, 0x00, // class_of_device[0] (unspecified) |
| 0x00, 0x00, // clock_offset[0] |
| 0xEC // RSSI (-20dBm) |
| ); |
| |
| #define REMOTE_NAME_REQUEST(addr1) StaticByteBuffer( \ |
| LowerBits(hci_spec::kRemoteNameRequest), UpperBits(hci_spec::kRemoteNameRequest), \ |
| 0x0a, /* parameter_total_size (10 bytes) */ \ |
| BD_ADDR(addr1), /* BD_ADDR */ \ |
| 0x00, 0x00, 0x00, 0x80 /* page_scan_repetition_mode, 0, clock_offset */ \ |
| ); |
| |
| const auto kRemoteNameRequest1 = REMOTE_NAME_REQUEST(0x01) |
| const auto kRemoteNameRequest2 = REMOTE_NAME_REQUEST(0x02) |
| |
| #undef REMOTE_NAME_REQUEST |
| |
| const auto kRemoteNameRequestRsp = |
| COMMAND_STATUS_RSP(hci_spec::kRemoteNameRequest, pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| #undef COMMAND_STATUS_RSP |
| |
| const auto kRemoteNameRequestComplete1 = testing::RemoteNameRequestCompletePacket( |
| kDeviceAddress1, {'F', 'u', 'c', 'h', 's', 'i', 'a', '\xF0', '\x9F', |
| '\x92', '\x96', '\x00', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', |
| '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', '\x20'} |
| // remote name (Fuchsia💖) |
| // Everything after the 0x00 should be ignored. |
| ); |
| const auto kRemoteNameRequestComplete2 = |
| testing::RemoteNameRequestCompletePacket(kDeviceAddress2, "Sapphire"); |
| |
| const StaticByteBuffer kExtendedInquiryResult( |
| hci_spec::kExtendedInquiryResultEventCode, |
| 0xFF, // parameter_total_size (255 bytes) |
| 0x01, // num_responses |
| BD_ADDR(0x03), // bd_addr |
| 0x00, // page_scan_repetition_mode (R0) |
| 0x00, // unused / reserved |
| 0x00, 0x1F, 0x00, // class_of_device (unspecified) |
| 0x00, 0x00, // clock_offset |
| 0xEC, // RSSI (-20dBm) |
| // Extended Inquiry Response (240 bytes total) |
| // Complete Local Name (12 bytes): Fuchsia 💖 |
| 0x0C, 0x09, 'F', 'u', 'c', 'h', 's', 'i', 'a', 0xF0, 0x9F, 0x92, 0x96, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 |
| ); |
| |
| #undef BD_ADDR |
| |
| const StaticByteBuffer kSetExtendedMode( |
| LowerBits(hci_spec::kWriteInquiryMode), UpperBits(hci_spec::kWriteInquiryMode), |
| 0x01, // parameter_total_size |
| 0x02 // Extended Inquiry Result or Inquiry Result with RSSI |
| ); |
| |
| const auto kSetExtendedModeRsp = COMMAND_COMPLETE_RSP(hci_spec::kWriteInquiryMode); |
| |
| const StaticByteBuffer kReadScanEnable( |
| LowerBits(hci_spec::kReadScanEnable), UpperBits(hci_spec::kReadScanEnable), |
| 0x00 // No parameters |
| ); |
| |
| const StaticByteBuffer kWriteLocalName( |
| LowerBits(hci_spec::kWriteLocalName), UpperBits(hci_spec::kWriteLocalName), |
| 0xF8, // parameter_total_size (248 bytes) |
| // Complete Local Name () |
| 'A', 'B', 'C', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00 |
| ); |
| |
| const StaticByteBuffer kWriteExtendedInquiryResponse( |
| LowerBits(hci_spec::kWriteExtendedInquiryResponse), |
| UpperBits(hci_spec::kWriteExtendedInquiryResponse), |
| 0xF1, // parameter_total_size (241 bytes) |
| 0x00, // fec_required |
| 0x04, // name_length + 1 |
| 0x09, // DataType::kCompleteLocalName, |
| // Complete Local Name (3 bytes + 1 byte null terminator + 234 bytes of zero padding) |
| 'A', 'B', 'C', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 |
| ); |
| |
| #define READ_SCAN_ENABLE_RSP(scan_enable) \ |
| StaticByteBuffer(hci_spec::kCommandCompleteEventCode, 0x05, 0xF0, \ |
| LowerBits(hci_spec::kReadScanEnable), \ |
| UpperBits(hci_spec::kReadScanEnable), \ |
| pw::bluetooth::emboss::StatusCode::SUCCESS, (scan_enable)) |
| |
| const auto kReadScanEnableRspNone = READ_SCAN_ENABLE_RSP(0x00); |
| const auto kReadScanEnableRspInquiry = READ_SCAN_ENABLE_RSP(0x01); |
| const auto kReadScanEnableRspPage = READ_SCAN_ENABLE_RSP(0x02); |
| const auto kReadScanEnableRspBoth = READ_SCAN_ENABLE_RSP(0x03); |
| |
| #undef READ_SCAN_ENABLE_RSP |
| |
| #define WRITE_SCAN_ENABLE_CMD(scan_enable) \ |
| StaticByteBuffer(LowerBits(hci_spec::kWriteScanEnable), \ |
| UpperBits(hci_spec::kWriteScanEnable), 0x01, \ |
| (scan_enable)) |
| |
| const auto kWriteScanEnableNone = WRITE_SCAN_ENABLE_CMD(0x00); |
| const auto kWriteScanEnableInq = WRITE_SCAN_ENABLE_CMD(0x01); |
| const auto kWriteScanEnablePage = WRITE_SCAN_ENABLE_CMD(0x02); |
| const auto kWriteScanEnableBoth = WRITE_SCAN_ENABLE_CMD(0x03); |
| |
| #undef WRITE_SCAN_ENABLE_CMD |
| |
| const auto kWriteScanEnableRsp = COMMAND_COMPLETE_RSP(hci_spec::kWriteScanEnable); |
| |
| #undef COMMAND_COMPLETE_RSP |
| // clang-format on |
| |
| // Test: malformed inquiry result is fatal |
| TEST_F(BrEdrDiscoveryManagerDeathTest, |
| MalformedInquiryResultFromControllerIsFatal) { |
| EXPECT_CMD_PACKET_OUT(test_device(), hci_spec::kInquiry, &kInquiryRsp); |
| |
| std::unique_ptr<BrEdrDiscoverySession> session; |
| |
| discovery_manager()->RequestDiscovery( |
| [&session](auto status, auto cb_session) { |
| EXPECT_EQ(fit::ok(), status); |
| session = std::move(cb_session); |
| }); |
| |
| RunUntilIdle(); |
| |
| for (auto event : {kInquiryResultIncompleteHeader.view(), |
| kInquiryResultMissingResponses.view(), |
| kInquiryResultIncompleteResponse.view()}) { |
| EXPECT_DEATH_IF_SUPPORTED( |
| [=] { |
| test_device()->SendCommandChannelPacket(event); |
| RunUntilIdle(); |
| }(), |
| ".*"); |
| } |
| } |
| |
| // Test: discovering() answers correctly |
| |
| // Test: requesting discovery should start inquiry |
| // Test: Inquiry Results that come in when there is discovery get reported up |
| // correctly to the sessions |
| // Test: Peers discovered are reported to the cache |
| // Test: RemoteNameRequest is processed correctly |
| // Test: Inquiry Results that come in when there's no discovery happening get |
| // discarded. |
| TEST_F(BrEdrDiscoveryManagerTest, RequestDiscoveryAndDrop) { |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), hci_spec::kInquiry, &kInquiryRsp, &kInquiryResult); |
| EXPECT_CMD_PACKET_OUT(test_device(), |
| kRemoteNameRequest1, |
| &kRemoteNameRequestRsp, |
| &kRemoteNameRequestComplete1); |
| |
| std::unique_ptr<BrEdrDiscoverySession> session; |
| size_t peers_found = 0u; |
| |
| discovery_manager()->RequestDiscovery( |
| [&session, &peers_found](auto status, auto cb_session) { |
| EXPECT_EQ(fit::ok(), status); |
| cb_session->set_result_callback( |
| [&peers_found](const auto&) { peers_found++; }); |
| session = std::move(cb_session); |
| }); |
| |
| EXPECT_FALSE(discovery_manager()->discovering()); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1u, peers_found); |
| EXPECT_TRUE(discovery_manager()->discovering()); |
| |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), hci_spec::kInquiry, &kInquiryRsp, &kInquiryResult); |
| |
| test_device()->SendCommandChannelPacket(kInquiryComplete); |
| |
| RunUntilIdle(); |
| |
| // Confirm that post-inquiry peer name request is processed correctly. |
| Peer* peer = peer_cache()->FindByAddress(kDeviceAddress1); |
| ASSERT_TRUE(peer); |
| EXPECT_EQ("Fuchsia💖", *peer->name()); |
| EXPECT_EQ(Peer::NameSource::kNameDiscoveryProcedure, *peer->name_source()); |
| |
| EXPECT_EQ(2u, peers_found); |
| |
| // TODO(jamuraa, https://fxbug.dev/42145646): test InquiryCancel when it is |
| // implemented |
| |
| session = nullptr; |
| test_device()->SendCommandChannelPacket(kInquiryResult); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(2u, peers_found); |
| EXPECT_FALSE(discovery_manager()->discovering()); |
| |
| test_device()->SendCommandChannelPacket(kInquiryComplete); |
| RunUntilIdle(); |
| } |
| |
| // Test: requesting a second discovery should start a session without sending |
| // any more HCI commands. |
| // Test: dropping the first discovery shouldn't stop inquiry |
| // Test: starting two sessions at once should only start inquiry once |
| TEST_F(BrEdrDiscoveryManagerTest, MultipleRequests) { |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), hci_spec::kInquiry, &kInquiryRsp, &kInquiryResult); |
| EXPECT_CMD_PACKET_OUT(test_device(), |
| kRemoteNameRequest1, |
| &kRemoteNameRequestRsp, |
| &kRemoteNameRequestComplete1); |
| |
| std::unique_ptr<BrEdrDiscoverySession> session1; |
| size_t peers_found1 = 0u; |
| |
| discovery_manager()->RequestDiscovery( |
| [&session1, &peers_found1](auto status, auto cb_session) { |
| EXPECT_EQ(fit::ok(), status); |
| cb_session->set_result_callback( |
| [&peers_found1](const auto&) { peers_found1++; }); |
| session1 = std::move(cb_session); |
| }); |
| |
| EXPECT_FALSE(discovery_manager()->discovering()); |
| |
| RunUntilIdle(); |
| |
| EXPECT_TRUE(session1); |
| EXPECT_EQ(1u, peers_found1); |
| EXPECT_TRUE(discovery_manager()->discovering()); |
| |
| std::unique_ptr<BrEdrDiscoverySession> session2; |
| size_t peers_found2 = 0u; |
| |
| discovery_manager()->RequestDiscovery( |
| [&session2, &peers_found2](auto status, auto cb_session) { |
| EXPECT_EQ(fit::ok(), status); |
| cb_session->set_result_callback( |
| [&peers_found2](const auto&) { peers_found2++; }); |
| session2 = std::move(cb_session); |
| }); |
| |
| RunUntilIdle(); |
| |
| EXPECT_TRUE(session2); |
| EXPECT_EQ(1u, peers_found1); |
| EXPECT_EQ(0u, peers_found2); |
| EXPECT_TRUE(discovery_manager()->discovering()); |
| |
| test_device()->SendCommandChannelPacket(kInquiryResult); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(2u, peers_found1); |
| EXPECT_EQ(1u, peers_found2); |
| |
| session1 = nullptr; |
| |
| RunUntilIdle(); |
| |
| test_device()->SendCommandChannelPacket(kInquiryResult); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(2u, peers_found1); |
| EXPECT_EQ(2u, peers_found2); |
| |
| // TODO(jamuraa, https://fxbug.dev/42145646): test InquiryCancel when it is |
| // implemented |
| |
| session2 = nullptr; |
| |
| test_device()->SendCommandChannelPacket(kInquiryResult); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(2u, peers_found1); |
| EXPECT_EQ(2u, peers_found2); |
| |
| EXPECT_FALSE(discovery_manager()->discovering()); |
| |
| test_device()->SendCommandChannelPacket(kInquiryComplete); |
| |
| RunUntilIdle(); |
| } |
| |
| // Test: starting a session "while" the other one is stopping a session should |
| // still restart the Inquiry. |
| // Test: starting a session "while" the other one is stopping should return |
| // without needing an InquiryComplete first. |
| // Test: we should only request a peer's name if it's the first time we |
| // encounter it. |
| TEST_F(BrEdrDiscoveryManagerTest, RequestDiscoveryWhileStop) { |
| EXPECT_CMD_PACKET_OUT(test_device(), kInquiry, &kInquiryRsp, &kInquiryResult); |
| EXPECT_CMD_PACKET_OUT(test_device(), |
| kRemoteNameRequest1, |
| &kRemoteNameRequestRsp, |
| &kRemoteNameRequestComplete1); |
| |
| std::unique_ptr<BrEdrDiscoverySession> session1; |
| size_t peers_found1 = 0u; |
| |
| discovery_manager()->RequestDiscovery( |
| [&session1, &peers_found1](auto status, auto cb_session) { |
| EXPECT_EQ(fit::ok(), status); |
| cb_session->set_result_callback( |
| [&peers_found1](const auto&) { peers_found1++; }); |
| session1 = std::move(cb_session); |
| }); |
| |
| EXPECT_FALSE(discovery_manager()->discovering()); |
| |
| RunUntilIdle(); |
| |
| EXPECT_TRUE(session1); |
| EXPECT_EQ(1u, peers_found1); |
| EXPECT_TRUE(discovery_manager()->discovering()); |
| |
| // Drop the active session. |
| session1 = nullptr; |
| RunUntilIdle(); |
| |
| std::unique_ptr<BrEdrDiscoverySession> session2; |
| size_t peers_found2 = 0u; |
| discovery_manager()->RequestDiscovery( |
| [&session2, &peers_found2](auto status, auto cb_session) { |
| EXPECT_EQ(fit::ok(), status); |
| cb_session->set_result_callback( |
| [&peers_found2](const auto&) { peers_found2++; }); |
| session2 = std::move(cb_session); |
| }); |
| |
| // The new session should be started at this point, and inquiry results |
| // returned. |
| EXPECT_TRUE(session2); |
| test_device()->SendCommandChannelPacket(kInquiryResult); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1u, peers_found2); |
| |
| // Inquiry should be restarted when the Complete comes in because an active |
| // session2 still exists. |
| // TODO(jamuraa, https://fxbug.dev/42145646): test InquiryCancel when it is |
| // implemented |
| EXPECT_CMD_PACKET_OUT(test_device(), kInquiry, &kInquiryRsp, &kInquiryResult); |
| test_device()->SendCommandChannelPacket(kInquiryComplete); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1u, peers_found1); |
| EXPECT_EQ(2u, peers_found2); |
| EXPECT_TRUE(discovery_manager()->discovering()); |
| |
| test_device()->SendCommandChannelPacket(kInquiryResult); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1u, peers_found1); |
| EXPECT_EQ(3u, peers_found2); |
| |
| // TODO(jamuraa, https://fxbug.dev/42145646): test InquiryCancel when it is |
| // implemented |
| session2 = nullptr; |
| |
| // After the session is dropped, even if another result comes in, no results |
| // are sent to the callback. |
| test_device()->SendCommandChannelPacket(kInquiryResult); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1u, peers_found1); |
| EXPECT_EQ(3u, peers_found2); |
| } |
| |
| // Test: When Inquiry Fails to start, we report this back to the requester. |
| TEST_F(BrEdrDiscoveryManagerTest, RequestDiscoveryError) { |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), kInquiry, &kInquiryRspError, &kInquiryResult); |
| EXPECT_CMD_PACKET_OUT(test_device(), |
| kRemoteNameRequest1, |
| &kRemoteNameRequestRsp, |
| &kRemoteNameRequestComplete1); |
| |
| std::unique_ptr<BrEdrDiscoverySession> session; |
| |
| discovery_manager()->RequestDiscovery([](auto status, auto cb_session) { |
| EXPECT_TRUE(status.is_error()); |
| EXPECT_FALSE(cb_session); |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::HARDWARE_FAILURE), |
| status); |
| }); |
| |
| EXPECT_FALSE(discovery_manager()->discovering()); |
| |
| RunUntilIdle(); |
| |
| EXPECT_FALSE(discovery_manager()->discovering()); |
| } |
| |
| // Test: When inquiry complete indicates failure, we signal to the current |
| // sessions. |
| TEST_F(BrEdrDiscoveryManagerTest, ContinuingDiscoveryError) { |
| EXPECT_CMD_PACKET_OUT(test_device(), kInquiry, &kInquiryRsp, &kInquiryResult); |
| EXPECT_CMD_PACKET_OUT(test_device(), |
| kRemoteNameRequest1, |
| &kRemoteNameRequestRsp, |
| &kRemoteNameRequestComplete1); |
| |
| std::unique_ptr<BrEdrDiscoverySession> session; |
| size_t peers_found = 0u; |
| bool error_callback = false; |
| |
| discovery_manager()->RequestDiscovery( |
| [&session, &peers_found, &error_callback](auto status, auto cb_session) { |
| EXPECT_EQ(fit::ok(), status); |
| cb_session->set_result_callback( |
| [&peers_found](const auto&) { peers_found++; }); |
| cb_session->set_error_callback( |
| [&error_callback]() { error_callback = true; }); |
| session = std::move(cb_session); |
| }); |
| |
| EXPECT_FALSE(discovery_manager()->discovering()); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1u, peers_found); |
| EXPECT_TRUE(discovery_manager()->discovering()); |
| |
| test_device()->SendCommandChannelPacket(kInquiryCompleteError); |
| |
| RunUntilIdle(); |
| |
| EXPECT_TRUE(error_callback); |
| EXPECT_FALSE(discovery_manager()->discovering()); |
| |
| session = nullptr; |
| |
| RunUntilIdle(); |
| } |
| |
| // clang-format off |
| const StaticByteBuffer kWriteLocalNameMaxLen( |
| LowerBits(hci_spec::kWriteLocalName), UpperBits(hci_spec::kWriteLocalName), |
| 0xF8, // parameter_total_size (248 bytes) |
| // Complete Local Name (exactly 248 bytes) |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', |
| 'T', 'U', 'V', 'W', 'X', 'Y', |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', |
| 'T', 'U', 'V', 'W', 'X', 'Y', |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', |
| 'T', 'U', 'V', 'W', 'X', 'Y', |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', |
| 'T', 'U', 'V', 'W', 'X', 'Y', |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', |
| 'T', 'U', 'V', 'W', 'X', 'Y', |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', |
| 'T', 'U', 'V', 'W', 'X', 'Y', |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', |
| 'T', 'U', 'V', 'W', 'X', 'Y', |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', |
| 'T', 'U', 'V', 'W', 'X', 'Y', |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', |
| 'T', 'U', 'V', 'W', 'X', 'Y', |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', |
| 'T', 'U', 'V', 'W' |
| ); |
| |
| const StaticByteBuffer kWriteExtInquiryResponseMaxLen( |
| LowerBits(hci_spec::kWriteExtendedInquiryResponse), |
| UpperBits(hci_spec::kWriteExtendedInquiryResponse), |
| 0xF1, // parameter_total_size (241 bytes) |
| 0x00, // fec_required |
| 0xEF, // 239 bytes (1 + 238 bytes) |
| 0x08, // DataType::kShortenedLocalName, |
| // Shortened Local Name (238 bytes, truncated from above) |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', |
| 'T', 'U', 'V', 'W', 'X', 'Y', |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', |
| 'T', 'U', 'V', 'W', 'X', 'Y', |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', |
| 'T', 'U', 'V', 'W', 'X', 'Y', |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', |
| 'T', 'U', 'V', 'W', 'X', 'Y', |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', |
| 'T', 'U', 'V', 'W', 'X', 'Y', |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', |
| 'T', 'U', 'V', 'W', 'X', 'Y', |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', |
| 'T', 'U', 'V', 'W', 'X', 'Y', |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', |
| 'T', 'U', 'V', 'W', 'X', 'Y', |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', |
| 'T', 'U', 'V', 'W', 'X', 'Y', |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M' |
| ); |
| // clang-format on |
| |
| // Test: UpdateLocalName successfully sends hci command, and further calls |
| // UpdateEIRResponseData (private). Ensures the name is updated at the very end. |
| TEST_F(BrEdrDiscoveryManagerTest, UpdateLocalNameShortenedSuccess) { |
| EXPECT_CMD_PACKET_OUT(test_device(), kWriteLocalNameMaxLen, ); |
| |
| // Set the status to be an arbitrary invalid status. |
| hci::Result<> result = |
| ToResult(pw::bluetooth::emboss::StatusCode::PAIRING_NOT_ALLOWED); |
| size_t callback_count = 0u; |
| auto name_cb = [&result, &callback_count](const auto& status) { |
| EXPECT_EQ(fit::ok(), status); |
| callback_count++; |
| result = status; |
| }; |
| std::string kNewName = ""; |
| while (kNewName.length() < 225) { |
| kNewName.append("ABCDEFGHIJKLMNOPQRSTUVWXY"); |
| } |
| kNewName.append("ABCDEFGHIJKLMNOPQRSTUVW"); |
| discovery_manager()->UpdateLocalName(kNewName, name_cb); |
| |
| RunUntilIdle(); |
| |
| // Local name should not be set, callback shouldn't be called yet. |
| EXPECT_NE(kNewName, discovery_manager()->local_name()); |
| EXPECT_EQ(0u, callback_count); |
| |
| test_device()->SendCommandChannelPacket(kWriteLocalNameRsp); |
| EXPECT_CMD_PACKET_OUT(test_device(), kWriteExtInquiryResponseMaxLen, ); |
| |
| RunUntilIdle(); |
| |
| // Still waiting on EIR response. |
| // Local name should not be set, callback shouldn't be called yet. |
| EXPECT_NE(kNewName, discovery_manager()->local_name()); |
| EXPECT_EQ(0u, callback_count); |
| |
| test_device()->SendCommandChannelPacket(kWriteExtendedInquiryResponseRsp); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(kNewName, discovery_manager()->local_name()); |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), result); |
| EXPECT_EQ(1u, callback_count); |
| } |
| |
| // Test: UpdateLocalName successfully sends hci command, and further calls |
| // UpdateEIRResponseData (private). Ensures the name is updated at the very end. |
| TEST_F(BrEdrDiscoveryManagerTest, UpdateLocalNameSuccess) { |
| EXPECT_CMD_PACKET_OUT(test_device(), kWriteLocalName, ); |
| |
| // Set the status to be an arbitrary invalid status. |
| hci::Result<> result = |
| ToResult(pw::bluetooth::emboss::StatusCode::PAIRING_NOT_ALLOWED); |
| size_t callback_count = 0u; |
| auto name_cb = [&result, &callback_count](const auto& status) { |
| EXPECT_EQ(fit::ok(), status); |
| callback_count++; |
| result = status; |
| }; |
| const std::string kNewName = "ABC"; |
| discovery_manager()->UpdateLocalName(kNewName, name_cb); |
| |
| RunUntilIdle(); |
| |
| // Local name should not be set, callback shouldn't be called yet. |
| EXPECT_NE(kNewName, discovery_manager()->local_name()); |
| EXPECT_EQ(0u, callback_count); |
| |
| test_device()->SendCommandChannelPacket(kWriteLocalNameRsp); |
| EXPECT_CMD_PACKET_OUT(test_device(), kWriteExtendedInquiryResponse, ); |
| |
| RunUntilIdle(); |
| |
| // Still waiting on EIR response. |
| // Local name should not be set, callback shouldn't be called yet. |
| EXPECT_NE(kNewName, discovery_manager()->local_name()); |
| EXPECT_EQ(0u, callback_count); |
| |
| test_device()->SendCommandChannelPacket(kWriteExtendedInquiryResponseRsp); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(kNewName, discovery_manager()->local_name()); |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), result); |
| EXPECT_EQ(1u, callback_count); |
| } |
| |
| // Test: UpdateLocalName passes back error code through the callback and |
| // |local_name_| does not get updated. |
| TEST_F(BrEdrDiscoveryManagerTest, UpdateLocalNameError) { |
| EXPECT_CMD_PACKET_OUT(test_device(), kWriteLocalName, ); |
| |
| // Set the status to be an arbitrary invalid status. |
| hci::Result<> result = |
| ToResult(pw::bluetooth::emboss::StatusCode::UNSUPPORTED_REMOTE_FEATURE); |
| size_t callback_count = 0u; |
| auto name_cb = [&result, &callback_count](const auto& status) { |
| EXPECT_TRUE(status.is_error()); |
| callback_count++; |
| result = status; |
| }; |
| const std::string kNewName = "ABC"; |
| discovery_manager()->UpdateLocalName(kNewName, name_cb); |
| |
| RunUntilIdle(); |
| |
| // Local name should not be set, callback shouldn't be called yet. |
| EXPECT_NE(kNewName, discovery_manager()->local_name()); |
| EXPECT_EQ(0u, callback_count); |
| |
| // Send a response error. |
| test_device()->SendCommandChannelPacket(kWriteLocalNameRspError); |
| |
| RunUntilIdle(); |
| |
| // |local_name_| should not be updated, return status should be error. |
| EXPECT_NE(kNewName, discovery_manager()->local_name()); |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::HARDWARE_FAILURE), |
| result); |
| EXPECT_EQ(1u, callback_count); |
| } |
| |
| // Test: UpdateLocalName should succeed, but UpdateEIRResponseData should fail. |
| // Consequently, the |local_name_| should not be updated, and the callback |
| // should return the error. |
| TEST_F(BrEdrDiscoveryManagerTest, UpdateEIRResponseDataError) { |
| EXPECT_CMD_PACKET_OUT(test_device(), kWriteLocalName, ); |
| |
| // Set the status to be an arbitrary invalid status. |
| hci::Result<> result = |
| ToResult(pw::bluetooth::emboss::StatusCode::UNSUPPORTED_REMOTE_FEATURE); |
| size_t callback_count = 0u; |
| auto name_cb = [&result, &callback_count](const auto& status) { |
| EXPECT_TRUE(status.is_error()); |
| callback_count++; |
| result = status; |
| }; |
| const std::string kNewName = "ABC"; |
| discovery_manager()->UpdateLocalName(kNewName, name_cb); |
| |
| RunUntilIdle(); |
| |
| // Local name should not be set, callback shouldn't be called yet. |
| EXPECT_NE(kNewName, discovery_manager()->local_name()); |
| EXPECT_EQ(0u, callback_count); |
| |
| // kWriteLocalName should succeed. |
| test_device()->SendCommandChannelPacket(kWriteLocalNameRsp); |
| EXPECT_CMD_PACKET_OUT(test_device(), kWriteExtendedInquiryResponse, ); |
| |
| RunUntilIdle(); |
| |
| // Still waiting on EIR response. |
| // Local name should not be set, callback shouldn't be called yet. |
| EXPECT_NE(kNewName, discovery_manager()->local_name()); |
| EXPECT_EQ(0u, callback_count); |
| |
| // kWriteExtendedInquiryResponse should fail. |
| test_device()->SendCommandChannelPacket( |
| kWriteExtendedInquiryResponseRspError); |
| |
| RunUntilIdle(); |
| |
| // |local_name_| should not be updated, return status should be error. |
| EXPECT_NE(kNewName, discovery_manager()->local_name()); |
| EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::HARDWARE_FAILURE), |
| result); |
| EXPECT_EQ(1u, callback_count); |
| } |
| |
| // Test: requesting discoverable works |
| // Test: requesting discoverable while discoverable is pending doesn't send |
| // any more HCI commands |
| TEST_F(BrEdrDiscoveryManagerTest, DiscoverableSet) { |
| EXPECT_CMD_PACKET_OUT(test_device(), kReadScanEnable, ); |
| |
| std::vector<std::unique_ptr<BrEdrDiscoverableSession>> sessions; |
| auto session_cb = [&sessions](auto status, auto cb_session) { |
| EXPECT_EQ(fit::ok(), status); |
| sessions.emplace_back(std::move(cb_session)); |
| }; |
| |
| discovery_manager()->RequestDiscoverable(session_cb); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(0u, sessions.size()); |
| EXPECT_FALSE(discovery_manager()->discoverable()); |
| |
| EXPECT_CMD_PACKET_OUT(test_device(), kWriteScanEnableInq, ); |
| |
| test_device()->SendCommandChannelPacket(kReadScanEnableRspNone); |
| |
| RunUntilIdle(); |
| |
| // Request another session while the first is pending. |
| discovery_manager()->RequestDiscoverable(session_cb); |
| |
| test_device()->SendCommandChannelPacket(kWriteScanEnableRsp); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(2u, sessions.size()); |
| EXPECT_TRUE(discovery_manager()->discoverable()); |
| |
| discovery_manager()->RequestDiscoverable(session_cb); |
| |
| EXPECT_EQ(3u, sessions.size()); |
| EXPECT_TRUE(discovery_manager()->discoverable()); |
| |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), kReadScanEnable, &kReadScanEnableRspInquiry); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), kWriteScanEnableNone, &kWriteScanEnableRsp); |
| |
| sessions.clear(); |
| |
| RunUntilIdle(); |
| |
| EXPECT_FALSE(discovery_manager()->discoverable()); |
| } |
| |
| // Test: requesting discoverable while discovery is disabling leaves |
| // the discoverable enabled and reports success |
| // Test: enable/disable while page scan is enabled works. |
| TEST_F(BrEdrDiscoveryManagerTest, DiscoverableRequestWhileStopping) { |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), kReadScanEnable, &kReadScanEnableRspPage); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), kWriteScanEnableBoth, &kWriteScanEnableRsp); |
| |
| std::vector<std::unique_ptr<BrEdrDiscoverableSession>> sessions; |
| auto session_cb = [&sessions](auto status, auto cb_session) { |
| EXPECT_EQ(fit::ok(), status); |
| sessions.emplace_back(std::move(cb_session)); |
| }; |
| |
| discovery_manager()->RequestDiscoverable(session_cb); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1u, sessions.size()); |
| EXPECT_TRUE(discovery_manager()->discoverable()); |
| |
| EXPECT_CMD_PACKET_OUT(test_device(), kReadScanEnable, ); |
| |
| sessions.clear(); |
| |
| RunUntilIdle(); |
| |
| // Request a new discovery before the procedure finishes. |
| // This will queue another ReadScanEnable just in case the disable write is |
| // in progress. |
| EXPECT_CMD_PACKET_OUT(test_device(), kReadScanEnable, ); |
| discovery_manager()->RequestDiscoverable(session_cb); |
| |
| test_device()->SendCommandChannelPacket(kReadScanEnableRspBoth); |
| |
| // This shouldn't send any WriteScanEnable because we're already in the right |
| // mode (MockController will assert if we do as it's not expecting) |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1u, sessions.size()); |
| EXPECT_TRUE(discovery_manager()->discoverable()); |
| |
| // If somehow the scan got turned off, we will still turn it back on. |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), kWriteScanEnableBoth, &kWriteScanEnableRsp); |
| test_device()->SendCommandChannelPacket(kReadScanEnableRspPage); |
| |
| RunUntilIdle(); |
| |
| EXPECT_EQ(1u, sessions.size()); |
| EXPECT_TRUE(discovery_manager()->discoverable()); |
| |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), kReadScanEnable, &kReadScanEnableRspBoth); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), kWriteScanEnablePage, &kWriteScanEnableRsp); |
| |
| sessions.clear(); |
| |
| RunUntilIdle(); |
| |
| EXPECT_FALSE(discovery_manager()->discoverable()); |
| } |
| |
| // Test: non-standard inquiry modes mean before the first discovery, the |
| // inquiry mode is set. |
| // Test: extended inquiry is stored in the remote peer |
| TEST_F(BrEdrDiscoveryManagerTest, ExtendedInquiry) { |
| NewDiscoveryManager(pw::bluetooth::emboss::InquiryMode::EXTENDED); |
| |
| EXPECT_CMD_PACKET_OUT(test_device(), kSetExtendedMode, &kSetExtendedModeRsp); |
| EXPECT_CMD_PACKET_OUT(test_device(), |
| kInquiry, |
| &kInquiryRsp, |
| &kExtendedInquiryResult, |
| &kRSSIInquiryResult); |
| EXPECT_CMD_PACKET_OUT(test_device(), |
| kRemoteNameRequest2, |
| &kRemoteNameRequestRsp, |
| &kRemoteNameRequestComplete2); |
| |
| std::unique_ptr<BrEdrDiscoverySession> session1; |
| size_t peers_found1 = 0u; |
| |
| discovery_manager()->RequestDiscovery( |
| [&session1, &peers_found1](auto status, auto cb_session) { |
| EXPECT_EQ(fit::ok(), status); |
| cb_session->set_result_callback( |
| [&peers_found1](const auto&) { peers_found1++; }); |
| session1 = std::move(cb_session); |
| }); |
| |
| EXPECT_FALSE(discovery_manager()->discovering()); |
| |
| RunUntilIdle(); |
| |
| EXPECT_TRUE(session1); |
| EXPECT_EQ(2u, peers_found1); |
| EXPECT_TRUE(discovery_manager()->discovering()); |
| session1 = nullptr; |
| |
| Peer* peer1 = peer_cache()->FindByAddress(kDeviceAddress2); |
| ASSERT_TRUE(peer1); |
| EXPECT_EQ(-20, peer1->rssi()); |
| |
| Peer* peer2 = peer_cache()->FindByAddress(kDeviceAddress3); |
| ASSERT_TRUE(peer2); |
| ASSERT_TRUE(peer2->name()); |
| EXPECT_EQ("Fuchsia💖", *peer2->name()); |
| EXPECT_EQ(Peer::NameSource::kInquiryResultComplete, *peer2->name_source()); |
| |
| test_device()->SendCommandChannelPacket(kInquiryComplete); |
| |
| RunUntilIdle(); |
| |
| EXPECT_FALSE(discovery_manager()->discovering()); |
| } |
| |
| // Verify that receiving a inquiry response for a known LE non-connectable peer |
| // results in the peer being changed to DualMode and connectable. |
| TEST_F(BrEdrDiscoveryManagerTest, InquiryResultUpgradesKnownLowEnergyPeer) { |
| Peer* peer = peer_cache()->NewPeer(kLeAliasAddress1, /*connectable=*/false); |
| ASSERT_TRUE(peer); |
| ASSERT_FALSE(peer->connectable()); |
| ASSERT_EQ(TechnologyType::kLowEnergy, peer->technology()); |
| |
| EXPECT_CMD_PACKET_OUT(test_device(), kInquiry, &kInquiryRsp, &kInquiryResult); |
| EXPECT_CMD_PACKET_OUT(test_device(), |
| kRemoteNameRequest1, |
| &kRemoteNameRequestRsp, |
| &kRemoteNameRequestComplete1); |
| |
| std::unique_ptr<BrEdrDiscoverySession> session; |
| size_t peers_found = 0u; |
| |
| discovery_manager()->RequestDiscovery([&session, &peers_found]( |
| auto status, auto cb_session) { |
| EXPECT_EQ(fit::ok(), status); |
| cb_session->set_result_callback([&peers_found](auto&) { peers_found++; }); |
| session = std::move(cb_session); |
| }); |
| RunUntilIdle(); |
| session = nullptr; |
| |
| EXPECT_EQ(1u, peers_found); |
| ASSERT_EQ(peer, peer_cache()->FindByAddress(kDeviceAddress1)); |
| EXPECT_EQ(TechnologyType::kDualMode, peer->technology()); |
| EXPECT_TRUE(peer->connectable()); |
| |
| test_device()->SendCommandChannelPacket(kInquiryComplete); |
| |
| RunUntilIdle(); |
| } |
| |
| // Verify that receiving an extended inquiry response for a known LE |
| // non-connectable peer results in the peer being changed to DualMode and |
| // connectable. |
| TEST_F(BrEdrDiscoveryManagerTest, |
| ExtendedInquiryResultUpgradesKnownLowEnergyPeer) { |
| Peer* peer = peer_cache()->NewPeer(kLeAliasAddress3, /*connectable=*/false); |
| ASSERT_TRUE(peer); |
| ASSERT_FALSE(peer->connectable()); |
| ASSERT_EQ(TechnologyType::kLowEnergy, peer->technology()); |
| |
| NewDiscoveryManager(pw::bluetooth::emboss::InquiryMode::EXTENDED); |
| |
| EXPECT_CMD_PACKET_OUT(test_device(), kSetExtendedMode, &kSetExtendedModeRsp); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), kInquiry, &kInquiryRsp, &kExtendedInquiryResult); |
| |
| std::unique_ptr<BrEdrDiscoverySession> session; |
| size_t peers_found = 0u; |
| |
| discovery_manager()->RequestDiscovery([&session, &peers_found]( |
| auto status, auto cb_session) { |
| EXPECT_EQ(fit::ok(), status); |
| cb_session->set_result_callback([&peers_found](auto&) { peers_found++; }); |
| session = std::move(cb_session); |
| }); |
| RunUntilIdle(); |
| session = nullptr; |
| |
| EXPECT_EQ(1u, peers_found); |
| ASSERT_EQ(peer, peer_cache()->FindByAddress(kDeviceAddress3)); |
| EXPECT_EQ(TechnologyType::kDualMode, peer->technology()); |
| EXPECT_TRUE(peer->connectable()); |
| |
| test_device()->SendCommandChannelPacket(kInquiryComplete); |
| |
| RunUntilIdle(); |
| } |
| |
| // Verify that receiving an extended inquiry response with RSSI for a known LE |
| // non-connectable peer results in the peer being changed to DualMode and |
| // connectable. |
| TEST_F(BrEdrDiscoveryManagerTest, RSSIInquiryResultUpgradesKnownLowEnergyPeer) { |
| Peer* peer = peer_cache()->NewPeer(kLeAliasAddress2, /*connectable=*/false); |
| ASSERT_TRUE(peer); |
| ASSERT_FALSE(peer->connectable()); |
| ASSERT_EQ(TechnologyType::kLowEnergy, peer->technology()); |
| |
| NewDiscoveryManager(pw::bluetooth::emboss::InquiryMode::EXTENDED); |
| |
| EXPECT_CMD_PACKET_OUT(test_device(), kSetExtendedMode, &kSetExtendedModeRsp); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), kInquiry, &kInquiryRsp, &kRSSIInquiryResult); |
| EXPECT_CMD_PACKET_OUT(test_device(), |
| kRemoteNameRequest2, |
| &kRemoteNameRequestRsp, |
| &kRemoteNameRequestComplete2); |
| |
| std::unique_ptr<BrEdrDiscoverySession> session; |
| size_t peers_found = 0u; |
| |
| discovery_manager()->RequestDiscovery([&session, &peers_found]( |
| auto status, auto cb_session) { |
| EXPECT_EQ(fit::ok(), status); |
| cb_session->set_result_callback([&peers_found](auto&) { peers_found++; }); |
| session = std::move(cb_session); |
| }); |
| RunUntilIdle(); |
| session = nullptr; |
| |
| EXPECT_EQ(1u, peers_found); |
| ASSERT_EQ(peer, peer_cache()->FindByAddress(kDeviceAddress2)); |
| EXPECT_EQ(TechnologyType::kDualMode, peer->technology()); |
| EXPECT_TRUE(peer->connectable()); |
| |
| test_device()->SendCommandChannelPacket(kInquiryComplete); |
| |
| RunUntilIdle(); |
| } |
| |
| #ifndef NINSPECT |
| TEST_F(BrEdrDiscoveryManagerTest, Inspect) { |
| inspect::Inspector inspector; |
| discovery_manager()->AttachInspect(inspector.GetRoot(), |
| "bredr_discovery_manager"); |
| |
| auto discoverable_session_active_matcher = |
| Contains(UintIs("discoverable_sessions", 1)); |
| |
| std::unique_ptr<BrEdrDiscoverableSession> discoverable_session; |
| auto session_cb = [&discoverable_session](auto status, auto cb_session) { |
| EXPECT_EQ(fit::ok(), status); |
| discoverable_session = std::move(cb_session); |
| }; |
| |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), kReadScanEnable, &kReadScanEnableRspPage); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), kWriteScanEnableBoth, &kWriteScanEnableRsp); |
| discovery_manager()->RequestDiscoverable(session_cb); |
| RunUntilIdle(); |
| EXPECT_TRUE(discoverable_session); |
| |
| auto properties = inspect::ReadFromVmo(inspector.DuplicateVmo()) |
| .take_value() |
| .take_children() |
| .front() |
| .node_ptr() |
| ->take_properties(); |
| EXPECT_THAT(properties, discoverable_session_active_matcher); |
| |
| auto discoverable_session_counted_matcher = |
| ::testing::IsSupersetOf({UintIs("discoverable_sessions", 0), |
| UintIs("discoverable_sessions_count", 1), |
| UintIs("last_discoverable_length_sec", 4)}); |
| |
| RunFor(std::chrono::seconds(4)); |
| discoverable_session = nullptr; |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), kReadScanEnable, &kReadScanEnableRspBoth); |
| EXPECT_CMD_PACKET_OUT( |
| test_device(), kWriteScanEnablePage, &kWriteScanEnableRsp); |
| RunUntilIdle(); |
| |
| properties = inspect::ReadFromVmo(inspector.DuplicateVmo()) |
| .take_value() |
| .take_children() |
| .front() |
| .node_ptr() |
| ->take_properties(); |
| EXPECT_THAT(properties, discoverable_session_counted_matcher); |
| |
| auto discovery_session_active_matcher = |
| Contains(UintIs("discovery_sessions", 1)); |
| |
| std::unique_ptr<BrEdrDiscoverySession> discovery_session; |
| |
| discovery_manager()->RequestDiscovery( |
| [&discovery_session](auto status, auto cb_session) { |
| EXPECT_EQ(fit::ok(), status); |
| discovery_session = std::move(cb_session); |
| }); |
| |
| EXPECT_CMD_PACKET_OUT(test_device(), kInquiry, &kInquiryRsp); |
| RunUntilIdle(); |
| EXPECT_TRUE(discovery_session); |
| |
| properties = inspect::ReadFromVmo(inspector.DuplicateVmo()) |
| .take_value() |
| .take_children() |
| .front() |
| .node_ptr() |
| ->take_properties(); |
| EXPECT_THAT(properties, discovery_session_active_matcher); |
| |
| auto discovery_session_counted_matcher = |
| ::testing::IsSupersetOf({UintIs("discovery_sessions", 0), |
| UintIs("inquiry_sessions_count", 1), |
| UintIs("last_inquiry_length_sec", 7)}); |
| |
| RunFor(std::chrono::seconds(7)); |
| discovery_session = nullptr; |
| RunUntilIdle(); |
| test_device()->SendCommandChannelPacket(kInquiryComplete); |
| RunUntilIdle(); |
| |
| properties = inspect::ReadFromVmo(inspector.DuplicateVmo()) |
| .take_value() |
| .take_children() |
| .front() |
| .node_ptr() |
| ->take_properties(); |
| EXPECT_THAT(properties, discovery_session_counted_matcher); |
| } |
| #endif // NINSPECT |
| |
| } // namespace |
| } // namespace bt::gap |