| // 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 |