1#[derive(Debug, Clone)]
3pub struct Checksum {
4 state: u32,
5}
6
7impl Checksum {
8 #[inline]
10 #[must_use]
11 pub const fn new() -> Self {
12 Self { state: 0 }
13 }
14
15 pub fn update(&mut self, x: &[u8]) -> &mut Self {
21 assert!(x.len() & 0x1 == 0, "length of slice must be even");
22
23 x.chunks_exact(2).for_each(|word| {
24 let word = unsafe {
25 word.as_ptr().cast::<u16>().read_unaligned()
26 };
27 self.state += u32::from(word);
28 });
29
30 self
31 }
32
33 #[inline]
35 #[must_use]
36 pub fn finalize(&self) -> u16 {
37 !fold_u32_to_u16(self.state)
38 }
39
40 #[inline]
44 #[must_use]
45 pub fn checksum(data: &[u8]) -> u16 {
46 Self::new().update(data).finalize()
47 }
48}
49
50impl Default for Checksum {
51 fn default() -> Self {
52 Self::new()
53 }
54}
55
56#[allow(clippy::cast_possible_truncation)]
57#[inline]
58const fn fold_u32_to_u16(mut x: u32) -> u16 {
59 while x >> 16 != 0 {
60 x = (x >> 16) + (x & 0xffff);
61 }
62
63 x as u16
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 macro_rules! tests {
71 ($(
72 $test_name:ident {
73 input: $input:expr,
74 expected: $expected:expr,
75 },
76 )*) => {
77 $(
78 #[test]
79 fn $test_name() {
80 let input: &'static [u8] = $input;
81 let expected: [u8; 2] = $expected;
82
83 assert_eq!(
84 Checksum::checksum(input),
85 u16::from_ne_bytes(expected),
86 concat!("`", stringify!($test_name), "` failed")
87 );
88 }
89 )*
90 };
91 }
92
93 tests! {
94 test_checksum_ip_packet_calculate {
95 input: &[0x45, 0x00, 0x00, 0x73, 0x00, 0x00, 0x40, 0x00, 0x40, 0x11, 0x00, 0x00, 0xc0, 0xa8, 0x00, 0x01, 0xc0, 0xa8, 0x00, 0xc7],
96 expected: [0xb8, 0x61],
97 },
98 test_checksum_ip_packet_verify {
99 input: &[0x45, 0x00, 0x00, 0x73, 0x00, 0x00, 0x40, 0x00, 0x40, 0x11, 0xb8, 0x61, 0xc0, 0xa8, 0x00, 0x01, 0xc0, 0xa8, 0x00, 0xc7],
100 expected: [0x00, 0x00],
101 },
102
103 test_checksum_random_1_calculate {
104 input: &[0x1c, 0x3b, 0xe6, 0x6f, 0xc4, 0xdc, 0xd5, 0x70, 0x30, 0x3f, 0xca, 0xb5, 0x72, 0x8d, 0x00, 0x00],
105 expected: [0xf5, 0x84],
106 },
107 test_checksum_random_1_verify {
108 input: &[0x1c, 0x3b, 0xe6, 0x6f, 0xc4, 0xdc, 0xd5, 0x70, 0x30, 0x3f, 0xca, 0xb5, 0x72, 0x8d, 0xf5, 0x84],
109 expected: [0x00, 0x00],
110 },
111
112 test_checksum_random_2_calculate {
113 input: &[0xae, 0x3e, 0x0d, 0x98, 0xbd, 0x16, 0xa2, 0xef, 0xac, 0x70, 0x9f, 0x49, 0x5e, 0xf3, 0x00, 0x00],
114 expected: [0x39, 0x75],
115 },
116 test_checksum_random_2_verify {
117 input: &[0xae, 0x3e, 0x0d, 0x98, 0xbd, 0x16, 0xa2, 0xef, 0xac, 0x70, 0x9f, 0x49, 0x5e, 0xf3, 0x39, 0x75],
118 expected: [0x00, 0x00],
119 },
120
121 test_checksum_random_3_calculate {
122 input: &[0x4d, 0xa7, 0x01, 0x69, 0xe3, 0x6b, 0xfb, 0xf5, 0xf2, 0x2f, 0x08, 0x61, 0x47, 0x0a, 0x00, 0x00],
123 expected: [0x8f, 0xf2],
124 },
125 test_checksum_random_3_verify {
126 input: &[0x4d, 0xa7, 0x01, 0x69, 0xe3, 0x6b, 0xfb, 0xf5, 0xf2, 0x2f, 0x08, 0x61, 0x47, 0x0a, 0x8f, 0xf2],
127 expected: [0x00, 0x00],
128 },
129 }
130
131 #[test]
132 #[should_panic = "length of slice must be even"]
133 fn test_calculate_checksum_invalid_slice() {
134 let _ = Checksum::checksum(&[0x00, 0x00, 0x00]);
135 }
136}