blob: 62a5515b291c45511cf48ac4aa69d17778eeb782 [file] [log] [blame]
// Copyright 2019 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, Error};
use fidl::prelude::*;
use fidl_fuchsia_bluetooth_gatt2::{
self as gatt, AttributePermissions, Characteristic, CharacteristicPropertyBits, Descriptor,
Handle, LocalServiceControlHandle, LocalServiceMarker, LocalServiceReadValueResponder,
LocalServiceRequest, LocalServiceRequestStream, LocalServiceWriteValueResponder,
SecurityRequirements, Server_Marker, Server_Proxy, ServiceHandle, ServiceInfo, ServiceKind,
ValueChangedParameters,
};
use fuchsia_async as fasync;
use fuchsia_bluetooth::types::{PeerId, Uuid};
use fuchsia_component as app;
use fuchsia_sync::RwLock;
use futures::stream::TryStreamExt;
use serde_json::value::Value;
use std::{collections::HashMap, str::FromStr};
use tracing::{error, info, warn};
use crate::bluetooth::constants::{
CHARACTERISTIC_EXTENDED_PROPERTIES_UUID, GATT_MAX_ATTRIBUTE_VALUE_LENGTH,
PERMISSION_READ_ENCRYPTED, PERMISSION_READ_ENCRYPTED_MITM, PERMISSION_WRITE_ENCRYPTED,
PERMISSION_WRITE_ENCRYPTED_MITM, PERMISSION_WRITE_SIGNED, PERMISSION_WRITE_SIGNED_MITM,
PROPERTY_INDICATE, PROPERTY_NOTIFY, PROPERTY_READ, PROPERTY_WRITE,
};
#[derive(Debug)]
struct Counter {
count: u64,
}
impl Counter {
pub fn new() -> Counter {
Counter { count: 0 }
}
fn next(&mut self) -> u64 {
let id: u64 = self.count;
self.count += 1;
id
}
}
#[derive(Debug)]
struct InnerGattServerFacade {
/// attribute_value_mapping: A Hashmap that will be used for capturing
/// and updating Characteristic and Descriptor values for each service.
/// The bool value represents whether value size of the initial Characteristic
/// Descriptor value should be enforced or not for prepared writes. True
/// for enforce, false to allow the size to grow to max values.
attribute_value_mapping: HashMap<u64, (Vec<u8>, bool)>,
/// A generic counter GATT server attributes
generic_id_counter: Counter,
/// The current Gatt Server Proxy
server_proxy: Option<Server_Proxy>,
/// List of active LocalService server tasks.
service_tasks: Vec<fasync::Task<()>>,
}
/// Perform Gatt Server operations.
///
/// Note this object is shared among all threads created by server.
///
#[derive(Debug)]
pub struct GattServerFacade {
inner: RwLock<InnerGattServerFacade>,
}
impl GattServerFacade {
pub fn new() -> GattServerFacade {
GattServerFacade {
inner: RwLock::new(InnerGattServerFacade {
attribute_value_mapping: HashMap::new(),
generic_id_counter: Counter::new(),
server_proxy: None,
service_tasks: vec![],
}),
}
}
fn create_server_proxy(&self) -> Result<Server_Proxy, Error> {
let tag = "GattServerFacade::create_server_proxy:";
match self.inner.read().server_proxy.clone() {
Some(service) => {
info!(
tag = &[tag, &line!().to_string()].join("").as_str(),
"Current service proxy: {:?}", service
);
Ok(service)
}
None => {
info!(
tag = &[tag, &line!().to_string()].join("").as_str(),
"Setting new server proxy"
);
let service = app::client::connect_to_protocol::<Server_Marker>();
if let Err(err) = service {
error!(
tag = &[tag, &line!().to_string()].join("").as_str(),
?err,
"Failed to create server proxy"
);
return Err(format_err!("Failed to create server proxy: {:?}", err));
}
service
}
}
}
/// Function to take the input attribute value and parse it to
/// a byte array. Types can be Strings, u8, or generic Array.
fn parse_attribute_value_to_byte_array(&self, value_to_parse: &Value) -> Vec<u8> {
match value_to_parse {
Value::String(obj) => String::from(obj.as_str()).into_bytes(),
Value::Number(obj) => match obj.as_u64() {
Some(num) => vec![num as u8],
None => vec![],
},
Value::Array(obj) => {
obj.into_iter().filter_map(|v| v.as_u64()).map(|v| v as u8).collect()
}
_ => vec![],
}
}
fn on_characteristic_configuration(
peer_id: PeerId,
handle: Handle,
notify: bool,
indicate: bool,
control_handle: &LocalServiceControlHandle,
) {
let tag = "GattServerFacade::on_characteristic_configuration:";
info!(
tag = &[tag, &line!().to_string()].join("").as_str(),
%notify,
%indicate,
id = %peer_id,
"OnCharacteristicConfiguration"
);
if indicate {
let value = ValueChangedParameters {
handle: Some(handle),
value: Some(vec![0x02, 0x00]),
peer_ids: Some(vec![peer_id.into()]),
..Default::default()
};
// Ignore the confirmation.
let (confirmation, _) = fidl::EventPair::create();
let _ = control_handle.send_on_indicate_value(&value, confirmation);
} else if notify {
let value = ValueChangedParameters {
handle: Some(handle),
value: Some(vec![0x01, 0x00]),
peer_ids: Some(vec![peer_id.into()]),
..Default::default()
};
let _ = control_handle.send_on_notify_value(&value);
}
}
fn on_read_value(
peer_id: PeerId,
handle: Handle,
offset: i32,
responder: LocalServiceReadValueResponder,
value_in_mapping: Option<&(Vec<u8>, bool)>,
) {
let tag = "GattServerFacade::on_read_value:";
info!(
tag = &[tag, &line!().to_string()].join("").as_str(),
at_id = ?handle.value,
offset = ?offset,
id = %peer_id,
"OnReadValue request",
);
match value_in_mapping {
Some(v) => {
let (value, _enforce_initial_attribute_length) = v;
if value.len() < offset as usize {
let _result = responder.send(Err(gatt::Error::InvalidOffset));
} else {
let _result = responder.send(Ok(&value[offset as usize..]));
}
}
None => {
// ID doesn't exist in the database
let _result = responder.send(Err(gatt::Error::ReadNotPermitted));
}
};
}
fn write_and_extend(value: &mut Vec<u8>, value_to_write: Vec<u8>, offset: usize) {
let split_idx = (value.len() - offset).min(value_to_write.len());
let (overlapping, extending) = value_to_write.split_at(split_idx);
let end_of_overlap = offset + overlapping.len();
value.splice(offset..end_of_overlap, overlapping.iter().cloned());
value.extend_from_slice(extending);
}
fn on_write_value(
peer_id: PeerId,
handle: Handle,
offset: u32,
value_to_write: Vec<u8>,
responder: LocalServiceWriteValueResponder,
value_in_mapping: Option<&mut (Vec<u8>, bool)>,
) {
let tag = "GattServerFacade::on_write_value:";
info!(
tag = &[tag, &line!().to_string()].join("").as_str(),
at_id = handle.value,
offset = offset,
value = ?value_to_write,
id = %peer_id,
"OnWriteValue request",
);
match value_in_mapping {
Some(v) => {
let (value, enforce_initial_attribute_length) = v;
let max_attribute_size: usize = match enforce_initial_attribute_length {
true => value.len(),
false => GATT_MAX_ATTRIBUTE_VALUE_LENGTH,
};
if max_attribute_size < (value_to_write.len() + offset as usize) {
let _result = responder.send(Err(gatt::Error::InvalidAttributeValueLength));
} else if value.len() < offset as usize {
let _result = responder.send(Err(gatt::Error::InvalidOffset));
} else {
GattServerFacade::write_and_extend(value, value_to_write, offset as usize);
let _result = responder.send(Ok(()));
}
}
None => {
// ID doesn't exist in the database
let _result = responder.send(Err(gatt::Error::WriteNotPermitted));
}
}
}
async fn monitor_service_request_stream(
stream: LocalServiceRequestStream,
control_handle: LocalServiceControlHandle,
mut attribute_value_mapping: HashMap<u64, (Vec<u8>, bool)>,
) -> Result<(), Error> {
stream
.map_ok(move |request| match request {
LocalServiceRequest::CharacteristicConfiguration {
peer_id,
handle,
notify,
indicate,
responder,
} => {
GattServerFacade::on_characteristic_configuration(
peer_id.into(),
handle,
notify,
indicate,
&control_handle,
);
let _ = responder.send();
}
LocalServiceRequest::ReadValue { peer_id, handle, offset, responder } => {
GattServerFacade::on_read_value(
peer_id.into(),
handle,
offset,
responder,
attribute_value_mapping.get(&handle.value),
);
}
LocalServiceRequest::WriteValue { payload, responder } => {
GattServerFacade::on_write_value(
payload.peer_id.unwrap().into(),
payload.handle.unwrap(),
payload.offset.unwrap(),
payload.value.unwrap(),
responder,
attribute_value_mapping.get_mut(&payload.handle.unwrap().value),
);
}
LocalServiceRequest::PeerUpdate { payload: _, responder } => {
responder.drop_without_shutdown();
}
LocalServiceRequest::ValueChangedCredit { .. } => {}
})
.try_collect::<()>()
.await
.map_err(|e| e.into())
}
/// Convert a number representing permissions into AttributePermissions.
///
/// Fuchsia GATT Server uses a u32 as a property value and an AttributePermissions
/// object to represent Characteristic and Descriptor permissions. In order to
/// simplify the incoming json object the incoming permission value will be
/// treated as a u32 and converted into the proper AttributePermission object.
///
/// The incoming permissions number is represented by adding the numbers representing
/// the permission level.
/// Values:
/// 0x001 - Allow read permission
/// 0x002 - Allow encrypted read operations
/// 0x004 - Allow reading with man-in-the-middle protection
/// 0x010 - Allow write permission
/// 0x020 - Allow encrypted writes
/// 0x040 - Allow writing with man-in-the-middle protection
/// 0x080 - Allow signed writes
/// 0x100 - Allow signed write perations with man-in-the-middle protection
///
/// Example input that allows read and write: 0x01 | 0x10 = 0x11
/// This function will convert this to the proper AttributePermission permissions.
fn permissions_and_properties_from_raw_num(
&self,
permissions: u32,
properties: u32,
) -> AttributePermissions {
let mut read_encryption_required = false;
let mut read_authentication_required = false;
let mut read_authorization_required = false;
let mut write_encryption_required = false;
let mut write_authentication_required = false;
let mut write_authorization_required = false;
let mut update_encryption_required = false;
let mut update_authentication_required = false;
let mut update_authorization_required = false;
if permissions & PERMISSION_READ_ENCRYPTED != 0 {
read_encryption_required = true;
read_authentication_required = true;
read_authorization_required = true;
}
if permissions & PERMISSION_READ_ENCRYPTED_MITM != 0 {
read_encryption_required = true;
update_encryption_required = true;
}
if permissions & PERMISSION_WRITE_ENCRYPTED != 0 {
write_encryption_required = true;
update_encryption_required = true;
}
if permissions & PERMISSION_WRITE_ENCRYPTED_MITM != 0 {
write_encryption_required = true;
update_encryption_required = true;
update_authentication_required = true;
update_authorization_required = true;
}
if permissions & PERMISSION_WRITE_SIGNED != 0 {
write_authorization_required = true;
}
if permissions & PERMISSION_WRITE_SIGNED_MITM != 0 {
write_encryption_required = true;
write_authentication_required = true;
write_authorization_required = true;
update_encryption_required = true;
update_authentication_required = true;
update_authorization_required = true;
}
// Update Security Requirements only required if notify or indicate
// properties set.
let update_sec_requirement = if properties & (PROPERTY_NOTIFY | PROPERTY_INDICATE) != 0 {
Some(SecurityRequirements {
encryption_required: Some(update_encryption_required),
authentication_required: Some(update_authentication_required),
authorization_required: Some(update_authorization_required),
..Default::default()
})
} else {
None
};
let read_sec_requirement = if properties & PROPERTY_READ != 0 {
Some(SecurityRequirements {
encryption_required: Some(read_encryption_required),
authentication_required: Some(read_authentication_required),
authorization_required: Some(read_authorization_required),
..Default::default()
})
} else {
None
};
let write_sec_requirement = if properties & PROPERTY_WRITE != 0 {
Some(SecurityRequirements {
encryption_required: Some(write_encryption_required),
authentication_required: Some(write_authentication_required),
authorization_required: Some(write_authorization_required),
..Default::default()
})
} else {
None
};
AttributePermissions {
read: read_sec_requirement,
write: write_sec_requirement,
update: update_sec_requirement,
..Default::default()
}
}
/// Converts `descriptor_list_json` to FIDL descriptors and filters out descriptors banned by
/// the Server FIDL API. The Characteristic Extended Properties descriptor is one such banned
/// descriptor, and its value will be returned.
///
/// Returns a tuple of (filtered FIDL descriptors, extended property bits)
fn process_descriptors(
&self,
descriptor_list_json: &Value,
) -> Result<(Vec<Descriptor>, CharacteristicPropertyBits), Error> {
let mut descriptors: Vec<Descriptor> = Vec::new();
// Fuchsia will automatically setup these descriptors and manage them.
// Skip setting them up if found in the input descriptor list.
let banned_descriptor_uuids = [
Uuid::from_str("00002900-0000-1000-8000-00805f9b34fb").unwrap(), // CCC Descriptor
Uuid::from_str("00002902-0000-1000-8000-00805f9b34fb").unwrap(), // Client Configuration Descriptor
Uuid::from_str("00002903-0000-1000-8000-00805f9b34fb").unwrap(), // Server Configuration Descriptor
];
if descriptor_list_json.is_null() {
return Ok((descriptors, CharacteristicPropertyBits::empty()));
}
let descriptor_list = descriptor_list_json
.as_array()
.ok_or(format_err!("Attribute 'descriptors' is not a parseable list."))?;
let mut ext_property_bits = CharacteristicPropertyBits::empty();
for descriptor in descriptor_list.into_iter() {
let descriptor_uuid: Uuid = match descriptor["uuid"].as_str() {
Some(uuid_str) => Uuid::from_str(uuid_str)
.map_err(|_| format_err!("Descriptor uuid is invalid"))?,
None => return Err(format_err!("Descriptor uuid was unable to cast to str.")),
};
let descriptor_value = self.parse_attribute_value_to_byte_array(&descriptor["value"]);
// Intercept the Extended Properties descriptor.
if descriptor_uuid == Uuid::new16(CHARACTERISTIC_EXTENDED_PROPERTIES_UUID) {
if descriptor_value.is_empty() {
warn!("Extended properties descriptor has empty value. Ignoring.");
continue;
}
// The second byte in CharacteristicPropertyBits is for extended property bits.
let ext_bits_raw: u16 = (descriptor_value[0] as u16) << u8::BITS;
ext_property_bits = CharacteristicPropertyBits::from_bits_truncate(ext_bits_raw);
continue;
}
let raw_enforce_enforce_initial_attribute_length =
descriptor["enforce_initial_attribute_length"].as_bool().unwrap_or(false);
// No properties for descriptors.
let properties = 0u32;
if banned_descriptor_uuids.contains(&descriptor_uuid) {
continue;
}
let raw_descriptor_permissions = match descriptor["permissions"].as_u64() {
Some(permissions) => permissions as u32,
None => {
return Err(format_err!("Descriptor permissions was unable to cast to u64."))
}
};
let desc_permission_attributes = self
.permissions_and_properties_from_raw_num(raw_descriptor_permissions, properties);
let descriptor_id = self.inner.write().generic_id_counter.next();
self.inner.write().attribute_value_mapping.insert(
descriptor_id,
(descriptor_value, raw_enforce_enforce_initial_attribute_length),
);
let fidl_descriptor = Descriptor {
handle: Some(Handle { value: descriptor_id }),
type_: Some(descriptor_uuid.into()),
permissions: Some(desc_permission_attributes),
..Default::default()
};
descriptors.push(fidl_descriptor);
}
Ok((descriptors, ext_property_bits))
}
fn generate_characteristics(
&self,
characteristic_list_json: &Value,
) -> Result<Vec<Characteristic>, Error> {
let mut characteristics: Vec<Characteristic> = Vec::new();
if characteristic_list_json.is_null() {
return Ok(characteristics);
}
let characteristic_list = match characteristic_list_json.as_array() {
Some(c) => c,
None => {
return Err(format_err!("Attribute 'characteristics' is not a parseable list."))
}
};
for characteristic in characteristic_list.into_iter() {
let characteristic_uuid = match characteristic["uuid"].as_str() {
Some(uuid_str) => Uuid::from_str(uuid_str)
.map_err(|_| format_err!("Invalid characteristic uuid: {}", uuid_str))?,
None => return Err(format_err!("Characteristic uuid was unable to cast to str.")),
};
let characteristic_properties = match characteristic["properties"].as_u64() {
Some(properties) => properties as u32,
None => {
return Err(format_err!("Characteristic properties was unable to cast to u64."))
}
};
let raw_characteristic_permissions = match characteristic["permissions"].as_u64() {
Some(permissions) => permissions as u32,
None => {
return Err(format_err!(
"Characteristic permissions was unable to cast to u64."
))
}
};
let characteristic_value =
self.parse_attribute_value_to_byte_array(&characteristic["value"]);
let raw_enforce_enforce_initial_attribute_length =
characteristic["enforce_initial_attribute_length"].as_bool().unwrap_or(false);
let descriptor_list = &characteristic["descriptors"];
let (fidl_descriptors, ext_properties_bits) =
self.process_descriptors(descriptor_list)?;
let characteristic_permissions = self.permissions_and_properties_from_raw_num(
raw_characteristic_permissions,
characteristic_properties,
);
// Properties map directly to CharacteristicPropertyBits except for
// property_extended_props (0x80), so we truncate. The extended properties descriptor is
// intercepted and added to the property bits (the Bluetooth stack will add the
// descriptor later).
let characteristic_properties =
CharacteristicPropertyBits::from_bits_truncate(characteristic_properties as u16)
| ext_properties_bits;
let characteristic_id = self.inner.write().generic_id_counter.next();
self.inner.write().attribute_value_mapping.insert(
characteristic_id,
(characteristic_value, raw_enforce_enforce_initial_attribute_length),
);
let fidl_characteristic = Characteristic {
handle: Some(Handle { value: characteristic_id }),
type_: Some(characteristic_uuid.into()),
properties: Some(characteristic_properties),
permissions: Some(characteristic_permissions),
descriptors: Some(fidl_descriptors),
..Default::default()
};
characteristics.push(fidl_characteristic);
}
Ok(characteristics)
}
fn generate_service(&self, service_json: &Value) -> Result<ServiceInfo, Error> {
// Determine if the service is primary or not.
let service_id = self.inner.write().generic_id_counter.next();
let service_kind =
match service_json["type"].as_i64().ok_or(format_err!("Invalid service type"))? {
0 => ServiceKind::Primary,
1 => ServiceKind::Secondary,
_ => return Err(format_err!("Invalid Service type")),
};
// Get the service UUID.
let service_uuid_str = service_json["uuid"]
.as_str()
.ok_or(format_err!("Service uuid was unable to cast to str"))?;
let service_uuid =
Uuid::from_str(service_uuid_str).map_err(|_| format_err!("Invalid service uuid"))?;
//Get the Characteristics from the service.
let characteristics = self.generate_characteristics(&service_json["characteristics"])?;
Ok(ServiceInfo {
handle: Some(ServiceHandle { value: service_id }),
kind: Some(service_kind),
type_: Some(service_uuid.into()),
characteristics: Some(characteristics),
..Default::default()
})
}
async fn publish_service(
&self,
service_info: ServiceInfo,
service_uuid: String,
) -> Result<(), Error> {
let tag = "GattServerFacade::publish_service:";
let (service_client, service_server) =
fidl::endpoints::create_endpoints::<LocalServiceMarker>();
let (service_request_stream, service_control_handle) =
service_server.into_stream_and_control_handle()?;
let server_proxy = self
.inner
.read()
.server_proxy
.as_ref()
.ok_or(format_err!("No Server Proxy created."))?
.clone();
match server_proxy.publish_service(&service_info, service_client).await? {
Ok(()) => info!(
tag = &[tag, &line!().to_string()].join("").as_str(),
uuid = ?service_uuid,
"Successfully published GATT service",
),
Err(e) => return Err(format_err!("PublishService error: {:?}", e)),
}
let monitor_delegate_fut = GattServerFacade::monitor_service_request_stream(
service_request_stream,
service_control_handle,
self.inner.read().attribute_value_mapping.clone(),
);
let fut = async {
let result = monitor_delegate_fut.await;
if let Err(err) = result {
error!(
tag = "publish_service",
?err,
"Failed to create or monitor the gatt service delegate"
);
}
};
self.inner.write().service_tasks.push(fasync::Task::spawn(fut));
Ok(())
}
/// Publish a GATT Server.
///
/// The input is a JSON object representing the attributes of the GATT
/// server Database to setup. This function will also start listening for
/// incoming requests to Characteristics and Descriptors in each Service.
///
/// This is primarially using the same input syntax as in the Android AOSP
/// ACTS test framework at:
/// <aosp_root>/tools/test/connectivity/acts/framework/acts/test_utils/bt/gatt_test_database.py
///
/// A "database" key wraps the database at:
/// <aosp root>/tools/test/connectivity/acts/framework/acts/controllers/fuchsia_lib/bt/gatts_lib.py
///
/// Example python dictionary that's turned into JSON (sub dic values can be found
/// in <aosp_root>/tools/test/connectivity/acts/framework/acts/test_utils/bt/bt_constants.py:
///
/// SMALL_DATABASE = {
/// 'services': [{
/// 'uuid': '00001800-0000-1000-8000-00805f9b34fb',
/// 'type': gatt_service_types['primary'],
/// 'characteristics': [{
/// 'uuid': gatt_char_types['device_name'],
/// 'properties': gatt_characteristic['property_read'],
/// 'permissions': gatt_characteristic['permission_read'],
/// 'handle': 0x0003,
/// 'value_type': gatt_characteristic_value_format['string'],
/// 'value': 'Test Database'
/// }, {
/// 'uuid': gatt_char_types['appearance'],
/// 'properties': gatt_characteristic['property_read'],
/// 'permissions': gatt_characteristic['permission_read'],
/// 'handle': 0x0005,
/// 'value_type': gatt_characteristic_value_format['sint32'],
/// 'offset': 0,
/// 'value': 17
/// }, {
/// 'uuid': gatt_char_types['peripheral_pref_conn'],
/// 'properties': gatt_characteristic['property_read'],
/// 'permissions': gatt_characteristic['permission_read'],
/// 'handle': 0x0007
/// }]
/// }, {
/// 'uuid': '00001801-0000-1000-8000-00805f9b34fb',
/// 'type': gatt_service_types['primary'],
/// 'characteristics': [{
/// 'uuid': gatt_char_types['service_changed'],
/// 'properties': gatt_characteristic['property_indicate'],
/// 'permissions': gatt_characteristic['permission_read'] |
/// gatt_characteristic['permission_write'],
/// 'handle': 0x0012,
/// 'value_type': gatt_characteristic_value_format['byte'],
/// 'value': [0x0000],
/// 'descriptors': [{
/// 'uuid': gatt_char_desc_uuids['client_char_cfg'],
/// 'permissions': gatt_descriptor['permission_read'] |
/// gatt_descriptor['permission_write'],
/// 'value': [0x0000]
/// }]
/// }, {
/// 'uuid': '0000b004-0000-1000-8000-00805f9b34fb',
/// 'properties': gatt_characteristic['property_read'],
/// 'permissions': gatt_characteristic['permission_read'],
/// 'handle': 0x0015,
/// 'value_type': gatt_characteristic_value_format['byte'],
/// 'value': [0x04]
/// }]
/// }]
/// }
pub async fn publish_server(&self, args: Value) -> Result<(), Error> {
let tag = "GattServerFacade::publish_server:";
info!(tag = &[tag, &line!().to_string()].join("").as_str(), "Publishing service");
let server_proxy = self.create_server_proxy()?;
self.inner.write().server_proxy = Some(server_proxy);
let services = args
.get("database")
.ok_or(format_err!("Could not find the 'database' key in the json database."))?
.get("services")
.ok_or(format_err!("Could not find the 'services' key in the json database."))?;
let service_list = match services.as_array() {
Some(s) => s,
None => return Err(format_err!("Attribute 'service' is not a parseable list.")),
};
for service in service_list.into_iter() {
self.inner.write().attribute_value_mapping.clear();
let service_info = self.generate_service(service)?;
let service_uuid = &service["uuid"];
self.publish_service(service_info, service_uuid.to_string()).await?;
}
Ok(())
}
pub async fn close_server(&self) {
self.inner.write().server_proxy = None;
let _ = self.inner.write().service_tasks.drain(..).collect::<Vec<fasync::Task<()>>>();
}
// GattServerFacade for cleaning up objects in use.
pub fn cleanup(&self) {
let tag = "GattServerFacade::cleanup:";
info!(tag = &[tag, &line!().to_string()].join("").as_str(), "Cleanup GATT server objects");
self.inner.write().server_proxy = None;
let _ = self.inner.write().service_tasks.drain(..).collect::<Vec<fasync::Task<()>>>();
}
// GattServerFacade for printing useful information pertaining to the facade for
// debug purposes.
pub fn print(&self) {
let tag = "GattServerFacade::print:";
info!(tag = &[tag, &line!().to_string()].join("").as_str(), "Unimplemented print function");
}
}