use serde::Serialize;
use std::convert::TryFrom;
use crate::{
checks::internet::profinet::{
extract_name_of_station, validate_dcp_block, validate_frame_id, validate_packet_length,
},
errors::internet::profinet::ProfinetPacketError,
};
#[repr(u16)]
#[derive(Debug, Serialize, Clone, Eq, PartialEq, Hash, Default)]
pub enum FrameId {
#[default]
Unicast = 0xC000,
Multicast = 0xF800,
GetReqSetReqGetRespSetResp = 0xFEFD,
IdentifyReq = 0xFEFE,
IdentifyResp = 0xFEFF,
}
impl FrameId {
pub(crate) fn from_u16(value: u16) -> Option<FrameId> {
match value {
0xC000..=0xF7FF => Some(FrameId::Unicast),
0xF800..=0xFBFF => Some(FrameId::Multicast),
0xFEFD => Some(FrameId::GetReqSetReqGetRespSetResp),
0xFEFE => Some(FrameId::IdentifyReq),
0xFEFF => Some(FrameId::IdentifyResp),
_ => None,
}
}
}
#[cfg_attr(doc, aquamarine::aquamarine)]
#[derive(Debug, Default, Serialize, Clone, Eq, PartialEq, Hash)]
pub struct ProfinetPacket<'a> {
pub frame_id: FrameId,
pub service_id: u8,
pub service_type: u8,
pub xid: u32,
pub response_delay: u16,
pub dcp_data_length: u16,
pub option: u8,
pub suboption: u8,
pub dcp_block_length: u16,
pub name_of_station: &'a str,
}
impl<'a> TryFrom<&'a [u8]> for ProfinetPacket<'a> {
type Error = ProfinetPacketError;
fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
validate_packet_length(data)?;
let frame_id = validate_frame_id(data)?;
validate_dcp_block(data)?;
let service_id = data[2];
let service_type = data[3];
let xid = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
let response_delay = u16::from_be_bytes([data[8], data[9]]);
let dcp_data_length = u16::from_be_bytes([data[10], data[11]]);
let option = data[12];
let suboption = data[13];
let dcp_block_length = u16::from_be_bytes([data[14], data[15]]);
let name_of_station = extract_name_of_station(data)?;
Ok(ProfinetPacket {
frame_id,
service_id,
service_type,
xid,
response_delay,
dcp_data_length,
option,
suboption,
dcp_block_length,
name_of_station,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frame_id_from_u16() {
assert_eq!(FrameId::from_u16(0xC000), Some(FrameId::Unicast));
assert_eq!(FrameId::from_u16(0xF800), Some(FrameId::Multicast));
assert_eq!(
FrameId::from_u16(0xFEFD),
Some(FrameId::GetReqSetReqGetRespSetResp)
);
assert_eq!(FrameId::from_u16(0xFEFE), Some(FrameId::IdentifyReq));
assert_eq!(FrameId::from_u16(0xFEFF), Some(FrameId::IdentifyResp));
assert_eq!(FrameId::from_u16(0x0000), None);
}
}
#[cfg(test)]
mod integration_tests {
use super::*;
use pnet::packet::MutablePacket;
use pnet::packet::ethernet::MutableEthernetPacket;
use pnet::packet::Packet;
#[test]
fn test_handle_profinet_packet() {
let mut ethernet_data = vec![0u8; 64];
let mut eth_packet = MutableEthernetPacket::new(&mut ethernet_data).unwrap();
eth_packet.set_ethertype(pnet::packet::ethernet::EtherType(0x8892));
let profinet_payload = [
0xFE, 0xFE, 0x01, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x0C, 0x02, 0x03, 0x00, 0x04, b'T', b'E', b'S', b'T', ];
eth_packet.payload_mut()[..profinet_payload.len()].copy_from_slice(&profinet_payload);
let payload = eth_packet.payload();
match ProfinetPacket::try_from(payload) {
Ok(packet) => {
assert_eq!(packet.frame_id, FrameId::IdentifyReq);
assert_eq!(packet.service_id, 0x01);
assert_eq!(packet.service_type, 0x02);
assert_eq!(packet.xid, 0x00000001);
assert_eq!(packet.response_delay, 0x0010);
assert_eq!(packet.dcp_data_length, 0x000C);
assert_eq!(packet.option, 0x02);
assert_eq!(packet.suboption, 0x03);
assert_eq!(packet.dcp_block_length, 0x0004);
assert_eq!(packet.name_of_station, "TEST");
}
Err(e) => panic!("Failed to parse Profinet packet: {:?}", e),
}
}
}