ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
crc.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file crc.h
6 * @brief Parameterized CRC engine - one source of truth for every cyclic redundancy check.
7 *
8 * Sixteen codecs in this tree each hand-rolled their own CRC loop under a different local name,
9 * differing only in a polynomial and a couple of flags - the same duplication `endian.h` was written
10 * to end for byte packing. This is that one implementation, and thirteen of them now call it
11 * (C37.118, DF1, DNP3, EnOcean, INTERBUS, Modbus, Modbus Plus, NEMA TS2, raw L2, SDI-12, SHT3x,
12 * Thread, Zigbee). The three that do not are excluded on purpose, not overlooked: the WAL is
13 * table-driven for bulk log throughput, RTCM3's CRC-24Q seed has no preset here yet, and DShot's
14 * "CRC" is a 4-bit XOR fold rather than a CRC at all. See the ROADMAP migration entry.
15 *
16 * It is the standard Rocksoft / Williams model, so any published CRC is expressible as six numbers
17 * and needs no new code:
18 *
19 * - @ref pc_crc_params::width register width in bits (8..32)
20 * - @ref pc_crc_params::poly generator polynomial, normal form, implicit top bit dropped
21 * - @ref pc_crc_params::init initial register value
22 * - @ref pc_crc_params::refin reflect each input octet
23 * - @ref pc_crc_params::refout reflect the final register
24 * - @ref pc_crc_params::xorout final XOR
25 *
26 * Every preset below carries its cataloge **check value** - the CRC of the nine ASCII octets
27 * `"123456789"` - and `test_crc` asserts each one: a wrong polynomial or a flipped reflect flag
28 * cannot reproduce a published check value by accident. `test_crc` then diffs the engine against the
29 * hand-rolled loops themselves, across every length from 0 to 64, so a preset meant to replace one
30 * of them is proven byte-identical to the interop-tested code it retires - not merely plausible.
31 *
32 * Bitwise, not table-driven: a 256-entry table per polynomial would cost more flash than the frames
33 * are worth on this class of device, and every caller here checksums tens to hundreds of octets, not
34 * megabytes. Header-only and pure (`<stddef.h>` / `<stdint.h>` only), so it is host-testable,
35 * identical on device and host, and costs nothing when unused - the same contract as `endian.h`.
36 *
37 * @author Douglas Quigg (dstroy0)
38 * @date 2026
39 */
40
41#ifndef PROTOCORE_CRC_H
42#define PROTOCORE_CRC_H
43
44#include <stddef.h>
45#include <stdint.h>
46
47/** @brief One CRC's full definition (Rocksoft model). See the file comment. */
49{
50 uint8_t width; ///< register width in bits, 8..32.
51 uint32_t poly; ///< generator polynomial, normal form (implicit top bit dropped).
52 uint32_t init; ///< initial register value.
53 bool refin; ///< reflect each input octet before feeding it in.
54 bool refout; ///< reflect the final register before the XOR.
55 uint32_t xorout; ///< XORed into the final register.
56};
57
59{
60/** @brief Mask of @p width low bits (width 32 handled without a 32-bit shift, which is UB). */
61inline uint32_t mask(uint8_t width)
62{
63 return (width >= 32) ? 0xFFFFFFFFu : ((1u << width) - 1u);
64}
65
66/** @brief Clamp a register width to the supported 8..32 range. */
67inline uint8_t clamp_width(uint8_t width)
68{
69 if (width < 8)
70 {
71 return 8;
72 }
73 if (width > 32)
74 {
75 return 32;
76 }
77 return width;
78}
79
80/** @brief Reverse the 8 bits of @p b. */
81inline uint8_t rev8(uint8_t b)
82{
83 b = (uint8_t)(((b & 0xF0u) >> 4) | ((b & 0x0Fu) << 4));
84 b = (uint8_t)(((b & 0xCCu) >> 2) | ((b & 0x33u) << 2));
85 b = (uint8_t)(((b & 0xAAu) >> 1) | ((b & 0x55u) << 1));
86 return b;
87}
88
89/** @brief Reverse the low @p width bits of @p v. */
90inline uint32_t revN(uint32_t v, uint8_t width)
91{
92 uint32_t r = 0;
93 for (uint8_t i = 0; i < width; i++)
94 {
95 r = (r << 1) | (v & 1u);
96 v >>= 1;
97 }
98 return r;
99}
100} // namespace pc_crc_detail
101
102/**
103 * @brief Start a CRC. @return the initial register value.
104 *
105 * Split from @ref pc_crc_update / @ref pc_crc_final so a caller can checksum a frame that is not
106 * contiguous in memory (a header struct then a payload buffer) without copying it together first.
107 */
108inline uint32_t pc_crc_begin(const pc_crc_params *p)
109{
110 return p ? (p->init & pc_crc_detail::mask(p->width)) : 0u;
111}
112
113/**
114 * @brief Fold @p len octets at @p data into the running register @p crc.
115 *
116 * Reflection of the input is applied per octet here; reflection of the *output* belongs to
117 * @ref pc_crc_final, so intermediate values from this function are not meaningful CRCs on their own.
118 */
119inline uint32_t pc_crc_update(const pc_crc_params *p, uint32_t crc, const uint8_t *data, size_t len)
120{
121 if (!p || (!data && len))
122 {
123 return crc;
124 }
125 const uint8_t width = pc_crc_detail::clamp_width(p->width);
126 const uint32_t m = pc_crc_detail::mask(width);
127 const uint32_t top = 1u << (width - 1);
128
129 for (size_t i = 0; i < len; i++)
130 {
131 uint8_t b = p->refin ? pc_crc_detail::rev8(data[i]) : data[i];
132 crc ^= ((uint32_t)b) << (width - 8);
133 for (int k = 0; k < 8; k++)
134 {
135 crc = (crc & top) ? (((crc << 1) ^ p->poly) & m) : ((crc << 1) & m);
136 }
137 }
138 return crc & m;
139}
140
141/** @brief Finish a CRC: apply the output reflection and the final XOR. */
142inline uint32_t pc_crc_final(const pc_crc_params *p, uint32_t crc)
143{
144 if (!p)
145 {
146 return 0u;
147 }
148 const uint8_t width = pc_crc_detail::clamp_width(p->width);
149 const uint32_t m = pc_crc_detail::mask(width);
150 if (p->refout)
151 {
152 crc = pc_crc_detail::revN(crc & m, width);
153 }
154 return (crc ^ p->xorout) & m;
155}
156
157/** @brief One-shot CRC of @p len octets at @p data. */
158inline uint32_t pc_crc(const pc_crc_params *p, const uint8_t *data, size_t len)
159{
160 return pc_crc_final(p, pc_crc_update(p, pc_crc_begin(p), data, len));
161}
162
163// --- Cataloge presets -------------------------------------------------------------------------
164// Each carries its published check value: the CRC of the ASCII octets "123456789". test_crc asserts
165// every one of them, so an incorrect parameter here fails the suite rather than corrupting a codec.
166//
167// `constexpr`, deliberately not `inline constexpr`: this tree builds at -std=c++14 and inline
168// variables are C++17. At namespace scope constexpr implies const, which implies internal linkage,
169// so every translation unit gets its own copy - no ODR violation, no linker error, and a preset no
170// TU references is never emitted at all.
171
172/** @brief CRC-8/SMBUS (a.k.a. CRC-8). check = 0xF4. */
173constexpr pc_crc_params PC_CRC8_SMBUS = {8, 0x07u, 0x00u, false, false, 0x00u};
174/** @brief CRC-8/MAXIM-DOW (1-Wire / Dallas). check = 0xA1. */
175constexpr pc_crc_params PC_CRC8_MAXIM_DOW = {8, 0x31u, 0x00u, true, true, 0x00u};
176/** @brief CRC-8/NRSC-5 - the Sensirion sensor CRC. check = 0xF7. Used by services/sht3x. */
177constexpr pc_crc_params PC_CRC8_NRSC5 = {8, 0x31u, 0xFFu, false, false, 0x00u};
178
179/** @brief CRC-16/ARC (a.k.a. CRC-16, IBM). check = 0xBB3D. */
180constexpr pc_crc_params PC_CRC16_ARC = {16, 0x8005u, 0x0000u, true, true, 0x0000u};
181/** @brief CRC-16/MODBUS. check = 0x4B37. */
182constexpr pc_crc_params PC_CRC16_MODBUS = {16, 0x8005u, 0xFFFFu, true, true, 0x0000u};
183/** @brief CRC-16/IBM-3740 (often called CCITT-FALSE). check = 0x29B1. */
184constexpr pc_crc_params PC_CRC16_IBM_3740 = {16, 0x1021u, 0xFFFFu, false, false, 0x0000u};
185/** @brief CRC-16/XMODEM. check = 0x31C3. */
186constexpr pc_crc_params PC_CRC16_XMODEM = {16, 0x1021u, 0x0000u, false, false, 0x0000u};
187/** @brief CRC-16/KERMIT (a.k.a. CRC-16/CCITT, reflected). check = 0x2189. */
188constexpr pc_crc_params PC_CRC16_KERMIT = {16, 0x1021u, 0x0000u, true, true, 0x0000u};
189/** @brief CRC-16/X-25 (HDLC FCS). check = 0x906E. Used by services/radio/thread, mbplus, nema_ts2. */
190constexpr pc_crc_params PC_CRC16_X25 = {16, 0x1021u, 0xFFFFu, true, true, 0xFFFFu};
191/** @brief CRC-16/DNP (DNP3 link-layer block check). check = 0xEA82. Used by services/dnp3. */
192constexpr pc_crc_params PC_CRC16_DNP = {16, 0x3D65u, 0x0000u, true, true, 0xFFFFu};
193
194/** @brief CRC-24/OPENPGP. check = 0x21CF02. */
195constexpr pc_crc_params PC_CRC24_OPENPGP = {24, 0x864CFBu, 0xB704CEu, false, false, 0x000000u};
196
197/** @brief CRC-32/ISO-HDLC (zlib / PKZIP / Ethernet). check = 0xCBF43926. */
198constexpr pc_crc_params PC_CRC32_ISO_HDLC = {32, 0x04C11DB7u, 0xFFFFFFFFu, true, true, 0xFFFFFFFFu};
199/** @brief CRC-32/BZIP2 (unreflected CRC-32). check = 0xFC891918. */
200constexpr pc_crc_params PC_CRC32_BZIP2 = {32, 0x04C11DB7u, 0xFFFFFFFFu, false, false, 0xFFFFFFFFu};
201
202#endif // PROTOCORE_CRC_H
constexpr pc_crc_params PC_CRC16_X25
CRC-16/X-25 (HDLC FCS). check = 0x906E. Used by services/radio/thread, mbplus, nema_ts2.
Definition crc.h:190
constexpr pc_crc_params PC_CRC16_ARC
CRC-16/ARC (a.k.a. CRC-16, IBM). check = 0xBB3D.
Definition crc.h:180
constexpr pc_crc_params PC_CRC16_DNP
CRC-16/DNP (DNP3 link-layer block check). check = 0xEA82. Used by services/dnp3.
Definition crc.h:192
uint32_t pc_crc_final(const pc_crc_params *p, uint32_t crc)
Finish a CRC: apply the output reflection and the final XOR.
Definition crc.h:142
constexpr pc_crc_params PC_CRC32_BZIP2
CRC-32/BZIP2 (unreflected CRC-32). check = 0xFC891918.
Definition crc.h:200
uint32_t pc_crc_begin(const pc_crc_params *p)
Start a CRC.
Definition crc.h:108
constexpr pc_crc_params PC_CRC32_ISO_HDLC
CRC-32/ISO-HDLC (zlib / PKZIP / Ethernet). check = 0xCBF43926.
Definition crc.h:198
constexpr pc_crc_params PC_CRC16_KERMIT
CRC-16/KERMIT (a.k.a. CRC-16/CCITT, reflected). check = 0x2189.
Definition crc.h:188
constexpr pc_crc_params PC_CRC16_MODBUS
CRC-16/MODBUS. check = 0x4B37.
Definition crc.h:182
constexpr pc_crc_params PC_CRC16_XMODEM
CRC-16/XMODEM. check = 0x31C3.
Definition crc.h:186
constexpr pc_crc_params PC_CRC8_NRSC5
CRC-8/NRSC-5 - the Sensirion sensor CRC. check = 0xF7. Used by services/sht3x.
Definition crc.h:177
constexpr pc_crc_params PC_CRC8_SMBUS
CRC-8/SMBUS (a.k.a. CRC-8). check = 0xF4.
Definition crc.h:173
constexpr pc_crc_params PC_CRC16_IBM_3740
CRC-16/IBM-3740 (often called CCITT-FALSE). check = 0x29B1.
Definition crc.h:184
constexpr pc_crc_params PC_CRC8_MAXIM_DOW
CRC-8/MAXIM-DOW (1-Wire / Dallas). check = 0xA1.
Definition crc.h:175
uint32_t pc_crc(const pc_crc_params *p, const uint8_t *data, size_t len)
One-shot CRC of len octets at data.
Definition crc.h:158
uint32_t pc_crc_update(const pc_crc_params *p, uint32_t crc, const uint8_t *data, size_t len)
Fold len octets at data into the running register crc.
Definition crc.h:119
constexpr pc_crc_params PC_CRC24_OPENPGP
CRC-24/OPENPGP. check = 0x21CF02.
Definition crc.h:195
uint32_t revN(uint32_t v, uint8_t width)
Reverse the low width bits of v.
Definition crc.h:90
uint8_t rev8(uint8_t b)
Reverse the 8 bits of b.
Definition crc.h:81
uint32_t mask(uint8_t width)
Mask of width low bits (width 32 handled without a 32-bit shift, which is UB).
Definition crc.h:61
uint8_t clamp_width(uint8_t width)
Clamp a register width to the supported 8..32 range.
Definition crc.h:67
One CRC's full definition (Rocksoft model). See the file comment.
Definition crc.h:49
bool refin
reflect each input octet before feeding it in.
Definition crc.h:53
uint32_t xorout
XORed into the final register.
Definition crc.h:55
uint8_t width
register width in bits, 8..32.
Definition crc.h:50
uint32_t poly
generator polynomial, normal form (implicit top bit dropped).
Definition crc.h:51
bool refout
reflect the final register before the XOR.
Definition crc.h:54
uint32_t init
initial register value.
Definition crc.h:52