@@ -143,6 +143,7 @@ mod tests {
143143 use super :: * ;
144144 use bytes:: BytesMut ;
145145 use std:: net:: { IpAddr , Ipv4Addr } ;
146+ use crate :: models:: capabilities:: { BgpCapabilityType , MultiprotocolExtensionsCapability } ;
146147
147148 #[ test]
148149 fn test_parse_peer_up_notification ( ) {
@@ -227,6 +228,11 @@ mod tests {
227228 warnings
228229 }
229230
231+ // Note: These tests verify that the parser handles LocalRib validation correctly.
232+ // The actual warning messages are logged via the `log` crate and would appear
233+ // in production use. For testing purposes, we verify that parsing succeeds
234+ // and the code paths are exercised.
235+
230236 #[ test]
231237 fn test_parse_peer_up_notification_no_warnings ( ) {
232238 let warnings = setup_warning_logger ( ) ;
@@ -448,4 +454,347 @@ mod tests {
448454 assert_eq ! ( peer_notification. tlvs[ 1 ] . info_type, PeerUpTlvType :: String ) ;
449455 assert_eq ! ( peer_notification. tlvs[ 1 ] . info_value, "Router" ) ;
450456 }
457+
458+ #[ test]
459+ fn test_local_rib_without_multiprotocol_capability ( ) {
460+ // This test verifies that LocalRib peer up notifications without multiprotocol
461+ // capabilities are parsed successfully. In production, a warning would be logged.
462+ let mut data = BytesMut :: new ( ) ;
463+
464+ // Local address setup
465+ data. extend_from_slice ( & [
466+ 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0xC0 , 0xA8 ,
467+ 0x01 , 0x01 , // 192.168.1.1
468+ 0x00 , 0xB3 , // local port 179
469+ 0x00 , 0xB3 , // remote port 179
470+ ] ) ;
471+
472+ // Create BGP OPEN message WITHOUT multiprotocol capabilities (RFC 9069 violation)
473+ let bgp_open = crate :: models:: BgpMessage :: Open ( BgpOpenMessage {
474+ version : 4 ,
475+ asn : crate :: models:: Asn :: new_32bit ( 65001 ) ,
476+ hold_time : 180 ,
477+ sender_ip : Ipv4Addr :: new ( 192 , 168 , 1 , 1 ) ,
478+ extended_length : false ,
479+ opt_params : vec ! [ ] , // No capabilities
480+ } ) ;
481+ let bgp_bytes = bgp_open. encode ( AsnLength :: Bits32 ) ;
482+ data. extend_from_slice ( & bgp_bytes) ;
483+ data. extend_from_slice ( & bgp_bytes) ;
484+
485+ let afi = Afi :: Ipv4 ;
486+ let asn_len = AsnLength :: Bits32 ;
487+ let peer_type = BmpPeerType :: LocalRib ;
488+
489+ let result = parse_peer_up_notification ( & mut data. freeze ( ) , & afi, & asn_len, Some ( & peer_type) ) ;
490+
491+ assert ! ( result. is_ok( ) , "Parsing should succeed" ) ;
492+ // In production, a warning would be logged about missing multiprotocol capability
493+ }
494+
495+ #[ test]
496+ fn test_local_rib_with_multiprotocol_capability ( ) {
497+ // This test verifies that LocalRib peer up notifications WITH multiprotocol
498+ // capabilities are parsed successfully without warnings.
499+ let mut data = BytesMut :: new ( ) ;
500+
501+ // Local address setup
502+ data. extend_from_slice ( & [
503+ 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0xC0 , 0xA8 ,
504+ 0x01 , 0x01 , // 192.168.1.1
505+ 0x00 , 0xB3 , // local port 179
506+ 0x00 , 0xB3 , // remote port 179
507+ ] ) ;
508+
509+ // Create BGP OPEN message WITH multiprotocol capabilities (RFC 9069 compliant)
510+ let bgp_open = crate :: models:: BgpMessage :: Open ( BgpOpenMessage {
511+ version : 4 ,
512+ asn : crate :: models:: Asn :: new_32bit ( 65001 ) ,
513+ hold_time : 180 ,
514+ sender_ip : Ipv4Addr :: new ( 192 , 168 , 1 , 1 ) ,
515+ extended_length : false ,
516+ opt_params : vec ! [ OptParam {
517+ param_type: 2 , // capability
518+ param_len: 6 ,
519+ param_value: ParamValue :: Capacities ( vec![ Capability {
520+ ty: BgpCapabilityType :: MULTIPROTOCOL_EXTENSIONS_FOR_BGP_4 ,
521+ value: CapabilityValue :: MultiprotocolExtensions (
522+ MultiprotocolExtensionsCapability {
523+ afi: Afi :: Ipv4 ,
524+ safi: Safi :: Unicast ,
525+ } ,
526+ ) ,
527+ } ] ) ,
528+ } ] ,
529+ } ) ;
530+ let bgp_bytes = bgp_open. encode ( AsnLength :: Bits32 ) ;
531+ data. extend_from_slice ( & bgp_bytes) ;
532+ data. extend_from_slice ( & bgp_bytes) ;
533+
534+ let afi = Afi :: Ipv4 ;
535+ let asn_len = AsnLength :: Bits32 ;
536+ let peer_type = BmpPeerType :: LocalRib ;
537+
538+ let result = parse_peer_up_notification ( & mut data. freeze ( ) , & afi, & asn_len, Some ( & peer_type) ) ;
539+
540+ assert ! ( result. is_ok( ) , "Parsing should succeed" ) ;
541+ // No warning should be logged when multiprotocol capability is present
542+ }
543+
544+ #[ test]
545+ fn test_local_rib_without_vr_table_name_tlv ( ) {
546+ // This test verifies that LocalRib peer up notifications without VrTableName TLV
547+ // are parsed successfully. In production, a warning would be logged.
548+ let mut data = BytesMut :: new ( ) ;
549+
550+ // Local address setup
551+ data. extend_from_slice ( & [
552+ 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0xC0 , 0xA8 ,
553+ 0x01 , 0x01 , // 192.168.1.1
554+ 0x00 , 0xB3 , // local port 179
555+ 0x00 , 0xB3 , // remote port 179
556+ ] ) ;
557+
558+ // Create BGP OPEN message with capabilities
559+ let bgp_open = crate :: models:: BgpMessage :: Open ( BgpOpenMessage {
560+ version : 4 ,
561+ asn : crate :: models:: Asn :: new_32bit ( 65001 ) ,
562+ hold_time : 180 ,
563+ sender_ip : Ipv4Addr :: new ( 192 , 168 , 1 , 1 ) ,
564+ extended_length : false ,
565+ opt_params : vec ! [ OptParam {
566+ param_type: 2 , // capability
567+ param_len: 6 ,
568+ param_value: ParamValue :: Capacities ( vec![ Capability {
569+ ty: BgpCapabilityType :: MULTIPROTOCOL_EXTENSIONS_FOR_BGP_4 ,
570+ value: CapabilityValue :: MultiprotocolExtensions (
571+ MultiprotocolExtensionsCapability {
572+ afi: Afi :: Ipv4 ,
573+ safi: Safi :: Unicast ,
574+ } ,
575+ ) ,
576+ } ] ) ,
577+ } ] ,
578+ } ) ;
579+ let bgp_bytes = bgp_open. encode ( AsnLength :: Bits32 ) ;
580+ data. extend_from_slice ( & bgp_bytes) ;
581+ data. extend_from_slice ( & bgp_bytes) ;
582+
583+ // Add TLVs but NO VrTableName (RFC 9069 violation)
584+ data. extend_from_slice ( & [ 0x00 , 0x00 ] ) ; // String TLV
585+ data. extend_from_slice ( & [ 0x00 , 0x04 ] ) ; // length 4
586+ data. extend_from_slice ( b"Test" ) ;
587+
588+ let afi = Afi :: Ipv4 ;
589+ let asn_len = AsnLength :: Bits32 ;
590+ let peer_type = BmpPeerType :: LocalRib ;
591+
592+ let result = parse_peer_up_notification ( & mut data. freeze ( ) , & afi, & asn_len, Some ( & peer_type) ) ;
593+
594+ assert ! ( result. is_ok( ) , "Parsing should succeed" ) ;
595+ // In production, a warning would be logged about missing VrTableName TLV
596+ }
597+
598+ #[ test]
599+ fn test_local_rib_with_valid_vr_table_name_tlv ( ) {
600+ // This test verifies that LocalRib peer up notifications with valid VrTableName TLV
601+ // are parsed successfully without warnings.
602+ let mut data = BytesMut :: new ( ) ;
603+
604+ // Local address setup
605+ data. extend_from_slice ( & [
606+ 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0xC0 , 0xA8 ,
607+ 0x01 , 0x01 , // 192.168.1.1
608+ 0x00 , 0xB3 , // local port 179
609+ 0x00 , 0xB3 , // remote port 179
610+ ] ) ;
611+
612+ // Create BGP OPEN message with capabilities
613+ let bgp_open = crate :: models:: BgpMessage :: Open ( BgpOpenMessage {
614+ version : 4 ,
615+ asn : crate :: models:: Asn :: new_32bit ( 65001 ) ,
616+ hold_time : 180 ,
617+ sender_ip : Ipv4Addr :: new ( 192 , 168 , 1 , 1 ) ,
618+ extended_length : false ,
619+ opt_params : vec ! [ OptParam {
620+ param_type: 2 , // capability
621+ param_len: 6 ,
622+ param_value: ParamValue :: Capacities ( vec![ Capability {
623+ ty: BgpCapabilityType :: MULTIPROTOCOL_EXTENSIONS_FOR_BGP_4 ,
624+ value: CapabilityValue :: MultiprotocolExtensions (
625+ MultiprotocolExtensionsCapability {
626+ afi: Afi :: Ipv4 ,
627+ safi: Safi :: Unicast ,
628+ } ,
629+ ) ,
630+ } ] ) ,
631+ } ] ,
632+ } ) ;
633+ let bgp_bytes = bgp_open. encode ( AsnLength :: Bits32 ) ;
634+ data. extend_from_slice ( & bgp_bytes) ;
635+ data. extend_from_slice ( & bgp_bytes) ;
636+
637+ // Add VrTableName TLV with valid length (1-255 bytes)
638+ data. extend_from_slice ( & [ 0x00 , 0x03 ] ) ; // VrTableName TLV type
639+ data. extend_from_slice ( & [ 0x00 , 0x08 ] ) ; // length 8
640+ data. extend_from_slice ( b"LocalRIB" ) ;
641+
642+ let afi = Afi :: Ipv4 ;
643+ let asn_len = AsnLength :: Bits32 ;
644+ let peer_type = BmpPeerType :: LocalRib ;
645+
646+ let result = parse_peer_up_notification ( & mut data. freeze ( ) , & afi, & asn_len, Some ( & peer_type) ) ;
647+
648+ assert ! ( result. is_ok( ) , "Parsing should succeed" ) ;
649+
650+ let peer_notification = result. unwrap ( ) ;
651+ assert_eq ! ( peer_notification. tlvs. len( ) , 1 ) ;
652+ assert_eq ! ( peer_notification. tlvs[ 0 ] . info_type, PeerUpTlvType :: VrTableName ) ;
653+ assert_eq ! ( peer_notification. tlvs[ 0 ] . info_value, "LocalRIB" ) ;
654+ // No warnings should be logged with valid VrTableName
655+ }
656+
657+ #[ test]
658+ fn test_local_rib_with_empty_vr_table_name ( ) {
659+ // This test verifies that LocalRib peer up notifications with empty VrTableName
660+ // are parsed successfully. In production, a warning would be logged.
661+ let mut data = BytesMut :: new ( ) ;
662+
663+ // Local address setup
664+ data. extend_from_slice ( & [
665+ 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0xC0 , 0xA8 ,
666+ 0x01 , 0x01 , // 192.168.1.1
667+ 0x00 , 0xB3 , // local port 179
668+ 0x00 , 0xB3 , // remote port 179
669+ ] ) ;
670+
671+ // Create BGP OPEN message with capabilities
672+ let bgp_open = crate :: models:: BgpMessage :: Open ( BgpOpenMessage {
673+ version : 4 ,
674+ asn : crate :: models:: Asn :: new_32bit ( 65001 ) ,
675+ hold_time : 180 ,
676+ sender_ip : Ipv4Addr :: new ( 192 , 168 , 1 , 1 ) ,
677+ extended_length : false ,
678+ opt_params : vec ! [ OptParam {
679+ param_type: 2 , // capability
680+ param_len: 6 ,
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 ) ;
693+ data. extend_from_slice ( & bgp_bytes) ;
694+ data. extend_from_slice ( & bgp_bytes) ;
695+
696+ // Add VrTableName TLV with EMPTY value (RFC 9069 violation)
697+ data. extend_from_slice ( & [ 0x00 , 0x03 ] ) ; // VrTableName TLV type
698+ data. extend_from_slice ( & [ 0x00 , 0x00 ] ) ; // length 0 (empty)
699+
700+ let afi = Afi :: Ipv4 ;
701+ let asn_len = AsnLength :: Bits32 ;
702+ let peer_type = BmpPeerType :: LocalRib ;
703+
704+ let result = parse_peer_up_notification ( & mut data. freeze ( ) , & afi, & asn_len, Some ( & peer_type) ) ;
705+
706+ assert ! ( result. is_ok( ) , "Parsing should succeed" ) ;
707+ // In production, a warning would be logged about invalid VrTableName length
708+ }
709+
710+ #[ test]
711+ fn test_local_rib_with_oversized_vr_table_name ( ) {
712+ // This test verifies that LocalRib peer up notifications with oversized VrTableName
713+ // are parsed successfully. In production, a warning would be logged.
714+ let mut data = BytesMut :: new ( ) ;
715+
716+ // Local address setup
717+ data. extend_from_slice ( & [
718+ 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0xC0 , 0xA8 ,
719+ 0x01 , 0x01 , // 192.168.1.1
720+ 0x00 , 0xB3 , // local port 179
721+ 0x00 , 0xB3 , // remote port 179
722+ ] ) ;
723+
724+ // Create BGP OPEN message with capabilities
725+ let bgp_open = crate :: models:: BgpMessage :: Open ( BgpOpenMessage {
726+ version : 4 ,
727+ asn : crate :: models:: Asn :: new_32bit ( 65001 ) ,
728+ hold_time : 180 ,
729+ sender_ip : Ipv4Addr :: new ( 192 , 168 , 1 , 1 ) ,
730+ extended_length : false ,
731+ opt_params : vec ! [ OptParam {
732+ param_type: 2 , // capability
733+ param_len: 6 ,
734+ param_value: ParamValue :: Capacities ( vec![ Capability {
735+ ty: BgpCapabilityType :: MULTIPROTOCOL_EXTENSIONS_FOR_BGP_4 ,
736+ value: CapabilityValue :: MultiprotocolExtensions (
737+ MultiprotocolExtensionsCapability {
738+ afi: Afi :: Ipv4 ,
739+ safi: Safi :: Unicast ,
740+ } ,
741+ ) ,
742+ } ] ) ,
743+ } ] ,
744+ } ) ;
745+ let bgp_bytes = bgp_open. encode ( AsnLength :: Bits32 ) ;
746+ data. extend_from_slice ( & bgp_bytes) ;
747+ data. extend_from_slice ( & bgp_bytes) ;
748+
749+ // Add VrTableName TLV with oversized value (>255 bytes, RFC 9069 violation)
750+ let oversized_name = "A" . repeat ( 256 ) ;
751+ data. extend_from_slice ( & [ 0x00 , 0x03 ] ) ; // VrTableName TLV type
752+ data. extend_from_slice ( & [ 0x01 , 0x00 ] ) ; // length 256
753+ data. extend_from_slice ( oversized_name. as_bytes ( ) ) ;
754+
755+ let afi = Afi :: Ipv4 ;
756+ let asn_len = AsnLength :: Bits32 ;
757+ let peer_type = BmpPeerType :: LocalRib ;
758+
759+ let result = parse_peer_up_notification ( & mut data. freeze ( ) , & afi, & asn_len, Some ( & peer_type) ) ;
760+
761+ assert ! ( result. is_ok( ) , "Parsing should succeed" ) ;
762+ // In production, a warning would be logged about oversized VrTableName
763+ }
764+
765+ #[ test]
766+ fn test_non_local_rib_no_validation ( ) {
767+ // This test verifies that non-LocalRib peer types don't trigger LocalRib validations
768+ let mut data = BytesMut :: new ( ) ;
769+
770+ // Local address setup
771+ data. extend_from_slice ( & [
772+ 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0xC0 , 0xA8 ,
773+ 0x01 , 0x01 , // 192.168.1.1
774+ 0x00 , 0xB3 , // local port 179
775+ 0x00 , 0xB3 , // remote port 179
776+ ] ) ;
777+
778+ // Create BGP OPEN message WITHOUT capabilities (but not LocalRib, so no warning)
779+ let bgp_open = crate :: models:: BgpMessage :: Open ( BgpOpenMessage {
780+ version : 4 ,
781+ asn : crate :: models:: Asn :: new_32bit ( 65001 ) ,
782+ hold_time : 180 ,
783+ sender_ip : Ipv4Addr :: new ( 192 , 168 , 1 , 1 ) ,
784+ extended_length : false ,
785+ opt_params : vec ! [ ] ,
786+ } ) ;
787+ let bgp_bytes = bgp_open. encode ( AsnLength :: Bits32 ) ;
788+ data. extend_from_slice ( & bgp_bytes) ;
789+ data. extend_from_slice ( & bgp_bytes) ;
790+
791+ let afi = Afi :: Ipv4 ;
792+ let asn_len = AsnLength :: Bits32 ;
793+ let peer_type = BmpPeerType :: Global ; // Not LocalRib
794+
795+ let result = parse_peer_up_notification ( & mut data. freeze ( ) , & afi, & asn_len, Some ( & peer_type) ) ;
796+
797+ assert ! ( result. is_ok( ) , "Parsing should succeed" ) ;
798+ // No warnings should be logged for non-LocalRib peer types
799+ }
451800}
0 commit comments