libexail 0.1.0

A rust library for communicating with Exail devices through their binary protocol
Documentation
//! Header types for the four Exail frame kinds: output navigation data,
//! input sensor data, commands, and answers.

use crate::{
    blocks::{
        extended::ExtendedNavigationBlock, external::ExternalBlock, navigation::NavigationBlock,
    },
    error::{Error, Result},
};
use std::io::{Cursor, Read};

/// Sync pattern type identifying the message kind.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SyncType {
    NavData,
    Command,
    Answer,
}

/// Output navigation data header (IX sync, output mode).
#[derive(Debug, Clone, PartialEq)]
pub struct OutputHeader {
    pub version: u8,
    pub nav_bitmask: u32,
    pub extended_nav_bitmask: u32,
    pub external_bitmask: u32,
    pub nav_data_size: u16,
    pub total_size: u16,
    pub validity_time: u32,
    pub counter: u32,
}

/// Input sensor data header (IX sync, input mode).
#[derive(Debug, Clone, PartialEq)]
pub struct InputHeader {
    pub version: u8,
    pub nav_bitmask: u32,
    pub extended_nav_bitmask: u32,
    pub external_bitmask: u32,
    pub nav_data_size: u16,
    pub total_size: u16,
    pub timestamp_ref: u8,
    pub rfu: [u8; 7],
}

/// Command header (CM sync).
#[derive(Debug, Clone, PartialEq)]
pub struct CommandHeader {
    pub version: u8,
    pub total_size: u16,
}

/// Answer header (AN sync).
#[derive(Debug, Clone, PartialEq)]
pub struct AnswerHeader {
    pub version: u8,
    pub total_size: u16,
}

fn read_u8(cursor: &mut Cursor<&[u8]>) -> Result<u8> {
    let mut buf = [0u8; 1];
    cursor.read_exact(&mut buf).map_err(Error::Incomplete)?;
    Ok(buf[0])
}

fn read_u16(cursor: &mut Cursor<&[u8]>) -> Result<u16> {
    let mut buf = [0u8; 2];
    cursor.read_exact(&mut buf).map_err(Error::Incomplete)?;
    Ok(u16::from_be_bytes(buf))
}

fn read_u32(cursor: &mut Cursor<&[u8]>) -> Result<u32> {
    let mut buf = [0u8; 4];
    cursor.read_exact(&mut buf).map_err(Error::Incomplete)?;
    Ok(u32::from_be_bytes(buf))
}

impl OutputHeader {
    /// Construct an output header from block slices.
    pub fn new(
        version: u8,
        navigation: &[NavigationBlock],
        extended_navigation: &[ExtendedNavigationBlock],
        external: &[ExternalBlock],
        validity_time: u32,
        counter: u32,
    ) -> Result<Self> {
        let header_size = Self::header_size(version)?;
        let nav_size: usize = navigation.iter().map(|b| b.size()).sum();
        let ext_nav_size: usize = extended_navigation.iter().map(|b| b.size()).sum();
        let external_size: usize = external.iter().map(|b| b.size()).sum();
        let total_size = (header_size + nav_size + ext_nav_size + external_size + 4) as u16;
        let nav_data_size = if version >= 4 {
            (nav_size + ext_nav_size) as u16
        } else {
            0
        };

        Ok(Self {
            version,
            nav_bitmask: NavigationBlock::bitmask(navigation),
            extended_nav_bitmask: ExtendedNavigationBlock::bitmask(extended_navigation),
            external_bitmask: ExternalBlock::bitmask(external),
            nav_data_size,
            total_size,
            validity_time,
            counter,
        })
    }

    /// Header size in bytes for a given protocol version (including sync bytes).
    pub fn header_size(version: u8) -> Result<usize> {
        match version {
            2 => Ok(21),
            3 => Ok(25),
            4..=6 => Ok(27),
            v => Err(Error::UnsupportedVersion(v)),
        }
    }

