use alloc::vec;
use alloc::vec::Vec;
use crate::apdu::Apdu;
use crate::message::TpduType;
use crate::types::AddressType;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Tpdu {
Data {
tpdu_type: TpduType,
sequence_number: u8,
apdu: Apdu,
},
Control {
tpdu_type: TpduType,
sequence_number: u8,
},
}
impl Tpdu {
pub fn parse(
payload: &[u8],
npdu_length: u8,
address_type: AddressType,
destination_raw: u16,
) -> Option<Self> {
if payload.is_empty() {
return None;
}
let tpci = payload[0];
let is_control = tpci & 0x80 != 0;
let is_numbered = tpci & 0x40 != 0;
let sequence_number = (tpci >> 2) & 0x0F;
if is_control {
let tpdu_type = if is_numbered {
if tpci & 0x01 == 0 {
TpduType::Ack
} else {
TpduType::Nack
}
} else if tpci & 0x01 == 0 {
TpduType::Connect
} else {
TpduType::Disconnect
};
Some(Self::Control {
tpdu_type,
sequence_number,
})
} else {
let tpdu_type = if address_type == AddressType::Group {
if destination_raw == 0 {
TpduType::DataBroadcast
} else {
TpduType::DataGroup
}
} else if is_numbered {
TpduType::DataConnected
} else {
TpduType::DataIndividual
};
let apdu = Apdu::parse(payload, npdu_length)?;
Some(Self::Data {
tpdu_type,
sequence_number,
apdu,
})
}
}
pub const fn tpdu_type(&self) -> TpduType {
match self {
Self::Data { tpdu_type, .. } | Self::Control { tpdu_type, .. } => *tpdu_type,
}
}
pub const fn sequence_number(&self) -> u8 {
match self {
Self::Data {
sequence_number, ..
}
| Self::Control {
sequence_number, ..
} => *sequence_number,
}
}
pub const fn apdu(&self) -> Option<&Apdu> {
match self {
Self::Data { apdu, .. } => Some(apdu),
Self::Control { .. } => None,
}
}
}
pub fn encode_control(tpdu_type: TpduType, seq_no: u8) -> Vec<u8> {
let tpci = match tpdu_type {
TpduType::Connect => 0x80, TpduType::Disconnect => 0x81, TpduType::Ack => 0xC0 | ((seq_no & 0x0F) << 2), TpduType::Nack => 0xC0 | ((seq_no & 0x0F) << 2) | 0x01, _ => return Vec::new(),
};
vec![tpci]
}
pub fn encode_data_connected(seq_no: u8, apdu: &[u8]) -> Vec<u8> {
let tpci = 0x40 | ((seq_no & 0x0F) << 2);
let mut buf = Vec::with_capacity(1 + apdu.len());
if apdu.is_empty() {
buf.push(tpci);
} else {
buf.push(tpci | (apdu[0] & 0x03)); buf.extend_from_slice(&apdu[1..]);
}
buf
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use crate::message::{ApduType, TpduType};
use crate::types::AddressType;
use super::Tpdu;
#[test]
fn parse_data_group() {
let payload = &[0x00, 0x81]; let tpdu = Tpdu::parse(payload, 1, AddressType::Group, 0x0801).unwrap();
assert_eq!(tpdu.tpdu_type(), TpduType::DataGroup);
assert_eq!(tpdu.sequence_number(), 0);
let apdu = tpdu.apdu().unwrap();
assert_eq!(apdu.apdu_type, ApduType::GroupValueWrite);
}
#[test]
fn parse_data_broadcast() {
let payload = &[0x00, 0x00]; let tpdu = Tpdu::parse(payload, 0, AddressType::Group, 0x0000).unwrap();
assert_eq!(tpdu.tpdu_type(), TpduType::DataBroadcast);
}
#[test]
fn parse_data_individual() {
let payload = &[0x03, 0xD5, 0x01, 0x02, 0x03]; let tpdu = Tpdu::parse(payload, 4, AddressType::Individual, 0x1101).unwrap();
assert_eq!(tpdu.tpdu_type(), TpduType::DataIndividual);
}
#[test]
fn parse_data_connected() {
let payload = &[0x48, 0x00]; let tpdu = Tpdu::parse(payload, 0, AddressType::Individual, 0x1101).unwrap();
assert_eq!(tpdu.tpdu_type(), TpduType::DataConnected);
assert_eq!(tpdu.sequence_number(), 2);
}
#[test]
fn parse_connect() {
let payload = &[0x80]; let tpdu = Tpdu::parse(payload, 0, AddressType::Individual, 0x1101).unwrap();
assert_eq!(tpdu.tpdu_type(), TpduType::Connect);
}
#[test]
fn parse_disconnect() {
let payload = &[0x81]; let tpdu = Tpdu::parse(payload, 0, AddressType::Individual, 0x1101).unwrap();
assert_eq!(tpdu.tpdu_type(), TpduType::Disconnect);
}
#[test]
fn parse_ack() {
let payload = &[0xC2]; let tpdu = Tpdu::parse(payload, 0, AddressType::Individual, 0x1101).unwrap();
assert_eq!(tpdu.tpdu_type(), TpduType::Ack);
}
#[test]
fn parse_nack() {
let payload = &[0xC3]; let tpdu = Tpdu::parse(payload, 0, AddressType::Individual, 0x1101).unwrap();
assert_eq!(tpdu.tpdu_type(), TpduType::Nack);
}
#[test]
fn parse_empty_payload() {
assert!(Tpdu::parse(&[], 0, AddressType::Group, 0).is_none());
}
}