use core::fmt::{Display, Formatter};
use std::error;
use std::io;
use crate::pdu::{
CommandLengthError, OctetStringCreationError, MAX_PDU_LENGTH,
MIN_PDU_LENGTH,
};
use super::{CheckError, PduStatus};
#[derive(Debug)]
pub enum PduParseErrorBody {
BodyNotAllowedWhenStatusIsNotZero,
BodyRequiredWhenStatusIsZero,
LengthLongerThanPdu(u32),
LengthTooLong(u32),
LengthTooShort(u32),
IncorrectLength(u32, String),
InvalidSequenceNumber,
NotEnoughBytes,
OctetStringCreationError(OctetStringCreationError),
OtherIoError(io::Error),
StatusIsNotZero,
StatusIsZero,
UnknownCommandId,
}
#[derive(Debug)]
pub struct PduParseError {
pub command_id: Option<u32>,
command_status: Option<u32>,
pub sequence_number: Option<u32>,
field_name: Option<String>,
body: PduParseErrorBody,
}
impl PduParseError {
pub fn new(body: PduParseErrorBody) -> Self {
Self {
command_id: None,
command_status: None,
sequence_number: None,
field_name: None,
body,
}
}
pub fn into_with_header(
mut self,
command_id: Option<u32>,
command_status: Option<u32>,
sequence_number: Option<u32>,
) -> Self {
self.command_id = command_id;
self.command_status = command_status;
self.sequence_number = sequence_number;
self
}
pub fn into_with_field_name(mut self, field_name: &str) -> Self {
self.field_name = Some(String::from(field_name));
self
}
pub fn status(&self) -> u32 {
(match self.body {
PduParseErrorBody::LengthTooLong(_) => PduStatus::ESME_RINVCMDLEN,
PduParseErrorBody::LengthTooShort(_) => PduStatus::ESME_RINVCMDLEN,
PduParseErrorBody::LengthLongerThanPdu(_) => {
PduStatus::ESME_RINVCMDLEN
}
PduParseErrorBody::IncorrectLength(_, _) => {
PduStatus::ESME_RINVMSGLEN
}
PduParseErrorBody::UnknownCommandId => PduStatus::ESME_RINVCMDID,
_ => PduStatus::ESME_RSYSERR,
}) as u32
}
}
impl From<OctetStringCreationError> for PduParseError {
fn from(e: OctetStringCreationError) -> Self {
Self::new(PduParseErrorBody::OctetStringCreationError(e))
}
}
impl From<CheckError> for PduParseError {
fn from(e: CheckError) -> Self {
match e {
CheckError::IoError(e) => e.into(),
CheckError::CommandLengthError(e) => e.into(),
}
}
}
impl From<CommandLengthError> for PduParseError {
fn from(e: CommandLengthError) -> Self {
match e {
CommandLengthError::TooLong(length) => {
Self::new(PduParseErrorBody::LengthTooLong(length))
}
CommandLengthError::TooShort(length) => {
Self::new(PduParseErrorBody::LengthTooShort(length))
}
}
}
}
impl From<io::Error> for PduParseError {
fn from(e: io::Error) -> Self {
match e.kind() {
io::ErrorKind::UnexpectedEof => {
Self::new(PduParseErrorBody::NotEnoughBytes)
}
_ => Self::new(PduParseErrorBody::OtherIoError(e)),
}
}
}
impl Display for PduParseError {
fn fmt(
&self,
formatter: &mut Formatter,
) -> std::result::Result<(), std::fmt::Error> {
let msg = match &self.body {
PduParseErrorBody::BodyNotAllowedWhenStatusIsNotZero => {
format!(
"PDU body must not be supplied when status is not zero, \
but command_status is {}.",
as_hex(self.command_status)
)
}
PduParseErrorBody::BodyRequiredWhenStatusIsZero => String::from(
"PDU body must be supplied when status is zero, \
but it is missing.",
),
PduParseErrorBody::IncorrectLength(length, message) => {
format!("Length {} was incorrect: {}", length, message)
}
PduParseErrorBody::InvalidSequenceNumber => {
format!(
"Sequence number {} is not allowed: \
must be 0x00000001 to 0x7FFFFFFF.",
as_hex(self.sequence_number)
)
}
PduParseErrorBody::LengthLongerThanPdu(length) => format!(
"Finished parsing PDU but its length ({}) suggested \
it was longer.",
length
),
PduParseErrorBody::LengthTooLong(length) => format!(
"Length ({}) too long. Max allowed is {} octets.",
length, MAX_PDU_LENGTH
),
PduParseErrorBody::LengthTooShort(length) => format!(
"Length ({}) too short. Min allowed is {} octets.",
length, MIN_PDU_LENGTH
),
PduParseErrorBody::NotEnoughBytes => String::from(
"Reached end of PDU length (or end of input) before \
finding all fields of the PDU.",
),
PduParseErrorBody::OctetStringCreationError(e) => e.to_string(),
PduParseErrorBody::OtherIoError(e) => {
format!("IO error: {}", e.to_string())
}
PduParseErrorBody::StatusIsNotZero => {
format!(
"command_status must be 0, but was {}.",
as_hex(self.command_status)
)
}
PduParseErrorBody::StatusIsZero => {
String::from("command_status must not be non-zero, but was 0.")
}
PduParseErrorBody::UnknownCommandId => {
String::from("Supplied command_id is unknown.")
}
};
formatter.write_fmt(format_args!(
"Error parsing PDU (\
command_id={}, command_status={}, \
sequence_number={}, field_name={}): {}",
as_hex(self.command_id),
as_hex(self.command_status),
as_hex(self.sequence_number),
self.field_name.clone().unwrap_or(String::from("UNKNOWN")),
msg,
))
}
}
impl error::Error for PduParseError {}
fn as_hex(num: Option<u32>) -> String {
if let Some(num) = num {
format!("{:#010X}", num)
} else {
String::from("UNKNOWN")
}
}
pub fn fld<T, E>(
field_name: &str,
res: Result<T, E>,
) -> Result<T, PduParseError>
where
E: Into<PduParseError>,
{
res.map_err(|e| e.into().into_with_field_name(field_name))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn formatting_unknown_command_id() {
assert_eq!(
PduParseError::new(PduParseErrorBody::UnknownCommandId)
.into_with_header(
Some(0x00001234),
Some(0x00000000),
Some(0x0004321)
)
.to_string(),
"Error parsing PDU (\
command_id=0x00001234, command_status=0x00000000, \
sequence_number=0x00004321, field_name=UNKNOWN): \
Supplied command_id is unknown."
);
}
}