| // 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"); |
| } |
| } |