blob: 8c4724399c0878e9f87286ad854bd1d78554a68a [file] [log] [blame]
// Copyright 2022, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! This module defines the UwbService and its related components.
use log::{debug, error, warn};
use tokio::runtime::{Builder, Handle};
use tokio::sync::{mpsc, oneshot};
use tokio::task;
use crate::error::{Error, Result};
use crate::params::app_config_params::AppConfigParams;
use crate::params::uci_packets::{
Controlee, CountryCode, DeviceState, PowerStats, RawUciMessage, ReasonCode, SessionId,
SessionState, SessionType, UpdateMulticastListAction,
};
use crate::session::session_manager::{SessionManager, SessionNotification};
use crate::uci::notification::{CoreNotification, SessionRangeData};
use crate::uci::uci_logger::UciLoggerMode;
use crate::uci::uci_manager::UciManager;
use crate::utils::clean_mpsc_receiver;
/// Callback builder
pub trait UwbServiceCallbackBuilder<C: UwbServiceCallback>: 'static + Send {
/// Builds UwbServiceCallback. The build operation Consumes Builder.
fn build(self) -> Option<C>;
}
/// The callback of the UwbService which is used to send the notification to UwbService's caller.
pub trait UwbServiceCallback: 'static {
/// Notify the UWB service has been reset due to internal error. All the sessions are closed.
/// `success` indicates the reset is successful or not.
fn on_service_reset(&mut self, success: bool);
/// Notify the status of the UCI device.
fn on_uci_device_status_changed(&mut self, state: DeviceState);
/// Notify the state of the session with the id |session_id| is changed.
fn on_session_state_changed(
&mut self,
session_id: SessionId,
session_state: SessionState,
reason_code: ReasonCode,
);
/// Notify the ranging data of the session with the id |session_id| is received.
fn on_range_data_received(&mut self, session_id: SessionId, range_data: SessionRangeData);
/// Notify the vendor notification is received.
fn on_vendor_notification_received(&mut self, gid: u32, oid: u32, payload: Vec<u8>);
// TODO(b/261762781): In the future, add a callback here to notify the Data Rx packet.
}
/// A placeholder implementation for UwbServiceCallback that does nothing.
pub struct NopUwbServiceCallback {}
impl UwbServiceCallback for NopUwbServiceCallback {
fn on_service_reset(&mut self, _success: bool) {}
fn on_uci_device_status_changed(&mut self, _state: DeviceState) {}
fn on_session_state_changed(
&mut self,
_session_id: SessionId,
_session_state: SessionState,
_reason_code: ReasonCode,
) {
}
fn on_range_data_received(&mut self, _session_id: SessionId, _range_data: SessionRangeData) {}
fn on_vendor_notification_received(&mut self, _gid: u32, _oid: u32, _payload: Vec<u8>) {}
}
/// The entry class (a.k.a top shim) of the core library. The class accepts requests from the
/// client, and delegates the requests to other components. It should provide the
/// backward-compatible interface for the client of the library.
pub struct UwbService {
/// The handle of the working runtime. All the commands are executed inside the runtime.
///
/// Note that the caller should guarantee that the working runtime outlives the UwbService.
runtime_handle: Handle,
/// Used to send the command to UwbServiceActor.
cmd_sender: mpsc::UnboundedSender<(Command, ResponseSender)>,
}
impl UwbService {
/// Create a new UwbService instance.
pub(super) fn new<C, B, U>(
runtime_handle: Handle,
callback_builder: B,
uci_manager: U,
) -> Option<Self>
where
C: UwbServiceCallback,
B: UwbServiceCallbackBuilder<C>,
U: UciManager,
{
let (cmd_sender, cmd_receiver) = mpsc::unbounded_channel();
let (service_status_sender, mut service_status_receiver) =
mpsc::unbounded_channel::<bool>();
std::thread::spawn(move || {
let actor_runtime = match Builder::new_current_thread().enable_all().build() {
Ok(ar) => ar,
Err(err) => {
error!("Failed to build Tokio Runtime! {:?}", err);
// unwrap safe since receiver is in scope
service_status_sender.send(false).unwrap();
return;
}
};
let callback = match callback_builder.build() {
Some(cb) => {
// unwrap safe since receiver is in scope
service_status_sender.send(true).unwrap();
cb
}
None => {
error!("Unable to build callback");
service_status_sender.send(false).unwrap();
return;
}
};
let mut actor = UwbServiceActor::new(cmd_receiver, callback, uci_manager);
let local = task::LocalSet::new();
local.spawn_local(async move {
task::spawn_local(async move { actor.run().await }).await.unwrap();
});
actor_runtime.block_on(local);
});
match service_status_receiver.blocking_recv() {
Some(true) => Some(Self { runtime_handle, cmd_sender }),
_ => None,
}
}
/// Set UCI log mode.
pub fn set_logger_mode(&self, logger_mode: UciLoggerMode) -> Result<()> {
self.block_on_cmd(Command::SetLoggerMode { logger_mode })?;
Ok(())
}
/// Enable the UWB service.
pub fn enable(&self) -> Result<()> {
self.block_on_cmd(Command::Enable)?;
Ok(())
}
/// Disable the UWB service.
pub fn disable(&self) -> Result<()> {
self.block_on_cmd(Command::Disable)?;
Ok(())
}
/// Initialize a new ranging session with the given parameters.
pub fn init_session(
&self,
session_id: SessionId,
session_type: SessionType,
params: AppConfigParams,
) -> Result<()> {
self.block_on_cmd(Command::InitSession { session_id, session_type, params })?;
Ok(())
}
/// Destroy the session.
pub fn deinit_session(&self, session_id: SessionId) -> Result<()> {
self.block_on_cmd(Command::DeinitSession { session_id })?;
Ok(())
}
/// Start ranging of the session.
pub fn start_ranging(&self, session_id: SessionId) -> Result<AppConfigParams> {
match self.block_on_cmd(Command::StartRanging { session_id })? {
Response::AppConfigParams(params) => Ok(params),
_ => panic!("start_ranging() should return AppConfigParams"),
}
}
/// Stop ranging.
pub fn stop_ranging(&self, session_id: SessionId) -> Result<()> {
self.block_on_cmd(Command::StopRanging { session_id })?;
Ok(())
}
/// Reconfigure the parameters of the session.
pub fn reconfigure(&self, session_id: SessionId, params: AppConfigParams) -> Result<()> {
self.block_on_cmd(Command::Reconfigure { session_id, params })?;
Ok(())
}
/// Update the list of the controlees to the ongoing session.
pub fn update_controller_multicast_list(
&self,
session_id: SessionId,
action: UpdateMulticastListAction,
controlees: Vec<Controlee>,
) -> Result<()> {
self.block_on_cmd(Command::UpdateControllerMulticastList {
session_id,
action,
controlees,
})?;
Ok(())
}
/// Set the country code. Android-specific method.
pub fn android_set_country_code(&self, country_code: CountryCode) -> Result<()> {
self.block_on_cmd(Command::AndroidSetCountryCode { country_code })?;
Ok(())
}
/// Get the power statistics. Android-specific method.
pub fn android_get_power_stats(&self) -> Result<PowerStats> {
match self.block_on_cmd(Command::AndroidGetPowerStats)? {
Response::PowerStats(stats) => Ok(stats),
_ => panic!("android_get_power_stats() should return PowerStats"),
}
}
/// Send a raw UCI message.
pub fn raw_uci_cmd(
&self,
mt: u32,
gid: u32,
oid: u32,
payload: Vec<u8>,
) -> Result<RawUciMessage> {
match self.block_on_cmd(Command::RawUciCmd { mt, gid, oid, payload })? {
Response::RawUciMessage(msg) => Ok(msg),
_ => panic!("raw_uci_cmd() should return RawUciMessage"),
}
}
/// Get app config params for the given session id
pub fn session_params(&self, session_id: SessionId) -> Result<AppConfigParams> {
match self.block_on_cmd(Command::GetParams { session_id })? {
Response::AppConfigParams(params) => Ok(params),
_ => panic!("session_params() should return AppConfigParams"),
}
}
/// Send the |cmd| to UwbServiceActor and wait until receiving the response.
fn block_on_cmd(&self, cmd: Command) -> Result<Response> {
let (result_sender, result_receiver) = oneshot::channel();
self.cmd_sender.send((cmd, result_sender)).map_err(|cmd| {
error!("Failed to send cmd: {:?}", cmd.0);
Error::Unknown
})?;
self.runtime_handle.block_on(async move {
result_receiver.await.unwrap_or_else(|e| {
error!("Failed to receive the result for cmd: {:?}", e);
Err(Error::Unknown)
})
})
}
/// Run an future task on the runtime. This method is only exposed for the testing.
#[cfg(test)]
fn block_on_for_testing<F: std::future::Future>(&self, future: F) -> F::Output {
self.runtime_handle.block_on(future)
}
}
struct UwbServiceActor<C: UwbServiceCallback, U: UciManager> {
cmd_receiver: mpsc::UnboundedReceiver<(Command, ResponseSender)>,
callback: C,
uci_manager: U,
session_manager: Option<SessionManager>,
core_notf_receiver: mpsc::UnboundedReceiver<CoreNotification>,
session_notf_receiver: mpsc::UnboundedReceiver<SessionNotification>,
vendor_notf_receiver: mpsc::UnboundedReceiver<RawUciMessage>,
}
impl<C: UwbServiceCallback, U: UciManager> UwbServiceActor<C, U> {
fn new(
cmd_receiver: mpsc::UnboundedReceiver<(Command, ResponseSender)>,
callback: C,
uci_manager: U,
) -> Self {
Self {
cmd_receiver,
callback,
uci_manager,
session_manager: None,
core_notf_receiver: mpsc::unbounded_channel().1,
session_notf_receiver: mpsc::unbounded_channel().1,
vendor_notf_receiver: mpsc::unbounded_channel().1,
}
}
async fn run(&mut self) {
loop {
tokio::select! {
cmd = self.cmd_receiver.recv() => {
match cmd {
None => {
debug!("UwbService is about to drop.");
break;
},
Some((cmd, result_sender)) => {
let result = self.handle_cmd(cmd).await;
let timeout_occurs = matches!(result, Err(Error::Timeout));
let _ = result_sender.send(result);
// The UCI HAL might be stuck at a weird state when the timeout occurs.
// Reset the HAL and clear the internal state, and hope the HAL goes
// back to the normal situation.
if timeout_occurs {
warn!("The command timeout, reset the service.");
self.reset_service().await;
}
}
}
}
Some(core_notf) = self.core_notf_receiver.recv() => {
self.handle_core_notification(core_notf).await;
}
Some(session_notf) = self.session_notf_receiver.recv() => {
self.handle_session_notification(session_notf).await;
}
Some(vendor_notf) = self.vendor_notf_receiver.recv() => {
self.handle_vendor_notification(vendor_notf).await;
}
}
}
}
async fn handle_cmd(&mut self, cmd: Command) -> Result<Response> {
match cmd {
Command::SetLoggerMode { logger_mode } => {
self.uci_manager.set_logger_mode(logger_mode).await?;
Ok(Response::Null)
}
Command::Enable => {
self.enable_service().await?;
Ok(Response::Null)
}
Command::Disable => {
self.disable_service(false).await?;
Ok(Response::Null)
}
Command::InitSession { session_id, session_type, params } => {
if let Some(session_manager) = self.session_manager.as_mut() {
session_manager.init_session(session_id, session_type, params).await?;
Ok(Response::Null)
} else {
error!("The service is not enabled yet");
Err(Error::BadParameters)
}
}
Command::DeinitSession { session_id } => {
if let Some(session_manager) = self.session_manager.as_mut() {
session_manager.deinit_session(session_id).await?;
Ok(Response::Null)
} else {
error!("The service is not enabled yet");
Err(Error::BadParameters)
}
}
Command::StartRanging { session_id } => {
if let Some(session_manager) = self.session_manager.as_mut() {
let params = session_manager.start_ranging(session_id).await?;
Ok(Response::AppConfigParams(params))
} else {
error!("The service is not enabled yet");
Err(Error::BadParameters)
}
}
Command::StopRanging { session_id } => {
if let Some(session_manager) = self.session_manager.as_mut() {
session_manager.stop_ranging(session_id).await?;
Ok(Response::Null)
} else {
error!("The service is not enabled yet");
Err(Error::BadParameters)
}
}
Command::Reconfigure { session_id, params } => {
if let Some(session_manager) = self.session_manager.as_mut() {
session_manager.reconfigure(session_id, params).await?;
Ok(Response::Null)
} else {
error!("The service is not enabled yet");
Err(Error::BadParameters)
}
}
Command::UpdateControllerMulticastList { session_id, action, controlees } => {
if let Some(session_manager) = self.session_manager.as_mut() {
session_manager
.update_controller_multicast_list(session_id, action, controlees)
.await?;
Ok(Response::Null)
} else {
error!("The service is not enabled yet");
Err(Error::BadParameters)
}
}
Command::AndroidSetCountryCode { country_code } => {
self.uci_manager.android_set_country_code(country_code).await?;
Ok(Response::Null)
}
Command::AndroidGetPowerStats => {
let stats = self.uci_manager.android_get_power_stats().await?;
Ok(Response::PowerStats(stats))
}
Command::RawUciCmd { mt, gid, oid, payload } => {
let msg = self.uci_manager.raw_uci_cmd(mt, gid, oid, payload).await?;
Ok(Response::RawUciMessage(msg))
}
Command::GetParams { session_id } => {
if let Some(session_manager) = self.session_manager.as_mut() {
let params = session_manager.session_params(session_id).await?;
Ok(Response::AppConfigParams(params))
} else {
error!("The service is not enabled yet");
Err(Error::BadParameters)
}
}
}
}
async fn handle_core_notification(&mut self, notf: CoreNotification) {
debug!("Receive core notification: {:?}", notf);
match notf {
CoreNotification::DeviceStatus(state) => {
if state == DeviceState::DeviceStateError {
warn!("Received DeviceStateError notification, reset the service");
self.reset_service().await;
} else {
self.callback.on_uci_device_status_changed(state);
}
}
CoreNotification::GenericError(_status) => {}
}
}
async fn handle_session_notification(&mut self, notf: SessionNotification) {
match notf {
SessionNotification::SessionState { session_id, session_state, reason_code } => {
self.callback.on_session_state_changed(session_id, session_state, reason_code);
}
SessionNotification::RangeData { session_id, range_data } => {
self.callback.on_range_data_received(session_id, range_data);
}
}
}
async fn handle_vendor_notification(&mut self, notf: RawUciMessage) {
self.callback.on_vendor_notification_received(notf.gid, notf.oid, notf.payload);
}
async fn enable_service(&mut self) -> Result<()> {
if self.session_manager.is_some() {
debug!("The service is already enabled, skip.");
return Ok(());
}
let (core_notf_sender, core_notf_receiver) = mpsc::unbounded_channel();
let (uci_session_notf_sender, uci_session_notf_receiver) = mpsc::unbounded_channel();
let (vendor_notf_sender, vendor_notf_receiver) = mpsc::unbounded_channel();
self.uci_manager.set_core_notification_sender(core_notf_sender).await;
self.uci_manager.set_session_notification_sender(uci_session_notf_sender).await;
self.uci_manager.set_vendor_notification_sender(vendor_notf_sender).await;
self.uci_manager.open_hal().await?;
let (session_notf_sender, session_notf_receiver) = mpsc::unbounded_channel();
self.core_notf_receiver = core_notf_receiver;
self.session_notf_receiver = session_notf_receiver;
self.vendor_notf_receiver = vendor_notf_receiver;
self.session_manager = Some(SessionManager::new(
self.uci_manager.clone(),
uci_session_notf_receiver,
session_notf_sender,
));
Ok(())
}
async fn disable_service(&mut self, force: bool) -> Result<()> {
self.core_notf_receiver = mpsc::unbounded_channel().1;
self.session_notf_receiver = mpsc::unbounded_channel().1;
self.vendor_notf_receiver = mpsc::unbounded_channel().1;
self.session_manager = None;
self.uci_manager.close_hal(force).await?;
Ok(())
}
async fn reset_service(&mut self) {
let _ = self.disable_service(true).await;
let result = self.enable_service().await;
if result.is_err() {
error!("Failed to reset the service.");
}
self.callback.on_service_reset(result.is_ok());
}
}
impl<C: UwbServiceCallback, U: UciManager> Drop for UwbServiceActor<C, U> {
fn drop(&mut self) {
// mpsc receivers are about to be dropped. Clean shutdown the mpsc message.
clean_mpsc_receiver(&mut self.core_notf_receiver);
clean_mpsc_receiver(&mut self.session_notf_receiver);
clean_mpsc_receiver(&mut self.vendor_notf_receiver);
}
}
#[derive(Debug)]
enum Command {
SetLoggerMode {
logger_mode: UciLoggerMode,
},
Enable,
Disable,
InitSession {
session_id: SessionId,
session_type: SessionType,
params: AppConfigParams,
},
DeinitSession {
session_id: SessionId,
},
StartRanging {
session_id: SessionId,
},
StopRanging {
session_id: SessionId,
},
Reconfigure {
session_id: SessionId,
params: AppConfigParams,
},
UpdateControllerMulticastList {
session_id: SessionId,
action: UpdateMulticastListAction,
controlees: Vec<Controlee>,
},
AndroidSetCountryCode {
country_code: CountryCode,
},
AndroidGetPowerStats,
RawUciCmd {
mt: u32,
gid: u32,
oid: u32,
payload: Vec<u8>,
},
GetParams {
session_id: SessionId,
},
}
#[derive(Debug)]
enum Response {
Null,
AppConfigParams(AppConfigParams),
PowerStats(PowerStats),
RawUciMessage(RawUciMessage),
}
type ResponseSender = oneshot::Sender<Result<Response>>;
#[cfg(test)]
mod tests {
use super::*;
use tokio::runtime::Runtime;
use crate::params::uci_packets::{SessionState, SetAppConfigResponse, StatusCode};
use crate::service::mock_uwb_service_callback::MockUwbServiceCallback;
use crate::service::uwb_service_builder::default_runtime;
use crate::service::uwb_service_callback_builder::UwbServiceCallbackSendBuilder;
use crate::session::session_manager::test_utils::{
generate_params, range_data_notf, session_range_data, session_status_notf,
};
use crate::uci::mock_uci_manager::MockUciManager;
use crate::uci::notification::UciNotification;
fn setup_uwb_service(
uci_manager: MockUciManager,
) -> (UwbService, MockUwbServiceCallback, Runtime) {
let runtime = default_runtime().unwrap();
let callback = MockUwbServiceCallback::new();
let callback_builder = UwbServiceCallbackSendBuilder::new(callback.clone());
let service =
UwbService::new(runtime.handle().to_owned(), callback_builder, uci_manager).unwrap();
(service, callback, runtime)
}
#[test]
fn test_open_close_uci() {
let mut uci_manager = MockUciManager::new();
uci_manager.expect_open_hal(vec![], Ok(()));
uci_manager.expect_close_hal(false, Ok(()));
let (service, _, _runtime) = setup_uwb_service(uci_manager);
let result = service.enable();
assert!(result.is_ok());
let result = service.disable();
assert!(result.is_ok());
}
#[test]
fn test_session_e2e() {
let session_id = 0x123;
let session_type = SessionType::FiraRangingSession;
let params = generate_params();
let tlvs = params.generate_tlvs();
let range_data = session_range_data(session_id);
let mut uci_manager = MockUciManager::new();
uci_manager.expect_open_hal(vec![], Ok(()));
uci_manager.expect_session_init(
session_id,
session_type,
vec![session_status_notf(session_id, SessionState::SessionStateInit)],
Ok(()),
);
uci_manager.expect_session_set_app_config(
session_id,
tlvs,
vec![session_status_notf(session_id, SessionState::SessionStateIdle)],
Ok(SetAppConfigResponse { status: StatusCode::UciStatusOk, config_status: vec![] }),
);
uci_manager.expect_range_start(
session_id,
vec![
session_status_notf(session_id, SessionState::SessionStateActive),
range_data_notf(range_data.clone()),
],
Ok(()),
);
uci_manager.expect_range_stop(
session_id,
vec![session_status_notf(session_id, SessionState::SessionStateIdle)],
Ok(()),
);
uci_manager.expect_session_deinit(
session_id,
vec![session_status_notf(session_id, SessionState::SessionStateDeinit)],
Ok(()),
);
let (service, mut callback, _runtime) = setup_uwb_service(uci_manager.clone());
service.enable().unwrap();
// Initialize a normal session.
callback.expect_on_session_state_changed(
session_id,
SessionState::SessionStateInit,
ReasonCode::StateChangeWithSessionManagementCommands,
);
callback.expect_on_session_state_changed(
session_id,
SessionState::SessionStateIdle,
ReasonCode::StateChangeWithSessionManagementCommands,
);
let result = service.init_session(session_id, session_type, params);
assert!(result.is_ok());
assert!(service.block_on_for_testing(callback.wait_expected_calls_done()));
// Start the ranging process, and should receive the range data.
callback.expect_on_session_state_changed(
session_id,
SessionState::SessionStateActive,
ReasonCode::StateChangeWithSessionManagementCommands,
);
callback.expect_on_range_data_received(session_id, range_data);
let result = service.start_ranging(session_id);
assert!(result.is_ok());
assert!(service.block_on_for_testing(callback.wait_expected_calls_done()));
// Stop the ranging process.
callback.expect_on_session_state_changed(
session_id,
SessionState::SessionStateIdle,
ReasonCode::StateChangeWithSessionManagementCommands,
);
let result = service.stop_ranging(session_id);
assert!(result.is_ok());
assert!(service.block_on_for_testing(callback.wait_expected_calls_done()));
// Deinitialize the session, and should receive the deinitialized notification.
callback.expect_on_session_state_changed(
session_id,
SessionState::SessionStateDeinit,
ReasonCode::StateChangeWithSessionManagementCommands,
);
let result = service.deinit_session(session_id);
assert!(result.is_ok());
assert!(service.block_on_for_testing(callback.wait_expected_calls_done()));
// Verify if all of the expected uci_manager method are called.
assert!(service.block_on_for_testing(uci_manager.wait_expected_calls_done()));
}
#[test]
fn test_session_api_without_enabled() {
let session_id = 0x123;
let session_type = SessionType::FiraRangingSession;
let params = generate_params();
let action = UpdateMulticastListAction::AddControlee;
let controlees = vec![Controlee { short_address: 0x13, subsession_id: 0x24 }];
let uci_manager = MockUciManager::new();
let (service, _, _runtime) = setup_uwb_service(uci_manager);
let result = service.init_session(session_id, session_type, params.clone());
assert!(result.is_err());
let result = service.deinit_session(session_id);
assert!(result.is_err());
let result = service.start_ranging(session_id);
assert!(result.is_err());
let result = service.stop_ranging(session_id);
assert!(result.is_err());
let result = service.reconfigure(session_id, params);
assert!(result.is_err());
let result = service.update_controller_multicast_list(session_id, action, controlees);
assert!(result.is_err());
}
#[test]
fn test_android_set_country_code() {
let country_code = CountryCode::new(b"US").unwrap();
let mut uci_manager = MockUciManager::new();
uci_manager.expect_android_set_country_code(country_code.clone(), Ok(()));
let (service, _, _runtime) = setup_uwb_service(uci_manager);
let result = service.android_set_country_code(country_code);
assert!(result.is_ok());
}
#[test]
fn test_android_get_power_stats() {
let stats = PowerStats {
status: StatusCode::UciStatusOk,
idle_time_ms: 123,
tx_time_ms: 456,
rx_time_ms: 789,
total_wake_count: 5,
};
let mut uci_manager = MockUciManager::new();
uci_manager.expect_android_get_power_stats(Ok(stats.clone()));
let (service, _, _runtime) = setup_uwb_service(uci_manager);
let result = service.android_get_power_stats().unwrap();
assert_eq!(result, stats);
}
#[test]
fn test_send_raw_cmd() {
let mt = 0x01;
let gid = 0x09;
let oid = 0x35;
let cmd_payload = vec![0x12, 0x34];
let resp_payload = vec![0x56, 0x78];
let mut uci_manager = MockUciManager::new();
uci_manager.expect_raw_uci_cmd(
mt,
gid,
oid,
cmd_payload.clone(),
Ok(RawUciMessage { gid, oid, payload: resp_payload.clone() }),
);
let (service, _, _runtime) = setup_uwb_service(uci_manager);
let result = service.raw_uci_cmd(mt, gid, oid, cmd_payload).unwrap();
assert_eq!(result, RawUciMessage { gid, oid, payload: resp_payload });
}
#[test]
fn test_vendor_notification() {
let gid = 5;
let oid = 7;
let payload = vec![0x13, 0x47];
let mut uci_manager = MockUciManager::new();
uci_manager.expect_open_hal(
vec![UciNotification::Vendor(RawUciMessage { gid, oid, payload: payload.clone() })],
Ok(()),
);
let (service, mut callback, _runtime) = setup_uwb_service(uci_manager);
callback.expect_on_vendor_notification_received(gid, oid, payload);
service.enable().unwrap();
assert!(service.block_on_for_testing(callback.wait_expected_calls_done()));
}
#[test]
fn test_core_device_status_notification() {
let state = DeviceState::DeviceStateReady;
let mut uci_manager = MockUciManager::new();
uci_manager.expect_open_hal(
vec![UciNotification::Core(CoreNotification::DeviceStatus(state))],
Ok(()),
);
let (service, mut callback, _runtime) = setup_uwb_service(uci_manager);
callback.expect_on_uci_device_status_changed(state);
service.enable().unwrap();
assert!(service.block_on_for_testing(callback.wait_expected_calls_done()));
}
#[test]
fn test_reset_service_after_timeout() {
let mut uci_manager = MockUciManager::new();
// The first open_hal() returns timeout.
uci_manager.expect_open_hal(vec![], Err(Error::Timeout));
// Then UwbService should close_hal() and open_hal() to reset the HAL.
uci_manager.expect_close_hal(true, Ok(()));
uci_manager.expect_open_hal(vec![], Ok(()));
let (service, mut callback, _runtime) = setup_uwb_service(uci_manager.clone());
callback.expect_on_service_reset(true);
let result = service.enable();
assert_eq!(result, Err(Error::Timeout));
assert!(service.block_on_for_testing(callback.wait_expected_calls_done()));
assert!(service.block_on_for_testing(uci_manager.wait_expected_calls_done()));
}
#[test]
fn test_reset_service_when_error_state() {
let mut uci_manager = MockUciManager::new();
// The first open_hal() send DeviceStateError notification.
uci_manager.expect_open_hal(
vec![UciNotification::Core(CoreNotification::DeviceStatus(
DeviceState::DeviceStateError,
))],
Ok(()),
);
// Then UwbService should close_hal() and open_hal() to reset the HAL.
uci_manager.expect_close_hal(true, Ok(()));
uci_manager.expect_open_hal(vec![], Ok(()));
let (service, mut callback, _runtime) = setup_uwb_service(uci_manager.clone());
callback.expect_on_service_reset(true);
let result = service.enable();
assert_eq!(result, Ok(()));
assert!(service.block_on_for_testing(callback.wait_expected_calls_done()));
assert!(service.block_on_for_testing(uci_manager.wait_expected_calls_done()));
}
}