use nom::bytes::streaming::{tag, take, take_till};
use nom::combinator::{map, verify};
use nom::error::context;
use nom::number::streaming::{be_u16, be_u32, u8};
use nom::Parser as _;
use nom_language::error::{VerboseError, VerboseErrorKind};
use strum::{EnumString, IntoStaticStr};
use thiserror::Error;
use crate::frame::SOF;
use super::{Frame, PacketType};
#[derive(Debug, Error, EnumString, IntoStaticStr)]
pub enum ParseErrorKind {
#[error("checksum error")]
ChecksumError,
#[error("invalid length")]
InvalidLength,
}
pub enum ParseResult<'a> {
Ok { rest: &'a [u8], frame: Frame },
Incomplete,
Failure {
rest: &'a [u8],
broken_data: &'a [u8],
error: ParseErrorKind,
},
}
pub type NomParseResult<T, U> = nom::IResult<T, U, VerboseError<T>>;
pub struct FrameParser {}
impl FrameParser {
#[must_use]
pub fn parse(input: &[u8]) -> ParseResult<'_> {
match Self::frame_parser(input) {
Ok((rest, frame)) => ParseResult::Ok { rest, frame },
Err(error) => match error {
nom::Err::Incomplete(_n) => ParseResult::Incomplete,
nom::Err::Error(error) | nom::Err::Failure(error) => {
let (rest, error) = error.errors.last().unwrap();
let error = match error {
VerboseErrorKind::Context(context) => {
ParseErrorKind::try_from(*context).unwrap()
}
VerboseErrorKind::Char(_) | VerboseErrorKind::Nom(_) => unimplemented!(),
};
ParseResult::Failure {
rest,
broken_data: input,
error,
}
}
},
}
}
fn frame_parser(data: &[u8]) -> NomParseResult<&[u8], Frame> {
let (message, _) = take_till(|b| b == SOF)(data)?;
let (input, _) = tag(&[SOF][..]).parse(message)?;
let (input, source_address) = map(u8, |source| source ^ 0x80).parse(input)?;
let (input, destination_address) = u8(input)?;
let (input, header_length) = context(
ParseErrorKind::InvalidLength.into(),
verify(u8, |&header_length| {
(4 + 4 + 2 + 1..70).contains(&header_length)
}),
)
.parse(input)?;
let payload_len = header_length - 4 - 4 - 2 - 1; let (input, packet_type) = u8(input)?;
let (input, field_id) = map(be_u32, |field_id| {
if [PacketType::Set as u8, PacketType::Get as u8].contains(&packet_type) {
(field_id & 0x0000_ffff)
| ((field_id >> 8) & 0x00ff_0000)
| ((field_id << 8) & 0xff00_0000)
} else {
field_id
}
})
.parse(input)?;
let (input, payload) = take(payload_len)(input)?;
let (_, message_without_checksum) = take(header_length - 2)(message)?;
let calculated_crc = crc16::State::<crc16::XMODEM>::calculate(message_without_checksum);
let (input, _crc) = context(
ParseErrorKind::ChecksumError.into(),
verify(be_u16, |&crc| crc == calculated_crc),
)
.parse(input)?;
Ok((
input,
Frame::new(
destination_address,
source_address,
packet_type,
field_id,
payload.to_vec(),
),
))
}
}
#[cfg(test)]
mod tests {
use nom_language::error::VerboseErrorKind;
use crate::frame::parser::ParseResult;
use super::{Frame, FrameParser};
#[test]
fn test_parse_get_message() {
let data = &[220, 194, 0, 11, 6, 61, 5, 25, 240, 36, 62];
let want = Frame::new(0, 66, 6, 87890416, vec![]);
let (rest, broetje) = FrameParser::frame_parser(data).unwrap();
assert_eq!(want, broetje);
assert!(rest.is_empty());
}
#[test]
fn test_parse_ret_message() {
let data = &[220, 128, 66, 14, 7, 5, 61, 25, 240, 0, 0, 15, 29, 116];
let want = Frame::new(66, 0, 7, 87890416, vec![0, 0, 15]);
let (rest, broetje) = FrameParser::frame_parser(data).unwrap();
assert_eq!(want, broetje);
assert!(rest.is_empty());
}
#[test]
fn test_parse_two_correct_frames() {
let test_data: &[u8; 11] = &[220, 194, 0, 11, 6, 61, 5, 25, 240, 36, 62];
let test_frame = Frame::new(0, 66, 6, 87890416, vec![]);
let testcase = vec![test_data.to_vec(), test_data.to_vec()]
.into_iter()
.flatten()
.collect::<Vec<_>>();
let want = test_frame;
let ParseResult::Ok { rest, frame } = Frame::parse(&testcase) else {
panic!("not a frame")
};
assert!(!rest.is_empty());
assert_eq!(frame, want);
let ParseResult::Ok { rest, frame } = Frame::parse(&testcase) else {
panic!("not a frame")
};
assert!(!rest.is_empty());
assert_eq!(frame, want);
}
#[test]
fn test_parser_frame_too_short_minimum() {
let data = &[220, 1, 2, 11, 4, 5, 6, 7, 8, 9];
let error = FrameParser::frame_parser(data).expect_err("not an error");
assert_eq!(
error,
nom::Err::Incomplete(nom::Needed::Size(std::num::NonZeroUsize::new(1).unwrap()))
);
}
#[test]
fn test_parse_frame_too_short() {
let data = &[220, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let error = FrameParser::frame_parser(data).expect_err("not an error");
assert_eq!(
error,
nom::Err::Incomplete(nom::Needed::Size(std::num::NonZeroUsize::new(1).unwrap()))
);
}
#[test]
fn test_parse_header_length_invalid_low() {
let data = &[220, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let nom::Err::Error(result) = FrameParser::frame_parser(data).expect_err("not an error")
else {
panic!()
};
assert_eq!(
result.errors[1].1,
VerboseErrorKind::Context("InvalidLength")
);
}
#[test]
fn test_parse_header_length_invalid_high() {
let data = &[220, 0, 0, 70, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let nom::Err::Error(result) = FrameParser::frame_parser(data).expect_err("not an error")
else {
panic!()
};
assert_eq!(
result.errors[1].1,
VerboseErrorKind::Context("InvalidLength")
);
}
#[test]
fn test_parse_no_sof() {
let data = &[0xBB, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0];
let error = FrameParser::frame_parser(data).expect_err("not an error");
assert_eq!(
error,
nom::Err::Incomplete(nom::Needed::Size(std::num::NonZeroUsize::new(1).unwrap()))
);
}
#[test]
fn test_parse_leading_garbage_then_ok() {
let data = &[0, 1, 2, 3, 220, 194, 0, 11, 6, 61, 5, 25, 240, 36, 62];
let want = Frame::new(0, 66, 6, 87890416, vec![]);
let (rest, broetje) = FrameParser::frame_parser(data).unwrap();
assert_eq!(want, broetje);
assert!(rest.is_empty());
}
#[test]
fn test_parse_frame_crc_error() {
let data = &[220, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let nom::Err::Error(result) = FrameParser::frame_parser(data).expect_err("not an error")
else {
panic!()
};
assert_eq!(
result.errors[1].1,
VerboseErrorKind::Context("ChecksumError")
);
}
}