use crate::{
command::EmapiCommand,
constants::{COMMAND_HEADER_LENGTH, FRAME_HEAD, FRAME_OVERHEAD_LENGTH, FRAME_TAIL, RESERVED},
crc32,
endian::{read_uint16, read_uint32, uint16, uint32},
error::{EmapiError, Result},
};
pub struct FrameCodec;
impl FrameCodec {
pub fn encode(command: &EmapiCommand) -> Result<Vec<u8>> {
let data_length = COMMAND_HEADER_LENGTH + command.payload.len();
if data_length > usize::from(u16::MAX) {
return Err(EmapiError::Range {
name: "dataLength",
min: 0,
max: u64::from(u16::MAX),
value: data_length as u64,
});
}
let mut data = Vec::with_capacity(data_length);
data.push(command.command_type);
data.push(command.parent);
data.push(command.child);
data.extend_from_slice(&uint16(command.payload.len() as u16));
data.extend_from_slice(&command.payload);
let mut frame = Vec::with_capacity(data_length + FRAME_OVERHEAD_LENGTH);
frame.push(FRAME_HEAD);
frame.push(RESERVED);
frame.extend_from_slice(&uint16(data.len() as u16));
frame.extend_from_slice(&data);
frame.extend_from_slice(&uint32(crc32::compute(&data)));
frame.push(FRAME_TAIL);
Ok(frame)
}
pub fn decode(frame: &[u8]) -> Result<EmapiCommand> {
if frame.len() < FRAME_OVERHEAD_LENGTH {
return Err(EmapiError::FrameTooShort);
}
if frame[0] != FRAME_HEAD {
return Err(EmapiError::MissingFrameHead);
}
if frame[frame.len() - 1] != FRAME_TAIL {
return Err(EmapiError::MissingFrameTail);
}
let data_length = usize::from(read_uint16(frame, 2)?);
let expected_frame_length = data_length + FRAME_OVERHEAD_LENGTH;
if frame.len() != expected_frame_length {
return Err(EmapiError::FrameLengthMismatch {
expected: expected_frame_length,
actual: frame.len(),
});
}
if data_length < COMMAND_HEADER_LENGTH {
return Err(EmapiError::CommandDataTooShort);
}
let data_start = 4;
let data_end = data_start + data_length;
let data = &frame[data_start..data_end];
let expected_crc = read_uint32(frame, data_end)?;
let actual_crc = crc32::compute(data);
if expected_crc != actual_crc {
return Err(EmapiError::BadFrameCrc {
expected: expected_crc,
actual: actual_crc,
});
}
let payload_length = usize::from(read_uint16(data, 3)?);
let expected_data_length = COMMAND_HEADER_LENGTH + payload_length;
if data.len() != expected_data_length {
return Err(EmapiError::PayloadLengthMismatch {
expected: payload_length,
actual: data.len() - COMMAND_HEADER_LENGTH,
});
}
Ok(EmapiCommand::new(
data[0],
data[1],
data[2],
data[COMMAND_HEADER_LENGTH..].to_vec(),
))
}
}