1
use std::time::Duration;
2

            
3
use super::{ RequestError as E, RequestResult, Mode };
4

            
5
11
fn try_ranged_from<T, const MIN: u64, const MAX: u64>(s: &[u8]) -> RequestResult<T>
6
11
where
7
11
    T: TryFrom<u64>,
8
11
{
9
11
    let mut tmp: u64 = 0;
10

            
11
91
    for c in s {
12
82
	let v = match c {
13
82
	    b'0'..=b'9'	=> *c - b'0',
14
	    c		=> return Err(E::BadDigit(*c)),
15
	} as u64;
16

            
17
82
	tmp = tmp
18
82
	    .checked_mul(10)
19
82
	    .and_then(|t| t.checked_add(v))
20
82
	    .ok_or(E::NumberOutOfRange)?;
21
    }
22

            
23
9
    if tmp < MIN || tmp > MAX {
24
2
	return Err(E::NumberOutOfRange);
25
7
    }
26
7

            
27
7
    tmp.try_into().map_err(|_| E::NumberOutOfRange)
28
11
}
29

            
30
#[derive(Debug, Clone)]
31
pub struct Request<'a> {
32
    filename:		&'a[u8],
33
    pub mode:		Mode,
34
    pub block_size:	Option<u16>,
35
    pub timeout:	Option<Duration>,
36
    pub window_size:	Option<u16>,
37
    pub tsize:		Option<u64>,
38
}
39

            
40
pub enum Dir {
41
    Read,
42
    Write,
43
}
44

            
45
impl <'a> Request<'a> {
46
    pub fn has_options(&self) -> bool {
47
	self.block_size.is_some() ||
48
	    self.timeout.is_some() ||
49
	    self.window_size.is_some() ||
50
	    self.tsize.is_some()
51
    }
52

            
53
    pub fn from_slice(data: &'a [u8], dir: Dir) -> RequestResult<Self> {
54
	if data.is_empty() {
55
	    return Err(E::TooShort);
56
	}
57

            
58
	if data[data.len() - 1] != b'\0' {
59
	    return Err(E::MissingZero);
60
	}
61

            
62
	let mut iter = data[..data.len() - 1].split(|c| *c == b'\0');
63

            
64
	let filename = iter.next().ok_or(E::MissingFilename)?;
65
	if filename.is_empty() {
66
	    return Err(E::MissingFilename);
67
	}
68

            
69
	let mode = iter.next().ok_or(E::MissingMode)?;
70
	let mode = Mode::try_from(mode)?;
71

            
72
	let mut res = Self {
73
	    filename:		filename,
74
	    mode:		mode,
75

            
76
	    block_size:		None,
77
	    timeout:		None,
78
	    window_size:	None,
79
	    tsize:		None,
80
	};
81

            
82
	while let Some(v) = iter.next() {
83
	    use crate::util::ToLower;
84

            
85
	    let name = v.to_lower();
86
	    let arg = iter.next().ok_or(E::MissingArgument)?;
87

            
88
	    match name.as_slice() {
89
		b"blksize"	=> res.block_size = Some(try_ranged_from::<u16, 8, 65464>(arg)?),
90
		b"timeout"	=> res.timeout = Some(Duration::from_secs(try_ranged_from::<u64, 0, 65536>(arg)?)),
91
		b"tsize"	=> res.tsize = Some(match dir {
92
		    Dir::Read	=> try_ranged_from::<u64, 0, 0>(arg),
93
		    Dir::Write	=> try_ranged_from::<u64, 0, 4294967295>(arg),
94
		}?),
95
		b"windowsize"	=> res.window_size = Some(try_ranged_from::<u16, 1, 65535>(arg)?),
96
		_		=> warn!("unsupported {:?}={:?} option", name, arg),
97
	    }
98
	}
99

            
100
	Ok(res)
101
    }
102

            
103
    pub fn get_filename(&self) -> std::path::PathBuf {
104
	use std::os::unix::ffi::OsStrExt;
105

            
106
	let tmp = std::ffi::OsStr::from_bytes(self.filename);
107

            
108
	tmp.into()
109
    }
110
}
111

            
112

            
113
#[cfg(test)]
114
mod test {
115
    use super::*;
116

            
117
1
    #[test]
118
1
    fn test_range() {
119
1
	assert_eq!(try_ranged_from::<u16, 0, 10>(b"000"),     RequestResult::Ok(0));
120
1
	assert_eq!(try_ranged_from::<u16, 0, 10>(b"001"),     RequestResult::Ok(1));
121
1
	assert_eq!(try_ranged_from::<u16, 0, 10>(b"10"),      RequestResult::Ok(10));
122
1
	assert_eq!(try_ranged_from::<u16, 0, 10>(b"010"),     RequestResult::Ok(10));
123
1
	assert_eq!(try_ranged_from::<u16, 0, 10>(b"011"),     RequestResult::Err(E::NumberOutOfRange));
124
1
	assert_eq!(try_ranged_from::<u16, 1, 10>(b"0"),       RequestResult::Err(E::NumberOutOfRange));
125
1
	assert_eq!(try_ranged_from::<u8,  1, 1000>(b"200"),   RequestResult::Ok(200));
126
1
	assert_eq!(try_ranged_from::<u8,  1, 1000>(b"300"),   RequestResult::Err(E::NumberOutOfRange));
127
1
	assert_eq!(try_ranged_from::<u128, 1, 18446744073709551615>(b"18446744073709551615"),
128
1
		   RequestResult::Ok(18446744073709551615));
129
1
	assert_eq!(try_ranged_from::<u128, 1, 18446744073709551615>(b"18446744073709551616"),
130
1
		   RequestResult::Err(E::NumberOutOfRange));
131
1
	assert_eq!(try_ranged_from::<u128, 1, 18446744073709551615>(b"184467440737095516150"),
132
1
		   RequestResult::Err(E::NumberOutOfRange));
133
1
    }
134
}