1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
//! This crate provides macros that define portable [CRC-8][crc8] algorithm implementations
//! with the parameters directly provided at compile time and without any dependencies.
//! Intended for use in `no_std`.
//!
//! [crc8]: https://en.wikipedia.org/wiki/CRC-8
//!
//! ## How this crate compares to others
//!
//! There is a number of crates implementing CRC algorithms but their intention is to
//! be configurable, generic, use acceleration via SIMD instructions, etc.
//!
//! This crate provides macros that define portable and non-configurable CRC-8 algorithm
//! implementations with the parameters provided at compile time (optionally using
//! a pre-calculated lookup table) and without any dependencies.
//!
//! This should allow the compiler to make good optimizations and allows for use of the
//! algorithm in any target architecture with minimal code bloat.
//!
//! Furthermore, this crate provides macros to generate the lookup tables on build time.
//!
//! This makes this crate specially well suited for use in `no_std` environments.
//!
//! # Usage
//!
//! The examples implement the System Management Bus (SMBus) Packet Error Code
//! calculation algorithm, also called CRC-8-ATM HEC.
//!
//! This algorithm uses the polynomial `x^8 + x^2 + x + 1` which corresponds to
//! the value `7` with an initial value of 0.
//!
//! See [here][smbus-pec] for more information.
//!
//! [smbus-pec]: https://en.wikipedia.org/wiki/System_Management_Bus#Packet_Error_Checking
//!
//! ## Define a function computing the SMBus PEC algorithm
//!
//! ```rust
//! use embedded_crc_macros::crc8;
//!
//! // 7 corresponds to the polynomial x^8 + x^2 + x + 1
//! crc8!(smbus_pec, 7, 0, "SMBus Packet Error Code");
//!
//! const ADDR: u8 = 0x5A;
//! let command = 0x06;
//! let value = 0xAB;
//!
//! let checksum = smbus_pec(&[ADDR << 1, command, value]);
//! println!("PEC: {}", checksum);
//! ```
//!
//! ## Define a function computing the SMBus PEC algorithm using a pre-calculated lookup table
//!
//! A lookup table must be defined in the same environment as `LOOKUP_TABLE`.
//! ```rust
//! use embedded_crc_macros::crc8_lookup_table;
//!
//! crc8_lookup_table!(smbus_pec, 0, "SMBus Packet Error Code");
//!
//! const ADDR: u8 = 0x5A;
//! let command = 0x06;
//! let value = 0xAB;
//!
//! let checksum = smbus_pec(&[ADDR << 1, command, value]);
//! println!("PEC: {}", checksum);
//!
//! # // This can be generated on build time with the
//! # // `build_rs_lookup_table_file_generation` macro.
//! # const LOOKUP_TABLE: [u8; 256] = [0; 256];
//! ```
//!
//! ## Define structure implementing the SMBus PEC algorithm as a `core::hash::Hasher`
//!
//! ```rust
//! use core::hash::Hasher;
//! use embedded_crc_macros::crc8_hasher;
//!
//! crc8_hasher!(SmbusPec, 7 /* x^8+x^2+x+1 */, 0, "SMBus Packet Error Code");
//!
//! let mut hasher = SmbusPec::new();
//! hasher.write(&[0xAB, 0xCD]);
//! let pec = hasher.finish();
//!
//! println!("PEC: {}", pec);
//! ```
//!
//! ## `core::hash::Hasher` implementation using a pre-calculated lookup table.
//!
//! A lookup table must be defined in the same environment as `LOOKUP_TABLE`.
//! This can be generated in the `build.rs` file with the
//! [build_rs_lookup_table_file_generation](macro.build_rs_lookup_table_file_generation.html)
//! macro.
//! ```rust
//! use core::hash::Hasher;
//! use embedded_crc_macros::crc8_hasher_lookup_table;
//!
//! // include!(concat!(env!("OUT_DIR"), "/lookup_table.rs"));
//! crc8_hasher_lookup_table!(SmbusPec, 0, "SMBus Packet Error Code");
//!
//! let mut hasher = SmbusPec::new();
//! hasher.write(&[0xAB, 0xCD]);
//! let pec = hasher.finish();
//!
//! println!("PEC: {}", pec);
//! #
//! # // This can be generated on build time with the
//! # // `build_rs_lookup_table_file_generation` macro.
//! # const LOOKUP_TABLE: [u8; 256] = [0; 256];
//! ```

