channels_packet/
checksum.rs

1/// The frame header checksum.
2#[derive(Debug, Clone)]
3pub struct Checksum {
4	state: u32,
5}
6
7impl Checksum {
8	/// Create a new empty [`Checksum`].
9	#[inline]
10	#[must_use]
11	pub const fn new() -> Self {
12		Self { state: 0 }
13	}
14
15	/// Update the checksum from `x`.
16	///
17	/// # Panics
18	///
19	/// If `x` does not have even length.
20	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	/// Finalize the checksum and produce a final value.
34	#[inline]
35	#[must_use]
36	pub fn finalize(&self) -> u16 {
37		!fold_u32_to_u16(self.state)
38	}
39
40	/// Calculate the checksum of `data`.
41	///
42	/// This is a shorthand for: `Checksum::new().update(data).finalize()`.
43	#[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}