blob: 919b6ad863c428b93ab98478e5bdfd6aad08fb11 [file] [log] [blame]
// 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 async;
use async::{temp::TempFutureExt, TimeoutExt};
use bt::error::Error as BTError;
use failure::Error;
use fidl_ble::{AdvertisingData, ScanFilter};
use fidl_gatt::ServiceInfo;
use futures::future::ready as fready;
use futures::prelude::*;
use futures::FutureExt;
use parking_lot::RwLock;
use serde_json::{to_value, Value};
use std::sync::Arc;
use zx::prelude::*;
// Bluetooth-related functionality
use bluetooth::constants::*;
use bluetooth::facade::BluetoothFacade;
use bluetooth::types::{BleConnectPeripheralResponse, BluetoothMethod};
macro_rules! parse_arg {
($args:ident, $func:ident, $name:expr) => {
match $args.get($name) {
Some(v) => match v.$func() {
Some(val) => Ok(val),
None => Err(BTError::new(format!("malformed {}", $name).as_str())),
},
None => Err(BTError::new(format!("{} missing", $name).as_str())),
}
};
}
// Takes a serde_json::Value and converts it to arguments required for
// a FIDL ble_advertise command
fn ble_advertise_to_fidl(args_raw: Value) -> Result<(Option<AdvertisingData>, Option<u32>), Error> {
let adv_data_raw = match args_raw.get("advertising_data") {
Some(adr) => Some(adr).unwrap().clone(),
None => return Err(BTError::new("Advertising data missing.").into()),
};
let interval_raw = match args_raw.get("interval_ms") {
Some(ir) => Some(ir).unwrap().clone(),
None => return Err(BTError::new("Interval_ms missing.").into()),
};
// Unpack the name for advertising data, as well as interval of advertising
let name: Option<String> = adv_data_raw["name"].as_str().map(String::from);
let interval: Option<u32> = interval_raw.as_u64().map(|i| i as u32);
// TODO(NET-1026): Is there a better way to unpack the args into an AdvData
// struct? Unfortunately, can't derive deserialize for AdvData
let ad = Some(AdvertisingData {
name: name,
tx_power_level: None,
appearance: None,
service_uuids: None,
service_data: None,
manufacturer_specific_data: None,
solicited_service_uuids: None,
uris: None,
});
fx_log_info!(tag: "ble_advertise_to_fidl", "AdvData: {:?}", ad);
Ok((ad, interval))
}
// Takes a serde_json::Value and converts it to arguments required for a FIDL
// ble_scan command
fn ble_scan_to_fidl(
args_raw: Value,
) -> Result<(Option<ScanFilter>, Option<u64>, Option<u64>), Error> {
let timeout_raw = match args_raw.get("scan_time_ms") {
Some(t) => Some(t).unwrap().clone(),
None => return Err(BTError::new("Timeout_ms missing.").into()),
};
let scan_filter_raw = match args_raw.get("filter") {
Some(f) => Some(f).unwrap().clone(),
None => return Err(BTError::new("Scan filter missing.").into()),
};
let scan_count_raw = match args_raw.get("scan_count") {
Some(c) => Some(c).unwrap().clone(),
None => return Err(BTError::new("Scan count missing.").into()),
};
let timeout: Option<u64> = timeout_raw.as_u64();
let name_substring: Option<String> =
scan_filter_raw["name_substring"].as_str().map(String::from);
let count: Option<u64> = scan_count_raw.as_u64();
// For now, no scan profile, so default to empty ScanFilter
let filter = Some(ScanFilter {
service_uuids: None,
service_data_uuids: None,
manufacturer_identifier: None,
connectable: None,
name_substring: name_substring,
max_path_loss: None,
});
Ok((filter, timeout, count))
}
// Takes a serde_json::Value and converts it to arguments required for a FIDL
// stop_advertising command. For stop advertise, no arguments are sent, rather
// uses current advertisement id (if it exists)
fn ble_stop_advertise_to_fidl(
_args_raw: Value, bt_facade: Arc<RwLock<BluetoothFacade>>,
) -> Result<String, Error> {
let adv_id = bt_facade.read().get_adv_id().clone();
match adv_id.name {
Some(aid) => Ok(aid.to_string()),
None => Err(BTError::new("No advertisement id outstanding.").into()),
}
}
fn parse_identifier(args_raw: Value) -> Result<String, Error> {
let id_raw = match args_raw.get("identifier") {
Some(id) => id,
None => return Err(BTError::new("Connect peripheral identifier missing").into()),
};
let id = id_raw.as_str().map(String::from);
match id {
Some(id) => Ok(id),
None => return Err(BTError::new("Identifier missing").into()),
}
}
fn ble_publish_service_to_fidl(args_raw: Value) -> Result<(ServiceInfo, String), Error> {
let id = parse_arg!(args_raw, as_u64, "id")?;
let primary = parse_arg!(args_raw, as_bool, "primary")?;
let type_ = parse_arg!(args_raw, as_str, "type")?;
let local_service_id = parse_arg!(args_raw, as_str, "local_service_id")?;
// TODO(NET-1293): Add support for GATT characterstics and includes
let characteristics = None;
let includes = None;
let service_info = ServiceInfo {
id,
primary,
type_: type_.to_string(),
characteristics,
includes,
};
Ok((service_info, local_service_id.to_string()))
}
// Takes ACTS method command and executes corresponding FIDL method
// Packages result into serde::Value
// To add new methods, add to the unsafe_many_futures! macro
pub fn ble_method_to_fidl(
method_name: String, args: Value, bt_facade: Arc<RwLock<BluetoothFacade>>,
) -> impl Future<Output = Result<Value, Error>> {
unsafe_many_futures!(
Output,
[
BleAdvertise,
BleConnectPeripheral,
BleDisconnectPeripheral,
BleListServices,
BlePublishService,
BleScan,
BleStopAdvertise,
Error
]
);
match BluetoothMethod::from_str(method_name) {
BluetoothMethod::BleAdvertise => {
let (ad, interval) = match ble_advertise_to_fidl(args) {
Ok((adv_data, intv)) => (adv_data, intv),
Err(e) => return Output::Error(fready(Err(e))),
};
let adv_fut = start_adv_async(bt_facade.clone(), ad, interval);
Output::BleAdvertise(adv_fut)
}
BluetoothMethod::BleScan => {
let (filter, timeout, count) = match ble_scan_to_fidl(args) {
Ok((f, t, c)) => (f, t, c),
Err(e) => return Output::Error(fready(Err(e))),
};
let scan_fut = start_scan_async(bt_facade.clone(), filter, timeout, count);
Output::BleScan(scan_fut)
}
BluetoothMethod::BleStopAdvertise => {
let advertisement_id = match ble_stop_advertise_to_fidl(args, bt_facade.clone()) {
Ok(aid) => aid,
Err(e) => return Output::Error(fready(Err(e))),
};
let stop_fut = stop_adv_async(bt_facade.clone(), advertisement_id.clone());
Output::BleStopAdvertise(stop_fut)
}
BluetoothMethod::BleConnectPeripheral => {
let id = match parse_identifier(args) {
Ok(id) => id,
Err(e) => return Output::Error(fready(Err(e))),
};
let connect_periph_fut = connect_peripheral_async(bt_facade.clone(), id.clone());
Output::BleConnectPeripheral(connect_periph_fut)
}
BluetoothMethod::BleDisconnectPeripheral => {
let id = match parse_identifier(args) {
Ok(id) => id,
Err(e) => return Output::Error(fready(Err(e))),
};
let disc_periph_fut = disconnect_peripheral_async(bt_facade.clone(), id.clone());
Output::BleDisconnectPeripheral(disc_periph_fut)
}
BluetoothMethod::BleListServices => {
let id = match parse_identifier(args) {
Ok(id) => id,
Err(e) => return Output::Error(fready(Err(e))),
};
let list_services_fut = list_services_async(bt_facade.clone(), id.clone());
Output::BleListServices(list_services_fut)
}
BluetoothMethod::BlePublishService => {
let (service_info, local_service_id) = match ble_publish_service_to_fidl(args) {
Ok((si, pk)) => (si, pk),
Err(e) => return Output::Error(fready(Err(e))),
};
let publish_service_fut =
publish_service_async(bt_facade.clone(), service_info, local_service_id);
Output::BlePublishService(publish_service_fut)
}
_ => Output::Error(fready(Err(BTError::new("Invalid BLE FIDL method").into()))),
}
}
fn start_adv_async(
bt_facade: Arc<RwLock<BluetoothFacade>>, ad: Option<AdvertisingData>, interval: Option<u32>,
) -> impl Future<Output = Result<Value, Error>> {
let start_adv_fut = BluetoothFacade::start_adv(bt_facade.clone(), ad, interval);
start_adv_fut.then(move |res| match res {
Ok(_) => {
let aid_response = bt_facade.read().get_adv_id();
match to_value(aid_response) {
Ok(val) => fready(Ok(val)),
Err(e) => fready(Err(e.into())),
}
}
Err(e) => fready(Err(e.into())),
})
}
fn stop_adv_async(
bt_facade: Arc<RwLock<BluetoothFacade>>, advertisement_id: String,
) -> impl Future<Output = Result<Value, Error>> {
let stop_adv_fut = bt_facade.write().stop_adv(advertisement_id);
stop_adv_fut.then(move |res| {
BluetoothFacade::cleanup_peripheral(bt_facade.clone());
match res {
Ok(r) => match to_value(r) {
Ok(val) => fready(Ok(val)),
Err(e) => fready(Err(e.into())),
},
Err(e) => fready(Err(e.into())),
}
})
}
// Synchronous wrapper for scanning
fn start_scan_async(
bt_facade: Arc<RwLock<BluetoothFacade>>, filter: Option<ScanFilter>, timeout: Option<u64>,
count: Option<u64>,
) -> impl Future<Output = Result<Value, Error>> {
let timeout_ms = (timeout.unwrap_or(DEFAULT_SCAN_TIMEOUT_MS) as i64).millis();
// Create scanning future and listen on central events for scan
let scan_fut = BluetoothFacade::start_scan(bt_facade.clone(), filter);
// Based on if a scan count is provided, either use TIMEOUT as termination criteria
// or custom future to terminate when <count> remote devices are discovered
let event_fut = if count.is_none() {
async::Timer::new(timeout_ms.after_now()).left_future()
} else {
// Future resolves when number of devices discovered is equal to count
let custom_fut =
BluetoothFacade::new_devices_found_future(bt_facade.clone(), count.unwrap());
// Chain the custom future with a timeout
custom_fut
.on_timeout(timeout_ms.after_now(), || ())
.right_future()
};
let fut = scan_fut.and_then(move |_| event_fut.map(Ok));
// Grab the central proxy created
let facade = bt_facade.clone();
let central = facade
.read()
.get_central_proxy()
.clone()
.expect("No central proxy.");
// After futures resolve, grab set of devices discovered and stop the scan
fut.then(move |_| {
let devices = bt_facade.read().get_devices();
if let Err(e) = central.stop_scan() {
fready(Err(e.into()))
} else {
match to_value(devices) {
Ok(dev) => fready(Ok(dev)),
Err(e) => fready(Err(e.into())),
}
}
})
}
fn connect_peripheral_async(
bt_facade: Arc<RwLock<BluetoothFacade>>, id: String,
) -> impl Future<Output = Result<Value, Error>> {
let connect_periph_fut = BluetoothFacade::connect_peripheral(bt_facade.clone(), id.clone());
let list_services_fut = connect_periph_fut
.and_then(move |_| BluetoothFacade::list_services(bt_facade.clone(), id.clone()));
list_services_fut.then(move |res| match res {
Ok(r) => {
let result = BleConnectPeripheralResponse::new(r);
match to_value(result) {
Ok(val) => fready(Ok(val)),
Err(e) => fready(Err(e.into())),
}
}
Err(e) => fready(Err(e.into())),
})
}
fn disconnect_peripheral_async(
bt_facade: Arc<RwLock<BluetoothFacade>>, id: String,
) -> impl Future<Output = Result<Value, Error>> {
let disconnect_periph_fut =
BluetoothFacade::disconnect_peripheral(bt_facade.clone(), id.clone());
disconnect_periph_fut.then(move |res| match res {
Ok(r) => match to_value(r) {
Ok(val) => fready(Ok(val)),
Err(e) => fready(Err(e.into())),
},
Err(e) => fready(Err(e.into())),
})
}
// Uses the same return type as connect_peripheral_async -- Returns subset of
// fidl::ServiceInfo
fn list_services_async(
bt_facade: Arc<RwLock<BluetoothFacade>>, id: String,
) -> impl Future<Output = Result<Value, Error>> {
let list_services_fut = BluetoothFacade::list_services(bt_facade.clone(), id.clone());
list_services_fut.then(move |res| match res {
Ok(r) => {
let result = BleConnectPeripheralResponse::new(r);
match to_value(result) {
Ok(val) => fready(Ok(val)),
Err(e) => fready(Err(e.into())),
}
}
Err(e) => fready(Err(e.into())),
})
}
fn publish_service_async(
bt_facade: Arc<RwLock<BluetoothFacade>>, service_info: ServiceInfo, local_service_id: String,
) -> impl Future<Output = Result<Value, Error>> {
let publish_fut =
BluetoothFacade::publish_service(bt_facade.clone(), service_info, local_service_id);
publish_fut.then(move |res| match res {
Ok(r) => match to_value(r) {
Ok(val) => fready(Ok(val)),
Err(e) => fready(Err(e.into())),
},
Err(e) => fready(Err(e.into())),
})
}