[fix](trx-ftx): fix infinite loop in callsign hash table when full
add() and lookup() had no wrap-around guard in their linear-probe loops. Once 256 unique callsigns filled the table, any subsequent add or lookup for an absent hash would cycle through all 256 slots forever, hanging the FT8 decoder task permanently inside block_in_place. On a busy band this could happen within a few minutes of operation. - add(): evict the probe-start slot when a full cycle completes - lookup(): return None after a full probe cycle - reset(): call cleanup(10) each slot boundary to age out stale entries - Add regression tests for both infinite-loop scenarios Also includes cargo fmt reformatting of pre-existing style issues. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -106,10 +106,12 @@ impl CallsignHashTable {
|
||||
/// The `hash` parameter is the full 22-bit hash value. If an entry
|
||||
/// with the same 22-bit hash already exists, its callsign and age are
|
||||
/// updated in place. Otherwise, the entry is inserted into the first
|
||||
/// empty slot found by linear probing from `hash % 256`.
|
||||
/// empty slot found by linear probing from `hash % 256`. If the table
|
||||
/// is full, the probe-start slot is evicted to make room.
|
||||
pub fn add(&mut self, callsign: &str, hash: u32) {
|
||||
let hash22 = hash & HASH22_MASK;
|
||||
let mut idx = (hash22 as usize) % CALLSIGN_HASHTABLE_SIZE;
|
||||
let start_idx = (hash22 as usize) % CALLSIGN_HASHTABLE_SIZE;
|
||||
let mut idx = start_idx;
|
||||
|
||||
loop {
|
||||
match &self.entries[idx] {
|
||||
@@ -124,6 +126,14 @@ impl CallsignHashTable {
|
||||
Some(_) => {
|
||||
// Collision — linear probe to next slot.
|
||||
idx = (idx + 1) % CALLSIGN_HASHTABLE_SIZE;
|
||||
if idx == start_idx {
|
||||
// Table is full; evict the start slot.
|
||||
self.entries[idx] = Some(CallsignEntry {
|
||||
hash: hash22,
|
||||
callsign: callsign.to_string(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// Empty slot — insert here.
|
||||
@@ -142,12 +152,12 @@ impl CallsignHashTable {
|
||||
/// determine which bits to compare.
|
||||
///
|
||||
/// Returns `Some(callsign)` if a matching entry is found, or `None`
|
||||
/// if the probe sequence reaches an empty slot without finding a
|
||||
/// match.
|
||||
/// if no match is found within a full probe cycle.
|
||||
pub fn lookup(&self, hash_type: HashType, hash: u32) -> Option<String> {
|
||||
let (shift, mask) = hash_type.shift_and_mask();
|
||||
let target = hash & mask;
|
||||
let mut idx = (hash as usize) % CALLSIGN_HASHTABLE_SIZE;
|
||||
let start_idx = (hash as usize) % CALLSIGN_HASHTABLE_SIZE;
|
||||
let mut idx = start_idx;
|
||||
|
||||
loop {
|
||||
match &self.entries[idx] {
|
||||
@@ -157,6 +167,9 @@ impl CallsignHashTable {
|
||||
return Some(entry.callsign.clone());
|
||||
}
|
||||
idx = (idx + 1) % CALLSIGN_HASHTABLE_SIZE;
|
||||
if idx == start_idx {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
None => return None,
|
||||
}
|
||||
@@ -391,6 +404,38 @@ mod tests {
|
||||
assert!(hash <= 0x3F_FFFF, "hash should fit in 22 bits");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_full_table_does_not_hang() {
|
||||
// Fill the table to capacity with distinct hashes, then add one more.
|
||||
// This must terminate (no infinite loop) and must not panic.
|
||||
let mut table = CallsignHashTable::new();
|
||||
for i in 0..CALLSIGN_HASHTABLE_SIZE {
|
||||
table.entries[i] = Some(CallsignEntry {
|
||||
hash: i as u32,
|
||||
callsign: format!("C{}", i),
|
||||
});
|
||||
}
|
||||
table.size = CALLSIGN_HASHTABLE_SIZE;
|
||||
// This hash won't match any existing entry — must not infinite-loop.
|
||||
table.add("W1AW", 0x3F_FFFF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_full_table_does_not_hang() {
|
||||
// Fill the table with entries that won't match the target, then look
|
||||
// up a hash that is absent. Must return None without looping forever.
|
||||
let mut table = CallsignHashTable::new();
|
||||
for i in 0..CALLSIGN_HASHTABLE_SIZE {
|
||||
table.entries[i] = Some(CallsignEntry {
|
||||
hash: i as u32,
|
||||
callsign: format!("C{}", i),
|
||||
});
|
||||
}
|
||||
table.size = CALLSIGN_HASHTABLE_SIZE;
|
||||
let result = table.lookup(HashType::Hash22Bits, 0x3F_FFFF);
|
||||
assert!(result.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compute_hash_invalid_char_returns_none() {
|
||||
// Lowercase letters are not in the AlphanumSpaceSlash table.
|
||||
|
||||
@@ -8,12 +8,8 @@ use crate::protocol::{FTX_LDPC_K_BYTES, FTX_LDPC_M, FTX_LDPC_N};
|
||||
pub const FT8_COSTAS_PATTERN: [u8; 7] = [3, 1, 4, 0, 6, 5, 2];
|
||||
|
||||
/// Costas sync tone patterns for FT4 (4 groups of 4 tones).
|
||||
pub const FT4_COSTAS_PATTERN: [[u8; 4]; 4] = [
|
||||
[0, 1, 3, 2],
|
||||
[1, 0, 2, 3],
|
||||
[2, 3, 1, 0],
|
||||
[3, 2, 0, 1],
|
||||
];
|
||||
pub const FT4_COSTAS_PATTERN: [[u8; 4]; 4] =
|
||||
[[0, 1, 3, 2], [1, 0, 2, 3], [2, 3, 1, 0], [3, 2, 0, 1]];
|
||||
|
||||
/// Gray code map for FT8 (8 symbols, 3 bits).
|
||||
pub const FT8_GRAY_MAP: [u8; 8] = [0, 1, 3, 2, 5, 6, 4, 7];
|
||||
@@ -22,95 +18,259 @@ pub const FT8_GRAY_MAP: [u8; 8] = [0, 1, 3, 2, 5, 6, 4, 7];
|
||||
pub const FT4_GRAY_MAP: [u8; 4] = [0, 1, 3, 2];
|
||||
|
||||
/// XOR sequence for FT4 encoding (prevents long zero runs on CQ).
|
||||
pub const FT4_XOR_SEQUENCE: [u8; 10] = [
|
||||
0x4A, 0x5E, 0x89, 0xB4, 0xB0, 0x8A, 0x79, 0x55, 0xBE, 0x28,
|
||||
];
|
||||
pub const FT4_XOR_SEQUENCE: [u8; 10] = [0x4A, 0x5E, 0x89, 0xB4, 0xB0, 0x8A, 0x79, 0x55, 0xBE, 0x28];
|
||||
|
||||
/// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first).
|
||||
pub const FTX_LDPC_GENERATOR: [[u8; FTX_LDPC_K_BYTES]; FTX_LDPC_M] = [
|
||||
[0x83, 0x29, 0xce, 0x11, 0xbf, 0x31, 0xea, 0xf5, 0x09, 0xf2, 0x7f, 0xc0],
|
||||
[0x76, 0x1c, 0x26, 0x4e, 0x25, 0xc2, 0x59, 0x33, 0x54, 0x93, 0x13, 0x20],
|
||||
[0xdc, 0x26, 0x59, 0x02, 0xfb, 0x27, 0x7c, 0x64, 0x10, 0xa1, 0xbd, 0xc0],
|
||||
[0x1b, 0x3f, 0x41, 0x78, 0x58, 0xcd, 0x2d, 0xd3, 0x3e, 0xc7, 0xf6, 0x20],
|
||||
[0x09, 0xfd, 0xa4, 0xfe, 0xe0, 0x41, 0x95, 0xfd, 0x03, 0x47, 0x83, 0xa0],
|
||||
[0x07, 0x7c, 0xcc, 0xc1, 0x1b, 0x88, 0x73, 0xed, 0x5c, 0x3d, 0x48, 0xa0],
|
||||
[0x29, 0xb6, 0x2a, 0xfe, 0x3c, 0xa0, 0x36, 0xf4, 0xfe, 0x1a, 0x9d, 0xa0],
|
||||
[0x60, 0x54, 0xfa, 0xf5, 0xf3, 0x5d, 0x96, 0xd3, 0xb0, 0xc8, 0xc3, 0xe0],
|
||||
[0xe2, 0x07, 0x98, 0xe4, 0x31, 0x0e, 0xed, 0x27, 0x88, 0x4a, 0xe9, 0x00],
|
||||
[0x77, 0x5c, 0x9c, 0x08, 0xe8, 0x0e, 0x26, 0xdd, 0xae, 0x56, 0x31, 0x80],
|
||||
[0xb0, 0xb8, 0x11, 0x02, 0x8c, 0x2b, 0xf9, 0x97, 0x21, 0x34, 0x87, 0xc0],
|
||||
[0x18, 0xa0, 0xc9, 0x23, 0x1f, 0xc6, 0x0a, 0xdf, 0x5c, 0x5e, 0xa3, 0x20],
|
||||
[0x76, 0x47, 0x1e, 0x83, 0x02, 0xa0, 0x72, 0x1e, 0x01, 0xb1, 0x2b, 0x80],
|
||||
[0xff, 0xbc, 0xcb, 0x80, 0xca, 0x83, 0x41, 0xfa, 0xfb, 0x47, 0xb2, 0xe0],
|
||||
[0x66, 0xa7, 0x2a, 0x15, 0x8f, 0x93, 0x25, 0xa2, 0xbf, 0x67, 0x17, 0x00],
|
||||
[0xc4, 0x24, 0x36, 0x89, 0xfe, 0x85, 0xb1, 0xc5, 0x13, 0x63, 0xa1, 0x80],
|
||||
[0x0d, 0xff, 0x73, 0x94, 0x14, 0xd1, 0xa1, 0xb3, 0x4b, 0x1c, 0x27, 0x00],
|
||||
[0x15, 0xb4, 0x88, 0x30, 0x63, 0x6c, 0x8b, 0x99, 0x89, 0x49, 0x72, 0xe0],
|
||||
[0x29, 0xa8, 0x9c, 0x0d, 0x3d, 0xe8, 0x1d, 0x66, 0x54, 0x89, 0xb0, 0xe0],
|
||||
[0x4f, 0x12, 0x6f, 0x37, 0xfa, 0x51, 0xcb, 0xe6, 0x1b, 0xd6, 0xb9, 0x40],
|
||||
[0x99, 0xc4, 0x72, 0x39, 0xd0, 0xd9, 0x7d, 0x3c, 0x84, 0xe0, 0x94, 0x00],
|
||||
[0x19, 0x19, 0xb7, 0x51, 0x19, 0x76, 0x56, 0x21, 0xbb, 0x4f, 0x1e, 0x80],
|
||||
[0x09, 0xdb, 0x12, 0xd7, 0x31, 0xfa, 0xee, 0x0b, 0x86, 0xdf, 0x6b, 0x80],
|
||||
[0x48, 0x8f, 0xc3, 0x3d, 0xf4, 0x3f, 0xbd, 0xee, 0xa4, 0xea, 0xfb, 0x40],
|
||||
[0x82, 0x74, 0x23, 0xee, 0x40, 0xb6, 0x75, 0xf7, 0x56, 0xeb, 0x5f, 0xe0],
|
||||
[0xab, 0xe1, 0x97, 0xc4, 0x84, 0xcb, 0x74, 0x75, 0x71, 0x44, 0xa9, 0xa0],
|
||||
[0x2b, 0x50, 0x0e, 0x4b, 0xc0, 0xec, 0x5a, 0x6d, 0x2b, 0xdb, 0xdd, 0x00],
|
||||
[0xc4, 0x74, 0xaa, 0x53, 0xd7, 0x02, 0x18, 0x76, 0x16, 0x69, 0x36, 0x00],
|
||||
[0x8e, 0xba, 0x1a, 0x13, 0xdb, 0x33, 0x90, 0xbd, 0x67, 0x18, 0xce, 0xc0],
|
||||
[0x75, 0x38, 0x44, 0x67, 0x3a, 0x27, 0x78, 0x2c, 0xc4, 0x20, 0x12, 0xe0],
|
||||
[0x06, 0xff, 0x83, 0xa1, 0x45, 0xc3, 0x70, 0x35, 0xa5, 0xc1, 0x26, 0x80],
|
||||
[0x3b, 0x37, 0x41, 0x78, 0x58, 0xcc, 0x2d, 0xd3, 0x3e, 0xc3, 0xf6, 0x20],
|
||||
[0x9a, 0x4a, 0x5a, 0x28, 0xee, 0x17, 0xca, 0x9c, 0x32, 0x48, 0x42, 0xc0],
|
||||
[0xbc, 0x29, 0xf4, 0x65, 0x30, 0x9c, 0x97, 0x7e, 0x89, 0x61, 0x0a, 0x40],
|
||||
[0x26, 0x63, 0xae, 0x6d, 0xdf, 0x8b, 0x5c, 0xe2, 0xbb, 0x29, 0x48, 0x80],
|
||||
[0x46, 0xf2, 0x31, 0xef, 0xe4, 0x57, 0x03, 0x4c, 0x18, 0x14, 0x41, 0x80],
|
||||
[0x3f, 0xb2, 0xce, 0x85, 0xab, 0xe9, 0xb0, 0xc7, 0x2e, 0x06, 0xfb, 0xe0],
|
||||
[0xde, 0x87, 0x48, 0x1f, 0x28, 0x2c, 0x15, 0x39, 0x71, 0xa0, 0xa2, 0xe0],
|
||||
[0xfc, 0xd7, 0xcc, 0xf2, 0x3c, 0x69, 0xfa, 0x99, 0xbb, 0xa1, 0x41, 0x20],
|
||||
[0xf0, 0x26, 0x14, 0x47, 0xe9, 0x49, 0x0c, 0xa8, 0xe4, 0x74, 0xce, 0xc0],
|
||||
[0x44, 0x10, 0x11, 0x58, 0x18, 0x19, 0x6f, 0x95, 0xcd, 0xd7, 0x01, 0x20],
|
||||
[0x08, 0x8f, 0xc3, 0x1d, 0xf4, 0xbf, 0xbd, 0xe2, 0xa4, 0xea, 0xfb, 0x40],
|
||||
[0xb8, 0xfe, 0xf1, 0xb6, 0x30, 0x77, 0x29, 0xfb, 0x0a, 0x07, 0x8c, 0x00],
|
||||
[0x5a, 0xfe, 0xa7, 0xac, 0xcc, 0xb7, 0x7b, 0xbc, 0x9d, 0x99, 0xa9, 0x00],
|
||||
[0x49, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xf6, 0x5e, 0xcd, 0xc9, 0x07, 0x60],
|
||||
[0x19, 0x44, 0xd0, 0x85, 0xbe, 0x4e, 0x7d, 0xa8, 0xd6, 0xcc, 0x7d, 0x00],
|
||||
[0x25, 0x1f, 0x62, 0xad, 0xc4, 0x03, 0x2f, 0x0e, 0xe7, 0x14, 0x00, 0x20],
|
||||
[0x56, 0x47, 0x1f, 0x87, 0x02, 0xa0, 0x72, 0x1e, 0x00, 0xb1, 0x2b, 0x80],
|
||||
[0x2b, 0x8e, 0x49, 0x23, 0xf2, 0xdd, 0x51, 0xe2, 0xd5, 0x37, 0xfa, 0x00],
|
||||
[0x6b, 0x55, 0x0a, 0x40, 0xa6, 0x6f, 0x47, 0x55, 0xde, 0x95, 0xc2, 0x60],
|
||||
[0xa1, 0x8a, 0xd2, 0x8d, 0x4e, 0x27, 0xfe, 0x92, 0xa4, 0xf6, 0xc8, 0x40],
|
||||
[0x10, 0xc2, 0xe5, 0x86, 0x38, 0x8c, 0xb8, 0x2a, 0x3d, 0x80, 0x75, 0x80],
|
||||
[0xef, 0x34, 0xa4, 0x18, 0x17, 0xee, 0x02, 0x13, 0x3d, 0xb2, 0xeb, 0x00],
|
||||
[0x7e, 0x9c, 0x0c, 0x54, 0x32, 0x5a, 0x9c, 0x15, 0x83, 0x6e, 0x00, 0x00],
|
||||
[0x36, 0x93, 0xe5, 0x72, 0xd1, 0xfd, 0xe4, 0xcd, 0xf0, 0x79, 0xe8, 0x60],
|
||||
[0xbf, 0xb2, 0xce, 0xc5, 0xab, 0xe1, 0xb0, 0xc7, 0x2e, 0x07, 0xfb, 0xe0],
|
||||
[0x7e, 0xe1, 0x82, 0x30, 0xc5, 0x83, 0xcc, 0xcc, 0x57, 0xd4, 0xb0, 0x80],
|
||||
[0xa0, 0x66, 0xcb, 0x2f, 0xed, 0xaf, 0xc9, 0xf5, 0x26, 0x64, 0x12, 0x60],
|
||||
[0xbb, 0x23, 0x72, 0x5a, 0xbc, 0x47, 0xcc, 0x5f, 0x4c, 0xc4, 0xcd, 0x20],
|
||||
[0xde, 0xd9, 0xdb, 0xa3, 0xbe, 0xe4, 0x0c, 0x59, 0xb5, 0x60, 0x9b, 0x40],
|
||||
[0xd9, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xe6, 0xde, 0xcd, 0xc9, 0x03, 0x60],
|
||||
[0x9a, 0xd4, 0x6a, 0xed, 0x5f, 0x70, 0x7f, 0x28, 0x0a, 0xb5, 0xfc, 0x40],
|
||||
[0xe5, 0x92, 0x1c, 0x77, 0x82, 0x25, 0x87, 0x31, 0x6d, 0x7d, 0x3c, 0x20],
|
||||
[0x4f, 0x14, 0xda, 0x82, 0x42, 0xa8, 0xb8, 0x6d, 0xca, 0x73, 0x35, 0x20],
|
||||
[0x8b, 0x8b, 0x50, 0x7a, 0xd4, 0x67, 0xd4, 0x44, 0x1d, 0xf7, 0x70, 0xe0],
|
||||
[0x22, 0x83, 0x1c, 0x9c, 0xf1, 0x16, 0x94, 0x67, 0xad, 0x04, 0xb6, 0x80],
|
||||
[0x21, 0x3b, 0x83, 0x8f, 0xe2, 0xae, 0x54, 0xc3, 0x8e, 0xe7, 0x18, 0x00],
|
||||
[0x5d, 0x92, 0x6b, 0x6d, 0xd7, 0x1f, 0x08, 0x51, 0x81, 0xa4, 0xe1, 0x20],
|
||||
[0x66, 0xab, 0x79, 0xd4, 0xb2, 0x9e, 0xe6, 0xe6, 0x95, 0x09, 0xe5, 0x60],
|
||||
[0x95, 0x81, 0x48, 0x68, 0x2d, 0x74, 0x8a, 0x38, 0xdd, 0x68, 0xba, 0xa0],
|
||||
[0xb8, 0xce, 0x02, 0x0c, 0xf0, 0x69, 0xc3, 0x2a, 0x72, 0x3a, 0xb1, 0x40],
|
||||
[0xf4, 0x33, 0x1d, 0x6d, 0x46, 0x16, 0x07, 0xe9, 0x57, 0x52, 0x74, 0x60],
|
||||
[0x6d, 0xa2, 0x3b, 0xa4, 0x24, 0xb9, 0x59, 0x61, 0x33, 0xcf, 0x9c, 0x80],
|
||||
[0xa6, 0x36, 0xbc, 0xbc, 0x7b, 0x30, 0xc5, 0xfb, 0xea, 0xe6, 0x7f, 0xe0],
|
||||
[0x5c, 0xb0, 0xd8, 0x6a, 0x07, 0xdf, 0x65, 0x4a, 0x90, 0x89, 0xa2, 0x00],
|
||||
[0xf1, 0x1f, 0x10, 0x68, 0x48, 0x78, 0x0f, 0xc9, 0xec, 0xdd, 0x80, 0xa0],
|
||||
[0x1f, 0xbb, 0x53, 0x64, 0xfb, 0x8d, 0x2c, 0x9d, 0x73, 0x0d, 0x5b, 0xa0],
|
||||
[0xfc, 0xb8, 0x6b, 0xc7, 0x0a, 0x50, 0xc9, 0xd0, 0x2a, 0x5d, 0x03, 0x40],
|
||||
[0xa5, 0x34, 0x43, 0x30, 0x29, 0xea, 0xc1, 0x5f, 0x32, 0x2e, 0x34, 0xc0],
|
||||
[0xc9, 0x89, 0xd9, 0xc7, 0xc3, 0xd3, 0xb8, 0xc5, 0x5d, 0x75, 0x13, 0x00],
|
||||
[0x7b, 0xb3, 0x8b, 0x2f, 0x01, 0x86, 0xd4, 0x66, 0x43, 0xae, 0x96, 0x20],
|
||||
[0x26, 0x44, 0xeb, 0xad, 0xeb, 0x44, 0xb9, 0x46, 0x7d, 0x1f, 0x42, 0xc0],
|
||||
[0x60, 0x8c, 0xc8, 0x57, 0x59, 0x4b, 0xfb, 0xb5, 0x5d, 0x69, 0x60, 0x00],
|
||||
[
|
||||
0x83, 0x29, 0xce, 0x11, 0xbf, 0x31, 0xea, 0xf5, 0x09, 0xf2, 0x7f, 0xc0,
|
||||
],
|
||||
[
|
||||
0x76, 0x1c, 0x26, 0x4e, 0x25, 0xc2, 0x59, 0x33, 0x54, 0x93, 0x13, 0x20,
|
||||
],
|
||||
[
|
||||
0xdc, 0x26, 0x59, 0x02, 0xfb, 0x27, 0x7c, 0x64, 0x10, 0xa1, 0xbd, 0xc0,
|
||||
],
|
||||
[
|
||||
0x1b, 0x3f, 0x41, 0x78, 0x58, 0xcd, 0x2d, 0xd3, 0x3e, 0xc7, 0xf6, 0x20,
|
||||
],
|
||||
[
|
||||
0x09, 0xfd, 0xa4, 0xfe, 0xe0, 0x41, 0x95, 0xfd, 0x03, 0x47, 0x83, 0xa0,
|
||||
],
|
||||
[
|
||||
0x07, 0x7c, 0xcc, 0xc1, 0x1b, 0x88, 0x73, 0xed, 0x5c, 0x3d, 0x48, 0xa0,
|
||||
],
|
||||
[
|
||||
0x29, 0xb6, 0x2a, 0xfe, 0x3c, 0xa0, 0x36, 0xf4, 0xfe, 0x1a, 0x9d, 0xa0,
|
||||
],
|
||||
[
|
||||
0x60, 0x54, 0xfa, 0xf5, 0xf3, 0x5d, 0x96, 0xd3, 0xb0, 0xc8, 0xc3, 0xe0,
|
||||
],
|
||||
[
|
||||
0xe2, 0x07, 0x98, 0xe4, 0x31, 0x0e, 0xed, 0x27, 0x88, 0x4a, 0xe9, 0x00,
|
||||
],
|
||||
[
|
||||
0x77, 0x5c, 0x9c, 0x08, 0xe8, 0x0e, 0x26, 0xdd, 0xae, 0x56, 0x31, 0x80,
|
||||
],
|
||||
[
|
||||
0xb0, 0xb8, 0x11, 0x02, 0x8c, 0x2b, 0xf9, 0x97, 0x21, 0x34, 0x87, 0xc0,
|
||||
],
|
||||
[
|
||||
0x18, 0xa0, 0xc9, 0x23, 0x1f, 0xc6, 0x0a, 0xdf, 0x5c, 0x5e, 0xa3, 0x20,
|
||||
],
|
||||
[
|
||||
0x76, 0x47, 0x1e, 0x83, 0x02, 0xa0, 0x72, 0x1e, 0x01, 0xb1, 0x2b, 0x80,
|
||||
],
|
||||
[
|
||||
0xff, 0xbc, 0xcb, 0x80, 0xca, 0x83, 0x41, 0xfa, 0xfb, 0x47, 0xb2, 0xe0,
|
||||
],
|
||||
[
|
||||
0x66, 0xa7, 0x2a, 0x15, 0x8f, 0x93, 0x25, 0xa2, 0xbf, 0x67, 0x17, 0x00,
|
||||
],
|
||||
[
|
||||
0xc4, 0x24, 0x36, 0x89, 0xfe, 0x85, 0xb1, 0xc5, 0x13, 0x63, 0xa1, 0x80,
|
||||
],
|
||||
[
|
||||
0x0d, 0xff, 0x73, 0x94, 0x14, 0xd1, 0xa1, 0xb3, 0x4b, 0x1c, 0x27, 0x00,
|
||||
],
|
||||
[
|
||||
0x15, 0xb4, 0x88, 0x30, 0x63, 0x6c, 0x8b, 0x99, 0x89, 0x49, 0x72, 0xe0,
|
||||
],
|
||||
[
|
||||
0x29, 0xa8, 0x9c, 0x0d, 0x3d, 0xe8, 0x1d, 0x66, 0x54, 0x89, 0xb0, 0xe0,
|
||||
],
|
||||
[
|
||||
0x4f, 0x12, 0x6f, 0x37, 0xfa, 0x51, 0xcb, 0xe6, 0x1b, 0xd6, 0xb9, 0x40,
|
||||
],
|
||||
[
|
||||
0x99, 0xc4, 0x72, 0x39, 0xd0, 0xd9, 0x7d, 0x3c, 0x84, 0xe0, 0x94, 0x00,
|
||||
],
|
||||
[
|
||||
0x19, 0x19, 0xb7, 0x51, 0x19, 0x76, 0x56, 0x21, 0xbb, 0x4f, 0x1e, 0x80,
|
||||
],
|
||||
[
|
||||
0x09, 0xdb, 0x12, 0xd7, 0x31, 0xfa, 0xee, 0x0b, 0x86, 0xdf, 0x6b, 0x80,
|
||||
],
|
||||
[
|
||||
0x48, 0x8f, 0xc3, 0x3d, 0xf4, 0x3f, 0xbd, 0xee, 0xa4, 0xea, 0xfb, 0x40,
|
||||
],
|
||||
[
|
||||
0x82, 0x74, 0x23, 0xee, 0x40, 0xb6, 0x75, 0xf7, 0x56, 0xeb, 0x5f, 0xe0,
|
||||
],
|
||||
[
|
||||
0xab, 0xe1, 0x97, 0xc4, 0x84, 0xcb, 0x74, 0x75, 0x71, 0x44, 0xa9, 0xa0,
|
||||
],
|
||||
[
|
||||
0x2b, 0x50, 0x0e, 0x4b, 0xc0, 0xec, 0x5a, 0x6d, 0x2b, 0xdb, 0xdd, 0x00,
|
||||
],
|
||||
[
|
||||
0xc4, 0x74, 0xaa, 0x53, 0xd7, 0x02, 0x18, 0x76, 0x16, 0x69, 0x36, 0x00,
|
||||
],
|
||||
[
|
||||
0x8e, 0xba, 0x1a, 0x13, 0xdb, 0x33, 0x90, 0xbd, 0x67, 0x18, 0xce, 0xc0,
|
||||
],
|
||||
[
|
||||
0x75, 0x38, 0x44, 0x67, 0x3a, 0x27, 0x78, 0x2c, 0xc4, 0x20, 0x12, 0xe0,
|
||||
],
|
||||
[
|
||||
0x06, 0xff, 0x83, 0xa1, 0x45, 0xc3, 0x70, 0x35, 0xa5, 0xc1, 0x26, 0x80,
|
||||
],
|
||||
[
|
||||
0x3b, 0x37, 0x41, 0x78, 0x58, 0xcc, 0x2d, 0xd3, 0x3e, 0xc3, 0xf6, 0x20,
|
||||
],
|
||||
[
|
||||
0x9a, 0x4a, 0x5a, 0x28, 0xee, 0x17, 0xca, 0x9c, 0x32, 0x48, 0x42, 0xc0,
|
||||
],
|
||||
[
|
||||
0xbc, 0x29, 0xf4, 0x65, 0x30, 0x9c, 0x97, 0x7e, 0x89, 0x61, 0x0a, 0x40,
|
||||
],
|
||||
[
|
||||
0x26, 0x63, 0xae, 0x6d, 0xdf, 0x8b, 0x5c, 0xe2, 0xbb, 0x29, 0x48, 0x80,
|
||||
],
|
||||
[
|
||||
0x46, 0xf2, 0x31, 0xef, 0xe4, 0x57, 0x03, 0x4c, 0x18, 0x14, 0x41, 0x80,
|
||||
],
|
||||
[
|
||||
0x3f, 0xb2, 0xce, 0x85, 0xab, 0xe9, 0xb0, 0xc7, 0x2e, 0x06, 0xfb, 0xe0,
|
||||
],
|
||||
[
|
||||
0xde, 0x87, 0x48, 0x1f, 0x28, 0x2c, 0x15, 0x39, 0x71, 0xa0, 0xa2, 0xe0,
|
||||
],
|
||||
[
|
||||
0xfc, 0xd7, 0xcc, 0xf2, 0x3c, 0x69, 0xfa, 0x99, 0xbb, 0xa1, 0x41, 0x20,
|
||||
],
|
||||
[
|
||||
0xf0, 0x26, 0x14, 0x47, 0xe9, 0x49, 0x0c, 0xa8, 0xe4, 0x74, 0xce, 0xc0,
|
||||
],
|
||||
[
|
||||
0x44, 0x10, 0x11, 0x58, 0x18, 0x19, 0x6f, 0x95, 0xcd, 0xd7, 0x01, 0x20,
|
||||
],
|
||||
[
|
||||
0x08, 0x8f, 0xc3, 0x1d, 0xf4, 0xbf, 0xbd, 0xe2, 0xa4, 0xea, 0xfb, 0x40,
|
||||
],
|
||||
[
|
||||
0xb8, 0xfe, 0xf1, 0xb6, 0x30, 0x77, 0x29, 0xfb, 0x0a, 0x07, 0x8c, 0x00,
|
||||
],
|
||||
[
|
||||
0x5a, 0xfe, 0xa7, 0xac, 0xcc, 0xb7, 0x7b, 0xbc, 0x9d, 0x99, 0xa9, 0x00,
|
||||
],
|
||||
[
|
||||
0x49, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xf6, 0x5e, 0xcd, 0xc9, 0x07, 0x60,
|
||||
],
|
||||
[
|
||||
0x19, 0x44, 0xd0, 0x85, 0xbe, 0x4e, 0x7d, 0xa8, 0xd6, 0xcc, 0x7d, 0x00,
|
||||
],
|
||||
[
|
||||
0x25, 0x1f, 0x62, 0xad, 0xc4, 0x03, 0x2f, 0x0e, 0xe7, 0x14, 0x00, 0x20,
|
||||
],
|
||||
[
|
||||
0x56, 0x47, 0x1f, 0x87, 0x02, 0xa0, 0x72, 0x1e, 0x00, 0xb1, 0x2b, 0x80,
|
||||
],
|
||||
[
|
||||
0x2b, 0x8e, 0x49, 0x23, 0xf2, 0xdd, 0x51, 0xe2, 0xd5, 0x37, 0xfa, 0x00,
|
||||
],
|
||||
[
|
||||
0x6b, 0x55, 0x0a, 0x40, 0xa6, 0x6f, 0x47, 0x55, 0xde, 0x95, 0xc2, 0x60,
|
||||
],
|
||||
[
|
||||
0xa1, 0x8a, 0xd2, 0x8d, 0x4e, 0x27, 0xfe, 0x92, 0xa4, 0xf6, 0xc8, 0x40,
|
||||
],
|
||||
[
|
||||
0x10, 0xc2, 0xe5, 0x86, 0x38, 0x8c, 0xb8, 0x2a, 0x3d, 0x80, 0x75, 0x80,
|
||||
],
|
||||
[
|
||||
0xef, 0x34, 0xa4, 0x18, 0x17, 0xee, 0x02, 0x13, 0x3d, 0xb2, 0xeb, 0x00,
|
||||
],
|
||||
[
|
||||
0x7e, 0x9c, 0x0c, 0x54, 0x32, 0x5a, 0x9c, 0x15, 0x83, 0x6e, 0x00, 0x00,
|
||||
],
|
||||
[
|
||||
0x36, 0x93, 0xe5, 0x72, 0xd1, 0xfd, 0xe4, 0xcd, 0xf0, 0x79, 0xe8, 0x60,
|
||||
],
|
||||
[
|
||||
0xbf, 0xb2, 0xce, 0xc5, 0xab, 0xe1, 0xb0, 0xc7, 0x2e, 0x07, 0xfb, 0xe0,
|
||||
],
|
||||
[
|
||||
0x7e, 0xe1, 0x82, 0x30, 0xc5, 0x83, 0xcc, 0xcc, 0x57, 0xd4, 0xb0, 0x80,
|
||||
],
|
||||
[
|
||||
0xa0, 0x66, 0xcb, 0x2f, 0xed, 0xaf, 0xc9, 0xf5, 0x26, 0x64, 0x12, 0x60,
|
||||
],
|
||||
[
|
||||
0xbb, 0x23, 0x72, 0x5a, 0xbc, 0x47, 0xcc, 0x5f, 0x4c, 0xc4, 0xcd, 0x20,
|
||||
],
|
||||
[
|
||||
0xde, 0xd9, 0xdb, 0xa3, 0xbe, 0xe4, 0x0c, 0x59, 0xb5, 0x60, 0x9b, 0x40,
|
||||
],
|
||||
[
|
||||
0xd9, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xe6, 0xde, 0xcd, 0xc9, 0x03, 0x60,
|
||||
],
|
||||
[
|
||||
0x9a, 0xd4, 0x6a, 0xed, 0x5f, 0x70, 0x7f, 0x28, 0x0a, 0xb5, 0xfc, 0x40,
|
||||
],
|
||||
[
|
||||
0xe5, 0x92, 0x1c, 0x77, 0x82, 0x25, 0x87, 0x31, 0x6d, 0x7d, 0x3c, 0x20,
|
||||
],
|
||||
[
|
||||
0x4f, 0x14, 0xda, 0x82, 0x42, 0xa8, 0xb8, 0x6d, 0xca, 0x73, 0x35, 0x20,
|
||||
],
|
||||
[
|
||||
0x8b, 0x8b, 0x50, 0x7a, 0xd4, 0x67, 0xd4, 0x44, 0x1d, 0xf7, 0x70, 0xe0,
|
||||
],
|
||||
[
|
||||
0x22, 0x83, 0x1c, 0x9c, 0xf1, 0x16, 0x94, 0x67, 0xad, 0x04, 0xb6, 0x80,
|
||||
],
|
||||
[
|
||||
0x21, 0x3b, 0x83, 0x8f, 0xe2, 0xae, 0x54, 0xc3, 0x8e, 0xe7, 0x18, 0x00,
|
||||
],
|
||||
[
|
||||
0x5d, 0x92, 0x6b, 0x6d, 0xd7, 0x1f, 0x08, 0x51, 0x81, 0xa4, 0xe1, 0x20,
|
||||
],
|
||||
[
|
||||
0x66, 0xab, 0x79, 0xd4, 0xb2, 0x9e, 0xe6, 0xe6, 0x95, 0x09, 0xe5, 0x60,
|
||||
],
|
||||
[
|
||||
0x95, 0x81, 0x48, 0x68, 0x2d, 0x74, 0x8a, 0x38, 0xdd, 0x68, 0xba, 0xa0,
|
||||
],
|
||||
[
|
||||
0xb8, 0xce, 0x02, 0x0c, 0xf0, 0x69, 0xc3, 0x2a, 0x72, 0x3a, 0xb1, 0x40,
|
||||
],
|
||||
[
|
||||
0xf4, 0x33, 0x1d, 0x6d, 0x46, 0x16, 0x07, 0xe9, 0x57, 0x52, 0x74, 0x60,
|
||||
],
|
||||
[
|
||||
0x6d, 0xa2, 0x3b, 0xa4, 0x24, 0xb9, 0x59, 0x61, 0x33, 0xcf, 0x9c, 0x80,
|
||||
],
|
||||
[
|
||||
0xa6, 0x36, 0xbc, 0xbc, 0x7b, 0x30, 0xc5, 0xfb, 0xea, 0xe6, 0x7f, 0xe0,
|
||||
],
|
||||
[
|
||||
0x5c, 0xb0, 0xd8, 0x6a, 0x07, 0xdf, 0x65, 0x4a, 0x90, 0x89, 0xa2, 0x00,
|
||||
],
|
||||
[
|
||||
0xf1, 0x1f, 0x10, 0x68, 0x48, 0x78, 0x0f, 0xc9, 0xec, 0xdd, 0x80, 0xa0,
|
||||
],
|
||||
[
|
||||
0x1f, 0xbb, 0x53, 0x64, 0xfb, 0x8d, 0x2c, 0x9d, 0x73, 0x0d, 0x5b, 0xa0,
|
||||
],
|
||||
[
|
||||
0xfc, 0xb8, 0x6b, 0xc7, 0x0a, 0x50, 0xc9, 0xd0, 0x2a, 0x5d, 0x03, 0x40,
|
||||
],
|
||||
[
|
||||
0xa5, 0x34, 0x43, 0x30, 0x29, 0xea, 0xc1, 0x5f, 0x32, 0x2e, 0x34, 0xc0,
|
||||
],
|
||||
[
|
||||
0xc9, 0x89, 0xd9, 0xc7, 0xc3, 0xd3, 0xb8, 0xc5, 0x5d, 0x75, 0x13, 0x00,
|
||||
],
|
||||
[
|
||||
0x7b, 0xb3, 0x8b, 0x2f, 0x01, 0x86, 0xd4, 0x66, 0x43, 0xae, 0x96, 0x20,
|
||||
],
|
||||
[
|
||||
0x26, 0x44, 0xeb, 0xad, 0xeb, 0x44, 0xb9, 0x46, 0x7d, 0x1f, 0x42, 0xc0,
|
||||
],
|
||||
[
|
||||
0x60, 0x8c, 0xc8, 0x57, 0x59, 0x4b, 0xfb, 0xb5, 0x5d, 0x69, 0x60, 0x00,
|
||||
],
|
||||
];
|
||||
|
||||
/// LDPC parity check matrix Nm: each row describes one parity check.
|
||||
@@ -204,49 +364,185 @@ pub const FTX_LDPC_NM: [[u8; 7]; FTX_LDPC_M] = [
|
||||
/// Mn: each row corresponds to a codeword bit.
|
||||
/// The numbers indicate which three parity checks refer to the codeword bit (1-origin).
|
||||
pub const FTX_LDPC_MN: [[u8; 3]; FTX_LDPC_N] = [
|
||||
[16, 45, 73], [25, 51, 62], [33, 58, 78], [1, 44, 45], [2, 7, 61],
|
||||
[3, 6, 54], [4, 35, 48], [5, 13, 21], [8, 56, 79], [9, 64, 69],
|
||||
[10, 19, 66], [11, 36, 60], [12, 37, 58], [14, 32, 43], [15, 63, 80],
|
||||
[17, 28, 77], [18, 74, 83], [22, 53, 81], [23, 30, 34], [24, 31, 40],
|
||||
[26, 41, 76], [27, 57, 70], [29, 49, 65], [3, 38, 78], [5, 39, 82],
|
||||
[46, 50, 73], [51, 52, 74], [55, 71, 72], [44, 67, 72], [43, 68, 78],
|
||||
[1, 32, 59], [2, 6, 71], [4, 16, 54], [7, 65, 67], [8, 30, 42],
|
||||
[9, 22, 31], [10, 18, 76], [11, 23, 82], [12, 28, 61], [13, 52, 79],
|
||||
[14, 50, 51], [15, 81, 83], [17, 29, 60], [19, 33, 64], [20, 26, 73],
|
||||
[21, 34, 40], [24, 27, 77], [25, 55, 58], [35, 53, 66], [36, 48, 68],
|
||||
[37, 46, 75], [38, 45, 47], [39, 57, 69], [41, 56, 62], [20, 49, 53],
|
||||
[46, 52, 63], [45, 70, 75], [27, 35, 80], [1, 15, 30], [2, 68, 80],
|
||||
[3, 36, 51], [4, 28, 51], [5, 31, 56], [6, 20, 37], [7, 40, 82],
|
||||
[8, 60, 69], [9, 10, 49], [11, 44, 57], [12, 39, 59], [13, 24, 55],
|
||||
[14, 21, 65], [16, 71, 78], [17, 30, 76], [18, 25, 80], [19, 61, 83],
|
||||
[22, 38, 77], [23, 41, 50], [7, 26, 58], [29, 32, 81], [33, 40, 73],
|
||||
[18, 34, 48], [13, 42, 64], [5, 26, 43], [47, 69, 72], [54, 55, 70],
|
||||
[45, 62, 68], [10, 63, 67], [14, 66, 72], [22, 60, 74], [35, 39, 79],
|
||||
[1, 46, 64], [1, 24, 66], [2, 5, 70], [3, 31, 65], [4, 49, 58],
|
||||
[1, 4, 5], [6, 60, 67], [7, 32, 75], [8, 48, 82], [9, 35, 41],
|
||||
[10, 39, 62], [11, 14, 61], [12, 71, 74], [13, 23, 78], [11, 35, 55],
|
||||
[15, 16, 79], [7, 9, 16], [17, 54, 63], [18, 50, 57], [19, 30, 47],
|
||||
[20, 64, 80], [21, 28, 69], [22, 25, 43], [13, 22, 37], [2, 47, 51],
|
||||
[23, 54, 74], [26, 34, 72], [27, 36, 37], [21, 36, 63], [29, 40, 44],
|
||||
[19, 26, 57], [3, 46, 82], [23, 54, 74], [33, 52, 53], [30, 43, 52],
|
||||
[6, 9, 52], [27, 33, 65], [25, 69, 73], [38, 55, 83], [20, 39, 77],
|
||||
[18, 29, 56], [32, 48, 71], [42, 51, 59], [28, 44, 79], [34, 60, 62],
|
||||
[31, 45, 61], [46, 68, 77], [6, 24, 76], [8, 10, 78], [40, 41, 70],
|
||||
[17, 50, 53], [42, 66, 68], [4, 22, 72], [36, 64, 81], [13, 29, 47],
|
||||
[2, 8, 81], [56, 67, 73], [5, 38, 50], [12, 38, 64], [59, 72, 80],
|
||||
[3, 26, 79], [45, 76, 81], [1, 65, 74], [7, 18, 77], [11, 56, 59],
|
||||
[14, 39, 54], [16, 37, 66], [10, 28, 55], [15, 60, 70], [17, 25, 82],
|
||||
[20, 30, 31], [12, 67, 68], [23, 75, 80], [27, 32, 62], [24, 69, 75],
|
||||
[19, 21, 71], [34, 53, 61], [35, 46, 47], [33, 59, 76], [40, 43, 83],
|
||||
[41, 42, 63], [49, 75, 83], [20, 44, 48], [42, 49, 57],
|
||||
[16, 45, 73],
|
||||
[25, 51, 62],
|
||||
[33, 58, 78],
|
||||
[1, 44, 45],
|
||||
[2, 7, 61],
|
||||
[3, 6, 54],
|
||||
[4, 35, 48],
|
||||
[5, 13, 21],
|
||||
[8, 56, 79],
|
||||
[9, 64, 69],
|
||||
[10, 19, 66],
|
||||
[11, 36, 60],
|
||||
[12, 37, 58],
|
||||
[14, 32, 43],
|
||||
[15, 63, 80],
|
||||
[17, 28, 77],
|
||||
[18, 74, 83],
|
||||
[22, 53, 81],
|
||||
[23, 30, 34],
|
||||
[24, 31, 40],
|
||||
[26, 41, 76],
|
||||
[27, 57, 70],
|
||||
[29, 49, 65],
|
||||
[3, 38, 78],
|
||||
[5, 39, 82],
|
||||
[46, 50, 73],
|
||||
[51, 52, 74],
|
||||
[55, 71, 72],
|
||||
[44, 67, 72],
|
||||
[43, 68, 78],
|
||||
[1, 32, 59],
|
||||
[2, 6, 71],
|
||||
[4, 16, 54],
|
||||
[7, 65, 67],
|
||||
[8, 30, 42],
|
||||
[9, 22, 31],
|
||||
[10, 18, 76],
|
||||
[11, 23, 82],
|
||||
[12, 28, 61],
|
||||
[13, 52, 79],
|
||||
[14, 50, 51],
|
||||
[15, 81, 83],
|
||||
[17, 29, 60],
|
||||
[19, 33, 64],
|
||||
[20, 26, 73],
|
||||
[21, 34, 40],
|
||||
[24, 27, 77],
|
||||
[25, 55, 58],
|
||||
[35, 53, 66],
|
||||
[36, 48, 68],
|
||||
[37, 46, 75],
|
||||
[38, 45, 47],
|
||||
[39, 57, 69],
|
||||
[41, 56, 62],
|
||||
[20, 49, 53],
|
||||
[46, 52, 63],
|
||||
[45, 70, 75],
|
||||
[27, 35, 80],
|
||||
[1, 15, 30],
|
||||
[2, 68, 80],
|
||||
[3, 36, 51],
|
||||
[4, 28, 51],
|
||||
[5, 31, 56],
|
||||
[6, 20, 37],
|
||||
[7, 40, 82],
|
||||
[8, 60, 69],
|
||||
[9, 10, 49],
|
||||
[11, 44, 57],
|
||||
[12, 39, 59],
|
||||
[13, 24, 55],
|
||||
[14, 21, 65],
|
||||
[16, 71, 78],
|
||||
[17, 30, 76],
|
||||
[18, 25, 80],
|
||||
[19, 61, 83],
|
||||
[22, 38, 77],
|
||||
[23, 41, 50],
|
||||
[7, 26, 58],
|
||||
[29, 32, 81],
|
||||
[33, 40, 73],
|
||||
[18, 34, 48],
|
||||
[13, 42, 64],
|
||||
[5, 26, 43],
|
||||
[47, 69, 72],
|
||||
[54, 55, 70],
|
||||
[45, 62, 68],
|
||||
[10, 63, 67],
|
||||
[14, 66, 72],
|
||||
[22, 60, 74],
|
||||
[35, 39, 79],
|
||||
[1, 46, 64],
|
||||
[1, 24, 66],
|
||||
[2, 5, 70],
|
||||
[3, 31, 65],
|
||||
[4, 49, 58],
|
||||
[1, 4, 5],
|
||||
[6, 60, 67],
|
||||
[7, 32, 75],
|
||||
[8, 48, 82],
|
||||
[9, 35, 41],
|
||||
[10, 39, 62],
|
||||
[11, 14, 61],
|
||||
[12, 71, 74],
|
||||
[13, 23, 78],
|
||||
[11, 35, 55],
|
||||
[15, 16, 79],
|
||||
[7, 9, 16],
|
||||
[17, 54, 63],
|
||||
[18, 50, 57],
|
||||
[19, 30, 47],
|
||||
[20, 64, 80],
|
||||
[21, 28, 69],
|
||||
[22, 25, 43],
|
||||
[13, 22, 37],
|
||||
[2, 47, 51],
|
||||
[23, 54, 74],
|
||||
[26, 34, 72],
|
||||
[27, 36, 37],
|
||||
[21, 36, 63],
|
||||
[29, 40, 44],
|
||||
[19, 26, 57],
|
||||
[3, 46, 82],
|
||||
[23, 54, 74],
|
||||
[33, 52, 53],
|
||||
[30, 43, 52],
|
||||
[6, 9, 52],
|
||||
[27, 33, 65],
|
||||
[25, 69, 73],
|
||||
[38, 55, 83],
|
||||
[20, 39, 77],
|
||||
[18, 29, 56],
|
||||
[32, 48, 71],
|
||||
[42, 51, 59],
|
||||
[28, 44, 79],
|
||||
[34, 60, 62],
|
||||
[31, 45, 61],
|
||||
[46, 68, 77],
|
||||
[6, 24, 76],
|
||||
[8, 10, 78],
|
||||
[40, 41, 70],
|
||||
[17, 50, 53],
|
||||
[42, 66, 68],
|
||||
[4, 22, 72],
|
||||
[36, 64, 81],
|
||||
[13, 29, 47],
|
||||
[2, 8, 81],
|
||||
[56, 67, 73],
|
||||
[5, 38, 50],
|
||||
[12, 38, 64],
|
||||
[59, 72, 80],
|
||||
[3, 26, 79],
|
||||
[45, 76, 81],
|
||||
[1, 65, 74],
|
||||
[7, 18, 77],
|
||||
[11, 56, 59],
|
||||
[14, 39, 54],
|
||||
[16, 37, 66],
|
||||
[10, 28, 55],
|
||||
[15, 60, 70],
|
||||
[17, 25, 82],
|
||||
[20, 30, 31],
|
||||
[12, 67, 68],
|
||||
[23, 75, 80],
|
||||
[27, 32, 62],
|
||||
[24, 69, 75],
|
||||
[19, 21, 71],
|
||||
[34, 53, 61],
|
||||
[35, 46, 47],
|
||||
[33, 59, 76],
|
||||
[40, 43, 83],
|
||||
[41, 42, 63],
|
||||
[49, 75, 83],
|
||||
[20, 44, 48],
|
||||
[42, 49, 57],
|
||||
];
|
||||
|
||||
/// Number of entries per row in FTX_LDPC_NM.
|
||||
pub const FTX_LDPC_NUM_ROWS: [u8; FTX_LDPC_M] = [
|
||||
7, 6, 6, 6, 7, 6, 7, 6, 6, 7, 6, 6, 7, 7, 6, 6,
|
||||
6, 7, 6, 7, 6, 7, 6, 6, 6, 7, 6, 6, 6, 7, 6, 6,
|
||||
6, 6, 7, 6, 6, 6, 7, 7, 6, 6, 6, 6, 7, 7, 6, 6,
|
||||
6, 6, 7, 6, 6, 6, 7, 6, 6, 6, 6, 7, 6, 6, 6, 7,
|
||||
6, 6, 6, 7, 7, 6, 6, 7, 6, 6, 6, 6, 6, 6, 6, 7,
|
||||
6, 6, 6,
|
||||
7, 6, 6, 6, 7, 6, 7, 6, 6, 7, 6, 6, 7, 7, 6, 6, 6, 7, 6, 7, 6, 7, 6, 6, 6, 7, 6, 6, 6, 7, 6, 6,
|
||||
6, 6, 7, 6, 6, 6, 7, 7, 6, 6, 6, 6, 7, 7, 6, 6, 6, 6, 7, 6, 6, 6, 7, 6, 6, 6, 6, 7, 6, 6, 6, 7,
|
||||
6, 6, 6, 7, 7, 6, 6, 7, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, 6,
|
||||
];
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
use num_complex::Complex32;
|
||||
|
||||
use crate::constants::*;
|
||||
use crate::monitor::{WfElem, Waterfall};
|
||||
use crate::monitor::{Waterfall, WfElem};
|
||||
use crate::protocol::*;
|
||||
|
||||
/// Candidate position in time and frequency.
|
||||
@@ -55,15 +55,25 @@ fn wf_mag_at(wf: &Waterfall, base: usize, idx: isize) -> &WfElem {
|
||||
if i < wf.mag.len() {
|
||||
&wf.mag[i]
|
||||
} else {
|
||||
&WfElem { mag: -120.0, phase: 0.0 }
|
||||
&WfElem {
|
||||
mag: -120.0,
|
||||
phase: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Leaked reference for out-of-bounds default
|
||||
static DEFAULT_WF_ELEM: WfElem = WfElem { mag: -120.0, phase: 0.0 };
|
||||
static DEFAULT_WF_ELEM: WfElem = WfElem {
|
||||
mag: -120.0,
|
||||
phase: 0.0,
|
||||
};
|
||||
|
||||
fn wf_mag_safe(wf: &Waterfall, idx: usize) -> &WfElem {
|
||||
if idx < wf.mag.len() { &wf.mag[idx] } else { &DEFAULT_WF_ELEM }
|
||||
if idx < wf.mag.len() {
|
||||
&wf.mag[idx]
|
||||
} else {
|
||||
&DEFAULT_WF_ELEM
|
||||
}
|
||||
}
|
||||
|
||||
fn ft8_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
||||
@@ -75,8 +85,12 @@ fn ft8_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
||||
for k in 0..FT8_LENGTH_SYNC {
|
||||
let block = FT8_SYNC_OFFSET * m + k;
|
||||
let block_abs = cand.time_offset as i32 + block as i32;
|
||||
if block_abs < 0 { continue; }
|
||||
if block_abs >= wf.num_blocks as i32 { break; }
|
||||
if block_abs < 0 {
|
||||
continue;
|
||||
}
|
||||
if block_abs >= wf.num_blocks as i32 {
|
||||
break;
|
||||
}
|
||||
|
||||
let p_offset = base + block * wf.block_stride;
|
||||
let sm = FT8_COSTAS_PATTERN[k] as usize;
|
||||
@@ -96,7 +110,11 @@ fn ft8_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
||||
if k > 0 && block_abs > 0 {
|
||||
let a = wf_mag_safe(wf, p_offset + sm).mag_int();
|
||||
let b_idx = (p_offset + sm).wrapping_sub(wf.block_stride);
|
||||
let b = if b_idx < wf.mag.len() { wf.mag[b_idx].mag_int() } else { 0 };
|
||||
let b = if b_idx < wf.mag.len() {
|
||||
wf.mag[b_idx].mag_int()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
score += a - b;
|
||||
num_average += 1;
|
||||
}
|
||||
@@ -109,7 +127,11 @@ fn ft8_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
||||
}
|
||||
}
|
||||
|
||||
if num_average > 0 { score / num_average } else { 0 }
|
||||
if num_average > 0 {
|
||||
score / num_average
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn ft4_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
||||
@@ -121,8 +143,12 @@ fn ft4_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
||||
for k in 0..FT4_LENGTH_SYNC {
|
||||
let block = 1 + FT4_SYNC_OFFSET * m + k;
|
||||
let block_abs = cand.time_offset as i32 + block as i32;
|
||||
if block_abs < 0 { continue; }
|
||||
if block_abs >= wf.num_blocks as i32 { break; }
|
||||
if block_abs < 0 {
|
||||
continue;
|
||||
}
|
||||
if block_abs >= wf.num_blocks as i32 {
|
||||
break;
|
||||
}
|
||||
|
||||
let p_offset = base + block * wf.block_stride;
|
||||
let sm = FT4_COSTAS_PATTERN[m][k] as usize;
|
||||
@@ -142,7 +168,11 @@ fn ft4_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
||||
if k > 0 && block_abs > 0 {
|
||||
let a = wf_mag_safe(wf, p_offset + sm).mag_int();
|
||||
let b_idx = (p_offset + sm).wrapping_sub(wf.block_stride);
|
||||
let b = if b_idx < wf.mag.len() { wf.mag[b_idx].mag_int() } else { 0 };
|
||||
let b = if b_idx < wf.mag.len() {
|
||||
wf.mag[b_idx].mag_int()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
score += a - b;
|
||||
num_average += 1;
|
||||
}
|
||||
@@ -155,7 +185,11 @@ fn ft4_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
||||
}
|
||||
}
|
||||
|
||||
if num_average > 0 { score / num_average } else { 0 }
|
||||
if num_average > 0 {
|
||||
score / num_average
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn ft2_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
||||
@@ -178,12 +212,16 @@ fn ft2_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
||||
let elem = *wf_mag_safe(wf, sym_offset + tone);
|
||||
sum += wf_elem_to_complex(elem);
|
||||
}
|
||||
if !complete { continue; }
|
||||
if !complete {
|
||||
continue;
|
||||
}
|
||||
score_f += sum.norm();
|
||||
groups += 1;
|
||||
}
|
||||
|
||||
if groups == 0 { return 0; }
|
||||
if groups == 0 {
|
||||
return 0;
|
||||
}
|
||||
(score_f / groups as f32 * 8.0).round() as i32
|
||||
}
|
||||
|
||||
@@ -194,9 +232,15 @@ fn heapify_down(heap: &mut [Candidate], size: usize) {
|
||||
let left = 2 * current + 1;
|
||||
let right = left + 1;
|
||||
let mut smallest = current;
|
||||
if left < size && heap[left].score < heap[smallest].score { smallest = left; }
|
||||
if right < size && heap[right].score < heap[smallest].score { smallest = right; }
|
||||
if smallest == current { break; }
|
||||
if left < size && heap[left].score < heap[smallest].score {
|
||||
smallest = left;
|
||||
}
|
||||
if right < size && heap[right].score < heap[smallest].score {
|
||||
smallest = right;
|
||||
}
|
||||
if smallest == current {
|
||||
break;
|
||||
}
|
||||
heap.swap(current, smallest);
|
||||
current = smallest;
|
||||
}
|
||||
@@ -206,14 +250,20 @@ fn heapify_up(heap: &mut [Candidate], size: usize) {
|
||||
let mut current = size - 1;
|
||||
while current > 0 {
|
||||
let parent = (current - 1) / 2;
|
||||
if heap[current].score >= heap[parent].score { break; }
|
||||
if heap[current].score >= heap[parent].score {
|
||||
break;
|
||||
}
|
||||
heap.swap(current, parent);
|
||||
current = parent;
|
||||
}
|
||||
}
|
||||
|
||||
/// Find candidate signals in the waterfall. Returns sorted candidates (best first).
|
||||
pub fn ftx_find_candidates(wf: &Waterfall, max_candidates: usize, min_score: i32) -> Vec<Candidate> {
|
||||
pub fn ftx_find_candidates(
|
||||
wf: &Waterfall,
|
||||
max_candidates: usize,
|
||||
min_score: i32,
|
||||
) -> Vec<Candidate> {
|
||||
let is_ft2 = wf.protocol == FtxProtocol::Ft2;
|
||||
let num_tones = if wf.protocol.uses_ft4_layout() { 4 } else { 8 };
|
||||
|
||||
@@ -325,7 +375,13 @@ fn ft4_extract_likelihood(wf: &Waterfall, cand: &Candidate, log174: &mut [f32; F
|
||||
let base = get_cand_offset(wf, cand);
|
||||
|
||||
for k in 0..FT4_ND {
|
||||
let sym_idx = k + if k < 29 { 5 } else if k < 58 { 9 } else { 13 };
|
||||
let sym_idx = k + if k < 29 {
|
||||
5
|
||||
} else if k < 58 {
|
||||
9
|
||||
} else {
|
||||
13
|
||||
};
|
||||
let bit_idx = 2 * k;
|
||||
let block = cand.time_offset as i32 + sym_idx as i32;
|
||||
|
||||
@@ -358,7 +414,9 @@ fn ft2_extract_likelihood(wf: &Waterfall, cand: &Candidate, log174: &mut [f32; F
|
||||
for frame_sym in 0..frame_syms {
|
||||
let sym_idx = frame_sym + 1; // skip ramp-up
|
||||
let block = cand.time_offset as i32 + sym_idx as i32;
|
||||
if block < 0 || block >= wf.num_blocks as i32 { continue; }
|
||||
if block < 0 || block >= wf.num_blocks as i32 {
|
||||
continue;
|
||||
}
|
||||
let sym_offset = base + sym_idx * wf.block_stride;
|
||||
for tone in 0..4 {
|
||||
let elem = *wf_mag_safe(wf, sym_offset + tone);
|
||||
@@ -399,7 +457,14 @@ fn ft2_extract_likelihood(wf: &Waterfall, cand: &Candidate, log174: &mut [f32; F
|
||||
|
||||
// Map to 174 data bits, selecting max-magnitude metric
|
||||
for data_sym in 0..FT2_ND {
|
||||
let frame_sym = data_sym + if data_sym < 29 { 4 } else if data_sym < 58 { 8 } else { 12 };
|
||||
let frame_sym = data_sym
|
||||
+ if data_sym < 29 {
|
||||
4
|
||||
} else if data_sym < 58 {
|
||||
8
|
||||
} else {
|
||||
12
|
||||
};
|
||||
let src_bit = 2 * frame_sym;
|
||||
let dst_bit = 2 * data_sym;
|
||||
|
||||
@@ -418,7 +483,12 @@ fn ft2_extract_likelihood(wf: &Waterfall, cand: &Candidate, log174: &mut [f32; F
|
||||
}
|
||||
}
|
||||
|
||||
fn ft2_extract_logl_seq(symbols: &[[Complex32; 103]; 4], start_sym: usize, n_syms: usize, metrics: &mut [f32]) {
|
||||
fn ft2_extract_logl_seq(
|
||||
symbols: &[[Complex32; 103]; 4],
|
||||
start_sym: usize,
|
||||
n_syms: usize,
|
||||
metrics: &mut [f32],
|
||||
) {
|
||||
let n_bits = 2 * n_syms;
|
||||
let n_sequences = 1 << n_bits;
|
||||
|
||||
@@ -438,7 +508,9 @@ fn ft2_extract_logl_seq(symbols: &[[Complex32; 103]; 4], start_sym: usize, n_sym
|
||||
let strength = sum.norm();
|
||||
let mask_bit = n_bits - bit - 1;
|
||||
if (seq >> mask_bit) & 1 != 0 {
|
||||
if strength > max_one { max_one = strength; }
|
||||
if strength > max_one {
|
||||
max_one = strength;
|
||||
}
|
||||
} else if strength > max_zero {
|
||||
max_zero = strength;
|
||||
}
|
||||
@@ -470,7 +542,9 @@ fn ftx_normalize_logl(log174: &mut [f32; FTX_LDPC_N]) {
|
||||
/// Pack bits into bytes (MSB first).
|
||||
pub fn pack_bits(bit_array: &[u8], num_bits: usize, packed: &mut [u8]) {
|
||||
let num_bytes = num_bits.div_ceil(8);
|
||||
for b in packed[..num_bytes].iter_mut() { *b = 0; }
|
||||
for b in packed[..num_bytes].iter_mut() {
|
||||
*b = 0;
|
||||
}
|
||||
let mut mask: u8 = 0x80;
|
||||
let mut byte_idx = 0;
|
||||
for i in 0..num_bits {
|
||||
@@ -560,7 +634,9 @@ pub fn ftx_post_decode_snr(wf: &Waterfall, cand: &Candidate, message: &FtxMessag
|
||||
|
||||
for sym in 0..nn {
|
||||
let block_abs = cand.time_offset as i32 + sym as i32;
|
||||
if block_abs < 0 || block_abs >= wf.num_blocks as i32 { continue; }
|
||||
if block_abs < 0 || block_abs >= wf.num_blocks as i32 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let p_offset = base + sym * wf.block_stride;
|
||||
let sig_db = wf_mag_safe(wf, p_offset + tones[sym] as usize).mag;
|
||||
@@ -568,7 +644,9 @@ pub fn ftx_post_decode_snr(wf: &Waterfall, cand: &Candidate, message: &FtxMessag
|
||||
let mut noise_min = 0.0f32;
|
||||
let mut found_noise = false;
|
||||
for t in 0..num_tones {
|
||||
if t == tones[sym] as usize { continue; }
|
||||
if t == tones[sym] as usize {
|
||||
continue;
|
||||
}
|
||||
let db = wf_mag_safe(wf, p_offset + t).mag;
|
||||
if !found_noise || db < noise_min {
|
||||
noise_min = db;
|
||||
|
||||
@@ -138,6 +138,7 @@ impl Ft8Decoder {
|
||||
/// Reset the decoder state for a new decode cycle.
|
||||
pub fn reset(&mut self) {
|
||||
self.monitor.reset();
|
||||
self.callsign_hash.cleanup(10);
|
||||
if let Some(ref mut pipe) = self.ft2_pipeline {
|
||||
pipe.reset();
|
||||
}
|
||||
|
||||
@@ -7,9 +7,7 @@ use crate::constants::{
|
||||
FTX_LDPC_GENERATOR,
|
||||
};
|
||||
use crate::crc::ftx_add_crc;
|
||||
use crate::protocol::{
|
||||
FT4_NN, FT8_NN, FTX_LDPC_K, FTX_LDPC_K_BYTES, FTX_LDPC_M, FTX_LDPC_N_BYTES,
|
||||
};
|
||||
use crate::protocol::{FT4_NN, FT8_NN, FTX_LDPC_K, FTX_LDPC_K_BYTES, FTX_LDPC_M, FTX_LDPC_N_BYTES};
|
||||
|
||||
/// Returns 1 if an odd number of bits are set in `x`, zero otherwise.
|
||||
fn parity8(x: u8) -> u8 {
|
||||
@@ -29,11 +27,7 @@ fn parity8(x: u8) -> u8 {
|
||||
fn encode174(message: &[u8], codeword: &mut [u8]) {
|
||||
// Fill the codeword with message and zeros
|
||||
for j in 0..FTX_LDPC_N_BYTES {
|
||||
codeword[j] = if j < FTX_LDPC_K_BYTES {
|
||||
message[j]
|
||||
} else {
|
||||
0
|
||||
};
|
||||
codeword[j] = if j < FTX_LDPC_K_BYTES { message[j] } else { 0 };
|
||||
}
|
||||
|
||||
// Compute the byte index and bit mask for the first checksum bit
|
||||
@@ -230,8 +224,9 @@ mod tests {
|
||||
// The codeword should start with the message bytes (systematic code).
|
||||
// Byte 11 shares bits between the last 3 message bits and the first
|
||||
// parity bits, so only check bytes 0..10 for exact match.
|
||||
let message: [u8; FTX_LDPC_K_BYTES] =
|
||||
[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x22, 0x33, 0x40];
|
||||
let message: [u8; FTX_LDPC_K_BYTES] = [
|
||||
0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x22, 0x33, 0x40,
|
||||
];
|
||||
let mut codeword = [0u8; FTX_LDPC_N_BYTES];
|
||||
encode174(&message, &mut codeword);
|
||||
|
||||
@@ -246,8 +241,9 @@ mod tests {
|
||||
#[test]
|
||||
fn encode174_nonzero_parity() {
|
||||
// A non-zero message should produce non-zero parity bits
|
||||
let message: [u8; FTX_LDPC_K_BYTES] =
|
||||
[0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xE0];
|
||||
let message: [u8; FTX_LDPC_K_BYTES] = [
|
||||
0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xE0,
|
||||
];
|
||||
let mut codeword = [0u8; FTX_LDPC_N_BYTES];
|
||||
encode174(&message, &mut codeword);
|
||||
|
||||
@@ -255,7 +251,10 @@ mod tests {
|
||||
let parity_nonzero = codeword[FTX_LDPC_K_BYTES..FTX_LDPC_N_BYTES]
|
||||
.iter()
|
||||
.any(|&b| b != 0);
|
||||
assert!(parity_nonzero, "Parity bits should be non-zero for non-zero input");
|
||||
assert!(
|
||||
parity_nonzero,
|
||||
"Parity bits should be non-zero for non-zero input"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -65,10 +65,8 @@ pub fn ldpc_decode(
|
||||
plain: &mut [u8; FTX_LDPC_N],
|
||||
) -> i32 {
|
||||
// Allocate m[][] and e[][] on the heap (~60 kB each) to avoid stack overflow.
|
||||
let mut m_matrix: Vec<Vec<f32>> =
|
||||
vec![vec![0.0f32; FTX_LDPC_N]; FTX_LDPC_M];
|
||||
let mut e_matrix: Vec<Vec<f32>> =
|
||||
vec![vec![0.0f32; FTX_LDPC_N]; FTX_LDPC_M];
|
||||
let mut m_matrix: Vec<Vec<f32>> = vec![vec![0.0f32; FTX_LDPC_N]; FTX_LDPC_M];
|
||||
let mut e_matrix: Vec<Vec<f32>> = vec![vec![0.0f32; FTX_LDPC_N]; FTX_LDPC_M];
|
||||
|
||||
// Initialize m[][] with the channel LLRs.
|
||||
for j in 0..FTX_LDPC_M {
|
||||
|
||||
@@ -2,21 +2,14 @@
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
pub mod protocol;
|
||||
pub mod callsign_hash;
|
||||
pub mod constants;
|
||||
pub mod crc;
|
||||
pub mod text;
|
||||
#[allow(clippy::manual_memcpy, clippy::needless_range_loop)]
|
||||
pub mod ldpc;
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
pub mod encode;
|
||||
pub mod callsign_hash;
|
||||
#[allow(clippy::explicit_counter_loop, clippy::needless_range_loop)]
|
||||
pub mod message;
|
||||
#[allow(dead_code)]
|
||||
pub mod monitor;
|
||||
#[allow(dead_code, clippy::needless_range_loop)]
|
||||
pub mod decode;
|
||||
mod decoder;
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
pub mod encode;
|
||||
#[allow(
|
||||
dead_code,
|
||||
clippy::manual_memcpy,
|
||||
@@ -24,6 +17,13 @@ pub mod decode;
|
||||
clippy::too_many_arguments
|
||||
)]
|
||||
pub mod ft2;
|
||||
mod decoder;
|
||||
#[allow(clippy::manual_memcpy, clippy::needless_range_loop)]
|
||||
pub mod ldpc;
|
||||
#[allow(clippy::explicit_counter_loop, clippy::needless_range_loop)]
|
||||
pub mod message;
|
||||
#[allow(dead_code)]
|
||||
pub mod monitor;
|
||||
pub mod protocol;
|
||||
pub mod text;
|
||||
|
||||
pub use decoder::{Ft8Decoder, Ft8DecodeResult};
|
||||
pub use decoder::{Ft8DecodeResult, Ft8Decoder};
|
||||
|
||||
@@ -178,9 +178,7 @@ fn is_space(c: u8) -> bool {
|
||||
/// whitespace).
|
||||
fn copy_token(input: &str) -> (&str, String) {
|
||||
let input = input.trim_start();
|
||||
let end = input
|
||||
.find(' ')
|
||||
.unwrap_or(input.len());
|
||||
let end = input.find(' ').unwrap_or(input.len());
|
||||
let token = &input[..end];
|
||||
let rest = &input[end..].trim_start();
|
||||
(rest, token.to_string())
|
||||
@@ -307,11 +305,7 @@ pub fn pack_basecall(callsign: &str) -> Option<i32> {
|
||||
c6[i + 3] = b;
|
||||
}
|
||||
}
|
||||
} else if starts_with(callsign, "3X")
|
||||
&& length > 2
|
||||
&& is_letter(bytes[2])
|
||||
&& length <= 7
|
||||
{
|
||||
} else if starts_with(callsign, "3X") && length > 2 && is_letter(bytes[2]) && length <= 7 {
|
||||
// Guinea prefix: 3XA0XYZ -> QA0XYZ
|
||||
c6[0] = b'Q';
|
||||
for (i, &b) in bytes[2..].iter().enumerate() {
|
||||
@@ -361,10 +355,7 @@ pub fn pack_basecall(callsign: &str) -> Option<i32> {
|
||||
|
||||
/// Pack a special token, a 22-bit hash code, or a valid base call into a
|
||||
/// 28-bit integer. Returns `(n28, ip)` on success, or `None` on error.
|
||||
fn pack28(
|
||||
callsign: &str,
|
||||
hash_table: Option<&mut CallsignHashTable>,
|
||||
) -> Option<(i32, u8)> {
|
||||
fn pack28(callsign: &str, hash_table: Option<&mut CallsignHashTable>) -> Option<(i32, u8)> {
|
||||
let mut ip: u8 = 0;
|
||||
|
||||
// Check for special tokens
|
||||
@@ -452,11 +443,7 @@ fn unpack28(
|
||||
let n28_adj = n28 - NTOKENS;
|
||||
if n28_adj < MAX22 {
|
||||
// 22-bit hashed callsign
|
||||
let call = lookup_callsign(
|
||||
hash_table.as_deref(),
|
||||
HashType::Hash22Bits,
|
||||
n28_adj,
|
||||
);
|
||||
let call = lookup_callsign(hash_table.as_deref(), HashType::Hash22Bits, n28_adj);
|
||||
return Some((call, FtxFieldType::Call));
|
||||
}
|
||||
|
||||
@@ -519,10 +506,7 @@ fn unpack28(
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Pack a non-standard callsign into a 58-bit integer.
|
||||
fn pack58(
|
||||
hash_table: Option<&mut CallsignHashTable>,
|
||||
callsign: &str,
|
||||
) -> Option<u64> {
|
||||
fn pack58(hash_table: Option<&mut CallsignHashTable>, callsign: &str) -> Option<u64> {
|
||||
let src = callsign.trim_start_matches('<').trim_end_matches('>');
|
||||
|
||||
let mut result: u64 = 0;
|
||||
@@ -545,10 +529,7 @@ fn pack58(
|
||||
}
|
||||
|
||||
/// Unpack a non-standard callsign from a 58-bit integer.
|
||||
fn unpack58(
|
||||
n58: u64,
|
||||
hash_table: Option<&mut CallsignHashTable>,
|
||||
) -> Option<String> {
|
||||
fn unpack58(n58: u64, hash_table: Option<&mut CallsignHashTable>) -> Option<String> {
|
||||
let mut c11 = [0u8; 11];
|
||||
let mut n = n58;
|
||||
|
||||
@@ -643,11 +624,7 @@ fn unpackgrid(igrid4: u16, ir: u8) -> Option<(String, FtxFieldType)> {
|
||||
(b'0' + d3) as char,
|
||||
);
|
||||
|
||||
let result = if ir > 0 {
|
||||
format!("R {}", grid)
|
||||
} else {
|
||||
grid
|
||||
};
|
||||
let result = if ir > 0 { format!("R {}", grid) } else { grid };
|
||||
|
||||
Some((result, FtxFieldType::Grid))
|
||||
} else {
|
||||
@@ -776,10 +753,7 @@ pub fn ftx_message_encode_std(
|
||||
|
||||
let icq = call_to == "CQ" || starts_with(call_to, "CQ ");
|
||||
if let Some(slash_pos) = call_de.find('/') {
|
||||
if slash_pos >= 2
|
||||
&& icq
|
||||
&& !(call_de.ends_with("/P") || call_de.ends_with("/R"))
|
||||
{
|
||||
if slash_pos >= 2 && icq && !(call_de.ends_with("/P") || call_de.ends_with("/R")) {
|
||||
return FtxMessageRc::ErrorCallsign2;
|
||||
}
|
||||
}
|
||||
@@ -952,8 +926,7 @@ pub fn ftx_message_decode(
|
||||
let msg_type = msg.get_type();
|
||||
|
||||
let (field1, field2, field3, rc) = match msg_type {
|
||||
FtxMessageType::Standard => {
|
||||
match ftx_message_decode_std(msg, hash_table) {
|
||||
FtxMessageType::Standard => match ftx_message_decode_std(msg, hash_table) {
|
||||
(Some(f1), Some(f2), Some(f3), types, rc) => {
|
||||
offsets.types = types;
|
||||
(Some(f1), Some(f2), Some(f3), rc)
|
||||
@@ -962,10 +935,8 @@ pub fn ftx_message_decode(
|
||||
offsets.types = types;
|
||||
(f1, f2, f3, rc)
|
||||
}
|
||||
}
|
||||
}
|
||||
FtxMessageType::NonstdCall => {
|
||||
match ftx_message_decode_nonstd(msg, hash_table) {
|
||||
},
|
||||
FtxMessageType::NonstdCall => match ftx_message_decode_nonstd(msg, hash_table) {
|
||||
(Some(f1), Some(f2), Some(f3), types, rc) => {
|
||||
offsets.types = types;
|
||||
(Some(f1), Some(f2), Some(f3), rc)
|
||||
@@ -974,8 +945,7 @@ pub fn ftx_message_decode(
|
||||
offsets.types = types;
|
||||
(f1, f2, f3, rc)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
FtxMessageType::FreeText => {
|
||||
let text = ftx_message_decode_free(msg);
|
||||
(Some(text), None, None, FtxMessageRc::Ok)
|
||||
@@ -1053,7 +1023,15 @@ pub fn ftx_message_decode_std(
|
||||
|
||||
let (call_de, ft1) = match unpack28(n29b >> 1, (n29b & 1) as u8, i3, Some(hash_table)) {
|
||||
Some(v) => v,
|
||||
None => return (Some(call_to), None, None, field_types, FtxMessageRc::ErrorCallsign2),
|
||||
None => {
|
||||
return (
|
||||
Some(call_to),
|
||||
None,
|
||||
None,
|
||||
field_types,
|
||||
FtxMessageRc::ErrorCallsign2,
|
||||
)
|
||||
}
|
||||
};
|
||||
field_types[1] = ft1;
|
||||
|
||||
|
||||
@@ -38,7 +38,13 @@ pub struct Waterfall {
|
||||
}
|
||||
|
||||
impl Waterfall {
|
||||
pub fn new(max_blocks: usize, num_bins: usize, time_osr: usize, freq_osr: usize, protocol: FtxProtocol) -> Self {
|
||||
pub fn new(
|
||||
max_blocks: usize,
|
||||
num_bins: usize,
|
||||
time_osr: usize,
|
||||
freq_osr: usize,
|
||||
protocol: FtxProtocol,
|
||||
) -> Self {
|
||||
let block_stride = time_osr * freq_osr * num_bins;
|
||||
let mag = vec![WfElem::default(); max_blocks * block_stride];
|
||||
Self {
|
||||
@@ -115,7 +121,13 @@ impl Monitor {
|
||||
let num_bins = max_bin - min_bin;
|
||||
let max_blocks = (slot_time / symbol_period) as usize;
|
||||
|
||||
let wf = Waterfall::new(max_blocks, num_bins, cfg.time_osr as usize, cfg.freq_osr as usize, cfg.protocol);
|
||||
let wf = Waterfall::new(
|
||||
max_blocks,
|
||||
num_bins,
|
||||
cfg.time_osr as usize,
|
||||
cfg.freq_osr as usize,
|
||||
cfg.protocol,
|
||||
);
|
||||
|
||||
let mut real_planner = RealFftPlanner::<f32>::new();
|
||||
let real_fft = real_planner.plan_fft_forward(nfft);
|
||||
@@ -168,7 +180,8 @@ impl Monitor {
|
||||
for _time_sub in 0..self.wf.time_osr {
|
||||
// Shift new data into analysis frame
|
||||
let shift = self.nfft - self.subblock_size;
|
||||
self.last_frame.copy_within(self.subblock_size..self.nfft, 0);
|
||||
self.last_frame
|
||||
.copy_within(self.subblock_size..self.nfft, 0);
|
||||
for pos in shift..self.nfft {
|
||||
self.last_frame[pos] = if frame_pos < frame.len() {
|
||||
frame[frame_pos]
|
||||
@@ -183,7 +196,11 @@ impl Monitor {
|
||||
self.fft_input[pos] = self.window[pos] * self.last_frame[pos];
|
||||
}
|
||||
self.real_fft
|
||||
.process_with_scratch(&mut self.fft_input, &mut self.fft_output, &mut self.fft_scratch)
|
||||
.process_with_scratch(
|
||||
&mut self.fft_input,
|
||||
&mut self.fft_output,
|
||||
&mut self.fft_scratch,
|
||||
)
|
||||
.expect("FFT process failed");
|
||||
|
||||
// Extract magnitude and phase for each frequency sub-bin
|
||||
@@ -206,7 +223,10 @@ impl Monitor {
|
||||
}
|
||||
} else {
|
||||
if offset < self.wf.mag.len() {
|
||||
self.wf.mag[offset] = WfElem { mag: -120.0, phase: 0.0 };
|
||||
self.wf.mag[offset] = WfElem {
|
||||
mag: -120.0,
|
||||
phase: 0.0,
|
||||
};
|
||||
}
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
@@ -36,32 +36,56 @@ impl FtxProtocol {
|
||||
|
||||
/// Number of data symbols.
|
||||
pub fn nd(self) -> usize {
|
||||
if self.uses_ft4_layout() { FT4_ND } else { FT8_ND }
|
||||
if self.uses_ft4_layout() {
|
||||
FT4_ND
|
||||
} else {
|
||||
FT8_ND
|
||||
}
|
||||
}
|
||||
|
||||
/// Total channel symbols.
|
||||
pub fn nn(self) -> usize {
|
||||
if self.uses_ft4_layout() { FT4_NN } else { FT8_NN }
|
||||
if self.uses_ft4_layout() {
|
||||
FT4_NN
|
||||
} else {
|
||||
FT8_NN
|
||||
}
|
||||
}
|
||||
|
||||
/// Length of each sync group.
|
||||
pub fn sync_length(self) -> usize {
|
||||
if self.uses_ft4_layout() { FT4_LENGTH_SYNC } else { FT8_LENGTH_SYNC }
|
||||
if self.uses_ft4_layout() {
|
||||
FT4_LENGTH_SYNC
|
||||
} else {
|
||||
FT8_LENGTH_SYNC
|
||||
}
|
||||
}
|
||||
|
||||
/// Number of sync groups.
|
||||
pub fn num_sync(self) -> usize {
|
||||
if self.uses_ft4_layout() { FT4_NUM_SYNC } else { FT8_NUM_SYNC }
|
||||
if self.uses_ft4_layout() {
|
||||
FT4_NUM_SYNC
|
||||
} else {
|
||||
FT8_NUM_SYNC
|
||||
}
|
||||
}
|
||||
|
||||
/// Offset between sync groups.
|
||||
pub fn sync_offset(self) -> usize {
|
||||
if self.uses_ft4_layout() { FT4_SYNC_OFFSET } else { FT8_SYNC_OFFSET }
|
||||
if self.uses_ft4_layout() {
|
||||
FT4_SYNC_OFFSET
|
||||
} else {
|
||||
FT8_SYNC_OFFSET
|
||||
}
|
||||
}
|
||||
|
||||
/// Number of FSK tones.
|
||||
pub fn num_tones(self) -> usize {
|
||||
if self.uses_ft4_layout() { 4 } else { 8 }
|
||||
if self.uses_ft4_layout() {
|
||||
4
|
||||
} else {
|
||||
8
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,16 +108,14 @@ pub fn nchar(c: char, table: CharTable) -> Option<i32> {
|
||||
|
||||
// Extra symbols
|
||||
match table {
|
||||
CharTable::Full => {
|
||||
match c {
|
||||
CharTable::Full => match c {
|
||||
'+' => return Some(n),
|
||||
'-' => return Some(n + 1),
|
||||
'.' => return Some(n + 2),
|
||||
'/' => return Some(n + 3),
|
||||
'?' => return Some(n + 4),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
},
|
||||
CharTable::AlphanumSpaceSlash => {
|
||||
if c == '/' {
|
||||
return Some(n);
|
||||
@@ -240,11 +238,7 @@ mod tests {
|
||||
let expected = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?";
|
||||
for (i, ch) in expected.chars().enumerate() {
|
||||
assert_eq!(charn(i as i32, CharTable::Full), ch, "charn({i})");
|
||||
assert_eq!(
|
||||
nchar(ch, CharTable::Full),
|
||||
Some(i as i32),
|
||||
"nchar('{ch}')"
|
||||
);
|
||||
assert_eq!(nchar(ch, CharTable::Full), Some(i as i32), "nchar('{ch}')");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,11 +263,7 @@ mod tests {
|
||||
fn alphanum_space_round_trip() {
|
||||
let expected = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
for (i, ch) in expected.chars().enumerate() {
|
||||
assert_eq!(
|
||||
charn(i as i32, CharTable::AlphanumSpace),
|
||||
ch,
|
||||
"charn({i})"
|
||||
);
|
||||
assert_eq!(charn(i as i32, CharTable::AlphanumSpace), ch, "charn({i})");
|
||||
assert_eq!(
|
||||
nchar(ch, CharTable::AlphanumSpace),
|
||||
Some(i as i32),
|
||||
@@ -286,11 +276,7 @@ mod tests {
|
||||
fn letters_space_round_trip() {
|
||||
let expected = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
for (i, ch) in expected.chars().enumerate() {
|
||||
assert_eq!(
|
||||
charn(i as i32, CharTable::LettersSpace),
|
||||
ch,
|
||||
"charn({i})"
|
||||
);
|
||||
assert_eq!(charn(i as i32, CharTable::LettersSpace), ch, "charn({i})");
|
||||
assert_eq!(
|
||||
nchar(ch, CharTable::LettersSpace),
|
||||
Some(i as i32),
|
||||
|
||||
@@ -290,7 +290,12 @@ mod tests {
|
||||
let base_hz = 1496.0_f32;
|
||||
let start = EXPECTED_SIGNAL_START_SAMPLES;
|
||||
|
||||
for (sym, sync_tone) in SYNC_VECTOR.iter().copied().enumerate().take(WSPR_SYMBOL_COUNT) {
|
||||
for (sym, sync_tone) in SYNC_VECTOR
|
||||
.iter()
|
||||
.copied()
|
||||
.enumerate()
|
||||
.take(WSPR_SYMBOL_COUNT)
|
||||
{
|
||||
let tone = sync_tone + 2 * ((sym % 2) as u8);
|
||||
let freq = base_hz + tone as f32 * TONE_SPACING_HZ;
|
||||
let begin = start + sym * WSPR_SYMBOL_SAMPLES;
|
||||
@@ -347,7 +352,12 @@ mod tests {
|
||||
|
||||
// Generate a synthetic WSPR-like signal using the sync vector
|
||||
let mut signal = vec![0.0_f32; WSPR_SIGNAL_SAMPLES];
|
||||
for (sym, sync_tone) in SYNC_VECTOR.iter().copied().enumerate().take(WSPR_SYMBOL_COUNT) {
|
||||
for (sym, sync_tone) in SYNC_VECTOR
|
||||
.iter()
|
||||
.copied()
|
||||
.enumerate()
|
||||
.take(WSPR_SYMBOL_COUNT)
|
||||
{
|
||||
let freq = base_hz + sync_tone as f32 * TONE_SPACING_HZ;
|
||||
let begin = sym * WSPR_SYMBOL_SAMPLES;
|
||||
for i in 0..WSPR_SYMBOL_SAMPLES {
|
||||
|
||||
Reference in New Issue
Block a user