blob: d539db83c827c0b996a239dc83e4e26555f9c4c9 [file] [log] [blame]
// 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