aboutsummaryrefslogtreecommitdiffstats
path: root/components/shared/bluetooth
diff options
context:
space:
mode:
Diffstat (limited to 'components/shared/bluetooth')
-rw-r--r--components/shared/bluetooth/Cargo.toml17
-rw-r--r--components/shared/bluetooth/blocklist.rs112
-rw-r--r--components/shared/bluetooth/lib.rs119
-rw-r--r--components/shared/bluetooth/scanfilter.rs143
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()
+ }
+}