blob: 3741ec301fc82dbb06fd5dfedd7175dbae015567 [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, Procedure, ProcedureError, ProcedureMarker, ProcedureRequest};
use crate::{
peer::service_level_connection::SlcState, protocol::indicators::AgIndicatorsReporting,
};
use at_commands as at;
/// Converts the indicator activeness flags (represented as Strings) to a vector of
/// optional flags. Returns the set of flags or an Error if the `indicators` are formatted
/// incorrectly.
// TODO(fxbug.dev/73374): Remove this once the AT library represents the command as a Vec<Option<bool>>.
fn to_flags(indicators: Vec<String>) -> Result<Vec<Option<bool>>, ()> {
let result: Vec<Result<Option<bool>, ()>> = indicators
.into_iter()
.map(|ind| match ind.as_str() {
"" => Ok(None),
"1" => Ok(Some(true)),
"0" => Ok(Some(false)),
_ => Err(()),
})
.collect();
result.into_iter().collect()
}
/// The Hf may request to enable or disable indicators via this procedure. Defined in
/// HFP v1.8 Section 4.35.
///
/// 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, Default)]
pub struct IndicatorsActivationProcedure {
/// The current state of the procedure
terminated: bool,
}
impl IndicatorsActivationProcedure {
pub fn new() -> Self {
Self::default()
}
}
impl Procedure for IndicatorsActivationProcedure {
fn marker(&self) -> ProcedureMarker {
ProcedureMarker::Indicators
}
fn hf_update(&mut self, update: at::Command, state: &mut SlcState) -> ProcedureRequest {
match (self.terminated, update) {
(false, at::Command::Bia { indrep }) => {
self.terminated = true;
if let Ok(flags) = to_flags(indrep) {
state.ag_indicator_events_reporting.update_from_flags(flags);
AgUpdate::Ok.into()
} else {
// Per HFP v1.8 Section 4.35, if the command is incorrectly formatted,
// send an ERROR result code.
AgUpdate::Error.into()
}
}
(false, at::Command::Cmer { mode, ind, .. }) => {
self.terminated = true;
if mode == AgIndicatorsReporting::EVENT_REPORTING_MODE
&& state.ag_indicator_events_reporting.set_reporting_status(ind).is_ok()
{
AgUpdate::Ok.into()
} else {
AgUpdate::Error.into()
}
}
(_, update) => ProcedureRequest::Error(ProcedureError::UnexpectedHf(update)),
}
}
fn is_terminated(&self) -> bool {
self.terminated
}
}
#[cfg(test)]
mod tests {
use super::*;
use matches::assert_matches;
#[test]
fn to_flags_conversion_produces_expected_results() {
let empty = Vec::new();
let expected: Vec<Option<bool>> = vec![];
assert_matches!(to_flags(empty), Ok(v) if v == expected);
let valid = vec!["1".to_string(), String::new(), "0".to_string()];
let expected = vec![Some(true), None, Some(false)];
assert_matches!(to_flags(valid), Ok(v) if v == expected);
let invalid = vec!["0".to_string(), "foo".to_string()];
assert_matches!(to_flags(invalid), Err(()));
}
#[test]
fn correct_marker() {
let marker = IndicatorsActivationProcedure::new().marker();
assert_eq!(marker, ProcedureMarker::Indicators);
}
#[test]
fn unexpected_hf_update_returns_error() {
let mut procedure = IndicatorsActivationProcedure::new();
let mut state = SlcState::default();
// SLCI AT command.
let random_hf = at::Command::CindRead {};
assert_matches!(
procedure.hf_update(random_hf, &mut state),
ProcedureRequest::Error(ProcedureError::UnexpectedHf(_))
);
}
#[test]
fn unexpected_ag_update_returns_error() {
let mut procedure = IndicatorsActivationProcedure::new();
let mut state = SlcState::default();
// SLCI AT command.
let random_ag = AgUpdate::ThreeWaySupport;
assert_matches!(
procedure.ag_update(random_ag, &mut state),
ProcedureRequest::Error(ProcedureError::UnexpectedAg(_))
);
}
#[test]
fn update_with_invalid_indicators_produces_error_request() {
let mut procedure = IndicatorsActivationProcedure::new();
let mut state = SlcState::default();
assert!(!procedure.is_terminated());
// "8" is not a supported value for an indicator.
let indicators = vec![
"0".to_string(),
"1".to_string(),
"0".to_string(),
String::new(),
"8".to_string(),
"1".to_string(),
];
let update = at::Command::Bia { indrep: indicators };
let expected_messages = vec![at::Response::Error];
assert_matches!(procedure.hf_update(update, &mut state), ProcedureRequest::SendMessages(m) if m == expected_messages);
assert!(procedure.is_terminated());
}
#[test]
fn update_with_valid_indicators_produces_ok_request() {
let mut procedure = IndicatorsActivationProcedure::new();
let mut state = SlcState::default();
assert!(!procedure.is_terminated());
let indicators = vec![
"0".to_string(),
"1".to_string(),
"0".to_string(),
String::new(),
"1".to_string(),
String::new(),
String::new(),
];
let update = at::Command::Bia { indrep: indicators };
let expected_messages = vec![at::Response::Ok];
assert_matches!(procedure.hf_update(update.clone(), &mut state), ProcedureRequest::SendMessages(m) if m == expected_messages);
assert!(procedure.is_terminated());
// The SlcState's indicators should be updated with the new indicator values.
// Note: The request to change Call & Call Setup were ignored since those must be enabled
// at all times per HFP v1.8 Section 4.35.
let mut expected_indicators = AgIndicatorsReporting::default();
expected_indicators.set_service(false);
expected_indicators.set_signal(true);
assert_eq!(state.ag_indicator_events_reporting, expected_indicators);
// Trying to send a request after procedure is terminated will fail.
assert_matches!(
procedure.hf_update(update, &mut state),
ProcedureRequest::Error(ProcedureError::UnexpectedHf(_))
);
}
}