Skip to main content

rustbgpd_wire/
open.rs

1use std::net::Ipv4Addr;
2
3use bytes::{Buf, BufMut, BytesMut};
4
5use crate::capability::{Capability, decode_optional_parameters, encode_optional_parameters};
6use crate::constants::{BGP_VERSION, HEADER_LEN, MAX_MESSAGE_LEN};
7use crate::error::{DecodeError, EncodeError};
8use crate::header::{BgpHeader, MessageType};
9
10/// A decoded BGP OPEN message (RFC 4271 §4.2).
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct OpenMessage {
13    /// BGP version (must be 4).
14    pub version: u8,
15    /// 2-byte AS from the OPEN wire format. May be `AS_TRANS` (23456)
16    /// if the speaker's ASN > 65535 — the true ASN is in the
17    /// `FourOctetAs` capability.
18    pub my_as: u16,
19    /// Proposed hold time in seconds (0 = no keepalives, or >= 3).
20    pub hold_time: u16,
21    /// BGP Identifier (router ID).
22    pub bgp_identifier: Ipv4Addr,
23    /// Capabilities from optional parameters.
24    pub capabilities: Vec<Capability>,
25}
26
27impl OpenMessage {
28    /// Decode an OPEN message body from a buffer.
29    /// The header must already be consumed; `body_len` is
30    /// `header.length - HEADER_LEN`.
31    ///
32    /// # Errors
33    ///
34    /// Returns a [`DecodeError`] if the body is too short, the version is
35    /// unsupported, or optional parameters are malformed.
36    pub fn decode(buf: &mut impl Buf, body_len: usize) -> Result<Self, DecodeError> {
37        // OPEN body: version(1) + AS(2) + hold(2) + id(4) + opt_len(1) = 10 minimum
38        if body_len < 10 {
39            return Err(DecodeError::MalformedField {
40                message_type: "OPEN",
41                detail: format!("body too short: {body_len} bytes (need at least 10)"),
42            });
43        }
44
45        if buf.remaining() < body_len {
46            return Err(DecodeError::Incomplete {
47                needed: body_len,
48                available: buf.remaining(),
49            });
50        }
51
52        let version = buf.get_u8();
53        if version != BGP_VERSION {
54            return Err(DecodeError::UnsupportedVersion { version });
55        }
56
57        let my_as = buf.get_u16();
58        let hold_time = buf.get_u16();
59        let bgp_identifier = Ipv4Addr::from(buf.get_u32());
60        let opt_params_len = buf.get_u8();
61
62        // Validate opt_params_len fits within the remaining body
63        let expected_body = 10 + usize::from(opt_params_len);
64        if expected_body != body_len {
65            return Err(DecodeError::MalformedField {
66                message_type: "OPEN",
67                detail: format!(
68                    "optional parameters length {opt_params_len} inconsistent \
69                     with body length {body_len} (expected {expected_body})"
70                ),
71            });
72        }
73
74        let capabilities = decode_optional_parameters(buf, opt_params_len)?;
75
76        Ok(Self {
77            version,
78            my_as,
79            hold_time,
80            bgp_identifier,
81            capabilities,
82        })
83    }
84
85    /// Encode a complete OPEN message (header + body) into a buffer.
86    ///
87    /// # Errors
88    ///
89    /// Returns an [`EncodeError`] if the optional parameters exceed 255 bytes
90    /// or the total message exceeds the maximum BGP message size.
91    pub fn encode(&self, buf: &mut impl BufMut) -> Result<(), EncodeError> {
92        // Calculate optional parameters size
93        let mut opt_params = BytesMut::new();
94        encode_optional_parameters(&self.capabilities, &mut opt_params)?;
95        let opt_params_len = opt_params.len();
96
97        if opt_params_len > 255 {
98            return Err(EncodeError::ValueOutOfRange {
99                field: "optional_parameters_length",
100                value: opt_params_len.to_string(),
101            });
102        }
103
104        let total_len = HEADER_LEN + 10 + opt_params_len;
105        if total_len > usize::from(MAX_MESSAGE_LEN) {
106            return Err(EncodeError::MessageTooLong { size: total_len });
107        }
108
109        // Header
110        let header = BgpHeader {
111            #[expect(clippy::cast_possible_truncation)]
112            length: total_len as u16,
113            message_type: MessageType::Open,
114        };
115        header.encode(buf);
116
117        // Body
118        buf.put_u8(self.version);
119        buf.put_u16(self.my_as);
120        buf.put_u16(self.hold_time);
121        buf.put_u32(u32::from(self.bgp_identifier));
122        #[expect(clippy::cast_possible_truncation)]
123        buf.put_u8(opt_params_len as u8);
124        buf.put_slice(&opt_params);
125
126        Ok(())
127    }
128
129    /// Total encoded size in bytes.
130    #[must_use]
131    pub fn encoded_len(&self) -> usize {
132        let cap_size: usize = self.capabilities.iter().map(Capability::encoded_len).sum();
133        // opt params wrapper: type(1) + len(1) per parameter block
134        let opt_params_overhead = if self.capabilities.is_empty() { 0 } else { 2 };
135        HEADER_LEN + 10 + opt_params_overhead + cap_size
136    }
137
138    /// Extract the 4-byte ASN from capabilities, if advertised.
139    /// Falls back to `my_as` (2-byte) if no `FourOctetAs` capability.
140    #[must_use]
141    pub fn four_byte_as(&self) -> u32 {
142        for cap in &self.capabilities {
143            if let Capability::FourOctetAs { asn } = cap {
144                return *asn;
145            }
146        }
147        u32::from(self.my_as)
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154    use crate::capability::Afi;
155    use crate::capability::Safi;
156    use crate::constants::MAX_MESSAGE_LEN;
157
158    fn minimal_open() -> OpenMessage {
159        OpenMessage {
160            version: BGP_VERSION,
161            my_as: 65001,
162            hold_time: 90,
163            bgp_identifier: Ipv4Addr::new(10, 0, 0, 1),
164            capabilities: vec![],
165        }
166    }
167
168    #[test]
169    fn encode_decode_minimal_open() {
170        let original = minimal_open();
171        let mut encoded = BytesMut::with_capacity(64);
172        original.encode(&mut encoded).unwrap();
173
174        let mut bytes = encoded.freeze();
175        let header = BgpHeader::decode(&mut bytes, MAX_MESSAGE_LEN).unwrap();
176        assert_eq!(header.message_type, MessageType::Open);
177        assert_eq!(header.length, 29); // 19 + 10, no caps
178
179        let body_len = usize::from(header.length) - HEADER_LEN;
180        let decoded = OpenMessage::decode(&mut bytes, body_len).unwrap();
181        assert_eq!(original, decoded);
182    }
183
184    #[test]
185    fn encode_decode_with_capabilities() {
186        let original = OpenMessage {
187            version: BGP_VERSION,
188            my_as: 23456, // AS_TRANS
189            hold_time: 90,
190            bgp_identifier: Ipv4Addr::new(10, 0, 0, 1),
191            capabilities: vec![
192                Capability::MultiProtocol {
193                    afi: Afi::Ipv4,
194                    safi: Safi::Unicast,
195                },
196                Capability::FourOctetAs { asn: 4_200_000_001 },
197            ],
198        };
199
200        let mut encoded = BytesMut::with_capacity(128);
201        original.encode(&mut encoded).unwrap();
202
203        let mut bytes = encoded.freeze();
204        let header = BgpHeader::decode(&mut bytes, MAX_MESSAGE_LEN).unwrap();
205        let body_len = usize::from(header.length) - HEADER_LEN;
206        let decoded = OpenMessage::decode(&mut bytes, body_len).unwrap();
207        assert_eq!(original, decoded);
208    }
209
210    #[test]
211    fn four_byte_as_extraction() {
212        let open = OpenMessage {
213            version: BGP_VERSION,
214            my_as: 23456,
215            hold_time: 90,
216            bgp_identifier: Ipv4Addr::new(10, 0, 0, 1),
217            capabilities: vec![Capability::FourOctetAs { asn: 4_200_000_001 }],
218        };
219        assert_eq!(open.four_byte_as(), 4_200_000_001);
220    }
221
222    #[test]
223    fn four_byte_as_fallback_to_my_as() {
224        let open = minimal_open();
225        assert_eq!(open.four_byte_as(), 65001);
226    }
227
228    #[test]
229    fn reject_bad_version() {
230        let body: &[u8] = &[
231            3, // version 3 (bad)
232            0xFD, 0xE9, // AS 65001
233            0, 90, // hold time
234            10, 0, 0, 1, // router ID
235            0, // opt params len
236        ];
237        let mut buf = bytes::Bytes::copy_from_slice(body);
238        assert!(matches!(
239            OpenMessage::decode(&mut buf, 10),
240            Err(DecodeError::UnsupportedVersion { version: 3 })
241        ));
242    }
243
244    #[test]
245    fn reject_body_too_short() {
246        let body: &[u8] = &[4, 0, 1]; // only 3 bytes
247        let mut buf = bytes::Bytes::copy_from_slice(body);
248        assert!(matches!(
249            OpenMessage::decode(&mut buf, 3),
250            Err(DecodeError::MalformedField { .. })
251        ));
252    }
253
254    #[test]
255    fn reject_inconsistent_opt_params_length() {
256        let body: &[u8] = &[
257            4, // version
258            0xFD, 0xE9, // AS 65001
259            0, 90, // hold time
260            10, 0, 0, 1, // router ID
261            5, // opt params len = 5, but body_len says 10
262        ];
263        let mut buf = bytes::Bytes::copy_from_slice(body);
264        assert!(matches!(
265            OpenMessage::decode(&mut buf, 10),
266            Err(DecodeError::MalformedField { .. })
267        ));
268    }
269
270    #[test]
271    fn unknown_capabilities_preserved() {
272        let original = OpenMessage {
273            version: BGP_VERSION,
274            my_as: 65001,
275            hold_time: 90,
276            bgp_identifier: Ipv4Addr::new(10, 0, 0, 1),
277            capabilities: vec![Capability::Unknown {
278                code: 128,
279                data: bytes::Bytes::from_static(&[0xDE, 0xAD]),
280            }],
281        };
282
283        let mut encoded = BytesMut::with_capacity(64);
284        original.encode(&mut encoded).unwrap();
285
286        let mut bytes = encoded.freeze();
287        let header = BgpHeader::decode(&mut bytes, MAX_MESSAGE_LEN).unwrap();
288        let body_len = usize::from(header.length) - HEADER_LEN;
289        let decoded = OpenMessage::decode(&mut bytes, body_len).unwrap();
290        assert_eq!(original, decoded);
291    }
292}