blob: ef0ea3f3effbad24e0a1fe8c15aa6e7d898fbb0c [file] [log] [blame]
// Copyright 2018 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 "device.h"
#include <lib/async/cpp/task.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <cstdio>
#include <future>
#include <thread>
#include <ddk/protocol/bt/hci.h>
#include "src/connectivity/bluetooth/core/bt-host/testing/fake_peer.h"
#include "src/connectivity/bluetooth/hci/emulator/log.h"
namespace fbt = fuchsia::bluetooth;
namespace ftest = fuchsia::bluetooth::test;
using bt::DeviceAddress;
using bt::testing::FakeController;
using bt::testing::FakePeer;
namespace bt_hci_emulator {
namespace {
FakeController::Settings SettingsFromFidl(const ftest::EmulatorSettings& input) {
FakeController::Settings settings;
if (input.has_hci_config() && input.hci_config() == ftest::HciConfig::LE_ONLY) {
settings.ApplyLEOnlyDefaults();
} else {
settings.ApplyDualModeDefaults();
}
if (input.has_address()) {
settings.bd_addr = DeviceAddress(DeviceAddress::Type::kBREDR, input.address().bytes);
}
// TODO(armansito): Don't ignore "extended_advertising" setting when
// supported.
if (input.has_acl_buffer_settings()) {
settings.acl_data_packet_length = input.acl_buffer_settings().data_packet_length;
settings.total_num_acl_data_packets = input.acl_buffer_settings().total_num_data_packets;
}
if (input.has_le_acl_buffer_settings()) {
settings.le_acl_data_packet_length = input.le_acl_buffer_settings().data_packet_length;
settings.le_total_num_acl_data_packets = input.le_acl_buffer_settings().total_num_data_packets;
}
return settings;
}
fuchsia::bluetooth::AddressType LeOwnAddressTypeToFidl(bt::hci::LEOwnAddressType type) {
switch (type) {
case bt::hci::LEOwnAddressType::kPublic:
case bt::hci::LEOwnAddressType::kPrivateDefaultToPublic:
return fuchsia::bluetooth::AddressType::PUBLIC;
case bt::hci::LEOwnAddressType::kRandom:
case bt::hci::LEOwnAddressType::kPrivateDefaultToRandom:
return fuchsia::bluetooth::AddressType::RANDOM;
}
ZX_PANIC("unsupported own address type");
return fuchsia::bluetooth::AddressType::PUBLIC;
}
} // namespace
Device::Device(zx_device_t* device)
: loop_(&kAsyncLoopConfigNoAttachToCurrentThread),
parent_(device),
hci_dev_(nullptr),
emulator_dev_(nullptr),
binding_(this) {}
#define DEV(c) static_cast<Device*>(c)
static zx_protocol_device_t bt_emulator_device_ops = {
.version = DEVICE_OPS_VERSION,
.get_protocol = [](void* ctx, uint32_t proto_id, void* out_proto) -> zx_status_t {
return DEV(ctx)->GetProtocol(proto_id, out_proto);
},
.unbind = [](void* ctx) { DEV(ctx)->Unbind(); },
.release = [](void* ctx) { DEV(ctx)->Release(); },
.message = [](void* ctx, fidl_incoming_msg_t* msg,
fidl_txn_t* txn) { return DEV(ctx)->EmulatorMessage(msg, txn); }};
// NOTE: We do not implement unbind and release. The lifecycle of the bt-hci
// device is strictly tied to the bt-emulator device (i.e. it can never out-live
// bt-emulator). We handle its destruction in the bt_emulator_device_ops
// messages.
static zx_protocol_device_t bt_hci_device_ops = {
.version = DEVICE_OPS_VERSION,
.get_protocol = [](void* ctx, uint32_t proto_id, void* out_proto) -> zx_status_t {
return DEV(ctx)->GetProtocol(proto_id, out_proto);
},
.message = [](void* ctx, fidl_incoming_msg_t* msg,
fidl_txn_t* txn) { return DEV(ctx)->HciMessage(msg, txn); }};
static bt_hci_protocol_ops_t hci_protocol_ops = {
.open_command_channel = [](void* ctx, zx_handle_t chan) -> zx_status_t {
return DEV(ctx)->OpenChan(Channel::COMMAND, chan);
},
.open_acl_data_channel = [](void* ctx, zx_handle_t chan) -> zx_status_t {
return DEV(ctx)->OpenChan(Channel::ACL, chan);
},
.open_snoop_channel = [](void* ctx, zx_handle_t chan) -> zx_status_t {
return DEV(ctx)->OpenChan(Channel::SNOOP, chan);
},
};
#undef DEV
zx_status_t Device::Bind() {
logf(TRACE, "bind\n");
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "bt_hci_emulator",
.ctx = this,
.ops = &bt_emulator_device_ops,
.proto_id = ZX_PROTOCOL_BT_EMULATOR,
.flags = DEVICE_ADD_NON_BINDABLE,
};
zx_status_t status = device_add(parent_, &args, &emulator_dev_);
if (status != ZX_OK) {
logf(ERROR, "could not add bt-emulator device: %s\n", zx_status_get_string(status));
return status;
}
fake_device_ = fbl::AdoptRef(new FakeController());
fake_device_->set_controller_parameters_callback(
fit::bind_member(this, &Device::OnControllerParametersChanged));
fake_device_->set_advertising_state_callback(
fit::bind_member(this, &Device::OnLegacyAdvertisingStateChanged));
fake_device_->set_connection_state_callback(
fit::bind_member(this, &Device::OnPeerConnectionStateChanged));
loop_.StartThread("bt_hci_emulator");
return status;
}
void Device::Release() {
logf(TRACE, "release\n");
delete this;
}
void Device::Unbind() {
logf(TRACE, "unbind\n");
// Clean up all FIDL channels and the underlying FakeController on the
// dispatcher thread, due to the FakeController object's thread-safety
// requirements. It is OK to capture references to members in the task since
// this function will block until the dispatcher loop has terminated.
async::PostTask(loop_.dispatcher(),
[binding = &binding_, dev = fake_device_, loop = &loop_, peers = &peers_] {
binding->Unbind();
dev->Stop();
loop->Quit();
// Clean up all fake peers. This will close their local channels and remove them
// from the fake controller.
peers->clear();
});
// Block here until all the shutdown tasks we just posted are completed on the FIDL/emulator
// dispatcher thread to guarantee that the operations below don't happen concurrently with them.
loop_.JoinThreads();
logf(TRACE, "emulator dispatcher shut down\n");
// Destroy the FakeController here. Since |loop_| has been shutdown, we
// don't expect it to be dereferenced again.
fake_device_ = nullptr;
UnpublishHci();
device_unbind_reply(emulator_dev_);
emulator_dev_ = nullptr;
}
zx_status_t Device::HciMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn) {
logf(TRACE, "HciMessage\n");
return fuchsia_hardware_bluetooth_Hci_dispatch(this, txn, msg, &hci_fidl_ops_);
}
zx_status_t Device::EmulatorMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn) {
logf(TRACE, "EmulatorMessage\n");
return fuchsia_hardware_bluetooth_Emulator_dispatch(this, txn, msg, &emul_fidl_ops_);
}
zx_status_t Device::GetProtocol(uint32_t proto_id, void* out_proto) {
// The bt-emulator device doesn't support a non-FIDL protocol.
if (proto_id != ZX_PROTOCOL_BT_HCI) {
return ZX_ERR_NOT_SUPPORTED;
}
bt_hci_protocol_t* hci_proto = static_cast<bt_hci_protocol_t*>(out_proto);
hci_proto->ctx = this;
hci_proto->ops = &hci_protocol_ops;
return ZX_OK;
}
zx_status_t Device::OpenChan(Channel chan_type, zx_handle_t in_h) {
logf(TRACE, "open HCI channel\n");
zx::channel in(in_h);
if (chan_type == Channel::COMMAND) {
async::PostTask(loop_.dispatcher(), [device = fake_device_, in = std::move(in)]() mutable {
device->StartCmdChannel(std::move(in));
});
} else if (chan_type == Channel::ACL) {
async::PostTask(loop_.dispatcher(), [device = fake_device_, in = std::move(in)]() mutable {
device->StartAclChannel(std::move(in));
});
} else if (chan_type == Channel::SNOOP) {
async::PostTask(loop_.dispatcher(), [device = fake_device_, in = std::move(in)]() mutable {
device->StartSnoopChannel(std::move(in));
});
} else if (chan_type == Channel::EMULATOR) {
async::PostTask(loop_.dispatcher(), [this, in = std::move(in)]() mutable {
StartEmulatorInterface(std::move(in));
});
} else {
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_OK;
}
void Device::StartEmulatorInterface(zx::channel chan) {
logf(TRACE, "start HciEmulator interface\n");
if (binding_.is_bound()) {
logf(TRACE, "HciEmulator channel already bound\n");
return;
}
// Process HciEmulator messages on a thread that can safely access the
// FakeController, which is thread-hostile.
binding_.Bind(std::move(chan), loop_.dispatcher());
binding_.set_error_handler([this](zx_status_t status) {
logf(TRACE, "emulator channel closed (status: %s); unpublish device\n",
zx_status_get_string(status));
UnpublishHci();
});
}
void Device::Publish(ftest::EmulatorSettings in_settings, PublishCallback callback) {
logf(TRACE, "HciEmulator.Publish\n");
ftest::HciEmulator_Publish_Result result;
if (hci_dev_) {
result.set_err(ftest::EmulatorError::HCI_ALREADY_PUBLISHED);
callback(std::move(result));
return;
}
FakeController::Settings settings = SettingsFromFidl(in_settings);
fake_device_->set_settings(settings);
// Publish the bt-hci device.
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "bt_hci_emulator",
.ctx = this,
.ops = &bt_hci_device_ops,
.proto_id = ZX_PROTOCOL_BT_HCI,
};
zx_status_t status = device_add(emulator_dev_, &args, &hci_dev_);
if (status != ZX_OK) {
result.set_err(ftest::EmulatorError::FAILED);
} else {
result.set_response(ftest::HciEmulator_Publish_Response{});
}
callback(std::move(result));
}
void Device::AddLowEnergyPeer(ftest::LowEnergyPeerParameters params,
fidl::InterfaceRequest<ftest::Peer> request,
AddLowEnergyPeerCallback callback) {
logf(TRACE, "HciEmulator.AddLowEnergyPeer\n");
ftest::HciEmulator_AddLowEnergyPeer_Result fidl_result;
auto result = Peer::NewLowEnergy(std::move(params), std::move(request), fake_device_);
if (result.is_error()) {
fidl_result.set_err(result.error());
callback(std::move(fidl_result));
return;
}
AddPeer(result.take_value());
fidl_result.set_response(ftest::HciEmulator_AddLowEnergyPeer_Response{});
callback(std::move(fidl_result));
}
void Device::AddBredrPeer(ftest::BredrPeerParameters params,
fidl::InterfaceRequest<fuchsia::bluetooth::test::Peer> request,
AddBredrPeerCallback callback) {
logf(TRACE, "HciEmulator.AddBredrPeer\n");
ftest::HciEmulator_AddBredrPeer_Result fidl_result;
auto result = Peer::NewBredr(std::move(params), std::move(request), fake_device_);
if (result.is_error()) {
fidl_result.set_err(result.error());
callback(std::move(fidl_result));
return;
}
AddPeer(result.take_value());
fidl_result.set_response(ftest::HciEmulator_AddBredrPeer_Response{});
callback(std::move(fidl_result));
}
void Device::WatchControllerParameters(WatchControllerParametersCallback callback) {
logf(TRACE, "HciEmulator.WatchControllerParameters\n");
controller_parameters_getter_.Watch(std::move(callback));
}
void Device::WatchLeScanStates(WatchLeScanStatesCallback callback) {
// TODO(fxbug.dev/822): Implement
}
void Device::WatchLegacyAdvertisingStates(WatchLegacyAdvertisingStatesCallback callback) {
logf(TRACE, "HciEmulator.WatchLegacyAdvertisingState\n");
legacy_adv_state_getter_.Watch(std::move(callback));
}
void Device::AddPeer(std::unique_ptr<Peer> peer) {
auto address = peer->address();
peer->set_closed_callback([this, address] { peers_.erase(address); });
peers_[address] = std::move(peer);
}
void Device::OnControllerParametersChanged() {
logf(TRACE, "HciEmulator.OnControllerParametersChanged\n");
ftest::ControllerParameters fidl_value;
fidl_value.set_local_name(fake_device_->local_name());
const auto& device_class_bytes = fake_device_->device_class().bytes();
uint32_t device_class = 0;
device_class |= device_class_bytes[0];
device_class |= static_cast<uint32_t>(device_class_bytes[1]) << 8;
device_class |= static_cast<uint32_t>(device_class_bytes[2]) << 16;
fidl_value.set_device_class(fbt::DeviceClass{device_class});
controller_parameters_getter_.Set(std::move(fidl_value));
}
void Device::OnLegacyAdvertisingStateChanged() {
logf(TRACE, "HciEmulator.OnLegacyAdvertisingStateChanged\n");
// We have requests to resolve. Construct the FIDL table for the current state.
ftest::LegacyAdvertisingState fidl_state;
FakeController::LEAdvertisingState adv_state = fake_device_->le_advertising_state();
fidl_state.set_enabled(adv_state.enabled);
// Populate the rest only if advertising is enabled.
fidl_state.set_type(static_cast<ftest::LegacyAdvertisingType>(adv_state.adv_type));
fidl_state.set_address_type(LeOwnAddressTypeToFidl(adv_state.own_address_type));
if (adv_state.interval_min) {
fidl_state.set_interval_min(adv_state.interval_min);
}
if (adv_state.interval_max) {
fidl_state.set_interval_max(adv_state.interval_max);
}
if (adv_state.data_length) {
std::vector<uint8_t> output(adv_state.data_length);
bt::MutableBufferView output_view(output.data(), output.size());
output_view.Write(adv_state.data, adv_state.data_length);
fidl_state.set_advertising_data(std::move(output));
}
if (adv_state.scan_rsp_length) {
std::vector<uint8_t> output(adv_state.scan_rsp_length);
bt::MutableBufferView output_view(output.data(), output.size());
output_view.Write(adv_state.scan_rsp_data, adv_state.scan_rsp_length);
fidl_state.set_scan_response(std::move(output));
}
legacy_adv_state_getter_.Add(std::move(fidl_state));
}
void Device::UnpublishHci() {
if (hci_dev_) {
device_async_remove(hci_dev_);
hci_dev_ = nullptr;
}
}
void Device::OnPeerConnectionStateChanged(const bt::DeviceAddress& address,
bt::hci::ConnectionHandle handle, bool connected,
bool canceled) {
logf(TRACE, "Peer connection state changed: %s (handle: %#.4x) (connected: %s) (canceled: %s):\n",
address.ToString().c_str(), handle, (connected ? "true" : "false"),
(canceled ? "true" : "false"));
auto iter = peers_.find(address);
if (iter != peers_.end()) {
iter->second->UpdateConnectionState(connected);
}
}
zx_status_t Device::OpenCommandChannel(void* ctx, zx_handle_t channel) {
return static_cast<Device*>(ctx)->OpenChan(Channel::COMMAND, channel);
}
zx_status_t Device::OpenAclDataChannel(void* ctx, zx_handle_t channel) {
return static_cast<Device*>(ctx)->OpenChan(Channel::ACL, channel);
}
zx_status_t Device::OpenSnoopChannel(void* ctx, zx_handle_t channel) {
return static_cast<Device*>(ctx)->OpenChan(Channel::SNOOP, channel);
}
zx_status_t Device::OpenEmulatorChannel(void* ctx, zx_handle_t channel) {
return static_cast<Device*>(ctx)->OpenChan(Channel::EMULATOR, channel);
}
} // namespace bt_hci_emulator