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