blob: b159e136ba3e9a8cf6c6fba4fdec02235d3e9704 [file] [log] [blame]
// Copyright 2019 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 "emulated_peer.h"
#include "src/connectivity/bluetooth/core/bt-host/fidl/helpers.h"
namespace fbt = fuchsia_bluetooth;
namespace bt_hci_virtual {
namespace {
bt::DeviceAddress::Type LeAddressTypeFromFidl(fbt::AddressType type) {
return (type == fbt::AddressType::kRandom) ? bt::DeviceAddress::Type::kLERandom
: bt::DeviceAddress::Type::kLEPublic;
}
bt::DeviceAddress LeAddressFromFidl(const fbt::Address& address) {
return bt::DeviceAddress(LeAddressTypeFromFidl(address.type()), address.bytes());
}
pw::bluetooth::emboss::ConnectionRole ConnectionRoleFromFidl(fbt::ConnectionRole role) {
switch (role) {
case fbt::ConnectionRole::kLeader:
return pw::bluetooth::emboss::ConnectionRole::CENTRAL;
case fbt::ConnectionRole::kFollower:
[[fallthrough]];
default:
break;
}
return pw::bluetooth::emboss::ConnectionRole::PERIPHERAL;
}
} // namespace
// static
EmulatedPeer::Result EmulatedPeer::NewLowEnergy(
fuchsia_hardware_bluetooth::PeerParameters parameters,
bt::testing::FakeController* fake_controller, async_dispatcher_t* dispatcher) {
ZX_DEBUG_ASSERT(parameters.channel().has_value());
ZX_DEBUG_ASSERT(fake_controller);
if (!parameters.address().has_value()) {
bt_log(ERROR, "virtual", "A fake peer address is mandatory!\n");
return fpromise::error(fuchsia_hardware_bluetooth::EmulatorPeerError::kParametersInvalid);
}
auto address = LeAddressFromFidl(parameters.address().value());
bool connectable = parameters.connectable().has_value() && parameters.connectable().value();
// TODO(armansito): We should consider splitting bt::testing::FakePeer into separate types for
// BR/EDR and LE transport emulation logic.
auto peer = std::make_unique<bt::testing::FakePeer>(address, fake_controller->pw_dispatcher(),
connectable);
if (!fake_controller->AddPeer(std::move(peer))) {
bt_log(ERROR, "virtual", "A fake LE peer with given address already exists: %s\n",
address.ToString().c_str());
return fpromise::error(fuchsia_hardware_bluetooth::EmulatorPeerError::kAddressRepeated);
}
return fpromise::ok(std::unique_ptr<EmulatedPeer>(new EmulatedPeer(
address, std::move(parameters.channel().value()), fake_controller, dispatcher)));
}
// static
EmulatedPeer::Result EmulatedPeer::NewBredr(fuchsia_hardware_bluetooth::PeerParameters parameters,
bt::testing::FakeController* fake_controller,
async_dispatcher_t* dispatcher) {
ZX_DEBUG_ASSERT(parameters.channel().has_value());
ZX_DEBUG_ASSERT(fake_controller);
if (!parameters.address().has_value()) {
bt_log(ERROR, "virtual", "A fake peer address is mandatory!\n");
return fpromise::error(fuchsia_hardware_bluetooth::EmulatorPeerError::kParametersInvalid);
}
auto address = bt::DeviceAddress(bt::DeviceAddress::Type::kBREDR, parameters.address()->bytes());
bool connectable = parameters.connectable().has_value() && parameters.connectable().value();
// TODO(armansito): We should consider splitting bt::testing::FakePeer into separate types for
// BR/EDR and LE transport emulation logic.
auto peer = std::make_unique<bt::testing::FakePeer>(address, fake_controller->pw_dispatcher(),
connectable, false);
if (!fake_controller->AddPeer(std::move(peer))) {
bt_log(ERROR, "virtual", "A fake BR/EDR peer with given address already exists: %s\n",
address.ToString().c_str());
return fpromise::error(fuchsia_hardware_bluetooth::EmulatorPeerError::kAddressRepeated);
}
return fpromise::ok(std::unique_ptr<EmulatedPeer>(new EmulatedPeer(
address, std::move(parameters.channel().value()), fake_controller, dispatcher)));
}
EmulatedPeer::EmulatedPeer(bt::DeviceAddress address,
fidl::ServerEnd<fuchsia_hardware_bluetooth::Peer> request,
bt::testing::FakeController* fake_controller,
async_dispatcher_t* dispatcher)
: address_(address),
fake_controller_(fake_controller),
binding_(dispatcher, std::move(request), this, std::mem_fn(&EmulatedPeer::OnChannelClosed)) {
ZX_DEBUG_ASSERT(fake_controller_);
}
EmulatedPeer::~EmulatedPeer() { CleanUp(); }
void EmulatedPeer::AssignConnectionStatus(AssignConnectionStatusRequest& request,
AssignConnectionStatusCompleter::Sync& completer) {
bt_log(TRACE, "virtual", "EmulatedPeer.AssignConnectionStatus\n");
auto peer = fake_controller_->FindPeer(address_);
if (peer) {
peer->set_connect_response(bthost::fidl_helpers::FidlHciErrorToStatusCode(request.status()));
}
completer.Reply();
}
void EmulatedPeer::EmulateLeConnectionComplete(
EmulateLeConnectionCompleteRequest& request,
EmulateLeConnectionCompleteCompleter::Sync& completer) {
bt_log(TRACE, "virtual", "EmulatedPeer.EmulateLeConnectionComplete\n");
fake_controller_->ConnectLowEnergy(address_, ConnectionRoleFromFidl(request.role()));
}
void EmulatedPeer::EmulateDisconnectionComplete(
EmulateDisconnectionCompleteCompleter::Sync& completer) {
bt_log(TRACE, "virtual", "EmulatedPeer.EmulateDisconnectionComplete\n");
fake_controller_->Disconnect(address_);
}
void EmulatedPeer::WatchConnectionStates(WatchConnectionStatesCompleter::Sync& completer) {
bt_log(TRACE, "virtual", "EmulatedPeer.WatchConnectionState\n");
{
std::lock_guard<std::mutex> lock(connection_states_lock_);
connection_states_completers_.emplace(completer.ToAsync());
}
MaybeUpdateConnectionStates();
}
void EmulatedPeer::SetDeviceClass(SetDeviceClassRequest& request,
SetDeviceClassCompleter::Sync& completer) {
auto peer = fake_controller_->FindPeer(address_);
if (!peer) {
bt_log(WARN, "virtual", "Peer with address %s not found", address_.ToString().c_str());
binding_.Close(ZX_ERR_NOT_SUPPORTED);
return;
}
if (!peer->supports_bredr()) {
bt_log(WARN, "virtual", "Expected fake BR/EDR peer");
binding_.Close(ZX_ERR_NOT_SUPPORTED);
return;
}
peer->set_class_of_device(bt::DeviceClass(request.value()));
completer.Reply();
}
void EmulatedPeer::SetServiceDefinitions(SetServiceDefinitionsRequest& request,
SetServiceDefinitionsCompleter::Sync& completer) {
auto peer = fake_controller_->FindPeer(address_);
if (!peer) {
bt_log(WARN, "virtual", "Peer with address %s not found", address_.ToString().c_str());
binding_.Close(ZX_ERR_NOT_SUPPORTED);
return;
}
if (!peer->supports_bredr()) {
bt_log(WARN, "virtual", "Expected fake BR/EDR peer");
binding_.Close(ZX_ERR_NOT_SUPPORTED);
return;
}
std::vector<bt::sdp::ServiceRecord> recs;
for (const auto& defn : request.service_definitions()) {
auto rec = bthost::fidl_helpers::ServiceDefinitionToServiceRecord(defn);
if (rec.is_ok()) {
recs.emplace_back(std::move(rec.value()));
}
}
bt::l2cap::ChannelParameters params;
auto NopConnectCallback = [](auto /*channel*/, const bt::sdp::DataElement&) {};
peer->sdp_server()->server()->RegisterService(std::move(recs), params, NopConnectCallback);
completer.Reply();
}
void EmulatedPeer::SetLeAdvertisement(SetLeAdvertisementRequest& request,
SetLeAdvertisementCompleter::Sync& completer) {
auto peer = fake_controller_->FindPeer(address_);
if (!peer) {
bt_log(WARN, "virtual", "Peer with address %s not found", address_.ToString().c_str());
completer.Reply(fit::error(fuchsia_hardware_bluetooth::EmulatorPeerError::kParametersInvalid));
return;
}
if (!peer->supports_le()) {
bt_log(WARN, "virtual", "Expected fake LE peer");
completer.Reply(fit::error(fuchsia_hardware_bluetooth::EmulatorPeerError::kParametersInvalid));
return;
}
if (request.le_address().has_value()) {
bt::DeviceAddress le_address = LeAddressFromFidl(request.le_address().value());
auto le_peer = fake_controller_->FindPeer(le_address);
if (le_peer != peer) {
bt_log(ERROR, "virtual", "A fake LE peer with given address already exists: %s\n",
le_address.ToString().c_str());
completer.Reply(fit::error(fuchsia_hardware_bluetooth::EmulatorPeerError::kAddressRepeated));
return;
}
peer->set_le_advertising_address(le_address);
}
if (request.advertisement().has_value() && request.advertisement().value().data().has_value()) {
peer->set_advertising_data(bt::BufferView(request.advertisement().value().data().value()));
}
if (request.scan_response().has_value() && request.scan_response().value().data().has_value()) {
bt::BufferView scan_rsp = bt::BufferView(request.scan_response().value().data().value());
peer->set_scannable(true);
peer->set_scan_response(scan_rsp);
}
completer.Reply(fit::success());
}
void EmulatedPeer::handle_unknown_method(
fidl::UnknownMethodMetadata<fuchsia_hardware_bluetooth::Peer> metadata,
fidl::UnknownMethodCompleter::Sync& completer) {
bt_log(WARN, "virtual", "Unknown method in Peer request, closing with ZX_ERR_NOT_SUPPORTED");
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
void EmulatedPeer::UpdateConnectionState(bool connected) {
fuchsia_hardware_bluetooth::ConnectionState state =
connected ? fuchsia_hardware_bluetooth::ConnectionState::kConnected
: fuchsia_hardware_bluetooth::ConnectionState::kDisconnected;
connection_states_.emplace_back(state);
MaybeUpdateConnectionStates();
}
void EmulatedPeer::MaybeUpdateConnectionStates() {
std::lock_guard<std::mutex> lock(connection_states_lock_);
if (connection_states_.empty() || connection_states_completers_.empty()) {
return;
}
while (!connection_states_completers_.empty()) {
connection_states_completers_.front().Reply(connection_states_);
connection_states_completers_.pop();
}
connection_states_.clear();
}
void EmulatedPeer::OnChannelClosed(fidl::UnbindInfo info) {
bt_log(TRACE, "virtual", "EmulatedPeer channel closed\n");
NotifyChannelClosed();
}
void EmulatedPeer::CleanUp() { fake_controller_->RemovePeer(address_); }
void EmulatedPeer::NotifyChannelClosed() {
if (closed_callback_) {
closed_callback_();
}
}
} // namespace bt_hci_virtual