| // 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_endpoints, ClientEnd}; |
| use fidl_fuchsia_bluetooth::{Appearance, Uuid}; |
| use fidl_fuchsia_bluetooth_le::{ |
| AdvertisingData, AdvertisingHandleMarker, AdvertisingModeHint, AdvertisingParameters, |
| ConnectionOptions, ManufacturerData, PeripheralMarker, PeripheralProxy, ServiceData, |
| }; |
| use fuchsia_component as app; |
| use fuchsia_syslog::macros::*; |
| use parking_lot::RwLock; |
| |
| // Sl4f-Constants and Ble advertising related functionality |
| use crate::common_utils::error::Sl4fError; |
| |
| use serde_json::Value; |
| |
| use std::convert::TryInto; |
| |
| #[derive(Debug)] |
| struct InnerBleAdvertiseFacade { |
| /// Advertisement ID of device, only one advertisement at a time. |
| // TODO(fxbug.dev/807): Potentially scale up to storing multiple AdvertisingHandles. We may want to |
| // generate unique identifiers for each advertisement and to allow RPC clients to manage them. |
| adv_handle: Option<ClientEnd<AdvertisingHandleMarker>>, |
| |
| ///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: RwLock<InnerBleAdvertiseFacade>, |
| } |
| |
| impl BleAdvertiseFacade { |
| pub fn new() -> BleAdvertiseFacade { |
| BleAdvertiseFacade { |
| inner: RwLock::new(InnerBleAdvertiseFacade { adv_handle: None, peripheral: None }), |
| } |
| } |
| |
| /// Parse json input UUID and return Bluetooth UUID format. |
| /// |
| /// # Arguments |
| /// * `json_uuid`: The JSON UUID in the form of a list of bytes. |
| fn parse_uuid(&self, json_uuid: &Value) -> Result<Uuid, Error> { |
| let mut byte_list = vec![]; |
| |
| for byte_string in json_uuid.as_array().unwrap() { |
| let raw_value = match i64::from_str_radix(byte_string.as_str().unwrap(), 16) { |
| Ok(v) => v as u8, |
| Err(e) => bail!("Failed to convert raw value with: {:?}", e), |
| }; |
| byte_list.push(raw_value); |
| } |
| Ok(Uuid { value: byte_list.as_slice().try_into().expect("Failed to set UUID value.") }) |
| } |
| |
| /// Parse a list of service uuids from a json input list. |
| /// |
| /// # Arguments |
| /// * `json_service_uuids`: The JSON UUIDs in the form of lists of list of bytes. |
| fn parse_service_uuids(&self, json_service_uuids: &Vec<Value>) -> Result<Vec<Uuid>, Error> { |
| let mut uuid_list = Vec::new(); |
| |
| for raw_uuid_list in json_service_uuids { |
| uuid_list.push(self.parse_uuid(raw_uuid_list)?); |
| } |
| Ok(uuid_list) |
| } |
| |
| /// Parse the json input service data into a list of ServiceData |
| /// |
| /// # Arguments |
| /// * `json_service_data`: The JSON representation of ServiceData to parse. |
| fn parse_service_data( |
| &self, |
| json_service_data: &Vec<Value>, |
| ) -> Result<Vec<ServiceData>, Error> { |
| let mut manufacturer_data_list = Vec::new(); |
| |
| for raw_service_data in json_service_data { |
| let uuid = match raw_service_data.get("uuid") { |
| Some(v) => self.parse_uuid(v)?, |
| None => bail!("Missing Service data info 'uuid'."), |
| }; |
| let data = match raw_service_data.get("data") { |
| Some(d) => d.to_string().into_bytes(), |
| None => bail!("Missing Service data info 'data'."), |
| }; |
| manufacturer_data_list.push(ServiceData { uuid, data }); |
| } |
| Ok(manufacturer_data_list) |
| } |
| |
| /// Parse the json input manufacturer data into a list of ManufacturerData |
| /// |
| /// # Arguments |
| /// * `json_manufacturer_data`: The JSON representation of ManufacturerData to parse. |
| fn parse_manufacturer_data( |
| &self, |
| json_manufacturer_data: &Vec<Value>, |
| ) -> Result<Vec<ManufacturerData>, Error> { |
| let mut manufacturer_data_list = Vec::new(); |
| |
| for raw_manufacturer_data in json_manufacturer_data { |
| let company_id = match raw_manufacturer_data.get("id") { |
| Some(v) => match v.as_u64() { |
| Some(c) => c as u16, |
| None => bail!("Company id not a valid value."), |
| }, |
| None => bail!("Missing Manufacturer info 'id'."), |
| }; |
| let data = match raw_manufacturer_data.get("data") { |
| Some(d) => d.to_string().into_bytes(), |
| None => bail!("Missing Manufacturer info 'data'."), |
| }; |
| manufacturer_data_list.push(ManufacturerData { company_id, data }); |
| } |
| Ok(manufacturer_data_list) |
| } |
| |
| /// Function to parse relevant advertising data based on json input. |
| /// |
| /// # Arguments |
| /// * `data` - A serde json object representing the Advertising data. |
| /// |
| /// Note: A human readable uuid is represented as a list of bytes: |
| /// Example Human Readable UUID: '00001801-0000-1000-8000-00805f9b34fb' |
| /// Actual input: |
| /// ['fb', '34', '9b', '5f', '80', '00', '00', '80', '00', '10', '00', '00', '01', '18', '00', '00'] |
| /// Example input |
| /// data = { |
| /// 'advertising_data': { |
| /// 'name': Some(String), |
| /// 'appearance': Some(u64), |
| /// 'service_data': { |
| /// 'uuid': ['fb', '34', '9b', '5f', '80', '00', '00', '80', '00', '10', '00', '00', '01', '18', '00', '00']. |
| /// 'data': "1" |
| /// }, |
| /// 'service_uuids': [ |
| /// ['fb', '34', '9b', '5f', '80', '00', '00', '80', '00', '10', '00', '00', '01', '18', '00', '00']. |
| /// ['fb', '34', '9b', '5f', '80', '00', '00', '80', '00', '10', '00', '00', '00', '18', '00', '00'] |
| /// ], |
| /// 'manufacturer_data': { |
| /// 'id': 10, |
| /// 'data' |
| /// }, |
| /// 'uris': Some(['telnet://192.0.2.16:80/']), |
| /// 'tx_power_level': Some(1), |
| /// |
| /// } |
| /// |
| /// } |
| fn generate_advertising_data(&self, data: Value) -> Result<Option<AdvertisingData>, Error> { |
| let name: Option<String> = data["name"].as_str().map(String::from); |
| let service_uuids = match data.get("service_uuids") { |
| Some(v) => { |
| if v.is_null() { |
| None |
| } else { |
| match v.clone().as_array() { |
| Some(list) => Some(self.parse_service_uuids(list)?), |
| None => bail!("Attribute 'service_uuids' is not a parseable list."), |
| } |
| } |
| } |
| None => None, |
| }; |
| |
| let appearance = match data.get("appearance") { |
| Some(v) => { |
| if v.is_null() { |
| None |
| } else { |
| match v.as_u64() { |
| Some(c) => Appearance::from_primitive(c as u16), |
| None => None, |
| } |
| } |
| } |
| None => bail!("Value 'appearance' missing."), |
| }; |
| |
| let tx_power_level = match data.get("tx_power_level") { |
| Some(v) => { |
| if v.is_null() { |
| None |
| } else { |
| match v.as_u64() { |
| Some(c) => Some(c as i8), |
| None => None, |
| } |
| } |
| } |
| None => bail!("Value 'tx_power_level' missing."), |
| }; |
| |
| let service_data = match data.get("service_data") { |
| Some(raw) => { |
| if raw.is_null() { |
| None |
| } else { |
| match raw.as_array() { |
| Some(list) => Some(self.parse_service_data(list)?), |
| None => None, |
| } |
| } |
| } |
| None => bail!("Value 'service_data' missing."), |
| }; |
| |
| let manufacturer_data = match data.get("manufacturer_data") { |
| Some(raw) => { |
| if raw.is_null() { |
| None |
| } else { |
| match raw.as_array() { |
| Some(list) => Some(self.parse_manufacturer_data(list)?), |
| None => None, |
| } |
| } |
| } |
| None => bail!("Value 'manufacturer_data' missing."), |
| }; |
| |
| let uris = match data.get("uris") { |
| Some(raw) => { |
| if raw.is_null() { |
| None |
| } else { |
| match raw.as_array() { |
| Some(list) => { |
| let mut uri_list = Vec::new(); |
| for item in list { |
| match item.as_str() { |
| Some(i) => uri_list.push(String::from(i)), |
| None => bail!("Expected URI string"), |
| } |
| } |
| Some(uri_list) |
| } |
| None => None, |
| } |
| } |
| } |
| None => bail!("Value 'uris' missing."), |
| }; |
| |
| Ok(Some(AdvertisingData { |
| name, |
| appearance, |
| tx_power_level, |
| service_uuids, |
| service_data, |
| manufacturer_data, |
| uris: uris, |
| ..AdvertisingData::EMPTY |
| })) |
| } |
| |
| // Takes a serde_json::Value and converts it to arguments required for |
| // a FIDL ble_advertise command |
| fn ble_advertise_args_to_fidl(&self, args_raw: Value) -> Result<AdvertisingParameters, Error> { |
| let advertising_data = match args_raw.get("advertising_data") { |
| Some(adr) => self.generate_advertising_data(adr.clone())?, |
| |
| None => bail!("Value 'advertising_data' missing."), |
| }; |
| |
| let scan_response = match args_raw.get("scan_response") { |
| Some(scn) => { |
| if scn.is_null() { |
| None |
| } else { |
| self.generate_advertising_data(scn.clone())? |
| } |
| } |
| None => None, |
| }; |
| |
| let conn_raw = args_raw.get("connectable").ok_or(format_err!("Connectable missing"))?; |
| |
| let connectable: bool = conn_raw.as_bool().unwrap_or(false); |
| |
| let conn_opts = if connectable { |
| Some(ConnectionOptions { |
| bondable_mode: Some(true), |
| service_filter: None, |
| ..ConnectionOptions::EMPTY |
| }) |
| } else { |
| None |
| }; |
| |
| let parameters = AdvertisingParameters { |
| data: advertising_data, |
| scan_response: scan_response, |
| mode_hint: Some(AdvertisingModeHint::VeryFast), |
| connectable: Some(connectable), |
| connection_options: conn_opts, |
| ..AdvertisingParameters::EMPTY |
| }; |
| |
| fx_log_info!(tag: "ble_advertise_args_to_fidl", "advertising parameters: {:?}", parameters); |
| Ok(parameters) |
| } |
| |
| // Store a new advertising handle. |
| fn set_adv_handle(&self, adv_handle: Option<ClientEnd<AdvertisingHandleMarker>>) { |
| if adv_handle.is_some() { |
| fx_log_info!(tag: "set_adv_handle", "Assigned new advertising handle"); |
| } else { |
| fx_log_info!(tag: "set_adv_handle", "Cleared advertising handle"); |
| } |
| self.inner.write().adv_handle = adv_handle; |
| } |
| |
| pub fn print(&self) { |
| let adv_status = match &self.inner.read().adv_handle { |
| Some(_) => "Valid", |
| None => "None", |
| }; |
| fx_log_info!(tag: "print", |
| "BleAdvertiseFacade: Adv Status: {}, Peripheral: {:?}", |
| adv_status, |
| self.get_peripheral_proxy(), |
| ); |
| } |
| |
| // Set the peripheral proxy only if none exists, otherwise, use existing |
| pub fn set_peripheral_proxy(&self) { |
| let new_peripheral = match self.inner.read().peripheral.clone() { |
| Some(p) => { |
| fx_log_warn!(tag: "set_peripheral_proxy", |
| "Current peripheral: {:?}", |
| p, |
| ); |
| Some(p) |
| } |
| None => { |
| let peripheral_svc: PeripheralProxy = |
| app::client::connect_to_service::<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 characteristics. |
| pub async fn start_adv(&self, args: Value) -> Result<(), Error> { |
| self.set_peripheral_proxy(); |
| let parameters = self.ble_advertise_args_to_fidl(args)?; |
| let periph = &self.inner.read().peripheral.clone(); |
| match &periph { |
| Some(p) => { |
| let (handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?; |
| let result = p.start_advertising(parameters, handle_remote).await?; |
| if let Err(err) = result { |
| fx_log_err!(tag: "start_adv", "Failed to start adveritising: {:?}", err); |
| return Err(Sl4fError::new(&format!("{:?}", err)).into()); |
| } |
| fx_log_info!(tag: "start_adv", "Started advertising"); |
| self.set_adv_handle(Some(handle)); |
| Ok(()) |
| } |
| None => { |
| fx_log_err!(tag: "start_adv", "No peripheral created."); |
| return Err(format_err!("No peripheral proxy created.")); |
| } |
| } |
| } |
| |
| pub fn stop_adv(&self) { |
| fx_log_info!(tag: "stop_adv", "Stop advertising"); |
| self.set_adv_handle(None); |
| } |
| |
| pub fn get_peripheral_proxy(&self) -> Option<PeripheralProxy> { |
| self.inner.read().peripheral.clone() |
| } |
| |
| pub fn cleanup_advertisements(&self) { |
| self.set_adv_handle(None); |
| } |
| |
| // Close peripheral proxy |
| pub fn cleanup_peripheral_proxy(&self) { |
| self.inner.write().peripheral = None; |
| } |
| |
| // Close both central and peripheral proxies |
| pub fn cleanup(&self) { |
| self.cleanup_advertisements(); |
| self.cleanup_peripheral_proxy(); |
| } |
| } |