use crate::fec::qra::Q65Codec;
use crate::fec::qra15_65_64::QRA15_65_64_IRR_E23;
use crate::msg::q65::pack77_to_symbols;
use crate::msg::wsjt77::pack77;
pub const MAX_AP_CODEWORDS: usize = 206;
pub fn standard_qso_codewords(my_call: &str, his_call: &str, his_grid: &str) -> Vec<[i32; 63]> {
let mut codec = Q65Codec::new(&QRA15_65_64_IRR_E23);
let mut out: Vec<[i32; 63]> = Vec::with_capacity(MAX_AP_CODEWORDS);
let mut push = |codec: &mut Q65Codec, c1: &str, c2: &str, report: &str| {
if let Some(bits) = pack77(c1, c2, report) {
let info = pack77_to_symbols(&bits);
let mut cw = [0_i32; 63];
codec.encode(&info, &mut cw);
out.push(cw);
}
};
push(&mut codec, my_call, his_call, "");
push(&mut codec, my_call, his_call, "RRR");
push(&mut codec, my_call, his_call, "RR73");
push(&mut codec, my_call, his_call, "73");
if !his_grid.trim().is_empty() {
push(&mut codec, "CQ", his_call, his_grid);
push(&mut codec, my_call, his_call, his_grid);
}
for isnr in -50..=49_i32 {
let bare = format!("{isnr:+03}");
let r = format!("R{isnr:+03}");
push(&mut codec, my_call, his_call, &bare);
push(&mut codec, my_call, his_call, &r);
}
debug_assert!(
out.len() <= MAX_AP_CODEWORDS,
"AP list overflowed MAX_AP_CODEWORDS"
);
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{DecodeContext, MessageCodec};
use crate::msg::Q65Message;
use crate::msg::q65::unpack_symbols_to_bits77;
fn unpack_codeword(cw: &[i32; 63]) -> String {
let mut info = [0_i32; 13];
info.copy_from_slice(&cw[..13]);
let bits77 = unpack_symbols_to_bits77(&info);
Q65Message
.unpack(&bits77, &DecodeContext::default())
.unwrap_or_else(|| "<unparseable>".into())
}
#[test]
fn standard_list_contains_expected_templates() {
let cws = standard_qso_codewords("K1ABC", "JA1ABC", "PM95");
let messages: Vec<String> = cws.iter().map(unpack_codeword).collect();
for expected in [
"K1ABC JA1ABC",
"K1ABC JA1ABC RRR",
"K1ABC JA1ABC RR73",
"K1ABC JA1ABC 73",
"CQ JA1ABC PM95",
"K1ABC JA1ABC PM95",
"K1ABC JA1ABC -10",
"K1ABC JA1ABC R-10",
"K1ABC JA1ABC +05",
"K1ABC JA1ABC R+25",
] {
assert!(
messages.iter().any(|m| m == expected),
"missing expected template {expected:?}; first 12 = {:?}",
&messages[..12.min(messages.len())]
);
}
}
#[test]
fn standard_list_size_matches_reference() {
let with_grid = standard_qso_codewords("K1ABC", "JA1ABC", "PM95");
assert_eq!(
with_grid.len(),
MAX_AP_CODEWORDS,
"with-grid list size must hit MAX_NCW = 206"
);
let no_grid = standard_qso_codewords("K1ABC", "JA1ABC", "");
assert_eq!(
no_grid.len(),
MAX_AP_CODEWORDS - 2,
"no-grid list size must be MAX_NCW - 2"
);
}
#[test]
fn invalid_my_callsign_only_emits_callsign_independent_templates() {
let cws = standard_qso_codewords("!!!", "JA1ABC", "PM95");
assert_eq!(
cws.len(),
1,
"garbage my_call should leave only the CQ template, got {} codewords",
cws.len()
);
assert_eq!(unpack_codeword(&cws[0]), "CQ JA1ABC PM95");
}
#[test]
fn distinct_callsign_pairs_yield_distinct_lists() {
let a = standard_qso_codewords("K1ABC", "JA1ABC", "PM95");
let b = standard_qso_codewords("K1ABC", "JA9XYZ", "PM95");
assert_eq!(a.len(), b.len());
assert_ne!(a, b, "different call pairs produced identical AP lists");
}
}