micro_ihex 0.1.0

A no std Intel Hex parser and serializer library
Documentation
use crate::checksum::checksum;
use crate::types;
use crate::IHex;
use core::fmt;
use core::iter::FusedIterator;
use core::str::FromStr;

#[derive(Debug, PartialEq)]
pub enum ParseError {
    MissingColon,
    DecodeError(hex::FromHexError),
    BadChecksum(u8, u8),
    BadLength,
    BadType,
}

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ParseError::MissingColon => write!(f, "missing start colon"),
            ParseError::DecodeError(e) => write!(f, "hex decode error: {}", e),
            ParseError::BadChecksum(expected, checksum) => write!(
                f,
                "incorrect checksum: expected {}, got {}",
                expected, checksum
            ),
            ParseError::BadLength => write!(f, "incorrect data length"),
            ParseError::BadType => write!(f, "unknown record type"),
        }
    }
}

type ParseResult = Result<IHex, ParseError>;

impl IHex {
    pub fn parse<T: AsRef<[u8]>>(line: T) -> ParseResult {
        let line = line.as_ref();

        if line[0] != b':' {
            return Err(ParseError::MissingColon);
        }

        let line = &line[1..];

        let mut bytes = [0; 0x200];

        let length = line.len() / 2;

        if let Err(e) = hex::decode_to_slice(line, &mut bytes[..length]) {
            return Err(ParseError::DecodeError(e));
        }

        let expected_checksum = bytes[length - 1];
        let bytes = &bytes[..length - 1];

        let checksum = checksum(bytes);

        if checksum != expected_checksum {
            return Err(ParseError::BadChecksum(expected_checksum, checksum));
        }

        let length = bytes[0];

        let mut short = [0; 2];

        short.clone_from_slice(&bytes[1..3]);
        let address = u16::from_be_bytes(short);

        let record_type = bytes[3];
        let data = &bytes[4..];

        if data.len() != length as usize {
            return Err(ParseError::BadLength);
        }

        match record_type {
            types::DATA => {
                let mut bytes = [0; 0xFF];

                bytes[..data.len()].clone_from_slice(data);

                Ok(Self::Data {
                    bytes,
                    length,
                    offset: address,
                })
            }
            types::END_OF_FILE => Ok(Self::EndOfFile),
            types::EXTENDED_SEGMENT_ADDRESS => {
                let mut short = [0; 2];

                short.clone_from_slice(&data[0..2]);
                let address = u16::from_be_bytes(short);

                Ok(Self::ExtendedSegmentAddress(address))
            }
            types::START_SEGMENT_ADDRESS => {
                let mut short = [0; 2];

                short.clone_from_slice(&data[0..2]);
                let cs = u16::from_be_bytes(short);

                short.clone_from_slice(&data[2..4]);
                let ip = u16::from_be_bytes(short);

                Ok(Self::StartSegmentAddress { cs, ip })
            }
            types::EXTENDED_LINEAR_ADDRESS => {
                let mut short = [0; 2];

                short.clone_from_slice(&data[0..2]);
                let ela = u16::from_be_bytes(short);

                Ok(Self::ExtendedLinearAddress(ela))
            }
            types::START_LINEAR_ADDRESS => {
                let mut word = [0; 4];

                word.clone_from_slice(&data[0..4]);
                let sla = u32::from_be_bytes(word);

                Ok(Self::StartLinearAddress(sla))
            }
            _ => Err(ParseError::BadType),
        }
    }
}

impl FromStr for IHex {
    type Err = ParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Self::parse(s)
    }
}

pub struct Parser<'a> {
    inner: core::str::Lines<'a>,
    done: bool,
}

impl<'a> Parser<'a> {
    pub fn new(s: &'a str) -> Self {
        Parser {
            inner: s.lines(),
            done: false,
        }
    }

    fn next_line(&mut self) -> Option<&'a str> {
        for line in &mut self.inner {
            if !line.is_empty() {
                return Some(line);
            }
        }

        None
    }
}

impl<'a> Iterator for Parser<'a> {
    type Item = ParseResult;

    fn next(&mut self) -> Option<Self::Item> {
        if self.done {
            return None;
        }

        self.next_line().map(IHex::parse)
    }
}

impl<'a> FusedIterator for Parser<'a> {}

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

    #[test]
    fn parse_data() {
        let expected = [
            0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x20, 0x67, 0x61, 0x70,
        ];

        let mut bytes = [0; 0xFF];
        bytes[..expected.len()].clone_from_slice(&expected);
        let data = IHex::Data {
            bytes,
            length: expected.len() as u8,
            offset: 0x0010,
        };

        assert_eq!(":0B0010006164647265737320676170A7".parse(), Ok(data));
    }

    #[test]
    fn parse_eof() {
        let eof = IHex::EndOfFile;

        assert_eq!(":00000001FF".parse(), Ok(eof));
    }

    #[test]
    fn parse_extended_segment_address() {
        let esa = IHex::ExtendedSegmentAddress(0x12FE);

        assert_eq!(":0200000212FEEC".parse(), Ok(esa));
    }

    #[test]
    fn parse_start_segment_address() {
        let ssa = IHex::StartSegmentAddress {
            cs: 0x1234,
            ip: 0x3800,
        };

        assert_eq!(":04000003123438007B".parse(), Ok(ssa));
    }

    #[test]
    fn parse_extended_linear_address() {
        let ela = IHex::ExtendedLinearAddress(0xABCD);

        assert_eq!(":02000004ABCD82".parse(), Ok(ela));
    }

    #[test]
    fn parse_start_linear_address() {
        let sla = IHex::StartLinearAddress(0x12345678);

        assert_eq!(":0400000512345678E3".parse(), Ok(sla));
    }

    #[test]
    fn multi_line_parser() {
        let ela = IHex::ExtendedLinearAddress(0xABCD);
        let sla = IHex::StartLinearAddress(0x12345678);

        let mut parser = Parser::new(":02000004ABCD82\r\n\r\n:0400000512345678E3\r\n");

        assert_eq!(parser.next(), Some(Ok(ela)));
        assert_eq!(parser.next(), Some(Ok(sla)));
        assert_eq!(parser.next(), None)
    }
}