use std::error::Error as StdError;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Module {
Http,
Tls,
Dns,
Icmp,
Pcap,
Reassembler,
Tracker,
Pipeline,
Layers,
}
impl fmt::Display for Module {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Module::Http => "http",
Module::Tls => "tls",
Module::Dns => "dns",
Module::Icmp => "icmp",
Module::Pcap => "pcap",
Module::Reassembler => "reassembler",
Module::Tracker => "tracker",
Module::Pipeline => "pipeline",
Module::Layers => "layers",
};
f.write_str(s)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorCode {
Parse,
BufferOverflow,
Io,
Unsupported,
Truncated,
Eof,
Other,
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
ErrorCode::Parse => "parse",
ErrorCode::BufferOverflow => "buffer-overflow",
ErrorCode::Io => "io",
ErrorCode::Unsupported => "unsupported",
ErrorCode::Truncated => "truncated",
ErrorCode::Eof => "eof",
ErrorCode::Other => "other",
};
f.write_str(s)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ErrorKind {
pub module: Module,
pub code: ErrorCode,
pub message: String,
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {}: {}", self.module, self.code, self.message)
}
}
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
source: Option<Box<dyn StdError + Send + Sync + 'static>>,
}
impl Error {
#[cfg(any(feature = "http", feature = "tls", feature = "dns", feature = "icmp"))]
pub(crate) fn parse(module: Module, message: impl Into<String>) -> Self {
Self {
kind: ErrorKind {
module,
code: ErrorCode::Parse,
message: message.into(),
},
source: None,
}
}
pub(crate) fn parse_with<E>(module: Module, message: impl Into<String>, source: E) -> Self
where
E: StdError + Send + Sync + 'static,
{
Self {
kind: ErrorKind {
module,
code: ErrorCode::Parse,
message: message.into(),
},
source: Some(Box::new(source)),
}
}
#[cfg(any(feature = "http", feature = "tls"))]
pub(crate) fn buffer_overflow(module: Module, cap: usize) -> Self {
Self {
kind: ErrorKind {
module,
code: ErrorCode::BufferOverflow,
message: format!("buffer exceeded cap={cap}"),
},
source: None,
}
}
#[cfg(feature = "pcap")]
pub(crate) fn io(module: Module, source: std::io::Error) -> Self {
Self {
kind: ErrorKind {
module,
code: ErrorCode::Io,
message: source.to_string(),
},
source: Some(Box::new(source)),
}
}
pub fn kind(&self) -> &ErrorKind {
&self.kind
}
pub fn module(&self) -> Module {
self.kind.module
}
pub fn code(&self) -> ErrorCode {
self.kind.code
}
pub fn is_recoverable(&self) -> bool {
matches!(
self.kind.code,
ErrorCode::Parse
| ErrorCode::BufferOverflow
| ErrorCode::Truncated
| ErrorCode::Unsupported
)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.kind, f)
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.source
.as_deref()
.map(|s| s as &(dyn StdError + 'static))
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_carries_module_and_code() {
let e = Error::parse(Module::Http, "bad request line");
assert_eq!(e.module(), Module::Http);
assert_eq!(e.code(), ErrorCode::Parse);
assert!(e.is_recoverable());
assert!(e.source().is_none());
}
#[test]
fn error_wraps_source() {
let io = std::io::Error::other("disk gone");
let e = Error::io(Module::Pcap, io);
assert_eq!(e.module(), Module::Pcap);
assert_eq!(e.code(), ErrorCode::Io);
assert!(!e.is_recoverable());
assert!(e.source().is_some());
}
#[test]
fn display_format() {
let e = Error::parse(Module::Tls, "alert received");
let s = format!("{e}");
assert!(s.contains("tls"));
assert!(s.contains("parse"));
assert!(s.contains("alert received"));
}
#[test]
fn send_sync_static() {
fn assert_traits<T: Send + Sync + 'static>() {}
assert_traits::<Error>();
}
#[test]
fn source_chain_walks() {
use std::error::Error as _;
let io = std::io::Error::other("disk gone");
let e = Error::io(Module::Pcap, io);
let s = e.source().unwrap();
assert!(format!("{s}").contains("disk gone"));
}
#[test]
fn buffer_overflow_includes_cap() {
let e = Error::buffer_overflow(Module::Http, 8192);
assert!(e.to_string().contains("8192"));
}
}