    /// Parse an output header from bytes starting at the version byte
    /// (after the 2 sync bytes have been consumed).
    pub fn parse(data: &[u8]) -> Result<Self> {
        let cursor = &mut Cursor::new(data);
        let version = read_u8(cursor)?;

        match version {
            2 => {
                let nav_bitmask = read_u32(cursor)?;
                let external_bitmask = read_u32(cursor)?;
                let total_size = read_u16(cursor)?;
                let validity_time = read_u32(cursor)?;
                let counter = read_u32(cursor)?;
                Ok(OutputHeader {
                    version,
                    nav_bitmask,
                    extended_nav_bitmask: 0,
                    external_bitmask,
                    nav_data_size: 0,
                    total_size,
                    validity_time,
                    counter,
                })
            }
            3 => {
                let nav_bitmask = read_u32(cursor)?;
                let extended_nav_bitmask = read_u32(cursor)?;
                let external_bitmask = read_u32(cursor)?;
                let total_size = read_u16(cursor)?;
                let validity_time = read_u32(cursor)?;
                let counter = read_u32(cursor)?;
                Ok(OutputHeader {
                    version,
                    nav_bitmask,
                    extended_nav_bitmask,
                    external_bitmask,
                    nav_data_size: 0,
                    total_size,
                    validity_time,
                    counter,
                })
            }
            4..=6 => {
                let nav_bitmask = read_u32(cursor)?;
                let extended_nav_bitmask = read_u32(cursor)?;
                let external_bitmask = read_u32(cursor)?;
                let nav_data_size = read_u16(cursor)?;
                let total_size = read_u16(cursor)?;
                let validity_time = read_u32(cursor)?;
                let counter = read_u32(cursor)?;
                Ok(OutputHeader {
                    version,
                    nav_bitmask,
                    extended_nav_bitmask,
                    external_bitmask,
                    nav_data_size,
                    total_size,
                    validity_time,
                    counter,
                })
            }
            v => Err(Error::UnsupportedVersion(v)),
        }
    }
}

impl InputHeader {
    /// Construct an input header from block slices.
    pub fn new(version: u8, external: &[ExternalBlock], timestamp_ref: u8) -> Result<Self> {
        let header_size = Self::header_size(version)?;
        let external_size: usize = external.iter().map(|b| b.size()).sum();
        let total_size = (header_size + external_size + 4) as u16;

        Ok(Self {
            version,
            nav_bitmask: 0,
            extended_nav_bitmask: 0,
            external_bitmask: ExternalBlock::bitmask(external),
            nav_data_size: 0,
            total_size,
            timestamp_ref,
            rfu: [0u8; 7],
        })
    }

    /// Header size in bytes for a given protocol version (including sync bytes).
    pub fn header_size(version: u8) -> Result<usize> {
        match version {
            2 => Ok(21),
            3 => Ok(25),
            4..=6 => Ok(27),
            v => Err(Error::UnsupportedVersion(v)),
        }
    }

    /// Parse an input header from bytes starting at the version byte
    /// (after the 2 sync bytes have been consumed).
    pub fn parse(data: &[u8]) -> Result<Self> {
        let cursor = &mut Cursor::new(data);
        let version = read_u8(cursor)?;

        match version {
            2 => {
                let nav_bitmask = read_u32(cursor)?;
                let external_bitmask = read_u32(cursor)?;
                let total_size = read_u16(cursor)?;
                let timestamp_ref = read_u8(cursor)?;
                let mut rfu = [0u8; 7];
                cursor.read_exact(&mut rfu).map_err(Error::Incomplete)?;
                Ok(InputHeader {
                    version,
                    nav_bitmask,
                    extended_nav_bitmask: 0,
                    external_bitmask,
                    nav_data_size: 0,
                    total_size,
                    timestamp_ref,
                    rfu,
                })
            }
            3 => {
                let nav_bitmask = read_u32(cursor)?;
                let extended_nav_bitmask = read_u32(cursor)?;
                let external_bitmask = read_u32(cursor)?;
                let total_size = read_u16(cursor)?;
                let timestamp_ref = read_u8(cursor)?;
                let mut rfu = [0u8; 7];
                cursor.read_exact(&mut rfu).map_err(Error::Incomplete)?;
                Ok(InputHeader {
                    version,
                    nav_bitmask,
                    extended_nav_bitmask,
                    external_bitmask,
                    nav_data_size: 0,
                    total_size,
                    timestamp_ref,
                    rfu,
                })
            }
            4..=6 => {
                let nav_bitmask = read_u32(cursor)?;
                let extended_nav_bitmask = read_u32(cursor)?;
                let external_bitmask = read_u32(cursor)?;
                let nav_data_size = read_u16(cursor)?;
                let total_size = read_u16(cursor)?;
                let timestamp_ref = read_u8(cursor)?;
                let mut rfu = [0u8; 7];
                cursor.read_exact(&mut rfu).map_err(Error::Incomplete)?;
                Ok(InputHeader {
                    version,
                    nav_bitmask,
                    extended_nav_bitmask,
                    external_bitmask,
                    nav_data_size,
                    total_size,
                    timestamp_ref,
                    rfu,
                })
            }
            v => Err(Error::UnsupportedVersion(v)),
        }
    }
}

