Skip to main content

bgpkit_parser/parser/bmp/messages/
peer_up_notification.rs

1use crate::bgp::parse_bgp_message;
2use crate::models::capabilities::BgpCapabilityType;
3use crate::models::*;
4use crate::parser::bmp::error::ParserBmpError;
5use crate::parser::bmp::messages::BmpPeerType;
6use crate::parser::ReadUtils;
7use bytes::{Buf, Bytes};
8use log::warn;
9use num_enum::{FromPrimitive, IntoPrimitive};
10use std::net::IpAddr;
11
12#[derive(Debug, PartialEq, Clone)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub struct PeerUpNotification {
15    pub local_addr: IpAddr,
16    pub local_port: u16,
17    pub remote_port: u16,
18    pub sent_open: BgpMessage,
19    pub received_open: BgpMessage,
20    pub tlvs: Vec<PeerUpNotificationTlv>,
21}
22
23/// BMP Peer Up Message TLV Type
24///
25/// <https://www.iana.org/assignments/bmp-parameters/bmp-parameters.xhtml#peer-up-message-tlvs>
26///
27/// RFC 9736 created an independent namespace for Peer Up Information TLVs, separate from
28/// the Initiation message namespace. Per the new registry, types 1, 2, and 65535 are reserved.
29/// The SysDescr and SysName variants are retained for backward compatibility with pre-RFC 9736
30/// implementations.
31#[derive(Debug, FromPrimitive, IntoPrimitive, PartialEq, Clone, Copy)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33#[repr(u16)]
34pub enum PeerUpTlvType {
35    String = 0,
36    /// Reserved per RFC 9736, retained for backward compatibility.
37    SysDescr = 1,
38    /// Reserved per RFC 9736, retained for backward compatibility.
39    SysName = 2,
40    VrTableName = 3,
41    AdminLabel = 4,
42    #[num_enum(catch_all)]
43    Unknown(u16) = 65535,
44}
45
46#[derive(Debug, PartialEq, Clone)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48pub struct PeerUpNotificationTlv {
49    pub info_type: PeerUpTlvType,
50    pub info_len: u16,
51    pub info_value: String,
52}
53
54pub fn parse_peer_up_notification(
55    data: &mut Bytes,
56    afi: &Afi,
57    asn_len: &AsnLength,
58    peer_type: Option<&BmpPeerType>,
59) -> Result<PeerUpNotification, ParserBmpError> {
60    let local_addr: IpAddr = match afi {
61        Afi::Ipv4 => {
62            data.has_n_remaining(12)?;
63            data.advance(12);
64            let ip = data.read_ipv4_address()?;
65            ip.into()
66        }
67        Afi::Ipv6 => data.read_ipv6_address()?.into(),
68        Afi::LinkState => {
69            // Link-State doesn't use traditional IP addresses for local address
70            // Use IPv4 zero address as placeholder
71            data.has_n_remaining(12)?;
72            data.advance(12);
73            std::net::Ipv4Addr::new(0, 0, 0, 0).into()
74        }
75    };
76
77    let local_port = data.read_u16()?;
78    let remote_port = data.read_u16()?;
79
80    // Extract first BGP message with proper boundary
81    data.has_n_remaining(19)?; // BGP header
82    let bgp1_length = u16::from_be_bytes([data[16], data[17]]) as usize;
83    data.has_n_remaining(bgp1_length)?;
84    let mut bgp1_data = data.split_to(bgp1_length);
85    let sent_open = parse_bgp_message(&mut bgp1_data, false, asn_len)?;
86
87    // Extract second BGP message with proper boundary
88    data.has_n_remaining(19)?; // BGP header
89    let bgp2_length = u16::from_be_bytes([data[16], data[17]]) as usize;
90    data.has_n_remaining(bgp2_length)?;
91    let mut bgp2_data = data.split_to(bgp2_length);
92    let received_open = parse_bgp_message(&mut bgp2_data, false, asn_len)?;
93
94    // RFC 9069: For Local RIB, the BGP OPEN messages MUST be fabricated
95    if let Some(BmpPeerType::LocalRib) = peer_type {
96        // Validate that the OPEN messages contain appropriate capabilities for Local RIB
97        if let BgpMessage::Open(ref open_msg) = &sent_open {
98            let has_multiprotocol_capability = open_msg.opt_params.iter().any(|param| {
99                if let ParamValue::Capacities(caps) = &param.param_value {
100                    caps.iter()
101                        .any(|cap| cap.ty == BgpCapabilityType::MULTIPROTOCOL_EXTENSIONS_FOR_BGP_4)
102                } else {
103                    false
104                }
105            });
106            if !has_multiprotocol_capability {
107                warn!("RFC 9069: Local RIB peer up notification should include multiprotocol capabilities in fabricated OPEN messages (parsing Peer Up Notification)");
108            }
109        }
110    }
111
112    let mut tlvs = vec![];
113    let mut has_vr_table_name = false;
114
115    while data.remaining() >= 4 {
116        let info_type = PeerUpTlvType::from(data.read_u16()?);
117        let info_len = data.read_u16()?;
118        let info_value = data.read_n_bytes_to_string(info_len as usize)?;
119
120        // RFC 9069: VrTableName TLV validation for Local RIB
121        if let Some(BmpPeerType::LocalRib) = peer_type {
122            if info_type == PeerUpTlvType::VrTableName {
123                has_vr_table_name = true;
124                // RFC 9069: VrTableName MUST be UTF-8 string of 1-255 bytes
125                if info_value.is_empty() || info_value.len() > 255 {
126                    warn!(
127                        "RFC 9069: VrTableName TLV length must be 1-255 bytes, found {} bytes (parsing Peer Up Notification)",
128                        info_value.len()
129                    );
130                }
131            }
132        }
133
134        tlvs.push(PeerUpNotificationTlv {
135            info_type,
136            info_len,
137            info_value,
138        })
139    }
140
141    // RFC 9069: Local RIB instances SHOULD include VrTableName TLV
142    if let Some(BmpPeerType::LocalRib) = peer_type {
143        if !has_vr_table_name {
144            warn!("RFC 9069: Local RIB peer up notification should include VrTableName TLV (parsing Peer Up Notification)");
145        }
146    }
147    Ok(PeerUpNotification {
148        local_addr,
149        local_port,
150        remote_port,
151        sent_open,
152        received_open,
153        tlvs,
154    })
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160    use crate::models::capabilities::{BgpCapabilityType, MultiprotocolExtensionsCapability};
161    use bytes::BytesMut;
162    use std::cell::RefCell;
163    use std::net::{IpAddr, Ipv4Addr};
164    use std::sync::{Arc, Mutex, Once};
165
166    thread_local! {
167        static WARNING_CAPTURE: RefCell<Option<Arc<Mutex<Vec<String>>>>> = const { RefCell::new(None) };
168    }
169
170    struct WarningCaptureGuard {
171        warnings: Arc<Mutex<Vec<String>>>,
172    }
173
174    impl WarningCaptureGuard {
175        fn warnings(&self) -> &Arc<Mutex<Vec<String>>> {
176            &self.warnings
177        }
178    }
179
180    impl Drop for WarningCaptureGuard {
181        fn drop(&mut self) {
182            clear_warning_logger();
183        }
184    }
185
186    #[test]
187    fn test_parse_peer_up_notification() {
188        let mut data = BytesMut::new();
189        // Assuming setup for test where local address is "10.1.1.1" IPv4
190        // local port is 8000, remote port is 9000. Adjust accordingly.
191        data.extend_from_slice(&[
192            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x01,
193            0x01, 0x01,
194        ]);
195        data.extend_from_slice(&[0x1F, 0x40]); // 8000 in network byte order
196        data.extend_from_slice(&[0x23, 0x28]); // 9000 in network byte order
197
198        let bgp_open_message = crate::models::BgpMessage::Open(BgpOpenMessage {
199            version: 0,
200            asn: Default::default(),
201            hold_time: 0,
202            bgp_identifier: Ipv4Addr::new(0, 0, 0, 0),
203            extended_length: false,
204            opt_params: vec![],
205        });
206        let bgp_open_message_bytes = bgp_open_message.encode(AsnLength::Bits32);
207        data.extend_from_slice(&bgp_open_message_bytes);
208        data.extend_from_slice(&bgp_open_message_bytes);
209
210        // add tlv
211        data.extend_from_slice(&[0x00, 0x01]); // info_type
212        data.extend_from_slice(&[0x00, 0x02]); // info_len
213        data.extend_from_slice(&[0x00, 0x03]); // info_value
214
215        let afi = Afi::Ipv4;
216        let asn_len = AsnLength::Bits32;
217
218        let result = parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, None);
219
220        match result {
221            Ok(peer_notification) => {
222                assert_eq!(
223                    peer_notification.local_addr,
224                    IpAddr::V4(std::net::Ipv4Addr::new(10, 1, 1, 1))
225                );
226                assert_eq!(peer_notification.local_port, 8000);
227                assert_eq!(peer_notification.remote_port, 9000);
228
229                // Continue to check other values from peer_notification like sent_open, received_open, tlvs
230                let tlv = peer_notification.tlvs.first().unwrap();
231                assert_eq!(tlv.info_type, PeerUpTlvType::SysDescr);
232                assert_eq!(tlv.info_len, 2);
233                assert_eq!(tlv.info_value, "\u{0}\u{3}");
234            }
235            Err(_) => {
236                panic!("parse_peer_up_notification should return Ok");
237            }
238        }
239    }
240
241    // Helper function to set up warning capture for tests
242    fn setup_warning_logger() -> WarningCaptureGuard {
243        use log::{Level, Record};
244
245        static LOGGER_INIT: Once = Once::new();
246
247        struct TestLogger;
248
249        impl log::Log for TestLogger {
250            fn enabled(&self, metadata: &log::Metadata) -> bool {
251                metadata.level() <= Level::Warn
252            }
253
254            fn log(&self, record: &Record) {
255                if record.level() <= Level::Warn {
256                    WARNING_CAPTURE.with(|capture| {
257                        if let Some(warnings) = capture.borrow().as_ref() {
258                            match warnings.lock() {
259                                Ok(mut warnings) => warnings.push(record.args().to_string()),
260                                Err(poisoned) => {
261                                    poisoned.into_inner().push(record.args().to_string());
262                                }
263                            }
264                        }
265                    });
266                }
267            }
268
269            fn flush(&self) {}
270        }
271
272        LOGGER_INIT.call_once(|| {
273            log::set_boxed_logger(Box::new(TestLogger)).unwrap();
274            log::set_max_level(log::LevelFilter::Warn);
275        });
276
277        let warnings = Arc::new(Mutex::new(Vec::new()));
278        WARNING_CAPTURE.with(|capture| {
279            *capture.borrow_mut() = Some(warnings.clone());
280        });
281        WarningCaptureGuard { warnings }
282    }
283
284    fn clear_warning_logger() {
285        WARNING_CAPTURE.with(|capture| {
286            *capture.borrow_mut() = None;
287        });
288    }
289
290    // Note: These tests verify that the parser handles LocalRib validation correctly.
291    // The actual warning messages are logged via the `log` crate and would appear
292    // in production use. For testing purposes, we verify that parsing succeeds
293    // and the code paths are exercised.
294
295    #[test]
296    fn test_parse_peer_up_notification_no_warnings() {
297        let warnings = setup_warning_logger();
298
299        // Regression test: This test creates a scenario with two consecutive BGP OPEN messages
300        // that would have triggered BGP length warnings in the old implementation.
301        // The new implementation should parse cleanly without warnings.
302
303        let mut data = BytesMut::new();
304
305        // Local address (IPv4): 192.168.1.1 + ports
306        data.extend_from_slice(&[
307            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
308            0x01, 0x01, // 192.168.1.1
309            0x00, 0xB3, // local port 179
310            0x00, 0xB3, // remote port 179
311        ]);
312
313        // Create two different BGP OPEN messages that will be concatenated
314        // This test verifies that the boundary extraction fix prevents BGP length warnings
315
316        // First BGP OPEN message
317        let bgp1 = crate::models::BgpMessage::Open(BgpOpenMessage {
318            version: 4,
319            asn: crate::models::Asn::new_16bit(65001),
320            hold_time: 180,
321            bgp_identifier: Ipv4Addr::new(192, 168, 1, 1),
322            extended_length: false,
323            opt_params: vec![],
324        });
325        let bgp1_bytes = bgp1.encode(AsnLength::Bits32);
326
327        // Second BGP OPEN message
328        let bgp2 = crate::models::BgpMessage::Open(BgpOpenMessage {
329            version: 4,
330            asn: crate::models::Asn::new_16bit(65002),
331            hold_time: 90,
332            bgp_identifier: Ipv4Addr::new(192, 168, 1, 2),
333            extended_length: false,
334            opt_params: vec![],
335        });
336        let bgp2_bytes = bgp2.encode(AsnLength::Bits32);
337
338        // Add both BGP messages consecutively
339        data.extend_from_slice(&bgp1_bytes);
340        data.extend_from_slice(&bgp2_bytes);
341
342        // Add a TLV (String type, length 8, "TestNode")
343        data.extend_from_slice(&[
344            0x00, 0x00, // String TLV type
345            0x00, 0x08, // length 8
346        ]);
347        data.extend_from_slice(b"TestNode");
348
349        let afi = Afi::Ipv4;
350        let asn_len = AsnLength::Bits32;
351
352        let result = parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, None);
353
354        // Test should succeed without any BGP length warnings
355        assert!(result.is_ok(), "Parsing should succeed without warnings");
356
357        let peer_notification = result.unwrap();
358        assert_eq!(
359            peer_notification.local_addr,
360            IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))
361        );
362        assert_eq!(peer_notification.local_port, 179);
363        assert_eq!(peer_notification.remote_port, 179);
364
365        // Ensure both BGP messages were parsed correctly with different ASNs
366        if let crate::models::BgpMessage::Open(ref open1) = &peer_notification.sent_open {
367            assert_eq!(open1.asn, crate::models::Asn::new_16bit(65001));
368        } else {
369            panic!("sent_open should be an OPEN message");
370        }
371
372        if let crate::models::BgpMessage::Open(ref open2) = &peer_notification.received_open {
373            assert_eq!(open2.asn, crate::models::Asn::new_16bit(65002));
374        } else {
375            panic!("received_open should be an OPEN message");
376        }
377
378        // Verify TLV was parsed correctly
379        assert_eq!(peer_notification.tlvs.len(), 1);
380        assert_eq!(peer_notification.tlvs[0].info_type, PeerUpTlvType::String);
381        assert_eq!(peer_notification.tlvs[0].info_value, "TestNode");
382
383        // The main assertion: verify no warnings were logged
384        let captured_warnings = warnings.warnings().lock().unwrap();
385        assert!(
386            captured_warnings.is_empty(),
387            "Test should not produce warnings, but got: {:?}",
388            *captured_warnings
389        );
390    }
391
392    #[test]
393    fn test_parse_peer_up_insufficient_data_first_bgp() {
394        let mut data = BytesMut::new();
395
396        // Local address setup
397        data.extend_from_slice(&[
398            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x01,
399            0x01, 0x01,
400        ]);
401        data.extend_from_slice(&[0x1F, 0x40]); // local port
402        data.extend_from_slice(&[0x23, 0x28]); // remote port
403
404        // Add incomplete first BGP message (only header, no body)
405        data.extend_from_slice(&[
406            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // BGP marker
407            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
408            0x40, // Length = 64 bytes (but we won't provide 64 bytes)
409            0x01, // OPEN message type
410        ]);
411        // Missing the remaining 45 bytes of the BGP OPEN message
412
413        let afi = Afi::Ipv4;
414        let asn_len = AsnLength::Bits32;
415        let result = parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, None);
416
417        assert!(
418            result.is_err(),
419            "Should fail with insufficient data for first BGP message"
420        );
421    }
422
423    #[test]
424    fn test_parse_peer_up_insufficient_data_second_bgp() {
425        let mut data = BytesMut::new();
426
427        // Local address setup
428        data.extend_from_slice(&[
429            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x01,
430            0x01, 0x01,
431        ]);
432        data.extend_from_slice(&[0x1F, 0x40]); // local port
433        data.extend_from_slice(&[0x23, 0x28]); // remote port
434
435        // Add complete first BGP message
436        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
437            version: 4,
438            asn: Default::default(),
439            hold_time: 180,
440            bgp_identifier: Ipv4Addr::new(0, 0, 0, 0),
441            extended_length: false,
442            opt_params: vec![],
443        });
444        let bgp_bytes = bgp_open.encode(AsnLength::Bits32);
445        data.extend_from_slice(&bgp_bytes);
446
447        // Add incomplete second BGP message (only partial header)
448        data.extend_from_slice(&[
449            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // BGP marker
450            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
451            0x30, // Length = 48 bytes
452                  // Missing message type and body
453        ]);
454
455        let afi = Afi::Ipv4;
456        let asn_len = AsnLength::Bits32;
457        let result = parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, None);
458
459        assert!(
460            result.is_err(),
461            "Should fail with insufficient data for second BGP message"
462        );
463    }
464
465    #[test]
466    fn test_parse_peer_up_excess_data_in_tlvs() {
467        let mut data = BytesMut::new();
468
469        // Local address setup
470        data.extend_from_slice(&[
471            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x01,
472            0x01, 0x01,
473        ]);
474        data.extend_from_slice(&[0x1F, 0x40]); // local port
475        data.extend_from_slice(&[0x23, 0x28]); // remote port
476
477        // Add two complete BGP messages
478        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
479            version: 4,
480            asn: Default::default(),
481            hold_time: 180,
482            bgp_identifier: Ipv4Addr::new(0, 0, 0, 0),
483            extended_length: false,
484            opt_params: vec![],
485        });
486        let bgp_bytes = bgp_open.encode(AsnLength::Bits32);
487        data.extend_from_slice(&bgp_bytes);
488        data.extend_from_slice(&bgp_bytes);
489
490        // Add valid TLVs
491        data.extend_from_slice(&[0x00, 0x00]); // String TLV
492        data.extend_from_slice(&[0x00, 0x04]); // length 4
493        data.extend_from_slice(b"Test"); // value
494
495        data.extend_from_slice(&[0x00, 0x00]); // String TLV
496        data.extend_from_slice(&[0x00, 0x06]); // length 6
497        data.extend_from_slice(b"Router"); // value
498
499        // Add some random extra bytes (should be safely ignored due to TLV parsing logic)
500        data.extend_from_slice(&[0x00, 0x01, 0x02]); // Less than 4 bytes, should exit TLV parsing loop
501
502        let afi = Afi::Ipv4;
503        let asn_len = AsnLength::Bits32;
504        let result = parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, None);
505
506        // Should succeed - TLV parsing handles excess data gracefully
507        assert!(result.is_ok(), "Should handle excess data gracefully");
508
509        let peer_notification = result.unwrap();
510        assert_eq!(peer_notification.tlvs.len(), 2); // Should have parsed 2 TLVs
511        assert_eq!(peer_notification.tlvs[0].info_type, PeerUpTlvType::String);
512        assert_eq!(peer_notification.tlvs[0].info_value, "Test");
513        assert_eq!(peer_notification.tlvs[1].info_type, PeerUpTlvType::String);
514        assert_eq!(peer_notification.tlvs[1].info_value, "Router");
515    }
516
517    #[test]
518    fn test_local_rib_without_multiprotocol_capability() {
519        // This test verifies that LocalRib peer up notifications without multiprotocol
520        // capabilities are parsed successfully. In production, a warning would be logged.
521        let mut data = BytesMut::new();
522
523        // Local address setup
524        data.extend_from_slice(&[
525            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
526            0x01, 0x01, // 192.168.1.1
527            0x00, 0xB3, // local port 179
528            0x00, 0xB3, // remote port 179
529        ]);
530
531        // Create BGP OPEN message WITHOUT multiprotocol capabilities (RFC 9069 violation)
532        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
533            version: 4,
534            asn: crate::models::Asn::new_32bit(65001),
535            hold_time: 180,
536            bgp_identifier: Ipv4Addr::new(192, 168, 1, 1),
537            extended_length: false,
538            opt_params: vec![], // No capabilities
539        });
540        let bgp_bytes = bgp_open.encode(AsnLength::Bits32);
541        data.extend_from_slice(&bgp_bytes);
542        data.extend_from_slice(&bgp_bytes);
543
544        let afi = Afi::Ipv4;
545        let asn_len = AsnLength::Bits32;
546        let peer_type = BmpPeerType::LocalRib;
547
548        let result =
549            parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, Some(&peer_type));
550
551        assert!(result.is_ok(), "Parsing should succeed");
552        // In production, a warning would be logged about missing multiprotocol capability
553    }
554
555    #[test]
556    fn test_local_rib_with_multiprotocol_capability() {
557        // This test verifies that LocalRib peer up notifications WITH multiprotocol
558        // capabilities are parsed successfully without warnings.
559        let mut data = BytesMut::new();
560
561        // Local address setup
562        data.extend_from_slice(&[
563            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
564            0x01, 0x01, // 192.168.1.1
565            0x00, 0xB3, // local port 179
566            0x00, 0xB3, // remote port 179
567        ]);
568
569        // Create BGP OPEN message WITH multiprotocol capabilities (RFC 9069 compliant)
570        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
571            version: 4,
572            asn: crate::models::Asn::new_32bit(65001),
573            hold_time: 180,
574            bgp_identifier: Ipv4Addr::new(192, 168, 1, 1),
575            extended_length: false,
576            opt_params: vec![OptParam {
577                param_type: 2, // capability
578                param_len: 6,
579                param_value: ParamValue::Capacities(vec![Capability {
580                    ty: BgpCapabilityType::MULTIPROTOCOL_EXTENSIONS_FOR_BGP_4,
581                    value: CapabilityValue::MultiprotocolExtensions(
582                        MultiprotocolExtensionsCapability {
583                            afi: Afi::Ipv4,
584                            safi: Safi::Unicast,
585                        },
586                    ),
587                }]),
588            }],
589        });
590        let bgp_bytes = bgp_open.encode(AsnLength::Bits32);
591        data.extend_from_slice(&bgp_bytes);
592        data.extend_from_slice(&bgp_bytes);
593
594        let afi = Afi::Ipv4;
595        let asn_len = AsnLength::Bits32;
596        let peer_type = BmpPeerType::LocalRib;
597
598        let result =
599            parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, Some(&peer_type));
600
601        assert!(result.is_ok(), "Parsing should succeed");
602        // No warning should be logged when multiprotocol capability is present
603    }
604
605    #[test]
606    fn test_local_rib_without_vr_table_name_tlv() {
607        // This test verifies that LocalRib peer up notifications without VrTableName TLV
608        // are parsed successfully. In production, a warning would be logged.
609        let mut data = BytesMut::new();
610
611        // Local address setup
612        data.extend_from_slice(&[
613            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
614            0x01, 0x01, // 192.168.1.1
615            0x00, 0xB3, // local port 179
616            0x00, 0xB3, // remote port 179
617        ]);
618
619        // Create BGP OPEN message with capabilities
620        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
621            version: 4,
622            asn: crate::models::Asn::new_32bit(65001),
623            hold_time: 180,
624            bgp_identifier: Ipv4Addr::new(192, 168, 1, 1),
625            extended_length: false,
626            opt_params: vec![OptParam {
627                param_type: 2, // capability
628                param_len: 6,
629                param_value: ParamValue::Capacities(vec![Capability {
630                    ty: BgpCapabilityType::MULTIPROTOCOL_EXTENSIONS_FOR_BGP_4,
631                    value: CapabilityValue::MultiprotocolExtensions(
632                        MultiprotocolExtensionsCapability {
633                            afi: Afi::Ipv4,
634                            safi: Safi::Unicast,
635                        },
636                    ),
637                }]),
638            }],
639        });
640        let bgp_bytes = bgp_open.encode(AsnLength::Bits32);
641        data.extend_from_slice(&bgp_bytes);
642        data.extend_from_slice(&bgp_bytes);
643
644        // Add TLVs but NO VrTableName (RFC 9069 violation)
645        data.extend_from_slice(&[0x00, 0x00]); // String TLV
646        data.extend_from_slice(&[0x00, 0x04]); // length 4
647        data.extend_from_slice(b"Test");
648
649        let afi = Afi::Ipv4;
650        let asn_len = AsnLength::Bits32;
651        let peer_type = BmpPeerType::LocalRib;
652
653        let result =
654            parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, Some(&peer_type));
655
656        assert!(result.is_ok(), "Parsing should succeed");
657        // In production, a warning would be logged about missing VrTableName TLV
658    }
659
660    #[test]
661    fn test_local_rib_with_valid_vr_table_name_tlv() {
662        // This test verifies that LocalRib peer up notifications with valid VrTableName TLV
663        // are parsed successfully without warnings.
664        let mut data = BytesMut::new();
665
666        // Local address setup
667        data.extend_from_slice(&[
668            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
669            0x01, 0x01, // 192.168.1.1
670            0x00, 0xB3, // local port 179
671            0x00, 0xB3, // remote port 179
672        ]);
673
674        // Create BGP OPEN message with capabilities
675        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
676            version: 4,
677            asn: crate::models::Asn::new_32bit(65001),
678            hold_time: 180,
679            bgp_identifier: Ipv4Addr::new(192, 168, 1, 1),
680            extended_length: false,
681            opt_params: vec![OptParam {
682                param_type: 2, // capability
683                param_len: 6,
684                param_value: ParamValue::Capacities(vec![Capability {
685                    ty: BgpCapabilityType::MULTIPROTOCOL_EXTENSIONS_FOR_BGP_4,
686                    value: CapabilityValue::MultiprotocolExtensions(
687                        MultiprotocolExtensionsCapability {
688                            afi: Afi::Ipv4,
689                            safi: Safi::Unicast,
690                        },
691                    ),
692                }]),
693            }],
694        });
695        let bgp_bytes = bgp_open.encode(AsnLength::Bits32);
696        data.extend_from_slice(&bgp_bytes);
697        data.extend_from_slice(&bgp_bytes);
698
699        // Add VrTableName TLV with valid length (1-255 bytes)
700        data.extend_from_slice(&[0x00, 0x03]); // VrTableName TLV type
701        data.extend_from_slice(&[0x00, 0x08]); // length 8
702        data.extend_from_slice(b"LocalRIB");
703
704        let afi = Afi::Ipv4;
705        let asn_len = AsnLength::Bits32;
706        let peer_type = BmpPeerType::LocalRib;
707
708        let result =
709            parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, Some(&peer_type));
710
711        assert!(result.is_ok(), "Parsing should succeed");
712
713        let peer_notification = result.unwrap();
714        assert_eq!(peer_notification.tlvs.len(), 1);
715        assert_eq!(
716            peer_notification.tlvs[0].info_type,
717            PeerUpTlvType::VrTableName
718        );
719        assert_eq!(peer_notification.tlvs[0].info_value, "LocalRIB");
720        // No warnings should be logged with valid VrTableName
721    }
722
723    #[test]
724    fn test_local_rib_with_empty_vr_table_name() {
725        // This test verifies that LocalRib peer up notifications with empty VrTableName
726        // are parsed successfully. In production, a warning would be logged.
727        let mut data = BytesMut::new();
728
729        // Local address setup
730        data.extend_from_slice(&[
731            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
732            0x01, 0x01, // 192.168.1.1
733            0x00, 0xB3, // local port 179
734            0x00, 0xB3, // remote port 179
735        ]);
736
737        // Create BGP OPEN message with capabilities
738        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
739            version: 4,
740            asn: crate::models::Asn::new_32bit(65001),
741            hold_time: 180,
742            bgp_identifier: Ipv4Addr::new(192, 168, 1, 1),
743            extended_length: false,
744            opt_params: vec![OptParam {
745                param_type: 2, // capability
746                param_len: 6,
747                param_value: ParamValue::Capacities(vec![Capability {
748                    ty: BgpCapabilityType::MULTIPROTOCOL_EXTENSIONS_FOR_BGP_4,
749                    value: CapabilityValue::MultiprotocolExtensions(
750                        MultiprotocolExtensionsCapability {
751                            afi: Afi::Ipv4,
752                            safi: Safi::Unicast,
753                        },
754                    ),
755                }]),
756            }],
757        });
758        let bgp_bytes = bgp_open.encode(AsnLength::Bits32);
759        data.extend_from_slice(&bgp_bytes);
760        data.extend_from_slice(&bgp_bytes);
761
762        // Add VrTableName TLV with EMPTY value (RFC 9069 violation)
763        data.extend_from_slice(&[0x00, 0x03]); // VrTableName TLV type
764        data.extend_from_slice(&[0x00, 0x00]); // length 0 (empty)
765
766        let afi = Afi::Ipv4;
767        let asn_len = AsnLength::Bits32;
768        let peer_type = BmpPeerType::LocalRib;
769
770        let result =
771            parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, Some(&peer_type));
772
773        assert!(result.is_ok(), "Parsing should succeed");
774        // In production, a warning would be logged about invalid VrTableName length
775    }
776
777    #[test]
778    fn test_local_rib_with_oversized_vr_table_name() {
779        // This test verifies that LocalRib peer up notifications with oversized VrTableName
780        // are parsed successfully. In production, a warning would be logged.
781        let mut data = BytesMut::new();
782
783        // Local address setup
784        data.extend_from_slice(&[
785            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
786            0x01, 0x01, // 192.168.1.1
787            0x00, 0xB3, // local port 179
788            0x00, 0xB3, // remote port 179
789        ]);
790
791        // Create BGP OPEN message with capabilities
792        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
793            version: 4,
794            asn: crate::models::Asn::new_32bit(65001),
795            hold_time: 180,
796            bgp_identifier: Ipv4Addr::new(192, 168, 1, 1),
797            extended_length: false,
798            opt_params: vec![OptParam {
799                param_type: 2, // capability
800                param_len: 6,
801                param_value: ParamValue::Capacities(vec![Capability {
802                    ty: BgpCapabilityType::MULTIPROTOCOL_EXTENSIONS_FOR_BGP_4,
803                    value: CapabilityValue::MultiprotocolExtensions(
804                        MultiprotocolExtensionsCapability {
805                            afi: Afi::Ipv4,
806                            safi: Safi::Unicast,
807                        },
808                    ),
809                }]),
810            }],
811        });
812        let bgp_bytes = bgp_open.encode(AsnLength::Bits32);
813        data.extend_from_slice(&bgp_bytes);
814        data.extend_from_slice(&bgp_bytes);
815
816        // Add VrTableName TLV with oversized value (>255 bytes, RFC 9069 violation)
817        let oversized_name = "A".repeat(256);
818        data.extend_from_slice(&[0x00, 0x03]); // VrTableName TLV type
819        data.extend_from_slice(&[0x01, 0x00]); // length 256
820        data.extend_from_slice(oversized_name.as_bytes());
821
822        let afi = Afi::Ipv4;
823        let asn_len = AsnLength::Bits32;
824        let peer_type = BmpPeerType::LocalRib;
825
826        let result =
827            parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, Some(&peer_type));
828
829        assert!(result.is_ok(), "Parsing should succeed");
830        // In production, a warning would be logged about oversized VrTableName
831    }
832
833    #[test]
834    fn test_parse_peer_up_unknown_tlv() {
835        let mut data = BytesMut::new();
836
837        // Local address setup
838        data.extend_from_slice(&[
839            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
840            0x01, 0x01, // 192.168.1.1
841            0x00, 0xB3, // local port 179
842            0x00, 0xB3, // remote port 179
843        ]);
844
845        // Add two complete BGP OPEN messages
846        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
847            version: 4,
848            asn: crate::models::Asn::new_32bit(65001),
849            hold_time: 180,
850            bgp_identifier: Ipv4Addr::new(192, 168, 1, 1),
851            extended_length: false,
852            opt_params: vec![],
853        });
854        let bgp_bytes = bgp_open.encode(AsnLength::Bits32);
855        data.extend_from_slice(&bgp_bytes);
856        data.extend_from_slice(&bgp_bytes);
857
858        // Add a known TLV (String) and an unknown TLV (type 255)
859        data.extend_from_slice(&[0x00, 0x00]); // String TLV type
860        data.extend_from_slice(&[0x00, 0x04]); // length 4
861        data.extend_from_slice(b"Test");
862
863        data.extend_from_slice(&[0x00, 0xFF]); // Unknown TLV type (255)
864        data.extend_from_slice(&[0x00, 0x03]); // length 3
865        data.extend_from_slice(b"Foo");
866
867        let afi = Afi::Ipv4;
868        let asn_len = AsnLength::Bits32;
869        let result = parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, None);
870
871        assert!(
872            result.is_ok(),
873            "Should parse unknown TLV types without error"
874        );
875        let peer_notification = result.unwrap();
876        assert_eq!(peer_notification.tlvs.len(), 2);
877        assert_eq!(peer_notification.tlvs[0].info_type, PeerUpTlvType::String);
878        assert_eq!(peer_notification.tlvs[0].info_value, "Test");
879        assert_eq!(
880            peer_notification.tlvs[1].info_type,
881            PeerUpTlvType::Unknown(255)
882        );
883        assert_eq!(peer_notification.tlvs[1].info_value, "Foo");
884    }
885
886    #[test]
887    fn test_non_local_rib_no_validation() {
888        // This test verifies that non-LocalRib peer types don't trigger LocalRib validations
889        let mut data = BytesMut::new();
890
891        // Local address setup
892        data.extend_from_slice(&[
893            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
894            0x01, 0x01, // 192.168.1.1
895            0x00, 0xB3, // local port 179
896            0x00, 0xB3, // remote port 179
897        ]);
898
899        // Create BGP OPEN message WITHOUT capabilities (but not LocalRib, so no warning)
900        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
901            version: 4,
902            asn: crate::models::Asn::new_32bit(65001),
903            hold_time: 180,
904            bgp_identifier: Ipv4Addr::new(192, 168, 1, 1),
905            extended_length: false,
906            opt_params: vec![],
907        });
908        let bgp_bytes = bgp_open.encode(AsnLength::Bits32);
909        data.extend_from_slice(&bgp_bytes);
910        data.extend_from_slice(&bgp_bytes);
911
912        let afi = Afi::Ipv4;
913        let asn_len = AsnLength::Bits32;
914        let peer_type = BmpPeerType::Global; // Not LocalRib
915
916        let result =
917            parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, Some(&peer_type));
918
919        assert!(result.is_ok(), "Parsing should succeed");
920        // No warnings should be logged for non-LocalRib peer types
921    }
922}