blob: 9d09a7d546c215e5fff31af0cd7943ce2e03bbd7 [file] [log] [blame]
use crate::bluetooth_manager::BluetoothManager;
use bt_common::time::Alarm;
use log::{debug, error, info, warn};
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;
use tokio::io::unix::AsyncFd;
use tokio::sync::mpsc;
// Directory for Bluetooth pid file
pub const PID_DIR: &str = "/var/run/bluetooth";
#[derive(Debug, PartialEq, Copy, Clone)]
#[repr(u32)]
pub enum State {
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
}
/// Check whether adapter is enabled by checking internal state.
pub fn state_to_enabled(state: State) -> bool {
match state {
State::On => true,
_ => false,
}
}
/// Adapter state actions
#[derive(Debug)]
pub enum AdapterStateActions {
StartBluetooth(i32),
StopBluetooth(i32),
BluetoothStarted(i32, i32), // PID and HCI
BluetoothStopped(i32),
}
/// Enum of all the messages that state machine handles.
#[derive(Debug)]
pub enum Message {
AdapterStateChange(AdapterStateActions),
PidChange(inotify::EventMask, Option<String>),
HciDeviceChange(inotify::EventMask, Option<String>),
CallbackDisconnected(u32),
CommandTimeout(),
}
pub struct StateMachineContext {
tx: mpsc::Sender<Message>,
rx: mpsc::Receiver<Message>,
state_machine: ManagerStateMachine,
}
impl StateMachineContext {
fn new(state_machine: ManagerStateMachine) -> StateMachineContext {
let (tx, rx) = mpsc::channel::<Message>(10);
StateMachineContext { tx: tx, rx: rx, state_machine: state_machine }
}
pub fn get_proxy(&self) -> StateMachineProxy {
StateMachineProxy { tx: self.tx.clone(), state: self.state_machine.state.clone() }
}
}
pub fn start_new_state_machine_context(invoker: Invoker) -> StateMachineContext {
match invoker {
Invoker::NativeInvoker => StateMachineContext::new(ManagerStateMachine::new_native()),
Invoker::SystemdInvoker => StateMachineContext::new(ManagerStateMachine::new_systemd()),
Invoker::UpstartInvoker => StateMachineContext::new(ManagerStateMachine::new_upstart()),
}
}
#[derive(Clone)]
pub struct StateMachineProxy {
tx: mpsc::Sender<Message>,
state: Arc<std::sync::Mutex<State>>,
}
const TX_SEND_TIMEOUT_DURATION: Duration = Duration::from_secs(3);
const COMMAND_TIMEOUT_DURATION: Duration = Duration::from_secs(3);
impl StateMachineProxy {
pub fn start_bluetooth(&self, hci_interface: i32) {
let tx = self.tx.clone();
tokio::spawn(async move {
let _ = tx
.send(Message::AdapterStateChange(AdapterStateActions::StartBluetooth(
hci_interface,
)))
.await;
});
}
pub fn stop_bluetooth(&self, hci_interface: i32) {
let tx = self.tx.clone();
tokio::spawn(async move {
let _ = tx
.send(Message::AdapterStateChange(AdapterStateActions::StopBluetooth(
hci_interface,
)))
.await;
});
}
pub fn get_state(&self) -> State {
// This assumes that self.state is never locked for a long period, i.e. never lock() and
// await for something else without unlocking. Otherwise this function will block.
return *self.state.lock().unwrap();
}
pub fn get_tx(&self) -> mpsc::Sender<Message> {
self.tx.clone()
}
}
fn pid_inotify_async_fd() -> AsyncFd<inotify::Inotify> {
let mut pid_detector = inotify::Inotify::init().expect("cannot use inotify");
pid_detector
.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")
}
/// Given an pid path, returns the adapter index for that pid path.
fn get_hci_index_from_pid_path(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
.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")
}
/// On startup, get and cache all hci devices by emitting the callback
fn startup_hci_devices(manager: &Arc<std::sync::Mutex<Box<BluetoothManager>>>) {
let devices = crate::config_util::list_hci_devices();
for device in devices {
manager.lock().unwrap().callback_hci_device_change(device, true);
}
}
/// Given an hci sysfs path, returns the index of the hci device at the path.
fn get_hci_index_from_device(path: &str) -> Option<i32> {
let re = Regex::new(r"hci([0-9]+)").unwrap();
re.captures(path)?.get(1)?.as_str().parse().ok()
}
fn event_name_to_string(name: Option<&std::ffi::OsStr>) -> Option<String> {
if let Some(val) = &name {
if let Some(strval) = val.to_str() {
return Some(strval.to_string());
}
}
return None;
}
pub async fn mainloop(
mut context: StateMachineContext,
bluetooth_manager: Arc<std::sync::Mutex<Box<BluetoothManager>>>,
) {
// Set up a command timeout listener to emit timeout messages
let command_timeout = Arc::new(Alarm::new());
let timeout_clone = command_timeout.clone();
let timeout_tx = context.tx.clone();
// First set up hci states
startup_hci_devices(&bluetooth_manager);
tokio::spawn(async move {
loop {
let _expired = timeout_clone.expired().await;
let _ = timeout_tx
.send_timeout(Message::CommandTimeout(), TX_SEND_TIMEOUT_DURATION)
.await
.unwrap();
}
});
// Get a list of active pid files to determine initial adapter status
let init_tx = context.tx.clone();
tokio::spawn(async move {
let files = crate::config_util::list_pid_files(PID_DIR);
for file in files {
let _ = init_tx
.send_timeout(
Message::PidChange(inotify::EventMask::CREATE, Some(file)),
TX_SEND_TIMEOUT_DURATION,
)
.await
.unwrap();
}
});
// Set up a PID file listener to emit PID inotify messages
let mut pid_async_fd = pid_inotify_async_fd();
let pid_tx = context.tx.clone();
tokio::spawn(async move {
debug!("Spawned pid notify task");
loop {
let r = pid_async_fd.readable_mut();
let mut fd_ready = r.await.unwrap();
let mut buffer: [u8; 1024] = [0; 1024];
debug!("Found new pid inotify entries. Reading them");
match fd_ready.try_io(|inner| inner.get_mut().read_events(&mut buffer)) {
Ok(Ok(events)) => {
for event in events {
debug!("got some events from pid {:?}", event.mask);
let _ = pid_tx
.send_timeout(
Message::PidChange(event.mask, event_name_to_string(event.name)),
TX_SEND_TIMEOUT_DURATION,
)
.await
.unwrap();
}
}
Err(_) | Ok(Err(_)) => panic!("why can't we read while the asyncfd is ready?"),
}
fd_ready.clear_ready();
drop(fd_ready);
}
});
// Set up an HCI device listener to emit HCI device inotify messages
let mut hci_devices_async_fd = hci_devices_inotify_async_fd();
let hci_tx = context.tx.clone();
tokio::spawn(async move {
debug!("Spawned hci notify task");
loop {
let r = hci_devices_async_fd.readable_mut();
let mut fd_ready = r.await.unwrap();
let mut buffer: [u8; 1024] = [0; 1024];
debug!("Found new hci device entries. Reading them.");
match fd_ready.try_io(|inner| inner.get_mut().read_events(&mut buffer)) {
Ok(Ok(events)) => {
for event in events {
let _ = hci_tx
.send_timeout(
Message::HciDeviceChange(
event.mask,
event_name_to_string(event.name),
),
TX_SEND_TIMEOUT_DURATION,
)
.await
.unwrap();
}
}
Err(_) | Ok(Err(_)) => panic!("why can't we read while the asyncfd is ready?"),
}
fd_ready.clear_ready();
drop(fd_ready);
}
});
// Listen for all messages and act on them
loop {
let m = context.rx.recv().await;
if m.is_none() {
info!("Exiting manager mainloop");
break;
}
debug!("Message handler: {:?}", m);
match m.unwrap() {
// Adapter action has changed
Message::AdapterStateChange(action) => {
// Grab previous state from lock and release
let next_state;
let prev_state;
{
prev_state = *context.state_machine.state.lock().unwrap();
}
let hci;
match action {
AdapterStateActions::StartBluetooth(i) => {
next_state = State::TurningOn;
hci = i;
match context.state_machine.action_start_bluetooth(i) {
true => {
command_timeout.reset(COMMAND_TIMEOUT_DURATION);
}
false => command_timeout.cancel(),
}
}
AdapterStateActions::StopBluetooth(i) => {
next_state = State::TurningOff;
hci = i;
match context.state_machine.action_stop_bluetooth(i) {
true => {
command_timeout.reset(COMMAND_TIMEOUT_DURATION);
}
false => command_timeout.cancel(),
}
}
AdapterStateActions::BluetoothStarted(pid, i) => {
next_state = State::On;
hci = i;
match context.state_machine.action_on_bluetooth_started(pid, hci) {
true => {
command_timeout.cancel();
}
false => error!("unexpected BluetoothStarted pid{} hci{}", pid, hci),
}
}
AdapterStateActions::BluetoothStopped(i) => {
next_state = State::Off;
hci = i;
match context.state_machine.action_on_bluetooth_stopped() {
true => {
command_timeout.cancel();
}
false => {
command_timeout.reset(COMMAND_TIMEOUT_DURATION);
}
}
}
};
// Only emit enabled event for certain transitions
if next_state != prev_state && (next_state == State::On || prev_state == State::On)
{
bluetooth_manager
.lock()
.unwrap()
.callback_hci_enabled_change(hci, next_state == State::On);
}
}
// Monitored pid directory has a change
Message::PidChange(mask, filename) => match (mask, &filename) {
(inotify::EventMask::CREATE, Some(fname)) => {
let path = std::path::Path::new(PID_DIR).join(&fname);
match (get_hci_index_from_pid_path(&fname), 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);
debug!("Sending bluetooth started action for pid={}, hci={}", pid, hci);
let _ = context
.tx
.send_timeout(
Message::AdapterStateChange(
AdapterStateActions::BluetoothStarted(pid, hci),
),
TX_SEND_TIMEOUT_DURATION,
)
.await
.unwrap();
}
(hci, s) => {
warn!("invalid file hci={:?} pid_file={:?}", hci, s)
}
}
}
(inotify::EventMask::DELETE, Some(fname)) => {
if let Some(hci) = get_hci_index_from_pid_path(&fname) {
debug!("Sending bluetooth stopped action for hci={}", hci);
context
.tx
.send_timeout(
Message::AdapterStateChange(AdapterStateActions::BluetoothStopped(
hci,
)),
TX_SEND_TIMEOUT_DURATION,
)
.await
.unwrap();
}
}
_ => debug!("Ignored event {:?} - {:?}", mask, &filename),
},
// Monitored hci directory has a change
Message::HciDeviceChange(mask, filename) => match (mask, &filename) {
(inotify::EventMask::CREATE, Some(fname)) => {
match get_hci_index_from_device(&fname) {
Some(hci) => {
bluetooth_manager.lock().unwrap().callback_hci_device_change(hci, true);
}
_ => (),
}
}
(inotify::EventMask::DELETE, Some(fname)) => {
match get_hci_index_from_device(&fname) {
Some(hci) => {
bluetooth_manager
.lock()
.unwrap()
.callback_hci_device_change(hci, false);
}
_ => (),
}
}
_ => debug!("Ignored event {:?} - {:?}", mask, &filename),
},
// Callback client has disconnected
Message::CallbackDisconnected(id) => {
bluetooth_manager.lock().unwrap().callback_disconnected(id);
}
// Handle command timeouts
Message::CommandTimeout() => {
// Hold state lock for short duration
{
debug!("expired {:?}", *context.state_machine.state.lock().unwrap());
}
let timeout_action = context.state_machine.action_on_command_timeout();
match timeout_action {
StateMachineTimeoutActions::Noop => (),
_ => command_timeout.reset(COMMAND_TIMEOUT_DURATION),
}
}
}
}
}
pub trait ProcessManager {
fn start(&mut self, hci_interface: String);
fn stop(&mut self, hci_interface: String);
}
pub enum Invoker {
NativeInvoker,
SystemdInvoker,
UpstartInvoker,
}
pub struct NativeInvoker {
process_container: Option<Child>,
bluetooth_pid: u32,
}
impl NativeInvoker {
pub fn new() -> NativeInvoker {
NativeInvoker { process_container: None, bluetooth_pid: 0 }
}
}
impl ProcessManager for NativeInvoker {
fn start(&mut self, hci_interface: String) {
let new_process = Command::new("/usr/bin/btadapterd")
.arg(format!("HCI={}", hci_interface))
.stdout(Stdio::piped())
.spawn()
.expect("cannot open");
self.bluetooth_pid = new_process.id();
self.process_container = Some(new_process);
}
fn stop(&mut self, _hci_interface: String) {
match self.process_container {
Some(ref mut _p) => {
signal::kill(Pid::from_raw(self.bluetooth_pid as i32), Signal::SIGTERM).unwrap();
self.process_container = None;
}
None => {
warn!("Process doesn't exist");
}
}
}
}
pub struct UpstartInvoker {}
impl UpstartInvoker {
pub fn new() -> UpstartInvoker {
UpstartInvoker {}
}
}
impl ProcessManager for UpstartInvoker {
fn start(&mut self, hci_interface: String) {
Command::new("initctl")
.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")
.args(&["stop", "btadapterd", format!("HCI={}", hci_interface).as_str()])
.output()
.expect("failed to stop bluetooth");
}
}
pub struct SystemdInvoker {}
impl SystemdInvoker {
pub fn new() -> SystemdInvoker {
SystemdInvoker {}
}
}
impl ProcessManager for SystemdInvoker {
fn start(&mut self, hci_interface: String) {
Command::new("systemctl")
.args(&["restart", format!("btadapterd@{}.service", hci_interface).as_str()])
.output()
.expect("failed to start bluetooth");
}
fn stop(&mut self, hci_interface: String) {
Command::new("systemctl")
.args(&["stop", format!("btadapterd@{}.service", hci_interface).as_str()])
.output()
.expect("failed to stop bluetooth");
}
}
struct ManagerStateMachine {
state: Arc<std::sync::Mutex<State>>,
process_manager: Box<dyn ProcessManager + Send>,
hci_interface: i32,
bluetooth_pid: i32,
}
impl ManagerStateMachine {
pub fn new_upstart() -> ManagerStateMachine {
ManagerStateMachine::new(Box::new(UpstartInvoker::new()))
}
pub fn new_systemd() -> ManagerStateMachine {
ManagerStateMachine::new(Box::new(SystemdInvoker::new()))
}
pub fn new_native() -> ManagerStateMachine {
ManagerStateMachine::new(Box::new(NativeInvoker::new()))
}
}
#[derive(Debug, PartialEq)]
enum StateMachineTimeoutActions {
RetryStart,
RetryStop,
Noop,
}
impl ManagerStateMachine {
pub fn new(process_manager: Box<dyn ProcessManager + Send>) -> ManagerStateMachine {
ManagerStateMachine {
state: Arc::new(std::sync::Mutex::new(State::Off)),
process_manager: process_manager,
hci_interface: 0,
bluetooth_pid: 0,
}
}
/// Returns true if we are starting bluetooth process.
pub fn action_start_bluetooth(&mut self, hci_interface: i32) -> bool {
let mut state = self.state.lock().unwrap();
match *state {
State::Off => {
*state = State::TurningOn;
self.hci_interface = hci_interface;
self.process_manager.start(format!("{}", hci_interface));
true
}
// Otherwise no op
_ => false,
}
}
/// Returns true if we are stopping bluetooth process.
pub fn action_stop_bluetooth(&mut self, hci_interface: i32) -> bool {
if self.hci_interface != hci_interface {
warn!(
"We are running hci{} but attempting to stop hci{}",
self.hci_interface, hci_interface
);
return false;
}
let mut state = self.state.lock().unwrap();
match *state {
State::On => {
*state = State::TurningOff;
self.process_manager.stop(self.hci_interface.to_string());
true
}
State::TurningOn => {
*state = State::Off;
self.process_manager.stop(self.hci_interface.to_string());
false
}
// Otherwise no op
_ => false,
}
}
/// Returns true if the event is expected.
pub fn action_on_bluetooth_started(&mut self, pid: i32, hci_interface: i32) -> bool {
let mut state = self.state.lock().unwrap();
if self.hci_interface != hci_interface {
warn!(
"We should start hci{} but hci{} is started; capturing that process",
self.hci_interface, hci_interface
);
self.hci_interface = hci_interface;
}
if *state != State::TurningOn {
warn!("Unexpected Bluetooth started");
}
*state = State::On;
self.bluetooth_pid = pid;
true
}
/// Returns true if the event is expected.
/// If unexpected, Bluetooth probably crashed;
/// start the timer for restart timeout
pub fn action_on_bluetooth_stopped(&mut self) -> bool {
let mut state = self.state.lock().unwrap();
match *state {
State::TurningOff => {
*state = State::Off;
true
}
State::On => {
warn!("Bluetooth stopped unexpectedly, try restarting");
*state = State::TurningOn;
self.process_manager.start(format!("{}", self.hci_interface));
false
}
State::TurningOn | State::Off => {
// Unexpected
panic!("unexpected bluetooth shutdown");
}
}
}
/// Triggered on Bluetooth start/stop timeout. Return the actions that the
/// state machine has taken, for the external context to reset the timer.
pub fn action_on_command_timeout(&mut self) -> StateMachineTimeoutActions {
let mut state = self.state.lock().unwrap();
match *state {
State::TurningOn => {
info!("Restarting bluetooth {}", self.hci_interface);
*state = State::TurningOn;
self.process_manager.stop(format! {"{}", self.hci_interface});
self.process_manager.start(format! {"{}", self.hci_interface});
StateMachineTimeoutActions::RetryStart
}
State::TurningOff => {
info!("Killing bluetooth {}", self.hci_interface);
self.process_manager.stop(format! {"{}", self.hci_interface});
StateMachineTimeoutActions::RetryStop
}
_ => StateMachineTimeoutActions::Noop,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::VecDeque;
#[derive(Debug, PartialEq)]
enum ExecutedCommand {
Start,
Stop,
}
struct MockProcessManager {
last_command: VecDeque<ExecutedCommand>,
}
impl MockProcessManager {
fn new() -> MockProcessManager {
MockProcessManager { last_command: VecDeque::new() }
}
fn expect_start(&mut self) {
self.last_command.push_back(ExecutedCommand::Start);
}
fn expect_stop(&mut self) {
self.last_command.push_back(ExecutedCommand::Stop);
}
}
impl ProcessManager for MockProcessManager {
fn start(&mut self, _: String) {
let start = self.last_command.pop_front().expect("Should expect start event");
assert_eq!(start, ExecutedCommand::Start);
}
fn stop(&mut self, _: String) {
let stop = self.last_command.pop_front().expect("Should expect stop event");
assert_eq!(stop, ExecutedCommand::Stop);
}
}
impl Drop for MockProcessManager {
fn drop(&mut self) {
assert_eq!(self.last_command.len(), 0);
}
}
#[test]
fn initial_state_is_off() {
tokio::runtime::Runtime::new().unwrap().block_on(async {
let process_manager = MockProcessManager::new();
let state_machine = ManagerStateMachine::new(Box::new(process_manager));
assert_eq!(*state_machine.state.lock().unwrap(), State::Off);
})
}
#[test]
fn off_turnoff_should_noop() {
tokio::runtime::Runtime::new().unwrap().block_on(async {
let process_manager = MockProcessManager::new();
let mut state_machine = ManagerStateMachine::new(Box::new(process_manager));
state_machine.action_stop_bluetooth(0);
assert_eq!(*state_machine.state.lock().unwrap(), State::Off);
})
}
#[test]
fn off_turnon_should_turningon() {
tokio::runtime::Runtime::new().unwrap().block_on(async {
let mut process_manager = MockProcessManager::new();
// Expect to send start command
process_manager.expect_start();
let mut state_machine = ManagerStateMachine::new(Box::new(process_manager));
state_machine.action_start_bluetooth(0);
assert_eq!(*state_machine.state.lock().unwrap(), State::TurningOn);
})
}
#[test]
fn turningon_turnon_again_noop() {
tokio::runtime::Runtime::new().unwrap().block_on(async {
let mut process_manager = MockProcessManager::new();
// Expect to send start command just once
process_manager.expect_start();
let mut state_machine = ManagerStateMachine::new(Box::new(process_manager));
state_machine.action_start_bluetooth(0);
assert_eq!(state_machine.action_start_bluetooth(0), false);
})
}
#[test]
fn turningon_bluetooth_started() {
tokio::runtime::Runtime::new().unwrap().block_on(async {
let mut process_manager = MockProcessManager::new();
process_manager.expect_start();
let mut state_machine = ManagerStateMachine::new(Box::new(process_manager));
state_machine.action_start_bluetooth(0);
state_machine.action_on_bluetooth_started(0, 0);
assert_eq!(*state_machine.state.lock().unwrap(), State::On);
})
}
#[test]
fn turningon_bluetooth_different_hci_started() {
tokio::runtime::Runtime::new().unwrap().block_on(async {
let mut process_manager = MockProcessManager::new();
process_manager.expect_start();
let mut state_machine = ManagerStateMachine::new(Box::new(process_manager));
state_machine.action_start_bluetooth(1);
state_machine.action_on_bluetooth_started(1, 1);
assert_eq!(*state_machine.state.lock().unwrap(), State::On);
})
}
#[test]
fn turningon_timeout() {
tokio::runtime::Runtime::new().unwrap().block_on(async {
let mut process_manager = MockProcessManager::new();
process_manager.expect_start();
process_manager.expect_stop();
process_manager.expect_start(); // start bluetooth again
let mut state_machine = ManagerStateMachine::new(Box::new(process_manager));
state_machine.action_start_bluetooth(0);
assert_eq!(
state_machine.action_on_command_timeout(),
StateMachineTimeoutActions::RetryStart
);
assert_eq!(*state_machine.state.lock().unwrap(), State::TurningOn);
})
}
#[test]
fn turningon_turnoff_should_turningoff_and_send_command() {
tokio::runtime::Runtime::new().unwrap().block_on(async {
let mut process_manager = MockProcessManager::new();
process_manager.expect_start();
// Expect to send stop command
process_manager.expect_stop();
let mut state_machine = ManagerStateMachine::new(Box::new(process_manager));
state_machine.action_start_bluetooth(0);
state_machine.action_stop_bluetooth(0);
assert_eq!(*state_machine.state.lock().unwrap(), State::Off);
})
}
#[test]
fn on_turnoff_should_turningoff_and_send_command() {
tokio::runtime::Runtime::new().unwrap().block_on(async {
let mut process_manager = MockProcessManager::new();
process_manager.expect_start();
// Expect to send stop command
process_manager.expect_stop();
let mut state_machine = ManagerStateMachine::new(Box::new(process_manager));
state_machine.action_start_bluetooth(0);
state_machine.action_on_bluetooth_started(0, 0);
state_machine.action_stop_bluetooth(0);
assert_eq!(*state_machine.state.lock().unwrap(), State::TurningOff);
})
}
#[test]
fn on_bluetooth_stopped() {
tokio::runtime::Runtime::new().unwrap().block_on(async {
let mut process_manager = MockProcessManager::new();
process_manager.expect_start();
// Expect to start again
process_manager.expect_start();
let mut state_machine = ManagerStateMachine::new(Box::new(process_manager));
state_machine.action_start_bluetooth(0);
state_machine.action_on_bluetooth_started(0, 0);
assert_eq!(state_machine.action_on_bluetooth_stopped(), false);
assert_eq!(*state_machine.state.lock().unwrap(), State::TurningOn);
})
}
#[test]
fn turningoff_bluetooth_down_should_off() {
tokio::runtime::Runtime::new().unwrap().block_on(async {
let mut process_manager = MockProcessManager::new();
process_manager.expect_start();
process_manager.expect_stop();
let mut state_machine = ManagerStateMachine::new(Box::new(process_manager));
state_machine.action_start_bluetooth(0);
state_machine.action_on_bluetooth_started(0, 0);
state_machine.action_stop_bluetooth(0);
state_machine.action_on_bluetooth_stopped();
assert_eq!(*state_machine.state.lock().unwrap(), State::Off);
})
}
#[test]
fn restart_bluetooth() {
tokio::runtime::Runtime::new().unwrap().block_on(async {
let mut process_manager = MockProcessManager::new();
process_manager.expect_start();
process_manager.expect_stop();
process_manager.expect_start();
let mut state_machine = ManagerStateMachine::new(Box::new(process_manager));
state_machine.action_start_bluetooth(0);
state_machine.action_on_bluetooth_started(0, 0);
state_machine.action_stop_bluetooth(0);
state_machine.action_on_bluetooth_stopped();
state_machine.action_start_bluetooth(0);
state_machine.action_on_bluetooth_started(0, 0);
assert_eq!(*state_machine.state.lock().unwrap(), State::On);
})
}
#[test]
fn path_to_hci_interface() {
assert_eq!(get_hci_index_from_pid_path("/var/run/bluetooth/bluetooth0.pid"), Some(0));
assert_eq!(get_hci_index_from_pid_path("/var/run/bluetooth/bluetooth1.pid"), Some(1));
assert_eq!(get_hci_index_from_pid_path("/var/run/bluetooth/bluetooth10.pid"), Some(10));
assert_eq!(get_hci_index_from_pid_path("/var/run/bluetooth/garbage"), None);
assert_eq!(get_hci_index_from_device("/sys/class/bluetooth/hci0"), Some(0));
assert_eq!(get_hci_index_from_device("/sys/class/bluetooth/hci1"), Some(1));
assert_eq!(get_hci_index_from_device("/sys/class/bluetooth/hci10"), Some(10));
assert_eq!(get_hci_index_from_device("/sys/class/bluetooth/eth0"), None);
}
}