Skip to content

fix: validate config CRC with CRC-16/CCITT (advisory) to match toolbox/nRF/Silabs#83

Open
balloob wants to merge 1 commit into
OpenDisplay:mainfrom
balloob:fix/config-crc-ccitt
Open

fix: validate config CRC with CRC-16/CCITT (advisory) to match toolbox/nRF/Silabs#83
balloob wants to merge 1 commit into
OpenDisplay:mainfrom
balloob:fix/config-crc-ccitt

Conversation

@balloob

@balloob balloob commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Problem

The on-wire toolbox-outer config container carries a trailing 2-byte CRC. Across the ecosystem this was validated three different ways. The website/toolbox (which configured most deployed displays), the nRF firmware, and the Silabs firmware all use the canonical CRC-16/CCITT-FALSE over the container body with the first two (length) bytes forced to zero. This Arduino firmware was the outlier: it validated with the low 16 bits of a CRC32.

Decision

Standardize the on-wire config-container CRC on CRC-16/CCITT, and keep it advisory (NOT enforced) — validation stays warn-only, exactly as it is today: it logs a warning on mismatch and continues parsing. CCITT was chosen because most deployed displays were configured by the website/toolbox, which writes CCITT, and the nRF/Silabs firmware and toolbox already use the canonical CCITT.

Fix

  • Added a static config_toolbox_outer_crc16(const uint8_t*, uint32_t) helper in src/config_parser.cpp that mirrors the nRF/Silabs implementation byte-for-byte: init 0xFFFF, poly 0x1021, MSB-first, no reflection, no final XOR, over [byte0, byte1, version, ...packets] with the first two (length) bytes forced to zero, excluding the trailing 2 CRC bytes. Built on a crc16_ccitt_feed primitive, matching the reference tree.
  • Replaced the calculateConfigCRC(...) & 0xFFFF computation at the on-wire validation site with a call to the new helper.
  • Behavior stays warn-only: a mismatch logs WARNING: Config CRC mismatch (...) and parsing continues. Nothing is rejected.

Scope note — the internal storage CRC32 is deliberately untouched

There are two distinct CRCs in this repo. Only the on-wire one changed. The internal flash-storage integrity CRC (calculateConfigCRC, CRC32) used for the config_storage_t wrapper on save/load never leaves the device and is unrelated to the on-wire config — it is left as CRC32, along with both of its call sites.

Testing

  • Verified the helper against ground-truth vectors (body = container without the trailing 2 CRC bytes; helper zeroes bytes 0-1 internally):
    • 00 00 010xDCBD
    • 2A 00 01 AA BB0xC239
    • [len_lo len_hi] 01 00 01 + fourteen 000x0D7F for any length-byte values (proves zeroing).
  • Builds green: pio run -e nrf52840custom (SUCCESS) and pio run -e esp32-c3-N4 (SUCCESS).

Advisory, not enforced

This does not add any rejection path. A CRC mismatch remains a logged warning only; config parsing proceeds regardless.


Part of the config-CRC CCITT standardization (MJ-7); companion changes in OpenDisplay/py-opendisplay and OpenDisplay/opendisplay.org. Cross-links to be added.

🤖 Generated with Claude Code

https://claude.ai/code/session_012g2e8mr132vcizx92WsgiR


Related PRs — config-CRC CCITT standardization (finding MJ-7):

Already canonical (no change): toolbox writer, nRF firmware, Silabs firmware. opendisplay-js (TS) intentionally not updated (abandoned).

…x/nRF/Silabs

The on-wire toolbox-outer config container carries a trailing 2-byte CRC that
was validated three different ways across the ecosystem. The website/toolbox
(which configured most deployed displays), the nRF firmware and the Silabs
firmware all use the canonical CRC-16/CCITT-FALSE over the container body with
the first two (length) bytes forced to zero. This Arduino firmware was the
outlier, validating with the low 16 bits of a CRC32. The team decided to
standardize on CRC-16/CCITT.

This moves the on-wire validation onto a new static config_toolbox_outer_crc16
helper that mirrors the nRF/Silabs implementation byte-for-byte (init 0xFFFF,
poly 0x1021, MSB-first, no reflection, no final XOR, length bytes zeroed).

The check stays advisory / warn-only exactly as before: a mismatch logs a
WARNING and parsing continues; nothing is rejected.

The unrelated internal flash-storage integrity CRC (calculateConfigCRC, CRC32)
for the config_storage_t wrapper is deliberately left untouched -- it never
leaves the device and its save/load call sites still use CRC32.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012g2e8mr132vcizx92WsgiR
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant