| // Copyright 2022 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 "bt_transport_uart.h" |
| |
| #include <fidl/fuchsia.hardware.serial/cpp/wire.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/async_patterns/testing/cpp/dispatcher_bound.h> |
| #include <lib/driver/testing/cpp/driver_lifecycle.h> |
| #include <lib/driver/testing/cpp/driver_runtime.h> |
| #include <lib/driver/testing/cpp/fixtures/gtest_fixture.h> |
| #include <lib/driver/testing/cpp/test_environment.h> |
| #include <lib/driver/testing/cpp/test_node.h> |
| #include <lib/fdio/directory.h> |
| #include <zircon/device/bt-hci.h> |
| |
| #include <queue> |
| |
| namespace bt_transport_uart { |
| namespace { |
| |
| // HCI UART packet indicators |
| enum BtHciPacketIndicator { |
| kHciNone = 0, |
| kHciCommand = 1, |
| kHciAclData = 2, |
| kHciSco = 3, |
| kHciEvent = 4, |
| }; |
| |
| class FakeSerialDevice : public fdf::WireServer<fuchsia_hardware_serialimpl::Device> { |
| public: |
| explicit FakeSerialDevice() = default; |
| |
| fuchsia_hardware_serialimpl::Service::InstanceHandler GetInstanceHandler() { |
| return fuchsia_hardware_serialimpl::Service::InstanceHandler({ |
| .device = binding_group_.CreateHandler(this, fdf::Dispatcher::GetCurrent()->get(), |
| fidl::kIgnoreBindingClosure), |
| }); |
| } |
| |
| void QueueReadValue(std::vector<uint8_t> buffer) { |
| read_rsp_queue_.emplace(std::move(buffer)); |
| MaybeRespondToRead(); |
| } |
| |
| void QueueWithoutSignaling(std::vector<uint8_t> buffer) { |
| read_rsp_queue_.emplace(std::move(buffer)); |
| } |
| |
| const std::vector<std::vector<uint8_t>> writes() const { return writes_; } |
| |
| bool canceled() const { return canceled_; } |
| |
| bool enabled() const { return enabled_; } |
| |
| void set_writes_paused(bool paused) { |
| writes_paused_ = paused; |
| MaybeRespondToWrite(); |
| } |
| |
| // fuchsia_hardware_serialimpl::Device FIDL implementation. |
| void GetInfo(fdf::Arena& arena, GetInfoCompleter::Sync& completer) override { |
| fuchsia_hardware_serial::wire::SerialPortInfo info = { |
| .serial_class = fuchsia_hardware_serial::Class::kBluetoothHci, |
| }; |
| |
| completer.buffer(arena).ReplySuccess(info); |
| } |
| void Config(ConfigRequestView request, fdf::Arena& arena, |
| ConfigCompleter::Sync& completer) override { |
| completer.buffer(arena).ReplySuccess(); |
| } |
| |
| void Enable(EnableRequestView request, fdf::Arena& arena, |
| EnableCompleter::Sync& completer) override { |
| enabled_ = request->enable; |
| completer.buffer(arena).ReplySuccess(); |
| } |
| |
| void Read(fdf::Arena& arena, ReadCompleter::Sync& completer) override { |
| // Serial only supports 1 pending read at a time. |
| if (!enabled_) { |
| completer.buffer(arena).ReplyError(ZX_ERR_IO_REFUSED); |
| return; |
| } |
| read_request_.emplace(ReadRequest{std::move(arena), completer.ToAsync()}); |
| // The real serial async handler will call the callback immediately if there is data |
| // available, do so here to simulate the recursive callstack there. |
| MaybeRespondToRead(); |
| } |
| |
| void Write(WriteRequestView request, fdf::Arena& arena, |
| WriteCompleter::Sync& completer) override { |
| ASSERT_FALSE(write_request_); |
| std::vector<uint8_t> buffer(request->data.data(), request->data.data() + request->data.count()); |
| writes_.emplace_back(std::move(buffer)); |
| write_request_.emplace(WriteRequest{std::move(arena), completer.ToAsync()}); |
| MaybeRespondToWrite(); |
| } |
| |
| void CancelAll(fdf::Arena& arena, CancelAllCompleter::Sync& completer) override { |
| if (read_request_) { |
| read_request_->completer.buffer(read_request_->arena).ReplyError(ZX_ERR_CANCELED); |
| read_request_.reset(); |
| } |
| canceled_ = true; |
| completer.buffer(arena).Reply(); |
| } |
| |
| void handle_unknown_method( |
| fidl::UnknownMethodMetadata<fuchsia_hardware_serialimpl::Device> metadata, |
| fidl::UnknownMethodCompleter::Sync& completer) override { |
| ZX_PANIC("Unknown method in Serial requests"); |
| } |
| |
| private: |
| struct ReadRequest { |
| fdf::Arena arena; |
| ReadCompleter::Async completer; |
| }; |
| struct WriteRequest { |
| fdf::Arena arena; |
| WriteCompleter::Async completer; |
| }; |
| |
| void MaybeRespondToRead() { |
| if (read_rsp_queue_.empty() || !read_request_) { |
| return; |
| } |
| std::vector<uint8_t> buffer = std::move(read_rsp_queue_.front()); |
| read_rsp_queue_.pop(); |
| // Callback may send new read request synchronously, so clear read_req_. |
| ReadRequest request = std::move(read_request_.value()); |
| read_request_.reset(); |
| auto data_view = fidl::VectorView<uint8_t>::FromExternal(buffer); |
| request.completer.buffer(request.arena).ReplySuccess(data_view); |
| } |
| |
| void MaybeRespondToWrite() { |
| if (writes_paused_) { |
| return; |
| } |
| if (!write_request_) { |
| return; |
| } |
| write_request_->completer.buffer(write_request_->arena).ReplySuccess(); |
| write_request_.reset(); |
| } |
| |
| bool enabled_ = false; |
| bool canceled_ = false; |
| bool writes_paused_ = false; |
| std::optional<ReadRequest> read_request_; |
| std::optional<WriteRequest> write_request_; |
| std::queue<std::vector<uint8_t>> read_rsp_queue_; |
| std::vector<std::vector<uint8_t>> writes_; |
| |
| fdf::ServerBindingGroup<fuchsia_hardware_serialimpl::Device> binding_group_; |
| }; |
| |
| class FixtureBasedTestEnvironment : fdf_testing::Environment { |
| public: |
| zx::result<> Serve(fdf::OutgoingDirectory& to_driver_vfs) override { |
| auto result = to_driver_vfs.AddService<fuchsia_hardware_serialimpl::Service>( |
| serial_device_.GetInstanceHandler()); |
| return result; |
| } |
| |
| FakeSerialDevice serial_device_; |
| }; |
| |
| class BackgroundFixtureConfig final { |
| public: |
| static constexpr bool kDriverOnForeground = false; |
| static constexpr bool kAutoStartDriver = true; |
| static constexpr bool kAutoStopDriver = false; |
| |
| using DriverType = BtTransportUart; |
| using EnvironmentType = FixtureBasedTestEnvironment; |
| }; |
| |
| class BtTransportUartTest : public fdf_testing::DriverTestFixture<BackgroundFixtureConfig> { |
| public: |
| BtTransportUartTest() = default; |
| |
| void SetUp() override { |
| EXPECT_TRUE(RunInEnvironmentTypeContext<bool>( |
| [](FixtureBasedTestEnvironment& env) { return env.serial_device_.enabled(); })); |
| |
| zx::result connect_result = Connect<fuchsia_hardware_bluetooth::HciService::Hci>(); |
| ASSERT_EQ(ZX_OK, connect_result.status_value()); |
| |
| ASSERT_EQ(ZX_OK, connect_result.status_value()); |
| hci_client_.Bind(std::move(connect_result.value())); |
| } |
| |
| void TearDown() override { |
| // Only PrepareStop() will be called in StopDriver(), Stop() won't be called. |
| zx::result prepare_stop_result = StopDriver(); |
| EXPECT_EQ(ZX_OK, prepare_stop_result.status_value()); |
| |
| EXPECT_TRUE(RunInEnvironmentTypeContext<bool>( |
| [](FixtureBasedTestEnvironment& env) { return env.serial_device_.canceled(); })); |
| } |
| |
| protected: |
| // The FIDL client used in the test to call into dut Hci server. |
| fidl::WireSyncClient<fuchsia_hardware_bluetooth::Hci> hci_client_; |
| }; |
| |
| // Test fixture that opens all channels and has helpers for reading/writing data. |
| class BtTransportUartHciProtocolTest : public BtTransportUartTest { |
| public: |
| void SetUp() override { |
| BtTransportUartTest::SetUp(); |
| |
| zx::channel cmd_chan_driver_end; |
| ASSERT_EQ(zx::channel::create(/*flags=*/0, &cmd_chan_, &cmd_chan_driver_end), ZX_OK); |
| { |
| auto result = hci_client_->OpenCommandChannel(std::move(cmd_chan_driver_end)); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_FALSE(result->is_error()); |
| } |
| // Configure wait for readable signal on command channel. |
| cmd_chan_readable_wait_.set_object(cmd_chan_.get()); |
| zx_status_t wait_begin_status = |
| cmd_chan_readable_wait_.Begin(fdf::Dispatcher::GetCurrent()->async_dispatcher()); |
| ASSERT_EQ(wait_begin_status, ZX_OK) << zx_status_get_string(wait_begin_status); |
| |
| zx::channel acl_chan_driver_end; |
| ASSERT_EQ(zx::channel::create(/*flags=*/0, &acl_chan_, &acl_chan_driver_end), ZX_OK); |
| { |
| auto result = hci_client_->OpenAclDataChannel(std::move(acl_chan_driver_end)); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_FALSE(result->is_error()); |
| } |
| // Configure wait for readable signal on ACL channel. |
| acl_chan_readable_wait_.set_object(acl_chan_.get()); |
| wait_begin_status = |
| acl_chan_readable_wait_.Begin(fdf::Dispatcher::GetCurrent()->async_dispatcher()); |
| ASSERT_EQ(wait_begin_status, ZX_OK) << zx_status_get_string(wait_begin_status); |
| |
| zx::channel sco_chan_driver_end; |
| ASSERT_EQ(zx::channel::create(/*flags=*/0, &sco_chan_, &sco_chan_driver_end), ZX_OK); |
| { |
| auto result = hci_client_->OpenScoDataChannel(std::move(sco_chan_driver_end)); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_FALSE(result->is_error()); |
| } |
| // Configure wait for readable signal on SCO channel. |
| sco_chan_readable_wait_.set_object(sco_chan_.get()); |
| wait_begin_status = |
| sco_chan_readable_wait_.Begin(fdf::Dispatcher::GetCurrent()->async_dispatcher()); |
| ASSERT_EQ(wait_begin_status, ZX_OK) << zx_status_get_string(wait_begin_status); |
| |
| zx::channel snoop_chan_driver_end; |
| ZX_ASSERT(zx::channel::create(/*flags=*/0, &snoop_chan_, &snoop_chan_driver_end) == ZX_OK); |
| { |
| auto result = hci_client_->OpenSnoopChannel(std::move(snoop_chan_driver_end)); |
| ASSERT_TRUE(result.ok()); |
| ASSERT_FALSE(result->is_error()); |
| } |
| // Configure wait for readable signal on snoop channel. |
| snoop_chan_readable_wait_.set_object(snoop_chan_.get()); |
| wait_begin_status = |
| snoop_chan_readable_wait_.Begin(fdf::Dispatcher::GetCurrent()->async_dispatcher()); |
| ASSERT_EQ(wait_begin_status, ZX_OK) << zx_status_get_string(wait_begin_status); |
| } |
| |
| void TearDown() override { |
| cmd_chan_readable_wait_.Cancel(); |
| cmd_chan_.reset(); |
| |
| acl_chan_readable_wait_.Cancel(); |
| acl_chan_.reset(); |
| |
| sco_chan_readable_wait_.Cancel(); |
| sco_chan_.reset(); |
| |
| snoop_chan_readable_wait_.Cancel(); |
| snoop_chan_.reset(); |
| |
| BtTransportUartTest::TearDown(); |
| } |
| |
| const std::vector<std::vector<uint8_t>>& hci_events() const { return cmd_chan_received_packets_; } |
| |
| const std::vector<std::vector<uint8_t>>& snoop_packets() const { |
| return snoop_chan_received_packets_; |
| } |
| |
| const std::vector<std::vector<uint8_t>>& received_acl_packets() const { |
| return acl_chan_received_packets_; |
| } |
| |
| const std::vector<std::vector<uint8_t>>& received_sco_packets() const { |
| return sco_chan_received_packets_; |
| } |
| |
| zx::channel* cmd_chan() { return &cmd_chan_; } |
| |
| zx::channel* acl_chan() { return &acl_chan_; } |
| |
| zx::channel* sco_chan() { return &sco_chan_; } |
| |
| private: |
| // This method is shared by the waits for all channels. |wait| is used to differentiate which |
| // wait called the method. |
| void OnChannelReady(async_dispatcher_t*, async::WaitBase* wait, zx_status_t status, |
| const zx_packet_signal_t* signal) { |
| ASSERT_EQ(status, ZX_OK); |
| ASSERT_TRUE(signal->observed & ZX_CHANNEL_READABLE); |
| |
| zx::unowned_channel chan; |
| std::vector<std::vector<uint8_t>>* received_packets = nullptr; |
| if (wait == &cmd_chan_readable_wait_) { |
| chan = zx::unowned_channel(cmd_chan_); |
| received_packets = &cmd_chan_received_packets_; |
| } else if (wait == &snoop_chan_readable_wait_) { |
| chan = zx::unowned_channel(snoop_chan_); |
| received_packets = &snoop_chan_received_packets_; |
| } else if (wait == &acl_chan_readable_wait_) { |
| chan = zx::unowned_channel(acl_chan_); |
| received_packets = &acl_chan_received_packets_; |
| } else if (wait == &sco_chan_readable_wait_) { |
| chan = zx::unowned_channel(sco_chan_); |
| received_packets = &sco_chan_received_packets_; |
| } else { |
| ADD_FAILURE() << "unexpected channel in OnChannelReady"; |
| return; |
| } |
| |
| // Make byte buffer arbitrarily large enough to hold test packets. |
| std::vector<uint8_t> bytes(255); |
| uint32_t actual_bytes; |
| zx_status_t read_status = chan->read( |
| /*flags=*/0, bytes.data(), /*handles=*/nullptr, static_cast<uint32_t>(bytes.size()), |
| /*num_handles=*/0, &actual_bytes, /*actual_handles=*/nullptr); |
| ASSERT_EQ(read_status, ZX_OK); |
| bytes.resize(actual_bytes); |
| received_packets->push_back(std::move(bytes)); |
| |
| // The wait needs to be restarted. |
| zx_status_t wait_begin_status = wait->Begin(fdf::Dispatcher::GetCurrent()->async_dispatcher()); |
| ASSERT_EQ(wait_begin_status, ZX_OK) << zx_status_get_string(wait_begin_status); |
| } |
| |
| zx::channel cmd_chan_; |
| zx::channel acl_chan_; |
| zx::channel sco_chan_; |
| zx::channel snoop_chan_; |
| |
| async::WaitMethod<BtTransportUartHciProtocolTest, &BtTransportUartHciProtocolTest::OnChannelReady> |
| cmd_chan_readable_wait_{this, zx_handle_t(), ZX_CHANNEL_READABLE}; |
| async::WaitMethod<BtTransportUartHciProtocolTest, &BtTransportUartHciProtocolTest::OnChannelReady> |
| snoop_chan_readable_wait_{this, zx_handle_t(), ZX_CHANNEL_READABLE}; |
| async::WaitMethod<BtTransportUartHciProtocolTest, &BtTransportUartHciProtocolTest::OnChannelReady> |
| acl_chan_readable_wait_{this, zx_handle_t(), ZX_CHANNEL_READABLE}; |
| async::WaitMethod<BtTransportUartHciProtocolTest, &BtTransportUartHciProtocolTest::OnChannelReady> |
| sco_chan_readable_wait_{this, zx_handle_t(), ZX_CHANNEL_READABLE}; |
| |
| std::vector<std::vector<uint8_t>> cmd_chan_received_packets_; |
| std::vector<std::vector<uint8_t>> snoop_chan_received_packets_; |
| std::vector<std::vector<uint8_t>> acl_chan_received_packets_; |
| std::vector<std::vector<uint8_t>> sco_chan_received_packets_; |
| }; |
| |
| TEST_F(BtTransportUartHciProtocolTest, SendAclPackets) { |
| const uint8_t kNumPackets = 25; |
| for (uint8_t i = 0; i < kNumPackets; i++) { |
| const std::vector<uint8_t> kAclPacket = {i}; |
| zx_status_t write_status = |
| acl_chan()->write(/*flags=*/0, kAclPacket.data(), static_cast<uint32_t>(kAclPacket.size()), |
| /*handles=*/nullptr, |
| /*num_handles=*/0); |
| ASSERT_EQ(write_status, ZX_OK); |
| } |
| // Allow ACL packets to be processed and sent to the serial device. |
| // This function waits until the condition in the lambda is satisfied, The default poll interval |
| // is 10 msec, the default wait timeout is 1 sec. The function returns true if it didn't timeout. |
| runtime().RunUntil([&]() { |
| return RunInEnvironmentTypeContext<size_t>([](FixtureBasedTestEnvironment& env) { |
| return env.serial_device_.writes().size(); |
| }) == kNumPackets; |
| }); |
| |
| const std::vector<std::vector<uint8_t>> packets = |
| RunInEnvironmentTypeContext<const std::vector<std::vector<uint8_t>>>( |
| [](FixtureBasedTestEnvironment& env) { return env.serial_device_.writes(); }); |
| ASSERT_EQ(packets.size(), kNumPackets); |
| for (uint8_t i = 0; i < kNumPackets; i++) { |
| // A packet indicator should be prepended. |
| std::vector<uint8_t> expected = {BtHciPacketIndicator::kHciAclData, i}; |
| EXPECT_EQ(packets[i], expected); |
| } |
| |
| runtime().RunUntil([&]() { return snoop_packets().size() == kNumPackets; }); |
| for (uint8_t i = 0; i < kNumPackets; i++) { |
| // Snoop packets should have a snoop packet flag prepended (NOT a UART packet indicator). |
| const std::vector<uint8_t> kExpectedSnoopPacket = {BT_HCI_SNOOP_TYPE_ACL, // Snoop packet flag |
| i}; |
| EXPECT_EQ(snoop_packets()[i], kExpectedSnoopPacket); |
| } |
| } |
| |
| TEST_F(BtTransportUartHciProtocolTest, AclReadableSignalIgnoredUntilFirstWriteCompletes) { |
| // Delay completion of first write. |
| RunInEnvironmentTypeContext( |
| [](FixtureBasedTestEnvironment& env) { return env.serial_device_.set_writes_paused(true); }); |
| |
| const uint8_t kNumPackets = 2; |
| for (uint8_t i = 0; i < kNumPackets; i++) { |
| const std::vector<uint8_t> kAclPacket = {i}; |
| zx_status_t write_status = |
| acl_chan()->write(/*flags=*/0, kAclPacket.data(), static_cast<uint32_t>(kAclPacket.size()), |
| /*handles=*/nullptr, |
| /*num_handles=*/0); |
| ASSERT_EQ(write_status, ZX_OK); |
| } |
| |
| // Wait until the first packet has been received by fake serial device. |
| runtime().RunUntil([&]() { |
| return RunInEnvironmentTypeContext<size_t>([](FixtureBasedTestEnvironment& env) { |
| return env.serial_device_.writes().size(); |
| }) == 1u; |
| }); |
| |
| // Call the first packet's completion callback. This should resume waiting for signals. |
| RunInEnvironmentTypeContext( |
| [](FixtureBasedTestEnvironment& env) { return env.serial_device_.set_writes_paused(false); }); |
| |
| // Wait for the readable signal to be processed, and both packets has been received by fake |
| // serial device. |
| runtime().RunUntil([&]() { |
| return RunInEnvironmentTypeContext<size_t>([](FixtureBasedTestEnvironment& env) { |
| return env.serial_device_.writes().size(); |
| }) == kNumPackets; |
| }); |
| |
| const std::vector<std::vector<uint8_t>> packets = |
| RunInEnvironmentTypeContext<const std::vector<std::vector<uint8_t>>>( |
| [](FixtureBasedTestEnvironment& env) { return env.serial_device_.writes(); }); |
| ASSERT_EQ(packets.size(), kNumPackets); |
| for (uint8_t i = 0; i < kNumPackets; i++) { |
| // A packet indicator should be prepended. |
| std::vector<uint8_t> expected = {BtHciPacketIndicator::kHciAclData, i}; |
| EXPECT_EQ(packets[i], expected); |
| } |
| } |
| |
| TEST_F(BtTransportUartHciProtocolTest, ReceiveAclPacketsIn2Parts) { |
| const std::vector<uint8_t> kSnoopAclBuffer = { |
| BT_HCI_SNOOP_TYPE_ACL | BT_HCI_SNOOP_FLAG_RECV, // Snoop packet flag |
| 0x00, |
| 0x00, // arbitrary header fields |
| 0x02, |
| 0x00, // 2-byte length in little endian |
| 0x01, |
| 0x02, // arbitrary payload |
| }; |
| std::vector<uint8_t> kSerialAclBuffer = kSnoopAclBuffer; |
| kSerialAclBuffer[0] = BtHciPacketIndicator::kHciAclData; |
| const std::vector<uint8_t> kAclBuffer(kSnoopAclBuffer.begin() + 1, kSnoopAclBuffer.end()); |
| // Split the packet length field in half to test corner case. |
| const std::vector<uint8_t> kPart1(kSerialAclBuffer.begin(), kSerialAclBuffer.begin() + 4); |
| const std::vector<uint8_t> kPart2(kSerialAclBuffer.begin() + 4, kSerialAclBuffer.end()); |
| |
| const size_t kNumPackets = 20; |
| for (size_t i = 0; i < kNumPackets; i++) { |
| RunInEnvironmentTypeContext([&](FixtureBasedTestEnvironment& env) { |
| return env.serial_device_.QueueReadValue(kPart1); |
| }); |
| RunInEnvironmentTypeContext([&](FixtureBasedTestEnvironment& env) { |
| return env.serial_device_.QueueReadValue(kPart2); |
| }); |
| } |
| |
| runtime().RunUntil( |
| [&]() { return received_acl_packets().size() == static_cast<size_t>(kNumPackets); }); |
| |
| for (const std::vector<uint8_t>& packet : received_acl_packets()) { |
| EXPECT_EQ(packet.size(), kAclBuffer.size()); |
| EXPECT_EQ(packet, kAclBuffer); |
| } |
| |
| runtime().RunUntil([&]() { return snoop_packets().size() == kNumPackets; }); |
| for (const std::vector<uint8_t>& packet : snoop_packets()) { |
| EXPECT_EQ(packet, kSnoopAclBuffer); |
| } |
| } |
| |
| TEST_F(BtTransportUartHciProtocolTest, ReceiveAclPacketsLotsInQueue) { |
| const std::vector<uint8_t> kSnoopAclBuffer = { |
| BT_HCI_SNOOP_TYPE_ACL | BT_HCI_SNOOP_FLAG_RECV, // Snoop packet flag |
| 0x00, |
| 0x00, // arbitrary header fields |
| 0x02, |
| 0x00, // 2-byte length in little endian |
| 0x01, |
| 0x02, // arbitrary payload |
| }; |
| std::vector<uint8_t> kSerialAclBuffer = kSnoopAclBuffer; |
| kSerialAclBuffer[0] = BtHciPacketIndicator::kHciAclData; |
| const std::vector<uint8_t> kAclBuffer(kSnoopAclBuffer.begin() + 1, kSnoopAclBuffer.end()); |
| // Split the packet length field in half to test corner case. |
| const std::vector<uint8_t> kPart1(kSerialAclBuffer.begin(), kSerialAclBuffer.begin() + 4); |
| const std::vector<uint8_t> kPart2(kSerialAclBuffer.begin() + 4, kSerialAclBuffer.end()); |
| |
| const size_t kNumPackets = 1000; |
| for (size_t i = 0; i < kNumPackets; i++) { |
| RunInEnvironmentTypeContext([&](FixtureBasedTestEnvironment& env) { |
| return env.serial_device_.QueueWithoutSignaling(kPart1); |
| }); |
| RunInEnvironmentTypeContext([&](FixtureBasedTestEnvironment& env) { |
| return env.serial_device_.QueueWithoutSignaling(kPart2); |
| }); |
| } |
| RunInEnvironmentTypeContext( |
| [&](FixtureBasedTestEnvironment& env) { return env.serial_device_.QueueReadValue(kPart1); }); |
| RunInEnvironmentTypeContext( |
| [&](FixtureBasedTestEnvironment& env) { return env.serial_device_.QueueReadValue(kPart2); }); |
| |
| // Wait Until all the packets to be received. |
| runtime().RunUntil( |
| [&]() { return received_acl_packets().size() == static_cast<size_t>(kNumPackets + 1); }); |
| |
| ASSERT_EQ(received_acl_packets().size(), static_cast<size_t>(kNumPackets + 1)); |
| for (const std::vector<uint8_t>& packet : received_acl_packets()) { |
| EXPECT_EQ(packet.size(), kAclBuffer.size()); |
| EXPECT_EQ(packet, kAclBuffer); |
| } |
| |
| runtime().RunUntil([&]() { return snoop_packets().size() == kNumPackets + 1; }); |
| for (const std::vector<uint8_t>& packet : snoop_packets()) { |
| EXPECT_EQ(packet, kSnoopAclBuffer); |
| } |
| } |
| |
| TEST_F(BtTransportUartHciProtocolTest, SendHciCommands) { |
| const std::vector<uint8_t> kSnoopCmd0 = { |
| BT_HCI_SNOOP_TYPE_CMD, // Snoop packet flag |
| 0x00, // arbitrary payload |
| }; |
| const std::vector<uint8_t> kCmd0(kSnoopCmd0.begin() + 1, kSnoopCmd0.end()); |
| const std::vector<uint8_t> kUartCmd0 = { |
| BtHciPacketIndicator::kHciCommand, // UART packet indicator |
| 0x00, // arbitrary payload |
| }; |
| zx_status_t write_status = |
| cmd_chan()->write(/*flags=*/0, kCmd0.data(), static_cast<uint32_t>(kCmd0.size()), |
| /*handles=*/nullptr, |
| /*num_handles=*/0); |
| EXPECT_EQ(write_status, ZX_OK); |
| |
| // Wait until the first packet is received. |
| runtime().RunUntil([&]() { |
| return RunInEnvironmentTypeContext<size_t>([](FixtureBasedTestEnvironment& env) { |
| return env.serial_device_.writes().size(); |
| }) == 1u; |
| }); |
| |
| const std::vector<uint8_t> kSnoopCmd1 = { |
| BT_HCI_SNOOP_TYPE_CMD, // Snoop packet flag |
| 0x01, // arbitrary payload |
| }; |
| const std::vector<uint8_t> kCmd1(kSnoopCmd1.begin() + 1, kSnoopCmd1.end()); |
| const std::vector<uint8_t> kUartCmd1 = { |
| BtHciPacketIndicator::kHciCommand, // UART packet indicator |
| 0x01, // arbitrary payload |
| }; |
| write_status = cmd_chan()->write(/*flags=*/0, kCmd1.data(), static_cast<uint32_t>(kCmd1.size()), |
| /*handles=*/nullptr, |
| /*num_handles=*/0); |
| EXPECT_EQ(write_status, ZX_OK); |
| |
| // Wait until the second packet is received. |
| runtime().RunUntil([&]() { |
| return RunInEnvironmentTypeContext<size_t>([](FixtureBasedTestEnvironment& env) { |
| return env.serial_device_.writes().size(); |
| }) == 2u; |
| }); |
| |
| const std::vector<std::vector<uint8_t>> packets = |
| RunInEnvironmentTypeContext<const std::vector<std::vector<uint8_t>>>( |
| [](FixtureBasedTestEnvironment& env) { return env.serial_device_.writes(); }); |
| EXPECT_EQ(packets[0], kUartCmd0); |
| EXPECT_EQ(packets[1], kUartCmd1); |
| |
| runtime().RunUntil([&]() { return snoop_packets().size() == 2u; }); |
| EXPECT_EQ(snoop_packets()[0], kSnoopCmd0); |
| EXPECT_EQ(snoop_packets()[1], kSnoopCmd1); |
| } |
| |
| TEST_F(BtTransportUartHciProtocolTest, CommandReadableSignalIgnoredUntilFirstWriteCompletes) { |
| // Delay completion of first write. |
| RunInEnvironmentTypeContext( |
| [](FixtureBasedTestEnvironment& env) { return env.serial_device_.set_writes_paused(true); }); |
| |
| const std::vector<uint8_t> kUartCmd0 = { |
| BtHciPacketIndicator::kHciCommand, // UART packet indicator |
| 0x00, // arbitrary payload |
| }; |
| const std::vector<uint8_t> kCmd0(kUartCmd0.begin() + 1, kUartCmd0.end()); |
| zx_status_t write_status = |
| cmd_chan()->write(/*flags=*/0, kCmd0.data(), static_cast<uint32_t>(kCmd0.size()), |
| /*handles=*/nullptr, |
| /*num_handles=*/0); |
| EXPECT_EQ(write_status, ZX_OK); |
| |
| const std::vector<uint8_t> kUartCmd1 = { |
| BtHciPacketIndicator::kHciCommand, // UART packet indicator |
| 0x01, // arbitrary payload |
| }; |
| const std::vector<uint8_t> kCmd1(kUartCmd1.begin() + 1, kUartCmd1.end()); |
| write_status = cmd_chan()->write(/*flags=*/0, kCmd1.data(), static_cast<uint32_t>(kCmd1.size()), |
| /*handles=*/nullptr, |
| /*num_handles=*/0); |
| EXPECT_EQ(write_status, ZX_OK); |
| |
| // Wait until the first packet is received. |
| runtime().RunUntil([&]() { |
| return RunInEnvironmentTypeContext<size_t>([](FixtureBasedTestEnvironment& env) { |
| return env.serial_device_.writes().size(); |
| }) == 1u; |
| }); |
| |
| // Make sure the number of packet received never run over 1 before the pause is released. |
| EXPECT_FALSE(runtime().RunWithTimeoutOrUntil( |
| [&]() { |
| return RunInEnvironmentTypeContext<size_t>([](FixtureBasedTestEnvironment& env) { |
| return env.serial_device_.writes().size(); |
| }) > 1u; |
| }, |
| zx::msec(500))); |
| |
| // Call the first command's completion callback. This should resume waiting for signals. |
| RunInEnvironmentTypeContext( |
| [](FixtureBasedTestEnvironment& env) { return env.serial_device_.set_writes_paused(false); }); |
| |
| // Wait for the readable signal to be processed and the second packet is received. |
| runtime().RunUntil([&]() { |
| return RunInEnvironmentTypeContext<size_t>([](FixtureBasedTestEnvironment& env) { |
| return env.serial_device_.writes().size(); |
| }) == 2u; |
| }); |
| |
| const std::vector<std::vector<uint8_t>> packets = |
| RunInEnvironmentTypeContext<const std::vector<std::vector<uint8_t>>>( |
| [](FixtureBasedTestEnvironment& env) { return env.serial_device_.writes(); }); |
| EXPECT_EQ(packets[0], kUartCmd0); |
| EXPECT_EQ(packets[1], kUartCmd1); |
| } |
| |
| TEST_F(BtTransportUartHciProtocolTest, ReceiveManyHciEventsSplitIntoTwoResponses) { |
| const std::vector<uint8_t> kSnoopEventBuffer = { |
| BT_HCI_SNOOP_TYPE_EVT | BT_HCI_SNOOP_FLAG_RECV, // Snoop packet flag |
| 0x01, // event code |
| 0x02, // parameter_total_size |
| 0x03, // arbitrary parameter |
| 0x04 // arbitrary parameter |
| }; |
| const std::vector<uint8_t> kEventBuffer(kSnoopEventBuffer.begin() + 1, kSnoopEventBuffer.end()); |
| std::vector<uint8_t> kSerialEventBuffer = kSnoopEventBuffer; |
| kSerialEventBuffer[0] = BtHciPacketIndicator::kHciEvent; |
| const std::vector<uint8_t> kPart1(kSerialEventBuffer.begin(), kSerialEventBuffer.begin() + 3); |
| const std::vector<uint8_t> kPart2(kSerialEventBuffer.begin() + 3, kSerialEventBuffer.end()); |
| |
| const size_t kNumEvents = 20; |
| for (size_t i = 0; i < kNumEvents; i++) { |
| RunInEnvironmentTypeContext([&](FixtureBasedTestEnvironment& env) { |
| return env.serial_device_.QueueReadValue(kPart1); |
| }); |
| RunInEnvironmentTypeContext([&](FixtureBasedTestEnvironment& env) { |
| return env.serial_device_.QueueReadValue(kPart2); |
| }); |
| } |
| |
| // Wait for all the packets to be received. |
| runtime().RunUntil([&]() { return hci_events().size() == kNumEvents; }); |
| |
| ASSERT_EQ(hci_events().size(), kNumEvents); |
| for (const std::vector<uint8_t>& event : hci_events()) { |
| EXPECT_EQ(event, kEventBuffer); |
| } |
| |
| runtime().RunUntil([&]() { return snoop_packets().size() == kNumEvents; }); |
| for (const std::vector<uint8_t>& packet : snoop_packets()) { |
| EXPECT_EQ(packet, kSnoopEventBuffer); |
| } |
| } |
| |
| TEST_F(BtTransportUartHciProtocolTest, SendScoPackets) { |
| const size_t kNumPackets = 25; |
| for (size_t i = 0; i < kNumPackets; i++) { |
| const std::vector<uint8_t> kScoPacket = {static_cast<uint8_t>(i)}; |
| zx_status_t write_status = |
| sco_chan()->write(/*flags=*/0, kScoPacket.data(), static_cast<uint32_t>(kScoPacket.size()), |
| /*handles=*/nullptr, |
| /*num_handles=*/0); |
| ASSERT_EQ(write_status, ZX_OK); |
| } |
| // Allow SCO packets to be processed and sent to the serial device. |
| runtime().RunUntil([&]() { |
| return RunInEnvironmentTypeContext<size_t>([](FixtureBasedTestEnvironment& env) { |
| return env.serial_device_.writes().size(); |
| }) == kNumPackets; |
| }); |
| |
| const std::vector<std::vector<uint8_t>> packets = |
| RunInEnvironmentTypeContext<const std::vector<std::vector<uint8_t>>>( |
| [](FixtureBasedTestEnvironment& env) { return env.serial_device_.writes(); }); |
| for (uint8_t i = 0; i < kNumPackets; i++) { |
| // A packet indicator should be prepended. |
| std::vector<uint8_t> expected = {BtHciPacketIndicator::kHciSco, i}; |
| EXPECT_EQ(packets[i], expected); |
| } |
| |
| runtime().RunUntil([&]() { return snoop_packets().size() == kNumPackets; }); |
| for (uint8_t i = 0; i < kNumPackets; i++) { |
| // Snoop packets should have a snoop packet flag prepended (NOT a UART packet indicator). |
| const std::vector<uint8_t> kExpectedSnoopPacket = {BT_HCI_SNOOP_TYPE_SCO, i}; |
| EXPECT_EQ(snoop_packets()[i], kExpectedSnoopPacket); |
| } |
| } |
| |
| TEST_F(BtTransportUartHciProtocolTest, ScoReadableSignalIgnoredUntilFirstWriteCompletes) { |
| // Delay completion of first write. |
| RunInEnvironmentTypeContext( |
| [](FixtureBasedTestEnvironment& env) { return env.serial_device_.set_writes_paused(true); }); |
| |
| const uint8_t kNumPackets = 2; |
| for (uint8_t i = 0; i < kNumPackets; i++) { |
| const std::vector<uint8_t> kScoPacket = {i}; |
| zx_status_t write_status = |
| sco_chan()->write(/*flags=*/0, kScoPacket.data(), static_cast<uint32_t>(kScoPacket.size()), |
| /*handles=*/nullptr, |
| /*num_handles=*/0); |
| ASSERT_EQ(write_status, ZX_OK); |
| } |
| |
| // Wait for the first packet to be received. |
| runtime().RunUntil([&]() { |
| return RunInEnvironmentTypeContext<size_t>([](FixtureBasedTestEnvironment& env) { |
| return env.serial_device_.writes().size(); |
| }) == 1u; |
| }); |
| |
| // Call the first packet's completion callback. This should resume waiting for signals. |
| RunInEnvironmentTypeContext( |
| [](FixtureBasedTestEnvironment& env) { return env.serial_device_.set_writes_paused(false); }); |
| |
| // Wait for the readable signal to be processed and the second packet to be received. |
| runtime().RunUntil([&]() { |
| return RunInEnvironmentTypeContext<size_t>([](FixtureBasedTestEnvironment& env) { |
| return env.serial_device_.writes().size(); |
| }) == kNumPackets; |
| }); |
| |
| const std::vector<std::vector<uint8_t>> packets = |
| RunInEnvironmentTypeContext<const std::vector<std::vector<uint8_t>>>( |
| [](FixtureBasedTestEnvironment& env) { return env.serial_device_.writes(); }); |
| ASSERT_EQ(packets.size(), kNumPackets); |
| for (uint8_t i = 0; i < kNumPackets; i++) { |
| // A packet indicator should be prepended. |
| std::vector<uint8_t> expected = {BtHciPacketIndicator::kHciSco, i}; |
| EXPECT_EQ(packets[i], expected); |
| } |
| } |
| |
| TEST_F(BtTransportUartHciProtocolTest, ReceiveScoPacketsIn2Parts) { |
| const std::vector<uint8_t> kSnoopScoBuffer = { |
| BT_HCI_SNOOP_TYPE_SCO | BT_HCI_SNOOP_FLAG_RECV, // Snoop packet flag |
| 0x07, |
| 0x08, // arbitrary header fields |
| 0x01, // 1-byte payload length in little endian |
| 0x02, // arbitrary payload |
| }; |
| std::vector<uint8_t> kSerialScoBuffer = kSnoopScoBuffer; |
| kSerialScoBuffer[0] = BtHciPacketIndicator::kHciSco; |
| const std::vector<uint8_t> kScoBuffer(kSnoopScoBuffer.begin() + 1, kSnoopScoBuffer.end()); |
| // Split the packet before length field to test corner case. |
| const std::vector<uint8_t> kPart1(kSerialScoBuffer.begin(), kSerialScoBuffer.begin() + 3); |
| const std::vector<uint8_t> kPart2(kSerialScoBuffer.begin() + 3, kSerialScoBuffer.end()); |
| |
| const size_t kNumPackets = 20; |
| for (size_t i = 0; i < kNumPackets; i++) { |
| RunInEnvironmentTypeContext([&](FixtureBasedTestEnvironment& env) { |
| return env.serial_device_.QueueReadValue(kPart1); |
| }); |
| RunInEnvironmentTypeContext([&](FixtureBasedTestEnvironment& env) { |
| return env.serial_device_.QueueReadValue(kPart2); |
| }); |
| } |
| |
| // Wait for all the packets to be received. |
| runtime().RunUntil( |
| [&]() { return received_sco_packets().size() == static_cast<size_t>(kNumPackets); }); |
| |
| for (const std::vector<uint8_t>& packet : received_sco_packets()) { |
| EXPECT_EQ(packet.size(), kScoBuffer.size()); |
| EXPECT_EQ(packet, kScoBuffer); |
| } |
| |
| runtime().RunUntil([&]() { return snoop_packets().size() == kNumPackets; }); |
| for (const std::vector<uint8_t>& packet : snoop_packets()) { |
| EXPECT_EQ(packet, kSnoopScoBuffer); |
| } |
| } |
| |
| } // namespace |
| } // namespace bt_transport_uart |