pub mod fec;
pub mod gal_fnav;
pub mod gal_inav;
pub mod galileo;
pub mod gps_cnav;
use crate::gps_lnav::GPS_PI;
use crate::portable_math::PortableFloat;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Field {
pub name: &'static str,
pub bits: u32,
pub signed: bool,
pub lsb: f64,
pub semicircles: bool,
}
pub(crate) const fn int(name: &'static str, bits: u32) -> Field {
Field {
name,
bits,
signed: false,
lsb: 1.0,
semicircles: false,
}
}
pub(crate) const fn p2(name: &'static str, bits: u32, signed: bool, lsb: f64) -> Field {
Field {
name,
bits,
signed,
lsb,
semicircles: false,
}
}
pub(crate) const fn sc(name: &'static str, bits: u32, signed: bool, lsb: f64) -> Field {
Field {
name,
bits,
signed,
lsb,
semicircles: true,
}
}
impl Field {
pub fn lsb_si(&self) -> f64 {
if self.semicircles {
self.lsb * GPS_PI
} else {
self.lsb
}
}
pub fn quantise(&self, value: f64) -> Result<u64, String> {
let q = (value / self.lsb_si()).round();
let (lo, hi) = if self.signed {
(
-(2f64.ppowi(self.bits as i32 - 1)),
2f64.ppowi(self.bits as i32 - 1) - 1.0,
)
} else {
(0.0, 2f64.ppowi(self.bits as i32) - 1.0)
};
if !(q >= lo && q <= hi) {
return Err(format!(
"{}: {value:e} at LSB {:e} does not fit {} {} bits",
self.name,
self.lsb_si(),
self.bits,
if self.signed { "signed" } else { "unsigned" }
));
}
Ok((q as i64 as u64) & mask(self.bits))
}
pub fn integer(&self, raw: u64) -> i64 {
if self.signed && self.bits < 64 && (raw >> (self.bits - 1)) & 1 == 1 {
(raw | !mask(self.bits)) as i64
} else {
raw as i64
}
}
pub fn value(&self, raw: u64) -> f64 {
self.integer(raw) as f64 * self.lsb_si()
}
}
fn mask(bits: u32) -> u64 {
if bits >= 64 {
u64::MAX
} else {
(1u64 << bits) - 1
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Decoded {
pub name: &'static str,
pub raw: u64,
pub integer: i64,
pub value: f64,
}
pub fn layout_bits(layout: &[Field]) -> u32 {
layout.iter().map(|f| f.bits).sum()
}
pub fn start_bit(layout: &[Field], name: &str, first_bit: u32) -> Option<u32> {
let mut at = first_bit;
for f in layout {
if f.name == name {
return Some(at);
}
at += f.bits;
}
None
}
pub fn push_bits(out: &mut Vec<u8>, v: u64, bits: u32) {
for i in (0..bits).rev() {
out.push(((v >> i) & 1) as u8);
}
}
pub fn read_bits(bits: &[u8], at: usize, n: u32) -> u64 {
bits[at..at + n as usize]
.iter()
.fold(0u64, |v, &b| (v << 1) | u64::from(b & 1))
}
pub fn pack(
layout: &[Field],
value: impl Fn(&str) -> Result<f64, String>,
) -> Result<Vec<u8>, String> {
let mut out = Vec::with_capacity(layout_bits(layout) as usize);
for f in layout {
let raw = f.quantise(value(f.name)?)?;
push_bits(&mut out, raw, f.bits);
}
Ok(out)
}
pub fn unpack(layout: &[Field], bits: &[u8]) -> Result<Vec<Decoded>, String> {
let need = layout_bits(layout) as usize;
if bits.len() < need {
return Err(format!("{} bits, layout needs {need}", bits.len()));
}
let mut at = 0usize;
Ok(layout
.iter()
.map(|f| {
let raw = read_bits(bits, at, f.bits);
at += f.bits as usize;
Decoded {
name: f.name,
raw,
integer: f.integer(raw),
value: f.value(raw),
}
})
.collect())
}
pub fn get<'a>(fields: &'a [Decoded], name: &str) -> Result<&'a Decoded, String> {
fields
.iter()
.find(|d| d.name == name)
.ok_or_else(|| format!("no field {name}"))
}
pub(crate) const fn pow2(e: i32) -> f64 {
f64::from_bits(((1023 + e) as u64) << 52)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pow2_is_exact() {
for e in -100..100 {
assert_eq!(pow2(e), 2f64.ppowi(e), "{e}");
}
}
#[test]
fn quantise_rounds_signs_and_refuses_overflow() {
let f = p2("x", 8, true, pow2(-5));
assert_eq!(f.quantise(-(pow2(-5))).unwrap(), 0xFF);
assert_eq!(f.integer(0xFF), -1);
assert_eq!(f.value(0x80), -4.0);
assert!(f.quantise(4.0).is_err());
assert!(f.quantise(f64::NAN).is_err());
let u = int("u", 3);
assert!(u.quantise(-1.0).is_err());
assert_eq!(u.quantise(7.0).unwrap(), 7);
let a = sc("a", 32, true, pow2(-31));
assert!((a.value(a.quantise(1.0).unwrap()) - 1.0).abs() <= 0.5 * a.lsb_si());
}
#[test]
fn pack_unpack_round_trip() {
let layout = [int("a", 3), p2("b", 10, true, 0.5), int("c", 1)];
let bits = pack(&layout, |n| {
Ok(match n {
"a" => 5.0,
"b" => -3.4,
_ => 1.0,
})
})
.unwrap();
assert_eq!(bits.len(), 14);
let d = unpack(&layout, &bits).unwrap();
assert_eq!(get(&d, "a").unwrap().integer, 5);
assert_eq!(get(&d, "b").unwrap().value, -3.5);
assert_eq!(start_bit(&layout, "c", 1), Some(14));
}
}