use bacnet_types::enums::{
AbortReason, ConfirmedServiceChoice, PduType, RejectReason, UnconfirmedServiceChoice,
};
use bacnet_types::error::{DecodeError, DecodingKind, Error};
use crate::sink::{EncodeSink, SinkFull};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MaxSegments(u8);
impl MaxSegments {
pub const UNSPECIFIED: Self = Self(0);
pub const MORE_THAN_64: Self = Self(7);
pub const fn from_capacity(capacity: Option<u8>) -> Option<Self> {
Some(Self(match capacity {
None => 0,
Some(0..=1) => return None,
Some(2..=3) => 1,
Some(4..=7) => 2,
Some(8..=15) => 3,
Some(16..=31) => 4,
Some(32..=63) => 5,
Some(64) => 6,
Some(65..=u8::MAX) => 7,
}))
}
pub const fn from_bits(bits: u8) -> Self {
Self(bits & 0x07)
}
pub const fn bits(self) -> u8 {
self.0
}
pub const fn count(self) -> Option<u8> {
match self.0 {
0 => None,
7 => Some(255),
bits => Some(1 << bits),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MaxApdu(u8);
const MAX_APDU_OCTETS: [u16; 6] = [50, 128, 206, 480, 1024, 1476];
impl MaxApdu {
pub const OCTETS_50: Self = Self(0);
pub const OCTETS_128: Self = Self(1);
pub const OCTETS_206: Self = Self(2);
pub const OCTETS_480: Self = Self(3);
pub const OCTETS_1024: Self = Self(4);
pub const OCTETS_1476: Self = Self(5);
pub const fn from_octets(octets: u16) -> Option<Self> {
let mut code = 0;
while code < MAX_APDU_OCTETS.len() {
if MAX_APDU_OCTETS[code] == octets {
return Some(Self(code as u8));
}
code += 1;
}
None
}
pub const fn at_or_below(capacity: u32) -> Option<Self> {
let mut code = MAX_APDU_OCTETS.len();
while code > 0 {
code -= 1;
if MAX_APDU_OCTETS[code] as u32 <= capacity {
return Some(Self(code as u8));
}
}
None
}
pub const fn from_bits(bits: u8) -> Option<Self> {
let code = bits & 0x0F;
if (code as usize) < MAX_APDU_OCTETS.len() {
Some(Self(code))
} else {
None
}
}
pub const fn bits(self) -> u8 {
self.0
}
pub const fn octets(self) -> u16 {
MAX_APDU_OCTETS[self.0 as usize]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ConfirmedRequestHeader {
pub segmented_response_accepted: bool,
pub max_segments: MaxSegments,
pub max_apdu: MaxApdu,
pub invoke_id: u8,
pub service_choice: ConfirmedServiceChoice,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ApduHeader {
ConfirmedRequest(ConfirmedRequestHeader),
UnconfirmedRequest(UnconfirmedServiceChoice),
ComplexAck {
invoke_id: u8,
service_choice: ConfirmedServiceChoice,
},
Error {
invoke_id: u8,
service_choice: ConfirmedServiceChoice,
},
Reject {
invoke_id: u8,
reason: RejectReason,
},
Abort {
server: bool,
invoke_id: u8,
reason: AbortReason,
},
}
const MAX_HEADER: usize = 4;
impl ApduHeader {
pub fn decode(apdu: &[u8]) -> Result<(Self, &[u8]), Error> {
let Some(&first) = apdu.first() else {
return Err(Error::decoding(0, "APDU data is empty"));
};
let pdu_type = PduType::from_raw(first >> 4);
let segmented = first & 0x08 != 0;
if pdu_type == PduType::CONFIRMED_REQUEST {
let [_, fields, invoke_id, service, ..] = *apdu else {
return Err(Error::buffer_too_short(4, apdu.len()));
};
let Some(max_apdu) = MaxApdu::from_bits(fields) else {
return Err(DecodeError::new(
DecodingKind::InvalidEncoding,
1,
"reserved max-APDU-length field value {}",
)
.with_value(u64::from(fields & 0x0F))
.into());
};
if segmented {
return Err(segmented_refusal("segmented ConfirmedRequest"));
}
let header = ConfirmedRequestHeader {
segmented_response_accepted: first & 0x02 != 0,
max_segments: MaxSegments::from_bits(fields >> 4),
max_apdu,
invoke_id,
service_choice: ConfirmedServiceChoice::from_raw(service),
};
Ok((Self::ConfirmedRequest(header), &apdu[4..]))
} else if pdu_type == PduType::UNCONFIRMED_REQUEST {
let [_, service, ..] = *apdu else {
return Err(Error::buffer_too_short(2, apdu.len()));
};
let service = UnconfirmedServiceChoice::from_raw(service);
Ok((Self::UnconfirmedRequest(service), &apdu[2..]))
} else if pdu_type == PduType::COMPLEX_ACK {
let [_, invoke_id, service, ..] = *apdu else {
return Err(Error::buffer_too_short(3, apdu.len()));
};
if segmented {
return Err(segmented_refusal("segmented ComplexAck"));
}
let service_choice = ConfirmedServiceChoice::from_raw(service);
Ok((
Self::ComplexAck {
invoke_id,
service_choice,
},
&apdu[3..],
))
} else if pdu_type == PduType::ERROR
|| pdu_type == PduType::REJECT
|| pdu_type == PduType::ABORT
{
let [_, invoke_id, third, ..] = *apdu else {
return Err(Error::buffer_too_short(3, apdu.len()));
};
let header = if pdu_type == PduType::ERROR {
Self::Error {
invoke_id,
service_choice: ConfirmedServiceChoice::from_raw(third),
}
} else if pdu_type == PduType::REJECT {
Self::Reject {
invoke_id,
reason: RejectReason::from_raw(third),
}
} else {
Self::Abort {
server: first & 0x01 != 0,
invoke_id,
reason: AbortReason::from_raw(third),
}
};
Ok((header, &apdu[3..]))
} else if first >> 4 <= 7 {
Err(DecodeError::new(
DecodingKind::Unsupported,
0,
"PDU type {} carries no service parameters this header reads",
)
.with_value(u64::from(first >> 4))
.into())
} else {
Err(DecodeError::new(
DecodingKind::InvalidEncoding,
0,
"unknown PDU type nibble: {:#x}",
)
.with_value(u64::from(first >> 4))
.into())
}
}
pub const fn encoded_len(&self) -> usize {
match self {
Self::ConfirmedRequest(_) => 4,
Self::UnconfirmedRequest(_) => 2,
Self::ComplexAck { .. }
| Self::Error { .. }
| Self::Reject { .. }
| Self::Abort { .. } => 3,
}
}
fn octets(&self) -> [u8; MAX_HEADER] {
match *self {
Self::ConfirmedRequest(header) => [
(PduType::CONFIRMED_REQUEST.to_raw() << 4)
| (u8::from(header.segmented_response_accepted) << 1),
(header.max_segments.bits() << 4) | header.max_apdu.bits(),
header.invoke_id,
header.service_choice.to_raw(),
],
Self::UnconfirmedRequest(service) => [
PduType::UNCONFIRMED_REQUEST.to_raw() << 4,
service.to_raw(),
0,
0,
],
Self::ComplexAck {
invoke_id,
service_choice,
} => [
PduType::COMPLEX_ACK.to_raw() << 4,
invoke_id,
service_choice.to_raw(),
0,
],
Self::Error {
invoke_id,
service_choice,
} => [
PduType::ERROR.to_raw() << 4,
invoke_id,
service_choice.to_raw(),
0,
],
Self::Reject { invoke_id, reason } => {
[PduType::REJECT.to_raw() << 4, invoke_id, reason.to_raw(), 0]
}
Self::Abort {
server,
invoke_id,
reason,
} => [
(PduType::ABORT.to_raw() << 4) | u8::from(server),
invoke_id,
reason.to_raw(),
0,
],
}
}
pub fn encode_to_sink<S: EncodeSink + ?Sized>(&self, sink: &mut S) -> Result<usize, SinkFull> {
self.encode_with_parameters_to_sink(sink, &[])
}
pub fn encode_with_parameters_to_sink<S: EncodeSink + ?Sized>(
&self,
sink: &mut S,
parameters: &[u8],
) -> Result<usize, SinkFull> {
let octets = self.octets();
let header = &octets[..self.encoded_len()];
sink.write_parts(&[header, parameters])?;
Ok(header.len() + parameters.len())
}
}
#[cold]
fn segmented_refusal(label: &'static str) -> Error {
DecodeError::new(DecodingKind::Unsupported, 0, label)
.within("APDU header")
.into()
}
#[cfg(test)]
mod tests;