[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:
2026-03-19 18:38:27 +01:00
parent f4bfaa70d2
commit 4c728bd8da
12 changed files with 729 additions and 294 deletions
+50 -5
View File
@@ -106,10 +106,12 @@ impl CallsignHashTable {
/// The `hash` parameter is the full 22-bit hash value. If an entry /// 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 /// with the same 22-bit hash already exists, its callsign and age are
/// updated in place. Otherwise, the entry is inserted into the first /// 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) { pub fn add(&mut self, callsign: &str, hash: u32) {
let hash22 = hash & HASH22_MASK; 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 { loop {
match &self.entries[idx] { match &self.entries[idx] {
@@ -124,6 +126,14 @@ impl CallsignHashTable {
Some(_) => { Some(_) => {
// Collision — linear probe to next slot. // Collision — linear probe to next slot.
idx = (idx + 1) % CALLSIGN_HASHTABLE_SIZE; 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 => { None => {
// Empty slot — insert here. // Empty slot — insert here.
@@ -142,12 +152,12 @@ impl CallsignHashTable {
/// determine which bits to compare. /// determine which bits to compare.
/// ///
/// Returns `Some(callsign)` if a matching entry is found, or `None` /// Returns `Some(callsign)` if a matching entry is found, or `None`
/// if the probe sequence reaches an empty slot without finding a /// if no match is found within a full probe cycle.
/// match.
pub fn lookup(&self, hash_type: HashType, hash: u32) -> Option<String> { pub fn lookup(&self, hash_type: HashType, hash: u32) -> Option<String> {
let (shift, mask) = hash_type.shift_and_mask(); let (shift, mask) = hash_type.shift_and_mask();
let target = hash & 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 { loop {
match &self.entries[idx] { match &self.entries[idx] {
@@ -157,6 +167,9 @@ impl CallsignHashTable {
return Some(entry.callsign.clone()); return Some(entry.callsign.clone());
} }
idx = (idx + 1) % CALLSIGN_HASHTABLE_SIZE; idx = (idx + 1) % CALLSIGN_HASHTABLE_SIZE;
if idx == start_idx {
return None;
}
} }
None => return None, None => return None,
} }
@@ -391,6 +404,38 @@ mod tests {
assert!(hash <= 0x3F_FFFF, "hash should fit in 22 bits"); 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] #[test]
fn compute_hash_invalid_char_returns_none() { fn compute_hash_invalid_char_returns_none() {
// Lowercase letters are not in the AlphanumSpaceSlash table. // Lowercase letters are not in the AlphanumSpaceSlash table.
+429 -133
View File
@@ -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]; 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). /// Costas sync tone patterns for FT4 (4 groups of 4 tones).
pub const FT4_COSTAS_PATTERN: [[u8; 4]; 4] = [ pub const FT4_COSTAS_PATTERN: [[u8; 4]; 4] =
[0, 1, 3, 2], [[0, 1, 3, 2], [1, 0, 2, 3], [2, 3, 1, 0], [3, 2, 0, 1]];
[1, 0, 2, 3],
[2, 3, 1, 0],
[3, 2, 0, 1],
];
/// Gray code map for FT8 (8 symbols, 3 bits). /// Gray code map for FT8 (8 symbols, 3 bits).
pub const FT8_GRAY_MAP: [u8; 8] = [0, 1, 3, 2, 5, 6, 4, 7]; 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]; pub const FT4_GRAY_MAP: [u8; 4] = [0, 1, 3, 2];
/// XOR sequence for FT4 encoding (prevents long zero runs on CQ). /// XOR sequence for FT4 encoding (prevents long zero runs on CQ).
pub const FT4_XOR_SEQUENCE: [u8; 10] = [ pub const FT4_XOR_SEQUENCE: [u8; 10] = [0x4A, 0x5E, 0x89, 0xB4, 0xB0, 0x8A, 0x79, 0x55, 0xBE, 0x28];
0x4A, 0x5E, 0x89, 0xB4, 0xB0, 0x8A, 0x79, 0x55, 0xBE, 0x28,
];
/// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first). /// 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] = [ 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], 0x83, 0x29, 0xce, 0x11, 0xbf, 0x31, 0xea, 0xf5, 0x09, 0xf2, 0x7f, 0xc0,
[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], 0x76, 0x1c, 0x26, 0x4e, 0x25, 0xc2, 0x59, 0x33, 0x54, 0x93, 0x13, 0x20,
[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], 0xdc, 0x26, 0x59, 0x02, 0xfb, 0x27, 0x7c, 0x64, 0x10, 0xa1, 0xbd, 0xc0,
[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], 0x1b, 0x3f, 0x41, 0x78, 0x58, 0xcd, 0x2d, 0xd3, 0x3e, 0xc7, 0xf6, 0x20,
[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], 0x09, 0xfd, 0xa4, 0xfe, 0xe0, 0x41, 0x95, 0xfd, 0x03, 0x47, 0x83, 0xa0,
[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], 0x07, 0x7c, 0xcc, 0xc1, 0x1b, 0x88, 0x73, 0xed, 0x5c, 0x3d, 0x48, 0xa0,
[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], 0x29, 0xb6, 0x2a, 0xfe, 0x3c, 0xa0, 0x36, 0xf4, 0xfe, 0x1a, 0x9d, 0xa0,
[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], 0x60, 0x54, 0xfa, 0xf5, 0xf3, 0x5d, 0x96, 0xd3, 0xb0, 0xc8, 0xc3, 0xe0,
[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], 0xe2, 0x07, 0x98, 0xe4, 0x31, 0x0e, 0xed, 0x27, 0x88, 0x4a, 0xe9, 0x00,
[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], 0x77, 0x5c, 0x9c, 0x08, 0xe8, 0x0e, 0x26, 0xdd, 0xae, 0x56, 0x31, 0x80,
[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], 0xb0, 0xb8, 0x11, 0x02, 0x8c, 0x2b, 0xf9, 0x97, 0x21, 0x34, 0x87, 0xc0,
[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], 0x18, 0xa0, 0xc9, 0x23, 0x1f, 0xc6, 0x0a, 0xdf, 0x5c, 0x5e, 0xa3, 0x20,
[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], 0x76, 0x47, 0x1e, 0x83, 0x02, 0xa0, 0x72, 0x1e, 0x01, 0xb1, 0x2b, 0x80,
[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], 0xff, 0xbc, 0xcb, 0x80, 0xca, 0x83, 0x41, 0xfa, 0xfb, 0x47, 0xb2, 0xe0,
[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], 0x66, 0xa7, 0x2a, 0x15, 0x8f, 0x93, 0x25, 0xa2, 0xbf, 0x67, 0x17, 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], 0xc4, 0x24, 0x36, 0x89, 0xfe, 0x85, 0xb1, 0xc5, 0x13, 0x63, 0xa1, 0x80,
[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], 0x0d, 0xff, 0x73, 0x94, 0x14, 0xd1, 0xa1, 0xb3, 0x4b, 0x1c, 0x27, 0x00,
[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], 0x15, 0xb4, 0x88, 0x30, 0x63, 0x6c, 0x8b, 0x99, 0x89, 0x49, 0x72, 0xe0,
[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], 0x29, 0xa8, 0x9c, 0x0d, 0x3d, 0xe8, 0x1d, 0x66, 0x54, 0x89, 0xb0, 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], 0x4f, 0x12, 0x6f, 0x37, 0xfa, 0x51, 0xcb, 0xe6, 0x1b, 0xd6, 0xb9, 0x40,
[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], 0x99, 0xc4, 0x72, 0x39, 0xd0, 0xd9, 0x7d, 0x3c, 0x84, 0xe0, 0x94, 0x00,
[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], 0x19, 0x19, 0xb7, 0x51, 0x19, 0x76, 0x56, 0x21, 0xbb, 0x4f, 0x1e, 0x80,
[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], 0x09, 0xdb, 0x12, 0xd7, 0x31, 0xfa, 0xee, 0x0b, 0x86, 0xdf, 0x6b, 0x80,
[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], 0x48, 0x8f, 0xc3, 0x3d, 0xf4, 0x3f, 0xbd, 0xee, 0xa4, 0xea, 0xfb, 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], 0x82, 0x74, 0x23, 0xee, 0x40, 0xb6, 0x75, 0xf7, 0x56, 0xeb, 0x5f, 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], 0xab, 0xe1, 0x97, 0xc4, 0x84, 0xcb, 0x74, 0x75, 0x71, 0x44, 0xa9, 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], 0x2b, 0x50, 0x0e, 0x4b, 0xc0, 0xec, 0x5a, 0x6d, 0x2b, 0xdb, 0xdd, 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], 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. /// 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. /// Mn: each row corresponds to a codeword bit.
/// The numbers indicate which three parity checks refer to the codeword bit (1-origin). /// The numbers indicate which three parity checks refer to the codeword bit (1-origin).
pub const FTX_LDPC_MN: [[u8; 3]; FTX_LDPC_N] = [ 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], [16, 45, 73],
[3, 6, 54], [4, 35, 48], [5, 13, 21], [8, 56, 79], [9, 64, 69], [25, 51, 62],
[10, 19, 66], [11, 36, 60], [12, 37, 58], [14, 32, 43], [15, 63, 80], [33, 58, 78],
[17, 28, 77], [18, 74, 83], [22, 53, 81], [23, 30, 34], [24, 31, 40], [1, 44, 45],
[26, 41, 76], [27, 57, 70], [29, 49, 65], [3, 38, 78], [5, 39, 82], [2, 7, 61],
[46, 50, 73], [51, 52, 74], [55, 71, 72], [44, 67, 72], [43, 68, 78], [3, 6, 54],
[1, 32, 59], [2, 6, 71], [4, 16, 54], [7, 65, 67], [8, 30, 42], [4, 35, 48],
[9, 22, 31], [10, 18, 76], [11, 23, 82], [12, 28, 61], [13, 52, 79], [5, 13, 21],
[14, 50, 51], [15, 81, 83], [17, 29, 60], [19, 33, 64], [20, 26, 73], [8, 56, 79],
[21, 34, 40], [24, 27, 77], [25, 55, 58], [35, 53, 66], [36, 48, 68], [9, 64, 69],
[37, 46, 75], [38, 45, 47], [39, 57, 69], [41, 56, 62], [20, 49, 53], [10, 19, 66],
[46, 52, 63], [45, 70, 75], [27, 35, 80], [1, 15, 30], [2, 68, 80], [11, 36, 60],
[3, 36, 51], [4, 28, 51], [5, 31, 56], [6, 20, 37], [7, 40, 82], [12, 37, 58],
[8, 60, 69], [9, 10, 49], [11, 44, 57], [12, 39, 59], [13, 24, 55], [14, 32, 43],
[14, 21, 65], [16, 71, 78], [17, 30, 76], [18, 25, 80], [19, 61, 83], [15, 63, 80],
[22, 38, 77], [23, 41, 50], [7, 26, 58], [29, 32, 81], [33, 40, 73], [17, 28, 77],
[18, 34, 48], [13, 42, 64], [5, 26, 43], [47, 69, 72], [54, 55, 70], [18, 74, 83],
[45, 62, 68], [10, 63, 67], [14, 66, 72], [22, 60, 74], [35, 39, 79], [22, 53, 81],
[1, 46, 64], [1, 24, 66], [2, 5, 70], [3, 31, 65], [4, 49, 58], [23, 30, 34],
[1, 4, 5], [6, 60, 67], [7, 32, 75], [8, 48, 82], [9, 35, 41], [24, 31, 40],
[10, 39, 62], [11, 14, 61], [12, 71, 74], [13, 23, 78], [11, 35, 55], [26, 41, 76],
[15, 16, 79], [7, 9, 16], [17, 54, 63], [18, 50, 57], [19, 30, 47], [27, 57, 70],
[20, 64, 80], [21, 28, 69], [22, 25, 43], [13, 22, 37], [2, 47, 51], [29, 49, 65],
[23, 54, 74], [26, 34, 72], [27, 36, 37], [21, 36, 63], [29, 40, 44], [3, 38, 78],
[19, 26, 57], [3, 46, 82], [23, 54, 74], [33, 52, 53], [30, 43, 52], [5, 39, 82],
[6, 9, 52], [27, 33, 65], [25, 69, 73], [38, 55, 83], [20, 39, 77], [46, 50, 73],
[18, 29, 56], [32, 48, 71], [42, 51, 59], [28, 44, 79], [34, 60, 62], [51, 52, 74],
[31, 45, 61], [46, 68, 77], [6, 24, 76], [8, 10, 78], [40, 41, 70], [55, 71, 72],
[17, 50, 53], [42, 66, 68], [4, 22, 72], [36, 64, 81], [13, 29, 47], [44, 67, 72],
[2, 8, 81], [56, 67, 73], [5, 38, 50], [12, 38, 64], [59, 72, 80], [43, 68, 78],
[3, 26, 79], [45, 76, 81], [1, 65, 74], [7, 18, 77], [11, 56, 59], [1, 32, 59],
[14, 39, 54], [16, 37, 66], [10, 28, 55], [15, 60, 70], [17, 25, 82], [2, 6, 71],
[20, 30, 31], [12, 67, 68], [23, 75, 80], [27, 32, 62], [24, 69, 75], [4, 16, 54],
[19, 21, 71], [34, 53, 61], [35, 46, 47], [33, 59, 76], [40, 43, 83], [7, 65, 67],
[41, 42, 63], [49, 75, 83], [20, 44, 48], [42, 49, 57], [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. /// Number of entries per row in FTX_LDPC_NM.
pub const FTX_LDPC_NUM_ROWS: [u8; FTX_LDPC_M] = [ 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, 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, 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, 7, 6, 6, 6, 7, 7, 6, 6, 6, 6, 7, 7, 6, 6, 6, 6, 6, 7, 7, 6, 6, 7, 6, 6, 6, 6, 6, 6, 6, 7, 6, 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,
]; ];
+105 -27
View File
@@ -9,7 +9,7 @@
use num_complex::Complex32; use num_complex::Complex32;
use crate::constants::*; use crate::constants::*;
use crate::monitor::{WfElem, Waterfall}; use crate::monitor::{Waterfall, WfElem};
use crate::protocol::*; use crate::protocol::*;
/// Candidate position in time and frequency. /// 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() { if i < wf.mag.len() {
&wf.mag[i] &wf.mag[i]
} else { } else {
&WfElem { mag: -120.0, phase: 0.0 } &WfElem {
mag: -120.0,
phase: 0.0,
}
} }
} }
// Leaked reference for out-of-bounds default // 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 { 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 { 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 { for k in 0..FT8_LENGTH_SYNC {
let block = FT8_SYNC_OFFSET * m + k; let block = FT8_SYNC_OFFSET * m + k;
let block_abs = cand.time_offset as i32 + block as i32; let block_abs = cand.time_offset as i32 + block as i32;
if block_abs < 0 { continue; } if block_abs < 0 {
if block_abs >= wf.num_blocks as i32 { break; } continue;
}
if block_abs >= wf.num_blocks as i32 {
break;
}
let p_offset = base + block * wf.block_stride; let p_offset = base + block * wf.block_stride;
let sm = FT8_COSTAS_PATTERN[k] as usize; 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 { if k > 0 && block_abs > 0 {
let a = wf_mag_safe(wf, p_offset + sm).mag_int(); let a = wf_mag_safe(wf, p_offset + sm).mag_int();
let b_idx = (p_offset + sm).wrapping_sub(wf.block_stride); 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; score += a - b;
num_average += 1; 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 { 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 { for k in 0..FT4_LENGTH_SYNC {
let block = 1 + FT4_SYNC_OFFSET * m + k; let block = 1 + FT4_SYNC_OFFSET * m + k;
let block_abs = cand.time_offset as i32 + block as i32; let block_abs = cand.time_offset as i32 + block as i32;
if block_abs < 0 { continue; } if block_abs < 0 {
if block_abs >= wf.num_blocks as i32 { break; } continue;
}
if block_abs >= wf.num_blocks as i32 {
break;
}
let p_offset = base + block * wf.block_stride; let p_offset = base + block * wf.block_stride;
let sm = FT4_COSTAS_PATTERN[m][k] as usize; 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 { if k > 0 && block_abs > 0 {
let a = wf_mag_safe(wf, p_offset + sm).mag_int(); let a = wf_mag_safe(wf, p_offset + sm).mag_int();
let b_idx = (p_offset + sm).wrapping_sub(wf.block_stride); 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; score += a - b;
num_average += 1; 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 { 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); let elem = *wf_mag_safe(wf, sym_offset + tone);
sum += wf_elem_to_complex(elem); sum += wf_elem_to_complex(elem);
} }
if !complete { continue; } if !complete {
continue;
}
score_f += sum.norm(); score_f += sum.norm();
groups += 1; groups += 1;
} }
if groups == 0 { return 0; } if groups == 0 {
return 0;
}
(score_f / groups as f32 * 8.0).round() as i32 (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 left = 2 * current + 1;
let right = left + 1; let right = left + 1;
let mut smallest = current; let mut smallest = current;
if left < size && heap[left].score < heap[smallest].score { smallest = left; } if left < size && heap[left].score < heap[smallest].score {
if right < size && heap[right].score < heap[smallest].score { smallest = right; } smallest = left;
if smallest == current { break; } }
if right < size && heap[right].score < heap[smallest].score {
smallest = right;
}
if smallest == current {
break;
}
heap.swap(current, smallest); heap.swap(current, smallest);
current = smallest; current = smallest;
} }
@@ -206,14 +250,20 @@ fn heapify_up(heap: &mut [Candidate], size: usize) {
let mut current = size - 1; let mut current = size - 1;
while current > 0 { while current > 0 {
let parent = (current - 1) / 2; 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); heap.swap(current, parent);
current = parent; current = parent;
} }
} }
/// Find candidate signals in the waterfall. Returns sorted candidates (best first). /// 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 is_ft2 = wf.protocol == FtxProtocol::Ft2;
let num_tones = if wf.protocol.uses_ft4_layout() { 4 } else { 8 }; 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); let base = get_cand_offset(wf, cand);
for k in 0..FT4_ND { 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 bit_idx = 2 * k;
let block = cand.time_offset as i32 + sym_idx as i32; 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 { for frame_sym in 0..frame_syms {
let sym_idx = frame_sym + 1; // skip ramp-up let sym_idx = frame_sym + 1; // skip ramp-up
let block = cand.time_offset as i32 + sym_idx as i32; 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; let sym_offset = base + sym_idx * wf.block_stride;
for tone in 0..4 { for tone in 0..4 {
let elem = *wf_mag_safe(wf, sym_offset + tone); 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 // Map to 174 data bits, selecting max-magnitude metric
for data_sym in 0..FT2_ND { 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 src_bit = 2 * frame_sym;
let dst_bit = 2 * data_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_bits = 2 * n_syms;
let n_sequences = 1 << n_bits; 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 strength = sum.norm();
let mask_bit = n_bits - bit - 1; let mask_bit = n_bits - bit - 1;
if (seq >> mask_bit) & 1 != 0 { 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 { } else if strength > max_zero {
max_zero = strength; max_zero = strength;
} }
@@ -470,7 +542,9 @@ fn ftx_normalize_logl(log174: &mut [f32; FTX_LDPC_N]) {
/// Pack bits into bytes (MSB first). /// Pack bits into bytes (MSB first).
pub fn pack_bits(bit_array: &[u8], num_bits: usize, packed: &mut [u8]) { pub fn pack_bits(bit_array: &[u8], num_bits: usize, packed: &mut [u8]) {
let num_bytes = num_bits.div_ceil(8); 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 mask: u8 = 0x80;
let mut byte_idx = 0; let mut byte_idx = 0;
for i in 0..num_bits { 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 { for sym in 0..nn {
let block_abs = cand.time_offset as i32 + sym as i32; 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 p_offset = base + sym * wf.block_stride;
let sig_db = wf_mag_safe(wf, p_offset + tones[sym] as usize).mag; 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 noise_min = 0.0f32;
let mut found_noise = false; let mut found_noise = false;
for t in 0..num_tones { 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; let db = wf_mag_safe(wf, p_offset + t).mag;
if !found_noise || db < noise_min { if !found_noise || db < noise_min {
noise_min = db; noise_min = db;
+1
View File
@@ -138,6 +138,7 @@ impl Ft8Decoder {
/// Reset the decoder state for a new decode cycle. /// Reset the decoder state for a new decode cycle.
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.monitor.reset(); self.monitor.reset();
self.callsign_hash.cleanup(10);
if let Some(ref mut pipe) = self.ft2_pipeline { if let Some(ref mut pipe) = self.ft2_pipeline {
pipe.reset(); pipe.reset();
} }
+12 -13
View File
@@ -7,9 +7,7 @@ use crate::constants::{
FTX_LDPC_GENERATOR, FTX_LDPC_GENERATOR,
}; };
use crate::crc::ftx_add_crc; use crate::crc::ftx_add_crc;
use crate::protocol::{ use crate::protocol::{FT4_NN, FT8_NN, FTX_LDPC_K, FTX_LDPC_K_BYTES, FTX_LDPC_M, FTX_LDPC_N_BYTES};
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. /// Returns 1 if an odd number of bits are set in `x`, zero otherwise.
fn parity8(x: u8) -> u8 { fn parity8(x: u8) -> u8 {
@@ -29,11 +27,7 @@ fn parity8(x: u8) -> u8 {
fn encode174(message: &[u8], codeword: &mut [u8]) { fn encode174(message: &[u8], codeword: &mut [u8]) {
// Fill the codeword with message and zeros // Fill the codeword with message and zeros
for j in 0..FTX_LDPC_N_BYTES { for j in 0..FTX_LDPC_N_BYTES {
codeword[j] = if j < FTX_LDPC_K_BYTES { codeword[j] = if j < FTX_LDPC_K_BYTES { message[j] } else { 0 };
message[j]
} else {
0
};
} }
// Compute the byte index and bit mask for the first checksum bit // 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). // The codeword should start with the message bytes (systematic code).
// Byte 11 shares bits between the last 3 message bits and the first // Byte 11 shares bits between the last 3 message bits and the first
// parity bits, so only check bytes 0..10 for exact match. // parity bits, so only check bytes 0..10 for exact match.
let message: [u8; FTX_LDPC_K_BYTES] = let message: [u8; FTX_LDPC_K_BYTES] = [
[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x22, 0x33, 0x40]; 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x22, 0x33, 0x40,
];
let mut codeword = [0u8; FTX_LDPC_N_BYTES]; let mut codeword = [0u8; FTX_LDPC_N_BYTES];
encode174(&message, &mut codeword); encode174(&message, &mut codeword);
@@ -246,8 +241,9 @@ mod tests {
#[test] #[test]
fn encode174_nonzero_parity() { fn encode174_nonzero_parity() {
// A non-zero message should produce non-zero parity bits // A non-zero message should produce non-zero parity bits
let message: [u8; FTX_LDPC_K_BYTES] = let message: [u8; FTX_LDPC_K_BYTES] = [
[0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xE0]; 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xE0,
];
let mut codeword = [0u8; FTX_LDPC_N_BYTES]; let mut codeword = [0u8; FTX_LDPC_N_BYTES];
encode174(&message, &mut codeword); encode174(&message, &mut codeword);
@@ -255,7 +251,10 @@ mod tests {
let parity_nonzero = codeword[FTX_LDPC_K_BYTES..FTX_LDPC_N_BYTES] let parity_nonzero = codeword[FTX_LDPC_K_BYTES..FTX_LDPC_N_BYTES]
.iter() .iter()
.any(|&b| b != 0); .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] #[test]
+2 -4
View File
@@ -65,10 +65,8 @@ pub fn ldpc_decode(
plain: &mut [u8; FTX_LDPC_N], plain: &mut [u8; FTX_LDPC_N],
) -> i32 { ) -> i32 {
// Allocate m[][] and e[][] on the heap (~60 kB each) to avoid stack overflow. // Allocate m[][] and e[][] on the heap (~60 kB each) to avoid stack overflow.
let mut m_matrix: Vec<Vec<f32>> = let mut m_matrix: Vec<Vec<f32>> = vec![vec![0.0f32; FTX_LDPC_N]; FTX_LDPC_M];
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 e_matrix: Vec<Vec<f32>> =
vec![vec![0.0f32; FTX_LDPC_N]; FTX_LDPC_M];
// Initialize m[][] with the channel LLRs. // Initialize m[][] with the channel LLRs.
for j in 0..FTX_LDPC_M { for j in 0..FTX_LDPC_M {
+13 -13
View File
@@ -2,21 +2,14 @@
// //
// SPDX-License-Identifier: BSD-2-Clause // SPDX-License-Identifier: BSD-2-Clause
pub mod protocol; pub mod callsign_hash;
pub mod constants; pub mod constants;
pub mod crc; 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)] #[allow(dead_code, clippy::needless_range_loop)]
pub mod decode; pub mod decode;
mod decoder;
#[allow(clippy::needless_range_loop)]
pub mod encode;
#[allow( #[allow(
dead_code, dead_code,
clippy::manual_memcpy, clippy::manual_memcpy,
@@ -24,6 +17,13 @@ pub mod decode;
clippy::too_many_arguments clippy::too_many_arguments
)] )]
pub mod ft2; 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};
+21 -43
View File
@@ -178,9 +178,7 @@ fn is_space(c: u8) -> bool {
/// whitespace). /// whitespace).
fn copy_token(input: &str) -> (&str, String) { fn copy_token(input: &str) -> (&str, String) {
let input = input.trim_start(); let input = input.trim_start();
let end = input let end = input.find(' ').unwrap_or(input.len());
.find(' ')
.unwrap_or(input.len());
let token = &input[..end]; let token = &input[..end];
let rest = &input[end..].trim_start(); let rest = &input[end..].trim_start();
(rest, token.to_string()) (rest, token.to_string())
@@ -307,11 +305,7 @@ pub fn pack_basecall(callsign: &str) -> Option<i32> {
c6[i + 3] = b; c6[i + 3] = b;
} }
} }
} else if starts_with(callsign, "3X") } else if starts_with(callsign, "3X") && length > 2 && is_letter(bytes[2]) && length <= 7 {
&& length > 2
&& is_letter(bytes[2])
&& length <= 7
{
// Guinea prefix: 3XA0XYZ -> QA0XYZ // Guinea prefix: 3XA0XYZ -> QA0XYZ
c6[0] = b'Q'; c6[0] = b'Q';
for (i, &b) in bytes[2..].iter().enumerate() { 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 /// 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. /// 28-bit integer. Returns `(n28, ip)` on success, or `None` on error.
fn pack28( fn pack28(callsign: &str, hash_table: Option<&mut CallsignHashTable>) -> Option<(i32, u8)> {
callsign: &str,
hash_table: Option<&mut CallsignHashTable>,
) -> Option<(i32, u8)> {
let mut ip: u8 = 0; let mut ip: u8 = 0;
// Check for special tokens // Check for special tokens
@@ -452,11 +443,7 @@ fn unpack28(
let n28_adj = n28 - NTOKENS; let n28_adj = n28 - NTOKENS;
if n28_adj < MAX22 { if n28_adj < MAX22 {
// 22-bit hashed callsign // 22-bit hashed callsign
let call = lookup_callsign( let call = lookup_callsign(hash_table.as_deref(), HashType::Hash22Bits, n28_adj);
hash_table.as_deref(),
HashType::Hash22Bits,
n28_adj,
);
return Some((call, FtxFieldType::Call)); return Some((call, FtxFieldType::Call));
} }
@@ -519,10 +506,7 @@ fn unpack28(
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/// Pack a non-standard callsign into a 58-bit integer. /// Pack a non-standard callsign into a 58-bit integer.
fn pack58( fn pack58(hash_table: Option<&mut CallsignHashTable>, callsign: &str) -> Option<u64> {
hash_table: Option<&mut CallsignHashTable>,
callsign: &str,
) -> Option<u64> {
let src = callsign.trim_start_matches('<').trim_end_matches('>'); let src = callsign.trim_start_matches('<').trim_end_matches('>');
let mut result: u64 = 0; let mut result: u64 = 0;
@@ -545,10 +529,7 @@ fn pack58(
} }
/// Unpack a non-standard callsign from a 58-bit integer. /// Unpack a non-standard callsign from a 58-bit integer.
fn unpack58( fn unpack58(n58: u64, hash_table: Option<&mut CallsignHashTable>) -> Option<String> {
n58: u64,
hash_table: Option<&mut CallsignHashTable>,
) -> Option<String> {
let mut c11 = [0u8; 11]; let mut c11 = [0u8; 11];
let mut n = n58; let mut n = n58;
@@ -643,11 +624,7 @@ fn unpackgrid(igrid4: u16, ir: u8) -> Option<(String, FtxFieldType)> {
(b'0' + d3) as char, (b'0' + d3) as char,
); );
let result = if ir > 0 { let result = if ir > 0 { format!("R {}", grid) } else { grid };
format!("R {}", grid)
} else {
grid
};
Some((result, FtxFieldType::Grid)) Some((result, FtxFieldType::Grid))
} else { } else {
@@ -776,10 +753,7 @@ pub fn ftx_message_encode_std(
let icq = call_to == "CQ" || starts_with(call_to, "CQ "); let icq = call_to == "CQ" || starts_with(call_to, "CQ ");
if let Some(slash_pos) = call_de.find('/') { if let Some(slash_pos) = call_de.find('/') {
if slash_pos >= 2 if slash_pos >= 2 && icq && !(call_de.ends_with("/P") || call_de.ends_with("/R")) {
&& icq
&& !(call_de.ends_with("/P") || call_de.ends_with("/R"))
{
return FtxMessageRc::ErrorCallsign2; return FtxMessageRc::ErrorCallsign2;
} }
} }
@@ -952,8 +926,7 @@ pub fn ftx_message_decode(
let msg_type = msg.get_type(); let msg_type = msg.get_type();
let (field1, field2, field3, rc) = match msg_type { let (field1, field2, field3, rc) = match msg_type {
FtxMessageType::Standard => { FtxMessageType::Standard => match ftx_message_decode_std(msg, hash_table) {
match ftx_message_decode_std(msg, hash_table) {
(Some(f1), Some(f2), Some(f3), types, rc) => { (Some(f1), Some(f2), Some(f3), types, rc) => {
offsets.types = types; offsets.types = types;
(Some(f1), Some(f2), Some(f3), rc) (Some(f1), Some(f2), Some(f3), rc)
@@ -962,10 +935,8 @@ pub fn ftx_message_decode(
offsets.types = types; offsets.types = types;
(f1, f2, f3, rc) (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) => { (Some(f1), Some(f2), Some(f3), types, rc) => {
offsets.types = types; offsets.types = types;
(Some(f1), Some(f2), Some(f3), rc) (Some(f1), Some(f2), Some(f3), rc)
@@ -974,8 +945,7 @@ pub fn ftx_message_decode(
offsets.types = types; offsets.types = types;
(f1, f2, f3, rc) (f1, f2, f3, rc)
} }
} },
}
FtxMessageType::FreeText => { FtxMessageType::FreeText => {
let text = ftx_message_decode_free(msg); let text = ftx_message_decode_free(msg);
(Some(text), None, None, FtxMessageRc::Ok) (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)) { let (call_de, ft1) = match unpack28(n29b >> 1, (n29b & 1) as u8, i3, Some(hash_table)) {
Some(v) => v, 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; field_types[1] = ft1;
+25 -5
View File
@@ -38,7 +38,13 @@ pub struct Waterfall {
} }
impl 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 block_stride = time_osr * freq_osr * num_bins;
let mag = vec![WfElem::default(); max_blocks * block_stride]; let mag = vec![WfElem::default(); max_blocks * block_stride];
Self { Self {
@@ -115,7 +121,13 @@ impl Monitor {
let num_bins = max_bin - min_bin; let num_bins = max_bin - min_bin;
let max_blocks = (slot_time / symbol_period) as usize; 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 mut real_planner = RealFftPlanner::<f32>::new();
let real_fft = real_planner.plan_fft_forward(nfft); let real_fft = real_planner.plan_fft_forward(nfft);
@@ -168,7 +180,8 @@ impl Monitor {
for _time_sub in 0..self.wf.time_osr { for _time_sub in 0..self.wf.time_osr {
// Shift new data into analysis frame // Shift new data into analysis frame
let shift = self.nfft - self.subblock_size; 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 { for pos in shift..self.nfft {
self.last_frame[pos] = if frame_pos < frame.len() { self.last_frame[pos] = if frame_pos < frame.len() {
frame[frame_pos] frame[frame_pos]
@@ -183,7 +196,11 @@ impl Monitor {
self.fft_input[pos] = self.window[pos] * self.last_frame[pos]; self.fft_input[pos] = self.window[pos] * self.last_frame[pos];
} }
self.real_fft 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"); .expect("FFT process failed");
// Extract magnitude and phase for each frequency sub-bin // Extract magnitude and phase for each frequency sub-bin
@@ -206,7 +223,10 @@ impl Monitor {
} }
} else { } else {
if offset < self.wf.mag.len() { 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; offset += 1;
} }
+30 -6
View File
@@ -36,32 +36,56 @@ impl FtxProtocol {
/// Number of data symbols. /// Number of data symbols.
pub fn nd(self) -> usize { 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. /// Total channel symbols.
pub fn nn(self) -> usize { 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. /// Length of each sync group.
pub fn sync_length(self) -> usize { 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. /// Number of sync groups.
pub fn num_sync(self) -> usize { 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. /// Offset between sync groups.
pub fn sync_offset(self) -> usize { 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. /// Number of FSK tones.
pub fn num_tones(self) -> usize { pub fn num_tones(self) -> usize {
if self.uses_ft4_layout() { 4 } else { 8 } if self.uses_ft4_layout() {
4
} else {
8
}
} }
} }
+5 -19
View File
@@ -108,16 +108,14 @@ pub fn nchar(c: char, table: CharTable) -> Option<i32> {
// Extra symbols // Extra symbols
match table { match table {
CharTable::Full => { CharTable::Full => match c {
match c {
'+' => return Some(n), '+' => return Some(n),
'-' => return Some(n + 1), '-' => return Some(n + 1),
'.' => return Some(n + 2), '.' => return Some(n + 2),
'/' => return Some(n + 3), '/' => return Some(n + 3),
'?' => return Some(n + 4), '?' => return Some(n + 4),
_ => {} _ => {}
} },
}
CharTable::AlphanumSpaceSlash => { CharTable::AlphanumSpaceSlash => {
if c == '/' { if c == '/' {
return Some(n); return Some(n);
@@ -240,11 +238,7 @@ mod tests {
let expected = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?"; let expected = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?";
for (i, ch) in expected.chars().enumerate() { for (i, ch) in expected.chars().enumerate() {
assert_eq!(charn(i as i32, CharTable::Full), ch, "charn({i})"); assert_eq!(charn(i as i32, CharTable::Full), ch, "charn({i})");
assert_eq!( assert_eq!(nchar(ch, CharTable::Full), Some(i as i32), "nchar('{ch}')");
nchar(ch, CharTable::Full),
Some(i as i32),
"nchar('{ch}')"
);
} }
} }
@@ -269,11 +263,7 @@ mod tests {
fn alphanum_space_round_trip() { fn alphanum_space_round_trip() {
let expected = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; let expected = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (i, ch) in expected.chars().enumerate() { for (i, ch) in expected.chars().enumerate() {
assert_eq!( assert_eq!(charn(i as i32, CharTable::AlphanumSpace), ch, "charn({i})");
charn(i as i32, CharTable::AlphanumSpace),
ch,
"charn({i})"
);
assert_eq!( assert_eq!(
nchar(ch, CharTable::AlphanumSpace), nchar(ch, CharTable::AlphanumSpace),
Some(i as i32), Some(i as i32),
@@ -286,11 +276,7 @@ mod tests {
fn letters_space_round_trip() { fn letters_space_round_trip() {
let expected = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"; let expected = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (i, ch) in expected.chars().enumerate() { for (i, ch) in expected.chars().enumerate() {
assert_eq!( assert_eq!(charn(i as i32, CharTable::LettersSpace), ch, "charn({i})");
charn(i as i32, CharTable::LettersSpace),
ch,
"charn({i})"
);
assert_eq!( assert_eq!(
nchar(ch, CharTable::LettersSpace), nchar(ch, CharTable::LettersSpace),
Some(i as i32), Some(i as i32),
+12 -2
View File
@@ -290,7 +290,12 @@ mod tests {
let base_hz = 1496.0_f32; let base_hz = 1496.0_f32;
let start = EXPECTED_SIGNAL_START_SAMPLES; 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 tone = sync_tone + 2 * ((sym % 2) as u8);
let freq = base_hz + tone as f32 * TONE_SPACING_HZ; let freq = base_hz + tone as f32 * TONE_SPACING_HZ;
let begin = start + sym * WSPR_SYMBOL_SAMPLES; let begin = start + sym * WSPR_SYMBOL_SAMPLES;
@@ -347,7 +352,12 @@ mod tests {
// Generate a synthetic WSPR-like signal using the sync vector // Generate a synthetic WSPR-like signal using the sync vector
let mut signal = vec![0.0_f32; WSPR_SIGNAL_SAMPLES]; 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 freq = base_hz + sync_tone as f32 * TONE_SPACING_HZ;
let begin = sym * WSPR_SYMBOL_SAMPLES; let begin = sym * WSPR_SYMBOL_SAMPLES;
for i in 0..WSPR_SYMBOL_SAMPLES { for i in 0..WSPR_SYMBOL_SAMPLES {