mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 22:56:12 -04:00
[fp-rs] Added custom FP error type to Rust code.
This commit is contained in:
@@ -134,7 +134,7 @@ mod tests {
|
||||
fn try_from_ble_address_to_classic() {
|
||||
let ble_addr = BleAddress::new(0x112233445566, BleAddressKind::Public);
|
||||
let result: Result<ClassicAddress, BluetoothError> =
|
||||
TryFrom::try_from(ble_addr);
|
||||
ble_addr.try_into();
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap().0, [0x66, 0x55, 0x44, 0x33, 0x22, 0x11]);
|
||||
|
||||
|
||||
@@ -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<DecibelMilliwatts>,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<u16>,
|
||||
fetcher: &Box<dyn FpFetcher>,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
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<Self, FpError> {
|
||||
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<dyn FpFetcher> = 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<dyn FpFetcher> = Box::new(FpFetcherMock::new(Err(anyhow::anyhow!(
|
||||
"mock intentional error"
|
||||
))));
|
||||
let fetcher: Box<dyn FpFetcher> = 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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,10 +160,9 @@ pub fn init() {
|
||||
}
|
||||
|
||||
/// Sets up `StreamSink` for Dart-Rust FFI.
|
||||
pub fn event_stream(s: StreamSink<Option<[String; 2]>>) -> Result<(), anyhow::Error> {
|
||||
pub fn event_stream(s: StreamSink<Option<[String; 2]>>) {
|
||||
let mut stream = DEVICE_STREAM.write().unwrap();
|
||||
*stream = Some(s);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Attempt classic pairing with currently displayed device.
|
||||
|
||||
@@ -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]>>(),
|
||||
))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<U: Copy>(
|
||||
service_data: &ServiceData<U>,
|
||||
) -> Result<Vec<u8>, anyhow::Error> {
|
||||
) -> Result<Vec<u8>, 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(_)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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<DeviceInfo, anyhow::Error>;
|
||||
fn get_device_info_from_model_id(&self, model_id: &ModelId) -> Result<DeviceInfo, FpError>;
|
||||
}
|
||||
|
||||
/// Holds Fast Pair device information parsed from JSON.
|
||||
|
||||
@@ -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<DeviceInfo, anyhow::Error> {
|
||||
fn get_device_info_from_model_id(&self, model_id: &ModelId) -> Result<DeviceInfo, FpError> {
|
||||
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())
|
||||
}
|
||||
|
||||
@@ -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<DeviceInfo, anyhow::Error>,
|
||||
device_info_from_model_id: Result<DeviceInfo, FpError>,
|
||||
}
|
||||
|
||||
impl FpFetcherMock {
|
||||
pub(crate) fn new(get_device_info_from_model_id: Result<DeviceInfo, anyhow::Error>) -> Self {
|
||||
pub(crate) fn new(device_info_from_model_id: Result<DeviceInfo, FpError>) -> 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<DeviceInfo, anyhow::Error> {
|
||||
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<DeviceInfo, FpError> {
|
||||
self.device_info_from_model_id.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user