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).unwrap();
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).unwrap();
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).unwrap();
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).unwrap();
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).unwrap();
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).unwrap();
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_value: ParamValue::Capacities(vec![Capability {
579                    ty: BgpCapabilityType::MULTIPROTOCOL_EXTENSIONS_FOR_BGP_4,
580                    value: CapabilityValue::MultiprotocolExtensions(
581                        MultiprotocolExtensionsCapability {
582                            afi: Afi::Ipv4,
583                            safi: Safi::Unicast,
584                        },
585                    ),
586                }]),
587            }],
588        });
589        let bgp_bytes = bgp_open.encode(AsnLength::Bits32).unwrap();
590        data.extend_from_slice(&bgp_bytes);
591        data.extend_from_slice(&bgp_bytes);
592
593        let afi = Afi::Ipv4;
594        let asn_len = AsnLength::Bits32;
595        let peer_type = BmpPeerType::LocalRib;
596
597        let result =
598            parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, Some(&peer_type));
599
600        assert!(result.is_ok(), "Parsing should succeed");
601        // No warning should be logged when multiprotocol capability is present
602    }
603
604    #[test]
605    fn test_local_rib_without_vr_table_name_tlv() {
606        // This test verifies that LocalRib peer up notifications without VrTableName TLV
607        // are parsed successfully. In production, a warning would be logged.
608        let mut data = BytesMut::new();
609
610        // Local address setup
611        data.extend_from_slice(&[
612            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
613            0x01, 0x01, // 192.168.1.1
614            0x00, 0xB3, // local port 179
615            0x00, 0xB3, // remote port 179
616        ]);
617
618        // Create BGP OPEN message with capabilities
619        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
620            version: 4,
621            asn: crate::models::Asn::new_32bit(65001),
622            hold_time: 180,
623            bgp_identifier: Ipv4Addr::new(192, 168, 1, 1),
624            extended_length: false,
625            opt_params: vec![OptParam {
626                param_type: 2, // capability
627                param_value: ParamValue::Capacities(vec![Capability {
628                    ty: BgpCapabilityType::MULTIPROTOCOL_EXTENSIONS_FOR_BGP_4,
629                    value: CapabilityValue::MultiprotocolExtensions(
630                        MultiprotocolExtensionsCapability {
631                            afi: Afi::Ipv4,
632                            safi: Safi::Unicast,
633                        },
634                    ),
635                }]),
636            }],
637        });
638        let bgp_bytes = bgp_open.encode(AsnLength::Bits32).unwrap();
639        data.extend_from_slice(&bgp_bytes);
640        data.extend_from_slice(&bgp_bytes);
641
642        // Add TLVs but NO VrTableName (RFC 9069 violation)
643        data.extend_from_slice(&[0x00, 0x00]); // String TLV
644        data.extend_from_slice(&[0x00, 0x04]); // length 4
645        data.extend_from_slice(b"Test");
646
647        let afi = Afi::Ipv4;
648        let asn_len = AsnLength::Bits32;
649        let peer_type = BmpPeerType::LocalRib;
650
651        let result =
652            parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, Some(&peer_type));
653
654        assert!(result.is_ok(), "Parsing should succeed");
655        // In production, a warning would be logged about missing VrTableName TLV
656    }
657
658    #[test]
659    fn test_local_rib_with_valid_vr_table_name_tlv() {
660        // This test verifies that LocalRib peer up notifications with valid VrTableName TLV
661        // are parsed successfully without warnings.
662        let mut data = BytesMut::new();
663
664        // Local address setup
665        data.extend_from_slice(&[
666            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
667            0x01, 0x01, // 192.168.1.1
668            0x00, 0xB3, // local port 179
669            0x00, 0xB3, // remote port 179
670        ]);
671
672        // Create BGP OPEN message with capabilities
673        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
674            version: 4,
675            asn: crate::models::Asn::new_32bit(65001),
676            hold_time: 180,
677            bgp_identifier: Ipv4Addr::new(192, 168, 1, 1),
678            extended_length: false,
679            opt_params: vec![OptParam {
680                param_type: 2, // capability
681                param_value: ParamValue::Capacities(vec![Capability {
682                    ty: BgpCapabilityType::MULTIPROTOCOL_EXTENSIONS_FOR_BGP_4,
683                    value: CapabilityValue::MultiprotocolExtensions(
684                        MultiprotocolExtensionsCapability {
685                            afi: Afi::Ipv4,
686                            safi: Safi::Unicast,
687                        },
688                    ),
689                }]),
690            }],
691        });
692        let bgp_bytes = bgp_open.encode(AsnLength::Bits32).unwrap();
693        data.extend_from_slice(&bgp_bytes);
694        data.extend_from_slice(&bgp_bytes);
695
696        // Add VrTableName TLV with valid length (1-255 bytes)
697        data.extend_from_slice(&[0x00, 0x03]); // VrTableName TLV type
698        data.extend_from_slice(&[0x00, 0x08]); // length 8
699        data.extend_from_slice(b"LocalRIB");
700
701        let afi = Afi::Ipv4;
702        let asn_len = AsnLength::Bits32;
703        let peer_type = BmpPeerType::LocalRib;
704
705        let result =
706            parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, Some(&peer_type));
707
708        assert!(result.is_ok(), "Parsing should succeed");
709
710        let peer_notification = result.unwrap();
711        assert_eq!(peer_notification.tlvs.len(), 1);
712        assert_eq!(
713            peer_notification.tlvs[0].info_type,
714            PeerUpTlvType::VrTableName
715        );
716        assert_eq!(peer_notification.tlvs[0].info_value, "LocalRIB");
717        // No warnings should be logged with valid VrTableName
718    }
719
720    #[test]
721    fn test_local_rib_with_empty_vr_table_name() {
722        // This test verifies that LocalRib peer up notifications with empty VrTableName
723        // are parsed successfully. In production, a warning would be logged.
724        let mut data = BytesMut::new();
725
726        // Local address setup
727        data.extend_from_slice(&[
728            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
729            0x01, 0x01, // 192.168.1.1
730            0x00, 0xB3, // local port 179
731            0x00, 0xB3, // remote port 179
732        ]);
733
734        // Create BGP OPEN message with capabilities
735        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
736            version: 4,
737            asn: crate::models::Asn::new_32bit(65001),
738            hold_time: 180,
739            bgp_identifier: Ipv4Addr::new(192, 168, 1, 1),
740            extended_length: false,
741            opt_params: vec![OptParam {
742                param_type: 2, // capability
743                param_value: ParamValue::Capacities(vec![Capability {
744                    ty: BgpCapabilityType::MULTIPROTOCOL_EXTENSIONS_FOR_BGP_4,
745                    value: CapabilityValue::MultiprotocolExtensions(
746                        MultiprotocolExtensionsCapability {
747                            afi: Afi::Ipv4,
748                            safi: Safi::Unicast,
749                        },
750                    ),
751                }]),
752            }],
753        });
754        let bgp_bytes = bgp_open.encode(AsnLength::Bits32).unwrap();
755        data.extend_from_slice(&bgp_bytes);
756        data.extend_from_slice(&bgp_bytes);
757
758        // Add VrTableName TLV with EMPTY value (RFC 9069 violation)
759        data.extend_from_slice(&[0x00, 0x03]); // VrTableName TLV type
760        data.extend_from_slice(&[0x00, 0x00]); // length 0 (empty)
761
762        let afi = Afi::Ipv4;
763        let asn_len = AsnLength::Bits32;
764        let peer_type = BmpPeerType::LocalRib;
765
766        let result =
767            parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, Some(&peer_type));
768
769        assert!(result.is_ok(), "Parsing should succeed");
770        // In production, a warning would be logged about invalid VrTableName length
771    }
772
773    #[test]
774    fn test_local_rib_with_oversized_vr_table_name() {
775        // This test verifies that LocalRib peer up notifications with oversized VrTableName
776        // are parsed successfully. In production, a warning would be logged.
777        let mut data = BytesMut::new();
778
779        // Local address setup
780        data.extend_from_slice(&[
781            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
782            0x01, 0x01, // 192.168.1.1
783            0x00, 0xB3, // local port 179
784            0x00, 0xB3, // remote port 179
785        ]);
786
787        // Create BGP OPEN message with capabilities
788        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
789            version: 4,
790            asn: crate::models::Asn::new_32bit(65001),
791            hold_time: 180,
792            bgp_identifier: Ipv4Addr::new(192, 168, 1, 1),
793            extended_length: false,
794            opt_params: vec![OptParam {
795                param_type: 2, // capability
796                param_value: ParamValue::Capacities(vec![Capability {
797                    ty: BgpCapabilityType::MULTIPROTOCOL_EXTENSIONS_FOR_BGP_4,
798                    value: CapabilityValue::MultiprotocolExtensions(
799                        MultiprotocolExtensionsCapability {
800                            afi: Afi::Ipv4,
801                            safi: Safi::Unicast,
802                        },
803                    ),
804                }]),
805            }],
806        });
807        let bgp_bytes = bgp_open.encode(AsnLength::Bits32).unwrap();
808        data.extend_from_slice(&bgp_bytes);
809        data.extend_from_slice(&bgp_bytes);
810
811        // Add VrTableName TLV with oversized value (>255 bytes, RFC 9069 violation)
812        let oversized_name = "A".repeat(256);
813        data.extend_from_slice(&[0x00, 0x03]); // VrTableName TLV type
814        data.extend_from_slice(&[0x01, 0x00]); // length 256
815        data.extend_from_slice(oversized_name.as_bytes());
816
817        let afi = Afi::Ipv4;
818        let asn_len = AsnLength::Bits32;
819        let peer_type = BmpPeerType::LocalRib;
820
821        let result =
822            parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, Some(&peer_type));
823
824        assert!(result.is_ok(), "Parsing should succeed");
825        // In production, a warning would be logged about oversized VrTableName
826    }
827
828    #[test]
829    fn test_parse_peer_up_unknown_tlv() {
830        let mut data = BytesMut::new();
831
832        // Local address setup
833        data.extend_from_slice(&[
834            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
835            0x01, 0x01, // 192.168.1.1
836            0x00, 0xB3, // local port 179
837            0x00, 0xB3, // remote port 179
838        ]);
839
840        // Add two complete BGP OPEN messages
841        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
842            version: 4,
843            asn: crate::models::Asn::new_32bit(65001),
844            hold_time: 180,
845            bgp_identifier: Ipv4Addr::new(192, 168, 1, 1),
846            extended_length: false,
847            opt_params: vec![],
848        });
849        let bgp_bytes = bgp_open.encode(AsnLength::Bits32).unwrap();
850        data.extend_from_slice(&bgp_bytes);
851        data.extend_from_slice(&bgp_bytes);
852
853        // Add a known TLV (String) and an unknown TLV (type 255)
854        data.extend_from_slice(&[0x00, 0x00]); // String TLV type
855        data.extend_from_slice(&[0x00, 0x04]); // length 4
856        data.extend_from_slice(b"Test");
857
858        data.extend_from_slice(&[0x00, 0xFF]); // Unknown TLV type (255)
859        data.extend_from_slice(&[0x00, 0x03]); // length 3
860        data.extend_from_slice(b"Foo");
861
862        let afi = Afi::Ipv4;
863        let asn_len = AsnLength::Bits32;
864        let result = parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, None);
865
866        assert!(
867            result.is_ok(),
868            "Should parse unknown TLV types without error"
869        );
870        let peer_notification = result.unwrap();
871        assert_eq!(peer_notification.tlvs.len(), 2);
872        assert_eq!(peer_notification.tlvs[0].info_type, PeerUpTlvType::String);
873        assert_eq!(peer_notification.tlvs[0].info_value, "Test");
874        assert_eq!(
875            peer_notification.tlvs[1].info_type,
876            PeerUpTlvType::Unknown(255)
877        );
878        assert_eq!(peer_notification.tlvs[1].info_value, "Foo");
879    }
880
881    #[test]
882    fn test_non_local_rib_no_validation() {
883        // This test verifies that non-LocalRib peer types don't trigger LocalRib validations
884        let mut data = BytesMut::new();
885
886        // Local address setup
887        data.extend_from_slice(&[
888            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
889            0x01, 0x01, // 192.168.1.1
890            0x00, 0xB3, // local port 179
891            0x00, 0xB3, // remote port 179
892        ]);
893
894        // Create BGP OPEN message WITHOUT capabilities (but not LocalRib, so no warning)
895        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
896            version: 4,
897            asn: crate::models::Asn::new_32bit(65001),
898            hold_time: 180,
899            bgp_identifier: Ipv4Addr::new(192, 168, 1, 1),
900            extended_length: false,
901            opt_params: vec![],
902        });
903        let bgp_bytes = bgp_open.encode(AsnLength::Bits32).unwrap();
904        data.extend_from_slice(&bgp_bytes);
905        data.extend_from_slice(&bgp_bytes);
906
907        let afi = Afi::Ipv4;
908        let asn_len = AsnLength::Bits32;
909        let peer_type = BmpPeerType::Global; // Not LocalRib
910
911        let result =
912            parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, Some(&peer_type));
913
914        assert!(result.is_ok(), "Parsing should succeed");
915        // No warnings should be logged for non-LocalRib peer types
916    }
917}