blob: 84270424f1f88fbb4f9ebd723ab6815645a32b5d [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 "low_energy_peripheral_server.h"
#include <zircon/assert.h>
#include "helpers.h"
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/advertising_data.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/peer.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/hci_constants.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/util.h"
#include "src/lib/fxl/strings/string_number_conversions.h"
using fuchsia::bluetooth::ErrorCode;
using fuchsia::bluetooth::Status;
using fuchsia::bluetooth::le::AdvertisingData;
using fuchsia::bluetooth::le::AdvertisingDataPtr;
using fuchsia::bluetooth::le::Peripheral;
using fuchsia::bluetooth::le::RemoteDevice;
using fuchsia::bluetooth::le::RemoteDevicePtr;
namespace bthost {
namespace {
std::string MessageFromStatus(bt::hci::Status status) {
switch (status.error()) {
case bt::HostError::kNoError:
return "Success";
case bt::HostError::kNotSupported:
return "Maximum advertisement amount reached";
case bt::HostError::kInvalidParameters:
return "Advertisement exceeds maximum allowed length";
default:
return status.ToString();
}
}
// TODO(BT-305): Remove this once the string IDs have been removed from the FIDL
// API.
std::optional<bt::gap::AdvertisementId> AdvertisementIdFromString(
const std::string& id) {
uint64_t value;
if (!fxl::StringToNumberWithError<decltype(value)>(id, &value,
fxl::Base::k16)) {
return std::nullopt;
}
return bt::gap::AdvertisementId(value);
}
} // namespace
LowEnergyPeripheralServer::InstanceData::InstanceData(
bt::gap::AdvertisementId id, fxl::WeakPtr<LowEnergyPeripheralServer> owner)
: id_(id), owner_(owner) {
ZX_DEBUG_ASSERT(owner_);
}
void LowEnergyPeripheralServer::InstanceData::RetainConnection(
ConnectionRefPtr conn_ref, RemoteDevice peer) {
ZX_DEBUG_ASSERT(connectable());
ZX_DEBUG_ASSERT(!conn_ref_);
conn_ref_ = std::move(conn_ref);
owner_->binding()->events().OnCentralConnected(id_.ToString(),
std::move(peer));
}
void LowEnergyPeripheralServer::InstanceData::ReleaseConnection() {
ZX_DEBUG_ASSERT(connectable());
ZX_DEBUG_ASSERT(conn_ref_);
owner_->binding()->events().OnCentralDisconnected(
conn_ref_->peer_identifier().ToString());
conn_ref_ = nullptr;
}
LowEnergyPeripheralServer::LowEnergyPeripheralServer(
fxl::WeakPtr<bt::gap::Adapter> adapter,
fidl::InterfaceRequest<Peripheral> request)
: AdapterServerBase(adapter, this, std::move(request)),
weak_ptr_factory_(this) {}
LowEnergyPeripheralServer::~LowEnergyPeripheralServer() {
auto* advertising_manager = adapter()->le_advertising_manager();
ZX_DEBUG_ASSERT(advertising_manager);
for (const auto& it : instances_) {
advertising_manager->StopAdvertising(it.first);
}
}
void LowEnergyPeripheralServer::StartAdvertising(
AdvertisingData advertising_data, AdvertisingDataPtr scan_result,
bool connectable, uint32_t interval, bool anonymous,
StartAdvertisingCallback callback) {
auto* advertising_manager = adapter()->le_advertising_manager();
ZX_DEBUG_ASSERT(advertising_manager);
bt::gap::AdvertisingData ad_data, scan_data;
if (!bt::gap::AdvertisingData::FromFidl(advertising_data, &ad_data)) {
callback(fidl_helpers::NewFidlError(ErrorCode::INVALID_ARGUMENTS,
"Invalid advertising data"),
"");
return;
}
if (scan_result &&
!bt::gap::AdvertisingData::FromFidl(*scan_result, &scan_data)) {
callback(fidl_helpers::NewFidlError(ErrorCode::INVALID_ARGUMENTS,
"Invalid scan response data"),
"");
return;
}
auto self = weak_ptr_factory_.GetWeakPtr();
bt::gap::LowEnergyAdvertisingManager::ConnectionCallback connect_cb;
// TODO(armansito): The conversion from hci::Connection to
// gap::LowEnergyConnectionRef should be performed by a gap library object
// and not in this layer (see NET-355).
if (connectable) {
connect_cb = [self](auto adv_id, auto link) {
if (self)
self->OnConnected(adv_id, std::move(link));
};
}
auto advertising_status_cb = [self, callback = std::move(callback)](
bt::gap::AdvertisementId ad_id,
bt::hci::Status status) mutable {
if (!self)
return;
if (!status) {
bt_log(TRACE, "bt-host", "failed to start advertising: %s",
status.ToString().c_str());
callback(fidl_helpers::StatusToFidl(status, MessageFromStatus(status)),
"");
return;
}
self->instances_[ad_id] =
InstanceData(ad_id, self->weak_ptr_factory_.GetWeakPtr());
callback(Status(), ad_id.ToString());
};
advertising_manager->StartAdvertising(
ad_data, scan_data, std::move(connect_cb), zx::msec(interval), anonymous,
std::move(advertising_status_cb));
}
void LowEnergyPeripheralServer::StopAdvertising(
::std::string id, StopAdvertisingCallback callback) {
auto peer_id = AdvertisementIdFromString(id);
if (!peer_id.has_value()) {
callback(fidl_helpers::NewFidlError(ErrorCode::INVALID_ARGUMENTS,
"invalid peer ID"));
return;
}
if (StopAdvertisingInternal(*peer_id)) {
callback(Status());
} else {
callback(fidl_helpers::NewFidlError(ErrorCode::NOT_FOUND,
"Unrecognized advertisement ID"));
}
}
bool LowEnergyPeripheralServer::StopAdvertisingInternal(
bt::gap::AdvertisementId id) {
auto count = instances_.erase(id);
if (count) {
adapter()->le_advertising_manager()->StopAdvertising(id);
}
return count != 0;
}
void LowEnergyPeripheralServer::OnConnected(
bt::gap::AdvertisementId advertisement_id, bt::hci::ConnectionPtr link) {
ZX_DEBUG_ASSERT(link);
// If the active adapter that was used to start advertising was changed before
// we process this connection then the instance will have been removed.
auto it = instances_.find(advertisement_id);
if (it == instances_.end()) {
bt_log(TRACE, "bt-host",
"connection received from wrong advertising instance");
return;
}
ZX_DEBUG_ASSERT(it->second.connectable());
auto conn = adapter()->le_connection_manager()->RegisterRemoteInitiatedLink(
std::move(link));
if (!conn) {
bt_log(TRACE, "bt-host", "incoming connection rejected");
return;
}
auto self = weak_ptr_factory_.GetWeakPtr();
conn->set_closed_callback([self, id = advertisement_id] {
bt_log(TRACE, "bt-host", "central disconnected");
if (!self)
return;
// Make sure that the instance hasn't been removed.
auto it = self->instances_.find(id);
if (it == self->instances_.end())
return;
// This sends OnCentralDisconnected() to the delegate.
it->second.ReleaseConnection();
});
// A peer will have been created for the new connection.
auto* peer = adapter()->peer_cache()->FindById(conn->peer_identifier());
ZX_DEBUG_ASSERT(peer);
bt_log(TRACE, "bt-host", "central connected");
RemoteDevicePtr remote_device =
fidl_helpers::NewLERemoteDevice(std::move(*peer));
ZX_DEBUG_ASSERT(remote_device);
it->second.RetainConnection(std::move(conn), std::move(*remote_device));
}
} // namespace bthost