use crate::core::SyncBlock;
use super::puncture::Mode;
pub const PREAMBLE_LEN: usize = 127;
pub const NUM_PREAMBLES: usize = 4;
const fn lfsr_seven(taps: u8, mut state: u8) -> [bool; PREAMBLE_LEN] {
let mut bits = [false; PREAMBLE_LEN];
let mut i = 0;
while i < PREAMBLE_LEN {
bits[i] = (state & 1) != 0;
let masked = state & taps;
let new_bit = (masked.count_ones() & 1) as u8;
state = (state >> 1) | (new_bit << 6);
i += 1;
}
bits
}
const TAPS_X7_X6: u8 = 0b100_0001;
const TAPS_X7_X3: u8 = 0b000_1001;
const TAPS_X7_X1: u8 = 0b000_0011;
const TAPS_X7_X4_X3_X2: u8 = 0b001_1101;
pub const PREAMBLE_ULTRA_ROBUST: [bool; PREAMBLE_LEN] = lfsr_seven(TAPS_X7_X6, 0b000_0001);
pub const PREAMBLE_ROBUST: [bool; PREAMBLE_LEN] = lfsr_seven(TAPS_X7_X3, 0b000_0001);
pub const PREAMBLE_STANDARD: [bool; PREAMBLE_LEN] = lfsr_seven(TAPS_X7_X1, 0b000_0001);
pub const PREAMBLE_EXPRESS: [bool; PREAMBLE_LEN] = lfsr_seven(TAPS_X7_X4_X3_X2, 0b000_0001);
pub const PREAMBLES: [&[bool; PREAMBLE_LEN]; NUM_PREAMBLES] = [
&PREAMBLE_ULTRA_ROBUST,
&PREAMBLE_ROBUST,
&PREAMBLE_STANDARD,
&PREAMBLE_EXPRESS,
];
pub const fn preamble_for(mode: Mode) -> &'static [bool; PREAMBLE_LEN] {
match mode {
Mode::UltraRobust => &PREAMBLE_ULTRA_ROBUST,
Mode::Robust => &PREAMBLE_ROBUST,
Mode::Standard => &PREAMBLE_STANDARD,
Mode::Express => &PREAMBLE_EXPRESS,
}
}
pub const fn mode_for_index(idx: usize) -> Option<Mode> {
match idx {
0 => Some(Mode::UltraRobust),
1 => Some(Mode::Robust),
2 => Some(Mode::Standard),
3 => Some(Mode::Express),
_ => None,
}
}
pub const UVPACKET_COSTAS: [u8; 4] = [0, 1, 3, 2];
pub const UVPACKET_SYNC_BLOCKS: [SyncBlock; 1] = [SyncBlock {
start_symbol: 0,
pattern: &UVPACKET_COSTAS,
}];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn each_preamble_is_almost_balanced() {
for (idx, p) in PREAMBLES.iter().enumerate() {
let ones = p.iter().filter(|&&b| b).count();
assert!(
ones == 63 || ones == 64,
"variant {idx} has {ones} ones (expect 63 or 64)",
);
}
}
#[test]
fn preambles_are_pairwise_distinct() {
for i in 0..NUM_PREAMBLES {
for j in (i + 1)..NUM_PREAMBLES {
assert_ne!(PREAMBLES[i], PREAMBLES[j], "preamble {i} == preamble {j}",);
}
}
}
#[test]
fn each_preamble_has_m_sequence_autocorrelation() {
for (idx, p) in PREAMBLES.iter().enumerate() {
let bpsk: Vec<i32> = p.iter().map(|&b| if b { -1 } else { 1 }).collect();
let n = bpsk.len() as i32;
let lag0: i32 = bpsk.iter().map(|x| x * x).sum();
assert_eq!(lag0, n, "variant {idx} lag-0 autocorr {lag0} ≠ {n}");
for lag in 1..bpsk.len() {
let sum: i32 = (0..bpsk.len())
.map(|i| bpsk[i] * bpsk[(i + lag) % bpsk.len()])
.sum();
assert_eq!(
sum, -1,
"variant {idx} cyclic lag {lag} autocorr = {sum} ≠ -1",
);
}
}
}
#[test]
fn cross_correlation_below_autocorr_peak() {
let n = PREAMBLE_LEN as i32;
for i in 0..NUM_PREAMBLES {
for j in (i + 1)..NUM_PREAMBLES {
let a: Vec<i32> = PREAMBLES[i]
.iter()
.map(|&b| if b { -1 } else { 1 })
.collect();
let b: Vec<i32> = PREAMBLES[j]
.iter()
.map(|&b| if b { -1 } else { 1 })
.collect();
let mut peak_xc: i32 = 0;
for lag in 0..PREAMBLE_LEN {
let s: i32 = (0..PREAMBLE_LEN)
.map(|k| a[k] * b[(k + lag) % PREAMBLE_LEN])
.sum();
if s.abs() > peak_xc {
peak_xc = s.abs();
}
}
assert!(
peak_xc <= 50,
"variants {i}/{j} cross-corr peak {peak_xc} > 50 (autocorr peak {n})",
);
}
}
}
#[test]
fn mode_index_roundtrip() {
for i in 0..NUM_PREAMBLES {
let m = mode_for_index(i).unwrap();
let p = preamble_for(m);
assert_eq!(p, PREAMBLES[i]);
}
assert!(mode_for_index(NUM_PREAMBLES).is_none());
}
}