1use thiserror::Error;
16
17pub const HEADER_SIZE: usize = 4;
19
20pub const MAVLINK_MAX_FRAME: usize = 65536;
22
23pub const PLUGIN_MAX_FRAME: usize = 4 * 1024 * 1024;
25
26#[derive(Debug, Error, PartialEq, Eq)]
27pub enum FrameError {
28 #[error("frame length {len} exceeds max {max}")]
29 TooLarge { len: usize, max: usize },
30 #[error("frame length is zero")]
31 ZeroLength,
32 #[error("payload of {0} bytes does not fit in a u32 length prefix")]
33 PayloadTooBig(usize),
34}
35
36pub fn encode_frame(payload: &[u8], max: usize) -> Result<Vec<u8>, FrameError> {
40 let n = payload.len();
41 if n > max {
42 return Err(FrameError::TooLarge { len: n, max });
43 }
44 let len = u32::try_from(n).map_err(|_| FrameError::PayloadTooBig(n))?;
45 let mut out = Vec::with_capacity(HEADER_SIZE + n);
46 out.extend_from_slice(&len.to_be_bytes());
47 out.extend_from_slice(payload);
48 Ok(out)
49}
50
51pub fn decode_len(
56 header: [u8; HEADER_SIZE],
57 max: usize,
58 reject_zero: bool,
59) -> Result<usize, FrameError> {
60 let len = u32::from_be_bytes(header) as usize;
61 if reject_zero && len == 0 {
62 return Err(FrameError::ZeroLength);
63 }
64 if len > max {
65 return Err(FrameError::TooLarge { len, max });
66 }
67 Ok(len)
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn round_trip_header_matches_python_struct_pack() {
76 let payload = b"hello world";
78 let frame = encode_frame(payload, MAVLINK_MAX_FRAME).unwrap();
79 assert_eq!(&frame[..HEADER_SIZE], &[0, 0, 0, 11]);
80 assert_eq!(&frame[HEADER_SIZE..], payload);
81
82 let header: [u8; HEADER_SIZE] = frame[..HEADER_SIZE].try_into().unwrap();
83 let len = decode_len(header, MAVLINK_MAX_FRAME, false).unwrap();
84 assert_eq!(len, payload.len());
85 }
86
87 #[test]
88 fn empty_payload_encodes_zero_length() {
89 let frame = encode_frame(b"", MAVLINK_MAX_FRAME).unwrap();
90 assert_eq!(frame, vec![0, 0, 0, 0]);
91 }
92
93 #[test]
94 fn mavlink_allows_zero_length_plugin_rejects_it() {
95 let header = [0u8, 0, 0, 0];
96 assert_eq!(decode_len(header, MAVLINK_MAX_FRAME, false).unwrap(), 0);
98 assert_eq!(
100 decode_len(header, PLUGIN_MAX_FRAME, true),
101 Err(FrameError::ZeroLength)
102 );
103 }
104
105 #[test]
106 fn oversized_payload_is_rejected_on_encode() {
107 let payload = vec![0u8; MAVLINK_MAX_FRAME + 1];
108 assert_eq!(
109 encode_frame(&payload, MAVLINK_MAX_FRAME),
110 Err(FrameError::TooLarge {
111 len: MAVLINK_MAX_FRAME + 1,
112 max: MAVLINK_MAX_FRAME
113 })
114 );
115 }
116
117 #[test]
118 fn oversized_length_is_rejected_on_decode() {
119 let header = (PLUGIN_MAX_FRAME as u32 + 1).to_be_bytes();
120 assert!(matches!(
121 decode_len(header, PLUGIN_MAX_FRAME, true),
122 Err(FrameError::TooLarge { .. })
123 ));
124 }
125}