| // 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, Error}; |
| use fidl::endpoints; |
| use fidl_fuchsia_bluetooth_gatt2::{ |
| Characteristic as FidlCharacteristic, CharacteristicNotifierMarker, |
| CharacteristicNotifierRequest, ClientProxy, Handle, LongReadOptions, ReadByTypeResult, |
| ReadOptions, ReadValue, RemoteServiceMarker, RemoteServiceProxy, ServiceInfo, ShortReadOptions, |
| WriteMode, WriteOptions, |
| }; |
| use fuchsia_async as fasync; |
| use fuchsia_bluetooth::types::Uuid; |
| use fuchsia_sync::RwLock; |
| use futures::StreamExt; |
| use num::Num; |
| use std::collections::HashMap; |
| use std::num::ParseIntError; |
| use std::str::FromStr; |
| use std::sync::Arc; |
| |
| use self::commands::Cmd; |
| use self::types::Service; |
| |
| pub mod commands; |
| pub mod repl; |
| pub mod types; |
| |
| struct ActiveService { |
| proxy: RemoteServiceProxy, |
| notifiers: HashMap<u64, fasync::Task<()>>, |
| } |
| |
| impl ActiveService { |
| fn new(proxy: RemoteServiceProxy) -> Self { |
| ActiveService { proxy: proxy, notifiers: HashMap::new() } |
| } |
| } |
| |
| type GattClientPtr = Arc<RwLock<GattClient>>; |
| |
| struct GattClient { |
| proxy: ClientProxy, |
| |
| // Services discovered on this client. |
| services: Vec<Service>, |
| |
| // The index of the currently connected service, if any. |
| active_index: usize, |
| |
| // Proxy and associated state for the currently connected service, if any. |
| active_service: Option<ActiveService>, |
| } |
| |
| impl GattClient { |
| fn new(proxy: ClientProxy) -> GattClientPtr { |
| Arc::new(RwLock::new(GattClient { |
| proxy: proxy, |
| services: vec![], |
| active_index: 0, |
| active_service: None, |
| })) |
| } |
| |
| fn set_services(&mut self, services: Vec<ServiceInfo>) { |
| self.services.clear(); |
| self.services.reserve(services.len()); |
| |
| self.services = services.into_iter().map(|info| Service::new(info)).collect(); |
| self.display_services(); |
| } |
| |
| fn active_service(&mut self) -> Option<&mut Service> { |
| self.services.get_mut(self.active_index) |
| } |
| |
| fn try_clone_proxy(&self) -> Option<RemoteServiceProxy> { |
| self.active_service.as_ref().map(|s| s.proxy.clone()) |
| } |
| |
| fn on_discover_characteristics(&mut self, chrcs: Vec<FidlCharacteristic>) { |
| if let Some(ref mut svc) = self.active_service() { |
| svc.set_characteristics(chrcs); |
| println!("{}", svc); |
| } |
| } |
| |
| fn display_services(&self) { |
| let mut i: i32 = 0; |
| for svc in &self.services { |
| println!(" {}: {}\n", i, &svc); |
| i += 1; |
| } |
| } |
| } |
| |
| // Discover the characteristics of `client`'s currently connected service and |
| // cache them. `client.service_proxy` MUST be valid. |
| async fn discover_characteristics( |
| svc: &RemoteServiceProxy, |
| ) -> Result<Vec<FidlCharacteristic>, Error> { |
| let chrcs: Vec<FidlCharacteristic> = svc |
| .discover_characteristics() |
| .await |
| .map_err(|_| format_err!("Failed to discover characteristics"))?; |
| Ok(chrcs) |
| } |
| |
| async fn read_characteristic(svc: &RemoteServiceProxy, id: u64) -> Result<(), Error> { |
| let options = ReadOptions::ShortRead(ShortReadOptions {}); |
| let value: ReadValue = svc |
| .read_characteristic(&Handle { value: id }, &options) |
| .await |
| .map_err(|e| format_err!("Failed to read characteristic: {e:?}"))? |
| .map_err(|e| format_err!("Failed to read characteristic: {:?}", e))?; |
| |
| if let Some(value) = value.value { |
| println!("(id = {}) value: {:X?} {}", id, &value, decoded_string_value(&value[..])); |
| } |
| |
| Ok(()) |
| } |
| |
| async fn read_long_characteristic( |
| svc: &RemoteServiceProxy, |
| id: u64, |
| offset: u16, |
| max_bytes: u16, |
| ) -> Result<(), Error> { |
| let options = ReadOptions::LongRead(LongReadOptions { |
| offset: Some(offset), |
| max_bytes: Some(max_bytes), |
| ..Default::default() |
| }); |
| |
| let value: ReadValue = svc |
| .read_characteristic(&Handle { value: id }, &options) |
| .await |
| .map_err(|e| format_err!("Failed to read characteristic: {e:?}"))? |
| .map_err(|e| format_err!("Failed to read characteristic: {:?}", e))?; |
| |
| if let Some(value) = value.value { |
| println!( |
| "(id = {}, offset = {}) value: {:X?} {}", |
| id, |
| offset, |
| &value, |
| decoded_string_value(&value) |
| ); |
| } |
| |
| Ok(()) |
| } |
| |
| async fn write_characteristic( |
| svc: &RemoteServiceProxy, |
| id: u64, |
| mode: WriteMode, |
| offset: u16, |
| value: Vec<u8>, |
| ) -> Result<(), Error> { |
| let options = |
| WriteOptions { write_mode: Some(mode), offset: Some(offset), ..Default::default() }; |
| svc.write_characteristic(&Handle { value: id }, &value, &options) |
| .await |
| .context("Failed to write characteristic")? |
| .map_err(|e| format_err!("Failed to write characteristic: {:?}", e))?; |
| |
| println!("(id = {}, offset = {}) done", id, offset); |
| |
| Ok(()) |
| } |
| |
| async fn read_descriptor(svc: &RemoteServiceProxy, id: u64) -> Result<(), Error> { |
| let value: ReadValue = svc |
| .read_descriptor(&Handle { value: id }, &ReadOptions::ShortRead(ShortReadOptions)) |
| .await |
| .context("Failed to read descriptor")? |
| .map_err(|e| format_err!("Failed to read descriptor: {:?}", e))?; |
| |
| if let Some(value) = value.value { |
| println!("(id = {}) value: {:X?}", id, value); |
| } |
| |
| Ok(()) |
| } |
| |
| async fn read_long_descriptor( |
| svc: &RemoteServiceProxy, |
| id: u64, |
| offset: u16, |
| max_bytes: u16, |
| ) -> Result<(), Error> { |
| let value: ReadValue = svc |
| .read_descriptor( |
| &Handle { value: id }, |
| &ReadOptions::LongRead(LongReadOptions { |
| offset: Some(offset), |
| max_bytes: Some(max_bytes), |
| ..Default::default() |
| }), |
| ) |
| .await |
| .context("Failed to read descriptor")? |
| .map_err(|e| format_err!("Failed to read descriptor: {:?}", e))?; |
| |
| if let Some(value) = value.value { |
| println!("(id = {}, offset = {}) value: {:X?}", id, offset, value); |
| } |
| |
| Ok(()) |
| } |
| |
| async fn write_descriptor( |
| svc: &RemoteServiceProxy, |
| id: u64, |
| mode: WriteMode, |
| offset: u16, |
| value: Vec<u8>, |
| ) -> Result<(), Error> { |
| svc.write_descriptor( |
| &Handle { value: id }, |
| &value, |
| &WriteOptions { write_mode: Some(mode), offset: Some(offset), ..Default::default() }, |
| ) |
| .await |
| .context("Failed to write descriptor")? |
| .map_err(|e| format_err!("Failed to write descriptor: {:?}", e))?; |
| |
| println!("(id = {}, offset = {}) done", id, offset); |
| |
| Ok(()) |
| } |
| |
| // ===== REPL ===== |
| |
| fn do_list(args: &[&str], client: &GattClientPtr) { |
| if !args.is_empty() { |
| println!("list: expected 0 arguments"); |
| } else { |
| client.read().display_services(); |
| } |
| } |
| |
| async fn do_connect<'a>(args: &'a [&'a str], client: &'a GattClientPtr) -> Result<(), Error> { |
| if args.len() != 1 { |
| println!("usage: {}", Cmd::Connect.cmd_help()); |
| return Ok(()); |
| } |
| |
| let index: usize = match parse_int(args[0]) { |
| Err(_) => { |
| println!("invalid index: {}", args[0]); |
| return Ok(()); |
| } |
| Ok(i) => i, |
| }; |
| |
| let svc_handle = match client.read().services.get(index) { |
| None => { |
| println!("index out of bounds! ({})", index); |
| return Ok(()); |
| } |
| Some(s) => s.info.handle.expect("service missing handle"), |
| }; |
| |
| // Initialize the remote service proxy. |
| let (proxy, server) = endpoints::create_proxy::<RemoteServiceMarker>(); |
| |
| // First close the connection to the currently active service. |
| let _ = client.write().active_service.take(); |
| |
| client.read().proxy.connect_to_service(&svc_handle, server)?; |
| let chrcs = discover_characteristics(&proxy).await?; |
| |
| client.write().active_index = index; |
| client.write().active_service = Some(ActiveService::new(proxy)); |
| client.write().on_discover_characteristics(chrcs); |
| |
| Ok(()) |
| } |
| |
| async fn do_read_chr<'a>(args: &'a [&'a str], client: &'a GattClientPtr) -> Result<(), Error> { |
| if args.len() != 1 { |
| println!("usage: {}", Cmd::ReadChr.cmd_help()); |
| return Ok(()); |
| } |
| |
| let id: u64 = match parse_int(args[0]) { |
| Err(_) => { |
| println!("invalid id: {}", args[0]); |
| return Ok(()); |
| } |
| Ok(i) => i, |
| }; |
| |
| match &client.read().active_service { |
| Some(svc) => read_characteristic(&svc.proxy, id).await, |
| None => { |
| println!("no service connected"); |
| Ok(()) |
| } |
| } |
| } |
| |
| async fn do_read_long_chr<'a>(args: &'a [&'a str], client: &'a GattClientPtr) -> Result<(), Error> { |
| if args.len() != 3 { |
| println!("usage: {}", Cmd::ReadLongChr.cmd_help()); |
| return Ok(()); |
| } |
| |
| let id: u64 = match parse_int(args[0]) { |
| Err(_) => { |
| println!("invalid id: {}", args[0]); |
| return Ok(()); |
| } |
| Ok(i) => i, |
| }; |
| |
| let offset: u16 = match parse_int(args[1]) { |
| Err(_) => { |
| println!("invalid offset: {}", args[1]); |
| return Ok(()); |
| } |
| Ok(i) => i, |
| }; |
| |
| let max_bytes: u16 = match parse_int(args[2]) { |
| Err(_) => { |
| println!("invalid max bytes: {}", args[2]); |
| return Ok(()); |
| } |
| Ok(i) => i, |
| }; |
| |
| let proxy_opt = client.read().try_clone_proxy(); |
| match proxy_opt { |
| Some(proxy) => read_long_characteristic(&proxy, id, offset, max_bytes).await, |
| None => { |
| println!("no service connected"); |
| Ok(()) |
| } |
| } |
| } |
| |
| async fn do_write_chr<'a>(mut args: Vec<&'a str>, client: &'a GattClientPtr) -> Result<(), Error> { |
| if args.len() < 2 { |
| println!("usage: {}", Cmd::WriteChr.cmd_help()); |
| return Ok(()); |
| } |
| |
| let mode = if args[0] == "-w" { |
| let _ = args.remove(0); |
| WriteMode::WithoutResponse |
| } else { |
| WriteMode::Default |
| }; |
| |
| let id: u64 = match parse_int(args[0]) { |
| Err(_) => { |
| println!("invalid id: {}", args[0]); |
| return Ok(()); |
| } |
| Ok(i) => i, |
| }; |
| |
| let value: Result<Vec<u8>, _> = args[1..].iter().map(|arg| parse_int(arg)).collect(); |
| |
| match value { |
| Err(_) => { |
| println!("invalid value"); |
| Ok(()) |
| } |
| Ok(v) => match &client.read().active_service { |
| Some(svc) => write_characteristic(&svc.proxy, id, mode, 0, v).await, |
| None => { |
| println!("no service connected"); |
| Ok(()) |
| } |
| }, |
| } |
| } |
| |
| async fn do_write_long_chr<'a>( |
| mut args: Vec<&'a str>, |
| client: &'a GattClientPtr, |
| ) -> Result<(), Error> { |
| if args.len() < 3 { |
| println!("usage: {}", Cmd::WriteLongChr.cmd_help()); |
| return Ok(()); |
| } |
| |
| let mode = if args[0] == "-r" { |
| let _ = args.remove(0); |
| WriteMode::Reliable |
| } else { |
| WriteMode::Default |
| }; |
| |
| let id: u64 = match parse_int(args[0]) { |
| Err(_) => { |
| println!("invalid id: {}", args[0]); |
| return Ok(()); |
| } |
| Ok(i) => i, |
| }; |
| |
| let offset: u16 = match parse_int(args[1]) { |
| Err(_) => { |
| println!("invalid offset: {}", args[1]); |
| return Ok(()); |
| } |
| Ok(i) => i, |
| }; |
| |
| let value: Result<Vec<u8>, _> = args[2..].iter().map(|arg| parse_int(arg)).collect(); |
| |
| match value { |
| Err(_) => { |
| println!("invalid value"); |
| Ok(()) |
| } |
| Ok(v) => match &client.read().active_service { |
| Some(svc) => write_characteristic(&svc.proxy, id, mode, offset, v).await, |
| None => { |
| println!("no service connected"); |
| Ok(()) |
| } |
| }, |
| } |
| } |
| |
| async fn do_read_desc<'a>(args: &'a [&'a str], client: &'a GattClientPtr) -> Result<(), Error> { |
| if args.len() != 1 { |
| println!("usage: {}", Cmd::ReadDesc.cmd_help()); |
| return Ok(()); |
| } |
| |
| let id: u64 = match parse_int(args[0]) { |
| Err(_) => { |
| println!("invalid id: {}", args[0]); |
| return Ok(()); |
| } |
| Ok(i) => i, |
| }; |
| |
| match &client.read().active_service { |
| Some(svc) => read_descriptor(&svc.proxy, id).await, |
| None => { |
| println!("no service connected"); |
| Ok(()) |
| } |
| } |
| } |
| |
| async fn do_read_long_desc<'a>( |
| args: &'a [&'a str], |
| client: &'a GattClientPtr, |
| ) -> Result<(), Error> { |
| if args.len() != 3 { |
| println!("usage: {}", Cmd::ReadLongDesc.cmd_help()); |
| return Ok(()); |
| } |
| |
| let id: u64 = match parse_int(args[0]) { |
| Err(_) => { |
| println!("invalid id: {}", args[0]); |
| return Ok(()); |
| } |
| Ok(i) => i, |
| }; |
| |
| let offset: u16 = match parse_int(args[1]) { |
| Err(_) => { |
| println!("invalid offset: {}", args[1]); |
| return Ok(()); |
| } |
| Ok(i) => i, |
| }; |
| |
| let max_bytes: u16 = match parse_int(args[2]) { |
| Err(_) => { |
| println!("invalid max bytes: {}", args[2]); |
| return Ok(()); |
| } |
| Ok(i) => i, |
| }; |
| |
| match &client.read().active_service { |
| Some(svc) => read_long_descriptor(&svc.proxy, id, offset, max_bytes).await, |
| None => { |
| println!("no service connected"); |
| Ok(()) |
| } |
| } |
| } |
| |
| async fn do_write_desc<'a>(args: Vec<&'a str>, client: &'a GattClientPtr) -> Result<(), Error> { |
| if args.len() < 2 { |
| println!("usage: {}", Cmd::WriteDesc.cmd_help()); |
| return Ok(()); |
| } |
| |
| let id: u64 = match parse_int(args[0]) { |
| Err(_) => { |
| println!("invalid id: {}", args[0]); |
| return Ok(()); |
| } |
| Ok(i) => i, |
| }; |
| |
| let value: Result<Vec<u8>, _> = args[1..].iter().map(|arg| parse_int(arg)).collect(); |
| |
| match value { |
| Err(_) => { |
| println!("invalid value"); |
| Ok(()) |
| } |
| Ok(v) => match &client.read().active_service { |
| Some(svc) => write_descriptor(&svc.proxy, id, WriteMode::Default, 0, v).await, |
| None => { |
| println!("no service connected"); |
| Ok(()) |
| } |
| }, |
| } |
| } |
| |
| async fn do_write_long_desc<'a>( |
| args: &'a [&'a str], |
| client: &'a GattClientPtr, |
| ) -> Result<(), Error> { |
| if args.len() < 3 { |
| println!("usage: {}", Cmd::WriteLongDesc.cmd_help()); |
| return Ok(()); |
| } |
| |
| let id: u64 = match parse_int(args[0]) { |
| Err(_) => { |
| println!("invalid id: {}", args[0]); |
| return Ok(()); |
| } |
| Ok(i) => i, |
| }; |
| |
| let offset: u16 = match parse_int(args[1]) { |
| Err(_) => { |
| println!("invalid offset: {}", args[1]); |
| return Ok(()); |
| } |
| Ok(i) => i, |
| }; |
| |
| let value: Result<Vec<u8>, _> = args[2..].iter().map(|arg| parse_int(arg)).collect(); |
| |
| match value { |
| Err(_) => { |
| println!("invalid value"); |
| Ok(()) |
| } |
| Ok(v) => match &client.read().active_service { |
| Some(svc) => write_descriptor(&svc.proxy, id, WriteMode::Default, offset, v).await, |
| None => { |
| println!("no service connected"); |
| Ok(()) |
| } |
| }, |
| } |
| } |
| |
| fn print_read_by_type_result(result: &ReadByTypeResult) { |
| match (result.value.as_ref(), result.error.as_ref()) { |
| (Some(value), None) => { |
| let value = value.value.as_ref().expect("read by value response value"); |
| println!( |
| "[id: {}, value: {:X?} {}]", |
| result.handle.as_ref().expect("read by value response handle").value, |
| value, |
| decoded_string_value(value) |
| ); |
| } |
| (None, Some(error)) => { |
| println!( |
| "[id: {}, error: {:?}]", |
| result.handle.as_ref().expect("read by value response id").value, |
| error |
| ); |
| } |
| _ => { |
| println!("Invalid FIDL response"); |
| } |
| } |
| } |
| |
| async fn do_read_by_type<'a>(args: &'a [&'a str], client: &'a GattClientPtr) -> Result<(), Error> { |
| if args.len() != 1 { |
| println!("usage: {}", Cmd::ReadByType.cmd_help()); |
| return Ok(()); |
| } |
| |
| let uuid = match Uuid::from_str(args[0]) { |
| Err(_) => { |
| println!("invalid uuid: {}", args[0]); |
| return Ok(()); |
| } |
| Ok(u) => u, |
| }; |
| |
| let fidl_uuid = fidl_fuchsia_bluetooth::Uuid::from(uuid); |
| match &client.read().active_service { |
| Some(svc) => match svc.proxy.read_by_type(&fidl_uuid).await { |
| Ok(Ok(results)) => { |
| if results.len() == 0 { |
| println!("No results received."); |
| } else { |
| for res in results.into_iter() { |
| print_read_by_type_result(&res); |
| } |
| } |
| } |
| Ok(Err(e)) => { |
| println!("read by type error result: {e:?}"); |
| } |
| Err(e) => { |
| println!("read by type FIDL error: {e:?}"); |
| } |
| }, |
| None => { |
| println!("no service connected"); |
| } |
| } |
| Ok(()) |
| } |
| |
| async fn do_enable_notify<'a>(args: &'a [&'a str], client: &'a GattClientPtr) -> Result<(), Error> { |
| if args.len() != 1 { |
| println!("usage: {}", Cmd::EnableNotify.cmd_help()); |
| return Ok(()); |
| } |
| |
| let id: u64 = match parse_int(args[0]) { |
| Err(_) => { |
| println!("invalid id: {}", args[0]); |
| return Ok(()); |
| } |
| Ok(i) => i, |
| }; |
| |
| let active_service_valid = || -> bool { |
| match &client.read().active_service { |
| None => { |
| println!("no service connected"); |
| return false; |
| } |
| Some(svc) => { |
| if svc.notifiers.contains_key(&id) { |
| println!("(id = {}) notifications already enabled", id); |
| return false; |
| } else { |
| return true; |
| } |
| } |
| }; |
| }; |
| if !active_service_valid() { |
| return Ok(()); |
| } |
| |
| let (notifier_client, mut notifier_req_stream) = |
| endpoints::create_request_stream::<CharacteristicNotifierMarker>(); |
| let proxy = client.read().try_clone_proxy().unwrap(); |
| proxy |
| .register_characteristic_notifier(&Handle { value: id }, notifier_client) |
| .await |
| .context("Failed to register characteristic notifier")? |
| .map_err(|e| format_err!("Failed to register characteristic notifier: {:?}", e))?; |
| |
| // The service could have closed during the async FIDL call above, or another notifier could have been registered (making this one redundant). |
| if !active_service_valid() { |
| return Ok(()); |
| } |
| |
| let task = fasync::Task::spawn(async move { |
| while let Some(req) = notifier_req_stream.next().await { |
| match req { |
| Ok(event) => match event { |
| CharacteristicNotifierRequest::OnNotification { value, responder } => { |
| if let Some(value) = value.value { |
| print!("{}{}", repl::CLEAR_LINE, repl::CHA); |
| println!( |
| "(id = {}) value updated: {:X?} {}", |
| id, |
| value, |
| decoded_string_value(&value) |
| ); |
| } |
| if let Err(e) = responder.send() { |
| println!("(id = {}) notifier closed due to responder error: {}", id, e); |
| return; |
| } |
| } |
| }, |
| Err(e) => { |
| println!("(id = {}) notifier closed due to error: {}", id, e); |
| return; |
| } |
| } |
| } |
| println!("(id = {}) notifier closed", id); |
| }); |
| |
| let old_value = client.write().active_service.as_mut().unwrap().notifiers.insert(id, task); |
| assert!(old_value.is_none()); |
| |
| println!("(id = {}) done", id); |
| Ok(()) |
| } |
| |
| async fn do_disable_notify<'a>( |
| args: &'a [&'a str], |
| client: &'a GattClientPtr, |
| ) -> Result<(), Error> { |
| if args.len() != 1 { |
| println!("usage: {}", Cmd::DisableNotify.cmd_help()); |
| return Ok(()); |
| } |
| |
| let id: u64 = match parse_int(args[0]) { |
| Err(_) => { |
| println!("invalid id: {}", args[0]); |
| return Ok(()); |
| } |
| Ok(i) => i, |
| }; |
| |
| let task = match client.write().active_service.as_mut() { |
| Some(svc) => svc.notifiers.remove(&id), |
| None => { |
| println!("no service connected"); |
| return Ok(()); |
| } |
| }; |
| match task { |
| Some(task) => { |
| let _ = task.cancel(); |
| println!("(id = {}) done", id); |
| } |
| None => println!("(id = {}) notifications not enabled", id), |
| }; |
| Ok(()) |
| } |
| |
| /// Attempt to decode the value as a utf-8 string, replacing any invalid byte sequences with '.' |
| /// characters. Returns an empty string if there are not any valid utf-8 characters. |
| fn decoded_string_value(value: &[u8]) -> String { |
| let decoded_value = String::from_utf8_lossy(value); |
| if decoded_value.chars().any(|c| c != '�') { |
| decoded_value.replace("�", ".") |
| } else { |
| String::new() |
| } |
| } |
| |
| /// Attempt to parse a string into an integer. If the string begins with 0x, treat the rest |
| /// of the string as a hex value, otherwise treat it as decimal. |
| fn parse_int<N>(input: &str) -> Result<N, ParseIntError> |
| where |
| N: Num<FromStrRadixErr = ParseIntError>, |
| { |
| if input.starts_with("0x") { |
| N::from_str_radix(&input[2..], 16) |
| } else { |
| N::from_str_radix(input, 10) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| use bt_fidl_mocks::gatt2::{ClientMock, RemoteServiceMock}; |
| use fidl::endpoints::create_proxy_and_stream; |
| use fidl_fuchsia_bluetooth_gatt2::{ClientMarker, ServiceHandle}; |
| |
| use futures::future::FutureExt; |
| use futures::{join, select}; |
| use std::pin::pin; |
| |
| #[fuchsia::test] |
| async fn test_read_by_type() { |
| let (client, _stream) = create_proxy_and_stream::<ClientMarker>(); |
| let gatt_client = GattClient::new(client); |
| |
| let args = vec!["0000180d-0000-1000-8000-00805f9b34fb"]; |
| let read_fut = do_read_by_type(&args, &gatt_client); |
| |
| let (service_proxy, mut service_mock) = |
| RemoteServiceMock::new(zx::MonotonicDuration::from_seconds(20)) |
| .expect("failed to create mock"); |
| |
| gatt_client.write().active_service = Some(ActiveService::new(service_proxy)); |
| |
| let expected_uuid = Uuid::new16(0x180d); |
| let result = Ok(&[][..]); |
| let expect_fut = service_mock.expect_read_by_type(expected_uuid, result); |
| |
| let (read_result, expect_result) = join!(read_fut, expect_fut); |
| let _ = read_result.expect("do read by type failed"); |
| let _ = expect_result.expect("read by type expectation not satisfied"); |
| } |
| |
| #[fuchsia::test] |
| async fn test_connect_and_enable_notify() { |
| let (client_proxy, mut client_mock) = |
| ClientMock::new(zx::MonotonicDuration::from_seconds(20)) |
| .expect("failed to create mock"); |
| let gatt_client = GattClient::new(client_proxy); |
| |
| let services = |
| vec![ServiceInfo { handle: Some(ServiceHandle { value: 1 }), ..Default::default() }]; |
| gatt_client.write().set_services(services); |
| |
| let args = vec!["0"]; // service index |
| let mut connect_fut = pin!(do_connect(&args, &gatt_client).fuse()); |
| let mut expect_connect_fut = |
| pin!(client_mock.expect_connect_to_service(ServiceHandle { value: 1 }).fuse()); |
| |
| let (_, service_server) = select! { |
| _ = connect_fut => panic!("connect_fut completed prematurely (characteristics haven't been discovered)"), |
| result = expect_connect_fut => result.expect("expect connect failed"), |
| }; |
| let service_stream = service_server.into_stream(); |
| let mut service_mock = |
| RemoteServiceMock::from_stream(service_stream, zx::MonotonicDuration::from_seconds(20)); |
| |
| let characteristics = Vec::new(); |
| let expect_discover_fut = service_mock.expect_discover_characteristics(&characteristics); |
| |
| let (connect_result, expect_discover_result) = join!(connect_fut, expect_discover_fut); |
| connect_result.expect("failed to connect"); |
| expect_discover_result.expect("expect discover failed"); |
| |
| let args = vec!["2"]; // characteristic handle |
| let register_fut = do_enable_notify(&args, &gatt_client); |
| let expect_register_fut = |
| service_mock.expect_register_characteristic_notifier(Handle { value: 2 }); |
| |
| let (register_result, expect_register_result) = join!(register_fut, expect_register_fut); |
| register_result.expect("failed to register notifier"); |
| let notifier_client = expect_register_result.expect("expect register failed"); |
| let notifier = notifier_client.into_proxy(); |
| |
| let notification_value = ReadValue { |
| handle: Some(Handle { value: 2 }), |
| value: Some(vec![0x00, 0x01, 0x02]), |
| maybe_truncated: Some(false), |
| ..Default::default() |
| }; |
| |
| // The notification should immediately receive a flow control response. |
| notifier.on_notification(¬ification_value).await.expect("on_notification"); |
| } |
| } |