use alloc::vec;
use alloc::vec::Vec;
use core::fmt;
use crate::{Criticality, Error, ErrorKind, Fault, Faults};
pub trait Diagnostics {
type Criticality: Copy + Eq + fmt::Debug + Into<Criticality> + From<Criticality>;
type TypeOfError;
type Cause;
type Item;
type CriticalityDiagnostics;
fn name(id: u16) -> Option<&'static str>;
fn type_of_error(missing: bool) -> Self::TypeOfError;
fn cause(cause: ProtocolCause) -> Self::Cause;
fn item(
criticality: Self::Criticality,
id: u16,
type_of_error: Self::TypeOfError,
) -> Self::Item;
fn criticality_diagnostics(
triggering: Option<(u8, &str)>,
criticality: Self::Criticality,
items: Option<Vec<Self::Item>>,
) -> Self::CriticalityDiagnostics;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ProtocolCause {
TransferSyntaxError,
AbstractSyntaxErrorReject,
AbstractSyntaxErrorIgnoreAndNotify,
AbstractSyntaxErrorFalselyConstructedMessage,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Deviation<P: Diagnostics> {
PduCriticality {
criticality: P::Criticality,
assigned: P::Criticality,
},
UnexpectedIe {
id: u16,
name: Option<&'static str>,
position: usize,
criticality: P::Criticality,
},
RepeatedIe {
id: u16,
name: &'static str,
position: usize,
first: usize,
},
OutOfOrder {
id: u16,
name: &'static str,
position: usize,
after: u16,
after_name: &'static str,
},
IeCriticality {
id: u16,
name: &'static str,
position: usize,
criticality: P::Criticality,
assigned: P::Criticality,
},
MissingIe {
id: u16,
name: &'static str,
criticality: P::Criticality,
},
OutsideRoot {
id: u16,
name: &'static str,
position: usize,
criticality: P::Criticality,
error: Error,
},
UndecodableIe {
id: u16,
name: &'static str,
position: usize,
criticality: P::Criticality,
error: Error,
},
NotCanonical {
ie: Option<(u16, &'static str, usize)>,
fault: Fault,
bit: usize,
},
UnknownProcedure {
procedure_code: u8,
direction: &'static str,
criticality: P::Criticality,
},
}
impl<P: Diagnostics> Deviation<P> {
pub fn ie(&self) -> Option<(u16, P::Criticality)> {
match self {
Self::PduCriticality { .. } => None,
Self::UnexpectedIe {
id, criticality, ..
}
| Self::IeCriticality {
id, criticality, ..
}
| Self::MissingIe {
id, criticality, ..
}
| Self::OutsideRoot {
id, criticality, ..
}
| Self::UndecodableIe {
id, criticality, ..
} => Some((*id, *criticality)),
Self::RepeatedIe { .. }
| Self::OutOfOrder { .. }
| Self::NotCanonical { .. }
| Self::UnknownProcedure { .. } => None,
}
}
pub fn type_of_error(&self) -> Option<P::TypeOfError> {
match self {
Self::UnexpectedIe { .. } | Self::UndecodableIe { .. } | Self::OutsideRoot { .. } => {
Some(P::type_of_error(false))
}
Self::MissingIe { .. } => Some(P::type_of_error(true)),
_ => None,
}
}
pub fn diagnostics_item(&self) -> Option<P::Item> {
let (id, criticality) = self.ie()?;
if matches!(criticality.into(), Criticality::Ignore) {
return None;
}
let type_of_error = self.type_of_error()?;
Some(P::item(criticality, id, type_of_error))
}
pub fn cause(&self) -> Option<P::Cause> {
let by_criticality = |criticality: &P::Criticality| match (*criticality).into() {
Criticality::Reject => Some(ProtocolCause::AbstractSyntaxErrorReject),
Criticality::Notify => Some(ProtocolCause::AbstractSyntaxErrorIgnoreAndNotify),
Criticality::Ignore => None,
};
let cause = match self {
Self::UndecodableIe { .. } => Some(ProtocolCause::TransferSyntaxError),
Self::UnexpectedIe { criticality, .. }
| Self::MissingIe { criticality, .. }
| Self::OutsideRoot { criticality, .. }
| Self::UnknownProcedure { criticality, .. } => by_criticality(criticality),
Self::RepeatedIe { .. } | Self::OutOfOrder { .. } => {
Some(ProtocolCause::AbstractSyntaxErrorFalselyConstructedMessage)
}
Self::PduCriticality { .. }
| Self::IeCriticality { .. }
| Self::NotCanonical { .. } => None,
};
cause.map(P::cause)
}
pub fn of(error: &Error) -> Option<Self> {
let ie = error.ie()?;
let (id, criticality) = (ie.id, P::Criticality::from(ie.criticality));
let name = P::name(id).unwrap_or_default();
match (error.kind(), ie.position) {
(ErrorKind::MissingIe, None) => Some(Self::MissingIe {
id,
name,
criticality,
}),
(ErrorKind::UndecodableIe, Some(position)) => Some(Self::UndecodableIe {
id,
name,
position,
criticality,
error: error.clone(),
}),
_ => None,
}
}
}
impl<P: Diagnostics> fmt::Display for Deviation<P> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PduCriticality {
criticality,
assigned,
} => write!(
formatter,
"the criticality of the PDU is {criticality:?}, and that of its procedure \
is {assigned:?}"
),
Self::UnexpectedIe {
id, name, position, ..
} => match name {
Some(name) => write!(
formatter,
"{name} (IE {id}), at position {position}, is not an IE of the message"
),
None => write!(
formatter,
"IE {id}, at position {position}, is not an IE of the message, nor of \
the protocol"
),
},
Self::RepeatedIe {
id,
name,
position,
first,
} => write!(
formatter,
"{name} (IE {id}), at position {position}, is in the message already, at \
position {first}"
),
Self::OutOfOrder {
id,
name,
position,
after,
after_name,
} => write!(
formatter,
"{name} (IE {id}), at position {position}, comes after {after_name} (IE \
{after}), which the object set of the message has after it"
),
Self::IeCriticality {
id,
name,
position,
criticality,
assigned,
} => write!(
formatter,
"the criticality of {name} (IE {id}), at position {position}, is \
{criticality:?}, and the message assigns it {assigned:?}"
),
Self::MissingIe { id, name, .. } => write!(
formatter,
"{name} (IE {id}) is mandatory, and the message does not have it"
),
Self::UndecodableIe {
id,
name,
position,
error,
..
} => write!(
formatter,
"{name} (IE {id}), at position {position}, does not decode: {}",
error.reason()
),
Self::OutsideRoot {
id,
name,
position,
error,
..
} => write!(
formatter,
"{name} (IE {id}), at position {position}, has a value of a later version \
of its type: {}",
error.reason()
),
Self::NotCanonical {
ie: Some((id, name, position)),
fault,
bit,
} => write!(
formatter,
"the value of {name} (IE {id}), at position {position}, is not written as \
its sender is to write it: at bit {bit}, {fault}"
),
Self::NotCanonical {
ie: None,
fault,
bit,
} => write!(
formatter,
"the message is not written as its sender is to write it: at bit {bit}, {fault}"
),
Self::UnknownProcedure {
procedure_code,
direction,
criticality,
} => write!(
formatter,
"the {direction} of procedure {procedure_code} is no message of the \
specification, and the criticality of the PDU is {criticality:?}"
),
}
}
}
pub fn criticality_diagnostics<P: Diagnostics>(
procedure_code: u8,
direction: &str,
criticality: P::Criticality,
deviations: &[Deviation<P>],
) -> P::CriticalityDiagnostics {
let items = items(deviations);
P::criticality_diagnostics(Some((procedure_code, direction)), criticality, items)
}
pub fn criticality_diagnostics_for_response<P: Diagnostics>(
criticality: P::Criticality,
deviations: &[Deviation<P>],
) -> P::CriticalityDiagnostics {
P::criticality_diagnostics(None, criticality, items(deviations))
}
fn items<P: Diagnostics>(deviations: &[Deviation<P>]) -> Option<Vec<P::Item>> {
const MOST: usize = 256;
let items: Vec<P::Item> = deviations
.iter()
.filter_map(Deviation::diagnostics_item)
.take(MOST)
.collect();
(!items.is_empty()).then_some(items)
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Wrong {
Undecodable(Error),
OutsideRoot(Error),
}
pub fn deviations<P: Diagnostics>(
set: &[(u16, P::Criticality, bool)],
ies: &[(u16, P::Criticality)],
value: &dyn Fn(usize) -> (Option<Wrong>, Faults),
found: &mut Vec<Deviation<P>>,
) {
compared(set, ies, Some(value), found)
}
pub fn structure_deviations<P: Diagnostics>(
set: &[(u16, P::Criticality, bool)],
ies: &[(u16, P::Criticality)],
found: &mut Vec<Deviation<P>>,
) {
compared(set, ies, None, found)
}
fn compared<P: Diagnostics>(
set: &[(u16, P::Criticality, bool)],
ies: &[(u16, P::Criticality)],
value: Option<&dyn Fn(usize) -> (Option<Wrong>, Faults)>,
found: &mut Vec<Deviation<P>>,
) {
let name = P::name;
let mut first: Vec<Option<usize>> = vec![None; set.len()];
let mut last: Option<usize> = None;
for (position, (id, criticality)) in ies.iter().enumerate() {
let (id, criticality) = (*id, *criticality);
let Some(index) = set.iter().position(|(known, ..)| *known == id) else {
found.push(Deviation::UnexpectedIe {
id,
name: name(id),
position,
criticality,
});
continue;
};
let named = || name(id).unwrap_or_default();
match (first[index], last) {
(Some(first), _) => found.push(Deviation::RepeatedIe {
id,
name: named(),
position,
first,
}),
(None, Some(last)) if index < last => {
first[index] = Some(position);
let (after, ..) = set[last];
found.push(Deviation::OutOfOrder {
id,
name: named(),
position,
after,
after_name: name(after).unwrap_or_default(),
});
}
(None, _) => {
first[index] = Some(position);
last = Some(index);
}
}
let (_, assigned, _) = set[index];
if criticality != assigned {
found.push(Deviation::IeCriticality {
id,
name: named(),
position,
criticality,
assigned,
});
}
let Some(value) = value else {
continue;
};
let (wrong, faults) = value(position);
match wrong {
Some(Wrong::Undecodable(error)) => found.push(Deviation::UndecodableIe {
id,
name: named(),
position,
criticality,
error,
}),
Some(Wrong::OutsideRoot(error)) => found.push(Deviation::OutsideRoot {
id,
name: named(),
position,
criticality,
error,
}),
None => {}
}
found.extend(faults.iter().map(|noted| Deviation::NotCanonical {
ie: Some((id, named(), position)),
fault: noted.fault,
bit: noted.bit,
}));
}
for ((id, criticality, mandatory), first) in set.iter().zip(first) {
if *mandatory && first.is_none() {
found.push(Deviation::MissingIe {
id: *id,
name: name(*id).unwrap_or_default(),
criticality: *criticality,
});
}
}
}