| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use super::{ |
| AgUpdate, InformationRequest, Procedure, ProcedureError, ProcedureMarker, ProcedureRequest, |
| }; |
| |
| use crate::peer::service_level_connection::SlcState; |
| use at_commands as at; |
| |
| /// Represents the current state of the HF request to transmit an HF Indicator value as |
| /// defined in HFP v1.8 Section 4.36.1.5. |
| #[derive(Debug, PartialEq, Clone, Copy)] |
| enum State { |
| /// Initial state of the Procedure. |
| Start, |
| /// A request has been received from the HF to transmit the indicator via the AG. |
| SendRequest, |
| /// Terminal state of the procedure. |
| Terminated, |
| } |
| |
| impl State { |
| /// Transition to the next state in the procedure. |
| fn transition(&mut self) { |
| match *self { |
| Self::Start => *self = Self::SendRequest, |
| Self::SendRequest => *self = Self::Terminated, |
| Self::Terminated => *self = Self::Terminated, |
| } |
| } |
| } |
| |
| /// The Hf may send an updated HF Indicator value via this procedure. Defined in |
| /// HFP v1.8 Section 4.36.1.5. |
| /// |
| /// This procedure is implemented from the perspective of the AG. Namely, outgoing `requests` |
| /// typically request information about the current state of the AG, to be sent to the remote |
| /// peer acting as the HF. |
| #[derive(Debug)] |
| pub struct TransferHfIndicatorProcedure { |
| /// The current state of the procedure |
| state: State, |
| } |
| |
| impl Default for TransferHfIndicatorProcedure { |
| fn default() -> Self { |
| Self { state: State::Start } |
| } |
| } |
| |
| impl TransferHfIndicatorProcedure { |
| /// Create a new Transfer HF Indicator procedure in the Start state. |
| pub fn new() -> Self { |
| Self::default() |
| } |
| } |
| |
| impl Procedure for TransferHfIndicatorProcedure { |
| fn marker(&self) -> ProcedureMarker { |
| ProcedureMarker::TransferHfIndicator |
| } |
| |
| fn hf_update(&mut self, update: at::Command, state: &mut SlcState) -> ProcedureRequest { |
| match (self.state, update) { |
| (State::Start, at::Command::Biev { anum, value }) => { |
| self.state.transition(); |
| // Per HFP v1.8 Section 4.36.1.5, we should send Error if the request `anum` is |
| // disabled, or the `value` is out of bounds. |
| if let Ok(indicator) = state.hf_indicators.update_indicator_value(anum, value) { |
| InformationRequest::SendHfIndicator { |
| indicator, |
| response: Box::new(|| AgUpdate::Ok), |
| } |
| .into() |
| } else { |
| self.state.transition(); |
| AgUpdate::Error.into() |
| } |
| } |
| (_, update) => ProcedureRequest::Error(ProcedureError::UnexpectedHf(update)), |
| } |
| } |
| |
| fn ag_update(&mut self, update: AgUpdate, _state: &mut SlcState) -> ProcedureRequest { |
| match (self.state, update) { |
| (State::SendRequest, update @ AgUpdate::Ok) => { |
| self.state.transition(); |
| update.into() |
| } |
| (_, update) => ProcedureRequest::Error(ProcedureError::UnexpectedAg(update)), |
| } |
| } |
| |
| fn is_terminated(&self) -> bool { |
| self.state == State::Terminated |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::protocol::indicators::HfIndicator; |
| use matches::assert_matches; |
| |
| #[test] |
| fn correct_marker() { |
| let marker = TransferHfIndicatorProcedure::new().marker(); |
| assert_eq!(marker, ProcedureMarker::TransferHfIndicator); |
| } |
| |
| #[test] |
| fn procedure_handles_invalid_messages() { |
| let mut proc = TransferHfIndicatorProcedure::new(); |
| let req = proc.hf_update(at::Command::CindRead {}, &mut SlcState::default()); |
| assert_matches!(req, ProcedureRequest::Error(ProcedureError::UnexpectedHf(_))); |
| |
| let req = proc.ag_update(AgUpdate::ThreeWaySupport, &mut SlcState::default()); |
| assert_matches!(req, ProcedureRequest::Error(ProcedureError::UnexpectedAg(_))); |
| } |
| |
| #[test] |
| fn procedure_with_invalid_battery_value_sends_error_message() { |
| let mut proc = TransferHfIndicatorProcedure::new(); |
| let mut state = SlcState::default(); |
| state.hf_indicators.enable_indicators(vec![ |
| at::BluetoothHFIndicator::BatteryLevel, |
| at::BluetoothHFIndicator::EnhancedSafety, |
| ]); |
| |
| // Battery level is not within the range [0,100]. |
| let cmd = at::Command::Biev { anum: at::BluetoothHFIndicator::BatteryLevel, value: 164 }; |
| let req = proc.hf_update(cmd, &mut state); |
| let expected = vec![at::Response::Error]; |
| assert_matches!(req, ProcedureRequest::SendMessages(m) if m == expected); |
| assert!(proc.is_terminated()); |
| } |
| |
| #[test] |
| fn procedure_with_valid_battery_value_sends_ok() { |
| let mut proc = TransferHfIndicatorProcedure::new(); |
| let mut state = SlcState::default(); |
| state.hf_indicators.enable_indicators(vec![ |
| at::BluetoothHFIndicator::BatteryLevel, |
| at::BluetoothHFIndicator::EnhancedSafety, |
| ]); |
| |
| let cmd = at::Command::Biev { anum: at::BluetoothHFIndicator::BatteryLevel, value: 76 }; |
| let req = proc.hf_update(cmd, &mut state); |
| let update = match req { |
| ProcedureRequest::Info(InformationRequest::SendHfIndicator { |
| indicator: HfIndicator::BatteryLevel(76), |
| response, |
| }) => response(), |
| x => panic!("Expected SendHFInd request but got: {:?}", x), |
| }; |
| |
| let req = proc.ag_update(update, &mut state); |
| assert_matches!( |
| req, |
| ProcedureRequest::SendMessages(msgs) if msgs == vec![at::Response::Ok] |
| ); |
| assert!(proc.is_terminated()); |
| } |
| |
| #[test] |
| fn procedure_with_invalid_safety_value_sends_error_message() { |
| let mut proc = TransferHfIndicatorProcedure::new(); |
| let mut state = SlcState::default(); |
| state.hf_indicators.enable_indicators(vec![ |
| at::BluetoothHFIndicator::BatteryLevel, |
| at::BluetoothHFIndicator::EnhancedSafety, |
| ]); |
| |
| // Enhanced Safety can only be 0 or 1. |
| let cmd = at::Command::Biev { anum: at::BluetoothHFIndicator::EnhancedSafety, value: 7 }; |
| let req = proc.hf_update(cmd, &mut state); |
| let expected = vec![at::Response::Error]; |
| assert_matches!(req, ProcedureRequest::SendMessages(m) if m == expected); |
| assert!(proc.is_terminated()); |
| } |
| |
| #[test] |
| fn procedure_with_valid_safety_value_sends_ok() { |
| let mut proc = TransferHfIndicatorProcedure::new(); |
| let mut state = SlcState::default(); |
| state.hf_indicators.enable_indicators(vec![ |
| at::BluetoothHFIndicator::BatteryLevel, |
| at::BluetoothHFIndicator::EnhancedSafety, |
| ]); |
| |
| let cmd = at::Command::Biev { anum: at::BluetoothHFIndicator::EnhancedSafety, value: 1 }; |
| let req = proc.hf_update(cmd, &mut state); |
| let update = match req { |
| ProcedureRequest::Info(InformationRequest::SendHfIndicator { |
| indicator: HfIndicator::EnhancedSafety(true), |
| response, |
| }) => response(), |
| x => panic!("Expected SendHFInd request but got: {:?}", x), |
| }; |
| |
| let req = proc.ag_update(update, &mut state); |
| assert_matches!( |
| req, |
| ProcedureRequest::SendMessages(msgs) if msgs == vec![at::Response::Ok] |
| ); |
| assert!(proc.is_terminated()); |
| } |
| } |