use super::crc_dvb_s2::CrcDvbS2;
#[derive(Clone, Copy, Default, Debug, PartialEq)]
pub enum MspVersion {
#[default]
V1,
V2overV1,
V2,
}
#[derive(Clone, Copy, Default, Debug, PartialEq)]
pub enum MspPacketType {
#[default]
Command,
Reply,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct MspStream {
packet_state: MspPacketState,
packet_type: MspPacketType,
msp_version: MspVersion,
pub cmd_msp: u16,
pub offset: u16,
pub data_size: u16,
pub cmd_flags: u8,
pub checksum1: u8,
pub checksum2: u8,
}
#[allow(unused)]
impl MspStream {
const JUMBO_FRAME_SIZE_LIMIT: usize = 255;
const MSP_EVALUATE_NON_MSP_DATA: u8 = 0;
const MSP_SKIP_NON_MSP_DATA: u8 = 1;
const MSP_HEADER_LENGTH: usize = 3;
const MSP_MAX_HEADER_SIZE: usize = 9;
}
impl MspStream {
pub const fn new() -> Self {
Self {
packet_state: MspPacketState::Idle,
packet_type: MspPacketType::Command,
msp_version: MspVersion::V1,
cmd_msp: 0,
offset: 0,
data_size: 0,
cmd_flags: 0,
checksum1: 0,
checksum2: 0,
}
}
}
impl Default for MspStream {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Default, Debug, PartialEq)]
pub enum MspPacketState {
#[default]
Idle,
HeaderM,
HeaderX,
HeaderV1 {
offset: usize,
checksum: u8,
},
PayloadV1 {
len: usize,
cmd: u8,
offset: usize,
checksum: u8,
},
HeaderV2 {
version: MspVersion,
offset: usize,
checksum1: u8, checksum2: u8, },
PayloadV2 {
version: MspVersion,
len: usize,
cmd: u16,
flags: u8,
offset: usize,
checksum1: u8,
checksum2: u8,
},
ChecksumV1 {
checksum: u8,
},
ChecksumV2 {
version: MspVersion,
checksum1: u8,
checksum2: u8,
},
CommandReceived,
}
impl MspStream {
#[allow(clippy::too_many_lines)]
#[allow(unused)]
pub fn process_received_packet_data(&mut self, buf: &mut [u8], c: u8) {
self.packet_state = match core::mem::take(&mut self.packet_state) {
MspPacketState::Idle | MspPacketState::CommandReceived => match c {
b'M' => MspPacketState::HeaderM,
b'X' => MspPacketState::HeaderX,
_ => MspPacketState::Idle,
},
MspPacketState::HeaderM => match c {
b'<' | b'>' => {
self.packet_type = if c == b'<' { MspPacketType::Command } else { MspPacketType::Reply };
MspPacketState::HeaderV1 { offset: 0, checksum: 0 }
}
_ => MspPacketState::Idle,
},
MspPacketState::HeaderX => match c {
b'<' | b'>' => {
self.packet_type = if c == b'<' { MspPacketType::Command } else { MspPacketType::Reply };
MspPacketState::HeaderV2 { version: MspVersion::V2, offset: 0, checksum1: 0, checksum2: 0 }
}
_ => MspPacketState::Idle,
},
MspPacketState::HeaderV1 { mut offset, mut checksum } => {
buf[offset] = c;
checksum ^= c;
offset += 1;
if offset == 2 {
let size = buf[0] as usize;
let cmd = buf[1];
if size > buf.len() {
MspPacketState::Idle
} else if cmd == 255 {
MspPacketState::HeaderV2 {
version: MspVersion::V2overV1,
offset: 2,
checksum1: checksum,
checksum2: 0,
}
} else if size > 0 {
MspPacketState::PayloadV1 { len: size, cmd, offset: 0, checksum }
} else {
MspPacketState::ChecksumV1 { checksum }
}
} else {
MspPacketState::HeaderV1 { offset, checksum }
}
}
MspPacketState::PayloadV1 { len, cmd, mut offset, mut checksum } => {
buf[offset] = c;
checksum ^= c;
offset += 1;
if offset == len {
self.cmd_msp = u16::from(cmd);
MspPacketState::ChecksumV1 { checksum }
} else {
MspPacketState::PayloadV1 { len, cmd, offset, checksum }
}
}
MspPacketState::HeaderV2 { version, mut offset, mut checksum1, mut checksum2 } => {
buf[offset] = c;
if version == MspVersion::V2overV1 {
checksum1 ^= c;
}
checksum2 = CrcDvbS2::update(checksum2, c);
offset += 1;
let start_index = if version == MspVersion::V2overV1 { 2 } else { 0 };
let header_end = start_index + 5;
if offset == header_end {
if start_index + 4 > buf.len() {
MspPacketState::Idle
} else {
let flags = buf[start_index];
let cmd = u16::from_le_bytes([buf[start_index + 1], buf[start_index + 2]]);
let size = u16::from_le_bytes([buf[start_index + 3], buf[start_index + 4]]) as usize;
if size > buf.len() {
MspPacketState::Idle
} else {
MspPacketState::PayloadV2 {
version,
len: size,
cmd,
flags,
offset: 0, checksum1,
checksum2,
}
}
}
} else {
MspPacketState::HeaderV2 { version, offset, checksum1, checksum2 }
}
}
MspPacketState::PayloadV2 { version, len, cmd, flags, mut offset, mut checksum1, mut checksum2 } => {
buf[offset] = c;
if version == MspVersion::V2overV1 {
checksum1 ^= c;
}
checksum2 = CrcDvbS2::update(checksum2, c);
offset += 1;
if offset == len {
self.cmd_msp = cmd;
self.cmd_flags = flags;
MspPacketState::ChecksumV2 { version, checksum1, checksum2 }
} else {
MspPacketState::PayloadV2 { version, len, cmd, flags, offset, checksum1, checksum2 }
}
}
MspPacketState::ChecksumV1 { checksum } => {
self.checksum1 = checksum;
if checksum == c { MspPacketState::CommandReceived } else { MspPacketState::Idle }
}
MspPacketState::ChecksumV2 { version, mut checksum1, checksum2 } => {
self.checksum2 = checksum2;
if version == MspVersion::V2overV1 {
if checksum2 == c {
checksum1 ^= c;
MspPacketState::ChecksumV1 { checksum: checksum1 }
} else {
MspPacketState::Idle
}
} else {
if checksum2 == c { MspPacketState::CommandReceived } else { MspPacketState::Idle }
}
}
}
}
#[allow(unused)]
pub fn serialize_packet(
version: MspVersion,
packet_type: MspPacketType,
cmd: u16,
flags: u8,
payload: &[u8],
dst: &mut [u8], ) -> Result<usize, MspError> {
let mut offset = 0;
let push = |b: u8, dst: &mut [u8], offset: &mut usize| -> Result<(), MspError> {
if *offset >= dst.len() {
return Err(MspError::BufferTooSmall);
}
dst[*offset] = b;
*offset += 1;
Ok(())
};
match version {
MspVersion::V1 => {
push(b'$', dst, &mut offset)?;
push(b'M', dst, &mut offset)?;
push(if packet_type == MspPacketType::Command { b'<' } else { b'>' }, dst, &mut offset)?;
#[allow(clippy::cast_possible_truncation)]
let size = payload.len() as u8;
push(size, dst, &mut offset)?;
#[allow(clippy::cast_possible_truncation)]
let cmd_u8 = cmd as u8;
push(cmd_u8, dst, &mut offset)?;
let mut checksum = size ^ cmd_u8;
for &byte in payload {
push(byte, dst, &mut offset)?;
checksum ^= byte;
}
push(checksum, dst, &mut offset)?;
}
MspVersion::V2 => {
push(b'$', dst, &mut offset)?;
push(b'X', dst, &mut offset)?;
push(if packet_type == MspPacketType::Command { b'<' } else { b'>' }, dst, &mut offset)?;
let mut crc = 0u8;
#[allow(clippy::cast_possible_truncation)]
let size = payload.len() as u16;
let push_v2 = |b: u8, dst: &mut [u8], offset: &mut usize, crc: &mut u8| -> Result<(), MspError> {
push(b, dst, offset)?;
*crc = CrcDvbS2::update(*crc, b);
Ok(())
};
push_v2(flags, dst, &mut offset, &mut crc)?;
let cmd_bytes = cmd.to_be_bytes();
push_v2(cmd_bytes[0], dst, &mut offset, &mut crc)?;
push_v2(cmd_bytes[1], dst, &mut offset, &mut crc)?;
let size_bytes = size.to_be_bytes();
push_v2(size_bytes[0], dst, &mut offset, &mut crc)?;
push_v2(size_bytes[1], dst, &mut offset, &mut crc)?;
for &byte in payload {
push_v2(byte, dst, &mut offset, &mut crc)?;
}
push(crc, dst, &mut offset)?;
}
MspVersion::V2overV1 => {
push(b'$', dst, &mut offset)?;
push(b'M', dst, &mut offset)?;
push(if packet_type == MspPacketType::Command { b'<' } else { b'>' }, dst, &mut offset)?;
let v2_payload_len = 5 + payload.len() + 1; #[allow(clippy::cast_possible_truncation)]
let v1_size = v2_payload_len as u8;
let v1_cmd = 255u8;
let mut xor = v1_size ^ v1_cmd;
push(v1_size, dst, &mut offset)?;
push(v1_cmd, dst, &mut offset)?;
let mut crc = 0u8;
#[allow(clippy::cast_possible_truncation)]
let size_v2 = payload.len() as u16;
let push_v2_over_v1 =
|b: u8, dst: &mut [u8], off: &mut usize, c: &mut u8, x: &mut u8| -> Result<(), MspError> {
push(b, dst, off)?;
*c = CrcDvbS2::update(*c, b);
*x ^= b;
Ok(())
};
push_v2_over_v1(flags, dst, &mut offset, &mut crc, &mut xor)?;
push_v2_over_v1((cmd & 0xFF) as u8, dst, &mut offset, &mut crc, &mut xor)?;
push_v2_over_v1((cmd >> 8) as u8, dst, &mut offset, &mut crc, &mut xor)?;
push_v2_over_v1((size_v2 & 0xFF) as u8, dst, &mut offset, &mut crc, &mut xor)?;
push_v2_over_v1((size_v2 >> 8) as u8, dst, &mut offset, &mut crc, &mut xor)?;
for &byte in payload {
push_v2_over_v1(byte, dst, &mut offset, &mut crc, &mut xor)?;
}
push(crc, dst, &mut offset)?;
xor ^= crc;
push(xor, dst, &mut offset)?;
}
}
Ok(offset) }
}
#[derive(Clone, Copy, Default, Debug, PartialEq)]
pub enum MspError {
#[default]
BufferTooSmall,
}
#[cfg(test)]
mod test_traits {
use super::*;
fn _is_normal<T: Sized + Send + Sync + Unpin>() {}
fn is_full<T: Sized + Send + Sync + Unpin + Copy + Clone + Default + PartialEq>() {}
#[test]
fn normal_types() {
is_full::<MspVersion>();
is_full::<MspPacketType>();
is_full::<MspStream>();
is_full::<MspPacketState>();
is_full::<MspError>();
}
}
#[allow(clippy::expect_used)]
#[cfg(test)]
mod tests {
use super::*;
const INBUF_SIZE: usize = 192;
#[test]
fn test_msp_v1_parsing() {
let mut buf = [0u8; INBUF_SIZE];
let mut stream = MspStream::new();
let packet: [u8; 7] = [
b'M', b'<', 2, 100, 1, 2, 101, ];
for &byte in &packet {
stream.process_received_packet_data(&mut buf, byte);
}
assert!(matches!(stream.packet_state, MspPacketState::CommandReceived));
assert_eq!(stream.cmd_msp, 100);
assert_eq!(&buf[0..2], &[1, 2]);
}
#[test]
fn test_msp_v2_native_parsing() {
let mut buf = [0u8; INBUF_SIZE];
let mut stream = MspStream::new();
let packet: [u8; 10] = [
b'X', b'<', 0, 2, 1, 2, 0, 0xAA, 0xBB, 19, ];
for &byte in &packet {
stream.process_received_packet_data(&mut buf, byte);
}
assert_eq!(MspPacketState::CommandReceived, stream.packet_state);
assert_eq!(19, stream.checksum2);
assert_eq!(stream.cmd_msp, 0x0102);
assert_eq!(stream.cmd_flags, 0);
assert_eq!(&buf[0..2], &[0xAA, 0xBB]);
}
#[test]
fn test_msp_v2_over_v1_parsing() {
let mut buf = [0u8; INBUF_SIZE];
let mut stream = MspStream::new();
let packet: [u8; 13] = [
b'M', b'<', 8, 255, 0, 2, 1, 2, 0, 0xAA, 0xBB, 19, 244, ];
for (i, &byte) in packet.iter().enumerate() {
stream.process_received_packet_data(&mut buf, byte);
if i < packet.len() - 1 {
assert!(
!matches!(stream.packet_state, MspPacketState::Idle),
"State machine reset to Idle at byte index {i}",
);
}
}
assert_eq!(stream.packet_state, MspPacketState::CommandReceived);
assert_eq!(244, stream.checksum1);
assert_eq!(19, stream.checksum2);
assert_eq!(stream.cmd_msp, 0x0102);
assert_eq!(stream.cmd_flags, 0);
assert_eq!(&buf[0..2], &[0xAA, 0xBB]);
}
#[test]
fn test_serialize_msp_v1_request() {
let mut buf = [0u8; 32];
let size = MspStream::serialize_packet(
MspVersion::V1,
MspPacketType::Command,
100,
0,
&[], &mut buf,
)
.expect("Serialization failed");
let expected = [b'$', b'M', b'<', 0, 100, 100];
assert_eq!(&buf[..size], &expected);
}
#[test]
fn test_serialize_msp_v1_with_payload() {
let mut buf = [0u8; 32];
let payload = [1, 2];
let size = MspStream::serialize_packet(MspVersion::V1, MspPacketType::Command, 100, 0, &payload, &mut buf)
.expect("Serialization failed");
let expected = [b'$', b'M', b'<', 2, 100, 1, 2, 101];
assert_eq!(&buf[..size], &expected);
}
#[test]
fn test_serialize_v2_over_v1() {
let mut buf = [0u8; 64];
let payload = [0xAA, 0xBB];
let size = MspStream::serialize_packet(
MspVersion::V2overV1,
MspPacketType::Command,
0x0102, 0, &payload,
&mut buf,
)
.expect("Serialization failed");
let expected = [
b'$', b'M', b'<', 8, 255, 0, 2, 1, 2, 0, 0xAA, 0xBB, 19, 244, ];
assert_eq!(size, 14); assert_eq!(&buf[..size], &expected);
}
}