use core::fmt;
use crate::enums::RejectReason;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DecodingKind {
InvalidEncoding,
OutOfRange,
Overflow,
InvalidTag,
Missing,
Trailing,
Unsupported,
}
impl DecodingKind {
pub fn reject_reason(self) -> RejectReason {
match self {
Self::InvalidEncoding => RejectReason::INVALID_DATA_ENCODING,
Self::OutOfRange => RejectReason::PARAMETER_OUT_OF_RANGE,
Self::Overflow => RejectReason::BUFFER_OVERFLOW,
Self::InvalidTag => RejectReason::INVALID_TAG,
Self::Missing => RejectReason::MISSING_REQUIRED_PARAMETER,
Self::Trailing => RejectReason::TOO_MANY_ARGUMENTS,
Self::Unsupported => RejectReason::OTHER,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct DecodeError {
pub offset: usize,
pub kind: DecodingKind,
pub context: &'static str,
pub label: &'static str,
pub values: [u64; 2],
pub index: Option<u16>,
}
impl DecodeError {
pub const fn new(kind: DecodingKind, offset: usize, label: &'static str) -> Self {
Self {
offset,
kind,
context: "",
label,
values: [0; 2],
index: None,
}
}
#[must_use]
pub const fn within(mut self, context: &'static str) -> Self {
self.context = context;
self
}
#[must_use]
pub const fn at_item(mut self, index: u16) -> Self {
self.index = Some(index);
self
}
#[must_use]
pub const fn with_value(mut self, value: u64) -> Self {
self.values = [value, 0];
self
}
#[must_use]
pub const fn with_values(mut self, first: u64, second: u64) -> Self {
self.values = [first, second];
self
}
pub fn message(&self) -> impl fmt::Display + '_ {
DecodeText(self)
}
}
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "decoding error at offset {}: ", self.offset)?;
fmt::Display::fmt(&self.message(), f)
}
}
impl core::error::Error for DecodeError {}
struct DecodeText<'a>(&'a DecodeError);
impl fmt::Display for DecodeText<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let error = self.0;
if !error.context.is_empty() || error.index.is_some() {
f.write_str(error.context)?;
if let Some(index) = error.index {
write!(f, "[{index}]")?;
}
f.write_str(": ")?;
}
write_label(f, error.label, error.values)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RangeError {
pub label: &'static str,
pub values: [u64; 2],
}
impl RangeError {
pub const fn new(label: &'static str) -> Self {
Self {
label,
values: [0; 2],
}
}
#[must_use]
pub const fn with_value(mut self, value: u64) -> Self {
self.values = [value, 0];
self
}
#[must_use]
pub const fn with_values(mut self, first: u64, second: u64) -> Self {
self.values = [first, second];
self
}
pub fn message(&self) -> impl fmt::Display + '_ {
Message(self)
}
}
impl fmt::Display for RangeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("value out of range: ")?;
write_label(f, self.label, self.values)
}
}
impl core::error::Error for RangeError {}
struct Message<'a>(&'a RangeError);
impl fmt::Display for Message<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_label(f, self.0.label, self.0.values)
}
}
fn datatype_name(number: u64) -> &'static str {
const NAMES: [&str; 13] = [
"NULL",
"BOOLEAN",
"Unsigned",
"INTEGER",
"REAL",
"Double",
"OCTET STRING",
"CharacterString",
"BIT STRING",
"ENUMERATED",
"Date",
"Time",
"BACnetObjectIdentifier",
];
usize::try_from(number)
.ok()
.and_then(|number| NAMES.get(number))
.copied()
.unwrap_or("value of a reserved type")
}
pub(super) fn write_label(
f: &mut fmt::Formatter<'_>,
label: &str,
values: [u64; 2],
) -> fmt::Result {
let mut rest = label;
let mut next = values.iter();
while let Some(at) = rest.find('{') {
let (before, from) = rest.split_at(at);
f.write_str(before)?;
let placeholder = ["{}", "{:#x}", "{:#04X}", "{t}"]
.into_iter()
.find(|placeholder| from.starts_with(placeholder));
match placeholder.zip(next.clone().next()) {
Some((placeholder, &value)) => {
next.next();
match placeholder {
"{:#x}" => write!(f, "{value:#x}")?,
"{:#04X}" => write!(f, "{value:#04X}")?,
"{t}" => f.write_str(datatype_name(value))?,
_ => write!(f, "{value}")?,
}
rest = &from[placeholder.len()..];
}
None => {
let placeholder = placeholder.unwrap_or("{");
f.write_str(placeholder)?;
rest = &from[placeholder.len()..];
}
}
}
f.write_str(rest)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn labels_fill_their_placeholders_in_order() {
let display = |label, values: [u64; 2]| {
let mut refused = DecodeError::new(DecodingKind::InvalidTag, 3, label);
refused.values = values;
refused.to_string()
};
assert_eq!(
display("closing tag {} does not match opening tag {}", [4, 2]),
"decoding error at offset 3: closing tag 4 does not match opening tag 2"
);
assert_eq!(
display("unknown PDU type nibble: {:#x}", [0xF, 0]),
"decoding error at offset 3: unknown PDU type nibble: 0xf"
);
assert_eq!(
display("type {:#04X}, function {:#04X}", [0x0B, 0x1FF]),
"decoding error at offset 3: type 0x0B, function 0x1FF"
);
assert_eq!(
display("no placeholder", [7, 8]),
"decoding error at offset 3: no placeholder"
);
assert_eq!(
display("expected application-tagged {t}, not {t}", [2, 12]),
"decoding error at offset 3: expected application-tagged Unsigned, \
not BACnetObjectIdentifier"
);
assert_eq!(
display("{t} and {t}", [0, 13]),
"decoding error at offset 3: NULL and value of a reserved type"
);
assert_eq!(
display("{a} {} {} {}", [1, 2]),
"decoding error at offset 3: {a} 1 2 {}"
);
assert_eq!(
DecodeError::new(DecodingKind::OutOfRange, 0, "[{}] value {} exceeds u32")
.within("WhoIs low limit")
.with_values(0, 1 << 32)
.to_string(),
"decoding error at offset 0: WhoIs low limit: [0] value 4294967296 exceeds u32"
);
assert_eq!(
DecodeError::new(DecodingKind::InvalidTag, 7, "expected {t}")
.within("record data")
.at_item(1)
.with_value(6)
.to_string(),
"decoding error at offset 7: record data[1]: expected OCTET STRING"
);
}
#[test]
fn range_errors_display_their_label() {
let refused =
RangeError::new("low limit {} is above its high limit {}").with_values(20, 10);
assert_eq!(
refused.to_string(),
"value out of range: low limit 20 is above its high limit 10"
);
assert_eq!(
refused.message().to_string(),
"low limit 20 is above its high limit 10"
);
}
#[test]
fn errors_stay_small() {
use core::mem::size_of;
assert!(size_of::<DecodeError>() <= 64);
assert!(size_of::<super::super::Error>() <= 64);
assert!(size_of::<RangeError>() <= 32);
assert!(size_of::<super::super::EncodeError>() <= 56);
assert!(size_of::<super::super::SegmentationError>() <= 24);
}
}
#[cfg(target_pointer_width = "32")]
const _: () = assert!(core::mem::size_of::<super::Error>() == 48);