blob: 4f800358dcd27d8e6259bbf02a90e7732c6ec641 [file] [log] [blame]
// 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());
}
}