| // Copyright 2024 The Pigweed Authors |
| // |
| // 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 |
| // |
| // https://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 "pw_bluetooth_proxy/proxy_host.h" |
| |
| #include <cstdint> |
| #include <numeric> |
| |
| #include "emboss_util.h" |
| #include "lib/stdcompat/utility.h" |
| #include "pw_bluetooth/hci_commands.emb.h" |
| #include "pw_bluetooth/hci_common.emb.h" |
| #include "pw_bluetooth/hci_events.emb.h" |
| #include "pw_bluetooth/hci_h4.emb.h" |
| #include "pw_unit_test/framework.h" // IWYU pragma: keep |
| |
| namespace pw::bluetooth::proxy { |
| |
| namespace { |
| |
| // ########## Util functions |
| |
| // Populate passed H4 command buffer and return Emboss view on it. |
| template <typename EmbossT, std::size_t arr_size> |
| EmbossT CreateAndPopulateToControllerView(std::array<uint8_t, arr_size>& h4_arr, |
| emboss::OpCode opcode) { |
| std::iota(h4_arr.begin(), h4_arr.end(), 100); |
| h4_arr[0] = cpp23::to_underlying(emboss::H4PacketType::COMMAND); |
| EmbossT view = MakeEmboss<EmbossT>(H4HciSubspan(h4_arr)); |
| EXPECT_TRUE(view.IsComplete()); |
| view.header().opcode_enum().Write(opcode); |
| return view; |
| } |
| |
| // Return a populated H4 command buffer of a type that proxy host doesn't |
| // interact with. |
| std::array<uint8_t, emboss::InquiryCommandView::SizeInBytes() + 1> |
| PopulateNoninteractingToControllerBuffer( |
| std::array<uint8_t, emboss::InquiryCommandView::SizeInBytes() + 1>& arr) { |
| CreateAndPopulateToControllerView<emboss::InquiryCommandWriter>( |
| arr, emboss::OpCode::LINK_KEY_REQUEST_REPLY); |
| return arr; |
| } |
| |
| // Populate passed H4 event buffer and return Emboss view on it. |
| template <typename EmbossT, std::size_t arr_size> |
| EmbossT CreateAndPopulateToHostEventView(std::array<uint8_t, arr_size>& arr, |
| emboss::EventCode event_code) { |
| std::iota(arr.begin(), arr.end(), 0x10); |
| arr[0] = cpp23::to_underlying(emboss::H4PacketType::EVENT); |
| EmbossT view = MakeEmboss<EmbossT>(H4HciSubspan(arr)); |
| view.header().event_code_enum().Write(event_code); |
| view.status().Write(emboss::StatusCode::SUCCESS); |
| EXPECT_TRUE(view.IsComplete()); |
| return view; |
| } |
| |
| // Return a populated H4 event buffer of a type that proxy host doesn't interact |
| // with. |
| std::array<uint8_t, emboss::InquiryCompleteEventView::SizeInBytes() + 1> |
| CreateNonInteractingToHostBuffer() { |
| std::array<uint8_t, emboss::InquiryCompleteEventWriter::SizeInBytes() + 1> |
| arr; |
| CreateAndPopulateToHostEventView<emboss::InquiryCompleteEventWriter>( |
| arr, emboss::EventCode::INQUIRY_COMPLETE); |
| return arr; |
| } |
| |
| // ########## Examples |
| |
| // Example for docs.rst. |
| TEST(Example, ExampleUsage) { |
| // Populate H4 buffer to send towards controller. |
| std::array<uint8_t, emboss::InquiryCommandView::SizeInBytes() + 1> |
| h4_array_from_host; |
| PopulateNoninteractingToControllerBuffer(h4_array_from_host); |
| auto h4_span_from_host = pw::span(h4_array_from_host); |
| |
| // Populate H4 buffer to send towards host. |
| std::array<uint8_t, emboss::InquiryCompleteEventView::SizeInBytes() + 1> |
| h4_array_from_controller = CreateNonInteractingToHostBuffer(); |
| auto h4_span_from_controller = pw::span(h4_array_from_controller); |
| |
| H4HciPacketSendFn containerSendToHostFn([](H4HciPacket packet) {}); |
| |
| H4HciPacketSendFn containerSendToControllerFn(([](H4HciPacket packet) {})); |
| |
| // DOCSTAG: [pw_bluetooth_proxy-examples-basic] |
| |
| #include "pw_bluetooth_proxy/proxy_host.h" |
| |
| // Container creates ProxyHost . |
| ProxyHost proxy = ProxyHost(std::move(containerSendToHostFn), |
| std::move(containerSendToControllerFn), |
| 2); |
| |
| // Container passes H4 packets from host through proxy. Proxy will in turn |
| // call the container-provided `containerSendToControllerFn` to pass them on |
| // to the controller. Some packets may be modified, added, or removed. |
| proxy.HandleH4HciFromHost(h4_span_from_host); |
| |
| // Container passes H4 packets from controller through proxy. Proxy will in |
| // turn call the container-provided `containerSendToHostFn` to pass them on to |
| // the controller. Some packets may be modified, added, or removed. |
| proxy.HandleH4HciFromController(h4_span_from_controller); |
| |
| // DOCSTAG: [pw_bluetooth_proxy-examples-basic] |
| } |
| |
| // ########## PassthroughTest |
| |
| // Verify buffer is properly passed (contents unaltered and zero-copy). |
| TEST(PassthroughTest, ToControllerPassesEqualBuffer) { |
| std::array<uint8_t, emboss::InquiryCommandView::SizeInBytes() + 1> |
| send_packet; |
| PopulateNoninteractingToControllerBuffer(send_packet); |
| |
| // Struct for capturing because `pw::Function` can't fit multiple captures. |
| struct { |
| // Use a copy for comparison to catch if proxy incorrectly changes the |
| // passed buffer. |
| std::array<uint8_t, emboss::InquiryCommandView::SizeInBytes() + 1> h4_array; |
| // Also use pointer to verify zero-copy. |
| uint8_t* send_array_ptr; |
| bool send_called; |
| } send_capture = {send_packet, send_packet.data(), false}; |
| |
| H4HciPacketSendFn send_to_controller_fn([&send_capture](H4HciPacket packet) { |
| send_capture.send_called = true; |
| EXPECT_TRUE(std::equal(packet.begin(), |
| packet.end(), |
| send_capture.h4_array.begin(), |
| send_capture.h4_array.end())); |
| EXPECT_EQ(packet.data(), send_capture.send_array_ptr); |
| }); |
| |
| H4HciPacketSendFn send_to_host_fn([]([[maybe_unused]] H4HciPacket packet) {}); |
| |
| ProxyHost proxy = ProxyHost( |
| std::move(send_to_host_fn), std::move(send_to_controller_fn), 2); |
| |
| proxy.HandleH4HciFromHost(send_packet); |
| |
| // Verify to controller callback was called. |
| EXPECT_EQ(send_capture.send_called, true); |
| } |
| |
| // Verify buffer is properly passed (contents unaltered and zero-copy). |
| TEST(PassthroughTest, ToHostPassesEqualBuffer) { |
| std::array<uint8_t, emboss::InquiryCompleteEventView::SizeInBytes() + 1> |
| send_packet = CreateNonInteractingToHostBuffer(); |
| |
| // Struct for capturing because `pw::Function` can't fit multiple captures. |
| struct { |
| // Use a copy for comparison to catch if proxy incorrectly changes the |
| // passed buffer. |
| std::array<uint8_t, emboss::InquiryCompleteEventView::SizeInBytes() + 1> |
| h4_array; |
| // Also use pointer to verify zero-copy. |
| uint8_t* send_array_ptr; |
| bool send_called; |
| } send_capture = {send_packet, send_packet.data(), false}; |
| |
| H4HciPacketSendFn send_to_host_fn([&send_capture](H4HciPacket packet) { |
| send_capture.send_called = true; |
| EXPECT_TRUE(std::equal(packet.begin(), |
| packet.end(), |
| send_capture.h4_array.begin(), |
| send_capture.h4_array.end())); |
| EXPECT_EQ(packet.data(), send_capture.send_array_ptr); |
| }); |
| |
| H4HciPacketSendFn send_to_controller_fn( |
| []([[maybe_unused]] H4HciPacket packet) {}); |
| |
| ProxyHost proxy = ProxyHost( |
| std::move(send_to_host_fn), std::move(send_to_controller_fn), 2); |
| |
| proxy.HandleH4HciFromController(send_packet); |
| |
| // Verify to controller callback was called. |
| EXPECT_EQ(send_capture.send_called, true); |
| } |
| |
| // Verify a command complete event (of a type that proxy doesn't act on) is |
| // properly passed (contents unaltered and zero-copy). |
| TEST(PassthroughTest, ToHostPassesEqualCommandComplete) { |
| std::array< |
| uint8_t, |
| emboss::ReadLocalVersionInfoCommandCompleteEventWriter::SizeInBytes() + 1> |
| send_packet; |
| emboss::ReadLocalVersionInfoCommandCompleteEventWriter view = |
| CreateAndPopulateToHostEventView< |
| emboss::ReadLocalVersionInfoCommandCompleteEventWriter>( |
| send_packet, emboss::EventCode::COMMAND_COMPLETE); |
| view.command_complete().command_opcode_enum().Write( |
| emboss::OpCode::READ_LOCAL_VERSION_INFO); |
| |
| // Struct for capturing because `pw::Function` can't fit multiple captures. |
| struct { |
| std::array< |
| uint8_t, |
| emboss::ReadLocalVersionInfoCommandCompleteEventWriter::SizeInBytes() + |
| 1> |
| arr; |
| // Also use pointer to verify zero-copy. |
| uint8_t* send_array_ptr; |
| bool send_called; |
| } send_capture = {send_packet, send_packet.data(), false}; |
| |
| H4HciPacketSendFn send_to_host_fn([&send_capture](H4HciPacket packet) { |
| send_capture.send_called = true; |
| EXPECT_TRUE(std::equal(packet.begin(), |
| packet.end(), |
| send_capture.arr.begin(), |
| send_capture.arr.end())); |
| EXPECT_EQ(packet.data(), send_capture.send_array_ptr); |
| }); |
| |
| H4HciPacketSendFn send_to_controller_fn( |
| []([[maybe_unused]] H4HciPacket packet) {}); |
| |
| ProxyHost proxy = ProxyHost( |
| std::move(send_to_host_fn), std::move(send_to_controller_fn), 2); |
| |
| proxy.HandleH4HciFromController(send_packet); |
| |
| // Verify to controller callback was called. |
| EXPECT_EQ(send_capture.send_called, true); |
| } |
| |
| // ########## BadPacketTest |
| // The proxy should not affect buffers it can't process (it should just pass |
| // them on). |
| |
| TEST(BadPacketTest, EmptyBufferToControllerIsPassedOn) { |
| std::array<uint8_t, 0> send_packet; |
| |
| bool send_called = false; |
| H4HciPacketSendFn send_to_controller_fn([&send_called](H4HciPacket packet) { |
| send_called = true; |
| EXPECT_TRUE(packet.empty()); |
| }); |
| |
| H4HciPacketSendFn send_to_host_fn([]([[maybe_unused]] H4HciPacket packet) {}); |
| |
| ProxyHost proxy = ProxyHost( |
| std::move(send_to_host_fn), std::move(send_to_controller_fn), 2); |
| |
| proxy.HandleH4HciFromHost(send_packet); |
| |
| // Verify callback was called. |
| EXPECT_EQ(send_called, true); |
| } |
| |
| TEST(BadPacketTest, EmptyBufferToHostIsPassedOn) { |
| std::array<uint8_t, 0> send_packet; |
| |
| bool send_called = false; |
| H4HciPacketSendFn send_to_host_fn([&send_called](H4HciPacket packet) { |
| send_called = true; |
| EXPECT_TRUE(packet.empty()); |
| }); |
| |
| H4HciPacketSendFn send_to_controller_fn( |
| []([[maybe_unused]] H4HciPacket packet) {}); |
| |
| ProxyHost proxy = ProxyHost( |
| std::move(send_to_host_fn), std::move(send_to_controller_fn), 2); |
| |
| proxy.HandleH4HciFromController(send_packet); |
| |
| // Verify callback was called. |
| EXPECT_EQ(send_called, true); |
| } |
| |
| TEST(BadPacketTest, TooShortEventToHostIsPassOn) { |
| std::array<uint8_t, emboss::InquiryCompleteEventView::SizeInBytes() + 1> |
| valid_packet = CreateNonInteractingToHostBuffer(); |
| |
| // Create span for sending whose size is one less than a valid command |
| // complete event. |
| pw::span<uint8_t> send_packet = |
| pw::span<uint8_t>(valid_packet) |
| .subspan(0, emboss::EventHeaderView::SizeInBytes() - 1); |
| |
| // Struct for capturing because `pw::Function` can't fit multiple captures. |
| struct { |
| std::array<uint8_t, emboss::EventHeaderView::SizeInBytes() - 1> short_array; |
| bool send_called; |
| } send_capture; |
| // Copy valid event into a short_array whose size is one less than a valid |
| // EventHeader. |
| std::copy_n(std::begin(send_packet), |
| emboss::EventHeaderView::SizeInBytes() - 1, |
| std::begin(send_capture.short_array)); |
| send_capture.send_called = false; |
| |
| H4HciPacketSendFn send_to_host_fn([&send_capture](H4HciPacket packet) { |
| send_capture.send_called = true; |
| EXPECT_TRUE(std::equal(packet.begin(), |
| packet.end(), |
| send_capture.short_array.begin(), |
| send_capture.short_array.end())); |
| }); |
| |
| H4HciPacketSendFn send_to_controller_fn( |
| []([[maybe_unused]] H4HciPacket packet) {}); |
| |
| ProxyHost proxy = ProxyHost( |
| std::move(send_to_host_fn), std::move(send_to_controller_fn), 2); |
| |
| proxy.HandleH4HciFromController(send_packet); |
| |
| // Verify callback was called. |
| EXPECT_EQ(send_capture.send_called, true); |
| } |
| |
| TEST(BadPacketTest, TooShortCommandCompleteEventToHost) { |
| std::array< |
| uint8_t, |
| emboss::ReadLocalVersionInfoCommandCompleteEventWriter::SizeInBytes() + 1> |
| valid_packet; |
| emboss::ReadLocalVersionInfoCommandCompleteEventWriter view = |
| CreateAndPopulateToHostEventView< |
| emboss::ReadLocalVersionInfoCommandCompleteEventWriter>( |
| valid_packet, emboss::EventCode::COMMAND_COMPLETE); |
| view.command_complete().command_opcode_enum().Write( |
| emboss::OpCode::READ_LOCAL_VERSION_INFO); |
| |
| // Create span for sending whose size is one less than a valid command |
| // complete event. |
| pw::span<uint8_t> send_packet = |
| pw::span<uint8_t>(valid_packet) |
| .subspan(0, emboss::CommandCompleteEventView::SizeInBytes() - 1); |
| |
| // Struct for capturing because `pw::Function` capture can't fit multiple |
| // fields . |
| struct { |
| std::array<uint8_t, emboss::CommandCompleteEventView::SizeInBytes() - 1> |
| expected_packet; |
| bool send_called; |
| } send_capture; |
| std::copy_n(std::begin(send_packet), |
| emboss::CommandCompleteEventView::SizeInBytes() - 1, |
| std::begin(send_capture.expected_packet)); |
| send_capture.send_called = false; |
| |
| H4HciPacketSendFn send_to_host_fn([&send_capture](H4HciPacket packet) { |
| send_capture.send_called = true; |
| EXPECT_TRUE(std::equal(packet.begin(), |
| packet.end(), |
| send_capture.expected_packet.begin(), |
| send_capture.expected_packet.end())); |
| }); |
| |
| H4HciPacketSendFn send_to_controller_fn( |
| []([[maybe_unused]] H4HciPacket packet) {}); |
| |
| ProxyHost proxy = ProxyHost( |
| std::move(send_to_host_fn), std::move(send_to_controller_fn), 2); |
| |
| proxy.HandleH4HciFromController(send_packet); |
| |
| // Verify callback was called. |
| EXPECT_EQ(send_capture.send_called, true); |
| } |
| |
| // ########## ReserveLeAclCredits Tests |
| |
| // Proxy Host should reserve requested ACL LE credits from controller's ACL LE |
| // credits when using LEReadBufferSizeV1 command. |
| TEST(ReserveLeAclCredits, ProxyCreditsReserveCreditsWithLEReadBufferSizeV1) { |
| std::array< |
| uint8_t, |
| emboss::LEReadBufferSizeV1CommandCompleteEventWriter::SizeInBytes() + 1> |
| send_packet; |
| emboss::LEReadBufferSizeV1CommandCompleteEventWriter view = |
| CreateAndPopulateToHostEventView< |
| emboss::LEReadBufferSizeV1CommandCompleteEventWriter>( |
| send_packet, emboss::EventCode::COMMAND_COMPLETE); |
| view.command_complete().command_opcode_enum().Write( |
| emboss::OpCode::LE_READ_BUFFER_SIZE_V1); |
| view.total_num_le_acl_data_packets().Write(10); |
| |
| bool send_called = false; |
| H4HciPacketSendFn send_to_host_fn([&send_called](H4HciPacket h4_packet) { |
| send_called = true; |
| emboss::LEReadBufferSizeV1CommandCompleteEventWriter view = |
| MakeEmboss<emboss::LEReadBufferSizeV1CommandCompleteEventWriter>( |
| H4HciSubspan(h4_packet)); |
| // Should reserve 2 credits from original total of 10 (so 8 left for host). |
| EXPECT_EQ(view.total_num_le_acl_data_packets().Read(), 8); |
| }); |
| |
| H4HciPacketSendFn send_to_controller_fn( |
| []([[maybe_unused]] H4HciPacket packet) {}); |
| |
| ProxyHost proxy = ProxyHost( |
| std::move(send_to_host_fn), std::move(send_to_controller_fn), 2); |
| |
| proxy.HandleH4HciFromController(send_packet); |
| |
| EXPECT_EQ(proxy.GetNumFreeLeAclPackets(), 2); |
| |
| // Verify to controller callback was called. |
| EXPECT_EQ(send_called, true); |
| } |
| |
| // Proxy Host should reserve requested ACL LE credits from controller's ACL LE |
| // credits when using LEReadBufferSizeV2 command. |
| TEST(ReserveLeAclCredits, ProxyCreditsReserveCreditsWithLEReadBufferSizeV2) { |
| std::array< |
| uint8_t, |
| emboss::LEReadBufferSizeV2CommandCompleteEventWriter::SizeInBytes() + 1> |
| send_packet; |
| emboss::LEReadBufferSizeV2CommandCompleteEventWriter view = |
| CreateAndPopulateToHostEventView< |
| emboss::LEReadBufferSizeV2CommandCompleteEventWriter>( |
| send_packet, emboss::EventCode::COMMAND_COMPLETE); |
| view.command_complete().command_opcode_enum().Write( |
| emboss::OpCode::LE_READ_BUFFER_SIZE_V2); |
| view.total_num_le_acl_data_packets().Write(10); |
| |
| bool send_called = false; |
| H4HciPacketSendFn send_to_host_fn([&send_called](H4HciPacket h4_packet) { |
| send_called = true; |
| emboss::LEReadBufferSizeV2CommandCompleteEventWriter view = |
| MakeEmboss<emboss::LEReadBufferSizeV2CommandCompleteEventWriter>( |
| H4HciSubspan(h4_packet)); |
| // Should reserve 2 credits from original total of 10 (so 8 left for host). |
| EXPECT_EQ(view.total_num_le_acl_data_packets().Read(), 8); |
| }); |
| |
| H4HciPacketSendFn send_to_controller_fn( |
| []([[maybe_unused]] H4HciPacket packet) {}); |
| |
| ProxyHost proxy = ProxyHost( |
| std::move(send_to_host_fn), std::move(send_to_controller_fn), 2); |
| |
| proxy.HandleH4HciFromController(send_packet); |
| |
| EXPECT_EQ(proxy.GetNumFreeLeAclPackets(), 2); |
| |
| // Verify to controller callback was called. |
| EXPECT_EQ(send_called, true); |
| } |
| |
| // If controller provides less than wanted credits, we should reserve that |
| // smaller amount. |
| TEST(ReserveLeAclCredits, ProxyCreditsCappedByControllerCredits) { |
| std::array< |
| uint8_t, |
| emboss::LEReadBufferSizeV1CommandCompleteEventWriter::SizeInBytes() + 1> |
| send_packet; |
| emboss::LEReadBufferSizeV1CommandCompleteEventWriter view = |
| CreateAndPopulateToHostEventView< |
| emboss::LEReadBufferSizeV1CommandCompleteEventWriter>( |
| send_packet, emboss::EventCode::COMMAND_COMPLETE); |
| view.command_complete().command_opcode_enum().Write( |
| emboss::OpCode::LE_READ_BUFFER_SIZE_V1); |
| view.total_num_le_acl_data_packets().Write(5); |
| |
| bool send_called = false; |
| H4HciPacketSendFn send_to_host_fn([&send_called](H4HciPacket h4_packet) { |
| send_called = true; |
| // We want 7, but can reserve only 5 from original 5 (so 0 left for host). |
| emboss::LEReadBufferSizeV1CommandCompleteEventWriter view = |
| MakeEmboss<emboss::LEReadBufferSizeV1CommandCompleteEventWriter>( |
| H4HciSubspan(h4_packet)); |
| EXPECT_EQ(view.total_num_le_acl_data_packets().Read(), 0); |
| }); |
| |
| H4HciPacketSendFn send_to_controller_fn( |
| []([[maybe_unused]] H4HciPacket packet) {}); |
| |
| ProxyHost proxy = ProxyHost( |
| std::move(send_to_host_fn), std::move(send_to_controller_fn), 7); |
| |
| proxy.HandleH4HciFromController(send_packet); |
| |
| EXPECT_EQ(proxy.GetNumFreeLeAclPackets(), 5); |
| |
| // Verify to controller callback was called. |
| EXPECT_EQ(send_called, true); |
| } |
| |
| // Proxy Host can reserve zero credits from controller's ACL LE credits. |
| TEST(ReserveLeAclCredits, ProxyCreditsReserveZeroCredits) { |
| std::array< |
| uint8_t, |
| emboss::LEReadBufferSizeV1CommandCompleteEventWriter::SizeInBytes() + 1> |
| send_packet; |
| emboss::LEReadBufferSizeV1CommandCompleteEventWriter view = |
| CreateAndPopulateToHostEventView< |
| emboss::LEReadBufferSizeV1CommandCompleteEventWriter>( |
| send_packet, emboss::EventCode::COMMAND_COMPLETE); |
| view.command_complete().command_opcode_enum().Write( |
| emboss::OpCode::LE_READ_BUFFER_SIZE_V1); |
| view.total_num_le_acl_data_packets().Write(10); |
| |
| bool send_called = false; |
| H4HciPacketSendFn send_to_host_fn([&send_called](H4HciPacket h4_packet) { |
| send_called = true; |
| emboss::LEReadBufferSizeV1CommandCompleteEventWriter view = |
| MakeEmboss<emboss::LEReadBufferSizeV1CommandCompleteEventWriter>( |
| H4HciSubspan(h4_packet)); |
| // Should reserve 0 credits from original total of 10 (so 10 left for host). |
| EXPECT_EQ(view.total_num_le_acl_data_packets().Read(), 10); |
| }); |
| |
| H4HciPacketSendFn send_to_controller_fn( |
| []([[maybe_unused]] H4HciPacket packet) {}); |
| |
| ProxyHost proxy = ProxyHost( |
| std::move(send_to_host_fn), std::move(send_to_controller_fn), 0); |
| |
| proxy.HandleH4HciFromController(send_packet); |
| |
| EXPECT_EQ(proxy.GetNumFreeLeAclPackets(), 0); |
| |
| // Verify to controller callback was called. |
| EXPECT_EQ(send_called, true); |
| } |
| |
| // If controller has no credits, proxy should reserve none. |
| TEST(ReserveLeAclPackets, ProxyCreditsZeroWhenHostCreditsZero) { |
| std::array< |
| uint8_t, |
| emboss::LEReadBufferSizeV1CommandCompleteEventWriter::SizeInBytes() + 1> |
| send_packet; |
| emboss::LEReadBufferSizeV1CommandCompleteEventWriter view = |
| CreateAndPopulateToHostEventView< |
| emboss::LEReadBufferSizeV1CommandCompleteEventWriter>( |
| send_packet, emboss::EventCode::COMMAND_COMPLETE); |
| view.command_complete().command_opcode_enum().Write( |
| emboss::OpCode::LE_READ_BUFFER_SIZE_V1); |
| view.total_num_le_acl_data_packets().Write(0); |
| |
| bool send_called = false; |
| H4HciPacketSendFn send_to_host_fn([&send_called](H4HciPacket h4_packet) { |
| send_called = true; |
| emboss::LEReadBufferSizeV1CommandCompleteEventWriter view = |
| MakeEmboss<emboss::LEReadBufferSizeV1CommandCompleteEventWriter>( |
| H4HciSubspan(h4_packet)); |
| // Should reserve 0 credit from original total of 0 (so 0 left for host). |
| EXPECT_EQ(view.total_num_le_acl_data_packets().Read(), 0); |
| }); |
| |
| H4HciPacketSendFn send_to_controller_fn( |
| []([[maybe_unused]] H4HciPacket packet) {}); |
| |
| ProxyHost proxy = ProxyHost( |
| std::move(send_to_host_fn), std::move(send_to_controller_fn), 2); |
| |
| proxy.HandleH4HciFromController(send_packet); |
| |
| EXPECT_EQ(proxy.GetNumFreeLeAclPackets(), 0); |
| |
| // Verify to controller callback was called. |
| EXPECT_EQ(send_called, true); |
| } |
| |
| TEST(ReserveLeAclPackets, ProxyCreditsZeroWhenNotInitialized) { |
| H4HciPacketSendFn send_to_host_fn([]([[maybe_unused]] H4HciPacket packet) {}); |
| |
| H4HciPacketSendFn send_to_controller_fn( |
| []([[maybe_unused]] H4HciPacket packet) {}); |
| |
| ProxyHost proxy = ProxyHost( |
| std::move(send_to_host_fn), std::move(send_to_controller_fn), 2); |
| |
| EXPECT_EQ(proxy.GetNumFreeLeAclPackets(), 0); |
| } |
| |
| } // namespace |
| } // namespace pw::bluetooth::proxy |