diff --git a/.gitignore b/.gitignore index 2f88dba..5cb9f6a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target **/*.rs.bk -Cargo.lock \ No newline at end of file +Cargo.lock +.vscode \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 6498e55..313fa77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,10 @@ path = "./postcard-derive" version = "0.1.1" optional = true +[dependencies.crc] +version = "3.0.1" +optional = true + [features] default = ["heapless-cas"] @@ -59,6 +63,7 @@ use-std = ["serde/std", "alloc"] heapless-cas = ["heapless", "heapless/cas"] alloc = ["serde/alloc"] use-defmt = ["defmt"] +use-crc = ["crc"] # Experimental features! # diff --git a/src/ser/flavors.rs b/src/ser/flavors.rs index f82c8c0..ce414e4 100644 --- a/src/ser/flavors.rs +++ b/src/ser/flavors.rs @@ -411,6 +411,152 @@ where } } +//////////////////////////////////////// +// CRC +//////////////////////////////////////// + +/// This Cyclic Redundancy Check flavor applies [the CRC crate's `Algorithm`](https://docs.rs/crc/latest/crc/struct.Algorithm.html) struct on +/// the serialized data. The output of this flavor receives the CRC appended to the bytes. +/// +/// CRCs are used for error detection when reading data back. +/// +/// The `crc` feature requires enabling to use this module. +/// +/// More on CRCs: https://en.wikipedia.org/wiki/Cyclic_redundancy_check. +#[cfg(feature = "crc")] +pub mod crc { + use crc::Digest; + use crc::Width; + + use super::Flavor; + use super::IndexMut; + use crate::Result; + + /// Manages CRC modifications as a flavor. + pub struct CrcModifier<'a, B, W> + where + B: Flavor + IndexMut, + W: Width, + { + flav: B, + digest: Digest<'a, W>, + } + + impl<'a, B, W> CrcModifier<'a, B, W> + where + B: Flavor + IndexMut, + W: Width, + { + /// Create a new Crc modifier Flavor. + pub fn new(bee: B, digest: Digest<'a, W>) -> Self { + Self { flav: bee, digest } + } + + #[inline(always)] + fn try_push_crc(flav: &mut B, crc_bytes: &[u8]) -> Result<()> { + for byte in crc_bytes { + flav.try_push(*byte)?; + } + Ok(()) + } + } + + impl<'a, B> Flavor for CrcModifier<'a, B, u8> + where + B: Flavor + IndexMut, + { + type Output = ::Output; + + #[inline(always)] + fn try_push(&mut self, data: u8) -> Result<()> { + self.digest.update(&[data]); + self.flav.try_push(data) + } + + fn finalize(mut self) -> Result { + let crc = self.digest.finalize(); + Self::try_push_crc(&mut self.flav, &crc.to_le_bytes())?; + self.flav.finalize() + } + } + + impl<'a, B> Flavor for CrcModifier<'a, B, u16> + where + B: Flavor + IndexMut, + { + type Output = ::Output; + + #[inline(always)] + fn try_push(&mut self, data: u8) -> Result<()> { + self.digest.update(&[data]); + self.flav.try_push(data) + } + + fn finalize(mut self) -> Result { + let crc = self.digest.finalize(); + Self::try_push_crc(&mut self.flav, &crc.to_le_bytes())?; + self.flav.finalize() + } + } + + impl<'a, B> Flavor for CrcModifier<'a, B, u32> + where + B: Flavor + IndexMut, + { + type Output = ::Output; + + #[inline(always)] + fn try_push(&mut self, data: u8) -> Result<()> { + self.digest.update(&[data]); + self.flav.try_push(data) + } + + fn finalize(mut self) -> Result { + let crc = self.digest.finalize(); + Self::try_push_crc(&mut self.flav, &crc.to_le_bytes())?; + self.flav.finalize() + } + } + + impl<'a, B> Flavor for CrcModifier<'a, B, u64> + where + B: Flavor + IndexMut, + { + type Output = ::Output; + + #[inline(always)] + fn try_push(&mut self, data: u8) -> Result<()> { + self.digest.update(&[data]); + self.flav.try_push(data) + } + + fn finalize(mut self) -> Result { + let crc = self.digest.finalize(); + Self::try_push_crc(&mut self.flav, &crc.to_le_bytes())?; + self.flav.finalize() + } + } + + impl<'a, B> Flavor for CrcModifier<'a, B, u128> + where + B: Flavor + IndexMut, + { + type Output = ::Output; + + #[inline(always)] + fn try_push(&mut self, data: u8) -> Result<()> { + self.digest.update(&[data]); + self.flav.try_push(data) + } + + fn finalize(mut self) -> Result { + let crc = self.digest.finalize(); + Self::try_push_crc(&mut self.flav, &crc.to_le_bytes())?; + self.flav.finalize() + } + } +} + /// The `Size` flavor is a measurement flavor, which accumulates the number of bytes needed to /// serialize the data. /// diff --git a/tests/crc.rs b/tests/crc.rs new file mode 100644 index 0000000..4c6d978 --- /dev/null +++ b/tests/crc.rs @@ -0,0 +1,17 @@ +#[test] +#[cfg(feature = "crc")] +fn test_crc() { + use crc::{Crc, CRC_32_ISCSI}; + use postcard::{ + ser_flavors::{crc::CrcModifier, Slice}, + serialize_with_flavor, + }; + + let data: &[u8] = &[0x01, 0x00, 0x20, 0x30]; + let buffer = &mut [0u8; 32]; + let crc = Crc::::new(&CRC_32_ISCSI); + let digest = crc.digest(); + let res = serialize_with_flavor(data, CrcModifier::new(Slice::new(buffer), digest)).unwrap(); + + assert_eq!(res, &[0x04, 0x01, 0x00, 0x20, 0x30, 0x8E, 0xC8, 0x1A, 0x37]); +}