use crate::ietf::{
ip::{Protocol, Version},
ipv4::{Address, Options},
};
use crate::{Buf, BufError, BufMut, BufResult, Codec, Cursor};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Dscp(pub u8);
impl Dscp {
pub fn new(value: u8) -> BufResult<Self> {
if value <= 63 {
Ok(Self(value))
} else {
Err(BufError::UnexpectedValue)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum Ecn {
NonEct = 0,
Ect0 = 1,
Ect1 = 2,
Ce = 3,
}
impl Ecn {
fn from_bits(bits: u8) -> BufResult<Self> {
match bits & 0x03 {
0 => Ok(Self::NonEct),
1 => Ok(Self::Ect0),
2 => Ok(Self::Ect1),
3 => Ok(Self::Ce),
_ => Err(BufError::UnexpectedValue),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Ihl(pub u8);
impl Ihl {
pub fn new(value: u8) -> BufResult<Self> {
if (5..=15).contains(&value) {
Ok(Self(value))
} else {
Err(BufError::UnexpectedValue)
}
}
}
impl PartialEq<u8> for Ihl {
fn eq(&self, other: &u8) -> bool {
self.0 == *other
}
}
impl PartialEq<usize> for Ihl {
fn eq(&self, other: &usize) -> bool {
(self.0 as usize) == *other
}
}
impl PartialOrd<u8> for Ihl {
fn partial_cmp(&self, other: &u8) -> Option<core::cmp::Ordering> {
self.0.partial_cmp(other)
}
}
impl PartialOrd<usize> for Ihl {
fn partial_cmp(&self, other: &usize) -> Option<core::cmp::Ordering> {
(self.0 as usize).partial_cmp(other)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Checksum(pub u16);
impl Checksum {
pub fn calculate(data: &[u8]) -> Self {
let mut sum: u32 = 0;
let mut i = 0;
while i + 1 < data.len() {
sum += u16::from_be_bytes([data[i], data[i + 1]]) as u32;
i += 2;
}
if i < data.len() {
sum += (data[i] as u32) << 8;
}
while sum >> 16 != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
Self(!(sum as u16))
}
}
impl Codec for Checksum {
fn encode<W: BufMut>(&self, writer: &mut Cursor<W>, _: ()) -> BufResult<()> {
self.0.encode(writer, ())
}
fn decode<R: Buf>(reader: &mut Cursor<R>, _: ()) -> BufResult<Self> {
Ok(Self(u16::decode(reader, ())?))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Header {
pub dscp: Dscp,
pub ecn: Ecn,
pub ihl: Ihl,
pub total_length: u16,
pub identification: u16,
pub df: bool,
pub mf: bool,
pub fragment_offset: u16,
pub ttl: u8,
pub protocol: Protocol,
pub checksum: Checksum,
pub source_address: Address,
pub destination_address: Address,
pub options: Options,
}
impl Codec for Header {
fn encode<W: BufMut>(&self, writer: &mut Cursor<W>, _: ()) -> BufResult<()> {
let version_ihl = ((Version::Ip as u8) << 4) | (self.ihl.0 & 0x0F);
writer.write_u8(version_ihl)?;
let dscp_ecn = (self.dscp.0 << 2) | (self.ecn as u8);
writer.write_u8(dscp_ecn)?;
writer.write_u16_be(self.total_length)?;
writer.write_u16_be(self.identification)?;
let mut flags_offset = self.fragment_offset & 0x1FFF;
if self.df {
flags_offset |= 0x4000;
}
if self.mf {
flags_offset |= 0x2000;
}
writer.write_u16_be(flags_offset)?;
writer.write_u8(self.ttl)?;
self.protocol.encode(writer, ())?;
self.checksum.encode(writer, ())?;
self.source_address.encode(writer, ())?;
self.destination_address.encode(writer, ())?;
let options_len = ((self.ihl.0 as usize) * 4).saturating_sub(20);
self.options.encode(writer, options_len)?;
Ok(())
}
fn decode<R: Buf>(reader: &mut Cursor<R>, _: ()) -> BufResult<Self> {
let version_ihl = reader.read_u8()?;
let version = version_ihl >> 4;
if version != (Version::Ip as u8) {
return Err(BufError::UnexpectedValue);
}
let ihl = Ihl::new(version_ihl & 0x0F)?;
let dscp_ecn = reader.read_u8()?;
let dscp = Dscp::new(dscp_ecn >> 2)?;
let ecn = Ecn::from_bits(dscp_ecn)?;
let total_length = reader.read_u16_be()?;
let identification = reader.read_u16_be()?;
let flags_offset = reader.read_u16_be()?;
let df = (flags_offset & 0x4000) != 0;
let mf = (flags_offset & 0x2000) != 0;
let fragment_offset = flags_offset & 0x1FFF;
let ttl = reader.read_u8()?;
let protocol = Protocol::decode(reader, ())?;
let checksum = Checksum::decode(reader, ())?;
let source_address = Address::decode(reader, ())?;
let destination_address = Address::decode(reader, ())?;
let options_len = ((ihl.0 as usize) * 4).saturating_sub(20);
let options = Options::decode(reader, options_len)?;
Ok(Self {
dscp,
ecn,
ihl,
total_length,
identification,
df,
mf,
fragment_offset,
ttl,
protocol,
checksum,
source_address,
destination_address,
options,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Cursor;
#[test]
fn ihl_comparisons() {
let ihl = Ihl::new(5).unwrap();
assert_eq!(ihl, 5u8);
assert_eq!(ihl, 5usize);
assert!(ihl < 6u8);
assert!(ihl <= 5usize);
}
#[test]
fn ihl_validation() {
assert!(Ihl::new(4).is_err());
assert!(Ihl::new(5).is_ok());
assert!(Ihl::new(15).is_ok());
assert!(Ihl::new(16).is_err());
}
#[test]
fn header_roundtrip() {
let etalon_bytes = &[
0x45, 0x00, 0x00, 0x34, 0x00, 0x00, 0x40, 0x00, 0x40, 0x11, 0x00, 0x00, 0xc0, 0xa8,
0x00, 0x01, 0xc0, 0xa8, 0x00, 0x02,
];
let etalon_struct = Header {
dscp: Dscp(0),
ecn: Ecn::NonEct,
ihl: Ihl(5),
total_length: 52,
identification: 0,
df: true,
mf: false,
fragment_offset: 0,
ttl: 64,
protocol: Protocol::UDP,
checksum: Checksum(0),
source_address: Address::from([192, 168, 0, 1]),
destination_address: Address::from([192, 168, 0, 2]),
options: Options::try_from(&[][..]).unwrap(),
};
let mut encoded_bytes = vec![];
{
let writer = &mut Cursor::new(&mut encoded_bytes);
etalon_struct.encode(writer, ()).unwrap();
}
assert_eq!(&etalon_bytes.to_vec(), &encoded_bytes);
let decoded_struct = {
let reader = &mut Cursor::new(&encoded_bytes);
Header::decode(reader, ()).unwrap()
};
assert_eq!(etalon_struct, decoded_struct);
}
}