use thiserror::Error;
pub const HEADER_SIZE: usize = 4;
pub const MAVLINK_MAX_FRAME: usize = 65536;
pub const PLUGIN_MAX_FRAME: usize = 4 * 1024 * 1024;
#[derive(Debug, Error, PartialEq, Eq)]
pub enum FrameError {
#[error("frame length {len} exceeds max {max}")]
TooLarge { len: usize, max: usize },
#[error("frame length is zero")]
ZeroLength,
#[error("payload of {0} bytes does not fit in a u32 length prefix")]
PayloadTooBig(usize),
}
pub fn encode_frame(payload: &[u8], max: usize) -> Result<Vec<u8>, FrameError> {
let n = payload.len();
if n > max {
return Err(FrameError::TooLarge { len: n, max });
}
let len = u32::try_from(n).map_err(|_| FrameError::PayloadTooBig(n))?;
let mut out = Vec::with_capacity(HEADER_SIZE + n);
out.extend_from_slice(&len.to_be_bytes());
out.extend_from_slice(payload);
Ok(out)
}
pub fn decode_len(
header: [u8; HEADER_SIZE],
max: usize,
reject_zero: bool,
) -> Result<usize, FrameError> {
let len = u32::from_be_bytes(header) as usize;
if reject_zero && len == 0 {
return Err(FrameError::ZeroLength);
}
if len > max {
return Err(FrameError::TooLarge { len, max });
}
Ok(len)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_header_matches_python_struct_pack() {
let payload = b"hello world";
let frame = encode_frame(payload, MAVLINK_MAX_FRAME).unwrap();
assert_eq!(&frame[..HEADER_SIZE], &[0, 0, 0, 11]);
assert_eq!(&frame[HEADER_SIZE..], payload);
let header: [u8; HEADER_SIZE] = frame[..HEADER_SIZE].try_into().unwrap();
let len = decode_len(header, MAVLINK_MAX_FRAME, false).unwrap();
assert_eq!(len, payload.len());
}
#[test]
fn empty_payload_encodes_zero_length() {
let frame = encode_frame(b"", MAVLINK_MAX_FRAME).unwrap();
assert_eq!(frame, vec![0, 0, 0, 0]);
}
#[test]
fn mavlink_allows_zero_length_plugin_rejects_it() {
let header = [0u8, 0, 0, 0];
assert_eq!(decode_len(header, MAVLINK_MAX_FRAME, false).unwrap(), 0);
assert_eq!(
decode_len(header, PLUGIN_MAX_FRAME, true),
Err(FrameError::ZeroLength)
);
}
#[test]
fn oversized_payload_is_rejected_on_encode() {
let payload = vec![0u8; MAVLINK_MAX_FRAME + 1];
assert_eq!(
encode_frame(&payload, MAVLINK_MAX_FRAME),
Err(FrameError::TooLarge {
len: MAVLINK_MAX_FRAME + 1,
max: MAVLINK_MAX_FRAME
})
);
}
#[test]
fn oversized_length_is_rejected_on_decode() {
let header = (PLUGIN_MAX_FRAME as u32 + 1).to_be_bytes();
assert!(matches!(
decode_len(header, PLUGIN_MAX_FRAME, true),
Err(FrameError::TooLarge { .. })
));
}
}