Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit aa529fbd authored by howardchung's avatar howardchung
Browse files

Floss: Implement service allowlist persistentcy

Admin policy will be stored in /var/lib/bluetooth/admin_policy.json

It will be updated upon a successful SetServiceAllowedlist call, and it
will be loaded upon btadapterd starts.

Bug: 239470589
Test: tested it with the following steps
Set allowed list via gdbus
check /var/lib/bluetooth/admin_policy.json is updated correctly
restart btadapterd
Get allowed list via gdbus and check if it's persistent
Tag: #floss
Change-Id: I141f60a618e905c8cfc4c58fd3966faa2c5de273

Change-Id: I158e90e9ac2eff95eda0d19bd18e4355ce5bd144
parent 0c540fd3
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ mod iface_bluetooth_gatt;
mod iface_bluetooth_media;

const DBUS_SERVICE_NAME: &str = "org.chromium.bluetooth";
const ADMIN_SETTINGS_FILE_PATH: &str = "/var/lib/bluetooth/admin_policy.json";

fn make_object_name(idx: i32, name: &str) -> String {
    String::from(format!("/org/chromium/bluetooth/hci{}/{}", idx, name))
@@ -127,7 +128,8 @@ fn main() -> Result<(), Box<dyn Error>> {
        battery_provider_manager.clone(),
        tx.clone(),
    ))));
    let bluetooth_admin = Arc::new(Mutex::new(Box::new(BluetoothAdmin::new())));
    let bluetooth_admin =
        Arc::new(Mutex::new(Box::new(BluetoothAdmin::new(String::from(ADMIN_SETTINGS_FILE_PATH)))));
    let bluetooth = Arc::new(Mutex::new(Box::new(Bluetooth::new(
        tx.clone(),
        intf.clone(),
+1 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ nix = "0.19"
num-derive = "0.3"
num-traits = "0.2"
rand = { version = "0.8.3", features = ["small_rng"] }
serde_json = "1.0"
tokio = { version = "1", features = ['bytes', 'fs', 'io-util', 'libc', 'macros', 'memchr', 'mio', 'net', 'num_cpus', 'rt', 'rt-multi-thread', 'sync', 'time', 'tokio-macros'] }

[lib]
+116 −5
Original line number Diff line number Diff line
//! Anything related to the Admin API (IBluetoothAdmin).

use std::collections::HashSet;
use std::fs::File;
use std::io::{Read, Result, Write};
use std::sync::{Arc, Mutex};

use crate::bluetooth::{Bluetooth, BluetoothDevice, IBluetooth};
use crate::uuid::UuidHelper;
use bt_topshim::btif::Uuid128Bit;
use log::warn;
use log::{info, warn};
use serde_json::{json, Value};

/// Defines the Admin API
pub trait IBluetoothAdmin {
@@ -25,17 +29,24 @@ pub struct PolicyEffect {
}

pub struct BluetoothAdmin {
    path: String,
    adapter: Option<Arc<Mutex<Box<Bluetooth>>>>,
    allowed_services: HashSet<Uuid128Bit>,
}

impl BluetoothAdmin {
    pub fn new() -> BluetoothAdmin {
        // TODO: Load all admin settings from a file.
        BluetoothAdmin {
    pub fn new(path: String) -> BluetoothAdmin {
        // default admin settings
        let mut admin = BluetoothAdmin {
            path,
            adapter: None,
            allowed_services: HashSet::new(), //empty means allowed all services
        };

        if admin.load_config().is_err() {
            warn!("Failed to load config file");
        }
        admin
    }

    pub fn set_adapter(&mut self, adapter: Arc<Mutex<Box<Bluetooth>>>) {
@@ -49,6 +60,48 @@ impl BluetoothAdmin {
            .cloned()
            .collect::<Vec<Uuid128Bit>>()
    }

    fn load_config(&mut self) -> Result<()> {
        let mut file = File::open(&self.path)?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;
        let json = serde_json::from_str::<Value>(contents.as_str()).unwrap();
        if let Some(_res) = self.load_config_from_json(&json) {
            info!("Load settings from {} successfully", &self.path);
        }
        Ok(())
    }

    fn load_config_from_json(&mut self, json: &Value) -> Option<bool> {
        let allowed_services: Vec<Uuid128Bit> = json
            .get("allowed_services")?
            .as_array()?
            .iter()
            .filter_map(|v| UuidHelper::from_string(v.as_str()?))
            .collect();
        self.set_allowed_services(allowed_services);
        Some(true)
    }

    fn write_config(&self) -> Result<()> {
        let mut f = File::create(&self.path)?;
        f.write_all(self.get_config_string().as_bytes()).and_then(|_| {
            info!("Write settings into {} successfully", &self.path);
            Ok(())
        })
    }

    fn get_config_string(&self) -> String {
        serde_json::to_string_pretty(&json!({
            "allowed_services":
                self.get_allowed_services()
                    .iter()
                    .map(UuidHelper::to_string)
                    .collect::<Vec<String>>()
        }))
        .ok()
        .unwrap()
    }
}

impl IBluetoothAdmin for BluetoothAdmin {
@@ -66,6 +119,9 @@ impl IBluetoothAdmin for BluetoothAdmin {
        if let Some(adapter) = &self.adapter {
            let allowed_services = self.get_allowed_services();
            adapter.lock().unwrap().toggle_enabled_profiles(&allowed_services);
            if self.write_config().is_err() {
                warn!("Failed to write config");
            }
            return true;
        }

@@ -96,16 +152,18 @@ impl IBluetoothAdmin for BluetoothAdmin {
#[cfg(test)]
mod tests {
    use crate::bluetooth_admin::{BluetoothAdmin, IBluetoothAdmin};
    use crate::uuid::UuidHelper;
    use bt_topshim::btif::Uuid128Bit;

    // A workaround needed for linking. For more details, check the comment in
    // system/gd/rust/topshim/facade/src/main.rs
    #[allow(unused)]
    use bt_shim::*;
    use serde_json::{json, Value};

    #[test]
    fn test_set_service_allowed() {
        let mut admin = BluetoothAdmin::new();
        let mut admin = BluetoothAdmin::new(String::from(""));
        let uuid1: Uuid128Bit = [1; 16];
        let uuid2: Uuid128Bit = [2; 16];
        let uuid3: Uuid128Bit = [3; 16];
@@ -133,4 +191,57 @@ mod tests {
        assert!(!admin.is_service_allowed(uuid3));
        assert_eq!(admin.get_blocked_services(&uuids), vec![uuid1.clone(), uuid3.clone()]);
    }

    fn get_sorted_allowed_services_from_config(admin: &BluetoothAdmin) -> Vec<String> {
        let mut v = serde_json::from_str::<Value>(admin.get_config_string().as_str())
            .unwrap()
            .get("allowed_services")
            .unwrap()
            .as_array()
            .unwrap()
            .iter()
            .map(|v| String::from(v.as_str().unwrap()))
            .collect::<Vec<String>>();
        v.sort();
        v
    }

    fn get_sorted_allowed_services(admin: &BluetoothAdmin) -> Vec<Uuid128Bit> {
        let mut v = admin.get_allowed_services();
        v.sort();
        v
    }

    #[test]
    fn test_config() {
        let mut admin = BluetoothAdmin::new(String::from(""));
        let a2dp_sink = "0000110b-0000-1000-8000-00805f9b34fb";
        let a2dp_source = "0000110a-0000-1000-8000-00805f9b34fb";

        let a2dp_sink_uuid128 = UuidHelper::from_string(a2dp_sink).unwrap();
        let a2dp_source_uuid128 = UuidHelper::from_string(a2dp_source).unwrap();

        let mut allowed_services = vec![a2dp_sink, a2dp_source];

        let mut allowed_services_128 = vec![a2dp_sink_uuid128, a2dp_source_uuid128];

        allowed_services.sort();
        allowed_services_128.sort();

        // valid configuration
        assert_eq!(
            admin.load_config_from_json(&json!({
                "allowed_services": allowed_services.clone()
            })),
            Some(true)
        );
        assert_eq!(get_sorted_allowed_services(&admin), allowed_services_128);
        assert_eq!(get_sorted_allowed_services_from_config(&admin), allowed_services);

        // invalid configuration
        assert_eq!(admin.load_config_from_json(&json!({ "allowed_services": a2dp_sink })), None);
        // config should remain unchanged
        assert_eq!(get_sorted_allowed_services(&admin), allowed_services_128);
        assert_eq!(get_sorted_allowed_services_from_config(&admin), allowed_services);
    }
}