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

Commit ad2a7084 authored by Hansong Zhang's avatar Hansong Zhang
Browse files

BluetoothManager: Introduce config_util

Helper for configuration files for BluetoothManager.
Implement Set/GetFlossEnabled.
Clean up some code.

Tag: #floss
Bug: 189501475
Test: cargo test
Change-Id: I432484ee1d3902bea0a45f663725608f21e7e6a4
parent a1ef268e
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@ dbus-tokio = "0.7.3"
dbus-crossroads = "0.3.0"
inotify = "*"
nix = "*"
serde_json = "1.0"
tokio = { version = "1.0", features = ["fs", "macros", "rt-multi-thread", "sync"] }

[[bin]]
+178 −0
Original line number Diff line number Diff line
use serde_json::{Map, Value};

// Directory for Bluetooth hci devices
pub const HCI_DEVICES_DIR: &str = "/sys/class/bluetooth";

// File to store the Bluetooth daemon to use (bluez or floss)
const BLUETOOTH_DAEMON_CURRENT: &str = "/var/lib/misc/bluetooth-daemon.current";

// File to store the config for BluetoothManager
const BTMANAGERD_CONF: &str = "/var/lib/bluetooth/btmanagerd.json";

pub fn is_floss_enabled() -> bool {
    match std::fs::read(BLUETOOTH_DAEMON_CURRENT) {
        Ok(v) => {
            let content = std::str::from_utf8(&v);
            match content {
                Ok(version) => version.contains("floss"),
                Err(_) => false,
            }
        }
        Err(_) => false,
    }
}

pub fn write_floss_enabled(enabled: bool) -> bool {
    std::fs::write(
        BLUETOOTH_DAEMON_CURRENT,
        match enabled {
            true => "floss",
            _ => "bluez",
        },
    )
    .is_ok()
}

pub fn read_config() -> std::io::Result<String> {
    std::fs::read_to_string(BTMANAGERD_CONF)
}

/// Returns whether hci N is enabled in config; defaults to true.
pub fn is_hci_n_enabled(n: i32) -> bool {
    match read_config().ok().and_then(|config| is_hci_n_enabled_internal(config, n)) {
        Some(v) => v,
        _ => true,
    }
}

fn is_hci_n_enabled_internal(config: String, n: i32) -> Option<bool> {
    serde_json::from_str::<Value>(config.as_str())
        .ok()?
        .get(format!("hci{}", n))?
        .as_object()?
        .get("enabled")?
        .as_bool()
}

// When we initialize BluetoothManager, we need to make sure the file is a well-formatted json.
pub fn fix_config_file_format() -> bool {
    match read_config() {
        Ok(s) => match serde_json::from_str::<Value>(s.as_str()) {
            Ok(_) => true,
            _ => std::fs::write(BTMANAGERD_CONF, "{}").is_ok(),
        },
        _ => std::fs::write(BTMANAGERD_CONF, "{}").is_ok(),
    }
}

pub fn modify_hci_n_enabled(n: i32, enabled: bool) -> bool {
    if !fix_config_file_format() {
        false
    } else {
        match read_config()
            .ok()
            .and_then(|config| modify_hci_n_enabled_internal(config, n, enabled))
        {
            Some(s) => std::fs::write(BTMANAGERD_CONF, s).is_ok(),
            _ => false,
        }
    }
}

fn modify_hci_n_enabled_internal(config: String, n: i32, enabled: bool) -> Option<String> {
    let hci_interface = format!("hci{}", n);
    let mut o = serde_json::from_str::<Value>(config.as_str()).ok()?;
    match o.get_mut(hci_interface.clone()) {
        Some(section) => {
            section.as_object_mut()?.insert("enabled".to_string(), Value::Bool(enabled));
            serde_json::ser::to_string_pretty(&o).ok()
        }
        _ => {
            let mut entry_map = Map::new();
            entry_map.insert("enabled".to_string(), Value::Bool(enabled));
            o.as_object_mut()?.insert(hci_interface, Value::Object(entry_map));
            serde_json::ser::to_string_pretty(&o).ok()
        }
    }
}

pub fn list_hci_devices() -> Vec<String> {
    match std::fs::read_dir(HCI_DEVICES_DIR) {
        Ok(entries) => entries
            .map(|e| e.unwrap().path().file_name().unwrap().to_str().unwrap().to_string())
            .collect::<Vec<_>>(),
        _ => Vec::new(),
    }
}

