use core::fmt;
use super::DecodeError;
use super::decode::write_label;
use crate::enums::Segmentation;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct EncodeError {
pub context: &'static str,
pub label: &'static str,
pub values: [u32; 2],
pub decode_offset: Option<u32>,
}
fn saturate(value: u64) -> u32 {
u32::try_from(value).unwrap_or(u32::MAX)
}
impl EncodeError {
pub const fn new(label: &'static str) -> Self {
Self {
context: "",
label,
values: [0; 2],
decode_offset: None,
}
}
pub fn from_decode(refused: &DecodeError) -> Self {
Self::restating(refused).with_offset(refused.offset)
}
pub(super) fn restating(refused: &DecodeError) -> Self {
Self {
context: refused.context,
label: refused.label,
values: [saturate(refused.values[0]), saturate(refused.values[1])],
decode_offset: None,
}
}
const fn with_offset(mut self, offset: usize) -> Self {
self.decode_offset = Some(if offset > u32::MAX as usize {
u32::MAX
} else {
offset as u32
});
self
}
#[must_use]
pub const fn within(mut self, context: &'static str) -> Self {
self.context = context;
self
}
#[must_use]
pub const fn with_value(mut self, value: u32) -> Self {
self.values = [value, 0];
self
}
#[must_use]
pub const fn with_values(mut self, first: u32, second: u32) -> Self {
self.values = [first, second];
self
}
pub fn message(&self) -> impl fmt::Display + '_ {
EncodeText(self)
}
}
impl fmt::Display for EncodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("encoding error: ")?;
fmt::Display::fmt(&self.message(), f)
}
}
impl core::error::Error for EncodeError {}
struct EncodeText<'a>(&'a EncodeError);
impl fmt::Display for EncodeText<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let error = self.0;
if let Some(offset) = error.decode_offset {
write!(f, "decoding error at offset {offset}: ")?;
}
if !error.context.is_empty() {
f.write_str(error.context)?;
f.write_str(": ")?;
}
let [first, second] = error.values;
write_label(f, error.label, [first.into(), second.into()])
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum SegmentationError {
ZeroSegmentSize,
TooManySegments {
segments: usize,
},
SegmentTooLarge {
size: usize,
max: usize,
},
TotalTooLarge {
total: usize,
},
MissingSegment {
index: usize,
total: usize,
},
PeerSegmentLimit {
segments: usize,
max: u32,
},
PeerCannotReceive {
capability: Segmentation,
},
PayloadCapacityExceeded,
PayloadAccountingOverflow,
NegativeAckRetriesExhausted,
EndpointAckTooLong,
EndpointRequestTooLong,
}
impl fmt::Display for SegmentationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::ZeroSegmentSize => {
f.write_str("payload cannot be segmented with max segment size 0")
}
Self::TooManySegments { segments } => {
write!(f, "payload requires {segments} segments, maximum is 256")
}
Self::SegmentTooLarge { size, max } => {
write!(f, "segment size {size} exceeds maximum {max}")
}
Self::TotalTooLarge { total } => write!(
f,
"total_segments {total} exceeds maximum BACnet value (256)"
),
Self::MissingSegment { index, total } => {
write!(f, "missing segment {index} of {total}")
}
Self::PeerSegmentLimit { segments, max } => write!(
f,
"request requires {segments} segments but remote accepts at most {max}"
),
Self::PeerCannotReceive { capability } => write!(
f,
"request requires segmentation but the peer advertised {capability:?} \
and cannot receive segmented requests"
),
Self::PayloadCapacityExceeded => f.write_str("request payload capacity exceeded"),
Self::PayloadAccountingOverflow => f.write_str("request payload accounting overflow"),
Self::NegativeAckRetriesExhausted => {
f.write_str("too many negative SegmentAck retransmissions")
}
Self::EndpointAckTooLong => {
f.write_str("endpoint operation ACK exceeds configured max APDU")
}
Self::EndpointRequestTooLong => {
f.write_str("endpoint requester supports only unsegmented requests")
}
}
}
}
impl core::error::Error for SegmentationError {}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::{DecodingKind, Error};
#[test]
fn encode_errors_display_their_label() {
assert_eq!(
Error::from(EncodeError::new("{} cannot be ALL").with_value(8)).to_string(),
"encoding error: 8 cannot be ALL"
);
let decoded = DecodeError::new(DecodingKind::InvalidTag, 3, "expected tag [{}]")
.within("opaque value")
.with_value(2);
let refused = EncodeError::from_decode(&decoded);
assert_eq!(
refused.to_string(),
"encoding error: decoding error at offset 3: opaque value: expected tag [2]"
);
assert_eq!(refused.to_string(), format!("encoding error: {decoded}"));
}
#[test]
fn segmentation_errors_keep_their_numbers() {
let refused = Error::Segmentation(SegmentationError::MissingSegment { index: 2, total: 5 });
assert_eq!(
refused.to_string(),
"segmentation error: missing segment 2 of 5"
);
assert_eq!(
SegmentationError::PeerCannotReceive {
capability: Segmentation::TRANSMIT
}
.to_string(),
"request requires segmentation but the peer advertised Segmentation::TRANSMIT \
and cannot receive segmented requests"
);
}
}