diff --git a/fastpair/rust/bluetooth/src/common/address.rs b/fastpair/rust/bluetooth/src/common/address.rs index bff124d7..85f81f04 100644 --- a/fastpair/rust/bluetooth/src/common/address.rs +++ b/fastpair/rust/bluetooth/src/common/address.rs @@ -134,7 +134,7 @@ mod tests { fn try_from_ble_address_to_classic() { let ble_addr = BleAddress::new(0x112233445566, BleAddressKind::Public); let result: Result = - TryFrom::try_from(ble_addr); + ble_addr.try_into(); assert!(result.is_ok()); assert_eq!(result.unwrap().0, [0x66, 0x55, 0x44, 0x33, 0x22, 0x11]); diff --git a/fastpair/rust/bluetooth/src/common/advertisement.rs b/fastpair/rust/bluetooth/src/common/advertisement.rs index 6eb679f3..00ce6bb3 100644 --- a/fastpair/rust/bluetooth/src/common/advertisement.rs +++ b/fastpair/rust/bluetooth/src/common/advertisement.rs @@ -18,7 +18,7 @@ use super::{BleAddress, BluetoothError}; /// information about the advertisement (e.g. address of sender) as well as /// data sections extracted from the advertisement. Platform-specific methods /// should be written to load in data sections from incoming advertisements. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct BleAdvertisement { address: BleAddress, rssi: Option, diff --git a/fastpair/rust/demo/lib/bridge_definitions.dart b/fastpair/rust/demo/lib/bridge_definitions.dart index 67d21a9c..b66ccf6b 100644 --- a/fastpair/rust/demo/lib/bridge_definitions.dart +++ b/fastpair/rust/demo/lib/bridge_definitions.dart @@ -1,17 +1,3 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - // AUTO GENERATED FILE, DO NOT EDIT. // Generated by `flutter_rust_bridge`@ 1.79.0. // ignore_for_file: non_constant_identifier_names, unused_element, duplicate_ignore, directives_ordering, curly_braces_in_flow_control_structures, unnecessary_lambdas, slash_for_doc_comments, prefer_const_literals_to_create_immutables, implicit_dynamic_list_literal, duplicate_import, unused_import, unnecessary_import, prefer_single_quotes, prefer_const_constructors, use_super_parameters, always_use_package_imports, annotate_overrides, invalid_use_of_protected_member, constant_identifier_names, invalid_use_of_internal_member, prefer_is_empty, unnecessary_const diff --git a/fastpair/rust/demo/lib/bridge_generated.dart b/fastpair/rust/demo/lib/bridge_generated.dart index 72999e9a..9fc5d9db 100644 --- a/fastpair/rust/demo/lib/bridge_generated.dart +++ b/fastpair/rust/demo/lib/bridge_generated.dart @@ -1,17 +1,3 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - // AUTO GENERATED FILE, DO NOT EDIT. // Generated by `flutter_rust_bridge`@ 1.79.0. // ignore_for_file: non_constant_identifier_names, unused_element, duplicate_ignore, directives_ordering, curly_braces_in_flow_control_structures, unnecessary_lambdas, slash_for_doc_comments, prefer_const_literals_to_create_immutables, implicit_dynamic_list_literal, duplicate_import, unused_import, unnecessary_import, prefer_single_quotes, prefer_const_constructors, use_super_parameters, always_use_package_imports, annotate_overrides, invalid_use_of_protected_member, constant_identifier_names, invalid_use_of_internal_member, prefer_is_empty, unnecessary_const diff --git a/fastpair/rust/demo/rust/Cargo.toml b/fastpair/rust/demo/rust/Cargo.toml index c1839a89..4d736a04 100644 --- a/fastpair/rust/demo/rust/Cargo.toml +++ b/fastpair/rust/demo/rust/Cargo.toml @@ -9,7 +9,6 @@ edition = "2021" crate-type = ["lib", "cdylib", "staticlib"] [dependencies] -anyhow = "1.0" bluetooth = { version = "0.1", path = "../../bluetooth" } flutter_rust_bridge = "1" futures = { version = "0.3", features = ["executor"] } @@ -17,3 +16,4 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tracing = "0.1.37" ttl_cache = "0.5.1" +thiserror = "1.0.43" diff --git a/fastpair/rust/demo/rust/src/advertisement.rs b/fastpair/rust/demo/rust/src/advertisement.rs index 9179ad75..da59517b 100644 --- a/fastpair/rust/demo/rust/src/advertisement.rs +++ b/fastpair/rust/demo/rust/src/advertisement.rs @@ -14,14 +14,14 @@ use bluetooth::{BleAddress, BleAdvertisement, ServiceData}; -use crate::{decoder::FpDecoder, fetcher::FpFetcher}; +use crate::{decoder::FpDecoder, error::FpError, fetcher::FpFetcher}; /// Represents a FP device model ID. pub(crate) type ModelId = String; /// Holds information required to make decisions about an incoming Fast Pair /// advertisement. -#[derive(Clone)] +#[derive(Clone, Debug)] pub(crate) struct FpPairingAdvertisement { inner: BleAdvertisement, /// Estimated distance in meters of device from BLE adapter. @@ -37,13 +37,15 @@ impl FpPairingAdvertisement { adv: BleAdvertisement, service_data: &ServiceData, fetcher: &Box, - ) -> Result { - let rssi = adv.rssi().ok_or(anyhow::anyhow!( - "Windows advertisements should contain RSSI information." - ))?; - let tx_power = adv.tx_power().ok_or(anyhow::anyhow!( - "Fast Pair advertisements should advertise their transmit power." - ))?; + ) -> Result { + let rssi = adv.rssi().ok_or(FpError::ContractViolation(String::from( + "Windows advertisements should contain RSSI information.", + )))?; + let tx_power = adv + .tx_power() + .ok_or(FpError::ContractViolation(String::from( + "Windows advertisements should contain RSSI information.", + )))?; let distance = distance_from_rssi_and_tx_power(rssi, tx_power); @@ -51,22 +53,16 @@ impl FpPairingAdvertisement { // data in the `FpPairingAdvertisement` since it's easily accessible from // `FpPairingAdvertisement.inner`, but it's convenient to save the parsed // model ID. - let mut model_id = - FpDecoder::get_model_id_from_service_data(service_data).or_else(|err| { - // Some FP advertisements can be GATT non-discoverable - // advertisements containing service data that isn't - // the device model ID. In this case, simply ignore - // advertisements with errors extracting the model ID. - // See: developers.google.com/nearby/fast-pair/specifications/service/provider - Err(anyhow::anyhow!("error extracting model ID: {}", err)) - })?; + let mut model_id = FpDecoder::get_model_id_from_service_data(service_data)?; if model_id.len() != 3 { // In this demo of Fast Pair Rust, only model ID's // of length 3 bytes are supported. Therefore, if a // larger model ID makes it this far, log an error. // TODO b/294453912 - return Err(anyhow::anyhow!("Error: model ID of unsupported length")); + return Err(FpError::Internal(String::from( + "creating `model_id` should have already failed", + ))); } // Pad with 0 at the beginning to successfully call `from_be_bytes`. @@ -193,6 +189,7 @@ mod tests { let fp_adv = FpPairingAdvertisement::new(ble_adv, &service_data, &fetcher); assert!(fp_adv.is_err()); + assert!(matches!(fp_adv.unwrap_err(), FpError::ContractViolation(_))); } #[test] @@ -212,25 +209,7 @@ mod tests { 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()); + assert!(matches!(fp_adv.unwrap_err(), FpError::ContractViolation(_))); } #[test] @@ -241,12 +220,11 @@ mod tests { 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 fetcher: Box = Box::new(FpFetcherMock::new(Err(FpError::Test))); let fp_adv = FpPairingAdvertisement::new(ble_adv, &service_data, &fetcher); assert!(fp_adv.is_err()); + assert!(matches!(fp_adv.unwrap_err(), FpError::Test)); } } diff --git a/fastpair/rust/demo/rust/src/api.rs b/fastpair/rust/demo/rust/src/api.rs index bb1b3a2f..5948bd57 100644 --- a/fastpair/rust/demo/rust/src/api.rs +++ b/fastpair/rust/demo/rust/src/api.rs @@ -160,10 +160,9 @@ pub fn init() { } /// Sets up `StreamSink` for Dart-Rust FFI. -pub fn event_stream(s: StreamSink>) -> Result<(), anyhow::Error> { +pub fn event_stream(s: StreamSink>) { let mut stream = DEVICE_STREAM.write().unwrap(); *stream = Some(s); - Ok(()) } /// Attempt classic pairing with currently displayed device. diff --git a/fastpair/rust/demo/rust/src/bridge_generated.rs b/fastpair/rust/demo/rust/src/bridge_generated.rs index 06d8ce6e..bf1f040f 100644 --- a/fastpair/rust/demo/rust/src/bridge_generated.rs +++ b/fastpair/rust/demo/rust/src/bridge_generated.rs @@ -54,7 +54,11 @@ fn wire_event_stream_impl(port_: MessagePort) { mode: FfiCallMode::Stream, }, move || { - move |task_callback| event_stream(task_callback.stream_sink::<_, Option<[String; 2]>>()) + move |task_callback| { + Ok(event_stream( + task_callback.stream_sink::<_, Option<[String; 2]>>(), + )) + } }, ) } diff --git a/fastpair/rust/demo/rust/src/decoder.rs b/fastpair/rust/demo/rust/src/decoder.rs index 4865a35b..91902e20 100644 --- a/fastpair/rust/demo/rust/src/decoder.rs +++ b/fastpair/rust/demo/rust/src/decoder.rs @@ -13,6 +13,8 @@ // limitations under the License. use bluetooth::ServiceData; +use crate::error::FpError; + /// Unit struct providing parsing operations for Fast Pair advertisements. pub(crate) struct FpDecoder; @@ -25,13 +27,13 @@ impl FpDecoder { /// Currently unavailable in Fast Pair devices and not supported. pub(crate) fn get_model_id_from_service_data( service_data: &ServiceData, - ) -> Result, anyhow::Error> { + ) -> Result, FpError> { static MIN_MODEL_ID_LENGTH: usize = 3; let data = service_data.data(); if data.len() < MIN_MODEL_ID_LENGTH { // If service data too small, invalid payload. - Err(anyhow::anyhow!(format!( + Err(FpError::ContractViolation(format!( "Invalid model ID for Fast Pair advertisement of length {}.", data.len() ))) @@ -41,13 +43,14 @@ impl FpDecoder { } else { // Else, this Fast Pair advertisement is currently unsupported. // b/294453912 - Err(anyhow::anyhow!( - "This Fast Pair device is currently unsupported." - )) + Err(FpError::NotImplemented(String::from( + "This Fast Pair device is currently unsupported.", + ))) } } } +#[cfg(test)] mod tests { use super::*; @@ -70,6 +73,7 @@ mod tests { let service_data = ServiceData::new(uuid, data); let result = FpDecoder::get_model_id_from_service_data(&service_data); assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), FpError::ContractViolation(_))); } #[test] @@ -80,5 +84,6 @@ mod tests { let service_data = ServiceData::new(uuid, data); let result = FpDecoder::get_model_id_from_service_data(&service_data); assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), FpError::NotImplemented(_))); } } diff --git a/fastpair/rust/demo/rust/src/error.rs b/fastpair/rust/demo/rust/src/error.rs new file mode 100644 index 00000000..81ecb400 --- /dev/null +++ b/fastpair/rust/demo/rust/src/error.rs @@ -0,0 +1,43 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use thiserror::Error; + +/// Library error type. +#[non_exhaustive] +#[derive(Error, Debug, PartialEq, Clone)] +pub enum FpError { + /// Reported when a requested resource could not be accessed, either because + /// it's missing or because the user lacks access permissions. + #[error("not found: {0}")] + AccessDenied(String), + /// Reported when Fast Pair functionality does not behave according to the + /// specification. For example, advertisement packets with invalid lengths, + /// JSON data with bad formatting, etc. + #[error("contract violation: {0}")] + ContractViolation(String), + /// Reported when trying to invoke Fast Pair functionality that is currently + /// not implemented. + #[error("feature not implemented: {0}")] + NotImplemented(String), + /// Reported when a bug occurs inside the library. Whenever a seemingly + /// impossible error condition arises where you could call `expect()`, + /// return this error instead. + #[error("internal error: {0}")] + Internal(String), + /// Reported when an error was intentionally raised by test code. + #[error("intentional error")] + #[cfg(test)] + Test, +} diff --git a/fastpair/rust/demo/rust/src/fetcher/common.rs b/fastpair/rust/demo/rust/src/fetcher/common.rs index ea7ed605..e3d5cf81 100644 --- a/fastpair/rust/demo/rust/src/fetcher/common.rs +++ b/fastpair/rust/demo/rust/src/fetcher/common.rs @@ -14,15 +14,12 @@ use serde::Deserialize; -use crate::advertisement::ModelId; +use crate::{advertisement::ModelId, error::FpError}; /// Types that can fetch Fast Pair data from external storage (e.g. filesystem, /// remote server). pub(crate) trait FpFetcher { - fn get_device_info_from_model_id( - &self, - model_id: &ModelId, - ) -> Result; + fn get_device_info_from_model_id(&self, model_id: &ModelId) -> Result; } /// Holds Fast Pair device information parsed from JSON. diff --git a/fastpair/rust/demo/rust/src/fetcher/fs.rs b/fastpair/rust/demo/rust/src/fetcher/fs.rs index ee45955e..df2db495 100644 --- a/fastpair/rust/demo/rust/src/fetcher/fs.rs +++ b/fastpair/rust/demo/rust/src/fetcher/fs.rs @@ -16,6 +16,7 @@ use std::fs; use crate::{ advertisement::ModelId, + error::FpError, fetcher::{DeviceInfo, FpFetcher, JsonData}, }; @@ -35,14 +36,13 @@ impl FpFetcher for FpFetcherFs { /// this information is saved locally. In the future, this should instead /// be retrieved from a remote server and cached. /// b/294456411 - fn get_device_info_from_model_id( - &self, - model_id: &ModelId, - ) -> Result { + fn get_device_info_from_model_id(&self, model_id: &ModelId) -> Result { let file_path = format!("{}/{}.json", self.path, model_id); - let contents = fs::read_to_string(file_path).expect("Couldn't find or open file."); + let contents = fs::read_to_string(file_path) + .or_else(|err| Err(FpError::AccessDenied(err.to_string())))?; - let model_info: JsonData = serde_json::from_str(&contents)?; + let model_info: JsonData = serde_json::from_str(&contents) + .or_else(|err| Err(FpError::ContractViolation(err.to_string())))?; Ok(model_info.device()) } diff --git a/fastpair/rust/demo/rust/src/fetcher/mock.rs b/fastpair/rust/demo/rust/src/fetcher/mock.rs index 93f9351f..1d82efb5 100644 --- a/fastpair/rust/demo/rust/src/fetcher/mock.rs +++ b/fastpair/rust/demo/rust/src/fetcher/mock.rs @@ -14,30 +14,25 @@ use crate::{ advertisement::ModelId, + error::FpError, fetcher::{DeviceInfo, FpFetcher}, }; /// A struct for mocking retrieval of Fast Pair data. pub(crate) struct FpFetcherMock { - get_device_info_from_model_id: Result, + device_info_from_model_id: Result, } impl FpFetcherMock { - pub(crate) fn new(get_device_info_from_model_id: Result) -> Self { + pub(crate) fn new(device_info_from_model_id: Result) -> Self { FpFetcherMock { - get_device_info_from_model_id, + device_info_from_model_id, } } } impl FpFetcher for FpFetcherMock { - fn get_device_info_from_model_id( - &self, - _model_id: &ModelId, - ) -> Result { - match &self.get_device_info_from_model_id { - Ok(result) => Ok(result.clone()), - Err(_) => Err(anyhow::anyhow!("intentional mock error")), - } + fn get_device_info_from_model_id(&self, _model_id: &ModelId) -> Result { + self.device_info_from_model_id.clone() } } diff --git a/fastpair/rust/demo/rust/src/lib.rs b/fastpair/rust/demo/rust/src/lib.rs index 34ba9081..3d3f5bfb 100644 --- a/fastpair/rust/demo/rust/src/lib.rs +++ b/fastpair/rust/demo/rust/src/lib.rs @@ -16,4 +16,5 @@ mod advertisement; mod api; mod bridge_generated; /* AUTO INJECTED BY flutter_rust_bridge. This line may not be accurate, and you can change it according to your needs. */ mod decoder; +mod error; mod fetcher; diff --git a/fastpair/rust/demo/windows/flutter/generated_plugin_registrant.cc b/fastpair/rust/demo/windows/flutter/generated_plugin_registrant.cc index 5da9cfa4..8b6d4680 100644 --- a/fastpair/rust/demo/windows/flutter/generated_plugin_registrant.cc +++ b/fastpair/rust/demo/windows/flutter/generated_plugin_registrant.cc @@ -1,24 +1,10 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - // // Generated file. Do not edit. // // clang-format off -#include "./generated_plugin_registrant.h" +#include "generated_plugin_registrant.h" void RegisterPlugins(flutter::PluginRegistry* registry) { diff --git a/fastpair/rust/demo/windows/flutter/generated_plugin_registrant.h b/fastpair/rust/demo/windows/flutter/generated_plugin_registrant.h index 7e32ef7b..dc139d85 100644 --- a/fastpair/rust/demo/windows/flutter/generated_plugin_registrant.h +++ b/fastpair/rust/demo/windows/flutter/generated_plugin_registrant.h @@ -1,17 +1,3 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - // // Generated file. Do not edit. //