embedded_crc_macros/
lib.rs

1//! This crate provides macros that define portable [CRC-8][crc8] algorithm implementations
2//! with the parameters directly provided at compile time and without any dependencies.
3//! Intended for use in `no_std`.
4//!
5//! [crc8]: https://en.wikipedia.org/wiki/CRC-8
6//!
7//! ## How this crate compares to others
8//!
9//! There is a number of crates implementing CRC algorithms but their intention is to
10//! be configurable, generic, use acceleration via SIMD instructions, etc.
11//!
12//! This crate provides macros that define portable and non-configurable CRC-8 algorithm
13//! implementations with the parameters provided at compile time (optionally using
14//! a pre-calculated lookup table) and without any dependencies.
15//!
16//! This should allow the compiler to make good optimizations and allows for use of the
17//! algorithm in any target architecture with minimal code bloat.
18//!
19//! Furthermore, this crate provides macros to generate the lookup tables on build time.
20//!
21//! This makes this crate specially well suited for use in `no_std` environments.
22//!
23//! # Usage
24//!
25//! The examples implement the System Management Bus (SMBus) Packet Error Code
26//! calculation algorithm, also called CRC-8-ATM HEC.
27//!
28//! This algorithm uses the polynomial `x^8 + x^2 + x + 1` which corresponds to
29//! the value `7` with an initial value of 0.
30//!
31//! See [here][smbus-pec] for more information.
32//!
33//! [smbus-pec]: https://en.wikipedia.org/wiki/System_Management_Bus#Packet_Error_Checking
34//!
35//! ## Define a function computing the SMBus PEC algorithm
36//!
37//! ```rust
38//! use embedded_crc_macros::crc8;
39//!
40//! // 7 corresponds to the polynomial x^8 + x^2 + x + 1
41//! crc8!(fn smbus_pec, 7, 0, "SMBus Packet Error Code");
42//!
43//! const ADDR: u8 = 0x5A;
44//! let command = 0x06;
45//! let value = 0xAB;
46//!
47//! let checksum = smbus_pec(&[ADDR << 1, command, value]);
48//! println!("PEC: {}", checksum);
49//! ```
50//!
51//! ## Define a function computing the SMBus PEC algorithm using a pre-calculated lookup table
52//!
53//! A lookup table constant must be defined in the same environment.
54//! This can be generated in the `build.rs` file with the
55//! [build_rs_lookup_table_file_generation](macro.build_rs_lookup_table_file_generation.html)
56//! macro.
57//! ```rust
58//! use embedded_crc_macros::crc8_lookup_table;
59//!
60//! crc8_lookup_table!(fn smbus_pec, 0, LOOKUP_TABLE, "SMBus Packet Error Code");
61//!
62//! const ADDR: u8 = 0x5A;
63//! let command = 0x06;
64//! let value = 0xAB;
65//!
66//! let checksum = smbus_pec(&[ADDR << 1, command, value]);
67//! println!("PEC: {}", checksum);
68//!
69//! # // This can be generated on build time with the
70//! # // `build_rs_lookup_table_file_generation` macro.
71//! # const LOOKUP_TABLE: [u8; 256] = [0; 256];
72//! ```
73//!
74//! ## Define structure implementing the SMBus PEC algorithm as a `core::hash::Hasher`
75//!
76//! ```rust
77//! use core::hash::Hasher;
78//! use embedded_crc_macros::crc8_hasher;
79//!
80//! crc8_hasher!(struct SmbusPec, 7 /* x^8+x^2+x+1 */, 0, "SMBus Packet Error Code");
81//!
82//! let mut hasher = SmbusPec::new();
83//! hasher.write(&[0xAB, 0xCD]);
84//! let pec = hasher.finish();
85//!
86//! println!("PEC: {}", pec);
87//! ```
88//!
89//! ## `core::hash::Hasher` implementation using a pre-calculated lookup table.
90//!
91//! A lookup table constant must be defined in the same environment.
92//! This can be generated in the `build.rs` file with the
93//! [build_rs_lookup_table_file_generation](macro.build_rs_lookup_table_file_generation.html)
94//! macro.
95//! ```rust
96//! use core::hash::Hasher;
97//! use embedded_crc_macros::crc8_hasher_lookup_table;
98//!
99//! // include!(concat!(env!("OUT_DIR"), "/lookup_table.rs"));
100//! crc8_hasher_lookup_table!(struct SmbusPec, 0, LOOKUP_TABLE, "SMBus Packet Error Code");
101//!
102//! let mut hasher = SmbusPec::new();
103//! hasher.write(&[0xAB, 0xCD]);
104//! let pec = hasher.finish();
105//!
106//! println!("PEC: {}", pec);
107//! #
108//! # // This can be generated on build time with the
109//! # // `build_rs_lookup_table_file_generation` macro.
110//! # const LOOKUP_TABLE: [u8; 256] = [0; 256];
111//! ```
112
113#![doc(html_root_url = "https://docs.rs/embedded-crc-macros/1.0.0")]
114#![deny(unsafe_code, missing_docs)]
115#![no_std]
116
117/// Define public function implementing the CRC-8 algorithm for the given polynomial and initial value.
118///
119/// A function name and some documentation for it must be provided. For example:
120/// ```rust
121/// use embedded_crc_macros::crc8;
122/// crc8!(fn smbus_pec, 7 /* x^8+x^2+x+1 */, 0, "SMBus Packet Error Code");
123/// ```
124#[macro_export]
125macro_rules! crc8 {
126    (fn $function_name:ident, $poly:expr, $initial_value:expr, $doc:expr) => {
127        #[doc=$doc]
128        pub fn $function_name(data: &[u8]) -> u8 {
129            let mut crc = $initial_value;
130            for byte in data {
131                crc ^= byte;
132                for _ in 0..8 {
133                    crc = if (crc & (1 << 7)) != 0 {
134                        (crc << 1) ^ $poly
135                    } else {
136                        crc << 1
137                    };
138                }
139            }
140            crc
141        }
142    };
143}
144
145/// Define public function implementing the CRC-8 algorithm for the given polynomial
146/// and initial value using a lookup table.
147///
148/// This implementation is much faster at the cost of some space.
149/// A function name and some documentation for it must be provided.
150///
151/// The lookup table must be stored in a constant defined in the same environment
152/// and can be generated in the `build.rs` file with the
153/// [build_rs_lookup_table_file_generation](macro.build_rs_lookup_table_file_generation.html)
154/// macro and then included like in the following example.
155///
156/// You can also find an example using this in the [`smbus-pec`] crate.
157///
158/// [`smbus-pec`]: https://crates.io/crates/smbus-pec
159///
160/// ```rust
161/// use embedded_crc_macros::crc8_lookup_table;
162/// // include!(concat!(env!("OUT_DIR"), "/lookup_table.rs"));
163/// crc8_lookup_table!(fn pec, 0, LOOKUP_TABLE, "SMBus Packet Error Code");
164///
165/// # // This can be generated on build time with the
166/// # // `build_rs_lookup_table_file_generation` macro.
167/// # const LOOKUP_TABLE: [u8; 256] = [0; 256];
168/// ```
169#[macro_export]
170macro_rules! crc8_lookup_table {
171    (fn $function_name:ident, $initial_value:expr, $lookup_table:ident, $doc:expr) => {
172        #[doc=$doc]
173        pub fn $function_name(data: &[u8]) -> u8 {
174            let mut crc = $initial_value;
175            for byte in data {
176                crc = $lookup_table[(crc ^ *byte) as usize];
177            }
178            crc
179        }
180    };
181}
182
183/// Define public structure implementing the CRC-8 algorithm for the given polynomial
184/// and initial value as a `core::hash::Hasher` trait implementation.
185///
186/// A struct name and some documentation for it must be provided. For example:
187/// ```rust
188/// use core::hash::Hasher;
189/// use embedded_crc_macros::crc8_hasher;
190///
191/// crc8_hasher!(struct SmbusPec, 7 /* x^8+x^2+x+1 */, 0, "SMBus Packet Error Code");
192///
193/// let mut hasher = SmbusPec::new();
194/// hasher.write(&[0xAB, 0xCD]);
195/// let pec = hasher.finish();
196///
197/// println!("PEC: {}", pec);
198/// ```
199#[macro_export]
200macro_rules! crc8_hasher {
201    (struct $struct_name:ident, $poly:expr, $initial_value:expr, $doc:expr) => {
202        #[doc=$doc]
203        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
204        pub struct $struct_name {
205            crc: u8,
206        }
207
208        impl $struct_name {
209            /// Create new instance
210            pub fn new() -> Self {
211                $struct_name {
212                    crc: $initial_value,
213                }
214            }
215        }
216
217        impl Default for $struct_name {
218            fn default() -> Self {
219                Self::new()
220            }
221        }
222
223        impl core::hash::Hasher for $struct_name {
224            #[inline]
225            fn write(&mut self, bytes: &[u8]) {
226                for byte in bytes {
227                    self.crc ^= byte;
228                    for _ in 0..8 {
229                        self.crc = if (self.crc & (1 << 7)) != 0 {
230                            (self.crc << 1) ^ $poly
231                        } else {
232                            self.crc << 1
233                        };
234                    }
235                }
236            }
237
238            #[inline]
239            fn finish(&self) -> u64 {
240                self.crc as u64
241            }
242        }
243    };
244}
245
246/// Define public structure implementing the CRC-8 algorithm as a `core::hash::Hasher`
247/// trait implementation using a pre-calculated lookup table.
248///
249/// This implementation is much faster at the cost of some space.
250/// A struct name and some documentation for it must be provided.
251///
252/// The lookup table must be stored in a constant defined in the same environment
253/// and can be generated in the `build.rs` file with the
254/// [build_rs_lookup_table_file_generation](macro.build_rs_lookup_table_file_generation.html)
255/// macro and then included like in the following example.
256///
257/// You can also find an example on how to do this in the [`smbus-pec`] crate.
258///
259/// [`smbus-pec`]: https://crates.io/crates/smbus-pec
260///
261/// ```rust
262/// use core::hash::Hasher;
263/// use embedded_crc_macros::crc8_hasher_lookup_table;
264///
265/// // include!(concat!(env!("OUT_DIR"), "/lookup_table.rs"));
266/// crc8_hasher_lookup_table!(struct SmbusPec, 0, LOOKUP_TABLE, "SMBus Packet Error Code");
267///
268/// let mut hasher = SmbusPec::new();
269/// hasher.write(&[0xAB, 0xCD]);
270/// let pec = hasher.finish();
271///
272/// println!("PEC: {}", pec);
273/// #
274/// # // This can be generated on build time with the
275/// # // `build_rs_lookup_table_file_generation` macro.
276/// # const LOOKUP_TABLE: [u8; 256] = [0; 256];
277/// ```
278#[macro_export]
279macro_rules! crc8_hasher_lookup_table {
280    (struct $struct_name:ident, $initial_value:expr, $lookup_table:ident, $doc:expr) => {
281        #[doc=$doc]
282        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
283        pub struct $struct_name {
284            crc: u8,
285        }
286
287        impl $struct_name {
288            /// Create new instance
289            pub fn new() -> Self {
290                Self {
291                    crc: $initial_value,
292                }
293            }
294        }
295
296        impl Default for $struct_name {
297            fn default() -> Self {
298                Self::new()
299            }
300        }
301
302        impl core::hash::Hasher for $struct_name {
303            #[inline]
304            fn write(&mut self, bytes: &[u8]) {
305                for byte in bytes {
306                    self.crc = $lookup_table[(self.crc ^ *byte) as usize];
307                }
308            }
309
310            #[inline]
311            fn finish(&self) -> u64 {
312                self.crc as u64
313            }
314        }
315    };
316}
317
318/// Code generation macro for use in `build.rs` files.
319///
320/// Generate file containing a lookup table constant with all the values for the checksum function.
321///
322/// Example `build.rs` file:
323/// ```no_run
324/// use embedded_crc_macros::{crc8, build_rs_lookup_table_file_generation};
325///
326/// crc8!(fn smbus_pec, 7, 0, "");
327/// build_rs_lookup_table_file_generation!(fn write_file, smbus_pec, LOOKUP_TABLE, "lookup_table.rs", u8, 256);
328///
329/// fn main() {
330///     println!("cargo:rerun-if-changed=build.rs");
331///     println!("cargo:rerun-if-changed=lib.rs");
332///
333///     write_file().expect("Couldn't write lookup table file!");
334/// }
335/// ```
336#[macro_export]
337macro_rules! build_rs_lookup_table_file_generation {
338    (fn $function_name:ident, $checksum_function:ident, $lookup_table_constant_name:ident, $lookup_table_file:expr, $t:ty, $size:expr) => {
339        fn $function_name() -> std::io::Result<()> {
340            use std::io::prelude::*;
341            let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
342            let out_path = out_path.join($lookup_table_file);
343            let mut file = std::fs::File::create(out_path)?;
344            file.write_all(
345                concat!(
346                    "const ",
347                    stringify!($lookup_table_constant_name),
348                    ": [",
349                    stringify!($t),
350                    ";",
351                    stringify!($size),
352                    "] = [\n"
353                )
354                .as_bytes(),
355            )?;
356            for i in 0..$size {
357                if i % 16 == 0 {
358                    file.write_all(b"    ")?;
359                }
360                file.write_all(format!("0x{:x}, ", $checksum_function(&[i as $t])).as_bytes())?;
361                if i > 0 && (i + 1) % 16 == 0 {
362                    file.write_all(b"\n")?;
363                }
364            }
365            file.write_all(b"];\n")
366        }
367    };
368}