pub fn hci_devices_string_to_int(devices: Vec<String>) -> Vec<i32> {
    devices
        .into_iter()
        .filter_map(|e| if e.starts_with("hci") { e[3..].parse::<i32>().ok() } else { None })
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    fn is_hci_n_enabled_internal_wrapper(config: String, n: i32) -> bool {
        is_hci_n_enabled_internal(config, n).or(Some(true)).unwrap()
    }

    #[test]
    fn parse_hci0_enabled() {
        assert_eq!(
            is_hci_n_enabled_internal_wrapper("{\"hci0\":\n{\"enabled\": true}}".to_string(), 0),
            true
        );
    }

    #[test]
    fn modify_hci0_enabled() {
        let modified_string =
            modify_hci_n_enabled_internal("{\"hci0\":\n{\"enabled\": false}}".to_string(), 0, true)
                .unwrap();
        assert_eq!(is_hci_n_enabled_internal_wrapper(modified_string, 0), true);
    }

    #[test]
    fn modify_hci0_enabled_from_empty() {
        let modified_string = modify_hci_n_enabled_internal("{}".to_string(), 0, true).unwrap();
        assert_eq!(is_hci_n_enabled_internal_wrapper(modified_string, 0), true);
    }

    #[test]
    fn parse_hci0_not_enabled() {
        assert_eq!(
            is_hci_n_enabled_internal_wrapper("{\"hci0\":\n{\"enabled\": false}}".to_string(), 0),
            false
        );
    }

    #[test]
    fn parse_hci1_not_present() {
        assert_eq!(
            is_hci_n_enabled_internal_wrapper("{\"hci0\":\n{\"enabled\": true}}".to_string(), 1),
            true
        );
    }

    #[test]
    fn test_hci_devices_string_to_int_none() {
        assert_eq!(hci_devices_string_to_int(vec!["somethingelse".to_string()]), Vec::<i32>::new());
    }

    #[test]
    fn test_hci_devices_string_to_int_one() {
        assert_eq!(hci_devices_string_to_int(vec!["hci0".to_string()]), vec![0]);
    }

    #[test]
    fn test_hci_devices_string_to_int_two() {
        assert_eq!(
            hci_devices_string_to_int(vec!["hci0".to_string(), "hci1".to_string()]),
            vec![0, 1]
        );
    }
}
+73 −12
Original line number Diff line number Diff line
mod config_util;
mod state_machine;

use dbus::channel::MatchingReceiver;
use dbus::message::MatchRule;
use dbus_crossroads::Crossroads;
use dbus_tokio::connection;
use std::process::Command;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

const BLUEZ_INIT_TARGET: &str = "bluetoothd";

#[derive(Clone)]
struct ManagerContext {
    proxy: state_machine::StateMachineProxy,
    floss_enabled: Arc<AtomicBool>,
}

#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize config util
    config_util::fix_config_file_format();

    let context = state_machine::start_new_state_machine_context();
    let proxy = context.get_proxy();
    let manager_context = ManagerContext {
        proxy: proxy,
        floss_enabled: Arc::new(AtomicBool::new(config_util::is_floss_enabled())),
    };

    // Connect to the D-Bus system bus (this is blocking, unfortunately).
    let (resource, c) = connection::new_system_sync()?;
