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/// Errors that can occur when encoding BGP/MRT messages to wire format.
42///
43/// These arise when in-memory data structures contain values that cannot be
44/// represented in their wire-format fields (e.g. an AS_PATH segment with more
45/// than 255 ASes, or an attribute value exceeding 65535 bytes). All such
46/// conditions were previously handled by silently truncating — see issue #313.
47#[derive(Debug, Clone, PartialEq, Eq)]
48#[non_exhaustive]
49pub enum EncodingError {
50    /// A value exceeded the maximum size that fits in its wire-format field.
51    ///
52    /// `field` names the wire field (e.g. `"AS_PATH segment count"`,
53    /// `"BGP attribute value length"`). `actual` is the byte/element count
54    /// that overflowed; `max` is the field's capacity.
55    ValueTooLarge {
56        field: &'static str,
57        actual: usize,
58        max: usize,
59    },
60    /// The value cannot be represented in wire format at all (e.g. ATTR_SET
61    /// encoding is not implemented, a labeled NLRI has an empty label stack,
62    /// or a BGP OPEN optional parameter uses the reserved type 255).
63    Unencodable { field: &'static str, reason: String },
64}
65
66impl EncodingError {
67    pub(crate) fn too_large(field: &'static str, actual: usize, max: usize) -> Self {
68        EncodingError::ValueTooLarge { field, actual, max }
69    }
70
71    #[cfg(feature = "parser")]
72    pub(crate) fn unencodable(field: &'static str, reason: impl Into<String>) -> Self {
73        EncodingError::Unencodable {
74            field,
75            reason: reason.into(),
76        }
77    }
78}
79
80/// Check that `actual` fits within `max`, returning `actual` unchanged.
81///
82/// This is the single place where wire-format capacity checks happen, so
83/// non-power-of-two bounds (e.g. the 12-bit FlowSpec NLRI length) are an
84/// explicit, greppable decision at the call site.
85pub(crate) fn check_max(
86    field: &'static str,
87    actual: usize,
88    max: usize,
89) -> Result<usize, EncodingError> {
90    if actual > max {
91        return Err(EncodingError::too_large(field, actual, max));
92    }
93    Ok(actual)
94}
95
96impl Display for EncodingError {
97    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
98        match self {
99            EncodingError::ValueTooLarge { field, actual, max } => write!(
100                f,
101                "encoding error: {field} ({actual}) exceeds maximum ({max})"
102            ),
103            EncodingError::Unencodable { field, reason } => {
104                write!(f, "encoding error: {field} cannot be encoded: {reason}")
105            }
106        }
107    }
108}
109
110impl Error for EncodingError {}
111
112#[derive(Debug)]
113pub struct ParserErrorWithBytes {
114    pub error: ParserError,
115    pub bytes: Option<Vec<u8>>,
116}
117
118impl Display for ParserErrorWithBytes {
119    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
120        write!(f, "{}", self.error)
121    }
122}
123
124impl Error for ParserErrorWithBytes {}
125
126/// implement Display trait for Error which satistifies the std::error::Error
127/// trait's requirement (must implement Display and Debug traits, Debug already derived)
128impl Display for ParserError {
129    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
130        match self {
131            ParserError::IoError(e) => write!(f, "Error: {e}"),
132            ParserError::EofError(e) => write!(f, "Error: {e}"),
133            ParserError::ParseError(s) => write!(f, "Error: {s}"),
134            ParserError::TruncatedMsg(s) => write!(f, "Error: {s}"),
135            ParserError::Unsupported(s) => write!(f, "Error: {s}"),
136            ParserError::EofExpected => write!(f, "Error: reach end of file"),
137            #[cfg(feature = "oneio")]
138            ParserError::OneIoError(e) => write!(f, "Error: {e}"),
139            ParserError::FilterError(e) => write!(f, "Error: {e}"),
140            ParserError::InvalidLabeledNlriLength => {
141                write!(f, "Error: invalid labeled NLRI length field")
142            }
143            ParserError::TruncatedLabeledNlri => write!(f, "Error: truncated labeled NLRI"),
144            ParserError::TruncatedPrefix => write!(f, "Error: truncated prefix in NLRI"),
145            ParserError::MaxLabelStackDepthExceeded => write!(
146                f,
147                "Error: max label stack depth exceeded without finding BoS bit"
148            ),
149            ParserError::PeerMaxLabelsExceeded => write!(
150                f,
151                "Error: received more labels than peer advertised maximum"
152            ),
153            ParserError::InvalidPrefix => write!(f, "Error: invalid prefix in NLRI"),
154        }
155    }
156}
157
158#[cfg(feature = "oneio")]
159impl From<OneIoError> for ParserErrorWithBytes {
160    fn from(error: OneIoError) -> Self {
161        ParserErrorWithBytes {
162            error: ParserError::OneIoError(error),
163            bytes: None,
164        }
165    }
166}
167
168#[cfg(feature = "oneio")]
169impl From<OneIoError> for ParserError {
170    fn from(error: OneIoError) -> Self {
171        ParserError::OneIoError(error)
172    }
173}
174
175impl From<ParserError> for ParserErrorWithBytes {
176    fn from(error: ParserError) -> Self {
177        ParserErrorWithBytes { error, bytes: None }
178    }
179}
180
181impl From<io::Error> for ParserError {
182    fn from(io_error: io::Error) -> Self {
183        match io_error.kind() {
184            ErrorKind::UnexpectedEof => ParserError::EofError(io_error),
185            _ => ParserError::IoError(io_error),
186        }
187    }
188}
189
190impl From<TryFromPrimitiveError<Bgp4MpType>> for ParserError {
191    fn from(value: TryFromPrimitiveError<Bgp4MpType>) -> Self {
192        ParserError::ParseError(format!("cannot parse bgp4mp subtype: {}", value.number))
193    }
194}
195
196impl From<TryFromPrimitiveError<BgpState>> for ParserError {
197    fn from(value: TryFromPrimitiveError<BgpState>) -> Self {
198        ParserError::ParseError(format!("cannot parse bgp4mp state: {}", value.number))
199    }
200}
201
202impl From<TryFromPrimitiveError<TableDumpV2Type>> for ParserError {
203    fn from(value: TryFromPrimitiveError<TableDumpV2Type>) -> Self {
204        ParserError::ParseError(format!("cannot parse table dump v2 type: {}", value.number))
205    }
206}
207
208impl From<TryFromPrimitiveError<EntryType>> for ParserError {
209    fn from(value: TryFromPrimitiveError<EntryType>) -> Self {
210        ParserError::ParseError(format!("cannot parse entry type: {}", value.number))
211    }
212}
213
214impl From<TryFromPrimitiveError<Afi>> for ParserError {
215    fn from(value: TryFromPrimitiveError<Afi>) -> Self {
216        ParserError::ParseError(format!("Unknown AFI type: {}", value.number))
217    }
218}
219
220impl From<TryFromPrimitiveError<Safi>> for ParserError {
221    fn from(value: TryFromPrimitiveError<Safi>) -> Self {
222        ParserError::ParseError(format!("Unknown SAFI type: {}", value.number))
223    }
224}
225
226/// BGP validation warnings for RFC 7606 compliant error handling.
227/// These represent non-fatal validation issues that don't prevent parsing.
228///
229/// This enum is `#[non_exhaustive]` so that new warning variants can be added
230/// in minor releases without breaking exhaustive matches.
231#[derive(Debug, Clone, PartialEq, Eq)]
232#[cfg_attr(feature = "serde", derive(serde::Serialize))]
233#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS), ts(export))]
234#[non_exhaustive]
235pub enum BgpValidationWarning {
236    /// Attribute flags conflict with attribute type code (RFC 4271 Section 6.3)
237    AttributeFlagsError {
238        attr_type: AttrType,
239        expected_flags: u8,
240        actual_flags: u8,
241    },
242    /// Attribute length conflicts with expected length (RFC 4271 Section 6.3)
243    AttributeLengthError {
244        attr_type: AttrType,
245        expected_length: Option<usize>,
246        actual_length: usize,
247    },
248    /// Missing well-known mandatory attribute (RFC 4271 Section 6.3)
249    MissingWellKnownAttribute { attr_type: AttrType },
250    /// Unrecognized well-known attribute (RFC 4271 Section 6.3)
251    UnrecognizedWellKnownAttribute { attr_type_code: u8 },
252    /// Invalid origin attribute value (RFC 4271 Section 6.3)
253    InvalidOriginAttribute { value: u8 },
254    /// Invalid next hop attribute (RFC 4271 Section 6.3)
255    InvalidNextHopAttribute { reason: String },
256    /// Malformed AS_PATH attribute (RFC 4271 Section 6.3)
257    MalformedAsPath { reason: String },
258    /// Optional attribute error (RFC 4271 Section 6.3)
259    OptionalAttributeError { attr_type: AttrType, reason: String },
260    /// Attribute appears more than once (RFC 4271 Section 6.3)
261    DuplicateAttribute { attr_type: AttrType },
262    /// Invalid network field in NLRI (RFC 4271 Section 6.3)
263    InvalidNetworkField { reason: String },
264    /// Malformed attribute list (RFC 4271 Section 6.3)
265    MalformedAttributeList { reason: String },
266    /// Partial attribute with errors (RFC 7606)
267    PartialAttributeError { attr_type: AttrType, reason: String },
268    /// Malformed NLRI field (RFC 7606 §5.3). The UPDATE message was parseable
269    /// up to the NLRI section, but the NLRI itself contained syntactic errors.
270    /// Per RFC 7606, the recommended action is "treat-as-withdrawal": all
271    /// routes carried in the NLRI section should be withdrawn.
272    ///
273    /// `nlri_type` distinguishes between the standard (IPv4 unicast) NLRI
274    /// field and the multiprotocol NLRI carried inside MP_REACH/MP_UNREACH
275    /// path attributes. `raw_bytes` preserves the offending NLRI bytes so
276    /// callers can inspect or export them without re-encoding.
277    MalformedNlri {
278        /// "withdrawn", "announced", "mp_reach", or "mp_unreach"
279        nlri_type: &'static str,
280        /// Human-readable description of the parse error
281        reason: String,
282        /// Raw NLRI bytes as they appeared in the UPDATE message
283        raw_bytes: Vec<u8>,
284    },
285    /// ROUTE-REFRESH message subtype other than 0, 1, or 2 (RFC 7313
286    /// Section 5). A live speaker MUST ignore such a message and SHOULD log
287    /// an error; the parser retains it raw and reports this warning.
288    UnknownRouteRefreshSubtype { subtype: u8 },
289    /// BoRR/EoRR ROUTE-REFRESH message (RFC 7313 subtype 1 or 2) whose body
290    /// is not exactly 4 bytes. On the wire this is a fatal "Invalid Message
291    /// Length" NOTIFICATION when the Enhanced Route Refresh capability was
292    /// negotiated; MRT data carries no session context, so the parser
293    /// retains the message and reports this warning instead.
294    InvalidRouteRefreshLength {
295        subtype: u8,
296        /// Message body length excluding the 19-byte BGP header
297        length: usize,
298    },
299}
300
301impl Display for BgpValidationWarning {
302    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
303        match self {
304            BgpValidationWarning::AttributeFlagsError { attr_type, expected_flags, actual_flags } => {
305                write!(f, "Attribute flags error for {attr_type:?}: expected 0x{expected_flags:02x}, got 0x{actual_flags:02x}")
306            }
307            BgpValidationWarning::AttributeLengthError { attr_type, expected_length, actual_length } => {
308                match expected_length {
309                    Some(expected) => write!(f, "Attribute length error for {attr_type:?}: expected {expected}, got {actual_length}"),
310                    None => write!(f, "Attribute length error for {attr_type:?}: invalid length {actual_length}"),
311                }
312            }
313            BgpValidationWarning::MissingWellKnownAttribute { attr_type } => {
314                write!(f, "Missing well-known mandatory attribute: {attr_type:?}")
315            }
316            BgpValidationWarning::UnrecognizedWellKnownAttribute { attr_type_code } => {
317                write!(f, "Unrecognized well-known attribute: type code {attr_type_code}")
318            }
319            BgpValidationWarning::InvalidOriginAttribute { value } => {
320                write!(f, "Invalid origin attribute value: {value}")
321            }
322            BgpValidationWarning::InvalidNextHopAttribute { reason } => {
323                write!(f, "Invalid next hop attribute: {reason}")
324            }
325            BgpValidationWarning::MalformedAsPath { reason } => {
326                write!(f, "Malformed AS_PATH: {reason}")
327            }
328            BgpValidationWarning::OptionalAttributeError { attr_type, reason } => {
329                write!(f, "Optional attribute error for {attr_type:?}: {reason}")
330            }
331            BgpValidationWarning::DuplicateAttribute { attr_type } => {
332                write!(f, "Duplicate attribute: {attr_type:?}")
333            }
334            BgpValidationWarning::InvalidNetworkField { reason } => {
335                write!(f, "Invalid network field: {reason}")
336            }
337            BgpValidationWarning::MalformedAttributeList { reason } => {
338                write!(f, "Malformed attribute list: {reason}")
339            }
340            BgpValidationWarning::PartialAttributeError { attr_type, reason } => {
341                write!(f, "Partial attribute error for {attr_type:?}: {reason}")
342            }
343            BgpValidationWarning::MalformedNlri { nlri_type, reason, .. } => {
344                write!(f, "Malformed NLRI ({nlri_type}): {reason}")
345            }
346            BgpValidationWarning::UnknownRouteRefreshSubtype { subtype } => {
347                write!(f, "Unknown ROUTE-REFRESH message subtype: {subtype}")
348            }
349            BgpValidationWarning::InvalidRouteRefreshLength { subtype, length } => {
350                write!(f, "Invalid ROUTE-REFRESH message length for subtype {subtype}: body is {length} bytes, expected 4")
351            }
352        }
353    }
354}
355
356/// Result type for BGP attribute parsing that includes validation warnings
357#[derive(Debug, Clone)]
358pub struct BgpValidationResult<T> {
359    pub value: T,
360    pub warnings: Vec<BgpValidationWarning>,
361}
362
363impl<T> BgpValidationResult<T> {
364    pub fn new(value: T) -> Self {
365        Self {
366            value,
367            warnings: Vec::new(),
368        }
369    }
370
371    pub fn with_warnings(value: T, warnings: Vec<BgpValidationWarning>) -> Self {
372        Self { value, warnings }
373    }
374
375    pub fn add_warning(&mut self, warning: BgpValidationWarning) {
376        self.warnings.push(warning);
377    }
378
379    pub fn has_warnings(&self) -> bool {
380        !self.warnings.is_empty()
381    }
382}