| // 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 "test_controller.h" |
| |
| #include <lib/async/cpp/task.h> |
| #include <zircon/status.h> |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h" |
| |
| namespace bt { |
| namespace testing { |
| |
| Transaction::Transaction(const ByteBuffer& expected, const std::vector<const ByteBuffer*>& replies) |
| : expected_({DynamicByteBuffer(expected)}) { |
| for (const auto* buffer : replies) { |
| replies_.push(DynamicByteBuffer(*buffer)); |
| } |
| } |
| |
| bool Transaction::Match(const ByteBuffer& packet) { |
| return ContainersEqual(expected_.data, packet); |
| } |
| |
| CommandTransaction::CommandTransaction(hci::OpCode expected_opcode, |
| const std::vector<const ByteBuffer*>& replies) |
| : Transaction({DynamicByteBuffer()}, replies), prefix_(true) { |
| hci::OpCode le_opcode = htole16(expected_opcode); |
| const BufferView expected(&le_opcode, sizeof(expected_opcode)); |
| set_expected({DynamicByteBuffer(expected)}); |
| } |
| |
| bool CommandTransaction::Match(const ByteBuffer& cmd) { |
| return ContainersEqual(expected().data, |
| (prefix_ ? cmd.view(0, expected().data.size()) : cmd.view())); |
| } |
| |
| TestController::TestController() |
| : FakeControllerBase(), |
| data_expectations_enabled_(false), |
| data_dispatcher_(nullptr), |
| transaction_dispatcher_(nullptr) {} |
| |
| TestController::~TestController() { |
| while (!cmd_transactions_.empty()) { |
| auto& transaction = cmd_transactions_.front(); |
| // TODO(46656): add file & line number to failure |
| ADD_FAILURE() << "Didn't receive expected outbound command packet {" |
| << ByteContainerToString(transaction.expected().data) << "}"; |
| cmd_transactions_.pop(); |
| } |
| |
| while (!data_transactions_.empty()) { |
| auto& transaction = data_transactions_.front(); |
| // TODO(46656): add file & line number to failure |
| ADD_FAILURE() << "Didn't receive expected outbound data packet {" |
| << ByteContainerToString(transaction.expected().data) << "}"; |
| data_transactions_.pop(); |
| } |
| Stop(); |
| } |
| |
| void TestController::QueueCommandTransaction(CommandTransaction transaction) { |
| cmd_transactions_.push(std::move(transaction)); |
| } |
| |
| void TestController::QueueCommandTransaction(const ByteBuffer& expected, |
| const std::vector<const ByteBuffer*>& replies) { |
| QueueCommandTransaction(CommandTransaction({DynamicByteBuffer(expected)}, replies)); |
| } |
| |
| void TestController::QueueDataTransaction(DataTransaction transaction) { |
| ZX_ASSERT(data_expectations_enabled_); |
| data_transactions_.push(std::move(transaction)); |
| } |
| |
| void TestController::QueueDataTransaction(const ByteBuffer& expected, |
| const std::vector<const ByteBuffer*>& replies) { |
| QueueDataTransaction(DataTransaction({DynamicByteBuffer(expected)}, replies)); |
| } |
| |
| bool TestController::AllExpectedDataPacketsSent() const { return data_transactions_.empty(); } |
| |
| void TestController::SetDataCallback(DataCallback callback, async_dispatcher_t* dispatcher) { |
| ZX_DEBUG_ASSERT(callback); |
| ZX_DEBUG_ASSERT(dispatcher); |
| ZX_DEBUG_ASSERT(!data_callback_); |
| ZX_DEBUG_ASSERT(!data_dispatcher_); |
| |
| data_callback_ = std::move(callback); |
| data_dispatcher_ = dispatcher; |
| } |
| |
| void TestController::ClearDataCallback() { |
| // Leave dispatcher set (if already set) to preserve its write-once-ness. |
| data_callback_ = nullptr; |
| } |
| |
| void TestController::SetTransactionCallback(fit::closure callback, async_dispatcher_t* dispatcher) { |
| SetTransactionCallback([f = std::move(callback)](const auto&) { f(); }, dispatcher); |
| } |
| |
| void TestController::SetTransactionCallback(TransactionCallback callback, |
| async_dispatcher_t* dispatcher) { |
| ZX_DEBUG_ASSERT(callback); |
| ZX_DEBUG_ASSERT(dispatcher); |
| ZX_DEBUG_ASSERT(!transaction_callback_); |
| ZX_DEBUG_ASSERT(!transaction_dispatcher_); |
| |
| transaction_callback_ = std::move(callback); |
| transaction_dispatcher_ = dispatcher; |
| } |
| |
| void TestController::ClearTransactionCallback() { |
| // Leave dispatcher set (if already set) to preserve its write-once-ness. |
| transaction_callback_ = nullptr; |
| } |
| |
| void TestController::OnCommandPacketReceived(const PacketView<hci::CommandHeader>& command_packet) { |
| uint16_t opcode = command_packet.header().opcode; |
| uint8_t ogf = hci::GetOGF(opcode); |
| uint16_t ocf = hci::GetOCF(opcode); |
| |
| // Note: we upcast ogf to uint16_t so that it does not get interpreted as a |
| // char for printing |
| ASSERT_FALSE(cmd_transactions_.empty()) |
| << "Received unexpected command packet with OGF: 0x" << std::hex << static_cast<uint16_t>(ogf) |
| << ", OCF: 0x" << std::hex << ocf; |
| |
| auto& current = cmd_transactions_.front(); |
| ASSERT_TRUE(current.Match(command_packet.data())); |
| |
| while (!current.replies().empty()) { |
| auto& reply = current.replies().front(); |
| auto status = SendCommandChannelPacket(reply); |
| ASSERT_EQ(ZX_OK, status) << "Failed to send reply: " << zx_status_get_string(status); |
| current.replies().pop(); |
| } |
| cmd_transactions_.pop(); |
| |
| if (transaction_callback_) { |
| DynamicByteBuffer rx(command_packet.data()); |
| async::PostTask(transaction_dispatcher_, |
| [rx = std::move(rx), f = transaction_callback_.share()] { f(rx); }); |
| } |
| } |
| |
| void TestController::OnACLDataPacketReceived(const ByteBuffer& acl_data_packet) { |
| if (data_expectations_enabled_) { |
| ASSERT_FALSE(data_transactions_.empty()) << "Received unexpected acl data packet: { " |
| << ByteContainerToString(acl_data_packet) << "}"; |
| |
| auto& current = data_transactions_.front(); |
| ASSERT_TRUE(current.Match(acl_data_packet.view())); |
| |
| while (!current.replies().empty()) { |
| auto& reply = current.replies().front(); |
| auto status = SendACLDataChannelPacket(reply); |
| ASSERT_EQ(ZX_OK, status) << "Failed to send reply: " << zx_status_get_string(status); |
| current.replies().pop(); |
| } |
| data_transactions_.pop(); |
| } |
| |
| if (data_callback_) { |
| DynamicByteBuffer packet_copy(acl_data_packet); |
| async::PostTask(data_dispatcher_, [packet_copy = std::move(packet_copy), |
| cb = data_callback_.share()]() mutable { cb(packet_copy); }); |
| } |
| } |
| |
| } // namespace testing |
| } // namespace bt |