Skip to main content

bgpkit_parser/
error.rs

1/*!
2error module defines the error types used in bgpkit-parser.
3*/
4use crate::models::{Afi, AttrType, Bgp4MpType, BgpState, EntryType, Safi, TableDumpV2Type};
5use num_enum::TryFromPrimitiveError;
6#[cfg(feature = "oneio")]
7use oneio::OneIoError;
8use std::fmt::{Display, Formatter};
9use std::io::ErrorKind;
10use std::{error::Error, fmt, io};
11
12#[derive(Debug)]
13pub enum ParserError {
14    IoError(io::Error),
15    EofError(io::Error),
16    #[cfg(feature = "oneio")]
17    OneIoError(OneIoError),
18    EofExpected,
19    ParseError(String),
20    TruncatedMsg(String),
21    Unsupported(String),
22    FilterError(String),
23    /// NLRI length field is inconsistent with content
24    /// (total_bits < minimum required, or underflow in prefix calculation)
25    InvalidLabeledNlriLength,
26    /// Input ended before completing NLRI structure
27    TruncatedLabeledNlri,
28    /// Input ended in the middle of prefix data
29    TruncatedPrefix,
30    /// Exceeded configured max_labels without finding Bottom-of-Stack bit
31    MaxLabelStackDepthExceeded,
32    /// Exceeded peer-advertised max labels (Multiple Labels Capability)
33    /// Per RFC 8277 §2.1, this should be treated as a withdrawal
34    PeerMaxLabelsExceeded,
35    /// Invalid prefix in NLRI
36    InvalidPrefix,
37}
38
39impl Error for ParserError {}
40
41#[derive(Debug)]
42pub struct ParserErrorWithBytes {
43    pub error: ParserError,
44    pub bytes: Option<Vec<u8>>,
45}
46
47impl Display for ParserErrorWithBytes {
48    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
49        write!(f, "{}", self.error)
50    }
51}
52
53impl Error for ParserErrorWithBytes {}
54
55/// implement Display trait for Error which satistifies the std::error::Error
56/// trait's requirement (must implement Display and Debug traits, Debug already derived)
57impl Display for ParserError {
58    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
59        match self {
60            ParserError::IoError(e) => write!(f, "Error: {e}"),
61            ParserError::EofError(e) => write!(f, "Error: {e}"),
62            ParserError::ParseError(s) => write!(f, "Error: {s}"),
63            ParserError::TruncatedMsg(s) => write!(f, "Error: {s}"),
64            ParserError::Unsupported(s) => write!(f, "Error: {s}"),
65            ParserError::EofExpected => write!(f, "Error: reach end of file"),
66            #[cfg(feature = "oneio")]
67            ParserError::OneIoError(e) => write!(f, "Error: {e}"),
68            ParserError::FilterError(e) => write!(f, "Error: {e}"),
69            ParserError::InvalidLabeledNlriLength => {
70                write!(f, "Error: invalid labeled NLRI length field")
71            }
72            ParserError::TruncatedLabeledNlri => write!(f, "Error: truncated labeled NLRI"),
73            ParserError::TruncatedPrefix => write!(f, "Error: truncated prefix in NLRI"),
74            ParserError::MaxLabelStackDepthExceeded => write!(
75                f,
76                "Error: max label stack depth exceeded without finding BoS bit"
77            ),
78            ParserError::PeerMaxLabelsExceeded => write!(
79                f,
80                "Error: received more labels than peer advertised maximum"
81            ),
82            ParserError::InvalidPrefix => write!(f, "Error: invalid prefix in NLRI"),
83        }
84    }
85}
86
87#[cfg(feature = "oneio")]
88impl From<OneIoError> for ParserErrorWithBytes {
89    fn from(error: OneIoError) -> Self {
90        ParserErrorWithBytes {
91            error: ParserError::OneIoError(error),
92            bytes: None,
93        }
94    }
95}
96
97#[cfg(feature = "oneio")]
98impl From<OneIoError> for ParserError {
99    fn from(error: OneIoError) -> Self {
100        ParserError::OneIoError(error)
101    }
102}
103
104impl From<ParserError> for ParserErrorWithBytes {
105    fn from(error: ParserError) -> Self {
106        ParserErrorWithBytes { error, bytes: None }
107    }
108}
109
110impl From<io::Error> for ParserError {
111    fn from(io_error: io::Error) -> Self {
112        match io_error.kind() {
113            ErrorKind::UnexpectedEof => ParserError::EofError(io_error),
114            _ => ParserError::IoError(io_error),
115        }
116    }
117}
118
119impl From<TryFromPrimitiveError<Bgp4MpType>> for ParserError {
120    fn from(value: TryFromPrimitiveError<Bgp4MpType>) -> Self {
121        ParserError::ParseError(format!("cannot parse bgp4mp subtype: {}", value.number))
122    }
123}
124
125impl From<TryFromPrimitiveError<BgpState>> for ParserError {
126    fn from(value: TryFromPrimitiveError<BgpState>) -> Self {
127        ParserError::ParseError(format!("cannot parse bgp4mp state: {}", value.number))
128    }
129}
130
131impl From<TryFromPrimitiveError<TableDumpV2Type>> for ParserError {
132    fn from(value: TryFromPrimitiveError<TableDumpV2Type>) -> Self {
133        ParserError::ParseError(format!("cannot parse table dump v2 type: {}", value.number))
134    }
135}
136
137impl From<TryFromPrimitiveError<EntryType>> for ParserError {
138    fn from(value: TryFromPrimitiveError<EntryType>) -> Self {
139        ParserError::ParseError(format!("cannot parse entry type: {}", value.number))
140    }
141}
142
143impl From<TryFromPrimitiveError<Afi>> for ParserError {
144    fn from(value: TryFromPrimitiveError<Afi>) -> Self {
145        ParserError::ParseError(format!("Unknown AFI type: {}", value.number))
146    }
147}
148
149impl From<TryFromPrimitiveError<Safi>> for ParserError {
150    fn from(value: TryFromPrimitiveError<Safi>) -> Self {
151        ParserError::ParseError(format!("Unknown SAFI type: {}", value.number))
152    }
153}
154
155/// BGP validation warnings for RFC 7606 compliant error handling.
156/// These represent non-fatal validation issues that don't prevent parsing.
157///
158/// This enum is `#[non_exhaustive]` so that new warning variants can be added
159/// in minor releases without breaking exhaustive matches.
160#[derive(Debug, Clone, PartialEq, Eq)]
161#[non_exhaustive]
162pub enum BgpValidationWarning {
163    /// Attribute flags conflict with attribute type code (RFC 4271 Section 6.3)
164    AttributeFlagsError {
165        attr_type: AttrType,
166        expected_flags: u8,
167        actual_flags: u8,
168    },
169    /// Attribute length conflicts with expected length (RFC 4271 Section 6.3)
170    AttributeLengthError {
171        attr_type: AttrType,
172        expected_length: Option<usize>,
173        actual_length: usize,
174    },
175    /// Missing well-known mandatory attribute (RFC 4271 Section 6.3)
176    MissingWellKnownAttribute { attr_type: AttrType },
177    /// Unrecognized well-known attribute (RFC 4271 Section 6.3)
178    UnrecognizedWellKnownAttribute { attr_type_code: u8 },
179    /// Invalid origin attribute value (RFC 4271 Section 6.3)
180    InvalidOriginAttribute { value: u8 },
181    /// Invalid next hop attribute (RFC 4271 Section 6.3)
182    InvalidNextHopAttribute { reason: String },
183    /// Malformed AS_PATH attribute (RFC 4271 Section 6.3)
184    MalformedAsPath { reason: String },
185    /// Optional attribute error (RFC 4271 Section 6.3)
186    OptionalAttributeError { attr_type: AttrType, reason: String },
187    /// Attribute appears more than once (RFC 4271 Section 6.3)
188    DuplicateAttribute { attr_type: AttrType },
189    /// Invalid network field in NLRI (RFC 4271 Section 6.3)
190    InvalidNetworkField { reason: String },
191    /// Malformed attribute list (RFC 4271 Section 6.3)
192    MalformedAttributeList { reason: String },
193    /// Partial attribute with errors (RFC 7606)
194    PartialAttributeError { attr_type: AttrType, reason: String },
195    /// Malformed NLRI field (RFC 7606 §5.3). The UPDATE message was parseable
196    /// up to the NLRI section, but the NLRI itself contained syntactic errors.
197    /// Per RFC 7606, the recommended action is "treat-as-withdrawal": all
198    /// routes carried in the NLRI section should be withdrawn.
199    ///
200    /// `nlri_type` distinguishes between the standard (IPv4 unicast) NLRI
201    /// field and the multiprotocol NLRI carried inside MP_REACH/MP_UNREACH
202    /// path attributes. `raw_bytes` preserves the offending NLRI bytes so
203    /// callers can inspect or export them without re-encoding.
204    MalformedNlri {
205        /// "withdrawn", "announced", "mp_reach", or "mp_unreach"
206        nlri_type: &'static str,
207        /// Human-readable description of the parse error
208        reason: String,
209        /// Raw NLRI bytes as they appeared in the UPDATE message
210        raw_bytes: Vec<u8>,
211    },
212}
213
214impl Display for BgpValidationWarning {
215    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
216        match self {
217            BgpValidationWarning::AttributeFlagsError { attr_type, expected_flags, actual_flags } => {
218                write!(f, "Attribute flags error for {attr_type:?}: expected 0x{expected_flags:02x}, got 0x{actual_flags:02x}")
219            }
220            BgpValidationWarning::AttributeLengthError { attr_type, expected_length, actual_length } => {
221                match expected_length {
222                    Some(expected) => write!(f, "Attribute length error for {attr_type:?}: expected {expected}, got {actual_length}"),
223                    None => write!(f, "Attribute length error for {attr_type:?}: invalid length {actual_length}"),
224                }
225            }
226            BgpValidationWarning::MissingWellKnownAttribute { attr_type } => {
227                write!(f, "Missing well-known mandatory attribute: {attr_type:?}")
228            }
229            BgpValidationWarning::UnrecognizedWellKnownAttribute { attr_type_code } => {
230                write!(f, "Unrecognized well-known attribute: type code {attr_type_code}")
231            }
232            BgpValidationWarning::InvalidOriginAttribute { value } => {
233                write!(f, "Invalid origin attribute value: {value}")
234            }
235            BgpValidationWarning::InvalidNextHopAttribute { reason } => {
236                write!(f, "Invalid next hop attribute: {reason}")
237            }
238            BgpValidationWarning::MalformedAsPath { reason } => {
239                write!(f, "Malformed AS_PATH: {reason}")
240            }
241            BgpValidationWarning::OptionalAttributeError { attr_type, reason } => {
242                write!(f, "Optional attribute error for {attr_type:?}: {reason}")
243            }
244            BgpValidationWarning::DuplicateAttribute { attr_type } => {
245                write!(f, "Duplicate attribute: {attr_type:?}")
246            }
247            BgpValidationWarning::InvalidNetworkField { reason } => {
248                write!(f, "Invalid network field: {reason}")
249            }
250            BgpValidationWarning::MalformedAttributeList { reason } => {
251                write!(f, "Malformed attribute list: {reason}")
252            }
253            BgpValidationWarning::PartialAttributeError { attr_type, reason } => {
254                write!(f, "Partial attribute error for {attr_type:?}: {reason}")
255            }
256            BgpValidationWarning::MalformedNlri { nlri_type, reason, .. } => {
257                write!(f, "Malformed NLRI ({nlri_type}): {reason}")
258            }
259        }
260    }
261}
262
263/// Result type for BGP attribute parsing that includes validation warnings
264#[derive(Debug, Clone)]
265pub struct BgpValidationResult<T> {
266    pub value: T,
267    pub warnings: Vec<BgpValidationWarning>,
268}
269
270impl<T> BgpValidationResult<T> {
271    pub fn new(value: T) -> Self {
272        Self {
273            value,
274            warnings: Vec::new(),
275        }
276    }
277
278    pub fn with_warnings(value: T, warnings: Vec<BgpValidationWarning>) -> Self {
279        Self { value, warnings }
280    }
281
282    pub fn add_warning(&mut self, warning: BgpValidationWarning) {
283        self.warnings.push(warning);
284    }
285
286    pub fn has_warnings(&self) -> bool {
287        !self.warnings.is_empty()
288    }
289}