blob: a6ec689eab08d9b7a3d95dcc079129212cfa08b5 [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/device/bt-hci.h>
#include <zircon/status.h>
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/acl_data_packet.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/hci_constants.h"
namespace bt {
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();
CloseSnoopChannel();
}
zx_status_t FakeControllerBase::SendCommandChannelPacket(
const 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 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 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) {
StaticByteBuffer<hci::kMaxCommandPacketPayloadSize> buffer;
uint32_t read_size;
zx_status_t status = cmd_channel_.read(0u, buffer.mutable_data(), nullptr,
hci::kMaxCommandPacketPayloadSize, 0,
&read_size, 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 {
MutableBufferView view(buffer.mutable_data(), read_size);
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) {
StaticByteBuffer<hci::kMaxACLPayloadSize + sizeof(hci::ACLDataHeader)> buffer;
uint32_t read_size;
zx_status_t status = acl_channel_.read(0u, buffer.mutable_data(), nullptr,
buffer.size(), 0, &read_size, 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;
}
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 bt