blob: ff1790227ad212799ecae1d936c21e4e4775e1eb [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;
/// The maximum number of characters of a long alphanumeric name.
/// Defined in HFP v1.9, Section 4.8.
pub const MAX_LONG_ALPHANUMERIC_NAME_SIZE: usize = 16;
/// Formats the provided `name` to conform to the current network operator format.
pub fn format_operator_name(format: at::NetworkOperatorNameFormat, mut name: String) -> String {
match format {
at::NetworkOperatorNameFormat::LongAlphanumeric => {
if name.len() > MAX_LONG_ALPHANUMERIC_NAME_SIZE {
log::info!("Truncating network operator name: {}", name);
name.truncate(MAX_LONG_ALPHANUMERIC_NAME_SIZE);
}
}
}
name
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum State {
/// Initial state of the procedure.
Start,
/// A request has been received from the HF to set the network operator format.
SetFormatRequest,
/// A request has been received from the HF to send the current Network Name.
GetName,
/// The AG has responded to the HF's request with the current Network Name and the procedure
/// is complete.
Terminated,
}
impl State {
fn is_start(&self) -> bool {
if let Self::Start = self {
true
} else {
false
}
}
/// Transition to the next state in the QOS procedure.
/// If `skip_set_format` is set, the transition will skip the `SetFormat` state.
fn transition(&mut self, skip_set_format: bool) {
match *self {
Self::Start if skip_set_format => *self = Self::GetName,
Self::Start => *self = Self::SetFormatRequest,
Self::SetFormatRequest => *self = Self::GetName,
Self::GetName => *self = Self::Terminated,
Self::Terminated => *self = Self::Terminated,
}
}
}
/// Represents the Query Operator Selection procedure as defined in HFP v1.8 Section 4.8.
///
/// The HF may request the name of the currently selected Network Operator in the AG
/// via this procedure.
///
/// 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.
pub struct QueryOperatorProcedure {
state: State,
}
impl QueryOperatorProcedure {
pub fn new() -> Self {
Self { state: State::Start }
}
}
impl Procedure for QueryOperatorProcedure {
fn marker(&self) -> ProcedureMarker {
ProcedureMarker::QueryOperatorSelection
}
fn hf_update(&mut self, update: at::Command, state: &mut SlcState) -> ProcedureRequest {
// This format is required to be used by the spec, so it's the default if none is set.
let format = state
.ag_network_operator_name_format
.unwrap_or(at::NetworkOperatorNameFormat::LongAlphanumeric);
match (self.state, update) {
(State::Start, at::Command::Cops { three: _, format }) => {
// The remote peer has requested to set the network name format.
state.ag_network_operator_name_format = Some(format);
self.state.transition(/* skip_set_format= */ false);
AgUpdate::Ok.into()
}
(State::Start, at::Command::CopsRead {})
| (State::SetFormatRequest, at::Command::CopsRead {}) => {
self.state.transition(/* skip_set_format= */ self.state.is_start());
let response = Box::new(move |network_name: Option<_>| {
let name =
format_operator_name(format, network_name.unwrap_or_else(String::new));
AgUpdate::NetworkOperatorName(format, name)
});
InformationRequest::GetNetworkOperatorName { response }.into()
}
(State::Terminated, at::Command::Cops { .. })
| (State::Terminated, at::Command::CopsRead {}) => {
ProcedureRequest::Error(ProcedureError::AlreadyTerminated)
}
(_, update) => ProcedureRequest::Error(ProcedureError::UnexpectedHf(update)),
}
}
fn ag_update(&mut self, update: AgUpdate, _state: &mut SlcState) -> ProcedureRequest {
match (self.state, update) {
(State::GetName, update @ AgUpdate::NetworkOperatorName(..)) => {
self.state.transition(/* skip_set_format= */ false);
update.into()
}
(State::Terminated, AgUpdate::NetworkOperatorName(..)) => {
ProcedureRequest::Error(ProcedureError::AlreadyTerminated)
}
(_, update) => ProcedureRequest::Error(ProcedureError::UnexpectedAg(update)),
}
}
/// Returns true if the Procedure is finished.
fn is_terminated(&self) -> bool {
self.state == State::Terminated
}
}
#[cfg(test)]
mod tests {
use super::*;
use matches::assert_matches;
#[test]
fn state_transitions() {
let mut state = State::Start;
state.transition(/* skip_set_format= */ false);
assert_eq!(state, State::SetFormatRequest);
state.transition(/* skip_set_format= */ false);
assert_eq!(state, State::GetName);
state.transition(/* skip_set_format= */ false);
assert_eq!(state, State::Terminated);
state.transition(/* skip_set_format= */ false);
assert_eq!(state, State::Terminated);
}
#[test]
fn state_transition_when_skipping_set_format() {
let mut state = State::Start;
state.transition(/* skip_set_format= */ true);
assert_eq!(state, State::GetName);
state.transition(/* skip_set_format= */ false);
assert_eq!(state, State::Terminated);
state.transition(/* skip_set_format= */ false);
assert_eq!(state, State::Terminated);
}
#[test]
fn correct_marker() {
let marker = QueryOperatorProcedure::new().marker();
assert_eq!(marker, ProcedureMarker::QueryOperatorSelection);
}
#[test]
fn is_terminated_in_terminated_state() {
let mut proc = QueryOperatorProcedure::new();
assert!(!proc.is_terminated());
proc.state = State::SetFormatRequest;
assert!(!proc.is_terminated());
proc.state = State::GetName;
assert!(!proc.is_terminated());
proc.state = State::Terminated;
assert!(proc.is_terminated());
}
#[test]
fn unexpected_hf_update_returns_error() {
let mut procedure = QueryOperatorProcedure::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 = QueryOperatorProcedure::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 updates_produce_expected_requests() {
let mut p = QueryOperatorProcedure::new();
let test_operator_name = Some("Foobar".to_string());
let mut state = SlcState::default();
// The HF request to set the format should update the shared state.
let expected_format = at::NetworkOperatorNameFormat::LongAlphanumeric;
let update1 = at::Command::Cops { three: 3, format: expected_format };
assert_matches!(p.hf_update(update1, &mut state), ProcedureRequest::SendMessages(_));
assert_eq!(state.ag_network_operator_name_format, Some(expected_format));
let update2 = at::Command::CopsRead {};
let update3 = match p.hf_update(update2, &mut state) {
ProcedureRequest::Info(InformationRequest::GetNetworkOperatorName { response }) => {
response(test_operator_name)
}
x => {
panic!("Expected get network operator request but got: {:?}", x);
}
};
assert_matches!(p.ag_update(update3, &mut state), ProcedureRequest::SendMessages(_));
// Check that the procedure is terminated and any new messages produce an error.
assert!(p.is_terminated());
assert_matches!(
p.hf_update(
at::Command::Cops {
three: 3,
format: at::NetworkOperatorNameFormat::LongAlphanumeric
},
&mut state
),
ProcedureRequest::Error(ProcedureError::AlreadyTerminated)
);
assert_matches!(
p.ag_update(
AgUpdate::NetworkOperatorName(
at::NetworkOperatorNameFormat::LongAlphanumeric,
"foo".into()
),
&mut state
),
ProcedureRequest::Error(ProcedureError::AlreadyTerminated)
);
}
#[test]
fn updates_when_skipping_set_format_produce_expected_requests() {
let mut p = QueryOperatorProcedure::new();
let mut state = SlcState::default();
let test_operator_name = Some("Bar".to_string());
let update1 = at::Command::CopsRead {};
let update2 = match p.hf_update(update1, &mut state) {
ProcedureRequest::Info(InformationRequest::GetNetworkOperatorName { response }) => {
response(test_operator_name)
}
x => panic!("Expected get network operator request but got: {:?}", x),
};
assert_matches!(p.ag_update(update2, &mut state), ProcedureRequest::SendMessages(_));
assert!(p.is_terminated());
}
#[test]
fn update_with_empty_name_produces_expected_requests() {
let mut p = QueryOperatorProcedure::new();
let mut state = SlcState::default();
let test_operator_name = None;
let update1 = at::Command::CopsRead {};
let update2 = match p.hf_update(update1, &mut state) {
ProcedureRequest::Info(InformationRequest::GetNetworkOperatorName { response }) => {
response(test_operator_name)
}
x => panic!("Expected get network operator request but got: {:?}", x),
};
assert_matches!(p.ag_update(update2, &mut state), ProcedureRequest::SendMessages(_));
assert!(p.is_terminated());
}
}