libexail 0.1.0

A rust library for communicating with Exail devices through their binary protocol
Documentation
//! Data block types for the Exail binary protocol.
//!
//! Each Exail frame carries a set of data blocks selected by bitmasks in the
//! header. This module defines enums whose variants correspond to individual
//! block types, along with parsing and bitmask computation functions.

use crate::{
    blocks::{
        extended::ExtendedNavigationBlock, external::ExternalBlock, navigation::NavigationBlock,
    },
    header::{InputHeader, OutputHeader},
};

pub mod extended;
pub mod external;
pub mod navigation;

pub(crate) fn read_be<T: for<'a> binrw::BinRead<Args<'a> = ()>>(
    cursor: &mut std::io::Cursor<&[u8]>,
) -> crate::error::Result<T> {
    T::read_be(cursor).map_err(crate::error::Error::InvalidBlock)
}

/// Parse blocks from a bitmask by calling a reader function for each set bit.
/// The reader is expected to return `Ok(Some(block))` for normal blocks, `Ok(None)`
/// to skip a reserved bit, and `Err` on failure.
pub(crate) fn parse_blocks<B>(
    bitmask: u32,
    num_bits: u32,
    mut read_fn: impl FnMut(u32) -> crate::error::Result<Option<B>>,
) -> crate::error::Result<Vec<B>> {
    (0..num_bits)
        .filter(|&bit| bitmask & (1 << bit) != 0)
        .filter_map(|bit| read_fn(bit).transpose())
        .collect()
}

/// Generate a block enum with `bit()`, `size()`, `read()`, and `bitmask()`.
///
/// Each entry maps a bit index to a variant name, inner type, and byte size.
/// An optional `if` guard on the bit index selects between variants that share
/// a bit (e.g. version-dependent blocks).
macro_rules! block_enum {
    (
        $(#[$meta:meta])*
        pub enum $name:ident {
            $(
                $bit:literal $(if $guard:expr)? => $variant:ident($inner:ty, $size:literal)
            ),* $(,)?
        }
        read_fn($($rp:ident: $rpt:ty),*);
    ) => {
        $(#[$meta])*
        #[derive(Debug, Clone, serde::Serialize)]
        pub enum $name {
            $($variant($inner),)*
        }

        impl $name {
            /// Return the bitmask bit index for this variant.
            pub fn bit(&self) -> u32 {
                match self {
                    $(Self::$variant(_) => $bit,)*
                }
            }

            /// Size of this block's payload in bytes.
            pub fn size(&self) -> usize {
                match self {
                    $(Self::$variant(_) => $size,)*
                }
            }

            /// Read the block variant for the given bit index from the cursor.
            pub fn read(bit: u32, $($rp: $rpt,)* cursor: &mut std::io::Cursor<&[u8]>) -> $crate::error::Result<Self> {
                match bit {
                    $($bit $(if $guard)? => Ok(Self::$variant($crate::blocks::read_be(cursor)?)),)*
                    _ => Err($crate::error::Error::InvalidBlock(
                        binrw::Error::AssertFail {
                            pos: cursor.position(),
                            message: format!("unknown {} bit {bit}", stringify!($name)),
                        }
                    )),
                }
            }

            /// Compute the bitmask from a slice of blocks.
            pub fn bitmask(blocks: &[Self]) -> u32 {
                blocks.iter().fold(0u32, |mask, b| mask | (1 << b.bit()))
            }
        }
    };
}

pub(crate) use block_enum;

/// A parsed output navigation data frame.
#[derive(Debug, Clone)]
pub struct NavigationDataFrame {
    pub header: OutputHeader,
    pub navigation: Vec<NavigationBlock>,
    pub extended_navigation: Vec<ExtendedNavigationBlock>,
    pub external: Vec<ExternalBlock>,
}

/// A parsed input sensor data frame.
#[derive(Debug, Clone)]
pub struct InputDataFrame {
    pub header: InputHeader,
    pub external: Vec<ExternalBlock>,
}

/// Top-level parsed message.
#[derive(Debug, Clone)]
pub enum Message {
    NavigationData(NavigationDataFrame),
    InputData(InputDataFrame),
    Command(Vec<u8>),
    Answer(Vec<u8>),
}