squitter 0.1.1

no_std, no_alloc parser and encoder for 1090ES/DF17 (ADS-B extended squitter) messages
Documentation
//! Type-code-dispatched decoding/encoding of a DF17 frame's `ME` field into
//! a typed [`AdsbMessage`].

mod airborne_position;
mod airborne_velocity;
mod identification;
mod surface_position;

pub mod cpr;

pub use airborne_position::{AirbornePosition, Altitude, PositionPair};
pub use airborne_velocity::{AirborneVelocity, VelocityData};
pub use cpr::CprFormat;
pub use identification::{CallSign, Identification};
pub use surface_position::{
    SurfacePosition, SurfacePositionPair, decode_movement, encode_movement,
};

use crate::bits::{BitReader, BitWriter};
use crate::error::{BitError, MessageError};

/// A decoded ADS-B message, one variant per supported type-code family.
///
/// `#[non_exhaustive]`: more type-code families may be added later.
///
/// Does not derive `Eq` (only `PartialEq`): `AirborneVelocity` carries an
/// `Option<f64>` heading, and `f64` has no total order (`NaN`).
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum AdsbMessage {
    /// Aircraft identification and category (type codes 1-4).
    Identification(Identification),
    /// Surface position: movement and track, no altitude (type codes 5-8).
    SurfacePosition(SurfacePosition),
    /// Airborne position, barometric altitude or GNSS height (type codes
    /// 9-18, 20-22).
    AirbornePosition(AirbornePosition),
    /// Airborne velocity: ground speed or airspeed, vertical rate (type
    /// code 19).
    AirborneVelocity(AirborneVelocity),
}

impl AdsbMessage {
    pub(crate) fn decode(me: &[u8]) -> Result<Self, MessageError> {
        let mut r = BitReader::new(me);
        let type_code = r.read_u8(5)?;
        match type_code {
            1..=4 => Ok(Self::Identification(Identification::decode(
                type_code, &mut r,
            )?)),
            5..=8 => Ok(Self::SurfacePosition(SurfacePosition::decode(
                type_code, &mut r,
            )?)),
            9..=18 | 20..=22 => Ok(Self::AirbornePosition(AirbornePosition::decode(
                type_code, &mut r,
            )?)),
            19 => Ok(Self::AirborneVelocity(AirborneVelocity::decode(&mut r)?)),
            other => Err(MessageError::UnknownTypeCode(other)),
        }
    }

    pub(crate) fn encode(&self, w: &mut BitWriter<'_>) -> Result<(), BitError> {
        match self {
            Self::Identification(m) => m.encode(w),
            Self::SurfacePosition(m) => m.encode(w),
            Self::AirbornePosition(m) => m.encode(w),
            Self::AirborneVelocity(m) => m.encode(w),
        }
    }
}

// Fuzzes AdsbMessage::decode/encode over arbitrary 56-bit ME payloads,
// rather than hand-picked per-field strategies: every raw bit pattern is a
// legal input to decode (only the type code selects a family), so this
// exercises the sign/scale arithmetic in every message type's decode and
// encode (altitude, velocity, movement) against boundary values a
// hand-written strategy would likely miss.
#[cfg(test)]
mod proptests {
    use proptest::prelude::*;

    use super::AdsbMessage;
    use crate::bits::BitWriter;

    proptest! {
        #[test]
        fn decode_never_panics(me: [u8; 7]) {
            let _ = AdsbMessage::decode(&me);
        }

        // Not `decode(me) == decode(encode(decode(me)))`: the identification
        // six-bit alphabet (string.rs) is intentionally lossy on its unused
        // code points (they collapse to `#`, which itself re-encodes to
        // space, not back to the original code point) -- a real callsign
        // never uses them, so this isn't a bug, but it does mean arbitrary
        // raw bytes aren't preserved on the first pass. What must hold for
        // any message type is that encode/decode reaches a fixed point:
        // once a message has been through one encode/decode pass, doing it
        // again changes nothing further.
        #[test]
        fn decode_encode_decode_reaches_a_fixed_point(me: [u8; 7]) {
            if let Ok(msg) = AdsbMessage::decode(&me) {
                let msg2 = encode_then_decode(&msg);
                let msg3 = encode_then_decode(&msg2);
                prop_assert_eq!(msg2, msg3);
            }
        }
    }

    fn encode_then_decode(msg: &AdsbMessage) -> AdsbMessage {
        let mut buf = [0u8; 7];
        let mut w = BitWriter::new(&mut buf);
        msg.encode(&mut w).unwrap();
        AdsbMessage::decode(&buf).expect("re-encoded ME must still decode")
    }
}