use crate::cobs;
use crate::crc::{crc8_valid, crc16_valid};
use crate::frame::{DATA_OFFSET, check_header_fields, wire_length};
use crate::{Frame, FrameError, FrameHeader, FrameType, PREAMBLE};
#[derive(Debug, Clone, Copy)]
pub(crate) struct GoodHeader {
pub(crate) header: FrameHeader,
pub(crate) length: usize,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct ValidFrame {
pub(crate) header: FrameHeader,
pub(crate) data_length: usize,
pub(crate) consumed: usize,
}
pub(crate) fn check_header(input: &[u8], reserved_cobs: bool) -> Result<GoodHeader, FrameError> {
if input.len() < DATA_OFFSET {
return Err(FrameError::TooShort {
needed: DATA_OFFSET,
available: input.len(),
});
}
if input[..PREAMBLE.len()] != PREAMBLE {
return Err(FrameError::InvalidPreamble {
first: input[0],
second: input[1],
});
}
if !crc8_valid(&input[PREAMBLE.len()..DATA_OFFSET]) {
return Err(FrameError::HeaderCrcMismatch);
}
let frame_type = FrameType::from_raw(input[2]);
let (destination, source) = (input[3], input[4]);
let length = usize::from(u16::from_be_bytes([input[5], input[6]]));
match check_header_fields(frame_type, source, length) {
Err(FrameError::UnsupportedCobsFrame { .. }) if reserved_cobs => {}
result => result?,
}
Ok(GoodHeader {
header: FrameHeader::new(frame_type, destination, source),
length,
})
}
pub(crate) fn check_frame(input: &[u8], reserved_cobs: bool) -> Result<ValidFrame, FrameError> {
let GoodHeader { header, length } = check_header(input, reserved_cobs)?;
let consumed = wire_length(length);
if input.len() < consumed {
return Err(FrameError::Truncated {
length,
available: input.len() - DATA_OFFSET,
});
}
let body = &input[DATA_OFFSET..consumed];
let data_length = if header.frame_type.is_cobs() {
cobs::check_fields(body).ok_or(FrameError::DataCrcMismatch { length })?
} else {
if length != 0 && !crc16_valid(body) {
return Err(FrameError::DataCrcMismatch { length });
}
length
};
Ok(ValidFrame {
header,
data_length,
consumed,
})
}
pub(crate) fn decode_extended_in_place(input: &mut [u8], valid: &ValidFrame) {
if valid.header.frame_type.is_cobs() {
cobs::decode_data_in_place(&mut input[DATA_OFFSET..valid.consumed]);
}
}
pub fn decode_frame(input: &[u8]) -> Result<(Frame<'_>, usize), FrameError> {
let frame_type = check_header(input, false)?.header.frame_type;
if frame_type.is_cobs() {
return Err(FrameError::ExtendedFrame {
raw_type: frame_type.to_raw(),
});
}
let valid = check_frame(input, false)?;
let frame = Frame {
header: valid.header,
data: &input[DATA_OFFSET..DATA_OFFSET + valid.data_length],
};
Ok((frame, valid.consumed))
}
pub fn decode_frame_in_place(input: &mut [u8]) -> Result<(Frame<'_>, usize), FrameError> {
let valid = check_frame(input, false)?;
decode_extended_in_place(input, &valid);
let frame = Frame {
header: valid.header,
data: &input[DATA_OFFSET..DATA_OFFSET + valid.data_length],
};
Ok((frame, valid.consumed))
}
#[must_use]
pub fn find_preamble(data: &[u8]) -> Option<usize> {
data.windows(2).position(|pair| pair == PREAMBLE)
}