AdapterStateChange and HciDeviceChange callback
* Move observers to main.
* Add dbus callback util.
* Use UpstartInvoker instead of NativeSubprocess.
* Fix multiple issues.
Bug: 189501676
Tag: #floss
Test: cargo test
Test: run btmanagerd and try sending dbus commands
Change-Id: Ie0a678a6f5622221f5d12bb2b8b82a72971ab949
diff --git a/system/gd/rust/linux/mgmt/Cargo.toml b/system/gd/rust/linux/mgmt/Cargo.toml
index 042d808..e182273 100644
--- a/system/gd/rust/linux/mgmt/Cargo.toml
+++ b/system/gd/rust/linux/mgmt/Cargo.toml
@@ -16,6 +16,7 @@
dbus-crossroads = "0.3.0"
inotify = "*"
nix = "*"
+regex = "1.5"
serde_json = "1.0"
tokio = { version = "1.0", features = ["fs", "macros", "rt-multi-thread", "sync"] }
diff --git a/system/gd/rust/linux/mgmt/src/bin/btmanagerd/config_util.rs b/system/gd/rust/linux/mgmt/src/bin/btmanagerd/config_util.rs
index e57c165..130e974 100644
--- a/system/gd/rust/linux/mgmt/src/bin/btmanagerd/config_util.rs
+++ b/system/gd/rust/linux/mgmt/src/bin/btmanagerd/config_util.rs
@@ -96,7 +96,11 @@
}
}
-pub fn list_hci_devices() -> Vec<String> {
+pub fn list_hci_devices() -> Vec<i32> {
+ hci_devices_string_to_int(list_hci_devices_string())
+}
+
+fn list_hci_devices_string() -> 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())
@@ -105,7 +109,7 @@
}
}
-pub fn hci_devices_string_to_int(devices: Vec<String>) -> Vec<i32> {
+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 })
diff --git a/system/gd/rust/linux/mgmt/src/bin/btmanagerd/dbus_callback_util.rs b/system/gd/rust/linux/mgmt/src/bin/btmanagerd/dbus_callback_util.rs
new file mode 100644
index 0000000..7ab45db
--- /dev/null
+++ b/system/gd/rust/linux/mgmt/src/bin/btmanagerd/dbus_callback_util.rs
@@ -0,0 +1,71 @@
+use dbus::nonblock::{Proxy, SyncConnection};
+use std::sync::Arc;
+use std::time::Duration;
+use tokio::sync::Mutex;
+
+#[derive(Clone)]
+pub struct DbusCallbackUtil {
+ dbus_connection: Arc<SyncConnection>,
+ state_change_observers: Arc<Mutex<Vec<String>>>,
+ hci_device_change_observer: Arc<Mutex<Vec<String>>>,
+}
+
+impl DbusCallbackUtil {
+ pub fn new(
+ dbus_connection: Arc<SyncConnection>,
+ state_change_observers: Arc<Mutex<Vec<String>>>,
+ hci_device_change_observer: Arc<Mutex<Vec<String>>>,
+ ) -> Self {
+ DbusCallbackUtil {
+ dbus_connection: dbus_connection,
+ state_change_observers: state_change_observers,
+ hci_device_change_observer: hci_device_change_observer,
+ }
+ }
+
+ pub async fn send_hci_device_change_callback(
+ &self,
+ hci_device: i32,
+ present: bool,
+ ) -> Result<(), Box<dyn std::error::Error>> {
+ for path in &*self.hci_device_change_observer.lock().await {
+ let proxy = Proxy::new(
+ "org.chromium.bluetooth.Manager",
+ path,
+ Duration::from_secs(2),
+ self.dbus_connection.clone(),
+ );
+ proxy
+ .method_call(
+ "org.chromium.bluetooth.Manager",
+ "HciDeviceChangeCallback",
+ (hci_device, present),
+ )
+ .await?;
+ }
+ Ok(())
+ }
+
+ pub async fn send_adapter_state_change_callback(
+ &self,
+ hci_device: i32,
+ state: i32,
+ ) -> Result<(), Box<dyn std::error::Error>> {
+ for path in &*self.state_change_observers.lock().await {
+ let proxy = Proxy::new(
+ "org.chromium.bluetooth.Manager",
+ path,
+ Duration::from_secs(2),
+ self.dbus_connection.clone(),
+ );
+ proxy
+ .method_call(
+ "org.chromium.bluetooth.Manager",
+ "AdapterStateChangeCallback",
+ (hci_device, state),
+ )
+ .await?;
+ }
+ Ok(())
+ }
+}
diff --git a/system/gd/rust/linux/mgmt/src/bin/btmanagerd/main.rs b/system/gd/rust/linux/mgmt/src/bin/btmanagerd/main.rs
index 27ac7b1..43ab8ca 100644
--- a/system/gd/rust/linux/mgmt/src/bin/btmanagerd/main.rs
+++ b/system/gd/rust/linux/mgmt/src/bin/btmanagerd/main.rs
@@ -1,13 +1,16 @@
mod config_util;
+mod dbus_callback_util;
mod state_machine;
use dbus::channel::MatchingReceiver;
use dbus::message::MatchRule;
+use dbus::nonblock::SyncConnection;
use dbus_crossroads::Crossroads;
use dbus_tokio::connection;
use std::process::Command;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
+use tokio::sync::Mutex;
const BLUEZ_INIT_TARGET: &str = "bluetoothd";
@@ -15,6 +18,9 @@
struct ManagerContext {
proxy: state_machine::StateMachineProxy,
floss_enabled: Arc<AtomicBool>,
+ dbus_connection: Arc<SyncConnection>,
+ state_change_observer: Arc<Mutex<Vec<String>>>,
+ hci_device_change_observer: Arc<Mutex<Vec<String>>>,
}
#[tokio::main]
@@ -22,15 +28,26 @@
// Initialize config util
config_util::fix_config_file_format();
+ // Connect to the D-Bus system bus (this is blocking, unfortunately).
+ let (resource, conn) = connection::new_system_sync()?;
+
let context = state_machine::start_new_state_machine_context();
let proxy = context.get_proxy();
+ let state_change_observer = Arc::new(Mutex::new(Vec::new()));
+ let hci_device_change_observer = Arc::new(Mutex::new(Vec::new()));
let manager_context = ManagerContext {
proxy: proxy,
floss_enabled: Arc::new(AtomicBool::new(config_util::is_floss_enabled())),
+ dbus_connection: conn.clone(),
+ state_change_observer: state_change_observer.clone(),
+ hci_device_change_observer: hci_device_change_observer.clone(),
};
- // Connect to the D-Bus system bus (this is blocking, unfortunately).
- let (resource, c) = connection::new_system_sync()?;
+ let dbus_callback_util = dbus_callback_util::DbusCallbackUtil::new(
+ conn.clone(),
+ state_change_observer.clone(),
+ hci_device_change_observer.clone(),
+ );
// The resource is a task that should be spawned onto a tokio compatible
// reactor ASAP. If the resource ever finishes, you lost connection to D-Bus.
@@ -40,7 +57,7 @@
});
// Let's request a name on the bus, so that clients can find us.
- c.request_name("org.chromium.bluetooth.Manager", false, true, false).await?;
+ conn.request_name("org.chromium.bluetooth.Manager", false, true, false).await?;
// Create a new crossroads instance.
// The instance is configured so that introspection and properties interfaces
@@ -49,7 +66,7 @@
// Enable async support for the crossroads instance.
cr.set_async_support(Some((
- c.clone(),
+ conn.clone(),
Box::new(|x| {
tokio::spawn(x);
}),
@@ -101,30 +118,20 @@
let proxy = cr.data_mut::<ManagerContext>(ctx.path()).unwrap().proxy.clone();
async move {
let state = proxy.get_state().await;
- let result = match state {
- state_machine::State::Off => 0,
- state_machine::State::TurningOn => 1,
- state_machine::State::On => 2,
- state_machine::State::TurningOff => 3,
- };
+ let result = state_machine::state_to_i32(state);
ctx.reply(Ok((result,)))
}
});
+ // Register AdapterStateChangeCallback(int hci_device, int state) on specified object_path
b.method_with_cr_async(
"RegisterStateChangeObserver",
("object_path",),
(),
|mut ctx, cr, (object_path,): (String,)| {
- let proxy = cr.data_mut::<ManagerContext>(ctx.path()).unwrap().proxy.clone();
+ let manager_context = cr.data_mut::<ManagerContext>(ctx.path()).unwrap().clone();
async move {
- let result = proxy.register_state_change_observer(object_path.clone()).await;
- match result {
- Ok(()) => ctx.reply(Ok(())),
- Err(_) => ctx.reply(Err(dbus_crossroads::MethodErr::failed(&format!(
- "cannot register {}",
- object_path
- )))),
- }
+ manager_context.state_change_observer.lock().await.push(object_path.clone());
+ ctx.reply(Ok(()))
}
},
);
@@ -133,12 +140,15 @@
("object_path",),
(),
|mut ctx, cr, (object_path,): (String,)| {
- let proxy = cr.data_mut::<ManagerContext>(ctx.path()).unwrap().proxy.clone();
+ let manager_context = cr.data_mut::<ManagerContext>(ctx.path()).unwrap().clone();
async move {
- let result = proxy.unregister_state_change_observer(object_path.clone()).await;
- match result {
- Ok(()) => ctx.reply(Ok(())),
- Err(_) => ctx.reply(Err(dbus_crossroads::MethodErr::failed(&format!(
+ let mut observers = manager_context.state_change_observer.lock().await;
+ match observers.iter().position(|x| *x == object_path) {
+ Some(index) => {
+ observers.remove(index);
+ ctx.reply(Ok(()))
+ }
+ _ => ctx.reply(Err(dbus_crossroads::MethodErr::failed(&format!(
"cannot unregister {}",
object_path
)))),
@@ -176,9 +186,13 @@
.args(&["stop", BLUEZ_INIT_TARGET])
.output()
.expect("failed to stop bluetoothd");
- let _ = proxy.start_bluetooth(0).await;
+ // TODO: Implement multi-hci case
+ let default_device = config_util::list_hci_devices()[0];
+ let _ = proxy.start_bluetooth(default_device).await;
} else if prev != enabled {
- let _ = proxy.stop_bluetooth(0).await;
+ // TODO: Implement multi-hci case
+ let default_device = config_util::list_hci_devices()[0];
+ let _ = proxy.stop_bluetooth(default_device).await;
Command::new("initctl")
.args(&["start", BLUEZ_INIT_TARGET])
.output()
@@ -188,6 +202,44 @@
}
},
);
+ b.method_with_cr_async("ListHciDevices", (), ("devices",), |mut ctx, _cr, ()| {
+ let devices = config_util::list_hci_devices();
+ async move { ctx.reply(Ok((devices,))) }
+ });
+ // Register AdapterStateChangeCallback(int hci_device, int state) on specified object_path
+ b.method_with_cr_async(
+ "RegisterHciDeviceChangeObserver",
+ ("object_path",),
+ (),
+ |mut ctx, cr, (object_path,): (String,)| {
+ let manager_context = cr.data_mut::<ManagerContext>(ctx.path()).unwrap().clone();
+ async move {
+ manager_context.hci_device_change_observer.lock().await.push(object_path);
+ ctx.reply(Ok(()))
+ }
+ },
+ );
+ b.method_with_cr_async(
+ "UnregisterHciDeviceChangeObserver",
+ ("object_path",),
+ (),
+ |mut ctx, cr, (object_path,): (String,)| {
+ let manager_context = cr.data_mut::<ManagerContext>(ctx.path()).unwrap().clone();
+ async move {
+ let mut observers = manager_context.hci_device_change_observer.lock().await;
+ match observers.iter().position(|x| *x == object_path) {
+ Some(index) => {
+ observers.remove(index);
+ ctx.reply(Ok(()))
+ }
+ _ => ctx.reply(Err(dbus_crossroads::MethodErr::failed(&format!(
+ "cannot unregister {}",
+ object_path
+ )))),
+ }
+ }
+ },
+ );
});
// Let's add the "/org/chromium/bluetooth/Manager" path, which implements the org.chromium.bluetooth.Manager interface,
@@ -195,7 +247,7 @@
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(
+ conn.start_receive(
MatchRule::new_method_call(),
Box::new(move |msg, conn| {
cr.handle_message(msg, conn).unwrap();
@@ -204,7 +256,7 @@
);
tokio::spawn(async move {
- state_machine::mainloop(context).await;
+ state_machine::mainloop(context, dbus_callback_util).await;
});
std::future::pending::<()>().await;
diff --git a/system/gd/rust/linux/mgmt/src/bin/btmanagerd/state_machine.rs b/system/gd/rust/linux/mgmt/src/bin/btmanagerd/state_machine.rs
index 5a67143..bec83b8 100644
--- a/system/gd/rust/linux/mgmt/src/bin/btmanagerd/state_machine.rs
+++ b/system/gd/rust/linux/mgmt/src/bin/btmanagerd/state_machine.rs
@@ -1,6 +1,7 @@
use bt_common::time::Alarm;
use nix::sys::signal::{self, Signal};
use nix::unistd::Pid;
+use regex::Regex;
use std::process::{Child, Command, Stdio};
use std::sync::Arc;
use std::time::Duration;
@@ -8,12 +9,26 @@
use tokio::sync::mpsc::error::SendError;
use tokio::sync::{mpsc, Mutex};
+// Directory for Bluetooth pid file
+pub const PID_DIR: &str = "/var/run/bluetooth";
+
#[derive(Debug, PartialEq, Copy, Clone)]
+#[repr(i32)]
pub enum State {
- Off, // Bluetooth is not running
- TurningOn, // We are not notified that the Bluetooth is running
- On, // Bluetooth is running
- TurningOff, // We are not notified that the Bluetooth is stopped
+ Off = 0, // Bluetooth is not running
+ TurningOn = 1, // We are not notified that the Bluetooth is running
+ On = 2, // Bluetooth is running
+ TurningOff = 3, // We are not notified that the Bluetooth is stopped
+}
+
+impl From<State> for i32 {
+ fn from(item: State) -> i32 {
+ item as i32
+ }
+}
+
+pub fn state_to_i32(state: State) -> i32 {
+ i32::from(state)
}
#[derive(Debug)]
@@ -40,23 +55,18 @@
}
pub fn get_proxy(&self) -> StateMachineProxy {
- StateMachineProxy {
- tx: self.tx.clone(),
- state: self.state_machine.state.clone(),
- state_change_observers: self.state_machine.state_change_observers.clone(),
- }
+ StateMachineProxy { tx: self.tx.clone(), state: self.state_machine.state.clone() }
}
}
-pub fn start_new_state_machine_context() -> StateMachineContext<NativeSubprocess> {
- StateMachineContext::new(ManagerStateMachine::new_native())
+pub fn start_new_state_machine_context() -> StateMachineContext<UpstartInvoker> {
+ StateMachineContext::new(ManagerStateMachine::new_upstart())
}
#[derive(Clone)]
pub struct StateMachineProxy {
tx: mpsc::Sender<StateMachineActions>,
state: Arc<Mutex<State>>,
- state_change_observers: Arc<Mutex<Vec<String>>>,
}
impl StateMachineProxy {
@@ -77,34 +87,21 @@
pub async fn get_state(&self) -> State {
*self.state.lock().await
}
-
- pub async fn register_state_change_observer(
- &self,
- object_path: String,
- ) -> Result<(), SendError<StateMachineActions>> {
- self.state_change_observers.lock().await.push(object_path);
- Ok(())
- }
-
- pub async fn unregister_state_change_observer(
- &self,
- object_path: String,
- ) -> Result<(), SendError<StateMachineActions>> {
- let mut observers = self.state_change_observers.lock().await;
- let index = observers.iter().position(|x| *x == object_path).unwrap();
- observers.remove(index);
- Ok(())
- }
}
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)
+ .add_watch(PID_DIR, inotify::WatchMask::CREATE | inotify::WatchMask::DELETE)
.expect("failed to add watch");
AsyncFd::new(pid_detector).expect("failed to add async fd")
}
+fn get_hci_interface_from_pid_file_name(path: &str) -> Option<i32> {
+ let re = Regex::new(r"bluetooth([0-9]+).pid").unwrap();
+ re.captures(path)?.get(1)?.as_str().parse().ok()
+}
+
fn hci_devices_inotify_async_fd() -> AsyncFd<inotify::Inotify> {
let mut detector = inotify::Inotify::init().expect("cannot use inotify");
detector
@@ -116,14 +113,21 @@
AsyncFd::new(detector).expect("failed to add async fd")
}
-pub async fn mainloop<PM>(mut context: StateMachineContext<PM>)
-where
+fn get_hci_interface_from_device(path: &str) -> Option<i32> {
+ let re = Regex::new(r"hci([0-9]+)").unwrap();
+ re.captures(path)?.get(1)?.as_str().parse().ok()
+}
+
+pub async fn mainloop<PM>(
+ mut context: StateMachineContext<PM>,
+ dbus_callback_util: crate::dbus_callback_util::DbusCallbackUtil,
+) where
PM: ProcessManager + Send,
{
let mut command_timeout = Alarm::new();
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();
+ let mut hci_devices_async_fd = hci_devices_inotify_async_fd();
loop {
tokio::select! {
Some(action) = context.rx.recv() => {
@@ -172,51 +176,66 @@
let mut buffer: [u8; 1024] = [0; 1024];
match fd_ready.try_io(|inner| inner.get_mut().read_events(&mut buffer)) {
Ok(Ok(events)) => {
+ println!("got some events");
for event in events {
match (event.mask, event.name) {
(inotify::EventMask::CREATE, Some(oss)) => {
- let file_name = oss.to_str().unwrap_or("invalid_file");
- if file_name.contains("/var/run/bluetooth.pid") {
- let read_result = tokio::fs::read("/var/run/bluetooth.pid").await;
- match read_result {
- Ok(v) => {
- let file_string = String::from_utf8(v).expect("invalid pid file");
- let mut iter = file_string.split_ascii_whitespace();
- let pid = match iter.next() {
- Some(s) => s.parse::<i32>().unwrap(),
- None => 0
- };
- let hci = match iter.next() {
- Some(s) => s.parse::<i32>().unwrap(),
- None => 0
- };
- let _ = context.tx.send(StateMachineActions::BluetoothStarted(pid, hci)).await;
- },
- Err(e) => println!("{}", e)
- }
+ let path = std::path::Path::new(PID_DIR).join(oss);
+ let file_name = oss.to_str().unwrap_or("invalid file");
+ match (get_hci_interface_from_pid_file_name(file_name), tokio::fs::read(path).await.ok()) {
+ (Some(hci), Some(s)) => {
+ let pid = String::from_utf8(s).expect("invalid pid file").parse::<i32>().unwrap_or(0);
+ let _ = context.tx.send(StateMachineActions::BluetoothStarted(pid, hci)).await;
+ },
+ (hci, s) => println!("invalid file hci={:?} pid_file={:?}", hci, s),
}
},
(inotify::EventMask::DELETE, Some(oss)) => {
- let file_name = oss.to_str().unwrap_or("invalid_file");
- if file_name.contains("bluetooth.pid") {
- let _ = context.tx.send(StateMachineActions::BluetoothStopped()).await;
- }
- },
+ let file_name = oss.to_str().unwrap_or("invalid file");
+ match get_hci_interface_from_pid_file_name(file_name) {
+ Some(hci) => {
+ let _ = context.tx.send(StateMachineActions::BluetoothStopped()).await;
+ },
+ _ => (),
+ }
+ },
_ => println!("Ignored event {:?}", event.mask)
}
}
- }
+ },
Err(_) | Ok(Err(_)) => panic!("why can't we read while the asyncfd is ready?"),
}
fd_ready.clear_ready();
drop(fd_ready);
},
- r = config_async_fd.readable_mut() => {
+ r = hci_devices_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
- _ => (),
+ Ok(Ok(events)) => {
+ for event in events {
+ match (event.mask, event.name) {
+ (inotify::EventMask::CREATE, Some(oss)) => {
+ match get_hci_interface_from_device(oss.to_str().unwrap_or("invalid hci device")) {
+ Some(hci) => {
+ dbus_callback_util.send_hci_device_change_callback(hci, true).await;
+ },
+ _ => (),
+ }
+ },
+ (inotify::EventMask::DELETE, Some(oss)) => {
+ match get_hci_interface_from_device(oss.to_str().unwrap_or("invalid hci device")) {
+ Some(hci) => {
+ dbus_callback_util.send_hci_device_change_callback(hci, false).await;
+ },
+ _ => (),
+ }
+ },
+ _ => println!("Ignored event {:?}", event.mask)
+ }
+ }
+ },
+ Err(_) | Ok(Err(_)) => panic!("why can't we read while the asyncfd is ready?"),
}
fd_ready.clear_ready();
drop(fd_ready);
@@ -243,7 +262,7 @@
impl ProcessManager for NativeSubprocess {
fn start(&mut self, hci_interface: String) {
- let new_process = Command::new("/usr/bin/bluetoothd")
+ let new_process = Command::new("/usr/bin/btadapterd")
.arg(format!("HCI={}", hci_interface))
.stdout(Stdio::piped())
.spawn()
@@ -275,18 +294,14 @@
impl ProcessManager for UpstartInvoker {
fn start(&mut self, hci_interface: String) {
Command::new("initctl")
- .arg("start")
- .arg("bluetooth")
- .arg(format!("HCI={}", hci_interface))
+ .args(&["start", "btadapterd", format!("HCI={}", hci_interface).as_str()])
.output()
.expect("failed to start bluetooth");
}
fn stop(&mut self, hci_interface: String) {
Command::new("initctl")
- .arg("stop")
- .arg("bluetooth")
- .arg(format!("HCI={}", hci_interface))
+ .args(&["stop", "btadapterd", format!("HCI={}", hci_interface).as_str()])
.output()
.expect("failed to stop bluetooth");
}
@@ -295,7 +310,6 @@
struct ManagerStateMachine<PM> {
state: Arc<Mutex<State>>,
process_manager: PM,
- state_change_observers: Arc<Mutex<Vec<String>>>,
hci_interface: i32,
bluetooth_pid: i32,
}
@@ -306,6 +320,12 @@
}
}
+impl ManagerStateMachine<UpstartInvoker> {
+ pub fn new_upstart() -> ManagerStateMachine<UpstartInvoker> {
+ ManagerStateMachine::new(UpstartInvoker::new())
+ }
+}
+
#[derive(Debug, PartialEq)]
enum StateMachineTimeoutActions {
RetryStart,
@@ -321,7 +341,6 @@
ManagerStateMachine {
state: Arc::new(Mutex::new(State::Off)),
process_manager: process_manager,
- state_change_observers: Arc::new(Mutex::new(Vec::new())),
hci_interface: 0,
bluetooth_pid: 0,
}
@@ -334,7 +353,7 @@
State::Off => {
*state = State::TurningOn;
self.hci_interface = hci_interface;
- self.process_manager.start(format!("hci{}", hci_interface));
+ self.process_manager.start(format!("{}", hci_interface));
true
}
// Otherwise no op
@@ -397,7 +416,7 @@
State::On => {
println!("Bluetooth stopped unexpectedly, try restarting");
*state = State::TurningOn;
- self.process_manager.start(format!("hci{}", self.hci_interface));
+ self.process_manager.start(format!("{}", self.hci_interface));
false
}
State::TurningOn | State::Off => {
@@ -415,7 +434,7 @@
State::TurningOn => {
println!("Restarting bluetooth");
*state = State::TurningOn;
- self.process_manager.start(format! {"hci{}", self.hci_interface});
+ self.process_manager.start(format! {"{}", self.hci_interface});
StateMachineTimeoutActions::RetryStart
}
State::TurningOff => {
@@ -601,4 +620,26 @@
state_machine.action_on_bluetooth_started(0, 0);
assert_eq!(*state_machine.state.try_lock().unwrap(), State::On);
}
+
+ #[test]
+ fn path_to_hci_interface() {
+ assert_eq!(
+ get_hci_interface_from_pid_file_name("/var/run/bluetooth/bluetooth0.pid"),
+ Some(0)
+ );
+ assert_eq!(
+ get_hci_interface_from_pid_file_name("/var/run/bluetooth/bluetooth1.pid"),
+ Some(1)
+ );
+ assert_eq!(
+ get_hci_interface_from_pid_file_name("/var/run/bluetooth/bluetooth10.pid"),
+ Some(10)
+ );
+ assert_eq!(get_hci_interface_from_pid_file_name("/var/run/bluetooth/garbage"), None);
+
+ assert_eq!(get_hci_interface_from_device("/sys/class/bluetooth/hci0"), Some(0));
+ assert_eq!(get_hci_interface_from_device("/sys/class/bluetooth/hci1"), Some(1));
+ assert_eq!(get_hci_interface_from_device("/sys/class/bluetooth/hci10"), Some(10));
+ assert_eq!(get_hci_interface_from_device("/sys/class/bluetooth/eth0"), None);
+ }
}