blob: cf7ce71079377d5f62df0ba9b196125741ddb2ca [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.
//! Tests for AT command responses.
#![cfg(test)]
use {
crate::{
highlevel, lowlevel,
lowlevel::write_to::WriteTo as _,
parser::response_parser,
serde::{internal::SerDeOne, success},
translate,
},
std::{collections::HashMap, io::Cursor},
};
fn cr_lf_delimit(str: &str) -> String {
format!("\r\n{}\r\n", str)
}
// This function tests that the raise, lower, parse and write funtions all compose and round trip as
// expected, and that the serde methods which compose them continue to work as well. It takes a
// highlevel representation, a lowlevel representation and a string representation of the same AT
// command and tests that converting between them in all possible ways produce the expected values.
fn test_roundtrips(highlevel: highlevel::Response, lowlevel: lowlevel::Response, string: String) {
// TEST I: highlevel -> lowlevel -> bytes -> lowlevel -> highlevel
let mut bytes_from_lowlevel = Vec::new();
// Do round trip
let lowlevel_from_highlevel = translate::lower_response(&highlevel);
lowlevel_from_highlevel.write_to(&mut bytes_from_lowlevel).expect("Failed to write lowlevel.");
// TODO(https://fxbug.dev/42144806) Convert parse to use Read rather than strings.
let string_from_lowlevel =
String::from_utf8(bytes_from_lowlevel).expect("Failed to convert bytes to UTF8.");
let lowlevel_from_bytes =
response_parser::parse(&string_from_lowlevel).expect("Failed to parse String.");
let highlevel_from_lowlevel =
translate::raise_response(&lowlevel_from_bytes).expect("Failed to raise lowlevel.");
// Assert all the things are equal.
assert_eq!(highlevel, highlevel_from_lowlevel);
assert_eq!(lowlevel, lowlevel_from_highlevel);
assert_eq!(lowlevel, lowlevel_from_bytes);
assert_eq!(string, string_from_lowlevel);
// TEST II: highlevel -> bytes -> highlevel
// This should be identical to above assuming SerDeOne::serialize_one and
// SerDeOne::deserialize_one are implemented correctly.
let mut bytes_from_highlevel = Vec::new();
// Do round trip
highlevel.serialize_one(&mut bytes_from_highlevel).expect("Failed to serialize highlevel.");
let highlevel_from_bytes =
highlevel::Response::deserialize_one(&mut Cursor::new(bytes_from_highlevel.clone()))
.expect("Failed to serialize bytes created by serializing highlevel.");
// Convert to a String so errors are human readable, not just hex.
let string_from_highlevel = String::from_utf8(bytes_from_highlevel)
.expect("Failed to serialize bytes created by serializing highlevel.");
// Assert all the things are equal.
assert_eq!(highlevel, highlevel_from_bytes);
assert_eq!(string, string_from_highlevel);
// TEST III: bytes -> lowlevel -> highlevel -> lowlevel -> bytes
let mut bytes_from_lowlevel = Vec::new();
// Do round trip
// TODO(https://fxbug.dev/42144806) Convert parse to use Read rather than strings.
let lowlevel_from_bytes = response_parser::parse(&string).expect("Failed to parse string.");
let highlevel_from_lowlevel =
translate::raise_response(&lowlevel_from_bytes).expect("Failed to raise lowlevel.");
let lowlevel_from_highlevel = translate::lower_response(&highlevel_from_lowlevel);
lowlevel_from_highlevel.write_to(&mut bytes_from_lowlevel).expect("Failed to write lowlevel.");
// Convert to a String so errors are human readable, not just hex.
let string_from_lowlevel =
String::from_utf8(bytes_from_lowlevel).expect("Failed to convert bytes to UFT8.");
// Assert all the things are equal.
assert_eq!(highlevel, highlevel_from_lowlevel);
assert_eq!(lowlevel, lowlevel_from_highlevel);
assert_eq!(lowlevel, lowlevel_from_bytes);
assert_eq!(string, string_from_lowlevel);
// TEST IV: bytes -> highlevel -> bytes
// This should be identical to above assuming SerDeOne::serialize_one and
// SerDeOne::deserialize_one are implemented correctly.
let mut bytes_from_highlevel = Vec::new();
// Do round trip
let highlevel_from_bytes =
highlevel::Response::deserialize_one(&mut Cursor::new(string.clone()))
.expect("Failed to deserialize string.");
highlevel_from_bytes
.serialize_one(&mut bytes_from_highlevel)
.expect("Failed to raise lowlevel.");
// Convert to a String so errors are human readable, not just hex.
let string_from_highlevel =
String::from_utf8(bytes_from_highlevel).expect("Failed to convert bytes to UFT8.");
// Assert all the things are equal.
assert_eq!(highlevel, highlevel_from_bytes);
assert_eq!(string, string_from_highlevel);
}
// Ok response
#[test]
fn ok() {
test_roundtrips(highlevel::Response::Ok, lowlevel::Response::Ok, cr_lf_delimit("OK"))
}
// Error response
#[test]
fn error() {
test_roundtrips(highlevel::Response::Error, lowlevel::Response::Error, cr_lf_delimit("ERROR"))
}
// Hardcoded error response NO CARRIER
#[test]
fn no_carrier() {
test_roundtrips(
highlevel::Response::HardcodedError(highlevel::HardcodedError::NoCarrier),
lowlevel::Response::HardcodedError(lowlevel::HardcodedError::NoCarrier),
cr_lf_delimit("NO CARRIER"),
)
}
// Hardcoded error response BUSY
#[test]
fn busy() {
test_roundtrips(
highlevel::Response::HardcodedError(highlevel::HardcodedError::Busy),
lowlevel::Response::HardcodedError(lowlevel::HardcodedError::Busy),
cr_lf_delimit("BUSY"),
)
}
// Hardcoded error response NO ANSWER
#[test]
fn no_answer() {
test_roundtrips(
highlevel::Response::HardcodedError(highlevel::HardcodedError::NoAnswer),
lowlevel::Response::HardcodedError(lowlevel::HardcodedError::NoAnswer),
cr_lf_delimit("NO ANSWER"),
)
}
// Hardcoded error response DELAYED
#[test]
fn delayed() {
test_roundtrips(
highlevel::Response::HardcodedError(highlevel::HardcodedError::Delayed),
lowlevel::Response::HardcodedError(lowlevel::HardcodedError::Delayed),
cr_lf_delimit("DELAYED"),
)
}
// Hardcoded error response BLACKLIST
#[test]
fn blacklist() {
test_roundtrips(
highlevel::Response::HardcodedError(highlevel::HardcodedError::Blacklist),
lowlevel::Response::HardcodedError(lowlevel::HardcodedError::Blacklist),
cr_lf_delimit("BLACKLIST"),
)
}
// +CME error
#[test]
fn cme_error() {
test_roundtrips(
highlevel::Response::CmeError(1),
lowlevel::Response::CmeError(1),
cr_lf_delimit("+CME ERROR: 1"),
)
}
// Response with no arguments
#[test]
fn no_args() {
test_roundtrips(
highlevel::Response::Success(highlevel::Success::Test {}),
lowlevel::Response::Success {
name: String::from("TEST"),
is_extension: false,
arguments: lowlevel::DelimitedArguments {
delimiter: Some(String::from(":")),
arguments: lowlevel::Arguments::ArgumentList(vec![]),
terminator: None,
},
},
cr_lf_delimit("TEST: "),
)
}
// Response with no arguments and no delimiter
#[test]
fn no_args_no_delimiter() {
test_roundtrips(
highlevel::Response::Success(highlevel::Success::Testnod {}),
lowlevel::Response::Success {
name: String::from("TESTNOD"),
is_extension: false,
arguments: lowlevel::DelimitedArguments {
delimiter: None,
arguments: lowlevel::Arguments::ArgumentList(vec![]),
terminator: None,
},
},
cr_lf_delimit("TESTNOD"),
)
}
// Extension response with no arguments
#[test]
fn ext_no_args() {
test_roundtrips(
highlevel::Response::Success(highlevel::Success::Testext {}),
lowlevel::Response::Success {
name: String::from("TESTEXT"),
is_extension: true,
arguments: lowlevel::DelimitedArguments {
delimiter: Some(String::from(":")),
arguments: lowlevel::Arguments::ArgumentList(vec![]),
terminator: None,
},
},
cr_lf_delimit("+TESTEXT: "),
)
}
// Extension response with no arguments and no delimiter
#[test]
fn ext_no_args_no_delimiter() {
test_roundtrips(
highlevel::Response::Success(highlevel::Success::Testextnod {}),
lowlevel::Response::Success {
name: String::from("TESTEXTNOD"),
is_extension: true,
arguments: lowlevel::DelimitedArguments {
delimiter: None,
arguments: lowlevel::Arguments::ArgumentList(vec![]),
terminator: None,
},
},
cr_lf_delimit("+TESTEXTNOD"),
)
}
// Extension response with one integer argument
#[test]
fn one_int_arg() {
test_roundtrips(
highlevel::Response::Success(highlevel::Success::Testi { field: 1 }),
lowlevel::Response::Success {
name: String::from("TESTI"),
is_extension: true,
arguments: lowlevel::DelimitedArguments {
delimiter: Some(String::from(":")),
arguments: lowlevel::Arguments::ArgumentList(vec![
lowlevel::Argument::PrimitiveArgument(String::from("1")),
]),
terminator: None,
},
},
cr_lf_delimit("+TESTI: 1"),
)
}
// Extension response with one integer argument
#[test]
fn one_int_arg_no_delimiter() {
test_roundtrips(
highlevel::Response::Success(highlevel::Success::Testinod { field: 1 }),
lowlevel::Response::Success {
name: String::from("TESTINOD"),
is_extension: true,
arguments: lowlevel::DelimitedArguments {
delimiter: None,
arguments: lowlevel::Arguments::ArgumentList(vec![
lowlevel::Argument::PrimitiveArgument(String::from("1")),
]),
terminator: None,
},
},
cr_lf_delimit("+TESTINOD1"),
)
}
// Extension response with one string argument
#[test]
fn one_string_arg() {
test_roundtrips(
highlevel::Response::Success(highlevel::Success::Tests { field: String::from("abc") }),
lowlevel::Response::Success {
name: String::from("TESTS"),
is_extension: true,
arguments: lowlevel::DelimitedArguments {
delimiter: Some(String::from(":")),
arguments: lowlevel::Arguments::ArgumentList(vec![
lowlevel::Argument::PrimitiveArgument(String::from("abc")),
]),
terminator: None,
},
},
cr_lf_delimit("+TESTS: abc"),
)
}
// Extension response with one key-value argument
#[test]
fn one_kv_arg() {
let mut map = HashMap::new();
map.insert(1, String::from("abc"));
test_roundtrips(
highlevel::Response::Success(highlevel::Success::Testm { field: map }),
lowlevel::Response::Success {
name: String::from("TESTM"),
is_extension: true,
arguments: lowlevel::DelimitedArguments {
delimiter: Some(String::from(":")),
arguments: lowlevel::Arguments::ArgumentList(vec![
lowlevel::Argument::KeyValueArgument {
key: String::from("1"),
value: String::from("abc"),
},
]),
terminator: None,
},
},
cr_lf_delimit("+TESTM: 1=abc"),
)
}
// Extension response with multiple arguments which form a list
#[test]
fn args_list() {
test_roundtrips(
highlevel::Response::Success(highlevel::Success::Testl { field: vec![1, 2] }),
lowlevel::Response::Success {
name: String::from("TESTL"),
is_extension: true,
arguments: lowlevel::DelimitedArguments {
delimiter: Some(String::from(":")),
arguments: lowlevel::Arguments::ArgumentList(vec![
lowlevel::Argument::PrimitiveArgument(String::from("1")),
lowlevel::Argument::PrimitiveArgument(String::from("2")),
]),
terminator: None,
},
},
cr_lf_delimit("+TESTL: 1,2"),
)
}
// Extension response with multiple arguments which form a list of optional elements
#[test]
fn args_optional_list() {
test_roundtrips(
highlevel::Response::Success(highlevel::Success::Testol {
field: vec![Some(1), None, Some(2)],
}),
lowlevel::Response::Success {
name: String::from("TESTOL"),
is_extension: true,
arguments: lowlevel::DelimitedArguments {
delimiter: Some(String::from(":")),
arguments: lowlevel::Arguments::ArgumentList(vec![
lowlevel::Argument::PrimitiveArgument(String::from("1")),
lowlevel::Argument::PrimitiveArgument(String::from("")),
lowlevel::Argument::PrimitiveArgument(String::from("2")),
]),
terminator: None,
},
},
cr_lf_delimit("+TESTOL: 1,,2"),
)
}
// Extension response with optional arg present
#[test]
fn args_optional_present() {
test_roundtrips(
highlevel::Response::Success(highlevel::Success::Testio { field1: 1, field2: Some(2) }),
lowlevel::Response::Success {
name: String::from("TESTIO"),
is_extension: true,
arguments: lowlevel::DelimitedArguments {
delimiter: Some(String::from(":")),
arguments: lowlevel::Arguments::ArgumentList(vec![
lowlevel::Argument::PrimitiveArgument(String::from("1")),
lowlevel::Argument::PrimitiveArgument(String::from("2")),
]),
terminator: None,
},
},
cr_lf_delimit("+TESTIO: 1,2"),
)
}
// Extension response with optional arg absent
#[test]
fn args_optional_absent() {
test_roundtrips(
highlevel::Response::Success(highlevel::Success::Testio { field1: 1, field2: None }),
lowlevel::Response::Success {
name: String::from("TESTIO"),
is_extension: true,
arguments: lowlevel::DelimitedArguments {
delimiter: Some(String::from(":")),
arguments: lowlevel::Arguments::ArgumentList(vec![
lowlevel::Argument::PrimitiveArgument(String::from("1")),
lowlevel::Argument::PrimitiveArgument(String::from("")),
]),
terminator: None,
},
},
cr_lf_delimit("+TESTIO: 1,"),
)
}
// Extension response with multiple arguments
#[test]
fn args() {
test_roundtrips(
highlevel::Response::Success(highlevel::Success::Testsi {
field1: String::from("abc"),
field2: 1,
}),
lowlevel::Response::Success {
name: String::from("TESTSI"),
is_extension: true,
arguments: lowlevel::DelimitedArguments {
delimiter: Some(String::from(":")),
arguments: lowlevel::Arguments::ArgumentList(vec![
lowlevel::Argument::PrimitiveArgument(String::from("abc")),
lowlevel::Argument::PrimitiveArgument(String::from("1")),
]),
terminator: None,
},
},
cr_lf_delimit("+TESTSI: abc,1"),
)
}
// Paren delimited argument list
#[test]
fn paren_args() {
test_roundtrips(
highlevel::Response::Success(highlevel::Success::Testp { field: 1 }),
lowlevel::Response::Success {
name: String::from("TESTP"),
is_extension: true,
arguments: lowlevel::DelimitedArguments {
delimiter: Some(String::from(":")),
arguments: lowlevel::Arguments::ParenthesisDelimitedArgumentLists(vec![vec![
lowlevel::Argument::PrimitiveArgument(String::from("1")),
]]),
terminator: None,
},
},
cr_lf_delimit("+TESTP: (1)"),
)
}
// Paren delimited multiple argument lists
#[test]
fn multiple_paren_args() {
test_roundtrips(
highlevel::Response::Success(highlevel::Success::Testpp {
field1: 1,
field2: 2,
field3: String::from("abc"),
}),
lowlevel::Response::Success {
name: String::from("TESTPP"),
is_extension: true,
arguments: lowlevel::DelimitedArguments {
delimiter: Some(String::from(":")),
arguments: lowlevel::Arguments::ParenthesisDelimitedArgumentLists(vec![
vec![lowlevel::Argument::PrimitiveArgument(String::from("1"))],
vec![
lowlevel::Argument::PrimitiveArgument(String::from("2")),
lowlevel::Argument::PrimitiveArgument(String::from("abc")),
],
]),
terminator: None,
},
},
cr_lf_delimit("+TESTPP: (1)(2,abc)"),
)
}
// Paren delimited multiple argument lists with map and list
#[test]
fn multiple_paren_kv_args() {
let mut map = HashMap::new();
map.insert(1, String::from("abc"));
test_roundtrips(
highlevel::Response::Success(highlevel::Success::Testpmpil {
field1: map,
field2: 2,
field3: vec![3, 4],
}),
lowlevel::Response::Success {
name: String::from("TESTPMPIL"),
is_extension: true,
arguments: lowlevel::DelimitedArguments {
delimiter: Some(String::from(":")),
arguments: lowlevel::Arguments::ParenthesisDelimitedArgumentLists(vec![
vec![lowlevel::Argument::KeyValueArgument {
key: String::from("1"),
value: String::from("abc"),
}],
vec![
lowlevel::Argument::PrimitiveArgument(String::from("2")),
lowlevel::Argument::PrimitiveArgument(String::from("3")),
lowlevel::Argument::PrimitiveArgument(String::from("4")),
],
]),
terminator: None,
},
},
cr_lf_delimit("+TESTPMPIL: (1=abc)(2,3,4)"),
)
}
// Two response that differ in their argument delimiters.
#[test]
fn two_responses_same_name() {
test_roundtrips(
highlevel::Response::Success(highlevel::Success::SameOne { field: 1 }),
lowlevel::Response::Success {
name: String::from("SAME"),
is_extension: false,
arguments: lowlevel::DelimitedArguments {
delimiter: Some(String::from(":")),
arguments: lowlevel::Arguments::ArgumentList(vec![
lowlevel::Argument::PrimitiveArgument(String::from("1")),
]),
terminator: None,
},
},
cr_lf_delimit("SAME: 1"),
);
test_roundtrips(
highlevel::Response::Success(highlevel::Success::SameTwo { field: 1 }),
lowlevel::Response::Success {
name: String::from("SAME"),
is_extension: false,
arguments: lowlevel::DelimitedArguments {
delimiter: Some(String::from(":")),
arguments: lowlevel::Arguments::ParenthesisDelimitedArgumentLists(vec![vec![
lowlevel::Argument::PrimitiveArgument(String::from("1")),
]]),
terminator: None,
},
},
cr_lf_delimit("SAME: (1)"),
);
}
#[test]
fn success_succeeds() {
assert_eq!(
success(highlevel::Success::Test {}),
highlevel::Response::Success(highlevel::Success::Test {},)
)
}