#[inline]
pub fn read_u16be(buf: &[u8]) -> u16 {
u16::from_be_bytes([buf[0], buf[1]])
}
#[inline]
pub fn read_u32be(buf: &[u8]) -> u32 {
u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]])
}
#[inline]
pub fn write_u16be(buf: &mut [u8], val: u16) {
buf[..2].copy_from_slice(&val.to_be_bytes());
}
#[inline]
pub fn write_u32be(buf: &mut [u8], val: u32) {
buf[..4].copy_from_slice(&val.to_be_bytes());
}
pub fn internet_checksum(mut data: &[u8]) -> u16 {
let mut sum: u32 = 0;
while data.len() >= 2 {
sum += read_u16be(data) as u32;
data = &data[2..];
}
if !data.is_empty() {
sum += (data[0] as u32) << 8;
}
while sum >> 16 != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!(sum as u16)
}
pub fn pseudo_header_checksum(src: &[u8], dst: &[u8], protocol: u8, transport_data: &[u8]) -> u16 {
let mut sum: u32 = 0;
sum = add_slice_to_sum(sum, src);
sum = add_slice_to_sum(sum, dst);
sum += protocol as u32;
sum += transport_data.len() as u32;
sum = add_slice_to_sum(sum, transport_data);
while sum >> 16 != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!(sum as u16)
}
fn add_slice_to_sum(mut sum: u32, mut data: &[u8]) -> u32 {
while data.len() >= 2 {
sum += read_u16be(data) as u32;
data = &data[2..];
}
if !data.is_empty() {
sum += (data[0] as u32) << 8;
}
sum
}
#[inline]
pub fn seal_internet_checksum(buf: &mut [u8], csum_offset: usize) {
buf[csum_offset] = 0;
buf[csum_offset + 1] = 0;
let csum = internet_checksum(buf);
write_u16be(&mut buf[csum_offset..csum_offset + 2], csum);
}
#[inline]
pub fn seal_transport_checksum(buf: &mut [u8], csum_offset: usize, src: &[u8], dst: &[u8], protocol: u8) {
buf[csum_offset] = 0;
buf[csum_offset + 1] = 0;
let csum = pseudo_header_checksum(src, dst, protocol, buf);
write_u16be(&mut buf[csum_offset..csum_offset + 2], csum);
}
#[macro_export]
macro_rules! parser_new {
($min_len:expr) => {
#[inline]
pub fn new(buf: &'a [u8]) -> Option<Self> {
if buf.len() < $min_len { return None; }
Some(Self { buf })
}
};
}
pub trait PacketBuilder {
type Output;
fn build(self) -> Self::Output;
}
#[derive(Debug, Clone)]
pub enum BuildError {
PayloadTooLarge { max: usize, actual: usize },
InvalidField { field: &'static str, reason: &'static str },
}
impl core::fmt::Display for BuildError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::PayloadTooLarge { max, actual } => {
write!(f, "payload too large: max {max} bytes, got {actual}")
}
Self::InvalidField { field, reason } => {
write!(f, "invalid field `{field}`: {reason}")
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn read_u16be_works() {
assert_eq!(read_u16be(&[0x08, 0x00]), 0x0800);
assert_eq!(read_u16be(&[0xFF, 0xFF]), 0xFFFF);
}
#[test]
fn read_u32be_works() {
assert_eq!(read_u32be(&[0xC0, 0xA8, 0x01, 0x01]), 0xC0A80101);
}
#[test]
fn write_u16be_works() {
let mut buf = [0u8; 2];
write_u16be(&mut buf, 0x0800);
assert_eq!(buf, [0x08, 0x00]);
}
#[test]
fn write_u32be_works() {
let mut buf = [0u8; 4];
write_u32be(&mut buf, 0xC0A80101);
assert_eq!(buf, [0xC0, 0xA8, 0x01, 0x01]);
}
#[test]
fn checksum_empty() {
assert_eq!(internet_checksum(&[]), 0xFFFF);
}
#[test]
fn checksum_single_byte() {
assert_eq!(internet_checksum(&[0x01]), 0xFEFF);
}
#[test]
fn checksum_known_ip_header() {
let header: [u8; 20] = [
0x45, 0x00, 0x00, 0x3C, 0x1C, 0x46, 0x40, 0x00,
0x40, 0x06, 0x00, 0x00, 0xAC, 0x10, 0x0A, 0x63,
0xAC, 0x10, 0x0A, 0x0C,
];
let checksum = internet_checksum(&header);
let mut with_checksum = header;
with_checksum[10..12].copy_from_slice(&checksum.to_be_bytes());
assert_eq!(internet_checksum(&with_checksum), 0);
}
#[test]
fn pseudo_header_checksum_basic() {
let src = [192u8, 168, 1, 1];
let dst = [192u8, 168, 1, 2];
let tcp_header = [0x00u8, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x50, 0x02, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00];
let csum = pseudo_header_checksum(&src, &dst, 6, &tcp_header);
assert_ne!(csum, 0);
}
#[test]
fn checksum_roundtrip() {
let data: [u8; 8] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
let csum = internet_checksum(&data);
let mut combined = [0u8; 10];
combined[..8].copy_from_slice(&data);
combined[8..10].copy_from_slice(&csum.to_be_bytes());
assert_eq!(internet_checksum(&combined), 0);
}
}