#![doc(html_root_url = "https://docs.rs/embedded-crc-macros/0.1.0")]
#![deny(unsafe_code, missing_docs)]
#![no_std]

/// Define public function implementing the CRC-8 algorithm for the given polynomial and initial value.
///
/// A function name and some documentation for it must be provided. For example:
/// ```rust
/// use embedded_crc_macros::crc8;
/// crc8!(smbus_pec, 7 /* x^8+x^2+x+1 */, 0, "SMBus Packet Error Code");
/// ```
#[macro_export]
macro_rules! crc8 {
    ($function_name:ident, $poly:expr, $initial_value:expr, $doc:expr) => {
        #[doc=$doc]
        pub fn $function_name(data: &[u8]) -> u8 {
            let mut crc = $initial_value;
            for byte in data {
                crc ^= byte;
                for _ in 0..8 {
                    crc = if (crc & (1 << 7)) != 0 {
                        (crc << 1) ^ $poly
                    } else {
                        crc << 1
                    };
                }
            }
            crc
        }
    };
}

/// Define public function implementing the CRC-8 algorithm for the given polynomial
/// and initial value using a lookup table.
///
/// This implementation is much faster at the cost of some space.
/// A function name and some documentation for it must be provided.
///
/// The lookup table can be generated in the `build.rs` file with the
/// [build_rs_lookup_table_file_generation](macro.build_rs_lookup_table_file_generation.html)
/// macro and then included like in the following example.
/// ```rust
/// use embedded_crc_macros::crc8_lookup_table;
/// // include!(concat!(env!("OUT_DIR"), "/lookup_table.rs"));
/// crc8_lookup_table!(pec, 0, "SMBus Packet Error Code");
///
/// # // This can be generated on build time with the
/// # // `build_rs_lookup_table_file_generation` macro.
/// # const LOOKUP_TABLE: [u8; 256] = [0; 256];
/// ```
#[macro_export]
macro_rules! crc8_lookup_table {
    ($function_name:ident, $initial_value:expr, $doc:expr) => {
        #[doc=$doc]
        pub fn $function_name(data: &[u8]) -> u8 {
            let mut crc = $initial_value;
            for byte in data {
                crc = LOOKUP_TABLE[(crc ^ *byte) as usize];
            }
            crc
        }
    };
}

/// Define public structure implementing the CRC-8 algorithm for the given polynomial
/// and initial value as a `core::hash::Hasher` trait implementation.
///
/// A struct name and some documentation for it must be provided. For example:
/// ```rust
/// use core::hash::Hasher;
/// use embedded_crc_macros::crc8_hasher;
///
/// crc8_hasher!(SmbusPec, 7 /* x^8+x^2+x+1 */, 0, "SMBus Packet Error Code");
///
/// let mut hasher = SmbusPec::new();
/// hasher.write(&[0xAB, 0xCD]);
/// let pec = hasher.finish();
///
/// println!("PEC: {}", pec);
/// ```
#[macro_export]
macro_rules! crc8_hasher {
    ($struct_name:ident, $poly:expr, $initial_value:expr, $doc:expr) => {
        #[doc=$doc]
        pub struct $struct_name {
            crc: u8,
        }

        impl $struct_name {
            /// Create new instance
            pub fn new() -> Self {
                $struct_name {
                    crc: $initial_value,
                }
            }
        }

        impl core::hash::Hasher for $struct_name {
            #[inline]
            fn write(&mut self, bytes: &[u8]) {
                for byte in bytes {
                    self.crc ^= byte;
                    for _ in 0..8 {
                        self.crc = if (self.crc & (1 << 7)) != 0 {
                            (self.crc << 1) ^ $poly
                        } else {
                            self.crc << 1
                        };
                    }
                }
            }

            #[inline]
            fn finish(&self) -> u64 {
                self.crc as u64
            }
        }
    };
}

