From fb95e26bf59dd39381d21194d228035f383755cd Mon Sep 17 00:00:00 2001 From: Lucas Silva Shepard Date: Tue, 15 Aug 2023 13:31:56 -0700 Subject: [PATCH] [fp-rs] Added FP Advertisement unit tests in Rust. --- .../bluetooth/src/common/advertisement.rs | 2 +- fastpair/rust/bluetooth/src/lib.rs | 4 +- fastpair/rust/demo/rust/src/advertisement.rs | 125 ++++++++++++++++-- fastpair/rust/demo/rust/src/api.rs | 12 +- 4 files changed, 125 insertions(+), 18 deletions(-) diff --git a/fastpair/rust/bluetooth/src/common/advertisement.rs b/fastpair/rust/bluetooth/src/common/advertisement.rs index 93413079..6eb679f3 100644 --- a/fastpair/rust/bluetooth/src/common/advertisement.rs +++ b/fastpair/rust/bluetooth/src/common/advertisement.rs @@ -33,7 +33,7 @@ type DecibelMilliwatts = i16; impl BleAdvertisement { /// Construct a new `BleAdvertisement` instance. - pub(crate) fn new( + pub fn new( address: BleAddress, rssi: Option, tx_power: Option, diff --git a/fastpair/rust/bluetooth/src/lib.rs b/fastpair/rust/bluetooth/src/lib.rs index ebe8384a..99da6362 100644 --- a/fastpair/rust/bluetooth/src/lib.rs +++ b/fastpair/rust/bluetooth/src/lib.rs @@ -17,8 +17,8 @@ mod common; use api::{BleAdapter, BleDevice, ClassicDevice}; pub use common::{ - BleAddress, BleAdvertisement, BleDataTypeId, BluetoothError, - ClassicAddress, PairingResult, ServiceData, + BleAddress, BleAddressKind, BleAdvertisement, BleDataTypeId, + BluetoothError, ClassicAddress, PairingResult, ServiceData, }; cfg_if::cfg_if! { diff --git a/fastpair/rust/demo/rust/src/advertisement.rs b/fastpair/rust/demo/rust/src/advertisement.rs index 58cc3338..9179ad75 100644 --- a/fastpair/rust/demo/rust/src/advertisement.rs +++ b/fastpair/rust/demo/rust/src/advertisement.rs @@ -14,10 +14,7 @@ use bluetooth::{BleAddress, BleAdvertisement, ServiceData}; -use crate::{ - decoder::FpDecoder, - fetcher::{DeviceInfo, FpFetcher, FpFetcherFs}, -}; +use crate::{decoder::FpDecoder, fetcher::FpFetcher}; /// Represents a FP device model ID. pub(crate) type ModelId = String; @@ -30,7 +27,7 @@ pub(crate) struct FpPairingAdvertisement { /// Estimated distance in meters of device from BLE adapter. distance: f64, model_id: ModelId, - name: String, + device_name: String, image_url: String, } @@ -39,6 +36,7 @@ impl FpPairingAdvertisement { pub(crate) fn new( adv: BleAdvertisement, service_data: &ServiceData, + fetcher: &Box, ) -> Result { let rssi = adv.rssi().ok_or(anyhow::anyhow!( "Windows advertisements should contain RSSI information." @@ -78,16 +76,13 @@ impl FpPairingAdvertisement { let model_id = format!("{}", u32::from_be_bytes(model_id.try_into().unwrap())); // Retrieve device info of the device corresponding to this model ID. - let fetcher = FpFetcherFs::new(String::from("./local")); - let device_info = fetcher - .get_device_info_from_model_id(&model_id) - .expect("Failed to create device info from model ID."); + let device_info = fetcher.get_device_info_from_model_id(&model_id)?; Ok(FpPairingAdvertisement { inner: adv, distance, model_id, - name: device_info.name().to_string(), + device_name: device_info.name().to_string(), image_url: device_info.image_url().to_string(), }) } @@ -109,8 +104,8 @@ impl FpPairingAdvertisement { &self.model_id } - pub(crate) fn name(&self) -> &String { - &self.name + pub(crate) fn device_name(&self) -> &String { + &self.device_name } pub(crate) fn image_url(&self) -> &String { @@ -149,3 +144,109 @@ pub(crate) fn distance_from_rssi_and_tx_power(rssi: i16, tx_power: i16) -> f64 { (f64::from(tx_power - rssi - RSSI_DROPOFF_AT_1_M)) / f64::from(10 * PATH_LOSS_EXPONENT), ) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::fetcher::{mock::FpFetcherMock, DeviceInfo}; + + use bluetooth::BleAddressKind; + + #[test] + fn test_new_fp_pairing_advertisement() { + let addr = BleAddress::new(0x112233, BleAddressKind::Public); + let ble_adv = BleAdvertisement::new(addr, Some(-60), Some(10)); + + let raw_data = vec![3, 2, 1]; + let expected_model_id = "197121"; // (3 << 16) + (2 << 8) + 1. + let service_data = ServiceData::new(0x123 as u16, raw_data); + + let image_url = String::from("image_url"); + let device_name = String::from("name"); + let device_info = Ok(DeviceInfo::new(image_url.clone(), device_name.clone())); + let fetcher: Box = Box::new(FpFetcherMock::new(device_info)); + + let fp_adv = FpPairingAdvertisement::new(ble_adv, &service_data, &fetcher); + + assert!(fp_adv.is_ok()); + let fp_adv = fp_adv.unwrap(); + assert_eq!(fp_adv.address(), addr); + assert_eq!(fp_adv.image_url(), &image_url); + assert_eq!(fp_adv.device_name(), &device_name); + assert_eq!(fp_adv.model_id(), &expected_model_id); + } + + #[test] + fn test_new_fp_pairing_advertisement_bad_rssi() { + let addr = BleAddress::new(0x112233, BleAddressKind::Public); + let ble_adv = BleAdvertisement::new(addr, None, Some(10)); + + let raw_data = vec![3, 2, 1]; + let service_data = ServiceData::new(0x123 as u16, raw_data); + + let device_info = Ok(DeviceInfo::new( + String::from("image_url"), + String::from("name"), + )); + let fetcher: Box = Box::new(FpFetcherMock::new(device_info)); + + let fp_adv = FpPairingAdvertisement::new(ble_adv, &service_data, &fetcher); + + assert!(fp_adv.is_err()); + } + + #[test] + fn test_new_fp_pairing_advertisement_bad_tx_power() { + let addr = BleAddress::new(0x112233, BleAddressKind::Public); + let ble_adv = BleAdvertisement::new(addr, Some(-60), None); + + let raw_data = vec![3, 2, 1]; + let service_data = ServiceData::new(0x123 as u16, raw_data); + + let device_info = Ok(DeviceInfo::new( + String::from("image_url"), + String::from("name"), + )); + let fetcher: Box = Box::new(FpFetcherMock::new(device_info)); + + let fp_adv = FpPairingAdvertisement::new(ble_adv, &service_data, &fetcher); + + assert!(fp_adv.is_err()); + } + + #[test] + fn test_new_fp_pairing_advertisement_bad_service_data() { + let addr = BleAddress::new(0x112233, BleAddressKind::Public); + let ble_adv = BleAdvertisement::new(addr, Some(-60), Some(10)); + + let raw_data = vec![4, 3, 2, 1]; + let service_data = ServiceData::new(0x123 as u16, raw_data); + + let device_info = Ok(DeviceInfo::new( + String::from("image_url"), + String::from("name"), + )); + let fetcher: Box = Box::new(FpFetcherMock::new(device_info)); + + let fp_adv = FpPairingAdvertisement::new(ble_adv, &service_data, &fetcher); + + assert!(fp_adv.is_err()); + } + + #[test] + fn test_new_fp_pairing_advertisement_bad_fetcher() { + let addr = BleAddress::new(0x112233, BleAddressKind::Public); + let ble_adv = BleAdvertisement::new(addr, Some(-60), Some(10)); + + let raw_data = vec![3, 2, 1]; + let service_data = ServiceData::new(0x123 as u16, raw_data); + + let fetcher: Box = Box::new(FpFetcherMock::new(Err(anyhow::anyhow!( + "mock intentional error" + )))); + + let fp_adv = FpPairingAdvertisement::new(ble_adv, &service_data, &fetcher); + + assert!(fp_adv.is_err()); + } +} diff --git a/fastpair/rust/demo/rust/src/api.rs b/fastpair/rust/demo/rust/src/api.rs index e80ae950..bb1b3a2f 100644 --- a/fastpair/rust/demo/rust/src/api.rs +++ b/fastpair/rust/demo/rust/src/api.rs @@ -9,7 +9,10 @@ use futures::executor; use tracing::{info, warn}; use ttl_cache::TtlCache; -use crate::advertisement::{FpPairingAdvertisement, ModelId}; +use crate::{ + advertisement::{FpPairingAdvertisement, ModelId}, + fetcher::{FpFetcher, FpFetcherFs}, +}; // Sends a device name to Flutter via `StreamSink` FFI layer. static DEVICE_STREAM: RwLock>>> = RwLock::new(None); @@ -29,7 +32,7 @@ async fn update_best_device(best_adv: FpPairingAdvertisement) { match DEVICE_STREAM.read().unwrap().as_ref() { Some(stream) => { stream.add(Some([ - best_adv.name().to_string(), + best_adv.device_name().to_string(), best_adv.image_url().to_string(), ])); } @@ -47,6 +50,7 @@ async fn update_best_device(best_adv: FpPairingAdvertisement) { fn new_best_fp_advertisement( advertisement: BleAdvertisement, service_data: &ServiceData, + fetcher: &Box, latest_advertisement_map: &mut HashMap, ) -> Option { // Analyze service data sections. @@ -57,7 +61,7 @@ fn new_best_fp_advertisement( return None; } - let fp_adv = match FpPairingAdvertisement::new(advertisement, service_data) { + let fp_adv = match FpPairingAdvertisement::new(advertisement, service_data, fetcher) { Ok(fp_adv) => fp_adv, Err(err) => { // If error during construction (e.g. non-discoverable @@ -130,6 +134,7 @@ pub fn init() { let mut latest_advertisement_map = HashMap::new(); let datatype_selector = vec![BleDataTypeId::ServiceData16BitUuid]; + let fetcher: Box = Box::new(FpFetcherFs::new(String::from("./local"))); loop { // Retrieve the next received advertisement. @@ -142,6 +147,7 @@ pub fn init() { if let Some(best_adv) = new_best_fp_advertisement( advertisement.clone(), service_data, + &fetcher, &mut latest_advertisement_map, ) { update_best_device(best_adv).await;