Field Guide · algorithm

Also known as: CRC-16/CCITT, CRC-CCITT, CRC-16 0x1021

CRC-16/CCITT is not one checksum but a family of them — every member built on the same 16-bit generator polynomial 0x1021 (x¹⁶ + x¹² + x⁵ + 1).1 What makes two members produce different check values on the same bytes is not the polynomial but three surrounding parameters: the register’s initial value, whether input and output bits are reflected, and a final XOR applied to the result. GopherTrunk reuses this one polynomial under several parameter sets across its protocols, which is why getting the parameters right matters more than the polynomial ever does.2

init value 16-bit register XOR 0x1021 on overflow feedback final XOR CRC same polynomial 0x1021 · different init / reflection / final XOR → different check value
Every CRC-16/CCITT variant runs the same 0x1021 feedback register; the initial value, bit reflection and final XOR are what separate CCITT/FALSE from XMODEM from the augmented form.

How it works

A CRC treats the message as a big binary polynomial and returns the remainder after dividing it by the generator — computed with a 16-bit shift register that, on each step, shifts a message bit in and XORs 0x1021 back whenever the register overflows the top bit. It is a cyclic redundancy check in the textbook sense; the “CCITT” label just fixes the polynomial. GopherTrunk’s CRCCCITTWithInit is the parametric core — MSB-first, no reflection — with the initial value passed in, and the reflected and augmented forms are separate functions with their own bit order.

The variants GopherTrunk uses

The same 0x1021 appears under these parameter sets. The check value is the CRC of the ASCII string "123456789", the standard reference vector:

Variant Init Reflect Final XOR Check Used by
CCITT/FALSE 0xFFFF no 0x0000 0x29B1 P25 TSBK (stored complemented)
XMODEM 0x0000 no 0x0000 0x31C3 YSF FICH, M17, LoRa payload
Reflected 0x0000 in/out 0xFFFF MDC1200
Augmented 0x0000 no 0xFFFF P25 TSBK trailer

The MDC1200 reflected form runs the polynomial in its bit-reversed representation 0x8408; the LoRa payload CRC is the plain XMODEM member.

Init, reflection and final XOR

Each parameter changes the result independently:

  • Initial value. Seeding the register with 0xFFFF instead of 0x0000 makes the CRC sensitive to leading zero bytes — a run of zeros at the start of a message no longer leaves the register at zero, so prepended zeros are detected.
  • Reflection. Reflecting input and output bits processes each byte LSB-first, matching how UART/serial hardware clocks bits out. It changes the value but not the error-detection strength; it is a convention, chosen to match the wire.
  • Final XOR. XORing the register with 0xFFFF at the end guards against trailing zero bytes the same way a non-zero init guards against leading ones.

Two implementations sharing the polynomial but differing in any of these will disagree on every input — which is exactly the failure mode below.

The augmented variant and issue #275

The P25 TSBK trailer uses the augmented-message form, which is a genuinely different algorithm, not just different parameters: message bits are shifted into the low end of the register and the polynomial is XORed when the register grows past 16 bits, with a final XOR of 0xFFFF. The encoder computes the trailer as the CRC of info ‖ 16 zero bits; a receiver checks that the CRC of info ‖ trailer is zero. GopherTrunk’s original P25 code used the CCITT/FALSE function here, and every on-air TSBK on the Mt Anakie capture failed verification even when the trellis decoder reported a clean path — issue #275. Switching to CRCCCITTAugmented made the on-air trailers verify. The lesson is the recurring one with this family: the polynomial is the easy part; the init, shift direction and final XOR are where a decoder silently gets it wrong.

Sources

  1. Cyclic redundancy check — Wikipedia, on CRC computation, generator polynomials and the init/reflect/xorout parameters. 

  2. CRC catalogue (16-bit) — Greg Cook’s catalogue, the reference for CRC-16 parameter sets and their check values. 

See also