use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorKind {
Decode,
Encode,
WrongMessage,
MissingIe,
UndecodableIe,
Argument,
UnknownMessage,
UnknownIe,
Path,
NoSuchMember,
NothingSelected,
NotOpened,
Value,
Form,
Edit,
Limit,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Criticality {
Reject,
Ignore,
Notify,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct ErrorIe {
pub id: u16,
pub criticality: Criticality,
pub position: Option<usize>,
}
impl ErrorIe {
pub fn new(id: u16, criticality: Criticality, position: Option<usize>) -> Self {
Self {
id,
criticality,
position,
}
}
}
#[derive(Clone)]
pub struct Error(Box<Inner>);
#[derive(Clone)]
struct Inner {
kind: ErrorKind,
what: String,
why: Option<String>,
fields: Vec<&'static str>,
ie: Option<ErrorIe>,
}
impl Error {
pub fn new(kind: ErrorKind, what: impl Into<String>) -> Self {
Self(Box::new(Inner {
kind,
what: what.into(),
why: None,
fields: Vec::new(),
ie: None,
}))
}
pub fn kind(&self) -> ErrorKind {
self.0.kind
}
#[must_use]
pub fn because(mut self, why: impl Into<String>) -> Self {
self.0.why = Some(why.into());
self
}
#[must_use]
pub fn of(mut self, what: impl Into<String>) -> Self {
let was = core::mem::replace(&mut self.0.what, what.into());
if self.0.why.is_none() {
self.0.why = Some(was);
}
self
}
#[must_use]
pub fn with_kind(mut self, kind: ErrorKind) -> Self {
self.0.kind = kind;
self
}
pub fn ie(&self) -> Option<ErrorIe> {
self.0.ie
}
#[must_use]
pub fn about_ie(mut self, ie: ErrorIe) -> Self {
self.0.ie = Some(ie);
self
}
#[must_use]
pub fn reworded(self, reword: impl FnOnce(&str) -> String) -> Self {
let mut reworded = Self::new(self.0.kind, reword(&self.to_string()));
reworded.0.ie = self.0.ie;
reworded
}
pub fn reason(&self) -> String {
let mut reason = self.0.why.clone().unwrap_or_else(|| self.0.what.clone());
self.write_fields(&mut reason);
reason
}
pub fn fields(&self) -> impl Iterator<Item = &'static str> + '_ {
self.0.fields.iter().rev().copied()
}
#[cold]
#[must_use]
pub fn in_field(mut self, name: &'static str) -> Self {
self.0.fields.push(name);
self
}
#[cold]
#[must_use]
pub fn decoding(mut self, name: &str) -> Self {
let what = format!("these octets do not decode as {name}");
let was = core::mem::replace(&mut self.0.what, what);
if self.0.why.is_none() {
self.0.why = Some(was);
}
self
}
#[cold]
#[must_use]
pub fn encoding(mut self, name: &str) -> Self {
let what = format!("a value of {name} does not encode");
let was = core::mem::replace(&mut self.0.what, what);
if self.0.why.is_none() {
self.0.why = Some(was);
}
self
}
#[cold]
pub(crate) fn decode(why: impl Into<String>) -> Self {
Self::new(ErrorKind::Decode, "the octets do not decode").because(why)
}
#[cold]
pub(crate) fn encode(why: impl Into<String>) -> Self {
Self::new(ErrorKind::Encode, "the value does not encode").because(why)
}
#[cold]
pub(crate) fn ended() -> Self {
Self::decode("the octets end before the value does")
}
#[cold]
pub(crate) fn number_read_outside(value: i128, lower: i128, upper: i128) -> Self {
Self::decode(outside(value, lower, upper))
}
#[cold]
pub(crate) fn number_written_outside(value: i128, lower: i128, upper: i128) -> Self {
Self::encode(outside(value, lower, upper))
}
#[cold]
pub(crate) fn number_written_outside_root(value: i128, lower: i128, upper: i128) -> Self {
Self::encode(format!(
"{value} is outside the root of its constraint ({lower}..{upper}, ...)"
))
}
#[cold]
pub(crate) fn size_written_outside_root(size: usize, lower: usize, upper: usize) -> Self {
Self::encode(match lower == upper {
true => {
format!("a size of {size} is outside the root of its constraint ({lower}, ...)")
}
false => format!(
"a size of {size} is outside the root of its constraint ({lower}..{upper}, ...)"
),
})
}
#[cold]
pub(crate) fn size_read_outside(size: usize, lower: usize, upper: Option<usize>) -> Self {
Self::decode(size_outside(size, lower, upper))
}
#[cold]
pub(crate) fn size_written_outside(size: usize, lower: usize, upper: Option<usize>) -> Self {
Self::encode(size_outside(size, lower, upper))
}
#[cold]
pub(crate) fn does_not_fit(bits: u32) -> Self {
Self::decode(format!("the number does not fit in {bits} bits"))
}
fn write_fields(&self, text: &mut String) {
if self.0.fields.is_empty() {
return;
}
text.push_str(", in its field `");
for (at, field) in self.fields().enumerate() {
if at > 0 {
text.push('.');
}
text.push_str(field);
}
text.push('`');
}
}
impl Error {
pub fn wrong_message(expected: &str, held: Option<&str>, direction: &str, code: u8) -> Self {
let message = match held {
Some(held) => format!(
"the message of this PDU is {held}, not {expected}: kind() says which \
message a PDU holds"
),
None => format!(
"the message of this PDU is not {expected}: the specification has no \
{direction} of procedure {code}"
),
};
Self::new(ErrorKind::WrongMessage, message)
}
pub fn no_protocol_ies(name: &str) -> Self {
let what =
format!("the message of this PDU is {name}, which has no container of protocol IEs");
Self::new(ErrorKind::WrongMessage, what)
}
pub fn no_message_of(direction: &str, code: u8) -> Self {
let what = format!(
"the specification has no {direction} of procedure {code}, and so no object set \
for its IEs"
);
Self::new(ErrorKind::UnknownMessage, what)
}
#[must_use]
pub fn of_message(self, name: Option<&str>) -> Self {
const UNNAMED: &str =
"the message of this PDU does not decode as a container of protocol IEs";
self.of(match name {
Some(name) => format!("the {name} of this PDU does not decode"),
None => UNNAMED.to_string(),
})
}
#[must_use]
pub fn of_protocol_ies(self, count: usize) -> Self {
self.of(format!(
"these {count} protocol IEs do not encode as a message"
))
}
pub fn missing_ie(message: &str, ie: &str, field: &str) -> Self {
let what = format!("this {message} has no {ie}, which the field `{field}` requires");
Self::new(ErrorKind::MissingIe, what)
}
#[must_use]
pub fn undecodable_ie(self, message: &str, ie: &str) -> Self {
self.of(format!("the {ie} of this {message} does not decode"))
.with_kind(ErrorKind::UndecodableIe)
}
#[must_use]
pub fn outside_its_type(self, name: &str) -> Self {
self.of(format!("this is not a value of {name}"))
.with_kind(ErrorKind::Value)
}
}
fn outside(value: i128, lower: i128, upper: i128) -> String {
format!("{value} is outside its constraint ({lower}..{upper})")
}
fn size_outside(size: usize, lower: usize, upper: Option<usize>) -> String {
match upper {
Some(upper) if upper == lower => {
format!("a size of {size} is outside its constraint ({lower})")
}
Some(upper) => format!("a size of {size} is outside its constraint ({lower}..{upper})"),
None => format!("a size of {size} is outside its constraint ({lower}..MAX)"),
}
}
macro_rules! kinds {
($($(#[$doc:meta])* $name:ident $kind:ident;)*) => {
impl Error {
$(
$(#[$doc])*
pub fn $name(what: impl Into<String>) -> Self {
Self::new(ErrorKind::$kind, what)
}
)*
}
};
}
kinds! {
argument Argument;
unknown_message UnknownMessage;
unknown_ie UnknownIe;
path Path;
no_such_member NoSuchMember;
nothing_selected NothingSelected;
not_opened NotOpened;
undecodable UndecodableIe;
value Value;
form Form;
edit Edit;
limit Limit;
}
impl fmt::Display for Error {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut text = self.0.what.to_string();
if let Some(why) = &self.0.why {
text.push_str(": ");
text.push_str(why);
}
self.write_fields(&mut text);
formatter.write_str(&text)
}
}
impl fmt::Debug for Error {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut error = formatter.debug_struct("Error");
error.field("kind", &self.0.kind);
error.field("message", &self.to_string());
error.finish()
}
}
impl core::error::Error for Error {}
impl PartialEq for Error {
fn eq(&self, other: &Self) -> bool {
self.0.kind == other.0.kind && self.to_string() == other.to_string()
}
}
impl Eq for Error {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_error_is_cloned_sent_and_compared_by_its_kind_and_its_message() {
fn sent<T: Clone + Send + Sync + core::error::Error + 'static>() {}
sent::<Error>();
let error = Error::new(ErrorKind::Argument, "an MCC has three digits");
assert_eq!(error.kind(), ErrorKind::Argument);
assert_eq!(error.to_string(), "an MCC has three digits");
assert_eq!(error, error.clone());
assert_ne!(
error,
Error::new(ErrorKind::Value, "an MCC has three digits")
);
assert_ne!(error, Error::new(ErrorKind::Argument, "an MNC has two"));
let shown = format!("{error:?}");
assert_eq!(
shown,
"Error { kind: Argument, message: \"an MCC has three digits\" }"
);
assert_eq!(size_of::<Error>(), size_of::<usize>());
let beside = size_of::<u64>() + size_of::<usize>();
assert_eq!(
size_of::<Result<u64, Error>>(),
beside.next_multiple_of(align_of::<u64>())
);
}
#[test]
fn a_decoding_error_names_its_type_and_the_field_where_it_stopped() {
let error = Error::ended().in_field("id").in_field("protocol_ies");
assert_eq!(error.kind(), ErrorKind::Decode);
assert_eq!(
error.to_string(),
"the octets do not decode: the octets end before the value does, in its field \
`protocol_ies.id`"
);
let named = error.clone().decoding("NGAP-PDU");
assert_eq!(
named.to_string(),
"these octets do not decode as NGAP-PDU: the octets end before the value does, in \
its field `protocol_ies.id`"
);
assert_eq!(named.fields().collect::<Vec<_>>(), ["protocol_ies", "id"]);
let told = named.of("the Cause of this message does not decode");
assert_eq!(
told.to_string(),
"the Cause of this message does not decode: the octets end before the value does, \
in its field `protocol_ies.id`"
);
assert_eq!(
told.reason(),
"the octets end before the value does, in its field `protocol_ies.id`"
);
let of_a_kind = told.with_kind(ErrorKind::UndecodableIe);
assert_eq!(of_a_kind.kind(), ErrorKind::UndecodableIe);
}
#[test]
fn a_constraint_is_told_as_asn1_writes_it() {
assert_eq!(
Error::number_read_outside(4096, 0, 4095).reason(),
"4096 is outside its constraint (0..4095)"
);
assert_eq!(
Error::number_written_outside(-9, -8, 7).to_string(),
"the value does not encode: -9 is outside its constraint (-8..7)"
);
assert_eq!(
Error::size_read_outside(4, 3, Some(3)).reason(),
"a size of 4 is outside its constraint (3)"
);
assert_eq!(
Error::size_written_outside(0, 1, Some(150)).reason(),
"a size of 0 is outside its constraint (1..150)"
);
assert_eq!(
Error::size_read_outside(0, 1, None).reason(),
"a size of 0 is outside its constraint (1..MAX)"
);
}
}