use crate::{
blocks::{
InputDataFrame, Message, NavigationDataFrame, extended::parse_extended_navigation_blocks,
external::parse_external_blocks, navigation::parse_navigation_blocks,
},
framing,
header::{AnswerHeader, CommandHeader, InputHeader, OutputHeader, SyncType},
};
use std::io::Cursor;
enum ParseError {
IncompleteData,
InvalidSync,
InvalidChecksum,
InvalidPayload,
}
fn parse_frame(input: &[u8]) -> Result<(Message, usize), ParseError> {
if input.len() < 3 {
return Err(ParseError::IncompleteData);
}
let sync_type = framing::is_sync(input[0], input[1]).ok_or(ParseError::InvalidSync)?;
let version = input[2];
match sync_type {
SyncType::Command => {
if input.len() < CommandHeader::HEADER_SIZE {
return Err(ParseError::IncompleteData);
}
let header =
CommandHeader::parse(&input[2..]).map_err(|_| ParseError::InvalidPayload)?;
let total = header.total_size as usize;
if input.len() < total {
return Err(ParseError::IncompleteData);
}
let frame = &input[..total];
if framing::validate_checksum(frame) != Some(true) {
return Err(ParseError::InvalidChecksum);
}
let body = input[CommandHeader::HEADER_SIZE..total - 4].to_vec();
Ok((Message::Command(body), total))
}
SyncType::Answer => {
if input.len() < AnswerHeader::HEADER_SIZE {
return Err(ParseError::IncompleteData);
}
let header =
AnswerHeader::parse(&input[2..]).map_err(|_| ParseError::InvalidPayload)?;
let total = header.total_size as usize;
if input.len() < total {
return Err(ParseError::IncompleteData);
}
let frame = &input[..total];
if framing::validate_checksum(frame) != Some(true) {
return Err(ParseError::InvalidChecksum);
}
let body = input[AnswerHeader::HEADER_SIZE..total - 4].to_vec();
Ok((Message::Answer(body), total))
}
SyncType::NavData => {
let header_size =
OutputHeader::header_size(version).map_err(|_| ParseError::InvalidPayload)?;
if input.len() < header_size {
return Err(ParseError::IncompleteData);
}
let output_header =
OutputHeader::parse(&input[2..]).map_err(|_| ParseError::InvalidPayload)?;
let total = output_header.total_size as usize;
if input.len() < total {
return Err(ParseError::IncompleteData);
}
let frame = &input[..total];
if framing::validate_checksum(frame) != Some(true) {
return Err(ParseError::InvalidChecksum);
}
let body = &input[header_size..total - 4];
if output_header.nav_bitmask != 0 || output_header.extended_nav_bitmask != 0 {
let cursor = &mut Cursor::new(body);
let navigation = parse_navigation_blocks(
output_header.nav_bitmask,
output_header.version,
cursor,
)
.map_err(|_| ParseError::InvalidPayload)?;
let extended_navigation =
parse_extended_navigation_blocks(output_header.extended_nav_bitmask, cursor)
.map_err(|_| ParseError::InvalidPayload)?;
let external = parse_external_blocks(output_header.external_bitmask, cursor)
.map_err(|_| ParseError::InvalidPayload)?;
Ok((
Message::NavigationData(NavigationDataFrame {
header: output_header,
navigation,
extended_navigation,
external,
}),
total,
))
} else {
let input_header =
InputHeader::parse(&input[2..]).map_err(|_| ParseError::InvalidPayload)?;
let cursor = &mut Cursor::new(body);
let external = parse_external_blocks(input_header.external_bitmask, cursor)
.map_err(|_| ParseError::InvalidPayload)?;
Ok((
Message::InputData(InputDataFrame {
header: input_header,
external,
}),
total,
))
}
}
}
}
#[derive(Debug)]
pub enum DatagramError {
IncompleteData,
InvalidSync,
InvalidChecksum,
InvalidPayload,
}
pub fn parse_datagram(datagram: &[u8]) -> Result<Message, DatagramError> {
match parse_frame(datagram) {
Ok((message, _len)) => Ok(message),
Err(ParseError::IncompleteData) => Err(DatagramError::IncompleteData),
Err(ParseError::InvalidSync) => Err(DatagramError::InvalidSync),
Err(ParseError::InvalidChecksum) => Err(DatagramError::InvalidChecksum),
Err(ParseError::InvalidPayload) => Err(DatagramError::InvalidPayload),
}
}
pub struct ExailParser {
buf: Vec<u8>,
buf_start: usize,
}
impl ExailParser {
pub fn new() -> Self {
Self {
buf: Vec::new(),
buf_start: 0,
}
}
pub fn consume(&mut self, input: &[u8]) -> Option<Message> {
self.buf.extend(input);
loop {
let available = &self.buf[self.buf_start..];
if available.is_empty() {
return None;
}
match parse_frame(available) {
Ok((message, bytes_consumed)) => {
self.buf_start += bytes_consumed;
if self.buf_start > self.buf.len() / 2 {
self.buf.drain(0..self.buf_start);
self.buf_start = 0;
}
return Some(message);
}
Err(ParseError::IncompleteData) => {
return None;
}
Err(
ParseError::InvalidSync
| ParseError::InvalidChecksum
| ParseError::InvalidPayload,
) => {
self.buf_start += 1;
if self.buf_start >= self.buf.len() {
self.buf.clear();
self.buf_start = 0;
return None;
}
}
}
}
}
pub fn buffer_len(&self) -> usize {
self.buf.len() - self.buf_start
}
pub fn clear(&mut self) {
self.buf.clear();
self.buf_start = 0;
}
}
impl Default for ExailParser {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use crate::framing;
use crate::parser::{DatagramError, ExailParser, parse_datagram};
#[test]
fn test_parser_incomplete() {
let mut parser = ExailParser::new();
assert!(parser.consume(&[b'I', b'X']).is_none());
assert_eq!(parser.buffer_len(), 2);
}
#[test]
fn test_parser_clear() {
let mut parser = ExailParser::new();
parser.consume(&[0x01, 0x02, 0x03]);
parser.clear();
assert_eq!(parser.buffer_len(), 0);
}
#[test]
fn test_datagram_too_short() {
assert!(matches!(
parse_datagram(&[b'I']),
Err(DatagramError::IncompleteData)
));
}
#[test]
fn test_datagram_bad_sync() {
assert!(matches!(
parse_datagram(&[b'Z', b'Z', 0x03, 0x00, 0x00]),
Err(DatagramError::InvalidSync)
));
}
#[test]
fn test_command_round_trip() {
let body = [0xAA, 0xBB];
let mut frame = Vec::new();
frame.push(b'C');
frame.push(b'M');
frame.push(0x03);
frame.extend_from_slice(&11u16.to_be_bytes());
frame.extend_from_slice(&body);
let checksum = framing::calculate_checksum(&frame);
frame.extend_from_slice(&checksum.to_be_bytes());
let msg = parse_datagram(&frame).unwrap();
match msg {
crate::blocks::Message::Command(data) => assert_eq!(data, body),
other => panic!("expected Command, got {:?}", other),
}
}
#[test]
fn test_streaming_command() {
let body = [0xCC];
let mut frame = Vec::new();
frame.push(b'C');
frame.push(b'M');
frame.push(0x03);
frame.extend_from_slice(&10u16.to_be_bytes());
frame.extend_from_slice(&body);
let checksum = framing::calculate_checksum(&frame);
frame.extend_from_slice(&checksum.to_be_bytes());
let mut parser = ExailParser::new();
let msg = parser.consume(&frame).expect("should parse command frame");
match msg {
crate::blocks::Message::Command(data) => assert_eq!(data, [0xCC]),
other => panic!("expected Command, got {:?}", other),
}
assert_eq!(parser.buffer_len(), 0);
}
}