Also known as: RS(12,9), RS129, Reed-Solomon (12,9,4)
DMR RS(12,9) is the Reed–Solomon(12,9,4) code over GF(2^8) that DMR layers on top of BPTC(196,96) to protect Full Link Control.1 BPTC reconstructs 96 information bits — nine octets of FLC plus three octets of RS parity — and the RS check tells the receiver whether that reconstruction was actually correct, catching the systematic mis-decodes that BPTC’s own success flag does not.2 The wrinkle that trips up every first implementation is that DMR XOR-masks the three parity octets with a context-specific seed before transmission, so the verifier has to un-XOR before it can compute syndromes.
The field and generator
GopherTrunk builds the code in internal/radio/framing/rs12_9.go, matching the HBLink rs129
reference. The field is GF(2^8) generated by the primitive polynomial
p(x) = x^8 + x^4 + x^3 + x^2 + 1 (0x11D, low byte 0x1D), with primitive element α = 2. The
generator uses the first-consecutive-root DMR convention:
g(x) = (x + α¹)(x + α²)(x + α³) = x³ + 14·x² + 56·x + 64
so a valid codeword satisfies c(α¹) = c(α²) = c(α³) = 0. VerifyRS12_9 evaluates the three
syndromes by forward Horner over the 12 octets and returns true only when all three are zero.
The context seeds
The one constant a table cannot convey compactly is the per-context XOR seed applied to the parity octets at transmit time — the verifier must un-XOR them before computing syndromes, and using the wrong seed rejects every otherwise-valid codeword:
// DMR-specific seeds XOR'd into the 3 parity octets before transmission.
// VerifyRS12_9 un-XORs them before computing syndromes.
var (
RS129SeedVoiceLCHeader = [3]byte{0x96, 0x96, 0x96}
RS129SeedTerminatorLC = [3]byte{0x99, 0x99, 0x99}
RS129SeedEmbeddedLC = [3]byte{0x6A, 0x6A, 0x6A}
RS129SeedNone = [3]byte{0x00, 0x00, 0x00}
)
Each of the three FLC-bearing contexts — the Voice LC Header, the Terminator with LC, and the
embedded LC — masks all three parity octets with its own byte, so
the seed doubles as a lightweight tag: a codeword only verifies against the seed of the context it
came from. VerifyRS12_9 copies the codeword, un-XORs the parity with the supplied seed, and then
checks the syndromes, leaving the caller’s buffer untouched.
Impl-gap: verify only
This pass implements verification only. The code’s nominal single-error correction (t = 1) is a
documented follow-up; GopherTrunk leans on the strong BPTC / embedded-BPTC layer
for correction over the same bits and uses RS(12,9) as an independent check that catches BPTC
mis-decodes the CSBK CRC-16 does not cover. EncodeRS12_9 is present (for
synthetic streams and round-trip tests) and emits the systematic 12-octet codeword without the
seed, which callers XOR in afterward to match on-air framing.
Relevance to SDR
RS(12,9) is the belt to BPTC’s braces on the most valuable DMR PDU — the Full Link Control that names a call’s talkgroup and source radio. Because BPTC can occasionally correct into a valid but wrong codeword on a marginal signal, an independent RS parity check over the recovered octets is how GopherTrunk can gain confidence that a decoded FLC is genuine rather than a plausible-looking artefact, and the seed discipline keeps a Voice LC Header from being mistaken for a Terminator or an embedded block. Getting the field polynomial, root convention, and the three seeds exactly right is what makes that check trustworthy.
Sources
-
Reed–Solomon error correction — Wikipedia, on the code family RS(12,9,4) belongs to. ↩
-
Finite field — Wikipedia, on the GF(2^8) arithmetic the code is defined over. ↩