blob: 80a267e055a59407a3b7ae3f6dd286ce0c3d81cb [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 "fake_controller_base.h"
#include <lib/async/default.h>
#include <zircon/status.h>
#include <zircon/device/bt-hci.h>
#include "garnet/drivers/bluetooth/lib/common/log.h"
#include "garnet/drivers/bluetooth/lib/hci/acl_data_packet.h"
#include "garnet/drivers/bluetooth/lib/hci/hci_constants.h"
namespace btlib {
namespace testing {
FakeControllerBase::FakeControllerBase() {}
FakeControllerBase::~FakeControllerBase() {
// When this destructor gets called any subclass state will be undefined. If
// Stop() has not been called before reaching this point this can cause
// runtime errors when our event loop handlers attempt to invoke the pure
// virtual methods of this class.
}
bool FakeControllerBase::StartCmdChannel(zx::channel chan) {
if (cmd_channel_.is_valid()) {
return false;
}
cmd_channel_ = std::move(chan);
cmd_channel_wait_.set_object(cmd_channel_.get());
cmd_channel_wait_.set_trigger(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED);
zx_status_t status = cmd_channel_wait_.Begin(async_get_default_dispatcher());
if (status != ZX_OK) {
cmd_channel_.reset();
bt_log(WARN, "fake-hci", "failed to start command channel: %s",
zx_status_get_string(status));
return false;
}
return true;
}
bool FakeControllerBase::StartAclChannel(zx::channel chan) {
if (acl_channel_.is_valid()) {
return false;
}
acl_channel_ = std::move(chan);
acl_channel_wait_.set_object(acl_channel_.get());
acl_channel_wait_.set_trigger(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED);
zx_status_t status = acl_channel_wait_.Begin(async_get_default_dispatcher());
if (status != ZX_OK) {
acl_channel_.reset();
bt_log(WARN, "fake-hci", "failed to start ACL channel: %s",
zx_status_get_string(status));
return false;
}
return true;
}
bool FakeControllerBase::StartSnoopChannel(zx::channel chan) {
if (snoop_channel_.is_valid()) {
return false;
}
snoop_channel_ = std::move(chan);
return true;
}
void FakeControllerBase::Stop() {
CloseCommandChannel();
CloseACLDataChannel();
}
zx_status_t FakeControllerBase::SendCommandChannelPacket(
const common::ByteBuffer& packet) {
zx_status_t status =
cmd_channel_.write(0, packet.data(), packet.size(), nullptr, 0);
if (status != ZX_OK) {
bt_log(WARN, "fake-hci", "failed to write to control channel: %s",
zx_status_get_string(status));
return status;
}
SendSnoopChannelPacket(packet, BT_HCI_SNOOP_TYPE_EVT, true);
return ZX_OK;
}
zx_status_t FakeControllerBase::SendACLDataChannelPacket(
const common::ByteBuffer& packet) {
zx_status_t status =
acl_channel_.write(0, packet.data(), packet.size(), nullptr, 0);
if (status != ZX_OK) {
bt_log(WARN, "fake-hci", "failed to write to ACL data channel: %s",
zx_status_get_string(status));
return status;
}
SendSnoopChannelPacket(packet, BT_HCI_SNOOP_TYPE_ACL, true);
return ZX_OK;
}
void FakeControllerBase::SendSnoopChannelPacket(
const common::ByteBuffer& packet,
bt_hci_snoop_type_t packet_type,
bool is_received) {
if (snoop_channel_.is_valid()) {
uint8_t snoop_buffer[packet.size() + 1];
uint8_t flags = bt_hci_snoop_flags(packet_type, is_received);
snoop_buffer[0] = flags;
memcpy(snoop_buffer + 1, packet.data(), packet.size());
zx_status_t status =
snoop_channel_.write(0, snoop_buffer, packet.size() + 1, nullptr, 0);
if (status != ZX_OK) {
bt_log(WARN, "fake-hci", "cleaning up snoop channel after failed write: %s",
zx_status_get_string(status));
CloseSnoopChannel();
}
}
}
void FakeControllerBase::CloseCommandChannel() {
if (cmd_channel_.is_valid()) {
cmd_channel_wait_.Cancel();
cmd_channel_wait_.set_object(ZX_HANDLE_INVALID);
cmd_channel_.reset();
}
}
void FakeControllerBase::CloseACLDataChannel() {
if (acl_channel_.is_valid()) {
acl_channel_wait_.Cancel();
acl_channel_wait_.set_object(ZX_HANDLE_INVALID);
acl_channel_.reset();
}
}
void FakeControllerBase::CloseSnoopChannel() {
if (snoop_channel_.is_valid()) {
snoop_channel_.reset();
}
}
void FakeControllerBase::HandleCommandPacket(
async_dispatcher_t* dispatcher,
async::WaitBase* wait,
zx_status_t wait_status,
const zx_packet_signal_t* signal) {
common::StaticByteBuffer<hci::kMaxCommandPacketPayloadSize> buffer;
uint32_t read_size;
zx_status_t status = cmd_channel_.read(0u, buffer.mutable_data(),
hci::kMaxCommandPacketPayloadSize,
&read_size, nullptr, 0, nullptr);
ZX_DEBUG_ASSERT(status == ZX_OK || status == ZX_ERR_PEER_CLOSED);
if (status < 0) {
if (status == ZX_ERR_PEER_CLOSED) {
bt_log(INFO, "fake-hci", "command channel was closed");
} else {
bt_log(ERROR, "fake-hci", "failed to read on cmd channel: %s",
zx_status_get_string(status));
}
CloseCommandChannel();
return;
}
if (read_size < sizeof(hci::CommandHeader)) {
bt_log(ERROR, "fake-hci", "malformed command packet received");
} else {
common::MutableBufferView view(buffer.mutable_data(), read_size);
common::PacketView<hci::CommandHeader> packet(
&view, read_size - sizeof(hci::CommandHeader));
SendSnoopChannelPacket(packet.data(), BT_HCI_SNOOP_TYPE_CMD, false);
OnCommandPacketReceived(packet);
}
status = wait->Begin(dispatcher);
if (status != ZX_OK) {
bt_log(ERROR, "fake-hci", "failed to wait on cmd channel: %s",
zx_status_get_string(status));
CloseCommandChannel();
}
}
void FakeControllerBase::HandleACLPacket(
async_dispatcher_t* dispatcher,
async::WaitBase* wait,
zx_status_t wait_status,
const zx_packet_signal_t* signal) {
common::StaticByteBuffer<hci::kMaxACLPayloadSize + sizeof(hci::ACLDataHeader)>
buffer;
uint32_t read_size;
zx_status_t status =
acl_channel_.read(0u, buffer.mutable_data(), buffer.size(), &read_size,
nullptr, 0, nullptr);
ZX_DEBUG_ASSERT(status == ZX_OK || status == ZX_ERR_PEER_CLOSED);
if (status < 0) {
if (status == ZX_ERR_PEER_CLOSED) {
bt_log(INFO, "fake-hci", "ACL channel was closed");
} else {
bt_log(ERROR, "fake-hci", "failed to read on ACL channel: %s",
zx_status_get_string(status));
}
CloseACLDataChannel();
return;
}
common::BufferView view(buffer.data(), read_size);
SendSnoopChannelPacket(view, BT_HCI_SNOOP_TYPE_ACL, false);
OnACLDataPacketReceived(view);
status = wait->Begin(dispatcher);
if (status != ZX_OK) {
bt_log(ERROR, "fake-hci", "failed to wait on ACL channel: %s",
zx_status_get_string(status));
CloseACLDataChannel();
}
}
} // namespace testing
} // namespace btlib