Skip to main content

tiny_ping/packet/
ipv4.rs

1use std::net::{IpAddr, Ipv4Addr};
2
3use crate::error::Error;
4
5const MINIMUM_PACKET_SIZE: usize = 20;
6
7#[derive(Debug, PartialEq)]
8pub enum IpV4Protocol {
9    Icmp,
10}
11
12impl IpV4Protocol {
13    fn decode(data: u8) -> Option<Self> {
14        match data {
15            1 => Some(IpV4Protocol::Icmp),
16            _ => None,
17        }
18    }
19}
20
21#[derive(Debug)]
22pub struct IpV4Packet<'a> {
23    pub protocol: IpV4Protocol,
24    pub source: IpAddr,
25    pub ttl: u8,
26    pub data: &'a [u8],
27}
28
29impl<'a> IpV4Packet<'a> {
30    pub fn decode(data: &'a [u8]) -> Result<Self, Error> {
31        if data.len() < MINIMUM_PACKET_SIZE {
32            return Err(Error::TooSmallHeader);
33        }
34        let byte0 = data[0];
35        let version = (byte0 & 0xf0) >> 4;
36        let header_size = 4 * ((byte0 & 0x0f) as usize);
37
38        if version != 4 {
39            return Err(Error::InvalidVersion);
40        }
41
42        if header_size < MINIMUM_PACKET_SIZE {
43            return Err(Error::InvalidHeaderSize);
44        }
45
46        if data.len() < header_size {
47            return Err(Error::InvalidHeaderSize);
48        }
49
50        let protocol = match IpV4Protocol::decode(data[9]) {
51            Some(protocol) => protocol,
52            None => return Err(Error::UnknownProtocol),
53        };
54
55        Ok(Self {
56            protocol,
57            source: IpAddr::V4(Ipv4Addr::new(data[12], data[13], data[14], data[15])),
58            ttl: data[8],
59            data: &data[header_size..],
60        })
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn decodes_ipv4_header_metadata_and_payload() {
70        let packet = [
71            0x45, 0, 0, 28, 0, 0, 0, 0, 63, 1, 0, 0, 192, 0, 2, 1, 8, 8, 8, 8, 0, 0, 0, 0, 0, 1, 0,
72            2,
73        ];
74
75        let decoded = IpV4Packet::decode(&packet).unwrap();
76
77        assert_eq!(decoded.protocol, IpV4Protocol::Icmp);
78        assert_eq!(decoded.source, IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1)));
79        assert_eq!(decoded.ttl, 63);
80        assert_eq!(decoded.data, &[0, 0, 0, 0, 0, 1, 0, 2]);
81    }
82
83    #[test]
84    fn rejects_too_small_ihl() {
85        let packet = [
86            0x44, 0, 0, 20, 0, 0, 0, 0, 63, 1, 0, 0, 192, 0, 2, 1, 8, 8, 8, 8,
87        ];
88
89        assert!(matches!(
90            IpV4Packet::decode(&packet),
91            Err(Error::InvalidHeaderSize)
92        ));
93    }
94}