use super::callsign::{CallsignHashTable, pack28, pack58, unpack28};
use super::free_text::{decode_free_text, encode_free_text};
use super::grid::{GridField, gridfield_to_pack, unpackgrid};
pub type Payload77 = [u8; 10];
#[derive(Debug, Clone, PartialEq)]
pub enum NonstdExtra {
RRR,
RR73,
Seventy3,
None,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Ft8Message {
Standard {
call_to: String,
call_de: String,
extra: GridField,
},
FreeText(String),
NonStd {
call_to: String,
call_de: String,
extra: NonstdExtra,
},
Telemetry([u8; 9]),
Unknown(Payload77),
}
pub fn pack77(msg: &Ft8Message, ht: &mut CallsignHashTable) -> Option<Payload77> {
match msg {
Ft8Message::Standard {
call_to,
call_de,
extra,
} => pack77_standard(call_to, call_de, extra, ht),
Ft8Message::FreeText(text) => pack77_free_text(text),
Ft8Message::NonStd {
call_to,
call_de,
extra,
} => pack77_nonstd(call_to, call_de, extra, ht),
Ft8Message::Telemetry(data) => Some(pack77_telemetry(data)),
Ft8Message::Unknown(p) => Some(*p),
}
}
pub fn unpack77(payload: &Payload77, ht: &CallsignHashTable) -> Ft8Message {
let i3 = (payload[9] >> 3) & 0x07;
let n3 = ((payload[8] << 2) | (payload[9] >> 6)) & 0x07;
match i3 {
0 => match n3 {
0 => {
let b71 = payload_to_b71(payload);
Ft8Message::FreeText(decode_free_text(&b71))
}
5 => {
let b71 = payload_to_b71(payload);
Ft8Message::Telemetry(b71)
}
_ => Ft8Message::Unknown(*payload),
},
1 | 2 => unpack77_standard(payload, i3, ht),
4 => unpack77_nonstd(payload, ht),
_ => Ft8Message::Unknown(*payload),
}
}
fn pack77_standard(
call_to: &str,
call_de: &str,
extra: &GridField,
ht: &mut CallsignHashTable,
) -> Option<Payload77> {
let mut ipa = false;
let mut ipb = false;
let n28a = pack28(call_to, ht, &mut ipa)?;
let n28b = pack28(call_de, ht, &mut ipb)?;
let mut i3: u8 = 1;
if call_to.ends_with("/P") || call_de.ends_with("/P") {
i3 = 2;
}
let mut n29a = (n28a << 1) | ipa as u32;
let n29b = (n28b << 1) | ipb as u32;
if call_to.ends_with("/R") {
n29a = (n28a << 1) | 1;
} else if call_to.ends_with("/P") {
n29a = (n28a << 1) | 1;
i3 = 2;
}
let (igrid4_15bits, ir) = gridfield_to_pack(extra);
let igrid4_packed = igrid4_15bits | (if ir { 0x8000 } else { 0 });
let ir_bit = if ir { 1u8 } else { 0u8 };
let igrid4 = igrid4_15bits;
let mut p = [0u8; 10];
p[0] = (n29a >> 21) as u8;
p[1] = (n29a >> 13) as u8;
p[2] = (n29a >> 5) as u8;
p[3] = ((n29a << 3) as u8) | (n29b >> 26) as u8;
p[4] = (n29b >> 18) as u8;
p[5] = (n29b >> 10) as u8;
p[6] = (n29b >> 2) as u8;
p[7] = ((n29b << 6) as u8) | (ir_bit << 5) | ((igrid4 >> 10) as u8);
p[8] = (igrid4 >> 2) as u8;
p[9] = ((igrid4 << 6) as u8) | (i3 << 3);
let _ = igrid4_packed;
Some(p)
}
fn pack77_free_text(text: &str) -> Option<Payload77> {
let b71 = encode_free_text(text)?;
let mut p = [0u8; 10];
let mut carry: u8 = 0;
for i in (0..9usize).rev() {
p[i] = (b71[i] << 1) | (carry >> 7);
carry = b71[i] & 0x80;
}
p[9] = 0; Some(p)
}
fn pack77_nonstd(
call_to: &str,
call_de: &str,
extra: &NonstdExtra,
ht: &mut CallsignHashTable,
) -> Option<Payload77> {
let i3: u8 = 4;
let icq: u8 = if call_to == "CQ" || call_to.starts_with("CQ ") {
1
} else {
0
};
let (iflip, n12, call58_str) = if icq == 0 {
let iflip: u8 = 0;
let n12 = {
let (_, n12_val, _) = ht.save(call_to);
n12_val
};
(iflip, n12, call_de.to_string())
} else {
(0u8, 0u16, call_de.to_string())
};
let n58 = pack58(&call58_str, ht)?;
let nrpt: u8 = if icq != 0 {
0
} else {
match extra {
NonstdExtra::RRR => 1,
NonstdExtra::RR73 => 2,
NonstdExtra::Seventy3 => 3,
NonstdExtra::None => 0,
}
};
let mut p = [0u8; 10];
p[0] = (n12 >> 4) as u8;
p[1] = ((n12 << 4) as u8) | ((n58 >> 54) as u8);
p[2] = (n58 >> 46) as u8;
p[3] = (n58 >> 38) as u8;
p[4] = (n58 >> 30) as u8;
p[5] = (n58 >> 22) as u8;
p[6] = (n58 >> 14) as u8;
p[7] = (n58 >> 6) as u8;
p[8] = ((n58 << 2) as u8) | (iflip << 1) | (nrpt >> 1);
p[9] = (nrpt << 7) | (icq << 6) | (i3 << 3);
Some(p)
}
fn pack77_telemetry(data: &[u8; 9]) -> Payload77 {
let mut p = [0u8; 10];
let mut carry: u8 = 0;
for i in (0..9usize).rev() {
p[i] = (data[i] << 1) | (carry >> 7);
carry = data[i] & 0x80;
}
p[8] |= 0x01; p[9] = 0b01 << 6; p
}
fn payload_to_b71(payload: &Payload77) -> [u8; 9] {
let mut b71 = [0u8; 9];
let mut carry: u8 = 0;
for i in 0..9usize {
b71[i] = (carry << 7) | (payload[i] >> 1);
carry = payload[i] & 0x01;
}
b71
}
fn unpack77_standard(payload: &Payload77, i3: u8, ht: &CallsignHashTable) -> Ft8Message {
let n29a: u32 = ((payload[0] as u32) << 21)
| ((payload[1] as u32) << 13)
| ((payload[2] as u32) << 5)
| ((payload[3] as u32) >> 3);
let n29b: u32 = (((payload[3] & 0x07) as u32) << 26)
| ((payload[4] as u32) << 18)
| ((payload[5] as u32) << 10)
| ((payload[6] as u32) << 2)
| ((payload[7] as u32) >> 6);
let ir = (payload[7] & 0x20) != 0;
let igrid4: u16 = (((payload[7] & 0x1F) as u16) << 10)
| ((payload[8] as u16) << 2)
| ((payload[9] as u16) >> 6);
let call_to = unpack28(n29a >> 1, (n29a & 1) != 0, i3, ht).unwrap_or_else(|| "<?>".to_string());
let call_de = unpack28(n29b >> 1, (n29b & 1) != 0, i3, ht).unwrap_or_else(|| "<?>".to_string());
let extra = unpackgrid(igrid4, ir);
Ft8Message::Standard {
call_to,
call_de,
extra,
}
}
fn unpack77_nonstd(payload: &Payload77, ht: &CallsignHashTable) -> Ft8Message {
let n12 = ((payload[0] as u16) << 4) | ((payload[1] as u16) >> 4);
let n58: u64 = (((payload[1] & 0x0F) as u64) << 54)
| ((payload[2] as u64) << 46)
| ((payload[3] as u64) << 38)
| ((payload[4] as u64) << 30)
| ((payload[5] as u64) << 22)
| ((payload[6] as u64) << 14)
| ((payload[7] as u64) << 6)
| ((payload[8] as u64) >> 2);
let iflip = (payload[8] >> 1) & 0x01;
let nrpt = ((payload[8] & 0x01) << 1) | (payload[9] >> 7);
let icq = (payload[9] >> 6) & 0x01;
let call_decoded = unpack58_readonly(n58);
let call_hashed = ht
.lookup_n12(n12)
.map(|s| format!("<{}>", s))
.unwrap_or_else(|| "<...>".to_string());
let (call_to_str, call_de_str) = if iflip == 0 {
(call_hashed, call_decoded)
} else {
(call_decoded, call_hashed)
};
let (call_to_final, call_de_final) = if icq == 1 {
("CQ".to_string(), call_de_str)
} else {
(call_to_str, call_de_str)
};
let extra = match nrpt {
1 => NonstdExtra::RRR,
2 => NonstdExtra::RR73,
3 => NonstdExtra::Seventy3,
_ => NonstdExtra::None,
};
let extra = if icq == 1 { NonstdExtra::None } else { extra };
Ft8Message::NonStd {
call_to: call_to_final,
call_de: call_de_final,
extra,
}
}
fn unpack58_readonly(n58: u64) -> String {
use super::tables::{Table, charn};
let mut n = n58;
let mut chars = [' '; 11];
for i in (0..11).rev() {
chars[i] = charn((n % 38) as u8, Table::AlphanumSpaceSlash);
n /= 38;
}
let s: String = chars.iter().collect();
s.trim_matches(' ').to_string()
}