| // 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 failure::{bail, Error}; |
| use fidl_fuchsia_bluetooth_gatt::{ |
| self as gatt, AttributePermissions, Characteristic, Descriptor, |
| LocalServiceDelegateControlHandle, LocalServiceDelegateMarker, |
| LocalServiceDelegateOnReadValueResponder, LocalServiceDelegateOnWriteValueResponder, |
| LocalServiceDelegateRequestStream, LocalServiceMarker, LocalServiceProxy, SecurityRequirements, |
| Server_Marker, Server_Proxy, ServiceInfo, |
| }; |
| use fuchsia_async as fasync; |
| use fuchsia_bluetooth::error::Error as BTError; |
| use fuchsia_component as app; |
| use fuchsia_syslog::{self, fx_log_err, fx_log_info}; |
| use fuchsia_zircon as zx; |
| use futures::stream::TryStreamExt; |
| use parking_lot::RwLock; |
| use serde_json::value::Value; |
| use std::collections::HashMap; |
| |
| use crate::bluetooth::constants::{ |
| 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, |
| }; |
| |
| #[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>, |
| |
| /// service_proxies: List of LocalServiceProxy objects in use |
| service_proxies: Vec<LocalServiceProxy>, |
| } |
| |
| /// 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_proxies: Vec::new(), |
| }), |
| } |
| } |
| |
| pub fn create_server_proxy(&self) -> Result<Server_Proxy, Error> { |
| let tag = "GattServerFacade::create_server_proxy:"; |
| match self.inner.read().server_proxy.clone() { |
| Some(service) => { |
| fx_log_info!(tag: &[tag, &line!().to_string()].join(""), "Current service proxy: {:?}", service); |
| Ok(service) |
| } |
| None => { |
| fx_log_info!(tag: &[tag, &line!().to_string()].join(""), "Setting new server proxy"); |
| let service = app::client::connect_to_service::<Server_Marker>(); |
| if let Err(err) = service { |
| fx_log_err!(tag: &[tag, &line!().to_string()].join(""), "Failed to create server proxy: {:?}", err); |
| bail!("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. |
| pub 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![], |
| } |
| } |
| |
| pub fn on_characteristic_configuration( |
| peer_id: String, |
| notify: bool, |
| indicate: bool, |
| characteristic_id: u64, |
| control_handle: LocalServiceDelegateControlHandle, |
| service_proxy: &LocalServiceProxy, |
| ) { |
| let tag = "GattServerFacade::on_characteristic_configuration:"; |
| fx_log_info!( |
| tag: &[tag, &line!().to_string()].join(""), |
| "OnCharacteristicConfiguration: (notify: {}, indicate: {}, id: {})", |
| notify, |
| indicate, |
| peer_id |
| ); |
| control_handle.shutdown(); |
| let value = if indicate { |
| [0x02, 0x00].to_vec() |
| } else if notify { |
| [0x01, 0x00].to_vec() |
| } else { |
| [0x00, 0x00].to_vec() |
| }; |
| let confirm = true; |
| let _result = service_proxy.notify_value( |
| characteristic_id, |
| &peer_id, |
| &mut value.to_vec().into_iter(), |
| confirm, |
| ); |
| } |
| |
| pub fn on_read_value( |
| id: u64, |
| offset: i32, |
| responder: LocalServiceDelegateOnReadValueResponder, |
| value_in_mapping: Option<&(Vec<u8>, bool)>, |
| ) { |
| let tag = "GattServerFacade::on_read_value:"; |
| fx_log_info!( |
| tag: &[tag, &line!().to_string()].join(""), |
| "OnReadValue request at id: {:?}, with offset: {:?}", |
| id, |
| offset |
| ); |
| match value_in_mapping { |
| Some(v) => { |
| let (value, _enforce_initial_attribute_length) = v; |
| if value.len() < offset as usize { |
| let _result = responder.send(None, gatt::ErrorCode::InvalidOffset); |
| } else { |
| let value_to_write = value.clone().split_off(offset as usize); |
| let _result = responder.send( |
| Some(&mut value_to_write.to_vec().into_iter()), |
| gatt::ErrorCode::NoError, |
| ); |
| } |
| } |
| None => { |
| // ID doesn't exist in the database |
| let _result = responder.send(None, gatt::ErrorCode::NotPermitted); |
| } |
| }; |
| } |
| |
| pub 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); |
| } |
| |
| pub fn on_write_value( |
| id: u64, |
| offset: u16, |
| value_to_write: Vec<u8>, |
| responder: LocalServiceDelegateOnWriteValueResponder, |
| value_in_mapping: Option<&mut (Vec<u8>, bool)>, |
| ) { |
| let tag = "GattServerFacade::on_write_value:"; |
| fx_log_info!( |
| tag: &[tag, &line!().to_string()].join(""), |
| "OnWriteValue request at id: {:?}, with offset: {:?}, with value: {:?}", |
| id, |
| offset, |
| value_to_write |
| ); |
| |
| 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(gatt::ErrorCode::InvalidValueLength); |
| } else if value.len() < offset as usize { |
| let _result = responder.send(gatt::ErrorCode::InvalidOffset); |
| } else { |
| &mut GattServerFacade::write_and_extend(value, value_to_write, offset as usize); |
| let _result = responder.send(gatt::ErrorCode::NoError); |
| } |
| } |
| None => { |
| // ID doesn't exist in the database |
| let _result = responder.send(gatt::ErrorCode::NotPermitted); |
| } |
| } |
| } |
| |
| pub fn on_write_without_response( |
| id: u64, |
| offset: u16, |
| value_to_write: Vec<u8>, |
| value_in_mapping: Option<&mut (Vec<u8>, bool)>, |
| ) { |
| let tag = "GattServerFacade::on_write_without_response:"; |
| fx_log_info!( |
| tag: &[tag, &line!().to_string()].join(""), |
| "OnWriteWithoutResponse request at id: {:?}, with offset: {:?}, with value: {:?}", |
| id, |
| offset, |
| value_to_write |
| ); |
| if let Some(v) = value_in_mapping { |
| let (value, _enforce_initial_attribute_length) = v; |
| &mut GattServerFacade::write_and_extend(value, value_to_write, offset as usize); |
| } |
| } |
| |
| pub async fn monitor_delegate_request_stream( |
| stream: LocalServiceDelegateRequestStream, |
| mut attribute_value_mapping: HashMap<u64, (Vec<u8>, bool)>, |
| service_proxy: LocalServiceProxy, |
| ) -> Result<(), Error> { |
| use fidl_fuchsia_bluetooth_gatt::LocalServiceDelegateRequest::*; |
| await!(stream |
| .map_ok(move |request| match request { |
| OnCharacteristicConfiguration { |
| peer_id, |
| notify, |
| indicate, |
| characteristic_id, |
| control_handle, |
| } => { |
| GattServerFacade::on_characteristic_configuration( |
| peer_id, |
| notify, |
| indicate, |
| characteristic_id, |
| control_handle, |
| &service_proxy, |
| ); |
| } |
| OnReadValue { id, offset, responder } => { |
| GattServerFacade::on_read_value( |
| id, |
| offset, |
| responder, |
| attribute_value_mapping.get(&id), |
| ); |
| } |
| OnWriteValue { id, offset, value, responder } => { |
| GattServerFacade::on_write_value( |
| id, |
| offset, |
| value, |
| responder, |
| attribute_value_mapping.get_mut(&id), |
| ); |
| } |
| OnWriteWithoutResponse { id, offset, value, .. } => { |
| GattServerFacade::on_write_without_response( |
| id, |
| offset, |
| value, |
| attribute_value_mapping.get_mut(&id), |
| ); |
| } |
| }) |
| .try_collect::<()>()) |
| .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 incomming 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. |
| pub 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; |
| } |
| |
| if permissions & PERMISSION_READ_ENCRYPTED_MITM != 0 { |
| read_encryption_required = true; |
| read_authentication_required = true; |
| read_authorization_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; |
| write_authentication_required = true; |
| write_authorization_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(Box::new(SecurityRequirements { |
| encryption_required: update_encryption_required, |
| authentication_required: update_authentication_required, |
| authorization_required: update_authorization_required, |
| })) |
| } else { |
| None |
| }; |
| |
| let read_sec_requirement = SecurityRequirements { |
| encryption_required: read_encryption_required, |
| authentication_required: read_authentication_required, |
| authorization_required: read_authorization_required, |
| }; |
| |
| let write_sec_requirement = SecurityRequirements { |
| encryption_required: write_encryption_required, |
| authentication_required: write_authentication_required, |
| authorization_required: write_authorization_required, |
| }; |
| |
| AttributePermissions { |
| read: Some(Box::new(read_sec_requirement)), |
| write: Some(Box::new(write_sec_requirement)), |
| update: update_sec_requirement, |
| } |
| } |
| |
| pub fn generate_descriptors( |
| &self, |
| descriptor_list_json: &Value, |
| ) -> Result<Vec<Descriptor>, 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 = [ |
| "00002900-0000-1000-8000-00805f9b34fb".to_string(), // CCC Descriptor |
| "00002902-0000-1000-8000-00805f9b34fb".to_string(), // Client Configuration Descriptor |
| "00002903-0000-1000-8000-00805f9b34fb".to_string(), // Server Configuration Descriptor |
| ]; |
| |
| if descriptor_list_json.is_null() { |
| return Ok(descriptors); |
| } |
| |
| let descriptor_list = match descriptor_list_json.as_array() { |
| Some(d) => d, |
| None => bail!("Attribute 'descriptors' is not a parseable list."), |
| }; |
| |
| for descriptor in descriptor_list.into_iter() { |
| let descriptor_uuid = match descriptor["uuid"].as_str() { |
| Some(uuid) => uuid.to_string(), |
| None => bail!("Descriptor uuid was unable to cast to str."), |
| }; |
| let descriptor_value = self.parse_attribute_value_to_byte_array(&descriptor["value"]); |
| |
| 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 => bail!("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 descriptor_obj = Descriptor { |
| id: descriptor_id, |
| type_: descriptor_uuid.clone(), |
| permissions: Some(Box::new(desc_permission_attributes)), |
| }; |
| |
| descriptors.push(descriptor_obj); |
| } |
| Ok(descriptors) |
| } |
| |
| pub 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 => bail!("Attribute 'characteristics' is not a parseable list."), |
| }; |
| |
| for characteristic in characteristic_list.into_iter() { |
| let characteristic_uuid = match characteristic["uuid"].as_str() { |
| Some(uuid) => uuid.to_string(), |
| None => bail!("Characteristic uuid was unable to cast to str."), |
| }; |
| |
| let characteristic_properties = match characteristic["properties"].as_u64() { |
| Some(properties) => properties as u32, |
| None => bail!("Characteristic properties was unable to cast to u64."), |
| }; |
| |
| let raw_characteristic_permissions = match characteristic["permissions"].as_u64() { |
| Some(permissions) => permissions as u32, |
| None => bail!("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 descriptors = self.generate_descriptors(descriptor_list)?; |
| |
| let characteristic_permissions = self.permissions_and_properties_from_raw_num( |
| raw_characteristic_permissions, |
| characteristic_properties, |
| ); |
| |
| 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 characteristic_obj = Characteristic { |
| id: characteristic_id, |
| type_: characteristic_uuid, |
| properties: characteristic_properties, |
| permissions: Some(Box::new(characteristic_permissions)), |
| descriptors: Some(descriptors), |
| }; |
| |
| characteristics.push(characteristic_obj); |
| } |
| Ok(characteristics) |
| } |
| |
| pub 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_type = &service_json["type"]; |
| let is_service_primary = match service_type.as_i64() { |
| Some(val) => match val { |
| 0 => true, |
| 1 => false, |
| _ => bail!("Invalid Service type. Expected 0 or 1."), |
| }, |
| None => bail!("Service type was unable to cast to i64."), |
| }; |
| |
| // Get the service UUID. |
| let service_uuid = match service_json["uuid"].as_str() { |
| Some(s) => s, |
| None => bail!("Service uuid was unable to cast to str."), |
| }; |
| |
| //Get the Characteristics from the service. |
| let characteristics = self.generate_characteristics(&service_json["characteristics"])?; |
| |
| // Includes: TBD |
| let includes = None; |
| |
| Ok(ServiceInfo { |
| id: service_id, |
| primary: is_service_primary, |
| type_: service_uuid.to_string(), |
| characteristics: Some(characteristics), |
| includes: includes, |
| }) |
| } |
| |
| pub async fn publish_service( |
| &self, |
| mut service_info: ServiceInfo, |
| service_uuid: String, |
| ) -> Result<(), Error> { |
| let tag = "GattServerFacade::publish_service:"; |
| let (service_local, service_remote) = zx::Channel::create()?; |
| let service_local = fasync::Channel::from_channel(service_local)?; |
| let service_proxy = LocalServiceProxy::new(service_local); |
| |
| let (delegate_client, delegate_request_stream) = |
| fidl::endpoints::create_request_stream::<LocalServiceDelegateMarker>()?; |
| |
| let service_server = fidl::endpoints::ServerEnd::<LocalServiceMarker>::new(service_remote); |
| |
| self.inner.write().service_proxies.push(service_proxy.clone()); |
| |
| match &self.inner.read().server_proxy { |
| Some(server) => { |
| let status = await!(server.publish_service( |
| &mut service_info, |
| delegate_client, |
| service_server |
| ))?; |
| match status.error { |
| None => fx_log_info!( tag: &[tag, &line!().to_string()].join(""), |
| "Successfully published GATT service with uuid {:?}", |
| service_uuid |
| ), |
| Some(e) => bail!("Failed to create GATT Service: {}", BTError::from(*e)), |
| } |
| } |
| None => bail!("No Server Proxy created."), |
| } |
| let monitor_delegate_fut = GattServerFacade::monitor_delegate_request_stream( |
| delegate_request_stream, |
| self.inner.read().attribute_value_mapping.clone(), |
| service_proxy, |
| ); |
| let fut = async { |
| let result = await!(monitor_delegate_fut); |
| if let Err(err) = result { |
| fx_log_err!(tag: "publish_service", |
| "Failed to create or monitor the gatt service delegate: {:?}", err); |
| } |
| }; |
| fasync::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 |
| /// |
| /// |
| /// 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:"; |
| fx_log_info!(tag: &[tag, &line!().to_string()].join(""), "Publishing service"); |
| let server_proxy = self.create_server_proxy()?; |
| self.inner.write().server_proxy = Some(server_proxy); |
| let database = args.get("database"); |
| let services = match database { |
| Some(d) => match d.get("services") { |
| Some(s) => s, |
| None => bail!("No services found."), |
| }, |
| None => bail!("Could not find the 'services' key in the json database."), |
| }; |
| |
| let service_list = match services.as_array() { |
| Some(s) => s, |
| None => bail!("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"]; |
| await!(self.publish_service(service_info, service_uuid.to_string()))?; |
| } |
| Ok(()) |
| } |
| |
| pub async fn close_server(&self) { |
| self.inner.write().server_proxy = None |
| } |
| |
| // GattServerFacade for cleaning up objects in use. |
| pub fn cleanup(&self) { |
| let tag = "GattServerFacade::cleanup:"; |
| fx_log_info!(tag: &[tag, &line!().to_string()].join(""), "Cleanup GATT server objects"); |
| let mut inner = self.inner.write(); |
| inner.server_proxy = None; |
| inner.service_proxies.clear(); |
| } |
| |
| // GattServerFacade for printing useful information pertaining to the facade for |
| // debug purposes. |
| pub fn print(&self) { |
| let tag = "GattServerFacade::print:"; |
| fx_log_info!(tag: &[tag, &line!().to_string()].join(""), "Unimplemented print function"); |
| } |
| } |