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");
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",
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");
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::net::{IpAddr, Ipv4Addr};
154
155    #[test]
156    fn test_parse_peer_up_notification() {
157        let mut data = BytesMut::new();
158        // Assuming setup for test where local address is "10.1.1.1" IPv4
159        // local port is 8000, remote port is 9000. Adjust accordingly.
160        data.extend_from_slice(&[
161            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x01,
162            0x01, 0x01,
163        ]);
164        data.extend_from_slice(&[0x1F, 0x40]); // 8000 in network byte order
165        data.extend_from_slice(&[0x23, 0x28]); // 9000 in network byte order
166
167        let bgp_open_message = crate::models::BgpMessage::Open(BgpOpenMessage {
168            version: 0,
169            asn: Default::default(),
170            hold_time: 0,
171            sender_ip: Ipv4Addr::new(0, 0, 0, 0),
172            extended_length: false,
173            opt_params: vec![],
174        });
175        let bgp_open_message_bytes = bgp_open_message.encode(AsnLength::Bits32);
176        data.extend_from_slice(&bgp_open_message_bytes);
177        data.extend_from_slice(&bgp_open_message_bytes);
178
179        // add tlv
180        data.extend_from_slice(&[0x00, 0x01]); // info_type
181        data.extend_from_slice(&[0x00, 0x02]); // info_len
182        data.extend_from_slice(&[0x00, 0x03]); // info_value
183
184        let afi = Afi::Ipv4;
185        let asn_len = AsnLength::Bits32;
186
187        let result = parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, None);
188
189        match result {
190            Ok(peer_notification) => {
191                assert_eq!(
192                    peer_notification.local_addr,
193                    IpAddr::V4(std::net::Ipv4Addr::new(10, 1, 1, 1))
194                );
195                assert_eq!(peer_notification.local_port, 8000);
196                assert_eq!(peer_notification.remote_port, 9000);
197
198                // Continue to check other values from peer_notification like sent_open, received_open, tlvs
199                let tlv = peer_notification.tlvs.first().unwrap();
200                assert_eq!(tlv.info_type, PeerUpTlvType::SysDescr);
201                assert_eq!(tlv.info_len, 2);
202                assert_eq!(tlv.info_value, "\u{0}\u{3}");
203            }
204            Err(_) => {
205                panic!("parse_peer_up_notification should return Ok");
206            }
207        }
208    }
209
210    // Helper function to set up warning capture for tests
211    fn setup_warning_logger() -> std::sync::Arc<std::sync::Mutex<Vec<String>>> {
212        use log::{Level, Record};
213        use std::sync::{Arc, Mutex};
214
215        struct TestLogger(Arc<Mutex<Vec<String>>>);
216
217        impl log::Log for TestLogger {
218            fn enabled(&self, metadata: &log::Metadata) -> bool {
219                metadata.level() <= Level::Warn
220            }
221
222            fn log(&self, record: &Record) {
223                if record.level() <= Level::Warn {
224                    self.0.lock().unwrap().push(record.args().to_string());
225                }
226            }
227
228            fn flush(&self) {}
229        }
230
231        let warnings = Arc::new(Mutex::new(Vec::new()));
232        let logger = TestLogger(warnings.clone());
233        let _ = log::set_boxed_logger(Box::new(logger));
234        log::set_max_level(log::LevelFilter::Warn);
235        warnings
236    }
237
238    // Note: These tests verify that the parser handles LocalRib validation correctly.
239    // The actual warning messages are logged via the `log` crate and would appear
240    // in production use. For testing purposes, we verify that parsing succeeds
241    // and the code paths are exercised.
242
243    #[test]
244    fn test_parse_peer_up_notification_no_warnings() {
245        let warnings = setup_warning_logger();
246
247        // Regression test: This test creates a scenario with two consecutive BGP OPEN messages
248        // that would have triggered BGP length warnings in the old implementation.
249        // The new implementation should parse cleanly without warnings.
250
251        let mut data = BytesMut::new();
252
253        // Local address (IPv4): 192.168.1.1 + ports
254        data.extend_from_slice(&[
255            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
256            0x01, 0x01, // 192.168.1.1
257            0x00, 0xB3, // local port 179
258            0x00, 0xB3, // remote port 179
259        ]);
260
261        // Create two different BGP OPEN messages that will be concatenated
262        // This test verifies that the boundary extraction fix prevents BGP length warnings
263
264        // First BGP OPEN message
265        let bgp1 = crate::models::BgpMessage::Open(BgpOpenMessage {
266            version: 4,
267            asn: crate::models::Asn::new_16bit(65001),
268            hold_time: 180,
269            sender_ip: Ipv4Addr::new(192, 168, 1, 1),
270            extended_length: false,
271            opt_params: vec![],
272        });
273        let bgp1_bytes = bgp1.encode(AsnLength::Bits32);
274
275        // Second BGP OPEN message
276        let bgp2 = crate::models::BgpMessage::Open(BgpOpenMessage {
277            version: 4,
278            asn: crate::models::Asn::new_16bit(65002),
279            hold_time: 90,
280            sender_ip: Ipv4Addr::new(192, 168, 1, 2),
281            extended_length: false,
282            opt_params: vec![],
283        });
284        let bgp2_bytes = bgp2.encode(AsnLength::Bits32);
285
286        // Add both BGP messages consecutively
287        data.extend_from_slice(&bgp1_bytes);
288        data.extend_from_slice(&bgp2_bytes);
289
290        // Add a TLV (String type, length 8, "TestNode")
291        data.extend_from_slice(&[
292            0x00, 0x00, // String TLV type
293            0x00, 0x08, // length 8
294        ]);
295        data.extend_from_slice(b"TestNode");
296
297        let afi = Afi::Ipv4;
298        let asn_len = AsnLength::Bits32;
299
300        let result = parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, None);
301
302        // Test should succeed without any BGP length warnings
303        assert!(result.is_ok(), "Parsing should succeed without warnings");
304
305        let peer_notification = result.unwrap();
306        assert_eq!(
307            peer_notification.local_addr,
308            IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))
309        );
310        assert_eq!(peer_notification.local_port, 179);
311        assert_eq!(peer_notification.remote_port, 179);
312
313        // Ensure both BGP messages were parsed correctly with different ASNs
314        if let crate::models::BgpMessage::Open(ref open1) = &peer_notification.sent_open {
315            assert_eq!(open1.asn, crate::models::Asn::new_16bit(65001));
316        } else {
317            panic!("sent_open should be an OPEN message");
318        }
319
320        if let crate::models::BgpMessage::Open(ref open2) = &peer_notification.received_open {
321            assert_eq!(open2.asn, crate::models::Asn::new_16bit(65002));
322        } else {
323            panic!("received_open should be an OPEN message");
324        }
325
326        // Verify TLV was parsed correctly
327        assert_eq!(peer_notification.tlvs.len(), 1);
328        assert_eq!(peer_notification.tlvs[0].info_type, PeerUpTlvType::String);
329        assert_eq!(peer_notification.tlvs[0].info_value, "TestNode");
330
331        // The main assertion: verify no warnings were logged
332        let captured_warnings = warnings.lock().unwrap();
333        assert!(
334            captured_warnings.is_empty(),
335            "Test should not produce warnings, but got: {:?}",
336            *captured_warnings
337        );
338    }
339
340    #[test]
341    fn test_parse_peer_up_insufficient_data_first_bgp() {
342        let mut data = BytesMut::new();
343
344        // Local address setup
345        data.extend_from_slice(&[
346            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x01,
347            0x01, 0x01,
348        ]);
349        data.extend_from_slice(&[0x1F, 0x40]); // local port
350        data.extend_from_slice(&[0x23, 0x28]); // remote port
351
352        // Add incomplete first BGP message (only header, no body)
353        data.extend_from_slice(&[
354            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // BGP marker
355            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
356            0x40, // Length = 64 bytes (but we won't provide 64 bytes)
357            0x01, // OPEN message type
358        ]);
359        // Missing the remaining 45 bytes of the BGP OPEN message
360
361        let afi = Afi::Ipv4;
362        let asn_len = AsnLength::Bits32;
363        let result = parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, None);
364
365        assert!(
366            result.is_err(),
367            "Should fail with insufficient data for first BGP message"
368        );
369    }
370
371    #[test]
372    fn test_parse_peer_up_insufficient_data_second_bgp() {
373        let mut data = BytesMut::new();
374
375        // Local address setup
376        data.extend_from_slice(&[
377            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x01,
378            0x01, 0x01,
379        ]);
380        data.extend_from_slice(&[0x1F, 0x40]); // local port
381        data.extend_from_slice(&[0x23, 0x28]); // remote port
382
383        // Add complete first BGP message
384        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
385            version: 4,
386            asn: Default::default(),
387            hold_time: 180,
388            sender_ip: Ipv4Addr::new(0, 0, 0, 0),
389            extended_length: false,
390            opt_params: vec![],
391        });
392        let bgp_bytes = bgp_open.encode(AsnLength::Bits32);
393        data.extend_from_slice(&bgp_bytes);
394
395        // Add incomplete second BGP message (only partial header)
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            0x30, // Length = 48 bytes
400                  // Missing message type and body
401        ]);
402
403        let afi = Afi::Ipv4;
404        let asn_len = AsnLength::Bits32;
405        let result = parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, None);
406
407        assert!(
408            result.is_err(),
409            "Should fail with insufficient data for second BGP message"
410        );
411    }
412
413    #[test]
414    fn test_parse_peer_up_excess_data_in_tlvs() {
415        let mut data = BytesMut::new();
416
417        // Local address setup
418        data.extend_from_slice(&[
419            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x01,
420            0x01, 0x01,
421        ]);
422        data.extend_from_slice(&[0x1F, 0x40]); // local port
423        data.extend_from_slice(&[0x23, 0x28]); // remote port
424
425        // Add two complete BGP messages
426        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
427            version: 4,
428            asn: Default::default(),
429            hold_time: 180,
430            sender_ip: Ipv4Addr::new(0, 0, 0, 0),
431            extended_length: false,
432            opt_params: vec![],
433        });
434        let bgp_bytes = bgp_open.encode(AsnLength::Bits32);
435        data.extend_from_slice(&bgp_bytes);
436        data.extend_from_slice(&bgp_bytes);
437
438        // Add valid TLVs
439        data.extend_from_slice(&[0x00, 0x00]); // String TLV
440        data.extend_from_slice(&[0x00, 0x04]); // length 4
441        data.extend_from_slice(b"Test"); // value
442
443        data.extend_from_slice(&[0x00, 0x00]); // String TLV
444        data.extend_from_slice(&[0x00, 0x06]); // length 6
445        data.extend_from_slice(b"Router"); // value
446
447        // Add some random extra bytes (should be safely ignored due to TLV parsing logic)
448        data.extend_from_slice(&[0x00, 0x01, 0x02]); // Less than 4 bytes, should exit TLV parsing loop
449
450        let afi = Afi::Ipv4;
451        let asn_len = AsnLength::Bits32;
452        let result = parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, None);
453
454        // Should succeed - TLV parsing handles excess data gracefully
455        assert!(result.is_ok(), "Should handle excess data gracefully");
456
457        let peer_notification = result.unwrap();
458        assert_eq!(peer_notification.tlvs.len(), 2); // Should have parsed 2 TLVs
459        assert_eq!(peer_notification.tlvs[0].info_type, PeerUpTlvType::String);
460        assert_eq!(peer_notification.tlvs[0].info_value, "Test");
461        assert_eq!(peer_notification.tlvs[1].info_type, PeerUpTlvType::String);
462        assert_eq!(peer_notification.tlvs[1].info_value, "Router");
463    }
464
465    #[test]
466    fn test_local_rib_without_multiprotocol_capability() {
467        // This test verifies that LocalRib peer up notifications without multiprotocol
468        // capabilities are parsed successfully. In production, a warning would be logged.
469        let mut data = BytesMut::new();
470
471        // Local address setup
472        data.extend_from_slice(&[
473            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
474            0x01, 0x01, // 192.168.1.1
475            0x00, 0xB3, // local port 179
476            0x00, 0xB3, // remote port 179
477        ]);
478
479        // Create BGP OPEN message WITHOUT multiprotocol capabilities (RFC 9069 violation)
480        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
481            version: 4,
482            asn: crate::models::Asn::new_32bit(65001),
483            hold_time: 180,
484            sender_ip: Ipv4Addr::new(192, 168, 1, 1),
485            extended_length: false,
486            opt_params: vec![], // No capabilities
487        });
488        let bgp_bytes = bgp_open.encode(AsnLength::Bits32);
489        data.extend_from_slice(&bgp_bytes);
490        data.extend_from_slice(&bgp_bytes);
491
492        let afi = Afi::Ipv4;
493        let asn_len = AsnLength::Bits32;
494        let peer_type = BmpPeerType::LocalRib;
495
496        let result =
497            parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, Some(&peer_type));
498
499        assert!(result.is_ok(), "Parsing should succeed");
500        // In production, a warning would be logged about missing multiprotocol capability
501    }
502
503    #[test]
504    fn test_local_rib_with_multiprotocol_capability() {
505        // This test verifies that LocalRib peer up notifications WITH multiprotocol
506        // capabilities are parsed successfully without warnings.
507        let mut data = BytesMut::new();
508
509        // Local address setup
510        data.extend_from_slice(&[
511            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
512            0x01, 0x01, // 192.168.1.1
513            0x00, 0xB3, // local port 179
514            0x00, 0xB3, // remote port 179
515        ]);
516
517        // Create BGP OPEN message WITH multiprotocol capabilities (RFC 9069 compliant)
518        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
519            version: 4,
520            asn: crate::models::Asn::new_32bit(65001),
521            hold_time: 180,
522            sender_ip: Ipv4Addr::new(192, 168, 1, 1),
523            extended_length: false,
524            opt_params: vec![OptParam {
525                param_type: 2, // capability
526                param_len: 6,
527                param_value: ParamValue::Capacities(vec![Capability {
528                    ty: BgpCapabilityType::MULTIPROTOCOL_EXTENSIONS_FOR_BGP_4,
529                    value: CapabilityValue::MultiprotocolExtensions(
530                        MultiprotocolExtensionsCapability {
531                            afi: Afi::Ipv4,
532                            safi: Safi::Unicast,
533                        },
534                    ),
535                }]),
536            }],
537        });
538        let bgp_bytes = bgp_open.encode(AsnLength::Bits32);
539        data.extend_from_slice(&bgp_bytes);
540        data.extend_from_slice(&bgp_bytes);
541
542        let afi = Afi::Ipv4;
543        let asn_len = AsnLength::Bits32;
544        let peer_type = BmpPeerType::LocalRib;
545
546        let result =
547            parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, Some(&peer_type));
548
549        assert!(result.is_ok(), "Parsing should succeed");
550        // No warning should be logged when multiprotocol capability is present
551    }
552
553    #[test]
554    fn test_local_rib_without_vr_table_name_tlv() {
555        // This test verifies that LocalRib peer up notifications without VrTableName TLV
556        // are parsed successfully. In production, a warning would be logged.
557        let mut data = BytesMut::new();
558
559        // Local address setup
560        data.extend_from_slice(&[
561            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
562            0x01, 0x01, // 192.168.1.1
563            0x00, 0xB3, // local port 179
564            0x00, 0xB3, // remote port 179
565        ]);
566
567        // Create BGP OPEN message with capabilities
568        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
569            version: 4,
570            asn: crate::models::Asn::new_32bit(65001),
571            hold_time: 180,
572            sender_ip: Ipv4Addr::new(192, 168, 1, 1),
573            extended_length: false,
574            opt_params: vec![OptParam {
575                param_type: 2, // capability
576                param_len: 6,
577                param_value: ParamValue::Capacities(vec![Capability {
578                    ty: BgpCapabilityType::MULTIPROTOCOL_EXTENSIONS_FOR_BGP_4,
579                    value: CapabilityValue::MultiprotocolExtensions(
580                        MultiprotocolExtensionsCapability {
581                            afi: Afi::Ipv4,
582                            safi: Safi::Unicast,
583                        },
584                    ),
585                }]),
586            }],
587        });
588        let bgp_bytes = bgp_open.encode(AsnLength::Bits32);
589        data.extend_from_slice(&bgp_bytes);
590        data.extend_from_slice(&bgp_bytes);
591
592        // Add TLVs but NO VrTableName (RFC 9069 violation)
593        data.extend_from_slice(&[0x00, 0x00]); // String TLV
594        data.extend_from_slice(&[0x00, 0x04]); // length 4
595        data.extend_from_slice(b"Test");
596
597        let afi = Afi::Ipv4;
598        let asn_len = AsnLength::Bits32;
599        let peer_type = BmpPeerType::LocalRib;
600
601        let result =
602            parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, Some(&peer_type));
603
604        assert!(result.is_ok(), "Parsing should succeed");
605        // In production, a warning would be logged about missing VrTableName TLV
606    }
607
608    #[test]
609    fn test_local_rib_with_valid_vr_table_name_tlv() {
610        // This test verifies that LocalRib peer up notifications with valid VrTableName TLV
611        // are parsed successfully without warnings.
612        let mut data = BytesMut::new();
613
614        // Local address setup
615        data.extend_from_slice(&[
616            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
617            0x01, 0x01, // 192.168.1.1
618            0x00, 0xB3, // local port 179
619            0x00, 0xB3, // remote port 179
620        ]);
621
622        // Create BGP OPEN message with capabilities
623        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
624            version: 4,
625            asn: crate::models::Asn::new_32bit(65001),
626            hold_time: 180,
627            sender_ip: Ipv4Addr::new(192, 168, 1, 1),
628            extended_length: false,
629            opt_params: vec![OptParam {
630                param_type: 2, // capability
631                param_len: 6,
632                param_value: ParamValue::Capacities(vec![Capability {
633                    ty: BgpCapabilityType::MULTIPROTOCOL_EXTENSIONS_FOR_BGP_4,
634                    value: CapabilityValue::MultiprotocolExtensions(
635                        MultiprotocolExtensionsCapability {
636                            afi: Afi::Ipv4,
637                            safi: Safi::Unicast,
638                        },
639                    ),
640                }]),
641            }],
642        });
643        let bgp_bytes = bgp_open.encode(AsnLength::Bits32);
644        data.extend_from_slice(&bgp_bytes);
645        data.extend_from_slice(&bgp_bytes);
646
647        // Add VrTableName TLV with valid length (1-255 bytes)
648        data.extend_from_slice(&[0x00, 0x03]); // VrTableName TLV type
649        data.extend_from_slice(&[0x00, 0x08]); // length 8
650        data.extend_from_slice(b"LocalRIB");
651
652        let afi = Afi::Ipv4;
653        let asn_len = AsnLength::Bits32;
654        let peer_type = BmpPeerType::LocalRib;
655
656        let result =
657            parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, Some(&peer_type));
658
659        assert!(result.is_ok(), "Parsing should succeed");
660
661        let peer_notification = result.unwrap();
662        assert_eq!(peer_notification.tlvs.len(), 1);
663        assert_eq!(
664            peer_notification.tlvs[0].info_type,
665            PeerUpTlvType::VrTableName
666        );
667        assert_eq!(peer_notification.tlvs[0].info_value, "LocalRIB");
668        // No warnings should be logged with valid VrTableName
669    }
670
671    #[test]
672    fn test_local_rib_with_empty_vr_table_name() {
673        // This test verifies that LocalRib peer up notifications with empty VrTableName
674        // are parsed successfully. In production, a warning would be logged.
675        let mut data = BytesMut::new();
676
677        // Local address setup
678        data.extend_from_slice(&[
679            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
680            0x01, 0x01, // 192.168.1.1
681            0x00, 0xB3, // local port 179
682            0x00, 0xB3, // remote port 179
683        ]);
684
685        // Create BGP OPEN message with capabilities
686        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
687            version: 4,
688            asn: crate::models::Asn::new_32bit(65001),
689            hold_time: 180,
690            sender_ip: Ipv4Addr::new(192, 168, 1, 1),
691            extended_length: false,
692            opt_params: vec![OptParam {
693                param_type: 2, // capability
694                param_len: 6,
695                param_value: ParamValue::Capacities(vec![Capability {
696                    ty: BgpCapabilityType::MULTIPROTOCOL_EXTENSIONS_FOR_BGP_4,
697                    value: CapabilityValue::MultiprotocolExtensions(
698                        MultiprotocolExtensionsCapability {
699                            afi: Afi::Ipv4,
700                            safi: Safi::Unicast,
701                        },
702                    ),
703                }]),
704            }],
705        });
706        let bgp_bytes = bgp_open.encode(AsnLength::Bits32);
707        data.extend_from_slice(&bgp_bytes);
708        data.extend_from_slice(&bgp_bytes);
709
710        // Add VrTableName TLV with EMPTY value (RFC 9069 violation)
711        data.extend_from_slice(&[0x00, 0x03]); // VrTableName TLV type
712        data.extend_from_slice(&[0x00, 0x00]); // length 0 (empty)
713
714        let afi = Afi::Ipv4;
715        let asn_len = AsnLength::Bits32;
716        let peer_type = BmpPeerType::LocalRib;
717
718        let result =
719            parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, Some(&peer_type));
720
721        assert!(result.is_ok(), "Parsing should succeed");
722        // In production, a warning would be logged about invalid VrTableName length
723    }
724
725    #[test]
726    fn test_local_rib_with_oversized_vr_table_name() {
727        // This test verifies that LocalRib peer up notifications with oversized VrTableName
728        // are parsed successfully. In production, a warning would be logged.
729        let mut data = BytesMut::new();
730
731        // Local address setup
732        data.extend_from_slice(&[
733            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
734            0x01, 0x01, // 192.168.1.1
735            0x00, 0xB3, // local port 179
736            0x00, 0xB3, // remote port 179
737        ]);
738
739        // Create BGP OPEN message with capabilities
740        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
741            version: 4,
742            asn: crate::models::Asn::new_32bit(65001),
743            hold_time: 180,
744            sender_ip: Ipv4Addr::new(192, 168, 1, 1),
745            extended_length: false,
746            opt_params: vec![OptParam {
747                param_type: 2, // capability
748                param_len: 6,
749                param_value: ParamValue::Capacities(vec![Capability {
750                    ty: BgpCapabilityType::MULTIPROTOCOL_EXTENSIONS_FOR_BGP_4,
751                    value: CapabilityValue::MultiprotocolExtensions(
752                        MultiprotocolExtensionsCapability {
753                            afi: Afi::Ipv4,
754                            safi: Safi::Unicast,
755                        },
756                    ),
757                }]),
758            }],
759        });
760        let bgp_bytes = bgp_open.encode(AsnLength::Bits32);
761        data.extend_from_slice(&bgp_bytes);
762        data.extend_from_slice(&bgp_bytes);
763
764        // Add VrTableName TLV with oversized value (>255 bytes, RFC 9069 violation)
765        let oversized_name = "A".repeat(256);
766        data.extend_from_slice(&[0x00, 0x03]); // VrTableName TLV type
767        data.extend_from_slice(&[0x01, 0x00]); // length 256
768        data.extend_from_slice(oversized_name.as_bytes());
769
770        let afi = Afi::Ipv4;
771        let asn_len = AsnLength::Bits32;
772        let peer_type = BmpPeerType::LocalRib;
773
774        let result =
775            parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, Some(&peer_type));
776
777        assert!(result.is_ok(), "Parsing should succeed");
778        // In production, a warning would be logged about oversized VrTableName
779    }
780
781    #[test]
782    fn test_non_local_rib_no_validation() {
783        // This test verifies that non-LocalRib peer types don't trigger LocalRib validations
784        let mut data = BytesMut::new();
785
786        // Local address setup
787        data.extend_from_slice(&[
788            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xA8,
789            0x01, 0x01, // 192.168.1.1
790            0x00, 0xB3, // local port 179
791            0x00, 0xB3, // remote port 179
792        ]);
793
794        // Create BGP OPEN message WITHOUT capabilities (but not LocalRib, so no warning)
795        let bgp_open = crate::models::BgpMessage::Open(BgpOpenMessage {
796            version: 4,
797            asn: crate::models::Asn::new_32bit(65001),
798            hold_time: 180,
799            sender_ip: Ipv4Addr::new(192, 168, 1, 1),
800            extended_length: false,
801            opt_params: vec![],
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        let afi = Afi::Ipv4;
808        let asn_len = AsnLength::Bits32;
809        let peer_type = BmpPeerType::Global; // Not LocalRib
810
811        let result =
812            parse_peer_up_notification(&mut data.freeze(), &afi, &asn_len, Some(&peer_type));
813
814        assert!(result.is_ok(), "Parsing should succeed");
815        // No warnings should be logged for non-LocalRib peer types
816    }
817}