at_commands/lowlevel/
arguments.rs1use crate::lowlevel::write_to::WriteTo;
11use std::io;
12
13#[derive(Debug, Clone, PartialEq)]
15pub struct DelimitedArguments {
16 pub delimiter: Option<String>,
20 pub arguments: Arguments,
22 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 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#[derive(Debug, Clone, PartialEq)]
51pub enum Arguments {
52 ParenthesisDelimitedArgumentLists(Vec<Vec<Argument>>),
54 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#[derive(Debug, Clone, PartialEq)]
105pub enum Argument {
106 PrimitiveArgument(String),
108 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
135fn 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", "abc\x7Fdef", "abc\x08def", "abc\x0Cdef", ];
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 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 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 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}