1use std::fmt::Display;
2use std::result;
3use std::str::FromStr;
4
5use super::packet::{Error, Result};
6use super::packetreader;
7use super::packetwriter;
8
9
10#[derive(Debug)]
12pub struct Options {
13 pub blksize: Option<u16>,
15 pub timeout: Option<u8>,
17 pub tsize: Option<u64>,
19 pub windowsize: Option<u16>,
21}
22
23
24impl Options {
25
26 pub fn new() -> Options {
27 Options{
28 blksize: None,
29 timeout: None,
30 tsize: None,
31 windowsize: None,
32 }
33 }
34
35 pub fn is_set(&self) -> bool {
37 self.blksize.is_some() || self.timeout.is_some() ||
38 self.tsize.is_some() || self.windowsize.is_some()
39 }
40
41 pub fn read<'a>
43 (reader: &mut packetreader::PacketReader<'a>)
44 -> Result<Self>
45 {
46 match reader.take_remaining() {
47 Ok(buffer) => match Self::parse(buffer) {
48 Ok(options) => Ok(options),
49 Err(error) => Err(Error::InvalidOptions(error)),
50 },
51 Err(error) => Err(Error::ReadError(error)),
52 }
53 }
54
55 pub fn write
57 (self, writer: &mut packetwriter::PacketWriter)
58 -> Result<()>
59 {
60 if let Some(blksize) = self.blksize {
61 writer.put_string("blksize")?;
62 writer.put_string(&blksize.to_string())?;
63 };
64 if let Some(timeout) = self.timeout {
65 writer.put_string("timeout")?;
66 writer.put_string(&timeout.to_string())?;
67 };
68 if let Some(tsize) = self.tsize {
69 writer.put_string("tsize")?;
70 writer.put_string(&tsize.to_string())?;
71 };
72 if let Some(windowsize) = self.windowsize {
73 writer.put_string("windowsize")?;
74 writer.put_string(&windowsize.to_string())?;
75 };
76 Ok(())
77 }
78
79 pub fn parse<'a>(buf: &'a [u8]) -> result::Result<Self, String> {
83 let mut container = Self::new();
84 let mut options = OptionStringIter::new(buf);
85 loop {
86 match options.next() {
87 OptionString::Terminated(option) => {
88 let option = &String::from_utf8_lossy(option);
89 match options.next() {
90 OptionString::Terminated(value) => {
91 let value = &String::from_utf8_lossy(value);
92 container.parse_option(option, value)?;
93 },
94 OptionString::Unterminated(value) => {
95 let value = &String::from_utf8_lossy(value);
96 return Err(format!(
97 "Option {} has unterminated value {}",
98 option, value));
99 },
100 OptionString::None => {
101 return Err(format!(
102 "Option {} has no corresponding value",
103 option));
104 },
105 };
106 },
107 OptionString::Unterminated(option) => {
108 let option = &String::from_utf8_lossy(option);
109 return Err(format!(
110 "Option {} is unterminated",
111 option));
112 },
113 OptionString::None => {
114 return Ok(container);
115 },
116 };
117 };
118 }
119
120 fn parse_option
121 (&mut self, option: &str, value: &str) -> result::Result<(), String>
122 {
123 match option.to_lowercase().as_ref() {
124 "blksize" => self.blksize = Some(
125 Options::parse_blksize(value)?),
126 "timeout" => self.timeout = Some(
127 Options::parse_timeout(value)?),
128 "tsize" => self.tsize = Some(
129 Options::parse_tsize(value)?),
130 "windowsize" => self.windowsize = Some(
131 Options::parse_windowsize(value)?),
132 _ => {
133 },
136 };
137 Ok(())
138 }
139
140 fn parse_blksize(value: &str) -> result::Result<u16, String> {
141 Options::parse_value("blksize", value)
142 }
143
144 fn parse_timeout(value: &str) -> result::Result<u8, String> {
145 Options::parse_value("timeout", value)
146 }
147
148 fn parse_tsize(value: &str) -> result::Result<u64, String> {
149 Options::parse_value("tsize", value)
150 }
151
152 fn parse_windowsize(value: &str) -> result::Result<u16, String> {
153 Options::parse_value("windowsize", value)
154 }
155
156 fn parse_value<T: FromStr>
157 (option: &str, value: &str) -> result::Result<T, String>
158 where <T as FromStr>::Err: Display
159 {
160 match T::from_str(value) {
161 Ok(value) => Ok(value),
162 Err(error) => Err(format!(
163 "Invalid {} value {:?}: {}", option, value, error))
164 }
165 }
166
167}
168
169
170#[cfg(test)]
171mod test_options {
172
173 use super::Options;
174
175 #[test]
176 fn test_creating_new_options() {
177 let options = Options::new();
178 assert_eq!(options.blksize, None);
179 assert_eq!(options.timeout, None);
180 assert_eq!(options.tsize, None);
181 assert_eq!(options.windowsize, None);
182 }
183
184 #[test]
185 fn test_parsing_blksize() {
186 assert_eq!(Options::parse_blksize("123"), Ok(123u16));
187 assert_eq!(
188 Options::parse_blksize("foo"), Err(
189 "Invalid blksize value \"foo\": ".to_string() +
190 "invalid digit found in string"));
191 assert_eq!(
192 Options::parse_blksize("65536"), Err(
193 "Invalid blksize value \"65536\": ".to_string() +
194 "number too large to fit in target type"));
195 }
196
197 #[test]
198 fn test_parsing_timeout() {
199 assert_eq!(Options::parse_timeout("123"), Ok(123u8));
200 assert_eq!(
201 Options::parse_timeout("foo"), Err(
202 "Invalid timeout value \"foo\": ".to_string() +
203 "invalid digit found in string"));
204 assert_eq!(
205 Options::parse_timeout("256"), Err(
206 "Invalid timeout value \"256\": ".to_string() +
207 "number too large to fit in target type"));
208 }
209
210 #[test]
211 fn test_parsing_tsize() {
212 assert_eq!(Options::parse_tsize("123"), Ok(123u64));
213 assert_eq!(
214 Options::parse_tsize("foo"), Err(
215 "Invalid tsize value \"foo\": ".to_string() +
216 "invalid digit found in string"));
217 assert_eq!(
218 Options::parse_tsize("18446744073709551616"), Err(
219 "Invalid tsize value \"18446744073709551616\": ".to_string() +
220 "number too large to fit in target type"));
221 }
222
223 #[test]
224 fn test_parsing_windowsize() {
225 assert_eq!(Options::parse_windowsize("123"), Ok(123u16));
226 assert_eq!(
227 Options::parse_windowsize("foo"), Err(
228 "Invalid windowsize value \"foo\": ".to_string() +
229 "invalid digit found in string"));
230 assert_eq!(
231 Options::parse_windowsize("65536"), Err(
232 "Invalid windowsize value \"65536\": ".to_string() +
233 "number too large to fit in target type"));
234 }
235
236 #[test]
237 fn test_parsing_options() {
238 let buf = "blksize\067\0timeout\076\0tsize\098\0windowsize\0429\0".as_bytes();
239 let options = Options::parse(buf).unwrap();
240 assert_eq!(options.blksize, Some(67));
241 assert_eq!(options.timeout, Some(76));
242 assert_eq!(options.tsize, Some(98));
243 assert_eq!(options.windowsize, Some(429));
244 }
245
246 #[test]
247 fn test_parsing_empty_options() {
248 let buf = "".as_bytes();
249 let options = Options::parse(buf).unwrap();
250 assert_eq!(options.blksize, None);
251 assert_eq!(options.timeout, None);
252 assert_eq!(options.tsize, None);
253 assert_eq!(options.windowsize, None);
254 }
255
256 #[test]
257 fn test_parsing_incorrectly_terminated_option_results_in_error() {
258 let buf = "blksize".as_bytes(); assert_eq!(
260 Options::parse(buf).unwrap_err(),
261 "Option blksize is unterminated");
262 }
263
264 #[test]
265 fn test_parsing_incorrectly_terminated_value_results_in_error() {
266 let buf = "blksize\067".as_bytes(); assert_eq!(
268 Options::parse(buf).unwrap_err(),
269 "Option blksize has unterminated value 67");
270 }
271
272 #[test]
273 fn test_parsing_option_without_value_results_in_error() {
274 let buf = "foo\0".as_bytes();
275 assert_eq!(
276 Options::parse(buf).unwrap_err(),
277 "Option foo has no corresponding value");
278 }
279
280 #[test]
281 fn test_parsing_option_with_empty_value_results_in_error() {
282 let buf = "blksize\0\0".as_bytes();
283 assert_eq!(
284 Options::parse(buf).unwrap_err(),
285 "Invalid blksize value \"\": ".to_string() +
286 "cannot parse integer from empty string");
287 }
288
289}
290
291
292#[derive(Debug,PartialEq)]
293enum OptionString<'a> {
294 Terminated(&'a [u8]),
295 Unterminated(&'a [u8]),
296 None,
297}
298
299
300#[derive(Debug)]
301struct OptionStringIter<'a> {
302 buf: &'a [u8],
303 pos: usize,
304}
305
306
307impl<'a> OptionStringIter<'a> {
308
309 fn new(buf: &'a [u8]) -> OptionStringIter<'a> {
310 OptionStringIter{buf: buf, pos: 0}
311 }
312
313 fn next(&mut self) -> OptionString<'a> {
314 for index in self.pos..self.buf.len() {
315 if self.buf[index] == 0u8 {
316 let cstr = &self.buf[self.pos..index];
317 self.pos = index + 1;
318 return OptionString::Terminated(cstr);
319 }
320 }
321 if self.buf.len() > self.pos {
322 let cstr = &self.buf[self.pos..];
323 self.pos = self.buf.len();
324 return OptionString::Unterminated(cstr);
325 }
326 else {
327 return OptionString::None;
328 }
329 }
330
331}
332
333
334#[cfg(test)]
335mod test_option_string {
336
337 use super::OptionString;
338 use super::OptionStringIter;
339
340 #[test]
341 fn test_split() {
342 let buf = "one\0two\0three".as_bytes();
343 let mut iter = OptionStringIter::new(buf);
344 assert_eq!(iter.next(), OptionString::Terminated("one".as_bytes()));
345 assert_eq!(iter.next(), OptionString::Terminated("two".as_bytes()));
346 assert_eq!(iter.next(), OptionString::Unterminated("three".as_bytes()));
347 assert_eq!(iter.next(), OptionString::None);
348 }
349
350 #[test]
351 fn test_split_unterminated() {
352 let buf = "one".as_bytes();
353 let mut iter = OptionStringIter::new(buf);
354 assert_eq!(iter.next(), OptionString::Unterminated("one".as_bytes()));
355 assert_eq!(iter.next(), OptionString::None);
356 }
357
358 #[test]
359 fn test_split_with_empty() {
360 let buf = "one\0\0three".as_bytes();
361 let mut iter = OptionStringIter::new(buf);
362 assert_eq!(iter.next(), OptionString::Terminated("one".as_bytes()));
363 assert_eq!(iter.next(), OptionString::Terminated("".as_bytes()));
364 assert_eq!(iter.next(), OptionString::Unterminated("three".as_bytes()));
365 assert_eq!(iter.next(), OptionString::None);
366 }
367
368 #[test]
369 fn test_split_empty() {
370 let buf = "".as_bytes();
371 let mut iter = OptionStringIter::new(buf);
372 assert_eq!(iter.next(), OptionString::None);
373 }
374
375}