@@ -42,8 +61,10 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
            ("hci_interface",),
            (),
            |mut ctx, cr, (hci_interface,): (i32,)| {
                let proxy =
                    cr.data_mut::<state_machine::StateMachineProxy>(ctx.path()).unwrap().clone();
                if !config_util::modify_hci_n_enabled(hci_interface, true) {
                    println!("Config is not successfully modified");
                }
                let proxy = cr.data_mut::<ManagerContext>(ctx.path()).unwrap().proxy.clone();
                println!("Incoming Start call for hci {}!", hci_interface);
                async move {
                    let result = proxy.start_bluetooth(hci_interface).await;
@@ -61,9 +82,10 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
            ("hci_interface",),
            (),
            |mut ctx, cr, (hci_interface,): (i32,)| {
                let proxy =
                    cr.data_mut::<state_machine::StateMachineProxy>(ctx.path()).unwrap().clone();
                println!("Incoming Stop call!");
                let proxy = cr.data_mut::<ManagerContext>(ctx.path()).unwrap().proxy.clone();
                if !config_util::modify_hci_n_enabled(hci_interface, false) {
                    println!("Config is not successfully modified");
                }
                async move {
                    let result = proxy.stop_bluetooth(hci_interface).await;
                    match result {
@@ -76,8 +98,7 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
            },
        );
        b.method_with_cr_async("GetState", (), ("result",), |mut ctx, cr, ()| {
            let proxy =
                cr.data_mut::<state_machine::StateMachineProxy>(ctx.path()).unwrap().clone();
            let proxy = cr.data_mut::<ManagerContext>(ctx.path()).unwrap().proxy.clone();
            async move {
                let state = proxy.get_state().await;
                let result = match state {
@@ -94,8 +115,7 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
            ("object_path",),
            (),
            |mut ctx, cr, (object_path,): (String,)| {
                let proxy =
                    cr.data_mut::<state_machine::StateMachineProxy>(ctx.path()).unwrap().clone();
                let proxy = cr.data_mut::<ManagerContext>(ctx.path()).unwrap().proxy.clone();
                async move {
                    let result = proxy.register_state_change_observer(object_path.clone()).await;
                    match result {
@@ -113,8 +133,7 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
            ("object_path",),
            (),
            |mut ctx, cr, (object_path,): (String,)| {
                let proxy =
                    cr.data_mut::<state_machine::StateMachineProxy>(ctx.path()).unwrap().clone();
                let proxy = cr.data_mut::<ManagerContext>(ctx.path()).unwrap().proxy.clone();
                async move {
                    let result = proxy.unregister_state_change_observer(object_path.clone()).await;
                    match result {
@@ -127,11 +146,53 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
                }
            },
        );
        b.method_with_cr_async("GetFlossEnabled", (), ("result",), |mut ctx, cr, ()| {
            let enabled = cr
                .data_mut::<ManagerContext>(ctx.path())
                .unwrap()
                .clone()
                .floss_enabled
                .load(Ordering::Relaxed);

            async move { ctx.reply(Ok((enabled,))) }
        });
        b.method_with_cr_async(
            "SetFlossEnabled",
            ("enabled",),
            (),
            |mut ctx, cr, (enabled,): (bool,)| {
                let prev = cr
                    .data_mut::<ManagerContext>(ctx.path())
                    .unwrap()
                    .clone()
                    .floss_enabled
                    .swap(enabled, Ordering::Relaxed);
                config_util::write_floss_enabled(enabled);
                let proxy = cr.data_mut::<ManagerContext>(ctx.path()).unwrap().proxy.clone();

                async move {
                    if prev != enabled && enabled {
                        Command::new("initctl")
                            .args(&["stop", BLUEZ_INIT_TARGET])
                            .output()
                            .expect("failed to stop bluetoothd");
                        let _ = proxy.start_bluetooth(0).await;
                    } else if prev != enabled {
                        let _ = proxy.stop_bluetooth(0).await;
                        Command::new("initctl")
                            .args(&["start", BLUEZ_INIT_TARGET])
                            .output()
                            .expect("failed to start bluetoothd");
                    }
                    ctx.reply(Ok(()))
                }
            },
        );
    });

    // Let's add the "/org/chromium/bluetooth/Manager" path, which implements the org.chromium.bluetooth.Manager interface,
    // to the crossroads instance.
    cr.insert("/org/chromium/bluetooth/Manager", &[iface_token], proxy);
    cr.insert("/org/chromium/bluetooth/Manager", &[iface_token], manager_context);

    // We add the Crossroads instance to the connection so that incoming method calls will be handled.
    c.start_receive(
+31 −5
Original line number Diff line number Diff line
@@ -97,17 +97,33 @@ impl StateMachineProxy {
    }
}

fn pid_inotify_async_fd() -> AsyncFd<inotify::Inotify> {
    let mut pid_detector = inotify::Inotify::init().expect("cannot use inotify");
    pid_detector
        .add_watch("/var/run", inotify::WatchMask::CREATE | inotify::WatchMask::DELETE)
        .expect("failed to add watch");
    AsyncFd::new(pid_detector).expect("failed to add async fd")
}

fn hci_devices_inotify_async_fd() -> AsyncFd<inotify::Inotify> {
    let mut detector = inotify::Inotify::init().expect("cannot use inotify");
    detector
        .add_watch(
            crate::config_util::HCI_DEVICES_DIR,
            inotify::WatchMask::CREATE | inotify::WatchMask::DELETE,
        )
        .expect("failed to add watch");
    AsyncFd::new(detector).expect("failed to add async fd")
}

pub async fn mainloop<PM>(mut context: StateMachineContext<PM>)
where
    PM: ProcessManager + Send,
{
    let mut command_timeout = Alarm::new();
    let mut pid_detector = inotify::Inotify::init().expect("cannot use inotify");
    pid_detector
        .add_watch("/var/run", inotify::WatchMask::CREATE | inotify::WatchMask::DELETE)
        .expect("failed to add watch");
    let mut pid_async_fd = AsyncFd::new(pid_detector).expect("failed to add async fd");
    let command_timeout_duration = Duration::from_secs(2);
    let mut pid_async_fd = pid_inotify_async_fd();
    let mut config_async_fd = hci_devices_inotify_async_fd();
    loop {
        tokio::select! {
            Some(action) = context.rx.recv() => {
@@ -195,6 +211,16 @@ where
                fd_ready.clear_ready();
                drop(fd_ready);
            },
            r = config_async_fd.readable_mut() => {
                let mut fd_ready = r.unwrap();
                let mut buffer: [u8; 1024] = [0; 1024];
                match fd_ready.try_io(|inner| inner.get_mut().read_events(&mut buffer)) {
                    // TODO: Placeholder
                    _ => (),
                }
                fd_ready.clear_ready();
                drop(fd_ready);
            },
        }
    }
}