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}