squitter 0.1.1

no_std, no_alloc parser and encoder for 1090ES/DF17 (ADS-B extended squitter) messages
Documentation
//! Mode S CRC-24, the frame-integrity layer for DF17 (analogous to NMEA's
//! XOR checksum for AIS, but a real cyclic redundancy check).
//!
//! Per ICAO Annex 10 Vol IV ยง3.1.2.6. Algorithm and polynomial cross-checked
//! against both `FlightAware`'s `dump1090` (`crc.c`) and `pyModeS`
//! (`_bits.py`), which independently agree.

/// Mode S CRC-24 generator polynomial (24-bit form, top bit implicit).
const CRC_POLY: u32 = 0x00FF_F409;

/// 256-entry byte-at-a-time CRC-24 lookup table, built at compile time.
const CRC_TABLE: [u32; 256] = build_table();

const fn build_table() -> [u32; 256] {
    let mut table = [0u32; 256];
    let mut i = 0;
    while i < 256 {
        #[allow(
            clippy::cast_possible_truncation,
            reason = "i is always < 256 here, fits u32"
        )]
        let mut c = (i as u32) << 16;
        let mut bit = 0;
        while bit < 8 {
            c = if c & 0x0080_0000 != 0 {
                (c << 1) ^ CRC_POLY
            } else {
                c << 1
            };
            bit += 1;
        }
        table[i] = c & 0x00FF_FFFF;
        i += 1;
    }
    table
}

/// Computes the CRC-24 remainder of a 112-bit DF17 frame: the byte-at-a-time
/// CRC of the first 11 bytes (DF/CA/ICAO/ME), `XOR`ed with the trailing
/// 3-byte parity field. A valid, uncorrupted frame has a remainder of `0`.
#[must_use]
pub fn remainder(frame: &[u8; 14]) -> u32 {
    let mut crc: u32 = 0;
    for &byte in &frame[..11] {
        let index = ((crc >> 16) ^ u32::from(byte)) & 0xFF;
        crc = ((crc << 8) & 0x00FF_FFFF) ^ CRC_TABLE[index as usize];
    }
    let parity = (u32::from(frame[11]) << 16) | (u32::from(frame[12]) << 8) | u32::from(frame[13]);
    (crc ^ parity) & 0x00FF_FFFF
}

/// Whether `frame`'s trailing parity field matches the CRC-24 computed over
/// the rest of the frame.
#[must_use]
pub fn verify(frame: &[u8; 14]) -> bool {
    remainder(frame) == 0
}

/// Computes the correct 3-byte parity field for a frame whose first 11
/// bytes (DF/CA/ICAO/ME) are already filled in, and writes it into the
/// frame's trailing 3 bytes.
#[allow(
    clippy::cast_possible_truncation,
    reason = "crc is masked to 24 bits above, so each byte slice fits u8"
)]
pub fn write_parity(frame: &mut [u8; 14]) {
    let mut crc: u32 = 0;
    for &byte in &frame[..11] {
        let index = ((crc >> 16) ^ u32::from(byte)) & 0xFF;
        crc = ((crc << 8) & 0x00FF_FFFF) ^ CRC_TABLE[index as usize];
    }
    frame[11] = (crc >> 16) as u8;
    frame[12] = (crc >> 8) as u8;
    frame[13] = crc as u8;
}

#[cfg(test)]
mod tests {
    use super::*;

    fn hex_to_frame(hex: &str) -> [u8; 14] {
        let mut frame = [0u8; 14];
        for i in 0..14 {
            frame[i] = u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16).unwrap();
        }
        frame
    }

    #[test]
    fn verifies_a_real_captured_frame() {
        // real DF17 aircraft identification frame, independently verified
        // by pyModeS (docstring example in its crc_remainder()).
        let frame = hex_to_frame("8D406B902015A678D4D220AA4BDA");
        assert_eq!(remainder(&frame), 0);
        assert!(verify(&frame));
    }

    #[test]
    fn detects_a_corrupted_frame() {
        let mut frame = hex_to_frame("8D406B902015A678D4D220AA4BDA");
        frame[5] ^= 0x01;
        assert!(!verify(&frame));
    }

    #[test]
    fn write_parity_produces_a_verifiable_frame() {
        let original = hex_to_frame("8D406B902015A678D4D220AA4BDA");
        let mut frame = original;
        frame[11] = 0;
        frame[12] = 0;
        frame[13] = 0;
        write_parity(&mut frame);
        assert_eq!(frame, original);
        assert!(verify(&frame));
    }
}