1#![allow(clippy::unneeded_field_pattern)]
43
44use sawp::error::{NomError, Result};
45use sawp::parser::{Direction, Parse};
46use sawp::probe::Probe;
47use sawp::protocol::Protocol;
48
49use num_enum::TryFromPrimitive;
50use std::convert::TryFrom;
51
52use nom::bytes::streaming::{tag, take_while};
53use nom::combinator::map_res;
54use nom::error::ErrorKind;
55use nom::number::streaming::be_u16;
56use nom::sequence::terminated;
57
58#[cfg(feature = "ffi")]
60mod ffi;
61
62#[cfg(feature = "ffi")]
63use sawp_ffi::GenerateFFI;
64
65#[derive(Clone, Copy, Debug, PartialEq, Eq, TryFromPrimitive)]
68#[repr(u16)]
69pub enum OpCode {
70 ReadRequest = 1,
71 WriteRequest = 2,
72 Data = 3,
73 Acknowledgement = 4,
74 Error = 5,
75 OptionAcknowledgement = 6,
76}
77
78#[cfg_attr(feature = "ffi", derive(GenerateFFI), sawp_ffi(prefix = "sawp_tftp"))]
79#[derive(Debug, PartialEq, Eq)]
80pub enum Mode {
81 NetASCII,
82 Mail,
83 Octet,
84 Unknown(String),
85}
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq, TryFromPrimitive)]
89#[repr(u16)]
90pub enum ErrorCode {
91 NotDefined = 0,
92 FileNotFound = 1,
93 AccessViolation = 2,
94 DiskFull = 3,
95 IllegalTFTPOperation = 4,
96 UnknownTransferId = 5,
97 FileAlreadyExists = 6,
98 NoSuchUser = 7,
99 OptionRejected = 8,
100 Unknown = 65535,
101}
102
103#[cfg_attr(feature = "ffi", derive(GenerateFFI))]
104#[cfg_attr(feature = "ffi", sawp_ffi(prefix = "sawp_tftp"))]
105#[derive(Debug, PartialEq, Eq)]
106pub struct OptionExtension {
107 pub name: String,
108 pub value: String,
109}
110
111#[cfg_attr(feature = "ffi", derive(GenerateFFI), sawp_ffi(prefix = "sawp_tftp"))]
113#[derive(Debug, PartialEq, Eq)]
114pub enum Packet {
115 ReadWriteRequest {
116 filename: String,
117 mode: Mode,
118 options: Vec<OptionExtension>,
119 },
120 Data {
121 block_number: u16,
122 data: Vec<u8>,
123 },
124 Ack(u16),
125 Error {
126 raw_code: u16,
127 code: ErrorCode,
128 message: String,
129 },
130 OptAck(Vec<OptionExtension>),
131}
132
133#[cfg_attr(feature = "ffi", derive(GenerateFFI), sawp_ffi(prefix = "sawp_tftp"))]
135#[derive(Debug, PartialEq, Eq)]
136pub struct Message {
137 #[cfg_attr(feature = "ffi", sawp_ffi(copy))]
138 pub op_code: OpCode,
139 pub packet: Packet,
140}
141
142#[derive(Debug)]
143pub struct TFTP {}
144
145impl<'a> Probe<'a> for TFTP {}
146
147impl Protocol<'_> for TFTP {
148 type Message = Message;
149
150 fn name() -> &'static str {
151 "tftp"
152 }
153}
154
155fn parse_options(input: &'_ [u8]) -> Result<(&'_ [u8], Vec<OptionExtension>)> {
156 let mut bytes = input;
157 let mut options: Vec<OptionExtension> = Vec::new();
158 while !bytes.is_empty() {
159 let (rest, name) = map_res(
160 terminated(take_while(|c| c != 0), tag(&[0])),
161 std::str::from_utf8,
162 )(bytes)?;
163 let (rest, value) = map_res(
164 terminated(take_while(|c| c != 0), tag(&[0])),
165 std::str::from_utf8,
166 )(rest)?;
167 options.push(OptionExtension {
168 name: name.into(),
169 value: value.into(),
170 });
171 bytes = rest;
172 }
173
174 Ok((bytes, options))
175}
176
177impl<'a> Parse<'a> for TFTP {
178 fn parse(
179 &self,
180 input: &'a [u8],
181 _direction: Direction,
182 ) -> Result<(&'a [u8], Option<Self::Message>)> {
183 let (input, op_code) = be_u16(input)?;
184 if let Ok(op_code) = OpCode::try_from(op_code) {
185 let (input, packet) = match op_code {
186 OpCode::ReadRequest | OpCode::WriteRequest => {
187 let (input, filename) = map_res(
188 terminated(take_while(|c| c != 0), tag(&[0])),
189 std::str::from_utf8,
190 )(input)?;
191 let (input, mode) = map_res(
192 terminated(take_while(|c| c != 0), tag(&[0])),
193 std::str::from_utf8,
194 )(input)?;
195 let mode = match &mode.to_lowercase()[..] {
196 "netascii" => Mode::NetASCII,
197 "octet" => Mode::Octet,
198 "mail" => Mode::Mail,
199 _ => Mode::Unknown(mode.into()),
200 };
201
202 let (input, options) = match parse_options(input) {
203 Ok((input, options)) => (input, options),
204 _ => (input, Vec::new()),
205 };
206
207 (
208 input,
209 Packet::ReadWriteRequest {
210 filename: filename.into(),
211 mode,
212 options,
213 },
214 )
215 }
216 OpCode::Data => {
217 let (input, block_number) = be_u16(input)?;
218 (
219 &[] as &[u8],
220 Packet::Data {
221 block_number,
222 data: input.into(),
223 },
224 )
225 }
226 OpCode::Acknowledgement => {
227 let (input, block_number) = be_u16(input)?;
228 (input, Packet::Ack(block_number))
229 }
230 OpCode::Error => {
231 let (input, raw_code) = be_u16(input)?;
232 let (input, message) = map_res(
233 terminated(take_while(|c| c != 0), tag(&[0])),
234 std::str::from_utf8,
235 )(input)?;
236
237 let code = ErrorCode::try_from(raw_code).unwrap_or(ErrorCode::Unknown);
238 (
239 input,
240 Packet::Error {
241 raw_code,
242 code,
243 message: message.into(),
244 },
245 )
246 }
247 OpCode::OptionAcknowledgement => match parse_options(input) {
248 Ok((input, options)) => (input, Packet::OptAck(options)),
249 _ => (input, Packet::OptAck(Vec::new())),
250 },
251 };
252 Ok((input, Some(Message { op_code, packet })))
253 } else {
254 Err(NomError::new(input, ErrorKind::IsA).into())
255 }
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262 use rstest::rstest;
263 use sawp::error;
264 use sawp::probe::Status;
265
266 #[test]
267 fn test_name() {
268 assert_eq!(TFTP::name(), "tftp");
269 }
270
271 #[rstest(
272 input,
273 expected,
274 case::empty(b"", Err(error::Error::incomplete_needed(2))),
275 case::hello_world(b"hello world", Err(NomError::new(b"hello world", ErrorKind::Tag).into())),
276 case::read(
277 &[
278 0x00, 0x01,
280 0x6c, 0x6f, 0x67, 0x2e, 0x74, 0x78, 0x74, 0x00,
283 0x6e, 0x65, 0x74, 0x61, 0x73, 0x63, 0x69, 0x69, 0x00,
285 ],
286 Ok((&[] as &[u8],
287 Some(Message {
288 op_code: OpCode::ReadRequest,
289 packet: Packet::ReadWriteRequest {
290 filename: String::from("log.txt"),
291 mode: Mode::NetASCII,
292 options: vec![],
293 },
294 })))),
295 case::opt_read(
296 &[
297 0x00, 0x01,
299 0x6c, 0x6f, 0x67, 0x2e, 0x74, 0x78, 0x74, 0x00,
302 0x6e, 0x65, 0x74, 0x61, 0x73, 0x63, 0x69, 0x69, 0x00,
304 0x74, 0x73, 0x69, 0x7a, 0x65, 0x00,
307 0x30, 0x00,
309 ],
310 Ok((&[] as &[u8],
311 Some(Message {
312 op_code: OpCode::ReadRequest,
313 packet: Packet::ReadWriteRequest {
314 filename: String::from("log.txt"),
315 mode: Mode::NetASCII,
316 options: vec![
317 OptionExtension {
318 name: String::from("tsize"),
319 value: String::from("0"),
320 }
321 ],
322 },
323 })))),
324 case::write(
325 &[
326 0x00, 0x02,
328 0x6c, 0x6f, 0x67, 0x2e, 0x74, 0x78, 0x74, 0x00,
331 0x4f, 0x63, 0x54, 0x65, 0x54, 0x00,
333 ],
334 Ok((&[] as &[u8],
335 Some(Message {
336 op_code: OpCode::WriteRequest,
337 packet: Packet::ReadWriteRequest {
338 filename: String::from("log.txt"),
339 mode: Mode::Octet,
340 options: vec![],
341 },
342 })))),
343 case::opt_write(
344 &[
345 0x00, 0x02,
347 0x6c, 0x6f, 0x67, 0x2e, 0x74, 0x78, 0x74, 0x00,
350 0x4f, 0x63, 0x54, 0x65, 0x54, 0x00,
352 0x74, 0x73, 0x69, 0x7a, 0x65, 0x00,
355 0x30, 0x00,
357 0x62, 0x6c, 0x6b, 0x73, 0x69, 0x7a, 0x65, 0x00,
359 0x31, 0x34, 0x33, 0x32, 0x00,
361 ],
362 Ok((&[] as &[u8],
363 Some(Message {
364 op_code: OpCode::WriteRequest,
365 packet: Packet::ReadWriteRequest {
366 filename: String::from("log.txt"),
367 mode: Mode::Octet,
368 options: vec![
369 OptionExtension {
370 name: String::from("tsize"),
371 value: String::from("0"),
372 },
373 OptionExtension {
374 name: String::from("blksize"),
375 value: String::from("1432"),
376 }
377 ],
378 },
379 })))),
380 case::unknown_mode(
381 &[
382 0x00, 0x02,
384 0x6c, 0x6f, 0x67, 0x2e, 0x74, 0x78, 0x74, 0x00,
387 0x53, 0x74, 0x52, 0x61, 0x4e, 0x67, 0x45, 0x72, 0x00,
389 ],
390 Ok((&[] as &[u8],
391 Some(Message {
392 op_code: OpCode::WriteRequest,
393 packet: Packet::ReadWriteRequest {
394 filename: String::from("log.txt"),
395 mode: Mode::Unknown("StRaNgEr".into()),
396 options: vec![],
397 },
398 })))),
399 case::no_null(
400 &[
401 0x00, 0x02,
403 0x6c, 0x6f, 0x67, 0x2e, 0x74, 0x78, 0x74, 0x00,
406 0x4f, 0x63, 0x54, 0x65, 0x54,
408 ],
409 Err(error::Error::incomplete_needed(1))),
410 case::data(
411 &[
412 0x00, 0x03,
414 0x00, 0x0c,
417 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
419 ],
420 Ok((&[] as &[u8],
421 Some(Message {
422 op_code: OpCode::Data,
423 packet: Packet::Data {
424 block_number: 12,
425 data: vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08],
426 },
427 })))),
428 case::ack(
429 &[
430 0x00, 0x04,
432 0x00, 0x10,
434 ],
435 Ok((&[] as &[u8],
436 Some(Message {
437 op_code: OpCode::Acknowledgement,
438 packet: Packet::Ack(16),
439 })))),
440 case::opt_ack(
441 &[
442 0x00, 0x06,
444 0x74, 0x73, 0x69, 0x7a, 0x65, 0x00,
447 0x30, 0x00,
449 ],
450 Ok((&[] as &[u8],
451 Some(Message {
452 op_code: OpCode::OptionAcknowledgement,
453 packet: Packet::OptAck(
454 vec![OptionExtension {
455 name: String::from("tsize"),
456 value: String::from("0"),
457 }],
458 )
459 })))),
460 case::error(
461 &[
462 0x00, 0x05,
464 0x00, 0x03,
467 0x44, 0x69, 0x73, 0x6b, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x00,
469 ],
470 Ok((&[] as &[u8],
471 Some(Message {
472 op_code: OpCode::Error,
473 packet: Packet::Error {
474 raw_code: 3,
475 code: ErrorCode::DiskFull,
476 message: String::from("Disk full"),
477 },
478 })))),
479 )]
480 fn test_parse(input: &[u8], expected: Result<(&[u8], Option<Message>)>) {
481 let tftp = TFTP {};
482 assert_eq!(tftp.parse(input, Direction::Unknown), expected);
483 }
484
485 #[rstest(
486 input,
487 expected,
488 case::empty(b"", Status::Incomplete),
489 case::hello_world(b"hello world", Status::Unrecognized),
490 case::header(
491 &[
492 0x00, 0x01,
494 0x6c, 0x6f, 0x67, 0x2e, 0x74, 0x78, 0x74, 0x00,
497 0x6e, 0x65, 0x74, 0x61, 0x73, 0x63, 0x69, 0x69, 0x00,
499 ],
500 Status::Recognized
501 ),
502 )]
503 fn test_probe(input: &[u8], expected: Status) {
504 let tftp = TFTP {};
505
506 assert_eq!(tftp.probe(input, Direction::Unknown), expected);
507 }
508}