bluetooth_traits/
blocklist.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::RefCell;
6use std::collections::HashMap;
7use std::string::String;
8
9use embedder_traits::resources::{self, Resource};
10use regex::Regex;
11
12const EXCLUDE_READS: &str = "exclude-reads";
13const EXCLUDE_WRITES: &str = "exclude-writes";
14const VALID_UUID_REGEX: &str = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
15
16thread_local!(pub static BLUETOOTH_BLOCKLIST: RefCell<BluetoothBlocklist> =
17              RefCell::new(BluetoothBlocklist(parse_blocklist())));
18
19pub fn uuid_is_blocklisted(uuid: &str, exclude_type: Blocklist) -> bool {
20    BLUETOOTH_BLOCKLIST.with(|blist| match exclude_type {
21        Blocklist::All => blist.borrow().is_blocklisted(uuid),
22        Blocklist::Reads => blist.borrow().is_blocklisted_for_reads(uuid),
23        Blocklist::Writes => blist.borrow().is_blocklisted_for_writes(uuid),
24    })
25}
26
27pub struct BluetoothBlocklist(Option<HashMap<String, Blocklist>>);
28
29#[derive(Eq, PartialEq)]
30pub enum Blocklist {
31    All, // Read and Write
32    Reads,
33    Writes,
34}
35
36impl BluetoothBlocklist {
37    // https://webbluetoothcg.github.io/web-bluetooth/#blocklisted
38    pub fn is_blocklisted(&self, uuid: &str) -> bool {
39        match self.0 {
40            Some(ref map) => map.get(uuid).is_some_and(|et| et.eq(&Blocklist::All)),
41            None => false,
42        }
43    }
44
45    // https://webbluetoothcg.github.io/web-bluetooth/#blocklisted-for-reads
46    pub fn is_blocklisted_for_reads(&self, uuid: &str) -> bool {
47        match self.0 {
48            Some(ref map) => map
49                .get(uuid)
50                .is_some_and(|et| et.eq(&Blocklist::All) || et.eq(&Blocklist::Reads)),
51            None => false,
52        }
53    }
54
55    // https://webbluetoothcg.github.io/web-bluetooth/#blocklisted-for-writes
56    pub fn is_blocklisted_for_writes(&self, uuid: &str) -> bool {
57        match self.0 {
58            Some(ref map) => map
59                .get(uuid)
60                .is_some_and(|et| et.eq(&Blocklist::All) || et.eq(&Blocklist::Writes)),
61            None => false,
62        }
63    }
64}
65
66// https://webbluetoothcg.github.io/web-bluetooth/#the-blocklist
67fn parse_blocklist() -> Option<HashMap<String, Blocklist>> {
68    // Step 1 missing, currently we parse ./resources/gatt_blocklist.txt.
69    let valid_uuid_regex = Regex::new(VALID_UUID_REGEX).unwrap();
70    let content = resources::read_string(Resource::BluetoothBlocklist);
71    // Step 3
72    let mut result = HashMap::new();
73    // Step 2 and 4
74    for line in content.lines() {
75        // Step 4.1
76        if line.is_empty() || line.starts_with('#') {
77            continue;
78        }
79        let mut exclude_type = Blocklist::All;
80        let mut words = line.split_whitespace();
81        let uuid = match words.next() {
82            Some(uuid) => uuid,
83            None => continue,
84        };
85        if !valid_uuid_regex.is_match(uuid) {
86            return None;
87        }
88        match words.next() {
89            // Step 4.2 We already have an initialized exclude_type variable with Blocklist::All.
90            None => {},
91            // Step 4.3
92            Some(EXCLUDE_READS) => {
93                exclude_type = Blocklist::Reads;
94            },
95            Some(EXCLUDE_WRITES) => {
96                exclude_type = Blocklist::Writes;
97            },
98            // Step 4.4
99            _ => {
100                return None;
101            },
102        }
103        // Step 4.5
104        if result.contains_key(uuid) {
105            return None;
106        }
107        // Step 4.6
108        result.insert(uuid.to_string(), exclude_type);
109    }
110    // Step 5
111    Some(result)
112}