Skip to main content

rustbgpd_wire/
notification_msg.rs

1use bytes::{Buf, BufMut, Bytes};
2
3use crate::constants::HEADER_LEN;
4use crate::error::{DecodeError, EncodeError};
5use crate::header::{BgpHeader, MessageType};
6use crate::notification::NotificationCode;
7
8/// A decoded BGP NOTIFICATION message (RFC 4271 §4.5).
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct NotificationMessage {
11    /// Error code identifying the type of error.
12    pub code: NotificationCode,
13    /// Error subcode providing more specific information.
14    pub subcode: u8,
15    /// Diagnostic data (contents depend on code/subcode).
16    pub data: Bytes,
17}
18
19impl NotificationMessage {
20    /// Decode a NOTIFICATION message body from a buffer.
21    /// The header must already be consumed; `body_len` is
22    /// `header.length - HEADER_LEN`.
23    ///
24    /// # Errors
25    ///
26    /// Returns [`DecodeError::MalformedField`] if the body is too short, or
27    /// [`DecodeError::Incomplete`] if the buffer has fewer bytes than `body_len`.
28    pub fn decode(buf: &mut impl Buf, body_len: usize) -> Result<Self, DecodeError> {
29        if body_len < 2 {
30            return Err(DecodeError::MalformedField {
31                message_type: "NOTIFICATION",
32                detail: format!("body too short: {body_len} bytes (need at least 2)"),
33            });
34        }
35
36        if buf.remaining() < body_len {
37            return Err(DecodeError::Incomplete {
38                needed: body_len,
39                available: buf.remaining(),
40            });
41        }
42
43        let code_byte = buf.get_u8();
44        let code = NotificationCode::from_u8(code_byte);
45
46        let subcode = buf.get_u8();
47
48        let data_len = body_len - 2;
49        let data = buf.copy_to_bytes(data_len);
50
51        Ok(Self {
52            code,
53            subcode,
54            data,
55        })
56    }
57
58    /// Encode a NOTIFICATION message body into a buffer.
59    /// Does NOT write the header — call this after encoding the header.
60    pub fn encode_body(&self, buf: &mut impl BufMut) {
61        buf.put_u8(self.code.as_u8());
62        buf.put_u8(self.subcode);
63        buf.put_slice(&self.data);
64    }
65
66    /// Encode a complete NOTIFICATION message (header + body).
67    ///
68    /// # Errors
69    ///
70    /// Returns [`EncodeError::MessageTooLong`] if the encoded message exceeds
71    /// the maximum BGP message size.
72    pub fn encode(&self, buf: &mut impl BufMut) -> Result<(), EncodeError> {
73        let total_len = HEADER_LEN + 2 + self.data.len();
74        if total_len > usize::from(crate::constants::MAX_MESSAGE_LEN) {
75            return Err(EncodeError::MessageTooLong { size: total_len });
76        }
77
78        let header = BgpHeader {
79            #[expect(clippy::cast_possible_truncation)]
80            length: total_len as u16,
81            message_type: MessageType::Notification,
82        };
83        header.encode(buf);
84        self.encode_body(buf);
85        Ok(())
86    }
87
88    /// Total encoded size in bytes.
89    #[must_use]
90    pub fn encoded_len(&self) -> usize {
91        HEADER_LEN + 2 + self.data.len()
92    }
93
94    /// Convenience: build a NOTIFICATION from code, subcode, and data.
95    #[must_use]
96    pub fn new(code: NotificationCode, subcode: u8, data: Bytes) -> Self {
97        Self {
98            code,
99            subcode,
100            data,
101        }
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use bytes::BytesMut;
108
109    use super::*;
110    use crate::constants::MAX_MESSAGE_LEN;
111
112    #[test]
113    fn decode_notification_no_data() {
114        // code=6 (Cease), subcode=2 (Administrative Shutdown), no data
115        let body: &[u8] = &[6, 2];
116        let mut buf = Bytes::copy_from_slice(body);
117        let msg = NotificationMessage::decode(&mut buf, 2).unwrap();
118        assert_eq!(msg.code, NotificationCode::Cease);
119        assert_eq!(msg.subcode, 2);
120        assert!(msg.data.is_empty());
121    }
122
123    #[test]
124    fn decode_notification_with_data() {
125        let body: &[u8] = &[1, 2, 0x00, 0x0F]; // Header Error, Bad Length, data=[0,15]
126        let mut buf = Bytes::copy_from_slice(body);
127        let msg = NotificationMessage::decode(&mut buf, 4).unwrap();
128        assert_eq!(msg.code, NotificationCode::MessageHeader);
129        assert_eq!(msg.subcode, 2);
130        assert_eq!(msg.data.as_ref(), &[0x00, 0x0F]);
131    }
132
133    #[test]
134    fn reject_body_too_short() {
135        let body: &[u8] = &[1];
136        let mut buf = Bytes::copy_from_slice(body);
137        assert!(NotificationMessage::decode(&mut buf, 1).is_err());
138    }
139
140    #[test]
141    fn encode_decode_roundtrip() {
142        let original = NotificationMessage::new(
143            NotificationCode::OpenMessage,
144            6, // Unacceptable Hold Time
145            Bytes::from_static(&[0x00, 0x02]),
146        );
147
148        let mut encoded = BytesMut::with_capacity(original.encoded_len());
149        original.encode(&mut encoded).unwrap();
150
151        // Decode: skip header, then decode body
152        let mut bytes = encoded.freeze();
153        let header = BgpHeader::decode(&mut bytes, MAX_MESSAGE_LEN).unwrap();
154        assert_eq!(header.message_type, MessageType::Notification);
155
156        let decoded =
157            NotificationMessage::decode(&mut bytes, usize::from(header.length) - HEADER_LEN)
158                .unwrap();
159        assert_eq!(original, decoded);
160    }
161
162    #[test]
163    fn reject_message_too_long() {
164        let msg = NotificationMessage::new(
165            NotificationCode::Cease,
166            0,
167            Bytes::from(vec![0u8; 4096]), // way too big
168        );
169        let mut buf = BytesMut::with_capacity(5000);
170        assert!(matches!(
171            msg.encode(&mut buf),
172            Err(EncodeError::MessageTooLong { .. })
173        ));
174    }
175}