use super::fec::{bits_of, conv_encode, crc24q, deinterleave, interleave, viterbi};
use super::galileo::{GalEphemeris, GalIono, Gst};
use super::{get, int, p2, pack, pow2, push_bits, read_bits, sc, unpack, Decoded, Field};
pub const WORD_BITS: usize = 128;
pub const PART_BITS: usize = 120;
pub const SYNC: &str = "0101100000";
pub const PART_SYMBOLS: usize = 250;
pub const INTERLEAVER_COLS: usize = 30;
pub const INTERLEAVER_ROWS: usize = 8;
pub const WT1: &[Field] = &[
int("type", 6),
int("iodnav", 10),
p2("toe", 14, false, 60.0),
sc("m0", 32, true, pow2(-31)),
p2("e", 32, false, pow2(-33)),
p2("sqrt_a", 32, false, pow2(-19)),
int("reserved", 2),
];
pub const WT2: &[Field] = &[
int("type", 6),
int("iodnav", 10),
sc("omega0", 32, true, pow2(-31)),
sc("i0", 32, true, pow2(-31)),
sc("omega", 32, true, pow2(-31)),
sc("idot", 14, true, pow2(-43)),
int("reserved", 2),
];
pub const WT3: &[Field] = &[
int("type", 6),
int("iodnav", 10),
sc("omega_dot", 24, true, pow2(-43)),
sc("delta_n", 16, true, pow2(-43)),
p2("cuc", 16, true, pow2(-29)),
p2("cus", 16, true, pow2(-29)),
p2("crc", 16, true, pow2(-5)),
p2("crs", 16, true, pow2(-5)),
int("sisa_e1e5b", 8),
];
pub const WT4: &[Field] = &[
int("type", 6),
int("iodnav", 10),
int("svid", 6),
p2("cic", 16, true, pow2(-29)),
p2("cis", 16, true, pow2(-29)),
p2("toc", 14, false, 60.0),
p2("af0", 31, true, pow2(-34)),
p2("af1", 21, true, pow2(-46)),
p2("af2", 6, true, pow2(-59)),
int("spare", 2),
];
pub const WT5: &[Field] = &[
int("type", 6),
p2("ai0", 11, false, pow2(-2)),
p2("ai1", 11, true, pow2(-8)),
p2("ai2", 14, true, pow2(-15)),
int("region1", 1),
int("region2", 1),
int("region3", 1),
int("region4", 1),
int("region5", 1),
p2("bgd_e1e5a", 10, true, pow2(-32)),
p2("bgd_e1e5b", 10, true, pow2(-32)),
int("e5b_hs", 2),
int("e1b_hs", 2),
int("e5b_dvs", 1),
int("e1b_dvs", 1),
int("wn", 12),
int("tow", 20),
int("spare", 23),
];
pub fn layout(wt: u32) -> Option<&'static [Field]> {
match wt {
1 => Some(WT1),
2 => Some(WT2),
3 => Some(WT3),
4 => Some(WT4),
5 => Some(WT5),
_ => None,
}
}
fn b(x: bool) -> f64 {
f64::from(u8::from(x))
}
pub fn word_value(
wt: u32,
e: &GalEphemeris,
iono: &GalIono,
gst: &Gst,
name: &str,
) -> Result<f64, String> {
Ok(match name {
"type" => f64::from(wt),
"iodnav" => f64::from(e.iodnav & 0x3FF),
"toe" => e.toe_s,
"m0" => e.m0,
"e" => e.e,
"sqrt_a" => e.sqrt_a,
"omega0" => e.omega0,
"i0" => e.i0,
"omega" => e.omega,
"idot" => e.idot,
"omega_dot" => e.omega_dot,
"delta_n" => e.delta_n,
"cuc" => e.cuc,
"cus" => e.cus,
"crc" => e.crc,
"crs" => e.crs,
"sisa_e1e5b" => f64::from(e.sisa_e1e5b),
"sisa_e1e5a" => f64::from(e.sisa_e1e5a),
"svid" => f64::from(e.svid),
"cic" => e.cic,
"cis" => e.cis,
"toc" => e.toc_s,
"af0" => e.af0,
"af1" => e.af1,
"af2" => e.af2,
"ai0" => iono.ai0,
"ai1" => iono.ai1,
"ai2" => iono.ai2,
"region1" => b(iono.region[0]),
"region2" => b(iono.region[1]),
"region3" => b(iono.region[2]),
"region4" => b(iono.region[3]),
"region5" => b(iono.region[4]),
"bgd_e1e5a" => e.bgd_e1e5a,
"bgd_e1e5b" => e.bgd_e1e5b,
"e1b_hs" => f64::from(e.hs_e1b_e5b_e5a[0]),
"e5b_hs" => f64::from(e.hs_e1b_e5b_e5a[1]),
"e5a_hs" => f64::from(e.hs_e1b_e5b_e5a[2]),
"e1b_dvs" => b(e.dvs_e1b_e5b_e5a[0]),
"e5b_dvs" => b(e.dvs_e1b_e5b_e5a[1]),
"e5a_dvs" => b(e.dvs_e1b_e5b_e5a[2]),
"wn" => f64::from(gst.wn % 4096),
"tow" => f64::from(gst.tow),
"reserved" | "spare" => 0.0,
_ => return Err(format!("no Galileo field {name}")),
})
}
pub fn encode_word(
wt: u32,
e: &GalEphemeris,
iono: &GalIono,
gst: &Gst,
) -> Result<Vec<u8>, String> {
let l = layout(wt).ok_or_else(|| format!("word type {wt} is not encoded here"))?;
pack(l, |n| word_value(wt, e, iono, gst, n))
}
pub fn decode_word(word: &[u8]) -> Result<(u32, Vec<Decoded>), String> {
if word.len() != WORD_BITS {
return Err(format!("{} bits, expected {WORD_BITS}", word.len()));
}
let wt = read_bits(word, 0, 6) as u32;
let l = layout(wt).ok_or_else(|| format!("word type {wt} is not decoded here"))?;
Ok((wt, unpack(l, word)?))
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct OddPageExtras {
pub reserved1: u64,
pub sar: u32,
pub spare: u32,
pub reserved2: u32,
}
pub fn page_parts(word: &[u8], x: &OddPageExtras) -> Result<(Vec<u8>, Vec<u8>), String> {
if word.len() != WORD_BITS {
return Err(format!("{} bits, expected {WORD_BITS}", word.len()));
}
let mut even = vec![0u8, 0u8];
even.extend_from_slice(&word[..112]);
let mut odd = vec![1u8, 0u8];
odd.extend_from_slice(&word[112..]);
push_bits(&mut odd, x.reserved1 & ((1 << 40) - 1), 40);
push_bits(&mut odd, u64::from(x.sar & 0x3F_FFFF), 22);
push_bits(&mut odd, u64::from(x.spare & 3), 2);
let covered: Vec<u8> = even.iter().chain(odd.iter()).copied().collect();
push_bits(&mut odd, u64::from(crc24q(&covered)), 24);
push_bits(&mut odd, u64::from(x.reserved2 & 0xFF), 8);
push_bits(&mut even, 0, 6);
push_bits(&mut odd, 0, 6);
debug_assert_eq!((even.len(), odd.len()), (PART_BITS, PART_BITS));
Ok((even, odd))
}
pub fn read_page(even: &[u8], odd: &[u8]) -> Result<(Vec<u8>, OddPageExtras), String> {
if even.len() != PART_BITS || odd.len() != PART_BITS {
return Err("page parts must be 120 bits".into());
}
if even[0] != 0 || odd[0] != 1 {
return Err("even/odd flags are not 0 then 1".into());
}
if even[1] != 0 || odd[1] != 0 {
return Err("not a nominal page (page type 1 is an alert page)".into());
}
if even[114..].iter().chain(&odd[114..]).any(|&t| t != 0) {
return Err("tail bits are not zero".into());
}
let covered: Vec<u8> = even[..114].iter().chain(&odd[..82]).copied().collect();
if crc24q(&covered) != read_bits(odd, 82, 24) as u32 {
return Err("CRC-24Q mismatch".into());
}
let mut word = even[2..114].to_vec();
word.extend_from_slice(&odd[2..18]);
let x = OddPageExtras {
reserved1: read_bits(odd, 18, 40),
sar: read_bits(odd, 58, 22) as u32,
spare: read_bits(odd, 80, 2) as u32,
reserved2: read_bits(odd, 106, 8) as u32,
};
Ok((word, x))
}
pub fn encode_part_symbols(part: &[u8]) -> Result<Vec<u8>, String> {
if part.len() != PART_BITS {
return Err(format!("{} bits, expected {PART_BITS}", part.len()));
}
let mut state = 0u8;
let coded = conv_encode(part, &mut state, true);
let mut out = bits_of(SYNC);
out.extend(interleave(&coded, INTERLEAVER_COLS, INTERLEAVER_ROWS));
Ok(out)
}
pub fn decode_part_symbols(symbols: &[u8]) -> Result<(Vec<u8>, u32), String> {
if symbols.len() != PART_SYMBOLS {
return Err(format!(
"{} symbols, expected {PART_SYMBOLS}",
symbols.len()
));
}
if symbols[..10] != bits_of(SYNC)[..] {
return Err("synchronisation pattern not found".into());
}
let d = deinterleave(&symbols[10..], INTERLEAVER_COLS, INTERLEAVER_ROWS);
Ok(viterbi(&d, 0, Some(0), true))
}
pub fn encode_page_symbols(
wt: u32,
e: &GalEphemeris,
iono: &GalIono,
gst: &Gst,
x: &OddPageExtras,
) -> Result<Vec<u8>, String> {
let (even, odd) = page_parts(&encode_word(wt, e, iono, gst)?, x)?;
let mut s = encode_part_symbols(&even)?;
s.extend(encode_part_symbols(&odd)?);
Ok(s)
}
pub fn ephemeris_from_words(
words: &[(u32, Vec<Decoded>)],
) -> Result<(GalEphemeris, GalIono, Gst), String> {
let w = |t: u32| {
words
.iter()
.find(|(k, _)| *k == t)
.map(|(_, d)| d.as_slice())
.ok_or_else(|| format!("word type {t} missing"))
};
let v = |t: u32, n: &str| -> Result<f64, String> { Ok(get(w(t)?, n)?.value) };
let iod = v(1, "iodnav")?;
for t in 2..=4 {
if v(t, "iodnav")? != iod {
return Err(format!("IOD_nav of word type {t} differs from word type 1"));
}
}
let e = GalEphemeris {
svid: v(4, "svid")? as u32,
iodnav: iod as u32,
toe_s: v(1, "toe")?,
toc_s: v(4, "toc")?,
m0: v(1, "m0")?,
e: v(1, "e")?,
sqrt_a: v(1, "sqrt_a")?,
omega0: v(2, "omega0")?,
i0: v(2, "i0")?,
omega: v(2, "omega")?,
idot: v(2, "idot")?,
omega_dot: v(3, "omega_dot")?,
delta_n: v(3, "delta_n")?,
cuc: v(3, "cuc")?,
cus: v(3, "cus")?,
crc: v(3, "crc")?,
crs: v(3, "crs")?,
cic: v(4, "cic")?,
cis: v(4, "cis")?,
af0: v(4, "af0")?,
af1: v(4, "af1")?,
af2: v(4, "af2")?,
sisa_e1e5b: v(3, "sisa_e1e5b")? as u32,
sisa_e1e5a: 0,
bgd_e1e5a: v(5, "bgd_e1e5a")?,
bgd_e1e5b: v(5, "bgd_e1e5b")?,
hs_e1b_e5b_e5a: [v(5, "e1b_hs")? as u32, v(5, "e5b_hs")? as u32, 0],
dvs_e1b_e5b_e5a: [v(5, "e1b_dvs")? == 1.0, v(5, "e5b_dvs")? == 1.0, false],
};
let iono = GalIono {
ai0: v(5, "ai0")?,
ai1: v(5, "ai1")?,
ai2: v(5, "ai2")?,
region: [
v(5, "region1")? == 1.0,
v(5, "region2")? == 1.0,
v(5, "region3")? == 1.0,
v(5, "region4")? == 1.0,
v(5, "region5")? == 1.0,
],
};
let gst = Gst {
wn: v(5, "wn")? as u32,
tow: v(5, "tow")? as u32,
};
Ok((e, iono, gst))
}
#[cfg(test)]
mod tests {
use super::super::layout_bits;
use super::*;
#[test]
fn every_word_is_128_bits() {
for wt in 1..=5 {
assert_eq!(layout_bits(layout(wt).unwrap()), 128, "word type {wt}");
}
}
}