Skip to main content

stackforge_core/
error.rs

1//! Error types for the stackforge-core crate.
2
3use crate::layer::LayerKind;
4use thiserror::Error;
5
6/// Errors that can occur during packet operations.
7#[derive(Debug, Error)]
8pub enum PacketError {
9    /// The packet buffer is too short to contain the expected data.
10    #[error("buffer too short: expected at least {expected} bytes, got {actual}")]
11    BufferTooShort { expected: usize, actual: usize },
12
13    /// Invalid field value encountered during parsing.
14    #[error("invalid field value: {0}")]
15    InvalidField(String),
16
17    /// The layer type is not supported or recognized.
18    #[error("unsupported layer type: {0}")]
19    UnsupportedLayer(String),
20
21    /// Attempted to access a layer that doesn't exist in the packet.
22    #[error("layer not found: {0:?}")]
23    LayerNotFound(LayerKind),
24
25    /// Checksum verification failed.
26    #[error("checksum mismatch: expected {expected:#06x}, got {actual:#06x}")]
27    ChecksumMismatch { expected: u16, actual: u16 },
28
29    /// Invalid protocol number.
30    #[error("invalid protocol number: {0}")]
31    InvalidProtocol(u8),
32
33    /// Failed to parse layer at the given offset.
34    #[error("parse error at offset {offset}: {message}")]
35    ParseError { offset: usize, message: String },
36
37    /// Field access error.
38    #[error("field error: {0}")]
39    FieldError(#[from] crate::layer::field::FieldError),
40
41    /// Layer binding not found.
42    #[error("no binding found for {lower:?} -> {upper:?}")]
43    BindingNotFound { lower: LayerKind, upper: LayerKind },
44
45    /// Neighbor resolution failed.
46    #[error("failed to resolve neighbor for {0}")]
47    NeighborResolutionFailed(String),
48
49    /// Invalid MAC address.
50    #[error("invalid MAC address: {0}")]
51    InvalidMac(String),
52
53    /// Invalid IP address.
54    #[error("invalid IP address: {0}")]
55    InvalidIp(String),
56
57    /// Operation not supported.
58    #[error("operation not supported: {0}")]
59    NotSupported(String),
60
61    /// Timeout error.
62    #[error("operation timed out after {0} ms")]
63    Timeout(u64),
64
65    /// I/O error wrapper.
66    #[error("I/O error: {0}")]
67    Io(String),
68}
69
70impl From<std::io::Error> for PacketError {
71    fn from(err: std::io::Error) -> Self {
72        PacketError::Io(err.to_string())
73    }
74}
75
76impl PacketError {
77    /// Create a buffer too short error.
78    #[must_use]
79    pub fn buffer_too_short(expected: usize, actual: usize) -> Self {
80        Self::BufferTooShort { expected, actual }
81    }
82
83    /// Create a parse error.
84    pub fn parse_error(offset: usize, message: impl Into<String>) -> Self {
85        Self::ParseError {
86            offset,
87            message: message.into(),
88        }
89    }
90
91    /// Create a binding not found error.
92    #[must_use]
93    pub fn binding_not_found(lower: LayerKind, upper: LayerKind) -> Self {
94        Self::BindingNotFound { lower, upper }
95    }
96
97    /// Check if this is a recoverable error.
98    #[must_use]
99    pub fn is_recoverable(&self) -> bool {
100        matches!(
101            self,
102            Self::BufferTooShort { .. } | Self::Timeout(_) | Self::NeighborResolutionFailed(_)
103        )
104    }
105}
106
107/// Result type alias for packet operations.
108pub type Result<T> = std::result::Result<T, PacketError>;
109
110/// Extension trait for Result types.
111pub trait ResultExt<T> {
112    /// Convert to `PacketError` with context.
113    fn with_context(self, context: impl FnOnce() -> String) -> Result<T>;
114}
115
116impl<T, E: std::fmt::Display> ResultExt<T> for std::result::Result<T, E> {
117    fn with_context(self, context: impl FnOnce() -> String) -> Result<T> {
118        self.map_err(|e| PacketError::InvalidField(format!("{}: {}", context(), e)))
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_error_display() {
128        let err = PacketError::BufferTooShort {
129            expected: 20,
130            actual: 10,
131        };
132        assert!(err.to_string().contains("20"));
133        assert!(err.to_string().contains("10"));
134    }
135
136    #[test]
137    fn test_error_helpers() {
138        let err = PacketError::buffer_too_short(100, 50);
139        assert!(matches!(err, PacketError::BufferTooShort { .. }));
140
141        let err = PacketError::parse_error(10, "invalid header");
142        assert!(matches!(err, PacketError::ParseError { .. }));
143    }
144
145    #[test]
146    fn test_is_recoverable() {
147        assert!(PacketError::Timeout(1000).is_recoverable());
148        assert!(!PacketError::InvalidProtocol(255).is_recoverable());
149    }
150}