use snafu::{OptionExt as _, ResultExt as _};
use tracing::instrument;
use crate::{
asdu::Asdu,
error::{self, Error, InvalidAsdu, NotEnoughBytes, SizedSlice},
};
pub(crate) const TELEGRAM_HEADER: u8 = 0x68;
pub(crate) const APUD_MAX_LENGTH: u8 = 253;
#[derive(Debug, Clone, PartialEq)]
pub struct Apdu {
pub length: u8,
pub frame: Frame,
}
impl Apdu {
#[instrument]
pub fn from_bytes(data: &[u8]) -> Result<Self, Error> {
if data.len() < 6 {
return error::ApduTooShort.fail();
}
if data[0] != TELEGRAM_HEADER {
return error::InvalidTelegramHeader.fail();
}
let length = data[1];
if length > APUD_MAX_LENGTH {
return error::InvalidLength.fail();
}
let control_fields = data[2..6].try_into().context(SizedSlice)?;
let frame = if length == 4_u8 {
Frame::from_control_fields(control_fields)
} else {
Frame::from_asdu(control_fields, data.get(6..).context(NotEnoughBytes)?)
}?;
Ok(Self { length, frame })
}
pub fn to_bytes(&self) -> Result<Vec<u8>, Error> {
let mut bytes = Vec::with_capacity(self.length as usize + 2);
bytes.push(TELEGRAM_HEADER);
bytes.push(self.length);
self.frame.to_bytes(&mut bytes)?;
Ok(bytes)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Frame {
I(IFrame),
S(SFrame),
U(UFrame),
}
impl Frame {
fn from_control_fields(control_fields: [u8; 4]) -> Result<Self, Error> {
match control_fields[0] & 0b0000_0011 {
0b0000_0011 => Ok(Frame::U(UFrame::from_control_fields(control_fields)?)),
0b0000_0001 => Ok(Frame::S(SFrame::from_control_fields(control_fields)?)),
_ => error::InvalidControlField.fail(),
}
}
fn from_asdu(control_fields: [u8; 4], asdu: &[u8]) -> Result<Self, Error> {
Ok(Frame::I(IFrame::from_asdu(control_fields, asdu)?))
}
pub fn to_bytes(&self, buffer: &mut Vec<u8>) -> Result<(), Error> {
match self {
Frame::I(i) => i.to_bytes(buffer),
Frame::S(s) => s.to_bytes(buffer),
Frame::U(u) => u.to_bytes(buffer),
}
}
pub fn to_apdu_bytes(&self) -> Result<Vec<u8>, Error> {
let mut buffer = Vec::new();
buffer.push(TELEGRAM_HEADER);
buffer.push(0); self.to_bytes(&mut buffer)?;
let payload_len = buffer.len() - 2;
if payload_len > APUD_MAX_LENGTH as usize {
return error::InvalidLength.fail();
}
buffer[1] = payload_len as u8;
Ok(buffer)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct IFrame {
pub send_sequence_number: u16,
pub receive_sequence_number: u16,
pub asdu: Asdu,
}
impl IFrame {
#[instrument]
fn from_asdu(control_fields: [u8; 4], asdu: &[u8]) -> Result<Self, Error> {
if (control_fields[0] & 0b0000_0001) != 0 || (control_fields[2] & 0b0000_0001) != 0 {
return error::InvalidIFrameControlFields.fail();
}
Ok(Self {
send_sequence_number: u16::from_le_bytes(
control_fields[0..2].try_into().context(SizedSlice)?,
) >> 1,
receive_sequence_number: u16::from_le_bytes(
control_fields[2..4].try_into().context(SizedSlice)?,
) >> 1,
asdu: Asdu::parse(asdu).context(InvalidAsdu)?,
})
}
fn to_bytes(&self, buffer: &mut Vec<u8>) -> Result<(), Error> {
let rsn = (self.receive_sequence_number << 1).to_le_bytes();
let ssn = (self.send_sequence_number << 1).to_le_bytes();
buffer.push(ssn[0]);
buffer.push(ssn[1]);
buffer.push(rsn[0]);
buffer.push(rsn[1]);
self.asdu.to_bytes(buffer).context(InvalidAsdu)?;
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct SFrame {
pub receive_sequence_number: u16,
}
impl SFrame {
#[instrument]
fn from_control_fields(control_fields: [u8; 4]) -> Result<Self, Error> {
if control_fields[0] != 0b0000_0001 || control_fields[1] != 0b0000_0000 {
return error::InvalidSFrameControlFields.fail();
}
Ok(Self {
receive_sequence_number: u16::from_le_bytes(
control_fields[2..4].try_into().context(SizedSlice)?,
) >> 1,
})
}
fn to_bytes(&self, buffer: &mut Vec<u8>) -> Result<(), Error> {
let rsn = (self.receive_sequence_number << 1).to_le_bytes();
buffer.push(0b0000_0001);
buffer.push(0b0000_0000);
buffer.push(rsn[0]);
buffer.push(rsn[1]);
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct UFrame {
pub start_dt_activation: bool,
pub start_dt_confirmation: bool,
pub stop_dt_activation: bool,
pub stop_dt_confirmation: bool,
pub test_fr_activation: bool,
pub test_fr_confirmation: bool,
}
impl UFrame {
#[instrument]
fn from_control_fields(control_fields: [u8; 4]) -> Result<Self, Error> {
if control_fields[1] != 0 || control_fields[2] != 0 || control_fields[3] != 0 {
return error::InvalidUFrameControlFields.fail();
}
Ok(Self {
start_dt_activation: control_fields[0] & 0b0000_0100 != 0,
start_dt_confirmation: control_fields[0] & 0b0000_1000 != 0,
stop_dt_activation: control_fields[0] & 0b0001_0000 != 0,
stop_dt_confirmation: control_fields[0] & 0b0010_0000 != 0,
test_fr_activation: control_fields[0] & 0b0100_0000 != 0,
test_fr_confirmation: control_fields[0] & 0b1000_0000 != 0,
})
}
fn to_bytes(&self, buffer: &mut Vec<u8>) -> Result<(), Error> {
let mut byte: u8 = 0b0000_0011;
if self.start_dt_activation {
byte |= 0b0000_0100;
}
if self.start_dt_confirmation {
byte |= 0b0000_1000;
}
if self.stop_dt_activation {
byte |= 0b0001_0000;
}
if self.stop_dt_confirmation {
byte |= 0b0010_0000;
}
if self.test_fr_activation {
byte |= 0b0100_0000;
}
if self.test_fr_confirmation {
byte |= 0b1000_0000;
}
buffer.push(byte);
buffer.push(0);
buffer.push(0);
buffer.push(0);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cot::Cot,
types::{
InformationObjects,
commands::{Frz, Rqt},
},
types_id::TypeId,
};
#[test]
fn test_s_frame() -> Result<(), Error> {
let bytes = [0x68, 0x04, 0x01, 0x00, 0x7E, 0x14];
let apdu = Apdu::from_bytes(&bytes)?;
assert_eq!(apdu.length, 4);
let Frame::S(s_frame) = &apdu.frame else { panic!("Frame was expected to be an S-frame") };
assert_eq!(s_frame.receive_sequence_number, 2623);
let apdu_bytes = apdu.to_bytes()?;
assert_eq!(apdu_bytes, bytes);
Ok(())
}
#[test]
fn test_i_frame() -> Result<(), Error> {
let bytes = [
0x68, 0x34, 0x5A, 0x14, 0x7C, 0x00, 0x0B, 0x07, 0x03, 0x00, 0x0C, 0x00, 0x10, 0x30,
0x00, 0xBE, 0x09, 0x00, 0x11, 0x30, 0x00, 0x90, 0x09, 0x00, 0x0E, 0x30, 0x00, 0x75,
0x00, 0x00, 0x28, 0x30, 0x00, 0x25, 0x09, 0x00, 0x29, 0x30, 0x00, 0x75, 0x00, 0x00,
0x0F, 0x30, 0x00, 0x0F, 0x0A, 0x00, 0x2E, 0x30, 0x00, 0xAE, 0x05, 0x00,
];
let apdu = Apdu::from_bytes(&bytes)?;
assert_eq!(apdu.length, 52);
let Frame::I(i_frame) = &apdu.frame else { panic!("Frame was expected to be an I-frame") };
assert_eq!(i_frame.send_sequence_number, 2605);
assert_eq!(i_frame.receive_sequence_number, 62);
assert_eq!(i_frame.asdu.type_id, TypeId::M_ME_NB_1);
assert_eq!(i_frame.asdu.information_objects.len(), 7);
assert_eq!(i_frame.asdu.cot, Cot::SpontaneousData);
assert_eq!(i_frame.asdu.originator_address, 0);
assert_eq!(i_frame.asdu.address_field, 12);
assert!(!i_frame.asdu.sequence);
assert!(!i_frame.asdu.test);
assert!(!i_frame.asdu.negative);
let InformationObjects::MMeNb1(objects) = &i_frame.asdu.information_objects else {
panic!("Information objects were expected to be a MMeNb1")
};
assert_eq!(objects.len(), 7);
assert_eq!(objects[0].address, 12304);
assert_eq!(objects[0].object.sva, 2494);
assert!(!objects[0].object.qds.iv);
assert!(!objects[0].object.qds.nt);
assert!(!objects[0].object.qds.sb);
assert!(!objects[0].object.qds.bl);
assert!(!objects[0].object.qds.ov);
assert_eq!(objects[1].address, 12305);
assert_eq!(objects[1].object.sva, 2448);
assert!(!objects[1].object.qds.iv);
assert!(!objects[1].object.qds.nt);
assert!(!objects[1].object.qds.sb);
assert!(!objects[1].object.qds.bl);
assert!(!objects[1].object.qds.ov);
assert_eq!(objects[2].address, 12302);
assert_eq!(objects[2].object.sva, 117);
assert!(!objects[2].object.qds.iv);
assert!(!objects[2].object.qds.nt);
assert!(!objects[2].object.qds.sb);
assert!(!objects[2].object.qds.bl);
assert!(!objects[2].object.qds.ov);
assert_eq!(objects[3].address, 12328);
assert_eq!(objects[3].object.sva, 2341);
assert!(!objects[3].object.qds.iv);
assert!(!objects[3].object.qds.nt);
assert!(!objects[3].object.qds.sb);
assert!(!objects[3].object.qds.bl);
assert!(!objects[3].object.qds.ov);
assert_eq!(objects[4].address, 12329);
assert_eq!(objects[4].object.sva, 117);
assert!(!objects[4].object.qds.iv);
assert!(!objects[4].object.qds.nt);
assert!(!objects[4].object.qds.sb);
assert!(!objects[4].object.qds.bl);
assert!(!objects[4].object.qds.ov);
assert_eq!(objects[5].address, 12303);
assert_eq!(objects[5].object.sva, 2575);
assert!(!objects[5].object.qds.iv);
assert!(!objects[5].object.qds.nt);
assert!(!objects[5].object.qds.sb);
assert!(!objects[5].object.qds.bl);
assert!(!objects[5].object.qds.ov);
assert_eq!(objects[6].address, 12334);
assert_eq!(objects[6].object.sva, 1454);
assert!(!objects[6].object.qds.iv);
assert!(!objects[6].object.qds.nt);
assert!(!objects[6].object.qds.sb);
assert!(!objects[6].object.qds.bl);
assert!(!objects[6].object.qds.ov);
let apdu_bytes = apdu.to_bytes()?;
assert_eq!(apdu_bytes, bytes);
Ok(())
}
#[test]
fn test_i_frame_command() -> Result<(), Error> {
let bytes = [
0x68, 0x0E, 0x4E, 0x14, 0x7C, 0x00, 0x65, 0x01, 0x0A, 0x00, 0x0C, 0x00, 0x00, 0x00,
0x00, 0x05,
];
let apdu = Apdu::from_bytes(&bytes)?;
assert_eq!(apdu.length, 14);
let Frame::I(i_frame) = &apdu.frame else { panic!("Frame was expected to be an I-frame") };
assert_eq!(i_frame.send_sequence_number, 2599);
assert_eq!(i_frame.receive_sequence_number, 62);
assert_eq!(i_frame.asdu.type_id, TypeId::C_CI_NA_1);
assert_eq!(i_frame.asdu.information_objects.len(), 1);
assert_eq!(i_frame.asdu.cot, Cot::ActivationTermination);
assert_eq!(i_frame.asdu.originator_address, 0);
assert_eq!(i_frame.asdu.address_field, 12);
assert!(!i_frame.asdu.sequence);
assert!(!i_frame.asdu.test);
assert!(!i_frame.asdu.negative);
let InformationObjects::CCiNa1(objects) = &i_frame.asdu.information_objects else {
panic!("Information objects were expected to be a CCiNa1")
};
assert_eq!(objects.len(), 1);
assert_eq!(objects[0].address, 0);
assert_eq!(objects[0].object.rqt, Rqt::ReqCoGen);
assert_eq!(objects[0].object.frz, Frz::Read);
let apdu_bytes = apdu.to_bytes()?;
assert_eq!(apdu_bytes, bytes);
Ok(())
}
#[test]
fn test_u_frame() -> Result<(), Error> {
let bytes = [0x68, 0x04, 0x01, 0x00, 0x7E, 0x14];
let apdu = Apdu::from_bytes(&bytes)?;
assert_eq!(apdu.length, 4);
Ok(())
}
#[test]
fn to_apdu_bytes_rejects_oversize_payload() {
use crate::{
asdu::Asdu,
types::{GenericObject, MMeNc1},
};
let objects: Vec<GenericObject<MMeNc1>> =
(0_u32..32).map(|i| GenericObject { address: i, object: MMeNc1::default() }).collect();
let asdu = Asdu {
type_id: TypeId::M_ME_NC_1,
cot: Cot::SpontaneousData,
originator_address: 0,
address_field: 1,
sequence: false,
test: false,
negative: false,
information_objects: InformationObjects::MMeNc1(objects),
};
let frame = Frame::I(IFrame { send_sequence_number: 0, receive_sequence_number: 0, asdu });
let err = frame.to_apdu_bytes().expect_err("must reject oversize APDU");
assert!(matches!(err, Error::InvalidLength { .. }), "got: {err:?}");
}
}