impl CommandHeader {
    pub const HEADER_SIZE: usize = 5;

    pub fn parse(data: &[u8]) -> Result<Self> {
        let cursor = &mut Cursor::new(data);
        let version = read_u8(cursor)?;
        if version != 3 {
            return Err(Error::UnsupportedVersion(version));
        }
        let total_size = read_u16(cursor)?;
        Ok(CommandHeader {
            version,
            total_size,
        })
    }
}

impl AnswerHeader {
    pub const HEADER_SIZE: usize = 5;

    pub fn parse(data: &[u8]) -> Result<Self> {
        let cursor = &mut Cursor::new(data);
        let version = read_u8(cursor)?;
        if version != 3 {
            return Err(Error::UnsupportedVersion(version));
        }
        let total_size = read_u16(cursor)?;
        Ok(AnswerHeader {
            version,
            total_size,
        })
    }
}

#[cfg(test)]
mod tests {
    use crate::header::{AnswerHeader, CommandHeader, OutputHeader};

    #[test]
    fn test_command_header_parse() {
        let data = [0x03, 0x00, 0x09];
        let hdr = CommandHeader::parse(&data).unwrap();
        assert_eq!(hdr.version, 3);
        assert_eq!(hdr.total_size, 9);
    }

    #[test]
    fn test_answer_header_parse() {
        let data = [0x03, 0x00, 0x09];
        let hdr = AnswerHeader::parse(&data).unwrap();
        assert_eq!(hdr.version, 3);
        assert_eq!(hdr.total_size, 9);
    }

    #[test]
    fn test_output_header_v2() {
        let mut buf = Vec::new();
        buf.push(0x02);
        buf.extend_from_slice(&0x0000_0001u32.to_be_bytes());
        buf.extend_from_slice(&0x0000_0000u32.to_be_bytes());
        buf.extend_from_slice(&21u16.to_be_bytes());
        buf.extend_from_slice(&1000u32.to_be_bytes());
        buf.extend_from_slice(&42u32.to_be_bytes());

        let hdr = OutputHeader::parse(&buf).unwrap();
        assert_eq!(hdr.version, 2);
        assert_eq!(hdr.nav_bitmask, 1);
        assert_eq!(hdr.extended_nav_bitmask, 0);
        assert_eq!(hdr.counter, 42);
    }

    #[test]
    fn test_output_header_v4() {
        let mut buf = Vec::new();
        buf.push(0x04);
        buf.extend_from_slice(&0x0000_0003u32.to_be_bytes());
        buf.extend_from_slice(&0x0000_0000u32.to_be_bytes());
        buf.extend_from_slice(&0x0000_0000u32.to_be_bytes());
        buf.extend_from_slice(&24u16.to_be_bytes());
        buf.extend_from_slice(&51u16.to_be_bytes());
        buf.extend_from_slice(&2000u32.to_be_bytes());
        buf.extend_from_slice(&99u32.to_be_bytes());

        let hdr = OutputHeader::parse(&buf).unwrap();
        assert_eq!(hdr.version, 4);
        assert_eq!(hdr.nav_bitmask, 3);
        assert_eq!(hdr.nav_data_size, 24);
        assert_eq!(hdr.total_size, 51);
        assert_eq!(hdr.counter, 99);
    }
}