diff options
Diffstat (limited to 'components/shared/bluetooth')
-rw-r--r-- | components/shared/bluetooth/Cargo.toml | 17 | ||||
-rw-r--r-- | components/shared/bluetooth/blocklist.rs | 112 | ||||
-rw-r--r-- | components/shared/bluetooth/lib.rs | 119 | ||||
-rw-r--r-- | components/shared/bluetooth/scanfilter.rs | 143 |
4 files changed, 391 insertions, 0 deletions
diff --git a/components/shared/bluetooth/Cargo.toml b/components/shared/bluetooth/Cargo.toml new file mode 100644 index 00000000000..c83b15c4cdc --- /dev/null +++ b/components/shared/bluetooth/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "bluetooth_traits" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" +edition = "2018" +publish = false + +[lib] +name = "bluetooth_traits" +path = "lib.rs" + +[dependencies] +embedder_traits = { workspace = true } +ipc-channel = { workspace = true } +regex = { workspace = true } +serde = { workspace = true } diff --git a/components/shared/bluetooth/blocklist.rs b/components/shared/bluetooth/blocklist.rs new file mode 100644 index 00000000000..ca413c41a1e --- /dev/null +++ b/components/shared/bluetooth/blocklist.rs @@ -0,0 +1,112 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::cell::RefCell; +use std::collections::HashMap; +use std::string::String; + +use embedder_traits::resources::{self, Resource}; +use regex::Regex; + +const EXCLUDE_READS: &str = "exclude-reads"; +const EXCLUDE_WRITES: &str = "exclude-writes"; +const VALID_UUID_REGEX: &str = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"; + +thread_local!(pub static BLUETOOTH_BLOCKLIST: RefCell<BluetoothBlocklist> = + RefCell::new(BluetoothBlocklist(parse_blocklist()))); + +pub fn uuid_is_blocklisted(uuid: &str, exclude_type: Blocklist) -> bool { + BLUETOOTH_BLOCKLIST.with(|blist| match exclude_type { + Blocklist::All => blist.borrow().is_blocklisted(uuid), + Blocklist::Reads => blist.borrow().is_blocklisted_for_reads(uuid), + Blocklist::Writes => blist.borrow().is_blocklisted_for_writes(uuid), + }) +} + +pub struct BluetoothBlocklist(Option<HashMap<String, Blocklist>>); + +#[derive(Eq, PartialEq)] +pub enum Blocklist { + All, // Read and Write + Reads, + Writes, +} + +impl BluetoothBlocklist { + // https://webbluetoothcg.github.io/web-bluetooth/#blocklisted + pub fn is_blocklisted(&self, uuid: &str) -> bool { + match self.0 { + Some(ref map) => map.get(uuid).map_or(false, |et| et.eq(&Blocklist::All)), + None => false, + } + } + + // https://webbluetoothcg.github.io/web-bluetooth/#blocklisted-for-reads + pub fn is_blocklisted_for_reads(&self, uuid: &str) -> bool { + match self.0 { + Some(ref map) => map.get(uuid).map_or(false, |et| { + et.eq(&Blocklist::All) || et.eq(&Blocklist::Reads) + }), + None => false, + } + } + + // https://webbluetoothcg.github.io/web-bluetooth/#blocklisted-for-writes + pub fn is_blocklisted_for_writes(&self, uuid: &str) -> bool { + match self.0 { + Some(ref map) => map.get(uuid).map_or(false, |et| { + et.eq(&Blocklist::All) || et.eq(&Blocklist::Writes) + }), + None => false, + } + } +} + +// https://webbluetoothcg.github.io/web-bluetooth/#parsing-the-blocklist +fn parse_blocklist() -> Option<HashMap<String, Blocklist>> { + // Step 1 missing, currently we parse ./resources/gatt_blocklist.txt. + let valid_uuid_regex = Regex::new(VALID_UUID_REGEX).unwrap(); + let content = resources::read_string(Resource::BluetoothBlocklist); + // Step 3 + let mut result = HashMap::new(); + // Step 2 and 4 + for line in content.lines() { + // Step 4.1 + if line.is_empty() || line.starts_with('#') { + continue; + } + let mut exclude_type = Blocklist::All; + let mut words = line.split_whitespace(); + let uuid = match words.next() { + Some(uuid) => uuid, + None => continue, + }; + if !valid_uuid_regex.is_match(uuid) { + return None; + } + match words.next() { + // Step 4.2 We already have an initialized exclude_type variable with Blocklist::All. + None => {}, + // Step 4.3 + Some(EXCLUDE_READS) => { + exclude_type = Blocklist::Reads; + }, + Some(EXCLUDE_WRITES) => { + exclude_type = Blocklist::Writes; + }, + // Step 4.4 + _ => { + return None; + }, + } + // Step 4.5 + if result.contains_key(uuid) { + return None; + } + // Step 4.6 + result.insert(uuid.to_string(), exclude_type); + } + // Step 5 + Some(result) +} diff --git a/components/shared/bluetooth/lib.rs b/components/shared/bluetooth/lib.rs new file mode 100644 index 00000000000..5910aa3c284 --- /dev/null +++ b/components/shared/bluetooth/lib.rs @@ -0,0 +1,119 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +pub mod blocklist; +pub mod scanfilter; + +use ipc_channel::ipc::IpcSender; +use serde::{Deserialize, Serialize}; + +use crate::scanfilter::{BluetoothScanfilterSequence, RequestDeviceoptions}; + +#[derive(Debug, Deserialize, Serialize)] +pub enum BluetoothError { + Type(String), + Network, + NotFound, + NotSupported, + Security, + InvalidState, +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum GATTType { + PrimaryService, + Characteristic, + IncludedService, + Descriptor, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct BluetoothDeviceMsg { + // Bluetooth Device properties + pub id: String, + pub name: Option<String>, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct BluetoothServiceMsg { + pub uuid: String, + pub is_primary: bool, + pub instance_id: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct BluetoothCharacteristicMsg { + // Characteristic + pub uuid: String, + pub instance_id: String, + // Characteristic properties + pub broadcast: bool, + pub read: bool, + pub write_without_response: bool, + pub write: bool, + pub notify: bool, + pub indicate: bool, + pub authenticated_signed_writes: bool, + pub reliable_write: bool, + pub writable_auxiliaries: bool, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct BluetoothDescriptorMsg { + pub uuid: String, + pub instance_id: String, +} + +pub type BluetoothServicesMsg = Vec<BluetoothServiceMsg>; + +pub type BluetoothCharacteristicsMsg = Vec<BluetoothCharacteristicMsg>; + +pub type BluetoothDescriptorsMsg = Vec<BluetoothDescriptorMsg>; + +pub type BluetoothResult<T> = Result<T, BluetoothError>; + +pub type BluetoothResponseResult = Result<BluetoothResponse, BluetoothError>; + +#[derive(Debug, Deserialize, Serialize)] +pub enum BluetoothRequest { + RequestDevice(RequestDeviceoptions, IpcSender<BluetoothResponseResult>), + GATTServerConnect(String, IpcSender<BluetoothResponseResult>), + GATTServerDisconnect(String, IpcSender<BluetoothResult<()>>), + GetGATTChildren( + String, + Option<String>, + bool, + GATTType, + IpcSender<BluetoothResponseResult>, + ), + ReadValue(String, IpcSender<BluetoothResponseResult>), + WriteValue(String, Vec<u8>, IpcSender<BluetoothResponseResult>), + EnableNotification(String, bool, IpcSender<BluetoothResponseResult>), + WatchAdvertisements(String, IpcSender<BluetoothResponseResult>), + SetRepresentedToNull(Vec<String>, Vec<String>, Vec<String>), + IsRepresentedDeviceNull(String, IpcSender<bool>), + GetAvailability(IpcSender<BluetoothResponseResult>), + MatchesFilter( + String, + BluetoothScanfilterSequence, + IpcSender<BluetoothResult<bool>>, + ), + Test(String, IpcSender<BluetoothResult<()>>), + Exit, +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum BluetoothResponse { + RequestDevice(BluetoothDeviceMsg), + GATTServerConnect(bool), + GetPrimaryServices(BluetoothServicesMsg, bool), + GetIncludedServices(BluetoothServicesMsg, bool), + GetCharacteristics(BluetoothCharacteristicsMsg, bool), + GetDescriptors(BluetoothDescriptorsMsg, bool), + ReadValue(Vec<u8>), + WriteValue(Vec<u8>), + EnableNotification(()), + WatchAdvertisements(()), + GetAvailability(bool), +} diff --git a/components/shared/bluetooth/scanfilter.rs b/components/shared/bluetooth/scanfilter.rs new file mode 100644 index 00000000000..b5590e1aa40 --- /dev/null +++ b/components/shared/bluetooth/scanfilter.rs @@ -0,0 +1,143 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::collections::{HashMap, HashSet}; +use std::slice::Iter; + +use serde::{Deserialize, Serialize}; + +// A device name can never be longer than 29 bytes. An adv packet is at most +// 31 bytes long. The length and identifier of the length field take 2 bytes. +// That leaves 29 bytes for the name. +const MAX_NAME_LENGTH: usize = 29; + +#[derive(Debug, Deserialize, Serialize)] +pub struct ServiceUUIDSequence(Vec<String>); + +impl ServiceUUIDSequence { + pub fn new(vec: Vec<String>) -> ServiceUUIDSequence { + ServiceUUIDSequence(vec) + } + + fn get_services_set(&self) -> HashSet<String> { + self.0.iter().map(String::clone).collect() + } +} + +type ManufacturerData = HashMap<u16, (Vec<u8>, Vec<u8>)>; +type ServiceData = HashMap<String, (Vec<u8>, Vec<u8>)>; + +#[derive(Debug, Deserialize, Serialize)] +pub struct BluetoothScanfilter { + name: Option<String>, + name_prefix: String, + services: ServiceUUIDSequence, + manufacturer_data: Option<ManufacturerData>, + service_data: Option<ServiceData>, +} + +impl BluetoothScanfilter { + pub fn new( + name: Option<String>, + name_prefix: String, + services: Vec<String>, + manufacturer_data: Option<ManufacturerData>, + service_data: Option<ServiceData>, + ) -> BluetoothScanfilter { + BluetoothScanfilter { + name, + name_prefix, + services: ServiceUUIDSequence::new(services), + manufacturer_data: manufacturer_data, + service_data, + } + } + + pub fn get_name(&self) -> Option<&str> { + self.name.as_deref() + } + + pub fn get_name_prefix(&self) -> &str { + &self.name_prefix + } + + pub fn get_services(&self) -> &[String] { + &self.services.0 + } + + pub fn get_manufacturer_data(&self) -> Option<&ManufacturerData> { + self.manufacturer_data.as_ref() + } + + pub fn get_service_data(&self) -> Option<&ServiceData> { + self.service_data.as_ref() + } + + pub fn is_empty_or_invalid(&self) -> bool { + (self.name.is_none() && + self.name_prefix.is_empty() && + self.get_services().is_empty() && + self.manufacturer_data.is_none() && + self.service_data.is_none()) || + self.get_name().unwrap_or("").len() > MAX_NAME_LENGTH || + self.name_prefix.len() > MAX_NAME_LENGTH + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct BluetoothScanfilterSequence(Vec<BluetoothScanfilter>); + +impl BluetoothScanfilterSequence { + pub fn new(vec: Vec<BluetoothScanfilter>) -> BluetoothScanfilterSequence { + BluetoothScanfilterSequence(vec) + } + + pub fn has_empty_or_invalid_filter(&self) -> bool { + self.0.iter().any(BluetoothScanfilter::is_empty_or_invalid) + } + + pub fn iter(&self) -> Iter<BluetoothScanfilter> { + self.0.iter() + } + + fn get_services_set(&self) -> HashSet<String> { + self.iter() + .flat_map(|filter| filter.services.get_services_set()) + .collect() + } + + fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct RequestDeviceoptions { + filters: BluetoothScanfilterSequence, + optional_services: ServiceUUIDSequence, +} + +impl RequestDeviceoptions { + pub fn new( + filters: BluetoothScanfilterSequence, + services: ServiceUUIDSequence, + ) -> RequestDeviceoptions { + RequestDeviceoptions { + filters: filters, + optional_services: services, + } + } + + pub fn get_filters(&self) -> &BluetoothScanfilterSequence { + &self.filters + } + + pub fn get_services_set(&self) -> HashSet<String> { + &self.filters.get_services_set() | &self.optional_services.get_services_set() + } + + pub fn is_accepting_all_devices(&self) -> bool { + self.filters.is_empty() + } +} |