squitter 0.1.1

no_std, no_alloc parser and encoder for 1090ES/DF17 (ADS-B extended squitter) messages
Documentation
//! Error types for every layer of the decode/encode pipeline.

use core::fmt;

/// Errors from the bit-level reader/writer.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BitError {
    /// Attempted to read past the end of the available bits.
    UnexpectedEof,
    /// A requested field width was zero or exceeded the return type's width.
    OutOfRange {
        /// Name of the field/operation that was out of range.
        field: &'static str,
    },
    /// The destination buffer has no room for more output.
    BufferFull,
}

impl fmt::Display for BitError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::UnexpectedEof => write!(f, "unexpected end of bit stream"),
            Self::OutOfRange { field } => write!(f, "field width out of range: {field}"),
            Self::BufferFull => write!(f, "output buffer is full"),
        }
    }
}

impl core::error::Error for BitError {}

/// Errors from parsing a raw Mode S / ADS-B hex frame.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameError {
    /// The hex payload was not exactly 28 characters (112 bits / 14 bytes).
    WrongLength {
        /// Expected hex character count (always 28 for a DF17 frame).
        expected: usize,
        /// Actual hex character count found.
        actual: usize,
    },
    /// A byte pair was not valid hexadecimal.
    InvalidHex,
    /// The 5-bit downlink format field was not 17 (civil ADS-B extended
    /// squitter); this crate only decodes DF17.
    UnsupportedDownlinkFormat(u8),
    /// The trailing 24-bit parity field did not match the CRC-24 computed
    /// over the rest of the frame.
    ChecksumMismatch,
}

impl fmt::Display for FrameError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::WrongLength { expected, actual } => {
                write!(f, "expected {expected} hex characters, got {actual}")
            }
            Self::InvalidHex => write!(f, "invalid hexadecimal in frame"),
            Self::UnsupportedDownlinkFormat(df) => {
                write!(
                    f,
                    "unsupported downlink format: {df} (only DF17 is decoded)"
                )
            }
            Self::ChecksumMismatch => write!(f, "CRC-24 checksum mismatch"),
        }
    }
}

impl core::error::Error for FrameError {}

/// Errors from decoding/encoding a typed ADS-B message from/to its bit-packed
/// `ME` field payload.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MessageError {
    /// The 5-bit type code field did not match any known/decoded message family.
    UnknownTypeCode(u8),
    /// An airborne velocity (type code 19) message's 3-bit subtype was a
    /// reserved value (`0`, `5..=7`) this crate doesn't decode.
    UnknownVelocitySubtype(u8),
    /// A bit-level read/write failed while decoding/encoding a message.
    Bit(BitError),
}

impl fmt::Display for MessageError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::UnknownTypeCode(tc) => write!(f, "unknown/undecoded ADS-B type code: {tc}"),
            Self::UnknownVelocitySubtype(st) => {
                write!(f, "unknown/reserved airborne velocity subtype: {st}")
            }
            Self::Bit(e) => write!(f, "{e}"),
        }
    }
}

impl core::error::Error for MessageError {}

impl From<BitError> for MessageError {
    fn from(e: BitError) -> Self {
        Self::Bit(e)
    }
}

/// Top-level error type composing every layer, for `?`-based error
/// propagation across the frame, bit, and message layers.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AdsbError {
    /// Error in the frame layer (hex parsing, CRC, downlink format).
    Frame(FrameError),
    /// Error in the bit-level codec.
    Bit(BitError),
    /// Error in the message layer.
    Message(MessageError),
}

impl fmt::Display for AdsbError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Frame(e) => write!(f, "{e}"),
            Self::Bit(e) => write!(f, "{e}"),
            Self::Message(e) => write!(f, "{e}"),
        }
    }
}

impl core::error::Error for AdsbError {}

impl From<FrameError> for AdsbError {
    fn from(e: FrameError) -> Self {
        Self::Frame(e)
    }
}

impl From<BitError> for AdsbError {
    fn from(e: BitError) -> Self {
        Self::Bit(e)
    }
}

impl From<MessageError> for AdsbError {
    fn from(e: MessageError) -> Self {
        Self::Message(e)
    }
}