stackforge_core/layer/tftp/
mod.rs1pub mod builder;
52pub use builder::TftpBuilder;
53
54use crate::layer::field::{FieldError, FieldValue};
55use crate::layer::{Layer, LayerIndex, LayerKind};
56
57pub const TFTP_MIN_HEADER_LEN: usize = 2;
59
60pub const TFTP_PORT: u16 = 69;
62
63pub const TFTP_DEFAULT_BLOCK_SIZE: usize = 512;
65
66pub const OPCODE_RRQ: u16 = 1;
70pub const OPCODE_WRQ: u16 = 2;
71pub const OPCODE_DATA: u16 = 3;
72pub const OPCODE_ACK: u16 = 4;
73pub const OPCODE_ERROR: u16 = 5;
74
75pub const ERR_UNDEFINED: u16 = 0;
79pub const ERR_FILE_NOT_FOUND: u16 = 1;
80pub const ERR_ACCESS_VIOLATION: u16 = 2;
81pub const ERR_DISK_FULL: u16 = 3;
82pub const ERR_ILLEGAL_OPERATION: u16 = 4;
83pub const ERR_UNKNOWN_TID: u16 = 5;
84pub const ERR_FILE_EXISTS: u16 = 6;
85pub const ERR_NO_SUCH_USER: u16 = 7;
86
87pub static TFTP_FIELD_NAMES: &[&str] = &[
89 "opcode",
90 "op_name",
91 "filename",
92 "mode",
93 "block_num",
94 "data",
95 "error_code",
96 "error_msg",
97];
98
99#[must_use]
107pub fn is_tftp_payload(buf: &[u8]) -> bool {
108 if buf.len() < 2 {
109 return false;
110 }
111 let opcode = u16::from_be_bytes([buf[0], buf[1]]);
112 (1..=5).contains(&opcode)
113}
114
115#[must_use]
117pub fn opcode_name(opcode: u16) -> &'static str {
118 match opcode {
119 OPCODE_RRQ => "RRQ",
120 OPCODE_WRQ => "WRQ",
121 OPCODE_DATA => "DATA",
122 OPCODE_ACK => "ACK",
123 OPCODE_ERROR => "ERROR",
124 _ => "UNKNOWN",
125 }
126}
127
128#[must_use]
130pub fn error_code_description(code: u16) -> &'static str {
131 match code {
132 ERR_UNDEFINED => "Not defined",
133 ERR_FILE_NOT_FOUND => "File not found",
134 ERR_ACCESS_VIOLATION => "Access violation",
135 ERR_DISK_FULL => "Disk full or allocation exceeded",
136 ERR_ILLEGAL_OPERATION => "Illegal TFTP operation",
137 ERR_UNKNOWN_TID => "Unknown transfer ID",
138 ERR_FILE_EXISTS => "File already exists",
139 ERR_NO_SUCH_USER => "No such user",
140 _ => "Unknown error",
141 }
142}
143
144#[must_use]
150#[derive(Debug, Clone)]
151pub struct TftpLayer {
152 pub index: LayerIndex,
153}
154
155impl TftpLayer {
156 pub fn new(index: LayerIndex) -> Self {
157 Self { index }
158 }
159
160 pub fn at_start(len: usize) -> Self {
161 Self {
162 index: LayerIndex::new(LayerKind::Tftp, 0, len),
163 }
164 }
165
166 #[inline]
167 fn slice<'a>(&self, buf: &'a [u8]) -> &'a [u8] {
168 let end = self.index.end.min(buf.len());
169 &buf[self.index.start..end]
170 }
171
172 pub fn opcode(&self, buf: &[u8]) -> Result<u16, FieldError> {
178 let s = self.slice(buf);
179 if s.len() < 2 {
180 return Err(FieldError::BufferTooShort {
181 offset: self.index.start,
182 need: 2,
183 have: s.len(),
184 });
185 }
186 Ok(u16::from_be_bytes([s[0], s[1]]))
187 }
188
189 pub fn op_name(&self, buf: &[u8]) -> Result<String, FieldError> {
195 self.opcode(buf).map(|op| opcode_name(op).to_string())
196 }
197
198 pub fn filename(&self, buf: &[u8]) -> Result<String, FieldError> {
208 let s = self.slice(buf);
209 let opcode = self.opcode(buf)?;
210 if opcode != OPCODE_RRQ && opcode != OPCODE_WRQ {
211 return Err(FieldError::InvalidValue(
212 "filename only available in RRQ/WRQ packets".into(),
213 ));
214 }
215 if s.len() < 3 {
216 return Err(FieldError::BufferTooShort {
217 offset: self.index.start,
218 need: 3,
219 have: s.len(),
220 });
221 }
222 let start = 2;
224 let end = s[start..]
225 .iter()
226 .position(|&b| b == 0)
227 .map_or(s.len(), |p| start + p);
228 let name = std::str::from_utf8(&s[start..end])
229 .map_err(|_| FieldError::InvalidValue("invalid UTF-8 in filename".into()))?;
230 Ok(name.to_string())
231 }
232
233 pub fn mode(&self, buf: &[u8]) -> Result<String, FieldError> {
240 let s = self.slice(buf);
241 let opcode = self.opcode(buf)?;
242 if opcode != OPCODE_RRQ && opcode != OPCODE_WRQ {
243 return Err(FieldError::InvalidValue(
244 "mode only available in RRQ/WRQ packets".into(),
245 ));
246 }
247 let mut offset = 2;
249 while offset < s.len() && s[offset] != 0 {
250 offset += 1;
251 }
252 offset += 1; let mode_start = offset;
255 let mode_end = s[mode_start..]
256 .iter()
257 .position(|&b| b == 0)
258 .map_or(s.len(), |p| mode_start + p);
259
260 let mode = std::str::from_utf8(&s[mode_start..mode_end])
261 .map_err(|_| FieldError::InvalidValue("invalid UTF-8 in mode".into()))?;
262 Ok(mode.to_ascii_lowercase())
263 }
264
265 pub fn block_num(&self, buf: &[u8]) -> Result<u16, FieldError> {
272 let s = self.slice(buf);
273 let opcode = self.opcode(buf)?;
274 if opcode != OPCODE_DATA && opcode != OPCODE_ACK {
275 return Err(FieldError::InvalidValue(
276 "block_num only available in DATA/ACK packets".into(),
277 ));
278 }
279 if s.len() < 4 {
280 return Err(FieldError::BufferTooShort {
281 offset: self.index.start,
282 need: 4,
283 have: s.len(),
284 });
285 }
286 Ok(u16::from_be_bytes([s[2], s[3]]))
287 }
288
289 pub fn data(&self, buf: &[u8]) -> Result<Vec<u8>, FieldError> {
296 let s = self.slice(buf);
297 let opcode = self.opcode(buf)?;
298 if opcode != OPCODE_DATA {
299 return Err(FieldError::InvalidValue(
300 "data only available in DATA packets".into(),
301 ));
302 }
303 if s.len() < 4 {
304 return Err(FieldError::BufferTooShort {
305 offset: self.index.start,
306 need: 4,
307 have: s.len(),
308 });
309 }
310 Ok(s[4..].to_vec())
311 }
312
313 pub fn error_code(&self, buf: &[u8]) -> Result<u16, FieldError> {
320 let s = self.slice(buf);
321 let opcode = self.opcode(buf)?;
322 if opcode != OPCODE_ERROR {
323 return Err(FieldError::InvalidValue(
324 "error_code only available in ERROR packets".into(),
325 ));
326 }
327 if s.len() < 4 {
328 return Err(FieldError::BufferTooShort {
329 offset: self.index.start,
330 need: 4,
331 have: s.len(),
332 });
333 }
334 Ok(u16::from_be_bytes([s[2], s[3]]))
335 }
336
337 pub fn error_msg(&self, buf: &[u8]) -> Result<String, FieldError> {
345 let s = self.slice(buf);
346 let opcode = self.opcode(buf)?;
347 if opcode != OPCODE_ERROR {
348 return Err(FieldError::InvalidValue(
349 "error_msg only available in ERROR packets".into(),
350 ));
351 }
352 if s.len() < 5 {
353 return Err(FieldError::BufferTooShort {
354 offset: self.index.start,
355 need: 5,
356 have: s.len(),
357 });
358 }
359 let msg_start = 4;
360 let msg_end = s[msg_start..]
361 .iter()
362 .position(|&b| b == 0)
363 .map_or(s.len(), |p| msg_start + p);
364 let msg = std::str::from_utf8(&s[msg_start..msg_end])
365 .map_err(|_| FieldError::InvalidValue("invalid UTF-8 in error message".into()))?;
366 Ok(msg.to_string())
367 }
368
369 pub fn get_field(&self, buf: &[u8], name: &str) -> Option<Result<FieldValue, FieldError>> {
371 match name {
372 "opcode" => Some(self.opcode(buf).map(FieldValue::U16)),
373 "op_name" => Some(self.op_name(buf).map(FieldValue::Str)),
374 "filename" => Some(self.filename(buf).map(FieldValue::Str)),
375 "mode" => Some(self.mode(buf).map(FieldValue::Str)),
376 "block_num" => Some(self.block_num(buf).map(FieldValue::U16)),
377 "data" => Some(self.data(buf).map(FieldValue::Bytes)),
378 "error_code" => Some(self.error_code(buf).map(FieldValue::U16)),
379 "error_msg" => Some(self.error_msg(buf).map(FieldValue::Str)),
380 _ => None,
381 }
382 }
383}
384
385impl Layer for TftpLayer {
386 fn kind(&self) -> LayerKind {
387 LayerKind::Tftp
388 }
389
390 fn summary(&self, buf: &[u8]) -> String {
391 let s = self.slice(buf);
392 if s.len() < 2 {
393 return "TFTP [truncated]".to_string();
394 }
395 let opcode = u16::from_be_bytes([s[0], s[1]]);
396 match opcode {
397 OPCODE_RRQ => {
398 let fname = self.filename(buf).unwrap_or_default();
399 let mode = self.mode(buf).unwrap_or_default();
400 format!("TFTP Read Request File: {fname} Mode: {mode}")
401 },
402 OPCODE_WRQ => {
403 let fname = self.filename(buf).unwrap_or_default();
404 let mode = self.mode(buf).unwrap_or_default();
405 format!("TFTP Write Request File: {fname} Mode: {mode}")
406 },
407 OPCODE_DATA => {
408 let block = self.block_num(buf).unwrap_or(0);
409 let data_len = if s.len() >= 4 { s.len() - 4 } else { 0 };
410 format!("TFTP Data Block#{block} ({data_len} bytes)")
411 },
412 OPCODE_ACK => {
413 let block = self.block_num(buf).unwrap_or(0);
414 format!("TFTP Ack Block#{block}")
415 },
416 OPCODE_ERROR => {
417 let code = self.error_code(buf).unwrap_or(0);
418 let msg = self.error_msg(buf).unwrap_or_default();
419 format!("TFTP Error Code: {code} Message: {msg}")
420 },
421 _ => format!("TFTP [unknown opcode {opcode}]"),
422 }
423 }
424
425 fn header_len(&self, buf: &[u8]) -> usize {
426 let s = self.slice(buf);
427 if s.len() < 2 {
428 return s.len();
429 }
430 let opcode = u16::from_be_bytes([s[0], s[1]]);
431 match opcode {
432 OPCODE_RRQ | OPCODE_WRQ | OPCODE_ERROR => s.len(), OPCODE_DATA | OPCODE_ACK => 4.min(s.len()), _ => 2,
435 }
436 }
437
438 fn hashret(&self, buf: &[u8]) -> Vec<u8> {
439 if let Ok(block) = self.block_num(buf) {
440 block.to_be_bytes().to_vec()
441 } else if let Ok(op) = self.opcode(buf) {
442 op.to_be_bytes().to_vec()
443 } else {
444 vec![]
445 }
446 }
447
448 fn field_names(&self) -> &'static [&'static str] {
449 TFTP_FIELD_NAMES
450 }
451}
452
453#[must_use]
455pub fn tftp_show_fields(l: &TftpLayer, buf: &[u8]) -> Vec<(&'static str, String)> {
456 let mut fields = Vec::new();
457 if let Ok(op) = l.opcode(buf) {
458 fields.push(("opcode", op.to_string()));
459 fields.push(("op_name", opcode_name(op).to_string()));
460 match op {
461 OPCODE_RRQ | OPCODE_WRQ => {
462 if let Ok(f) = l.filename(buf) {
463 fields.push(("filename", f));
464 }
465 if let Ok(m) = l.mode(buf) {
466 fields.push(("mode", m));
467 }
468 },
469 OPCODE_DATA | OPCODE_ACK => {
470 if let Ok(b) = l.block_num(buf) {
471 fields.push(("block_num", b.to_string()));
472 }
473 },
474 OPCODE_ERROR => {
475 if let Ok(c) = l.error_code(buf) {
476 fields.push(("error_code", c.to_string()));
477 }
478 if let Ok(m) = l.error_msg(buf) {
479 fields.push(("error_msg", m));
480 }
481 },
482 _ => {},
483 }
484 }
485 fields
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491
492 fn make_layer(data: &[u8]) -> TftpLayer {
493 TftpLayer::new(LayerIndex::new(LayerKind::Tftp, 0, data.len()))
494 }
495
496 #[test]
497 fn test_tftp_detection() {
498 let rrq = b"\x00\x01file.txt\x00octet\x00";
500 assert!(is_tftp_payload(rrq));
501 assert!(is_tftp_payload(b"\x00\x03\x00\x01hello"));
503 assert!(is_tftp_payload(b"\x00\x04\x00\x01"));
505 assert!(is_tftp_payload(b"\x00\x05\x00\x01File not found\x00"));
507 assert!(!is_tftp_payload(b"\x00\x06")); assert!(!is_tftp_payload(b"\x00")); assert!(!is_tftp_payload(b"")); }
512
513 #[test]
514 fn test_tftp_rrq_parsing() {
515 let data = b"\x00\x01file.txt\x00octet\x00";
516 let layer = make_layer(data);
517 assert_eq!(layer.opcode(data).unwrap(), OPCODE_RRQ);
518 assert_eq!(layer.op_name(data).unwrap(), "RRQ");
519 assert_eq!(layer.filename(data).unwrap(), "file.txt");
520 assert_eq!(layer.mode(data).unwrap(), "octet");
521 }
522
523 #[test]
524 fn test_tftp_wrq_parsing() {
525 let data = b"\x00\x02upload.bin\x00netascii\x00";
526 let layer = make_layer(data);
527 assert_eq!(layer.opcode(data).unwrap(), OPCODE_WRQ);
528 assert_eq!(layer.filename(data).unwrap(), "upload.bin");
529 assert_eq!(layer.mode(data).unwrap(), "netascii");
530 }
531
532 #[test]
533 fn test_tftp_data_parsing() {
534 let data = b"\x00\x03\x00\x01hello world data";
535 let layer = make_layer(data);
536 assert_eq!(layer.opcode(data).unwrap(), OPCODE_DATA);
537 assert_eq!(layer.block_num(data).unwrap(), 1);
538 assert_eq!(layer.data(data).unwrap(), b"hello world data");
539 }
540
541 #[test]
542 fn test_tftp_ack_parsing() {
543 let data = b"\x00\x04\x00\x05";
544 let layer = make_layer(data);
545 assert_eq!(layer.opcode(data).unwrap(), OPCODE_ACK);
546 assert_eq!(layer.block_num(data).unwrap(), 5);
547 }
548
549 #[test]
550 fn test_tftp_error_parsing() {
551 let data = b"\x00\x05\x00\x01File not found\x00";
552 let layer = make_layer(data);
553 assert_eq!(layer.opcode(data).unwrap(), OPCODE_ERROR);
554 assert_eq!(layer.error_code(data).unwrap(), ERR_FILE_NOT_FOUND);
555 assert_eq!(layer.error_msg(data).unwrap(), "File not found");
556 }
557
558 #[test]
559 fn test_tftp_error_code_descriptions() {
560 assert_eq!(error_code_description(ERR_FILE_NOT_FOUND), "File not found");
561 assert_eq!(
562 error_code_description(ERR_ACCESS_VIOLATION),
563 "Access violation"
564 );
565 assert_eq!(
566 error_code_description(ERR_DISK_FULL),
567 "Disk full or allocation exceeded"
568 );
569 }
570
571 #[test]
572 fn test_tftp_field_access() {
573 let data = b"\x00\x04\x00\x02";
574 let layer = make_layer(data);
575 assert!(matches!(
576 layer.get_field(data, "opcode"),
577 Some(Ok(FieldValue::U16(4)))
578 ));
579 assert!(matches!(
580 layer.get_field(data, "block_num"),
581 Some(Ok(FieldValue::U16(2)))
582 ));
583 assert!(layer.get_field(data, "bad_field").is_none());
584 }
585}