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