| // 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. |
| |
| use anyhow::{format_err, Context as _, Error}; |
| use fidl::endpoints::create_request_stream; |
| use fidl_fuchsia_bluetooth_le::{ |
| AdvertisedPeripheralMarker, AdvertisedPeripheralRequest, AdvertisedPeripheralRequestStream, |
| AdvertisingParameters, ConnectionProxy, PeripheralMarker, PeripheralProxy, |
| }; |
| use fuchsia_bluetooth::types::PeerId; |
| use fuchsia_sync::RwLock; |
| use futures::{pin_mut, select, FutureExt, StreamExt}; |
| use serde_json::Value; |
| use std::collections::HashMap; |
| use std::sync::Arc; |
| use tracing::{debug, error, info, warn}; |
| use {fuchsia_async as fasync, fuchsia_component as app}; |
| |
| // Sl4f-Constants and Ble advertising related functionality |
| use crate::bluetooth::types::FacadeArg; |
| use crate::common_utils::common::macros::with_line; |
| |
| #[allow(dead_code)] // TODO(https://fxbug.dev/318827209) |
| #[derive(Debug)] |
| struct Connection(ConnectionProxy, fasync::Task<()>); |
| |
| #[derive(Debug)] |
| struct InnerBleAdvertiseFacade { |
| /// Advertised peripheral server of the facade, only one advertisement at a time. |
| advertise_task: Option<fasync::Task<()>>, |
| |
| // Active connections. |
| connections: HashMap<PeerId, Connection>, |
| |
| ///PeripheralProxy used for Bluetooth Connections |
| peripheral: Option<PeripheralProxy>, |
| } |
| |
| /// Starts and stops device BLE advertisement(s). |
| /// Note this object is shared among all threads created by server. |
| #[derive(Debug)] |
| pub struct BleAdvertiseFacade { |
| inner: Arc<RwLock<InnerBleAdvertiseFacade>>, |
| } |
| |
| impl BleAdvertiseFacade { |
| pub fn new() -> BleAdvertiseFacade { |
| BleAdvertiseFacade { |
| inner: Arc::new(RwLock::new(InnerBleAdvertiseFacade { |
| advertise_task: None, |
| connections: HashMap::new(), |
| peripheral: None, |
| })), |
| } |
| } |
| |
| fn set_advertise_task( |
| inner: &Arc<RwLock<InnerBleAdvertiseFacade>>, |
| task: Option<fasync::Task<()>>, |
| ) { |
| let tag = "BleAdvertiseFacade::set_advertise_task"; |
| if task.is_some() { |
| info!(tag = &with_line!(tag), "Assigned new advertise task"); |
| } else if inner.read().advertise_task.is_some() { |
| info!(tag = &with_line!(tag), "Cleared advertise task"); |
| } |
| inner.write().advertise_task = task; |
| } |
| |
| pub fn print(&self) { |
| let adv_status = match &self.inner.read().advertise_task { |
| Some(_) => "Valid", |
| None => "None", |
| }; |
| info!(tag = &with_line!("BleAdvertiseFacade::print"), |
| %adv_status, |
| peripheral = ?self.get_peripheral_proxy(), |
| "BleAdvertiseFacade", |
| ); |
| } |
| |
| // Set the peripheral proxy only if none exists, otherwise, use existing |
| pub fn set_peripheral_proxy(&self) { |
| let tag = "BleAdvertiseFacade::set_peripheral_proxy"; |
| |
| let new_peripheral = match self.inner.read().peripheral.clone() { |
| Some(p) => { |
| warn!(tag = &with_line!(tag), current_peripheral = ?p); |
| Some(p) |
| } |
| None => { |
| let peripheral_svc: PeripheralProxy = |
| app::client::connect_to_protocol::<PeripheralMarker>() |
| .context("Failed to connect to BLE Peripheral service.") |
| .unwrap(); |
| Some(peripheral_svc) |
| } |
| }; |
| |
| self.inner.write().peripheral = new_peripheral |
| } |
| |
| /// Start BLE advertisement |
| /// |
| /// # Arguments |
| /// * `args`: A JSON input representing advertisement parameters. |
| pub async fn start_adv(&self, args: Value) -> Result<(), Error> { |
| self.set_peripheral_proxy(); |
| let parameters: AdvertisingParameters = FacadeArg::new(args).try_into()?; |
| let periph = &self.inner.read().peripheral.clone(); |
| match &periph { |
| Some(p) => { |
| // Clear any existing advertisement. |
| BleAdvertiseFacade::set_advertise_task(&self.inner, None); |
| |
| let advertise_task = fasync::Task::spawn(BleAdvertiseFacade::advertise( |
| self.inner.clone(), |
| p.clone(), |
| parameters, |
| )); |
| info!(tag = "start_adv", "Started advertising"); |
| BleAdvertiseFacade::set_advertise_task(&self.inner, Some(advertise_task)); |
| Ok(()) |
| } |
| None => { |
| error!(tag = "start_adv", "No peripheral created."); |
| return Err(format_err!("No peripheral proxy created.")); |
| } |
| } |
| } |
| |
| fn process_new_connection( |
| inner: Arc<RwLock<InnerBleAdvertiseFacade>>, |
| proxy: ConnectionProxy, |
| peer_id: PeerId, |
| ) { |
| let tag = "BleAdvertiseFacade::process_new_connection"; |
| |
| let mut stream = proxy.take_event_stream(); |
| |
| let inner_clone = inner.clone(); |
| let stream_fut = async move { |
| while let Some(event) = stream.next().await { |
| match event { |
| Ok(_) => { |
| debug!(tag = &with_line!(tag), "ignoring event for Connection"); |
| } |
| Err(err) => { |
| info!(tag = &with_line!(tag), "Connection ({}) error: {:?}", peer_id, err); |
| } |
| } |
| } |
| info!(tag = &with_line!(tag), "peer {} disconnected", peer_id); |
| inner_clone.write().connections.remove(&peer_id); |
| }; |
| let event_task = fasync::Task::spawn(stream_fut); |
| inner.write().connections.insert(peer_id, Connection(proxy, event_task)); |
| } |
| |
| async fn process_advertised_peripheral_stream( |
| inner: Arc<RwLock<InnerBleAdvertiseFacade>>, |
| mut stream: AdvertisedPeripheralRequestStream, |
| ) { |
| let tag = "BleAdvertiseFacade::process_advertised_peripheral_stream"; |
| while let Some(request) = stream.next().await { |
| match request { |
| Ok(AdvertisedPeripheralRequest::OnConnected { peer, connection, responder }) => { |
| if let Err(err) = responder.send() { |
| warn!( |
| tag = &with_line!(tag), |
| "error sending response to AdvertisedPeripheral::OnConnected: {}", err |
| ); |
| } |
| |
| let proxy = match connection.into_proxy() { |
| Ok(proxy) => proxy, |
| Err(_) => { |
| warn!( |
| tag = &with_line!(tag), |
| "error creating Connection proxy, dropping Connection" |
| ); |
| continue; |
| } |
| }; |
| let peer_id: PeerId = peer.id.unwrap().into(); |
| BleAdvertiseFacade::process_new_connection(inner.clone(), proxy, peer_id); |
| } |
| Err(err) => { |
| info!(tag = &with_line!(tag), "AdvertisedPeripheral error: {:?}", err); |
| } |
| } |
| } |
| info!(tag = &with_line!(tag), "AdvertisedPeripheral closed, stopping advertising"); |
| BleAdvertiseFacade::set_advertise_task(&inner, None); |
| } |
| |
| async fn advertise( |
| inner: Arc<RwLock<InnerBleAdvertiseFacade>>, |
| peripheral: PeripheralProxy, |
| parameters: AdvertisingParameters, |
| ) { |
| let tag = "BleAdvertiseFacade::advertise"; |
| let (client_end, server_request_stream) = |
| match create_request_stream::<AdvertisedPeripheralMarker>() { |
| Ok(value) => value, |
| Err(_) => return, |
| }; |
| |
| // advertise() only returns after advertising has been terminated, so we can't await here. |
| let advertise_fut = peripheral.advertise(¶meters, client_end); |
| |
| let server_fut = BleAdvertiseFacade::process_advertised_peripheral_stream( |
| inner.clone(), |
| server_request_stream, |
| ); |
| |
| let advertise_fut_fused = advertise_fut.fuse(); |
| let server_fut_fused = server_fut.fuse(); |
| pin_mut!(advertise_fut_fused, server_fut_fused); |
| select! { |
| result = advertise_fut_fused => { |
| info!(tag = &with_line!(tag), "advertise() returned with result {:?}", result); |
| } |
| _ = server_fut_fused => { |
| info!(tag = &with_line!(tag), "AdvertisedPeripheral closed"); |
| } |
| }; |
| |
| // Stop advertising. |
| inner.write().advertise_task.take(); |
| } |
| |
| pub fn stop_adv(&self) { |
| info!(tag = &with_line!("BleAdvertiseFacade::stop_adv"), "Stop advertising"); |
| BleAdvertiseFacade::set_advertise_task(&self.inner, None); |
| } |
| |
| pub fn get_peripheral_proxy(&self) -> Option<PeripheralProxy> { |
| self.inner.read().peripheral.clone() |
| } |
| |
| // Close peripheral proxy |
| pub fn cleanup_peripheral_proxy(&self) { |
| self.inner.write().peripheral = None; |
| } |
| |
| // Cancel all tasks and close all protocols. |
| pub fn cleanup(&self) { |
| self.inner.write().connections.clear(); |
| self.stop_adv(); |
| self.cleanup_peripheral_proxy(); |
| } |
| } |