Skip to main content

at_commands/lowlevel/
arguments.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! This module contains an AST for arguments for both AT commands and responses.
6//!
7//! The format of of these is not specifed in any one place in the spec, but they are
8//! described thoughout HFP 1.8.
9
10use crate::lowlevel::write_to::WriteTo;
11use std::io;
12
13/// An argument list set off from a command or response by a delimiter such as "=" or ":".
14#[derive(Debug, Clone, PartialEq)]
15pub struct DelimitedArguments {
16    /// A string setting off arguments from the command or response.  This is normally `=`
17    /// or ": ", but could be ">" or absent. This latter is currently only in a variants
18    /// of  the `ATD` command, specified in HFP v1.8 4.19.
19    pub delimiter: Option<String>,
20    /// The actual arguments to the execute commmand.
21    pub arguments: Arguments,
22    /// An optional terminator for the arguments to the command, such as the ";" used to
23    /// terminate ATD commands.
24    pub terminator: Option<String>,
25}
26
27impl WriteTo for DelimitedArguments {
28    fn write_to<W: io::Write>(&self, sink: &mut W) -> io::Result<()> {
29        let DelimitedArguments { delimiter, arguments, terminator } = self;
30        if let Some(string) = delimiter {
31            if string == ":" {
32                // Hack.  The parser ignores whitespace, but the colon in a response must be followed by a space, so special case this.
33                sink.write_all(": ".as_bytes())?;
34            } else {
35                sink.write_all(string.as_bytes())?;
36            }
37        };
38        arguments.write_to(sink)?;
39        if let Some(terminator) = terminator {
40            sink.write_all(terminator.as_bytes())?
41        };
42
43        Ok(())
44    }
45}
46/// The collection of arguments to a given command or response.
47///
48/// AT supports multiple different formats, represented here by the different enum
49/// branches.
50#[derive(Debug, Clone, PartialEq)]
51pub enum Arguments {
52    /// A sequence of multiple arguments lists delimited by parentheses, like, `(1,2)(3,4)(a=1)`
53    ParenthesisDelimitedArgumentLists(Vec<Vec<Argument>>),
54    /// A single argument list delimited by commas, like, `1,2,a=3`
55    ArgumentList(Vec<Argument>),
56}
57
58impl Arguments {
59    fn write_comma_delimited_argument_list<W: io::Write>(
60        &self,
61        arguments: &[Argument],
62        sink: &mut W,
63    ) -> io::Result<()> {
64        if !arguments.is_empty() {
65            for argument in &arguments[0..arguments.len() - 1] {
66                argument.write_to(sink)?;
67                sink.write_all(b",")?;
68            }
69            arguments[arguments.len() - 1].write_to(sink)?;
70        }
71
72        Ok(())
73    }
74
75    fn write_paren_delimited_argument_lists<W: io::Write>(
76        &self,
77        argument_lists: &[Vec<Argument>],
78        sink: &mut W,
79    ) -> io::Result<()> {
80        for arguments in argument_lists {
81            sink.write_all(b"(")?;
82            self.write_comma_delimited_argument_list(arguments, sink)?;
83            sink.write_all(b")")?;
84        }
85
86        Ok(())
87    }
88}
89
90impl WriteTo for Arguments {
91    fn write_to<W: io::Write>(&self, sink: &mut W) -> io::Result<()> {
92        match self {
93            Arguments::ParenthesisDelimitedArgumentLists(argument_lists) => {
94                self.write_paren_delimited_argument_lists(&argument_lists, sink)
95            }
96            Arguments::ArgumentList(argument_list) => {
97                self.write_comma_delimited_argument_list(&argument_list, sink)
98            }
99        }
100    }
101}
102
103/// An individual argument in a list.
104#[derive(Debug, Clone, PartialEq)]
105pub enum Argument {
106    /// A primitive string or int.
107    PrimitiveArgument(String),
108    /// A key-value pair like `a=1`
109    KeyValueArgument { key: String, value: String },
110}
111
112impl Argument {
113    pub fn is_empty(&self) -> bool {
114        match self {
115            Argument::PrimitiveArgument(argument) => argument.is_empty(),
116            Argument::KeyValueArgument { key, value } => key.is_empty() && value.is_empty(),
117        }
118    }
119}
120
121impl WriteTo for Argument {
122    fn write_to<W: io::Write>(&self, sink: &mut W) -> io::Result<()> {
123        match self {
124            Argument::PrimitiveArgument(argument) => write_string(sink, argument)?,
125            Argument::KeyValueArgument { key, value } => {
126                write_string(sink, key)?;
127                sink.write_all(b"=")?;
128                write_string(sink, value)?;
129            }
130        }
131        Ok(())
132    }
133}
134
135/// Writes a string, converting it to bytes first. Rejects any string containing
136/// control characters (except tab).
137fn write_string<W: io::Write>(sink: &mut W, string: &String) -> io::Result<()> {
138    if string.chars().any(|c| c.is_control() && c != '\t') {
139        return Err(io::Error::new(
140            io::ErrorKind::InvalidInput,
141            "AT command string argument contains control characters",
142        ));
143    }
144    sink.write_all(string.as_bytes())
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150    use crate::lowlevel::write_to::WriteTo;
151
152    #[test]
153    fn write_string_rejects_various_control_characters() {
154        let mut sink = Vec::new();
155        let malicious_payloads = vec![
156            "123;\rAT+CMGD=1",
157            "123\nAT+CHUP",
158            "123\0abc",
159            "123\x1B[2Jabc", // ESC
160            "abc\x7Fdef",    // DEL
161            "abc\x08def",    // Backspace
162            "abc\x0Cdef",    // Form feed
163        ];
164
165        for payload in malicious_payloads {
166            let arg = Argument::PrimitiveArgument(String::from(payload));
167            assert!(
168                arg.write_to(&mut sink).is_err(),
169                "Must reject argument with control characters: {:?}",
170                payload
171            );
172        }
173    }
174
175    #[test]
176    fn write_key_value_argument_rejects_control_characters_in_key_and_value() {
177        let mut sink = Vec::new();
178
179        // Malicious key
180        let kv_bad_key = Argument::KeyValueArgument {
181            key: String::from("KEY\rINJECT"),
182            value: String::from("VAL"),
183        };
184        assert!(kv_bad_key.write_to(&mut sink).is_err());
185
186        // Malicious value
187        let kv_bad_val = Argument::KeyValueArgument {
188            key: String::from("KEY"),
189            value: String::from("VAL\rINJECT"),
190        };
191        assert!(kv_bad_val.write_to(&mut sink).is_err());
192
193        // Both malicious
194        let kv_bad_both =
195            Argument::KeyValueArgument { key: String::from("KEY\n"), value: String::from("VAL\0") };
196        assert!(kv_bad_both.write_to(&mut sink).is_err());
197    }
198
199    #[test]
200    fn write_arguments_list_rejects_control_characters_in_any_element() {
201        let mut sink = Vec::new();
202
203        let args_list = Arguments::ArgumentList(vec![
204            Argument::PrimitiveArgument(String::from("\"valid1\"")),
205            Argument::PrimitiveArgument(String::from("123;\rAT+CMGD=1")),
206            Argument::PrimitiveArgument(String::from("\"valid2\"")),
207        ]);
208
209        assert!(args_list.write_to(&mut sink).is_err());
210    }
211
212    #[test]
213    fn write_parenthesis_delimited_argument_lists_rejects_control_characters() {
214        let mut sink = Vec::new();
215
216        let paren_list = Arguments::ParenthesisDelimitedArgumentLists(vec![
217            vec![Argument::PrimitiveArgument(String::from("1"))],
218            vec![Argument::PrimitiveArgument(String::from("2\rAT+CHUP"))],
219        ]);
220
221        assert!(paren_list.write_to(&mut sink).is_err());
222    }
223
224    #[test]
225    fn write_string_accepts_valid_edge_case_strings() {
226        let mut sink = Vec::new();
227
228        let arg_empty = Argument::PrimitiveArgument(String::from(""));
229        assert!(arg_empty.write_to(&mut sink).is_ok());
230        assert_eq!(sink, b"");
231
232        sink.clear();
233        let arg_quotes = Argument::PrimitiveArgument(String::from("\"\""));
234        assert!(arg_quotes.write_to(&mut sink).is_ok());
235        assert_eq!(sink, b"\"\"");
236
237        sink.clear();
238        let arg_symbols = Argument::PrimitiveArgument(String::from("\"+1-800-555-0199;#*>\""));
239        assert!(arg_symbols.write_to(&mut sink).is_ok());
240        assert_eq!(sink, b"\"+1-800-555-0199;#*>\"");
241
242        sink.clear();
243        let arg_tab = Argument::PrimitiveArgument(String::from("foo\tbar"));
244        assert!(arg_tab.write_to(&mut sink).is_ok());
245        assert_eq!(sink, b"foo\tbar");
246    }
247}