| // Copyright 2017 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 "garnet/drivers/bluetooth/lib/hci/command_channel.h" |
| |
| #include "gtest/gtest.h" |
| |
| #include "garnet/drivers/bluetooth/lib/common/byte_buffer.h" |
| #include "garnet/drivers/bluetooth/lib/hci/control_packets.h" |
| #include "garnet/drivers/bluetooth/lib/hci/hci.h" |
| #include "garnet/drivers/bluetooth/lib/testing/fake_controller_test.h" |
| #include "garnet/drivers/bluetooth/lib/testing/test_controller.h" |
| |
| namespace bluetooth { |
| namespace hci { |
| namespace { |
| |
| using ::bluetooth::testing::CommandTransaction; |
| |
| using TestingBase = ::bluetooth::testing::FakeControllerTest< |
| ::bluetooth::testing::TestController>; |
| |
| constexpr uint8_t UpperBits(const OpCode opcode) { |
| return opcode >> 8; |
| } |
| |
| constexpr uint8_t LowerBits(const OpCode opcode) { |
| return opcode & 0x00FF; |
| } |
| |
| constexpr uint8_t kNumHCICommandPackets = 1; |
| |
| // A reference counted object used to verify that HCI command completion and |
| // status callbacks are properly cleaned up after the end of a transaction. |
| class TestCallbackObject |
| : public fxl::RefCountedThreadSafe<TestCallbackObject> { |
| public: |
| explicit TestCallbackObject(const fxl::Closure& deletion_callback) |
| : deletion_cb_(deletion_callback) {} |
| |
| virtual ~TestCallbackObject() { deletion_cb_(); } |
| |
| private: |
| fxl::Closure deletion_cb_; |
| }; |
| |
| class CommandChannelTest : public TestingBase { |
| public: |
| CommandChannelTest() = default; |
| ~CommandChannelTest() override = default; |
| }; |
| |
| using HCI_CommandChannelTest = CommandChannelTest; |
| |
| TEST_F(HCI_CommandChannelTest, CommandTimeout) { |
| // Set up expectations: |
| // clang-format off |
| // HCI_Reset |
| auto req = common::CreateStaticByteBuffer( |
| LowerBits(kReset), UpperBits(kReset), // HCI_Reset opcode |
| 0x00 // parameter_total_size |
| ); |
| // clang-format on |
| |
| // No reply. |
| test_device()->QueueCommandTransaction(CommandTransaction(req, {})); |
| test_device()->Start(); |
| |
| // Send a HCI_Reset command. |
| CommandChannel::TransactionId last_id = 0; |
| Status last_status = Status::kSuccess; |
| auto status_cb = [&, this](CommandChannel::TransactionId id, Status status) { |
| last_id = id; |
| last_status = status; |
| message_loop()->QuitNow(); |
| }; |
| |
| auto reset = CommandPacket::New(kReset); |
| CommandChannel::TransactionId id = cmd_channel()->SendCommand( |
| std::move(reset), message_loop()->task_runner(), nullptr, status_cb); |
| |
| RunMessageLoop(); |
| |
| EXPECT_EQ(id, last_id); |
| EXPECT_EQ(Status::kCommandTimeout, last_status); |
| } |
| |
| TEST_F(HCI_CommandChannelTest, SingleRequestResponse) { |
| // Set up expectations: |
| // clang-format off |
| // HCI_Reset |
| auto req = common::CreateStaticByteBuffer( |
| LowerBits(kReset), UpperBits(kReset), // HCI_Reset opcode |
| 0x00 // parameter_total_size |
| ); |
| // HCI_CommandComplete |
| auto rsp = common::CreateStaticByteBuffer( |
| kCommandCompleteEventCode, |
| 0x04, // parameter_total_size (4 byte payload) |
| kNumHCICommandPackets, LowerBits(kReset), |
| UpperBits(kReset), // HCI_Reset opcode |
| Status::kHardwareFailure); |
| // clang-format on |
| test_device()->QueueCommandTransaction(CommandTransaction(req, {&rsp})); |
| test_device()->Start(); |
| |
| // Send a HCI_Reset command. We attach an instance of TestCallbackObject to |
| // the callbacks to verify that it gets cleaned up as expected. |
| bool test_obj_deleted = false; |
| auto test_obj = fxl::MakeRefCounted<TestCallbackObject>( |
| [&test_obj_deleted] { test_obj_deleted = true; }); |
| |
| auto reset = CommandPacket::New(kReset); |
| CommandChannel::TransactionId id = cmd_channel()->SendCommand( |
| std::move(reset), message_loop()->task_runner(), |
| [&id, this, test_obj](CommandChannel::TransactionId callback_id, |
| const EventPacket& event) { |
| EXPECT_EQ(id, callback_id); |
| EXPECT_EQ(kCommandCompleteEventCode, event.event_code()); |
| EXPECT_EQ(4, event.view().header().parameter_total_size); |
| EXPECT_EQ(kNumHCICommandPackets, |
| event.view() |
| .payload<CommandCompleteEventParams>() |
| .num_hci_command_packets); |
| EXPECT_EQ(kReset, le16toh(event.view() |
| .payload<CommandCompleteEventParams>() |
| .command_opcode)); |
| EXPECT_EQ(Status::kHardwareFailure, |
| event.return_params<SimpleReturnParams>()->status); |
| |
| // Quit the message loop to continue the test. |
| message_loop()->QuitNow(); |
| }); |
| |
| test_obj = nullptr; |
| EXPECT_FALSE(test_obj_deleted); |
| RunMessageLoop(); |
| |
| // Make sure that the I/O thread is no longer holding on to |test_obj|. |
| TearDown(); |
| |
| EXPECT_TRUE(test_obj_deleted); |
| } |
| |
| TEST_F(HCI_CommandChannelTest, SingleRequestWithStatusResponse) { |
| // Set up expectations: |
| // clang-format off |
| // HCI_Reset |
| auto req = common::CreateStaticByteBuffer( |
| LowerBits(kReset), UpperBits(kReset), // HCI_Reset opcode |
| 0x00 // parameter_total_size |
| ); |
| // HCI_CommandStatus |
| auto rsp0 = common::CreateStaticByteBuffer( |
| kCommandStatusEventCode, |
| 0x04, // parameter_total_size (4 byte payload) |
| Status::kSuccess, kNumHCICommandPackets, LowerBits(kReset), |
| UpperBits(kReset) // HCI_Reset opcode |
| ); |
| // HCI_CommandComplete |
| auto rsp1 = common::CreateStaticByteBuffer( |
| kCommandCompleteEventCode, |
| 0x04, // parameter_total_size (4 byte payload) |
| kNumHCICommandPackets, LowerBits(kReset), |
| UpperBits(kReset), // HCI_Reset opcode |
| Status::kSuccess); |
| // clang-format on |
| test_device()->QueueCommandTransaction( |
| CommandTransaction(req, {&rsp0, &rsp1})); |
| test_device()->Start(); |
| |
| // Send HCI_Reset |
| CommandChannel::TransactionId id; |
| int status_cb_count = 0; |
| auto status_cb = [&status_cb_count, &id]( |
| CommandChannel::TransactionId callback_id, |
| Status status) { |
| status_cb_count++; |
| EXPECT_EQ(id, callback_id); |
| EXPECT_EQ(Status::kSuccess, status); |
| }; |
| auto complete_cb = [&id, this](CommandChannel::TransactionId callback_id, |
| const EventPacket& event) { |
| EXPECT_EQ(callback_id, id); |
| EXPECT_EQ(kCommandCompleteEventCode, event.event_code()); |
| EXPECT_EQ(Status::kSuccess, |
| event.return_params<SimpleReturnParams>()->status); |
| |
| // Quit the message loop to continue the test. |
| message_loop()->QuitNow(); |
| }; |
| |
| auto reset = CommandPacket::New(kReset); |
| id = cmd_channel()->SendCommand( |
| std::move(reset), message_loop()->task_runner(), complete_cb, status_cb); |
| RunMessageLoop(); |
| EXPECT_EQ(1, status_cb_count); |
| } |
| |
| TEST_F(HCI_CommandChannelTest, SingleRequestWithCustomResponse) { |
| // Set up expectations |
| // clang-format off |
| // HCI_Reset for the sake of testing |
| auto req = common::CreateStaticByteBuffer( |
| LowerBits(kReset), UpperBits(kReset), // HCI_Reset opcode |
| 0x00 // parameter_total_size |
| ); |
| // HCI_CommandStatus |
| auto rsp = common::CreateStaticByteBuffer( |
| kCommandStatusEventCode, |
| 0x04, // parameter_total_size (4 byte payload) |
| Status::kSuccess, kNumHCICommandPackets, LowerBits(kReset), |
| UpperBits(kReset) // HCI_Reset opcode |
| ); |
| // clang-format on |
| test_device()->QueueCommandTransaction(CommandTransaction(req, {&rsp})); |
| test_device()->Start(); |
| |
| // Send HCI_Reset |
| CommandChannel::TransactionId id; |
| int status_cb_count = 0; |
| auto status_cb = [&status_cb_count](CommandChannel::TransactionId callback_id, |
| Status status) { status_cb_count++; }; |
| auto complete_cb = [&id, this](CommandChannel::TransactionId callback_id, |
| const EventPacket& event) { |
| EXPECT_EQ(callback_id, id); |
| EXPECT_EQ(kCommandStatusEventCode, event.event_code()); |
| EXPECT_EQ(Status::kSuccess, |
| event.view().payload<CommandStatusEventParams>().status); |
| EXPECT_EQ(1, event.view() |
| .payload<CommandStatusEventParams>() |
| .num_hci_command_packets); |
| EXPECT_EQ( |
| kReset, |
| le16toh( |
| event.view().payload<CommandStatusEventParams>().command_opcode)); |
| |
| // Quit the message loop to continue the test. |
| message_loop()->QuitNow(); |
| }; |
| |
| auto reset = CommandPacket::New(kReset); |
| id = cmd_channel()->SendCommand(std::move(reset), |
| message_loop()->task_runner(), complete_cb, |
| status_cb, kCommandStatusEventCode); |
| RunMessageLoop(); |
| |
| // |status_cb| shouldn't have been called since it was used as the completion |
| // callback. |
| EXPECT_EQ(0, status_cb_count); |
| } |
| |
| TEST_F(HCI_CommandChannelTest, SingleRequestWithCustomResponseAndMatcher) { |
| constexpr EventCode kTestEventCode = 0xFF; |
| constexpr size_t kExpectedEventCount = 3; |
| constexpr size_t kExpectedCommandCompleteCount = 1; |
| |
| // Set up expectations |
| // clang-format off |
| // HCI_Reset for the sake of testing |
| auto req = common::CreateStaticByteBuffer( |
| LowerBits(kReset), UpperBits(kReset), // HCI_Reset opcode |
| 0x00 // parameter_total_size |
| ); |
| // clang-format on |
| |
| // Custom completion events. We send two events that match the matcher below |
| // and two events that do not. We expect |rsp1| to complete the HCI command |
| // while the rest to be routed to the event handler. |
| auto rsp0 = common::CreateStaticByteBuffer(kTestEventCode, 0x00); |
| auto rsp1 = common::CreateStaticByteBuffer(kTestEventCode, 0x01, 0x00); |
| auto rsp2 = common::CreateStaticByteBuffer(kTestEventCode, 0x01, 0x00); |
| auto rsp3 = common::CreateStaticByteBuffer(kTestEventCode, 0x00); |
| |
| test_device()->QueueCommandTransaction( |
| CommandTransaction(req, {&rsp0, &rsp1, &rsp2, &rsp3})); |
| test_device()->Start(); |
| |
| // Send HCI_Reset |
| CommandChannel::TransactionId id; |
| unsigned int complete_cb_count = 0; |
| auto complete_cb = [&id, &complete_cb_count, kTestEventCode]( |
| CommandChannel::TransactionId callback_id, |
| const EventPacket& event) { |
| EXPECT_EQ(callback_id, id); |
| EXPECT_EQ(kTestEventCode, event.event_code()); |
| EXPECT_EQ(1u, event.view().payload_size()); |
| |
| complete_cb_count++; |
| }; |
| |
| unsigned int event_count_payload0 = 0; |
| unsigned int event_count_payload1 = 0; |
| auto event_cb = [&event_count_payload0, &event_count_payload1, kTestEventCode, |
| this](const EventPacket& event) { |
| EXPECT_EQ(kTestEventCode, event.event_code()); |
| |
| if (event.view().payload_size() == 0u) |
| event_count_payload0++; |
| else if (event.view().payload_size() == 1u) |
| event_count_payload1++; |
| |
| // Quit the message loop to continue the test after we reach the expected |
| // event count. |
| if (event_count_payload0 + event_count_payload1 < kExpectedEventCount) |
| return; |
| |
| message_loop()->PostQuitTask(); |
| }; |
| auto event_handler_id = cmd_channel()->AddEventHandler( |
| kTestEventCode, event_cb, message_loop()->task_runner()); |
| ASSERT_NE(0u, event_handler_id); |
| |
| // Below we send a Reset command with a custom completion event code and |
| // matcher. The matcher is set up to match only one of the events that we sent |
| // above. |
| auto matcher = [](const EventPacket& event) -> bool { |
| // This will match |rsp1| and |rsp2| above (but should never be called on |
| // |rsp2| since the command should complete before then). |
| return event.view().payload_size() == 1; |
| }; |
| |
| auto reset = CommandPacket::New(kReset); |
| id = cmd_channel()->SendCommand(std::move(reset), |
| message_loop()->task_runner(), complete_cb, |
| nullptr, kTestEventCode, matcher); |
| RunMessageLoop(); |
| |
| // |status_cb| shouldn't have been called since it was used as the completion |
| // callback. |
| EXPECT_EQ(2u, event_count_payload0); |
| EXPECT_EQ(1u, event_count_payload1); |
| EXPECT_EQ(kExpectedCommandCompleteCount, complete_cb_count); |
| |
| cmd_channel()->RemoveEventHandler(event_handler_id); |
| } |
| |
| TEST_F(HCI_CommandChannelTest, MultipleQueuedRequests) { |
| // Set up expectations: |
| // clang-format off |
| // Transaction 1: |
| // HCI_Reset |
| auto req0 = common::CreateStaticByteBuffer( |
| LowerBits(kReset), UpperBits(kReset), // HCI_Reset opcode |
| 0x00 // parameter_total_size |
| ); |
| // HCI_CommandStatus with error |
| auto rsp0 = common::CreateStaticByteBuffer( |
| kCommandStatusEventCode, |
| 0x04, // parameter_total_size (4 byte payload) |
| Status::kHardwareFailure, kNumHCICommandPackets, LowerBits(kReset), |
| UpperBits(kReset) // HCI_Reset opcode |
| ); |
| // Transaction 2: |
| // HCI_Read_BDADDR |
| auto req1 = common::CreateStaticByteBuffer( |
| LowerBits(kReadBDADDR), UpperBits(kReadBDADDR), // HCI_Read_BD_ADDR |
| 0x00 // parameter_total_size |
| ); |
| // HCI_CommandStatus |
| auto rsp1 = common::CreateStaticByteBuffer( |
| kCommandStatusEventCode, |
| 0x04, // parameter_total_size (4 byte payload) |
| Status::kSuccess, kNumHCICommandPackets, LowerBits(kReadBDADDR), |
| UpperBits(kReadBDADDR)); |
| // HCI_CommandComplete |
| auto rsp2 = common::CreateStaticByteBuffer( |
| kCommandCompleteEventCode, |
| 0x0A, // parameter_total_size (10 byte payload) |
| kNumHCICommandPackets, LowerBits(kReadBDADDR), UpperBits(kReadBDADDR), |
| Status::kSuccess, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 // BD_ADDR |
| ); |
| // clang-format on |
| test_device()->QueueCommandTransaction(CommandTransaction(req0, {&rsp0})); |
| test_device()->QueueCommandTransaction( |
| CommandTransaction(req1, {&rsp1, &rsp2})); |
| test_device()->Start(); |
| |
| // Begin transactions: |
| CommandChannel::TransactionId id0, id1; |
| int status_cb_count = 0; |
| auto status_cb = [&status_cb_count, &id0, &id1]( |
| CommandChannel::TransactionId callback_id, |
| Status status) { |
| status_cb_count++; |
| if (callback_id == id0) { |
| EXPECT_EQ(Status::kHardwareFailure, status); |
| } else { |
| ASSERT_EQ(id1, callback_id); |
| EXPECT_EQ(Status::kSuccess, status); |
| } |
| }; |
| int complete_cb_count = 0; |
| auto complete_cb = [&id1, &complete_cb_count, this]( |
| CommandChannel::TransactionId callback_id, |
| const EventPacket& event) { |
| EXPECT_EQ(kCommandCompleteEventCode, event.event_code()); |
| complete_cb_count++; |
| EXPECT_EQ(id1, callback_id); |
| |
| auto return_params = event.return_params<ReadBDADDRReturnParams>(); |
| EXPECT_EQ(Status::kSuccess, return_params->status); |
| EXPECT_EQ("06:05:04:03:02:01", return_params->bd_addr.ToString()); |
| |
| // Quit the message loop to continue the test. We post a delayed task so |
| // that our check for |complete_cb_count| == 1 isn't guaranteed to be true |
| // because we quit the message loop. |
| if (complete_cb_count == 1) |
| message_loop()->PostQuitTask(); |
| }; |
| |
| auto reset = CommandPacket::New(kReset); |
| id0 = cmd_channel()->SendCommand( |
| std::move(reset), message_loop()->task_runner(), complete_cb, status_cb); |
| auto read_bdaddr = CommandPacket::New(kReadBDADDR); |
| id1 = cmd_channel()->SendCommand(std::move(read_bdaddr), |
| message_loop()->task_runner(), complete_cb, |
| status_cb); |
| RunMessageLoop(); |
| EXPECT_EQ(2, status_cb_count); |
| EXPECT_EQ(1, complete_cb_count); |
| } |
| |
| TEST_F(HCI_CommandChannelTest, EventHandlerBasic) { |
| constexpr EventCode kTestEventCode0 = 0xFE; |
| constexpr EventCode kTestEventCode1 = 0xFF; |
| auto cmd_status = common::CreateStaticByteBuffer( |
| kCommandStatusEventCode, 0x04, 0x00, 0x01, 0x00, 0x00); |
| auto cmd_complete = common::CreateStaticByteBuffer(kCommandCompleteEventCode, |
| 0x03, 0x01, 0x00, 0x00); |
| auto event0 = common::CreateStaticByteBuffer(kTestEventCode0, 0x00); |
| auto event1 = common::CreateStaticByteBuffer(kTestEventCode1, 0x00); |
| |
| int event_count0 = 0; |
| auto event_cb0 = [&event_count0, kTestEventCode0](const EventPacket& event) { |
| event_count0++; |
| EXPECT_EQ(kTestEventCode0, event.event_code()); |
| }; |
| |
| int event_count1 = 0; |
| auto event_cb1 = [&event_count1, kTestEventCode1, |
| this](const EventPacket& event) { |
| event_count1++; |
| EXPECT_EQ(kTestEventCode1, event.event_code()); |
| |
| // The code below will send this event twice. Quit the message loop when we |
| // get the second event. |
| if (event_count1 == 2) |
| message_loop()->PostQuitTask(); |
| }; |
| |
| auto id0 = cmd_channel()->AddEventHandler(kTestEventCode0, event_cb0, |
| message_loop()->task_runner()); |
| EXPECT_NE(0u, id0); |
| |
| // Cannot register a handler for the same event code more than once. |
| auto id1 = cmd_channel()->AddEventHandler(kTestEventCode0, event_cb1, |
| message_loop()->task_runner()); |
| EXPECT_EQ(0u, id1); |
| |
| // Add a handler for a different event code. |
| id1 = cmd_channel()->AddEventHandler(kTestEventCode1, event_cb1, |
| message_loop()->task_runner()); |
| EXPECT_NE(0u, id1); |
| |
| test_device()->Start(); |
| test_device()->SendCommandChannelPacket(cmd_status); |
| test_device()->SendCommandChannelPacket(cmd_complete); |
| test_device()->SendCommandChannelPacket(event1); |
| test_device()->SendCommandChannelPacket(event0); |
| test_device()->SendCommandChannelPacket(cmd_complete); |
| test_device()->SendCommandChannelPacket(event0); |
| test_device()->SendCommandChannelPacket(event0); |
| test_device()->SendCommandChannelPacket(cmd_status); |
| test_device()->SendCommandChannelPacket(event1); |
| |
| RunMessageLoop(); |
| |
| EXPECT_EQ(3, event_count0); |
| EXPECT_EQ(2, event_count1); |
| |
| event_count0 = 0; |
| event_count1 = 0; |
| |
| // Remove the first event handler. |
| cmd_channel()->RemoveEventHandler(id0); |
| test_device()->SendCommandChannelPacket(event0); |
| test_device()->SendCommandChannelPacket(event0); |
| test_device()->SendCommandChannelPacket(event0); |
| test_device()->SendCommandChannelPacket(event1); |
| test_device()->SendCommandChannelPacket(event0); |
| test_device()->SendCommandChannelPacket(event0); |
| test_device()->SendCommandChannelPacket(event0); |
| test_device()->SendCommandChannelPacket(event0); |
| test_device()->SendCommandChannelPacket(event1); |
| |
| RunMessageLoop(); |
| |
| EXPECT_EQ(0, event_count0); |
| EXPECT_EQ(2, event_count1); |
| } |
| |
| TEST_F(HCI_CommandChannelTest, EventHandlerEventWhileTransactionPending) { |
| // clang-format off |
| // HCI_Reset |
| auto req = common::CreateStaticByteBuffer( |
| LowerBits(kReset), UpperBits(kReset), // HCI_Reset opcode |
| 0x00 // parameter_total_size |
| ); |
| |
| auto cmd_status = common::CreateStaticByteBuffer( |
| kCommandStatusEventCode, |
| 0x04, // parameter_total_size (4 byte payload) |
| Status::kSuccess, 0x01, LowerBits(kReset), |
| UpperBits(kReset) // HCI_Reset opcode |
| ); |
| // clang-format on |
| |
| constexpr EventCode kTestEventCode = 0xFF; |
| auto event0 = common::CreateStaticByteBuffer(kTestEventCode, 0x00); |
| auto event1 = common::CreateStaticByteBuffer(kTestEventCode, 0x01, 0x00); |
| |
| // We will send the HCI_Reset command with kTestEventCode as the completion |
| // event. The event handler we register below should only get invoked once and |
| // after the pending transaction completes. |
| test_device()->QueueCommandTransaction( |
| CommandTransaction(req, {&cmd_status, &event0, &event1})); |
| test_device()->Start(); |
| |
| int event_count = 0; |
| auto event_cb = [&event_count, kTestEventCode, |
| this](const EventPacket& event) { |
| event_count++; |
| EXPECT_EQ(kTestEventCode, event.event_code()); |
| EXPECT_EQ(1u, event.view().header().parameter_total_size); |
| EXPECT_EQ(1u, event.view().payload_size()); |
| |
| // We post this task to the end of the message queue so that the quit call |
| // doesn't inherently guarantee that this callback gets invoked only once. |
| message_loop()->PostQuitTask(); |
| }; |
| |
| cmd_channel()->AddEventHandler(kTestEventCode, event_cb, |
| message_loop()->task_runner()); |
| |
| auto reset = CommandPacket::New(kReset); |
| cmd_channel()->SendCommand(std::move(reset), message_loop()->task_runner(), |
| nullptr, nullptr, kTestEventCode); |
| |
| RunMessageLoop(); |
| |
| EXPECT_EQ(1, event_count); |
| } |
| |
| TEST_F(HCI_CommandChannelTest, LEMetaEventHandler) { |
| constexpr EventCode kTestSubeventCode0 = 0xFE; |
| constexpr EventCode kTestSubeventCode1 = 0xFF; |
| auto le_meta_event_bytes0 = common::CreateStaticByteBuffer( |
| hci::kLEMetaEventCode, 0x01, kTestSubeventCode0); |
| auto le_meta_event_bytes1 = common::CreateStaticByteBuffer( |
| hci::kLEMetaEventCode, 0x01, kTestSubeventCode1); |
| |
| int event_count0 = 0; |
| auto event_cb0 = [&event_count0, kTestSubeventCode0, |
| this](const EventPacket& event) { |
| event_count0++; |
| EXPECT_EQ(hci::kLEMetaEventCode, event.event_code()); |
| EXPECT_EQ(kTestSubeventCode0, |
| event.view().payload<LEMetaEventParams>().subevent_code); |
| message_loop()->PostQuitTask(); |
| }; |
| |
| int event_count1 = 0; |
| auto event_cb1 = [&event_count1, kTestSubeventCode1, |
| this](const EventPacket& event) { |
| event_count1++; |
| EXPECT_EQ(hci::kLEMetaEventCode, event.event_code()); |
| EXPECT_EQ(kTestSubeventCode1, |
| event.view().payload<LEMetaEventParams>().subevent_code); |
| message_loop()->PostQuitTask(); |
| }; |
| |
| auto id0 = cmd_channel()->AddLEMetaEventHandler( |
| kTestSubeventCode0, event_cb0, message_loop()->task_runner()); |
| EXPECT_NE(0u, id0); |
| |
| // Cannot register a handler for the same event code more than once. |
| auto id1 = cmd_channel()->AddLEMetaEventHandler( |
| kTestSubeventCode0, event_cb0, message_loop()->task_runner()); |
| EXPECT_EQ(0u, id1); |
| |
| // Add a handle for a different event code. |
| id1 = cmd_channel()->AddLEMetaEventHandler(kTestSubeventCode1, event_cb1, |
| message_loop()->task_runner()); |
| EXPECT_NE(0u, id1); |
| |
| test_device()->Start(); |
| |
| test_device()->SendCommandChannelPacket(le_meta_event_bytes0); |
| RunMessageLoop(); |
| EXPECT_EQ(1, event_count0); |
| EXPECT_EQ(0, event_count1); |
| |
| test_device()->SendCommandChannelPacket(le_meta_event_bytes0); |
| RunMessageLoop(); |
| EXPECT_EQ(2, event_count0); |
| EXPECT_EQ(0, event_count1); |
| |
| test_device()->SendCommandChannelPacket(le_meta_event_bytes1); |
| RunMessageLoop(); |
| EXPECT_EQ(2, event_count0); |
| EXPECT_EQ(1, event_count1); |
| |
| // Remove the first event handler. |
| cmd_channel()->RemoveEventHandler(id0); |
| test_device()->SendCommandChannelPacket(le_meta_event_bytes0); |
| test_device()->SendCommandChannelPacket(le_meta_event_bytes1); |
| RunMessageLoop(); |
| EXPECT_EQ(2, event_count0); |
| EXPECT_EQ(2, event_count1); |
| } |
| |
| TEST_F(HCI_CommandChannelTest, EventHandlerIdsDontCollide) { |
| // Add a LE Meta event handler and a event handler and make sure that IDs are |
| // generated correctly across the two methods. |
| EXPECT_EQ(1u, cmd_channel()->AddLEMetaEventHandler( |
| hci::kLEConnectionCompleteSubeventCode, [](const auto&) {}, |
| message_loop()->task_runner())); |
| EXPECT_EQ(2u, cmd_channel()->AddEventHandler( |
| hci::kDisconnectionCompleteEventCode, [](const auto&) {}, |
| message_loop()->task_runner())); |
| } |
| |
| TEST_F(HCI_CommandChannelTest, TransportClosedCallback) { |
| test_device()->Start(); |
| |
| bool closed_cb_called = false; |
| auto closed_cb = [&closed_cb_called, this] { |
| closed_cb_called = true; |
| message_loop()->QuitNow(); |
| }; |
| transport()->SetTransportClosedCallback(closed_cb, |
| message_loop()->task_runner()); |
| |
| message_loop()->task_runner()->PostTask( |
| [this] { test_device()->CloseCommandChannel(); }); |
| RunMessageLoop(); |
| EXPECT_TRUE(closed_cb_called); |
| } |
| |
| } // namespace |
| } // namespace hci |
| } // namespace bluetooth |