use std::convert::TryFrom;
use crate::{
checks::application::ams::{
AMS_HEADER_LEN, validate_ams_header_length, validate_cb_data_length, validate_cmd_id,
validate_state_flags,
},
errors::application::ams::AmsParseError,
};
#[cfg_attr(all(doc, feature = "doc-diagrams"), aquamarine::aquamarine)]
#[derive(Debug)]
pub struct AmsPacket<'a> {
pub ams_target_net_id: [u8; 6], pub ams_target_port: u16,
pub ams_sender_net_id: [u8; 6],
pub ams_sender_port: u16,
pub cmd_id: u16,
pub state_flags: u16,
pub cb_data: u32,
pub error_code: u32,
pub invoke_id: u32,
pub data: &'a [u8],
}
impl<'a> TryFrom<&'a [u8]> for AmsPacket<'a> {
type Error = AmsParseError;
fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
let len = bytes.len();
validate_ams_header_length(len)?;
let ams_target_net_id: [u8; 6] = bytes[0..6].try_into().unwrap();
let ams_target_port = u16::from_le_bytes(bytes[6..8].try_into().unwrap());
let ams_sender_net_id: [u8; 6] = bytes[8..14].try_into().unwrap();
let ams_sender_port = u16::from_le_bytes(bytes[14..16].try_into().unwrap());
let cmd_id = u16::from_le_bytes(bytes[16..18].try_into().unwrap());
let state_flags = u16::from_le_bytes(bytes[18..20].try_into().unwrap());
let cb_data = u32::from_le_bytes(bytes[20..24].try_into().unwrap());
let error_code = u32::from_le_bytes(bytes[24..28].try_into().unwrap());
let invoke_id = u32::from_le_bytes(bytes[28..32].try_into().unwrap());
let data_start = AMS_HEADER_LEN;
let actual_data_len = len - data_start;
validate_cb_data_length(cb_data, actual_data_len)?;
validate_cmd_id(cmd_id)?;
validate_state_flags(state_flags)?;
let data = &bytes[data_start..data_start + actual_data_len];
Ok(AmsPacket {
ams_target_net_id,
ams_target_port,
ams_sender_net_id,
ams_sender_port,
cmd_id,
state_flags,
cb_data,
error_code,
invoke_id,
data,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build_ams(cmd_id: u16, state_flags: u16, cb_data: u32, data: &[u8]) -> Vec<u8> {
let mut bytes = Vec::with_capacity(32 + data.len());
bytes.extend_from_slice(&[0, 0, 0, 0, 0, 130]); bytes.extend_from_slice(&851u16.to_le_bytes()); bytes.extend_from_slice(&[192, 168, 1, 1, 1, 1]); bytes.extend_from_slice(&33000u16.to_le_bytes()); bytes.extend_from_slice(&cmd_id.to_le_bytes());
bytes.extend_from_slice(&state_flags.to_le_bytes());
bytes.extend_from_slice(&cb_data.to_le_bytes());
bytes.extend_from_slice(&0u32.to_le_bytes()); bytes.extend_from_slice(&7u32.to_le_bytes()); bytes.extend_from_slice(data);
bytes
}
#[test]
fn test_parse_valid_ams_packet() {
let data = [0xDE, 0xAD, 0xBE, 0xEF];
let bytes = build_ams(0x0002, 0x0004, 4, &data);
let ams = AmsPacket::try_from(bytes.as_slice()).expect("paquet AMS valide");
assert_eq!(ams.ams_target_net_id, [0, 0, 0, 0, 0, 130]);
assert_eq!(ams.ams_target_port, 851);
assert_eq!(ams.ams_sender_net_id, [192, 168, 1, 1, 1, 1]);
assert_eq!(ams.ams_sender_port, 33000);
assert_eq!(ams.cmd_id, 2);
assert_eq!(ams.state_flags, 4);
assert_eq!(ams.cb_data, 4);
assert_eq!(ams.error_code, 0);
assert_eq!(ams.invoke_id, 7);
assert_eq!(ams.data, &data);
}
#[test]
fn test_parse_valid_ams_packet_without_data() {
let bytes = build_ams(0x0001, 0x0000, 0, &[]);
let ams = AmsPacket::try_from(bytes.as_slice()).expect("paquet AMS sans data");
assert!(ams.data.is_empty());
}
#[test]
fn test_header_too_short() {
let bytes = [0u8; 31];
assert!(matches!(
AmsPacket::try_from(&bytes[..]),
Err(AmsParseError::HeaderTooShort {
expected: 32,
actual: 31
})
));
}
#[test]
fn test_cb_data_mismatch() {
let bytes = build_ams(0x0001, 0x0004, 10, &[1, 2, 3, 4]);
assert!(matches!(
AmsPacket::try_from(bytes.as_slice()),
Err(AmsParseError::InvalidCbDataLength {
cb_data: 10,
actual: 4
})
));
}
#[test]
fn test_unknown_command() {
let bytes = build_ams(0x0000, 0x0004, 0, &[]);
assert!(matches!(
AmsPacket::try_from(bytes.as_slice()),
Err(AmsParseError::UnknownCommand(0))
));
let bytes = build_ams(0x000A, 0x0004, 0, &[]);
assert!(matches!(
AmsPacket::try_from(bytes.as_slice()),
Err(AmsParseError::UnknownCommand(0x000A))
));
}
#[test]
fn test_invalid_state_flags() {
let bytes = build_ams(0x0001, 0x0010, 0, &[]);
assert!(matches!(
AmsPacket::try_from(bytes.as_slice()),
Err(AmsParseError::InvalidStateFlags(0x0010))
));
}
}