allenap_libtftp/
options.rs

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/// TFTP transfer options. Defined in RFC-2347.
11#[derive(Debug)]
12pub struct Options {
13    /// Block size; 8-65464 inclusive. Defined in RFC-2348.
14    pub blksize:    Option<u16>,
15    /// Time-out; 1-255 seconds, inclusive. Defined in RFC-2349.
16    pub timeout:    Option<u8>,
17    /// Transfer size; 0 for query. Defined in RFC-2349.
18    pub tsize:      Option<u64>,
19    /// Window size; 1-65535. Defined in RFC-7440.
20    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    /// Is one or more of the options set?
36    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    /// Read options from the given reader.
42    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    /// Write options to the given writer.
56    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    /// Parse options from the given buffer.
80    ///
81    /// Note that errors arising from this method are *strings*.
82    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                // Ignore, as advised in RFC-2347.
134                // TODO: Record or log unrecognised options?
135            },
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();  // No trailing null byte.
259        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();  // No trailing null byte.
267        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}