/// Define public structure implementing the CRC-8 algorithm as a `core::hash::Hasher`
/// trait implementation using a pre-calculated lookup table.
///
/// This implementation is much faster at the cost of some space.
/// A struct name and some documentation for it must be provided.
///
/// The lookup table can be generated in the `build.rs` file with the
/// [build_rs_lookup_table_file_generation](macro.build_rs_lookup_table_file_generation.html)
/// macro and then included like in the following example.
/// ```rust
/// use core::hash::Hasher;
/// use embedded_crc_macros::crc8_hasher_lookup_table;
///
/// // include!(concat!(env!("OUT_DIR"), "/lookup_table.rs"));
/// crc8_hasher_lookup_table!(SmbusPec, 0, "SMBus Packet Error Code");
///
/// let mut hasher = SmbusPec::new();
/// hasher.write(&[0xAB, 0xCD]);
/// let pec = hasher.finish();
///
/// println!("PEC: {}", pec);
/// #
/// # // This can be generated on build time with the
/// # // `build_rs_lookup_table_file_generation` macro.
/// # const LOOKUP_TABLE: [u8; 256] = [0; 256];
/// ```
#[macro_export]
macro_rules! crc8_hasher_lookup_table {
    ($struct_name:ident, $initial_value:expr, $doc:expr) => {
        #[doc=$doc]
        pub struct $struct_name {
            crc: u8,
        }

        impl $struct_name {
            /// Create new instance
            pub fn new() -> Self {
                Self {
                    crc: $initial_value,
                }
            }
        }

        impl core::hash::Hasher for $struct_name {
            #[inline]
            fn write(&mut self, bytes: &[u8]) {
                for byte in bytes {
                    self.crc = LOOKUP_TABLE[(self.crc ^ *byte) as usize];
                }
            }

            #[inline]
            fn finish(&self) -> u64 {
                self.crc as u64
            }
        }
    };
}

/// Code generation macro for use in `build.rs` files.
///
/// Generate file containing a `LOOKUP_TABLE` constant with all the values for the checksum function.
///
/// Example `build.rs` file:
/// ```no_run
/// use embedded_crc_macros::{crc8, build_rs_lookup_table_file_generation};
///
/// crc8!(smbus_pec, 7, 0, "");
/// build_rs_lookup_table_file_generation!(write_file, smbus_pec, "lookup_table.rs", u8, 256);
///
/// fn main() {
///     println!("cargo:rerun-if-changed=build.rs");
///     println!("cargo:rerun-if-changed=lib.rs");
///
///     write_file().expect("Couldn't write lookup table file!");
/// }
/// ```
#[macro_export]
macro_rules! build_rs_lookup_table_file_generation {
    ($function_name:ident, $checksum_function:ident, $lookup_table_file:expr, $t:ty, $size:expr) => {
        fn $function_name() -> std::io::Result<()> {
            use std::io::prelude::*;
            let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
            let out_path = out_path.join($lookup_table_file);
            let mut file = std::fs::File::create(out_path)?;
            file.write_all(
                concat!(
                    "const LOOKUP_TABLE: [",
                    stringify!($t),
                    ";",
                    stringify!($size),
                    "] = [\n"
                )
                .as_bytes(),
            )?;
            for i in 0..$size {
                if i % 16 == 0 {
                    file.write_all(b"    ")?;
                }
                file.write_all(format!("0x{:x}, ", $checksum_function(&[i as $t])).as_bytes())?;
                if i > 0 && (i + 1) % 16 == 0 {
                    file.write_all(b"\n")?;
                }
            }
            file.write_all(b"];\n")
        }
    };
}