| // Copyright 2023 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. |
| |
| #ifndef SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_FIDL_FAKE_HCI_SERVER_H_ |
| #define SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_FIDL_FAKE_HCI_SERVER_H_ |
| |
| #include <fuchsia/hardware/bluetooth/cpp/fidl_test_base.h> |
| #include <lib/async/cpp/wait.h> |
| #include <lib/async/dispatcher.h> |
| #include <lib/zx/channel.h> |
| |
| #include <string> |
| |
| #include "fuchsia/hardware/bluetooth/cpp/fidl.h" |
| #include "gmock/gmock.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/byte_buffer.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/iso/iso_common.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/transport/slab_allocators.h" |
| |
| namespace bt::fidl::testing { |
| |
| namespace fhbt = fuchsia::hardware::bluetooth; |
| |
| class FakeHciServer final : public fhbt::testing::Hci_TestBase { |
| public: |
| FakeHciServer(::fidl::InterfaceRequest<fhbt::Hci> request, async_dispatcher_t* dispatcher) |
| : dispatcher_(dispatcher) { |
| binding_.Bind(std::move(request)); |
| } |
| |
| void Unbind() { binding_.Unbind(); } |
| |
| zx_status_t SendEvent(const BufferView& event) { |
| return command_channel_.write(/*flags=*/0, event.data(), static_cast<uint32_t>(event.size()), |
| /*handles=*/nullptr, /*num_handles=*/0); |
| } |
| zx_status_t SendAcl(const BufferView& buffer) { |
| return acl_channel_.write(/*flags=*/0, buffer.data(), static_cast<uint32_t>(buffer.size()), |
| /*handles=*/nullptr, /*num_handles=*/0); |
| } |
| zx_status_t SendSco(const BufferView& buffer) { |
| return sco_channel_.write(/*flags=*/0, buffer.data(), static_cast<uint32_t>(buffer.size()), |
| /*handles=*/nullptr, /*num_handles=*/0); |
| } |
| zx_status_t SendIso(const BufferView& buffer) { |
| return iso_channel_.write(/*flags=*/0, buffer.data(), static_cast<uint32_t>(buffer.size()), |
| /*handles=*/nullptr, /*num_handles=*/0); |
| } |
| |
| const std::vector<bt::DynamicByteBuffer>& commands_received() const { return commands_received_; } |
| const std::vector<bt::DynamicByteBuffer>& acl_packets_received() const { |
| return acl_packets_received_; |
| } |
| const std::vector<bt::DynamicByteBuffer>& sco_packets_received() const { |
| return sco_packets_received_; |
| } |
| const std::vector<bt::DynamicByteBuffer>& iso_packets_received() const { |
| return iso_packets_received_; |
| } |
| |
| bool CloseCommandChannel() { |
| bool was_valid = command_channel_valid(); |
| command_channel_.reset(); |
| return was_valid; |
| } |
| |
| bool CloseAclChannel() { |
| bool was_valid = acl_channel_.is_valid(); |
| acl_channel_.reset(); |
| return was_valid; |
| } |
| |
| // Use custom |ConfigureScoTestCallback| to manually verify configuration fields from tests |
| using ConfigureScoTestCallback = |
| fit::function<void(fhbt::ScoCodingFormat, fhbt::ScoEncoding, fhbt::ScoSampleRate)>; |
| void set_check_configure_sco(ConfigureScoTestCallback callback) { |
| check_configure_sco_ = std::move(callback); |
| } |
| |
| // Uee custom |ResetScoTestCallback| to manually perform reset actions from tests |
| using ResetScoTestCallback = fit::function<void()>; |
| void set_reset_sco_callback(ResetScoTestCallback callback) { |
| reset_sco_cb_ = std::move(callback); |
| } |
| |
| bool acl_channel_valid() const { return acl_channel_.is_valid(); } |
| bool command_channel_valid() const { return command_channel_.is_valid(); } |
| bool sco_channel_valid() const { return sco_channel_.is_valid(); } |
| bool iso_channel_valid() const { return iso_channel_.is_valid(); } |
| |
| private: |
| void OpenCommandChannel(zx::channel channel, OpenCommandChannelCallback callback) override { |
| command_channel_ = std::move(channel); |
| InitializeWait(command_wait_, command_channel_); |
| callback(fpromise::ok()); |
| } |
| |
| void OpenAclDataChannel(zx::channel channel, OpenAclDataChannelCallback callback) override { |
| acl_channel_ = std::move(channel); |
| InitializeWait(acl_wait_, acl_channel_); |
| callback(fpromise::ok()); |
| } |
| |
| void OpenScoDataChannel(zx::channel channel, OpenScoDataChannelCallback callback) override { |
| sco_channel_ = std::move(channel); |
| InitializeWait(sco_wait_, sco_channel_); |
| callback(fpromise::ok()); |
| } |
| |
| void OpenIsoDataChannel(zx::channel channel, OpenIsoDataChannelCallback callback) override { |
| iso_channel_ = std::move(channel); |
| InitializeWait(iso_wait_, iso_channel_); |
| callback(fpromise::ok()); |
| } |
| |
| void ConfigureSco(fhbt::ScoCodingFormat coding_format, fhbt::ScoEncoding encoding, |
| fhbt::ScoSampleRate sample_rate, ConfigureScoCallback callback) override { |
| if (check_configure_sco_) { |
| check_configure_sco_(coding_format, encoding, sample_rate); |
| } |
| callback(fpromise::ok()); |
| } |
| |
| void ResetSco(ResetScoCallback callback) override { |
| if (reset_sco_cb_) { |
| reset_sco_cb_(); |
| } |
| callback(fpromise::ok()); |
| } |
| |
| void NotImplemented_(const std::string& name) override { FAIL() << name << " not implemented"; } |
| |
| void InitializeWait(async::WaitBase& wait, zx::channel& channel) { |
| BT_ASSERT(channel.is_valid()); |
| wait.Cancel(); |
| wait.set_object(channel.get()); |
| wait.set_trigger(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED); |
| BT_ASSERT(wait.Begin(dispatcher_) == ZX_OK); |
| } |
| |
| void OnAclSignal(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status, |
| const zx_packet_signal_t* signal) { |
| ASSERT_TRUE(status == ZX_OK); |
| if (signal->observed & ZX_CHANNEL_PEER_CLOSED) { |
| acl_channel_.reset(); |
| return; |
| } |
| ASSERT_TRUE(signal->observed & ZX_CHANNEL_READABLE); |
| |
| bt::StaticByteBuffer<hci::allocators::kLargeACLDataPacketSize> buffer; |
| uint32_t read_size = 0; |
| zx_status_t read_status = acl_channel_.read(0u, buffer.mutable_data(), /*handles=*/nullptr, |
| static_cast<uint32_t>(buffer.size()), 0, &read_size, |
| /*actual_handles=*/nullptr); |
| ASSERT_TRUE(read_status == ZX_OK); |
| acl_packets_received_.emplace_back(bt::BufferView(buffer, read_size)); |
| acl_wait_.Begin(dispatcher_); |
| } |
| |
| void OnCommandSignal(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status, |
| const zx_packet_signal_t* signal) { |
| ASSERT_TRUE(status == ZX_OK); |
| if (signal->observed & ZX_CHANNEL_PEER_CLOSED) { |
| command_channel_.reset(); |
| return; |
| } |
| ASSERT_TRUE(signal->observed & ZX_CHANNEL_READABLE); |
| |
| bt::StaticByteBuffer<hci::allocators::kLargeControlPacketSize> buffer; |
| uint32_t read_size = 0; |
| zx_status_t read_status = |
| command_channel_.read(0u, buffer.mutable_data(), /*handles=*/nullptr, |
| static_cast<uint32_t>(buffer.size()), 0, &read_size, |
| /*actual_handles=*/nullptr); |
| ASSERT_TRUE(read_status == ZX_OK); |
| commands_received_.emplace_back(bt::BufferView(buffer, read_size)); |
| command_wait_.Begin(dispatcher_); |
| } |
| |
| void OnScoSignal(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status, |
| const zx_packet_signal_t* signal) { |
| ASSERT_TRUE(status == ZX_OK); |
| if (signal->observed & ZX_CHANNEL_PEER_CLOSED) { |
| sco_channel_.reset(); |
| return; |
| } |
| ASSERT_TRUE(signal->observed & ZX_CHANNEL_READABLE); |
| |
| bt::StaticByteBuffer<hci::allocators::kMaxScoDataPacketSize> buffer; |
| uint32_t read_size = 0; |
| zx_status_t read_status = sco_channel_.read(0u, buffer.mutable_data(), /*handles=*/nullptr, |
| static_cast<uint32_t>(buffer.size()), 0, &read_size, |
| /*actual_handles=*/nullptr); |
| ASSERT_TRUE(read_status == ZX_OK); |
| sco_packets_received_.emplace_back(bt::BufferView(buffer, read_size)); |
| sco_wait_.Begin(dispatcher_); |
| } |
| |
| void OnIsoSignal(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status, |
| const zx_packet_signal_t* signal) { |
| ASSERT_TRUE(status == ZX_OK); |
| if (signal->observed & ZX_CHANNEL_PEER_CLOSED) { |
| iso_channel_.reset(); |
| return; |
| } |
| ASSERT_TRUE(signal->observed & ZX_CHANNEL_READABLE); |
| |
| bt::StaticByteBuffer<iso::kMaxIsochronousDataPacketSize> buffer; |
| uint32_t read_size = 0; |
| zx_status_t read_status = iso_channel_.read(0u, buffer.mutable_data(), /*handles=*/nullptr, |
| static_cast<uint32_t>(buffer.size()), 0, &read_size, |
| /*actual_handles=*/nullptr); |
| ASSERT_TRUE(read_status == ZX_OK); |
| iso_packets_received_.emplace_back(bt::BufferView(buffer, read_size)); |
| iso_wait_.Begin(dispatcher_); |
| } |
| |
| ::fidl::Binding<fhbt::Hci> binding_{this}; |
| |
| zx::channel command_channel_; |
| std::vector<bt::DynamicByteBuffer> commands_received_; |
| |
| zx::channel acl_channel_; |
| std::vector<bt::DynamicByteBuffer> acl_packets_received_; |
| |
| zx::channel sco_channel_; |
| std::vector<bt::DynamicByteBuffer> sco_packets_received_; |
| ConfigureScoTestCallback check_configure_sco_; |
| ResetScoTestCallback reset_sco_cb_; |
| |
| zx::channel iso_channel_; |
| std::vector<bt::DynamicByteBuffer> iso_packets_received_; |
| |
| async::WaitMethod<FakeHciServer, &FakeHciServer::OnAclSignal> acl_wait_{this}; |
| async::WaitMethod<FakeHciServer, &FakeHciServer::OnCommandSignal> command_wait_{this}; |
| async::WaitMethod<FakeHciServer, &FakeHciServer::OnScoSignal> sco_wait_{this}; |
| async::WaitMethod<FakeHciServer, &FakeHciServer::OnIsoSignal> iso_wait_{this}; |
| |
| async_dispatcher_t* dispatcher_; |
| }; |
| |
| } // namespace bt::fidl::testing |
| |
| #endif // SRC_CONNECTIVITY_BLUETOOTH_CORE_BT_HOST_FIDL_FAKE_HCI_SERVER_H_ |