1use crate::buf::WriteBuf;
19use crate::checksum::Checksum;
20use crate::error::{Error, Result};
21use crate::framing::{Endian, Frame, Framer};
22
23#[non_exhaustive]
37#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
38pub enum ChecksumScope {
39 #[default]
42 Frame,
43 Payload,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
73pub struct Checksummed<F, C> {
74 inner: F,
75 checksum: C,
76 endian: Endian,
77 scope: ChecksumScope,
78}
79
80impl<F: Framer, C: Checksum> Checksummed<F, C> {
81 #[inline]
84 #[must_use]
85 pub const fn new(inner: F, checksum: C, endian: Endian) -> Self {
86 const {
87 assert!(C::LEN >= 1 && C::LEN <= 8, "Checksum::LEN must be in 1..=8");
88 }
89 Self {
90 inner,
91 checksum,
92 endian,
93 scope: ChecksumScope::Frame,
94 }
95 }
96
97 #[inline]
99 #[must_use]
100 pub const fn with_scope(mut self, scope: ChecksumScope) -> Self {
101 self.scope = scope;
102 self
103 }
104
105 #[inline]
107 #[must_use]
108 pub const fn inner(&self) -> &F {
109 &self.inner
110 }
111
112 #[inline]
114 #[must_use]
115 pub const fn checksum(&self) -> &C {
116 &self.checksum
117 }
118
119 #[inline]
121 #[must_use]
122 pub const fn endian(&self) -> Endian {
123 self.endian
124 }
125
126 #[inline]
128 #[must_use]
129 pub const fn scope(&self) -> ChecksumScope {
130 self.scope
131 }
132
133 fn trailer(&self, value: u64) -> [u8; 8] {
134 let mut out = [0u8; 8];
135 match self.endian {
136 Endian::Big => out[..C::LEN].copy_from_slice(&value.to_be_bytes()[8 - C::LEN..]),
137 Endian::Little => out[..C::LEN].copy_from_slice(&value.to_le_bytes()[..C::LEN]),
138 }
139 out
140 }
141
142 fn verify<'a>(&self, input: &'a [u8], frame: Frame<'a>) -> Result<Option<Frame<'a>>> {
143 let start = frame.consumed();
144 let end = start.saturating_add(C::LEN);
147 let Some(received) = input.get(start..end) else {
148 return Ok(None);
149 };
150 let covered = match self.scope {
151 ChecksumScope::Frame => &input[..start],
152 ChecksumScope::Payload => frame.payload(),
153 };
154 let expected = self.trailer(self.checksum.compute(covered));
155 if expected[..C::LEN] != *received {
156 return Err(Error::ChecksumMismatch);
157 }
158 Ok(Some(Frame::new(frame.payload(), end)))
159 }
160}
161
162impl<F: Framer, C: Checksum> Framer for Checksummed<F, C> {
163 #[inline]
170 fn next_frame<'a>(&self, input: &'a [u8]) -> Result<Option<Frame<'a>>> {
171 match self.inner.next_frame(input)? {
172 Some(frame) => self.verify(input, frame),
173 None => Ok(None),
174 }
175 }
176
177 fn write_frame(&self, payload: &[u8], out: &mut WriteBuf<'_>) -> Result<()> {
198 if let Some(inner_max) = self.inner.max_frame_len(payload.len()) {
199 if inner_max.saturating_add(C::LEN) > out.remaining() {
200 return Err(Error::BufferFull);
201 }
202 }
203 let start = out.position();
204 self.inner.write_frame(payload, out)?;
205 let covered = match self.scope {
206 ChecksumScope::Frame => &out.written()[start..],
207 ChecksumScope::Payload => payload,
208 };
209 let trailer = self.trailer(self.checksum.compute(covered));
210 if let Err(e) = out.write_bytes(&trailer[..C::LEN]) {
211 out.rewind(start);
212 return Err(e);
213 }
214 Ok(())
215 }
216
217 #[inline]
223 fn next_frame_from<'a>(
224 &self,
225 input: &'a [u8],
226 scanned: &mut usize,
227 ) -> Result<Option<Frame<'a>>> {
228 match self.inner.next_frame_from(input, scanned)? {
229 Some(frame) => self.verify(input, frame),
230 None => Ok(None),
231 }
232 }
233
234 #[inline]
235 fn max_frame_len(&self, payload_len: usize) -> Option<usize> {
236 self.inner.max_frame_len(payload_len)?.checked_add(C::LEN)
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243 use crate::checksum::{Crc16CcittFalse, Crc32, Crc32c, crc16_ccitt_false};
244 use crate::framing::{Delimited, LengthPrefixed, LengthWidth, StreamDecoder, VarintPrefixed};
245
246 #[test]
247 fn trailer_byte_order_and_coverage() {
248 let inner = LengthPrefixed::new(LengthWidth::U8, Endian::Big);
249 let mut out = [0u8; 16];
250
251 let framer = Checksummed::new(inner, Crc16CcittFalse, Endian::Big);
252 let mut buf = WriteBuf::new(&mut out);
253 framer.write_frame(b"ab", &mut buf).unwrap();
254 let crc = crc16_ccitt_false(&[0x02, b'a', b'b']).to_be_bytes();
255 assert_eq!(buf.written(), &[0x02, b'a', b'b', crc[0], crc[1]]);
256
257 let framer = framer.with_scope(ChecksumScope::Payload);
258 let mut buf = WriteBuf::new(&mut out);
259 framer.write_frame(b"ab", &mut buf).unwrap();
260 let crc = crc16_ccitt_false(b"ab").to_be_bytes();
261 assert_eq!(buf.written(), &[0x02, b'a', b'b', crc[0], crc[1]]);
262 let frame = framer.next_frame(&out[..5]).unwrap().unwrap();
263 assert_eq!(frame.payload(), b"ab");
264
265 let framer = Checksummed::new(inner, Crc16CcittFalse, Endian::Little);
266 let mut buf = WriteBuf::new(&mut out);
267 framer.write_frame(b"ab", &mut buf).unwrap();
268 let crc = crc16_ccitt_false(&[0x02, b'a', b'b']).to_le_bytes();
269 assert_eq!(&buf.written()[3..], &crc);
270 }
271
272 #[test]
273 fn corrupted_header_is_detected_with_frame_scope() {
274 let inner = LengthPrefixed::new(LengthWidth::U8, Endian::Big);
275 let framer = Checksummed::new(inner, Crc32, Endian::Big);
276 let mut out = [0u8; 16];
277 let mut buf = WriteBuf::new(&mut out);
278 framer.write_frame(b"abc", &mut buf).unwrap();
279 let n = buf.position();
280 out[0] = 2; assert_eq!(framer.next_frame(&out[..n]), Err(Error::ChecksumMismatch));
282 }
283
284 #[test]
285 fn incomplete_trailer_needs_more_input() {
286 let framer = Checksummed::new(VarintPrefixed::new(), Crc32c, Endian::Big);
287 let mut out = [0u8; 16];
288 let mut buf = WriteBuf::new(&mut out);
289 framer.write_frame(b"xyz", &mut buf).unwrap();
290 let n = buf.position();
291 assert_eq!(Some(n), framer.max_frame_len(3));
292 for cut in 0..n {
293 assert_eq!(framer.next_frame(&out[..cut]), Ok(None), "cut={cut}");
294 }
295 assert_eq!(framer.next_frame(&out[..n]).unwrap().unwrap().consumed(), n);
296 }
297
298 #[test]
299 fn write_is_atomic_when_trailer_does_not_fit() {
300 let inner = LengthPrefixed::new(LengthWidth::U8, Endian::Big);
301 let framer = Checksummed::new(inner, Crc32, Endian::Big);
302 let mut out = [0xEEu8; 6];
304 let mut buf = WriteBuf::new(&mut out);
305 assert_eq!(framer.write_frame(b"abc", &mut buf), Err(Error::BufferFull));
306 assert_eq!(buf.position(), 0);
307 assert_eq!(out, [0xEE; 6], "nothing is written on error");
308 let mut big = [0u8; 16];
309 let mut buf = WriteBuf::new(&mut big);
310 let framer = Checksummed::new(inner.with_max_payload(1), Crc32, Endian::Big);
312 assert!(matches!(
313 framer.write_frame(b"abc", &mut buf),
314 Err(Error::FrameTooLarge { .. })
315 ));
316 }
317
318 #[test]
319 fn delimited_inner_streams_linearly() {
320 let inner = Delimited::bounded(b"\n").unwrap();
321 let framer = Checksummed::new(inner, Crc16CcittFalse, Endian::Big);
322 let mut wire = [0u8; 64];
323 let mut buf = WriteBuf::new(&mut wire);
324 framer.write_frame(b"hello", &mut buf).unwrap();
325 framer.write_frame(b"world", &mut buf).unwrap();
326 let n = buf.position();
327
328 let mut decoder = StreamDecoder::new(framer);
329 let mut start = 0;
330 let mut frames = Vec::new();
331 for end in 1..=n {
332 while let Some(frame) = decoder.decode(&wire[start..end]).unwrap() {
333 frames.push(frame.payload().to_vec());
334 start += frame.consumed();
335 }
336 }
337 assert_eq!(frames, [b"hello".to_vec(), b"world".to_vec()]);
338 assert_eq!(start, n);
339 assert_eq!(decoder.framer().inner().delimiter(), b"\n");
340 assert_eq!(decoder.framer().checksum(), &Crc16CcittFalse);
341 assert_eq!(decoder.framer().endian(), Endian::Big);
342 assert_eq!(decoder.framer().scope(), ChecksumScope::Frame);
343 }
344
345 #[test]
346 fn default_scope_is_frame() {
347 assert_eq!(ChecksumScope::default(), ChecksumScope::Frame);
348 }
349}