// 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 "loopback.h" #include #include #include "gtest/gtest.h" namespace fhb = fuchsia_hardware_bluetooth; namespace bt_hci_virtual { // TODO: https://pwbug.dev/369120118 - Use ScopedGlobalLogger when it is // available in the SDK. See fxr/1110057. class LoopbackTest : public ::testing::Test, public fidl::AsyncEventHandler, public fidl::AsyncEventHandler { public: void SetUp() override { zx::channel loopback_channel_device_end; zx::channel::create(0, &loopback_chan_, &loopback_channel_device_end); int add_child_cb_count = 0; auto add_child_cb = [&](fuchsia_driver_framework::wire::NodeAddArgs) { add_child_cb_count++; }; zx_status_t status = loopback_device_.Initialize(std::move(loopback_channel_device_end), "loopback", std::move(add_child_cb)); ASSERT_EQ(status, ZX_OK); ASSERT_EQ(add_child_cb_count, 1); loopback_chan_wait_.set_object(loopback_chan_.get()); loopback_chan_wait_.set_trigger(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED); zx_status_t wait_begin_status = loopback_chan_wait_.Begin(dispatcher()); ASSERT_EQ(wait_begin_status, ZX_OK); // There's no driver instance to set up the logger for this test, so create // a driver logger for this test. InitializeLogger(); OpenHciTransport(); OpenSnoop(); fdf_testing_run_until_idle(); } void TearDown() override { loopback_chan_wait_.Cancel(); fdf_testing_run_until_idle(); runtime_.ShutdownAllDispatchers(fdf::Dispatcher::GetCurrent()->get()); fdf::Logger::SetGlobalInstance(nullptr); } async_dispatcher_t* dispatcher() { return fdf::Dispatcher::GetCurrent()->async_dispatcher(); } LoopbackDevice& device() { return loopback_device_; } zx::channel& chan() { return loopback_chan_; } fidl::Client& hci_client() { return hci_client_; } fidl::Client& snoop_client() { return snoop_client_; } const std::vector>& snoop_packets() const { return snoop_packets_; } const std::vector>& dropped_snoop_packets() { return dropped_snoop_packets_; } // Packets received on the loopback channel (i.e. packets sent by // LoopbackDevice). const std::vector>& sent_packets() const { return sent_packets_; } const std::vector& received_packets() const { return received_packets_; } private: // fidl::AsyncEventHandler overrides: void OnReceive( ::fidl::Event<::fuchsia_hardware_bluetooth::HciTransport::OnReceive>& event) override { received_packets_.emplace_back(std::move(event)); } void handle_unknown_event( fidl::UnknownEventMetadata metadata) override {} // fidl::AsyncEventHandler overrides: void OnObservePacket( fidl::Event& event) override { snoop_packets_.emplace_back(std::move(event)); } void OnDroppedPackets( fidl::Event& event) override { dropped_snoop_packets_.emplace_back(std::move(event)); } void handle_unknown_event( fidl::UnknownEventMetadata metadata) override {} void OpenHciTransport() { auto vendor_endpoints = fidl::CreateEndpoints(); loopback_device_.Connect(std::move(vendor_endpoints->server)); vendor_client_.Bind(std::move(vendor_endpoints->client), dispatcher()); fdf_testing_run_until_idle(); std::optional> features; vendor_client_->GetFeatures().Then( [&features](fidl::Result& result) { features = std::move(result); }); fdf_testing_run_until_idle(); ASSERT_TRUE(features.has_value()); ASSERT_TRUE(features->is_ok()); EXPECT_EQ(features->value(), fhb::VendorFeatures()); vendor_client_->OpenHciTransport().Then( [this](fidl::Result& result) { ASSERT_TRUE(result.is_ok()); hci_client_.Bind(std::move(result.value().channel()), dispatcher(), /*event_handler=*/this); }); fdf_testing_run_until_idle(); ASSERT_TRUE(hci_client_.is_valid()); } void OpenSnoop() { auto vendor_endpoints = fidl::CreateEndpoints(); loopback_device_.Connect(std::move(vendor_endpoints->server)); snoop_vendor_client_.Bind(std::move(vendor_endpoints->client), dispatcher()); snoop_vendor_client_->OpenSnoop().Then( [this](fidl::Result& result) { ASSERT_TRUE(result.is_ok()); snoop_client_.Bind(std::move(result.value().channel()), dispatcher(), /*event_handler=*/this); }); fdf_testing_run_until_idle(); ASSERT_TRUE(snoop_vendor_client_.is_valid()); // Simulate bt-snoop dropping the Vendor client after getting a Snoop // client. auto _ = snoop_vendor_client_.UnbindMaybeGetEndpoint(); } void InitializeLogger() { std::vector entries; zx::result open_result = component::OpenServiceRoot(); ZX_ASSERT(open_result.is_ok()); ::fidl::ClientEnd<::fuchsia_io::Directory> svc = std::move(*open_result); entries.emplace_back(fuchsia_component_runner::ComponentNamespaceEntry{{ .path = "/svc", .directory = std::move(svc), }}); // Create Namespace object from the entries. auto ns = fdf::Namespace::Create(entries); ZX_ASSERT(ns.is_ok()); // Create Logger with dispatcher and namespace. logger_ = fdf::Logger::Create2(*ns, dispatcher(), "vendor-hci-logger"); ZX_ASSERT(logger_); fdf::Logger::SetGlobalInstance(logger_.get()); } void OnChannelReady(async_dispatcher_t*, async::WaitBase* wait, zx_status_t status, const zx_packet_signal_t* signal) { if (status == ZX_ERR_CANCELED) { return; } ASSERT_EQ(status, ZX_OK) << zx_status_get_string(status); if (signal->observed & ZX_CHANNEL_READABLE) { // Make byte buffer arbitrarily large enough to hold test packets. std::vector bytes(255); uint32_t actual_bytes; zx_status_t read_status = loopback_chan_.read( /*flags=*/0, bytes.data(), /*handles=*/nullptr, static_cast(bytes.size()), /*num_handles=*/0, &actual_bytes, /*actual_handles=*/nullptr); ASSERT_EQ(read_status, ZX_OK) << zx_status_get_string(read_status); bytes.resize(actual_bytes); sent_packets_.push_back(std::move(bytes)); } if (signal->observed & ZX_CHANNEL_PEER_CLOSED) { FDF_LOG(INFO, "Loopback channel peer closed"); return; } // The wait needs to be restarted. zx_status_t wait_begin_status = loopback_chan_wait_.Begin(dispatcher()); ASSERT_EQ(wait_begin_status, ZX_OK) << zx_status_get_string(wait_begin_status); } // Attaches a foreground dispatcher for us automatically. fdf_testing::DriverRuntime runtime_; std::unique_ptr logger_; zx::channel loopback_chan_; LoopbackDevice loopback_device_; fidl::Client vendor_client_; fidl::Client hci_client_; fidl::Client snoop_vendor_client_; fidl::Client snoop_client_; std::vector> snoop_packets_; std::vector> dropped_snoop_packets_; std::vector> sent_packets_; std::vector received_packets_; async::WaitMethod loopback_chan_wait_{this}; }; TEST_F(LoopbackTest, SendManyCommandPackets) { int send_cb_count = 0; for (uint8_t i = 0; i < 10; i++) { fidl::Request request = fidl::Request::WithCommand({i, 0x07, 0x08}); hci_client()->Send(request).Then( [&send_cb_count](fidl::Result& result) { send_cb_count++; }); } fdf_testing_run_until_idle(); EXPECT_EQ(send_cb_count, 10); ASSERT_EQ(sent_packets().size(), 10u); for (uint8_t i = 0; i < 10; i++) { auto& packet = sent_packets()[i]; std::vector expected = {0x01, // packet indicator (command) i, 0x07, 0x08}; EXPECT_EQ(packet, expected); } ASSERT_EQ(snoop_packets().size(), 10u); for (uint8_t i = 0; i < 10; i++) { auto& packet = snoop_packets()[i]; EXPECT_EQ(packet.direction(), fhb::PacketDirection::kHostToController); EXPECT_EQ(packet.sequence(), i + 1); ASSERT_EQ(packet.packet()->Which(), fhb::SnoopPacket::Tag::kCommand); std::vector expected = {i, 0x07, 0x08}; EXPECT_EQ(packet.packet()->command().value(), expected); } } TEST_F(LoopbackTest, SendManyAclPackets) { int send_cb_count = 0; for (uint8_t i = 0; i < 10; i++) { fidl::Request request = fidl::Request::WithAcl({i, 0x07, 0x08}); hci_client()->Send(request).Then( [&send_cb_count](fidl::Result& result) { send_cb_count++; }); } fdf_testing_run_until_idle(); EXPECT_EQ(send_cb_count, 10); ASSERT_EQ(sent_packets().size(), 10u); for (uint8_t i = 0; i < 10; i++) { auto& packet = sent_packets()[i]; std::vector expected = {0x02, // packet indicator (ACL) i, 0x07, 0x08}; EXPECT_EQ(packet, expected); } ASSERT_EQ(snoop_packets().size(), 10u); for (uint8_t i = 0; i < 10; i++) { auto& packet = snoop_packets()[i]; EXPECT_EQ(packet.direction(), fhb::PacketDirection::kHostToController); EXPECT_EQ(packet.sequence(), i + 1); ASSERT_EQ(packet.packet()->Which(), fhb::SnoopPacket::Tag::kAcl); std::vector expected = {i, 0x07, 0x08}; EXPECT_EQ(packet.packet()->acl().value(), expected); } } TEST_F(LoopbackTest, ReceiveManyEventPackets) { for (uint8_t i = 0; i < LoopbackDevice::kMaxReceiveUnackedPackets; i++) { std::array packet = {0x04, // packet indicator (event), i, 0x05}; zx_status_t status = chan().write(/*flags=*/0, packet.data(), static_cast(packet.size()), /*handles=*/nullptr, /*num_handles=*/0); ASSERT_EQ(status, ZX_OK); } fdf_testing_run_until_idle(); ASSERT_EQ(received_packets().size(), LoopbackDevice::kMaxReceiveUnackedPackets); for (uint8_t i = 0; i < LoopbackDevice::kMaxReceiveUnackedPackets; i++) { auto& packet = received_packets()[i]; ASSERT_EQ(packet.Which(), fhb::ReceivedPacket::Tag::kEvent); std::vector expected = {i, 0x05}; EXPECT_EQ(packet.event().value(), expected); } ASSERT_EQ(snoop_packets().size(), LoopbackDevice::kMaxReceiveUnackedPackets); for (uint8_t i = 0; i < LoopbackDevice::kMaxReceiveUnackedPackets; i++) { auto& packet = snoop_packets()[i]; EXPECT_EQ(packet.direction(), fhb::PacketDirection::kControllerToHost); EXPECT_EQ(packet.sequence(), i + 1); ASSERT_EQ(packet.packet()->Which(), fhb::SnoopPacket::Tag::kEvent); std::vector expected = {i, 0x05}; EXPECT_EQ(packet.packet()->event().value(), expected); } } TEST_F(LoopbackTest, ReceiveAndQueueAndAckManyAclPackets) { const uint8_t arbitrary_value = 0x05; // 2 packets should be queued const size_t num_packets_sent = LoopbackDevice::kMaxReceiveUnackedPackets + 2; for (uint8_t i = 0; i < num_packets_sent; i++) { std::array packet = {0x02, // packet indicator (acl), i, arbitrary_value}; zx_status_t status = chan().write(/*flags=*/0, packet.data(), static_cast(packet.size()), /*handles=*/nullptr, /*num_handles=*/0); ASSERT_EQ(status, ZX_OK); } fdf_testing_run_until_idle(); ASSERT_EQ(received_packets().size(), LoopbackDevice::kMaxReceiveUnackedPackets); for (uint8_t i = 0; i < LoopbackDevice::kMaxReceiveUnackedPackets; i++) { auto& packet = received_packets()[i]; ASSERT_EQ(packet.Which(), fhb::ReceivedPacket::Tag::kAcl); std::vector expected = {i, arbitrary_value}; EXPECT_EQ(packet.acl().value(), expected); } ASSERT_EQ(snoop_packets().size(), LoopbackDevice::kMaxSnoopUnackedPackets); for (uint8_t i = 0; i < LoopbackDevice::kMaxSnoopUnackedPackets; i++) { auto& packet = snoop_packets()[i]; EXPECT_EQ(packet.direction(), fhb::PacketDirection::kControllerToHost); EXPECT_EQ(packet.sequence(), i + 1); ASSERT_EQ(packet.packet()->Which(), fhb::SnoopPacket::Tag::kAcl); std::vector expected = {i, arbitrary_value}; EXPECT_EQ(packet.packet()->acl().value(), expected); } // Ack 2x so that the 2 queued packets are sent. fit::result result = hci_client()->AckReceive(); ASSERT_TRUE(result.is_ok()); result = hci_client()->AckReceive(); ASSERT_TRUE(result.is_ok()); fdf_testing_run_until_idle(); ASSERT_EQ(received_packets().size(), num_packets_sent); for (uint8_t i = 0; i < num_packets_sent; i++) { auto& packet = received_packets()[i]; ASSERT_EQ(packet.Which(), fhb::ReceivedPacket::Tag::kAcl); std::vector expected = {i, arbitrary_value}; EXPECT_EQ(packet.acl().value(), expected); } // Ack 2x so that the 2 queued packets are sent. result = snoop_client()->AcknowledgePackets( fhb::SnoopAcknowledgePacketsRequest(2)); ASSERT_TRUE(result.is_ok()); fdf_testing_run_until_idle(); ASSERT_EQ(snoop_packets().size(), num_packets_sent); for (uint8_t i = 0; i < num_packets_sent; i++) { auto& packet = snoop_packets()[i]; EXPECT_EQ(packet.direction(), fhb::PacketDirection::kControllerToHost); EXPECT_EQ(packet.sequence(), i + 1); ASSERT_EQ(packet.packet()->Which(), fhb::SnoopPacket::Tag::kAcl); std::vector expected = {i, arbitrary_value}; EXPECT_EQ(packet.packet()->acl().value(), expected); } } TEST_F(LoopbackTest, DropSnoopPackets) { // 2 packets should get dropped const size_t num_dropped = 2; const size_t num_packets_sent = LoopbackDevice::kMaxSnoopUnackedPackets + LoopbackDevice::kMaxSnoopQueueSize + num_dropped; size_t send_cb_count = 0; for (uint8_t i = 0; i < num_packets_sent; i++) { fidl::Request request = fidl::Request::WithAcl({i, 0x07, 0x08}); hci_client()->Send(request).Then( [&send_cb_count](fidl::Result& result) { send_cb_count++; }); } fdf_testing_run_until_idle(); EXPECT_EQ(send_cb_count, num_packets_sent); ASSERT_EQ(snoop_packets().size(), LoopbackDevice::kMaxSnoopUnackedPackets); fit::result result = snoop_client()->AcknowledgePackets(fhb::SnoopAcknowledgePacketsRequest( LoopbackDevice::kMaxSnoopUnackedPackets)); ASSERT_TRUE(result.is_ok()); fdf_testing_run_until_idle(); ASSERT_EQ(snoop_packets().size(), 2 * LoopbackDevice::kMaxSnoopUnackedPackets - num_dropped); for (uint8_t i = LoopbackDevice::kMaxSnoopUnackedPackets; static_cast(i) < snoop_packets().size(); i++) { auto& packet = snoop_packets()[i]; EXPECT_EQ(packet.direction(), fhb::PacketDirection::kHostToController); EXPECT_EQ(packet.sequence(), i + 1 + num_dropped); ASSERT_EQ(packet.packet()->Which(), fhb::SnoopPacket::Tag::kAcl); std::vector expected = { static_cast(i + num_dropped), 0x07, 0x08}; EXPECT_EQ(packet.packet()->acl().value(), expected); } ASSERT_EQ(dropped_snoop_packets().size(), 1u); EXPECT_EQ(dropped_snoop_packets()[0].sent(), 2u); EXPECT_EQ(dropped_snoop_packets()[0].received(), 0u); } } // namespace bt_hci_virtual