async_tftp/
parse.rs

1use nom::branch::alt;
2use nom::bytes::complete::{tag, tag_no_case, take_till};
3use nom::combinator::{map, map_opt, map_res, rest};
4use nom::multi::many0;
5use nom::number::complete::be_u16;
6use nom::sequence::tuple;
7use nom::IResult;
8use num_traits::FromPrimitive;
9use std::str::{self, FromStr};
10
11use crate::error::Result;
12use crate::packet::{self, *};
13
14#[derive(Debug)]
15enum Opt<'a> {
16    BlkSize(u16),
17    Timeout(u8),
18    Tsize(u64),
19    Invalid(&'a str, &'a str),
20}
21
22pub(crate) fn parse_packet(input: &[u8]) -> Result<Packet> {
23    let (rest, packet) = match parse_packet_type(input)? {
24        (data, PacketType::Rrq) => parse_rrq(data)?,
25        (data, PacketType::Wrq) => parse_wrq(data)?,
26        (data, PacketType::Data) => parse_data(data)?,
27        (data, PacketType::Ack) => parse_ack(data)?,
28        (data, PacketType::Error) => parse_error(data)?,
29        (data, PacketType::OAck) => parse_oack(data)?,
30    };
31
32    if rest.is_empty() {
33        Ok(packet)
34    } else {
35        Err(crate::Error::InvalidPacket)
36    }
37}
38
39fn nul_str(input: &[u8]) -> IResult<&[u8], &str> {
40    map_res(
41        tuple((take_till(|c| c == b'\0'), tag(b"\0"))),
42        |(s, _): (&[u8], _)| str::from_utf8(s),
43    )(input)
44}
45
46fn parse_packet_type(input: &[u8]) -> IResult<&[u8], PacketType> {
47    map_opt(be_u16, PacketType::from_u16)(input)
48}
49
50fn parse_mode(input: &[u8]) -> IResult<&[u8], Mode> {
51    alt((
52        map(tag_no_case(b"netascii\0"), |_| Mode::Netascii),
53        map(tag_no_case(b"octet\0"), |_| Mode::Octet),
54        map(tag_no_case(b"mail\0"), |_| Mode::Mail),
55    ))(input)
56}
57
58fn parse_opt_blksize(input: &[u8]) -> IResult<&[u8], Opt> {
59    map_opt(tuple((tag_no_case(b"blksize\0"), nul_str)), |(_, n): (_, &str)| {
60        u16::from_str(n)
61            .ok()
62            .filter(|n| *n >= 8 && *n <= 65464)
63            .map(Opt::BlkSize)
64    })(input)
65}
66
67fn parse_opt_timeout(input: &[u8]) -> IResult<&[u8], Opt> {
68    map_opt(tuple((tag_no_case(b"timeout\0"), nul_str)), |(_, n): (_, &str)| {
69        u8::from_str(n).ok().filter(|n| *n >= 1).map(Opt::Timeout)
70    })(input)
71}
72
73fn parse_opt_tsize(input: &[u8]) -> IResult<&[u8], Opt> {
74    map_opt(tuple((tag_no_case(b"tsize\0"), nul_str)), |(_, n): (_, &str)| {
75        u64::from_str(n).ok().map(Opt::Tsize)
76    })(input)
77}
78
79pub(crate) fn parse_opts(input: &[u8]) -> IResult<&[u8], Opts> {
80    many0(alt((
81        parse_opt_blksize,
82        parse_opt_timeout,
83        parse_opt_tsize,
84        map(tuple((nul_str, nul_str)), |(k, v)| Opt::Invalid(k, v)),
85    )))(input)
86    .map(|(i, opt_vec)| (i, to_opts(opt_vec)))
87}
88
89fn to_opts(opt_vec: Vec<Opt>) -> Opts {
90    let mut opts = Opts::default();
91
92    for opt in opt_vec {
93        match opt {
94            Opt::BlkSize(size) => {
95                if opts.block_size.is_none() {
96                    opts.block_size.replace(size);
97                }
98            }
99            Opt::Timeout(timeout) => {
100                if opts.timeout.is_none() {
101                    opts.timeout.replace(timeout);
102                }
103            }
104            Opt::Tsize(size) => {
105                if opts.transfer_size.is_none() {
106                    opts.transfer_size.replace(size);
107                }
108            }
109            Opt::Invalid(..) => {}
110        }
111    }
112
113    opts
114}
115
116fn parse_rrq(input: &[u8]) -> IResult<&[u8], Packet> {
117    let (input, (filename, mode, opts)) =
118        tuple((nul_str, parse_mode, parse_opts))(input)?;
119
120    Ok((
121        input,
122        Packet::Rrq(RwReq {
123            filename: filename.to_owned(),
124            mode,
125            opts,
126        }),
127    ))
128}
129
130fn parse_wrq(input: &[u8]) -> IResult<&[u8], Packet> {
131    let (input, (filename, mode, opts)) =
132        tuple((nul_str, parse_mode, parse_opts))(input)?;
133
134    Ok((
135        input,
136        Packet::Wrq(RwReq {
137            filename: filename.to_owned(),
138            mode,
139            opts,
140        }),
141    ))
142}
143
144fn parse_data(input: &[u8]) -> IResult<&[u8], Packet> {
145    tuple((be_u16, rest))(input)
146        .map(|(i, (block_nr, data))| (i, Packet::Data(block_nr, data)))
147}
148
149fn parse_ack(input: &[u8]) -> IResult<&[u8], Packet> {
150    be_u16(input).map(|(i, block_nr)| (i, Packet::Ack(block_nr)))
151}
152
153fn parse_error(input: &[u8]) -> IResult<&[u8], Packet> {
154    tuple((be_u16, nul_str))(input).map(|(i, (code, msg))| {
155        (i, packet::Error::from_code(code, Some(msg)).into())
156    })
157}
158
159fn parse_oack(input: &[u8]) -> IResult<&[u8], Packet> {
160    parse_opts(input).map(|(i, opts)| (i, Packet::OAck(opts)))
161}