[fix](trx-ft8): add hard-error verification to FT2 OSD decoder
The OSD-lite decoder was the source of FT2 false positives. It tries 685 CRC-14 checks across 5 passes (1 + 16 + 120 per pass), giving a ~4% chance of accepting random noise as a valid decode. The reference implementation (decode174_91) verifies OSD results against the received signal; the trx-rs OSD-lite only checked CRC. Add ft2_count_hard_errors_vs_llr() which counts how many of the 174 coded bits in an OSD candidate disagree with the received hard decisions. A legitimate correction disagrees in very few positions; a false CRC match on noise disagrees in ~40-50 parity positions. Reject OSD results with more than 36 hard errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
@@ -980,11 +980,40 @@ static void ft2_encode_codeword_from_a91(const uint8_t a91[FTX_LDPC_K_BYTES], ui
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool ft2_try_crc_candidate(const uint8_t a91[FTX_LDPC_K_BYTES], ftx_message_t* message)
|
// Count how many of the 174 coded bits in a candidate codeword disagree
|
||||||
|
// with the received hard decisions (sign of LLRs). A legitimate OSD
|
||||||
|
// correction should disagree in very few positions; a false CRC match on
|
||||||
|
// noise will disagree in ~half of the 83 parity positions.
|
||||||
|
static int ft2_count_hard_errors_vs_llr(const float log174[FTX_LDPC_N], const uint8_t codeword[FTX_LDPC_N])
|
||||||
|
{
|
||||||
|
int errors = 0;
|
||||||
|
for (int i = 0; i < FTX_LDPC_N; ++i)
|
||||||
|
{
|
||||||
|
uint8_t received = (log174[i] >= 0.0f) ? 1 : 0;
|
||||||
|
if (received != codeword[i])
|
||||||
|
++errors;
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maximum hard-error count for accepting an OSD result.
|
||||||
|
// The (174,91) code has minimum distance ~20, so a legitimate near-threshold
|
||||||
|
// decode should disagree in far fewer than 36 positions. Random CRC matches
|
||||||
|
// typically disagree in ~40-50 parity positions alone.
|
||||||
|
#define FT2_OSD_MAX_HARD_ERRORS 36
|
||||||
|
|
||||||
|
static bool ft2_try_crc_candidate(const uint8_t a91[FTX_LDPC_K_BYTES],
|
||||||
|
const float log174[FTX_LDPC_N],
|
||||||
|
ftx_message_t* message)
|
||||||
{
|
{
|
||||||
uint8_t codeword[FTX_LDPC_N];
|
uint8_t codeword[FTX_LDPC_N];
|
||||||
ft2_encode_codeword_from_a91(a91, codeword);
|
ft2_encode_codeword_from_a91(a91, codeword);
|
||||||
return ft2_unpack_message(codeword, message);
|
if (!ft2_unpack_message(codeword, message))
|
||||||
|
return false;
|
||||||
|
// Verify the codeword is consistent with what we actually received.
|
||||||
|
if (log174 && ft2_count_hard_errors_vs_llr(log174, codeword) > FT2_OSD_MAX_HARD_ERRORS)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool ft2_osd_lite_decode(const float log174[FTX_LDPC_N], ftx_message_t* message)
|
static bool ft2_osd_lite_decode(const float log174[FTX_LDPC_N], ftx_message_t* message)
|
||||||
@@ -997,7 +1026,7 @@ static bool ft2_osd_lite_decode(const float log174[FTX_LDPC_N], ftx_message_t* m
|
|||||||
base_a91[i / 8] |= (uint8_t)(0x80u >> (i % 8));
|
base_a91[i / 8] |= (uint8_t)(0x80u >> (i % 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ft2_try_crc_candidate(base_a91, message))
|
if (ft2_try_crc_candidate(base_a91, log174, message))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
ft2_reliability_t reliabilities[FTX_LDPC_K];
|
ft2_reliability_t reliabilities[FTX_LDPC_K];
|
||||||
@@ -1018,7 +1047,7 @@ static bool ft2_osd_lite_decode(const float log174[FTX_LDPC_N], ftx_message_t* m
|
|||||||
memcpy(trial_a91, base_a91, sizeof(trial_a91));
|
memcpy(trial_a91, base_a91, sizeof(trial_a91));
|
||||||
int b0 = reliabilities[i].index;
|
int b0 = reliabilities[i].index;
|
||||||
trial_a91[b0 / 8] ^= (uint8_t)(0x80u >> (b0 % 8));
|
trial_a91[b0 / 8] ^= (uint8_t)(0x80u >> (b0 % 8));
|
||||||
if (ft2_try_crc_candidate(trial_a91, message))
|
if (ft2_try_crc_candidate(trial_a91, log174, message))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1032,7 +1061,7 @@ static bool ft2_osd_lite_decode(const float log174[FTX_LDPC_N], ftx_message_t* m
|
|||||||
int b1 = reliabilities[j].index;
|
int b1 = reliabilities[j].index;
|
||||||
trial_a91[b0 / 8] ^= (uint8_t)(0x80u >> (b0 % 8));
|
trial_a91[b0 / 8] ^= (uint8_t)(0x80u >> (b0 % 8));
|
||||||
trial_a91[b1 / 8] ^= (uint8_t)(0x80u >> (b1 % 8));
|
trial_a91[b1 / 8] ^= (uint8_t)(0x80u >> (b1 % 8));
|
||||||
if (ft2_try_crc_candidate(trial_a91, message))
|
if (ft2_try_crc_candidate(trial_a91, log174, message))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user