use crate::Protocol;
use std::net::IpAddr;
pub fn checksum(data: &[u8]) -> u16 {
let mut sum: u32 = 0;
let n = data.len();
let mut i = 0;
while i + 1 < n {
sum += ((data[i] as u32) << 8) | (data[i + 1] as u32);
i += 2;
}
if n & 1 != 0 {
sum += (data[n - 1] as u32) << 8;
}
while sum >> 16 != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!sum as u16
}
pub fn combine_checksums(a: u16, b: u16) -> u16 {
let mut sum = a as u32 + b as u32;
while sum >> 16 != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
sum as u16
}
pub fn pseudo_header_checksum(proto: Protocol, src: IpAddr, dst: IpAddr, length: u16) -> u16 {
match (src, dst) {
(IpAddr::V4(s), IpAddr::V4(d)) => {
let mut buf = [0u8; 12];
buf[0..4].copy_from_slice(&s.octets());
buf[4..8].copy_from_slice(&d.octets());
buf[8] = 0;
buf[9] = proto.as_u8();
buf[10..12].copy_from_slice(&length.to_be_bytes());
!checksum(&buf)
}
(IpAddr::V6(s), IpAddr::V6(d)) => {
let mut buf = [0u8; 40];
buf[0..16].copy_from_slice(&s.octets());
buf[16..32].copy_from_slice(&d.octets());
buf[34..36].copy_from_slice(&length.to_be_bytes());
buf[39] = proto.as_u8();
!checksum(&buf)
}
_ => 0,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{Ipv4Addr, Ipv6Addr};
#[test]
fn rfc1071_reference() {
let data = [0x00, 0x01, 0xf2, 0x03, 0xf4, 0xf5, 0xf6, 0xf7];
assert_eq!(checksum(&data), 0x220d);
}
#[test]
fn odd_length_is_padded() {
let data = [0x00, 0x01, 0x02];
assert_eq!(checksum(&data), 0xFDFE);
}
#[test]
fn combine_is_associative() {
let a = checksum(&[0xaa; 100]);
let b = checksum(&[0x55; 50]);
let c = combine_checksums(a, b);
let d = combine_checksums(b, a);
assert_eq!(c, d);
}
#[test]
fn pseudo_header_v4() {
let s = IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4));
let d = IpAddr::V4(Ipv4Addr::new(5, 6, 7, 8));
let sum = pseudo_header_checksum(Protocol::UDP, s, d, 20);
let mut buf = [0u8; 12];
buf[0..4].copy_from_slice(&[1, 2, 3, 4]);
buf[4..8].copy_from_slice(&[5, 6, 7, 8]);
buf[9] = 17;
buf[10..12].copy_from_slice(&20u16.to_be_bytes());
assert_eq!(sum, !checksum(&buf));
}
#[test]
fn pseudo_header_v6() {
let s = IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1));
let d = IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2));
let _ = pseudo_header_checksum(Protocol::TCP, s, d, 40);
}
#[test]
fn mixed_family_returns_zero() {
let s = IpAddr::V4(Ipv4Addr::LOCALHOST);
let d = IpAddr::V6(Ipv6Addr::LOCALHOST);
assert_eq!(pseudo_header_checksum(Protocol::UDP, s, d, 8), 0);
}
}