// Copyright 2021 Rayhaan Jaufeerally. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use crate::constants::AddressFamilyIdentifier; use crate::constants::SubsequentAddressFamilyIdentifier; use crate::traits::BGPParserError; use crate::traits::ParserContext; use crate::traits::ReadablePacket; use crate::traits::WritablePacket; use byteorder::{ByteOrder, NetworkEndian}; use nom::number::complete::{be_u16, be_u8}; use nom::Err::Failure; use nom::IResult; use std::fmt; use std::fmt::Display; /// BGPOpenOptionType represents the option types in the Open message. #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash)] pub struct BGPOpenOptionType(pub u8); impl BGPOpenOptionType { pub fn new(val: u8) -> BGPOpenOptionType { BGPOpenOptionType(val) } } impl From for u8 { fn from(val: BGPOpenOptionType) -> Self { val.0 } } #[allow(non_snake_case)] #[allow(non_upper_case_globals)] pub mod BGPOpenOptionTypeValues { use super::BGPOpenOptionType; pub const CAPABILITIES: BGPOpenOptionType = BGPOpenOptionType(2); } #[derive(Debug, PartialEq)] pub struct OpenOption { pub option_type: BGPOpenOptionType, pub oval: OpenOptions, } impl ReadablePacket for OpenOption { fn from_wire<'a>( ctx: &ParserContext, buf: &'a [u8], ) -> IResult<&'a [u8], OpenOption, BGPParserError<&'a [u8]>> { let (buf, typ) = nom::combinator::complete(be_u8)(buf)?; let (buf, val) = match BGPOpenOptionType(typ) { BGPOpenOptionTypeValues::CAPABILITIES => { let (b, cap) = OpenOptionCapabilities::from_wire(ctx, buf)?; (b, OpenOptions::Capabilities(cap)) } _ => { // TODO: This should gracefully degrrrrade and not fail the parser. return Err(Failure(BGPParserError::CustomText( "Unknown BGP OPEN option".to_string(), ))); } }; IResult::Ok(( buf, OpenOption { option_type: BGPOpenOptionType(typ), oval: val, }, )) } } impl WritablePacket for OpenOption { fn to_wire(&self, ctx: &ParserContext) -> Result, &'static str> { let mut buf = Vec::new(); match &self.oval { OpenOptions::Capabilities(c) => { buf.push(BGPOpenOptionTypeValues::CAPABILITIES.into()); buf.append(&mut c.to_wire(ctx)?); } } Ok(buf) } fn wire_len(&self, ctx: &ParserContext) -> Result { match &self.oval { OpenOptions::Capabilities(c) => Ok(2 + c.wire_len(ctx)?), } } } impl Display for OpenOption { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "OpenOption: {}", self.oval) } } #[derive(Debug, PartialEq)] pub enum OpenOptions { Capabilities(OpenOptionCapabilities), } impl Display for OpenOptions { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self { OpenOptions::Capabilities(c) => write!(f, "Capabilities: {}", c), } } } /// CapabilityList represents a list of capabilities which can be present in an OpenOption. #[derive(Debug, PartialEq)] pub struct OpenOptionCapabilities { pub caps: Vec, } impl ReadablePacket for OpenOptionCapabilities { // from wire reads the length and value of the TLV. fn from_wire<'a>( ctx: &ParserContext, buf: &'a [u8], ) -> IResult<&'a [u8], OpenOptionCapabilities, BGPParserError<&'a [u8]>> { let (buf, caps): (_, Vec) = nom::multi::length_value( be_u8, nom::multi::many0(|i| BGPCapability::from_wire(ctx, i)), )(buf)?; IResult::Ok((buf, OpenOptionCapabilities { caps })) } } impl WritablePacket for OpenOptionCapabilities { // to_wire writes the length and value of the TLV. fn to_wire(&self, ctx: &ParserContext) -> Result, &'static str> { let mut buf: Vec = Vec::new(); buf.push(self.wire_len(ctx).unwrap() as u8); for cap in &self.caps { let mut result: Vec = (*cap).to_wire(ctx)?; buf.append(&mut result); } Ok(buf) } fn wire_len(&self, ctx: &ParserContext) -> Result { let mut ttl: u16 = 0; for cap in &self.caps { ttl += (*cap).wire_len(ctx)?; } Ok(ttl) } } impl Display for OpenOptionCapabilities { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Capabilities: [")?; for cap in &self.caps { std::fmt::Display::fmt(cap, f)?; } write!(f, "]") } } /// BGP Capabilities. #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash)] pub struct BGPCapabilityType(pub u8); impl BGPCapabilityType { pub fn new(val: u8) -> BGPCapabilityType { BGPCapabilityType(val) } } impl From for u8 { fn from(val: BGPCapabilityType) -> Self { val.0 } } #[allow(non_snake_case)] #[allow(non_upper_case_globals)] pub mod BGPCapabilityTypeValues { use super::BGPCapabilityType; /// Multiprotocol Extensions for BGP-4 [RFC2858] pub const MULTPROTOCOL_BGP4: BGPCapabilityType = BGPCapabilityType(1); /// Route Refresh Capability for BGP-4 [RFC2918] pub const ROUTE_REFRESH_BGP4: BGPCapabilityType = BGPCapabilityType(2); /// Outbound Route Filtering Capability [RFC5291] pub const OUTBOUND_ROUTE_FILTERING: BGPCapabilityType = BGPCapabilityType(3); /// Extended Next Hop Encoding [RFC8950] pub const EXTENDED_NEXT_HOP: BGPCapabilityType = BGPCapabilityType(5); /// BGP Extended Message [RFC8654] pub const EXTENDED_MESSAGE: BGPCapabilityType = BGPCapabilityType(6); /// BGPsec Capability [RFC8205] pub const BGPSEC: BGPCapabilityType = BGPCapabilityType(7); /// Multiple Labels Capability [RFC8277] pub const MULTILABEL_COMPAT: BGPCapabilityType = BGPCapabilityType(8); /// Graceful Restart Capability [RFC4724] pub const GRACEFUL_RESTART: BGPCapabilityType = BGPCapabilityType(64); /// Support for 4-octet AS number capability [RFC6793] pub const FOUR_BYTE_ASN: BGPCapabilityType = BGPCapabilityType(65); /// ADD-PATH Capability [RFC7911] pub const ADD_PATH: BGPCapabilityType = BGPCapabilityType(69); /// Enhanced Route Refresh Capability [RFC7313] pub const ENHANCED_ROUTE_REFRESH: BGPCapabilityType = BGPCapabilityType(70); } #[derive(Debug, PartialEq)] pub struct BGPCapability { pub cap_type: BGPCapabilityType, pub val: BGPCapabilityValue, } impl ReadablePacket for BGPCapability { fn from_wire<'a>( ctx: &ParserContext, buf: &'a [u8], ) -> IResult<&'a [u8], BGPCapability, BGPParserError<&'a [u8]>> { let (buf, cap_type) = nom::combinator::peek(be_u8)(buf)?; // Peek the type, if we know it, consume. let (buf, val): (_, BGPCapabilityValue) = match BGPCapabilityType(cap_type) { BGPCapabilityTypeValues::FOUR_BYTE_ASN => { let (buf, _) = be_u8(buf)?; // Consume type let (buf, cap) = nom::multi::length_value(be_u8, |i| { FourByteASNCapability::from_wire(ctx, i) })(buf)?; (buf, BGPCapabilityValue::FourByteASN(cap)) } BGPCapabilityTypeValues::MULTPROTOCOL_BGP4 => { let (buf, _) = be_u8(buf)?; let (buf, cap) = nom::multi::length_value(be_u8, |i| { MultiprotocolCapability::from_wire(ctx, i) })(buf)?; (buf, BGPCapabilityValue::Multiprotocol(cap)) } // TODO: Add extended next hop. BGPCapabilityTypeValues::ROUTE_REFRESH_BGP4 => { let (buf, _) = be_u8(buf)?; let (buf, cap) = nom::multi::length_value(be_u8, |i| { RouteRefreshCapability::from_wire(ctx, i) })(buf)?; (buf, BGPCapabilityValue::RouteRefresh(cap)) } BGPCapabilityTypeValues::GRACEFUL_RESTART => { let (buf, _) = be_u8(buf)?; let (buf, cap) = nom::multi::length_value(be_u8, |i| { GracefulRestartCapability::from_wire(ctx, i) })(buf)?; (buf, BGPCapabilityValue::GracefulRestart(cap)) } _ => { // If we do not know what this is, then put the bytes in an UnknownCapability. let (buf, cap) = UnknownCapability::from_wire(ctx, buf)?; (buf, BGPCapabilityValue::UnknownCapability(cap)) } }; IResult::Ok(( buf, BGPCapability { cap_type: BGPCapabilityType(cap_type), val, }, )) } } impl WritablePacket for BGPCapability { fn to_wire(&self, ctx: &ParserContext) -> Result, &'static str> { let mut buf: Vec = vec![]; buf.push(self.cap_type.into()); match &self.val { BGPCapabilityValue::FourByteASN(v) => { buf.push(v.wire_len(ctx)? as u8); buf.extend_from_slice(&v.to_wire(ctx)?); } BGPCapabilityValue::Multiprotocol(v) => { buf.push(v.wire_len(ctx)? as u8); buf.extend_from_slice(&v.to_wire(ctx)?); } BGPCapabilityValue::RouteRefresh(v) => { buf.push(v.wire_len(ctx)? as u8); buf.extend_from_slice(&v.to_wire(ctx)?); } BGPCapabilityValue::GracefulRestart(v) => { buf.push(v.wire_len(ctx)? as u8); buf.extend_from_slice(&v.to_wire(ctx)?); } BGPCapabilityValue::UnknownCapability(v) => { buf.push(v.wire_len(ctx)? as u8); buf.extend_from_slice(&v.to_wire(ctx)?); } }; Ok(buf) } fn wire_len(&self, ctx: &ParserContext) -> Result { // BGPCapabilityType(u8) + cap_len(u8) + val match &self.val { BGPCapabilityValue::FourByteASN(v) => Ok(2 + v.wire_len(ctx)?), BGPCapabilityValue::Multiprotocol(v) => Ok(2 + v.wire_len(ctx)?), BGPCapabilityValue::RouteRefresh(v) => Ok(2 + v.wire_len(ctx)?), BGPCapabilityValue::GracefulRestart(v) => Ok(2 + v.wire_len(ctx)?), BGPCapabilityValue::UnknownCapability(v) => Ok(2 + v.wire_len(ctx)?), } } } impl Display for BGPCapability { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { std::fmt::Display::fmt(&self.val, f) } } #[derive(Clone, Debug, PartialEq)] pub enum BGPCapabilityValue { FourByteASN(FourByteASNCapability), Multiprotocol(MultiprotocolCapability), RouteRefresh(RouteRefreshCapability), GracefulRestart(GracefulRestartCapability), UnknownCapability(UnknownCapability), } impl Display for BGPCapabilityValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self { BGPCapabilityValue::FourByteASN(v) => std::fmt::Display::fmt(v, f), BGPCapabilityValue::Multiprotocol(v) => std::fmt::Display::fmt(v, f), BGPCapabilityValue::RouteRefresh(v) => std::fmt::Display::fmt(v, f), BGPCapabilityValue::GracefulRestart(v) => std::fmt::Display::fmt(v, f), BGPCapabilityValue::UnknownCapability(v) => std::fmt::Display::fmt(v, f), } } } #[derive(Clone, Debug, PartialEq)] pub struct UnknownCapability { cap_code: u8, payload: Vec, } impl ReadablePacket for UnknownCapability { fn from_wire<'a>( _: &ParserContext, buf: &'a [u8], ) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> { let (buf, typ) = be_u8(buf)?; let (buf, len) = be_u8(buf)?; let (buf, payload) = nom::bytes::complete::take(len)(buf)?; Ok(( buf, UnknownCapability { cap_code: typ, payload: payload.to_vec(), }, )) } } impl WritablePacket for UnknownCapability { fn to_wire(&self, _: &ParserContext) -> Result, &'static str> { let mut buf = vec![]; // No need to push the type or length on as that's done at a higher level. buf.extend(self.payload.to_owned()); Ok(buf) } fn wire_len(&self, _: &ParserContext) -> Result { Ok(self.payload.len() as u16) } } impl Display for UnknownCapability { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "UnknownCapability type: {}", self.cap_code) } } /// FourByteASNCapability represents the four byte BGP Capability value. #[derive(Clone, Debug, PartialEq)] pub struct FourByteASNCapability { pub asn: u32, } impl FourByteASNCapability { fn new(asn: u32) -> FourByteASNCapability { FourByteASNCapability { asn } } } impl ReadablePacket for FourByteASNCapability { fn from_wire<'a>( _: &ParserContext, buf: &'a [u8], ) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> { let (buf, asn) = nom::combinator::complete(nom::number::complete::be_u32)(buf)?; IResult::Ok((buf, FourByteASNCapability::new(asn))) } } impl WritablePacket for FourByteASNCapability { fn to_wire(&self, _: &ParserContext) -> Result, &'static str> { let mut buf: Vec = vec![0; 4]; byteorder::NetworkEndian::write_u32(&mut buf, self.asn); Ok(buf) } fn wire_len(&self, _: &ParserContext) -> Result { Ok(4) } } impl Display for FourByteASNCapability { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "FourByteASN: asn: {}", self.asn) } } /// MultiprotocolExtCapability represents support for RFC 4760. #[derive(Clone, Debug, PartialEq)] pub struct MultiprotocolCapability { pub afi: AddressFamilyIdentifier, pub safi: SubsequentAddressFamilyIdentifier, } impl MultiprotocolCapability { fn new( afi: AddressFamilyIdentifier, safi: SubsequentAddressFamilyIdentifier, ) -> MultiprotocolCapability { MultiprotocolCapability { afi, safi } } } impl ReadablePacket for MultiprotocolCapability { fn from_wire<'a>( ctx: &ParserContext, buf: &'a [u8], ) -> IResult<&'a [u8], MultiprotocolCapability, BGPParserError<&'a [u8]>> { let (buf, (afi, _, safi)) = nom::combinator::complete(nom::sequence::tuple(( |i| AddressFamilyIdentifier::from_wire(ctx, i), nom::bytes::complete::take(1u8), |i| SubsequentAddressFamilyIdentifier::from_wire(ctx, i), )))(buf)?; IResult::Ok((buf, MultiprotocolCapability::new(afi, safi))) } } impl WritablePacket for MultiprotocolCapability { fn to_wire(&self, _: &ParserContext) -> Result, &'static str> { // [ AFI: uint16, 0: uint8, SAFI: uint8 ] let mut res = [0u8; 4]; byteorder::NetworkEndian::write_u16(&mut res[..2], self.afi.into()); res[3] = self.safi.into(); Ok(res.to_vec()) } fn wire_len(&self, _: &ParserContext) -> Result { Ok(4) } } impl Display for MultiprotocolCapability { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "MultiprotocolCapbility: [ {} {} ]", self.afi, self.safi,) } } // Route refresh capability #[derive(Clone, Debug, PartialEq)] pub struct RouteRefreshCapability {} impl WritablePacket for RouteRefreshCapability { fn to_wire(&self, _: &ParserContext) -> Result, &'static str> { Ok(vec![]) } fn wire_len(&self, _: &ParserContext) -> Result { Ok(0) } } impl ReadablePacket for RouteRefreshCapability { fn from_wire<'a>( _: &ParserContext, buf: &'a [u8], ) -> IResult<&'a [u8], RouteRefreshCapability, BGPParserError<&'a [u8]>> { IResult::Ok((buf, RouteRefreshCapability {})) } } impl Display for RouteRefreshCapability { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "RouteRefreshCapability") } } // Graceful restart capability #[derive(Clone, Debug, PartialEq)] pub struct GracefulRestartCapability { pub restart_state: bool, // 4 bits total, most sig bit here, rest reserved. pub restart_time_sec: u16, // 12 bits. pub payloads: Vec, } // GracefulRestartPayload represents the contents of the graceful restart cap. #[derive(Clone, Debug, PartialEq)] pub struct GracefulRestartPayload { pub afi: AddressFamilyIdentifier, pub safi: SubsequentAddressFamilyIdentifier, pub af_flags: bool, // 8 bits total, most significant bit used here. } impl ReadablePacket for GracefulRestartPayload { fn from_wire<'a>( ctx: &ParserContext, buf: &'a [u8], ) -> IResult<&'a [u8], GracefulRestartPayload, BGPParserError<&'a [u8]>> { let (buf, (afi, safi, flags)) = nom::combinator::complete(nom::sequence::tuple(( |i| AddressFamilyIdentifier::from_wire(ctx, i), |i| SubsequentAddressFamilyIdentifier::from_wire(ctx, i), be_u8, )))(buf)?; IResult::Ok(( buf, GracefulRestartPayload { afi, safi, af_flags: (0x80 & flags) != 0, }, )) } } impl WritablePacket for GracefulRestartPayload { fn to_wire(&self, _: &ParserContext) -> Result, &'static str> { let afi: u16 = self.afi.into(); let mut res = vec![0u8; 2]; byteorder::NetworkEndian::write_u16(res.as_mut(), afi); res.push(self.safi.into()); res.push(if self.af_flags { 0x80 } else { 0 }); Ok(res) } fn wire_len(&self, _: &ParserContext) -> Result { Ok(4) } } impl Display for GracefulRestartPayload { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "GracefulRestartPayload: [afi:{} safi:{} af_flags:{}]", self.afi, self.safi, self.af_flags ) } } impl ReadablePacket for GracefulRestartCapability { fn from_wire<'a>( ctx: &ParserContext, buf: &'a [u8], ) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> { let (buf, state_rt) = nom::combinator::complete(be_u16)(buf)?; let (buf, payloads): (_, Vec) = nom::multi::many0(|i| GracefulRestartPayload::from_wire(ctx, i))(buf)?; let restart_time_sec: u16 = 0x0fff & state_rt; // Lower 14 bits. let restart_state: bool = (0x8000 & state_rt) != 0; // highest bit IResult::Ok(( buf, GracefulRestartCapability { restart_state, restart_time_sec, payloads, }, )) } } impl WritablePacket for GracefulRestartCapability { fn to_wire(&self, ctx: &ParserContext) -> Result, &'static str> { let mut buf: Vec = vec![0u8; 2]; let state_rt: u16 = ((self.restart_state as u16) << 15) | (0xfff & self.restart_time_sec); NetworkEndian::write_u16(&mut buf, state_rt); for item in &self.payloads { buf.append(&mut item.to_wire(ctx)?); } Ok(buf) } fn wire_len(&self, _: &ParserContext) -> Result { Ok((2 + self.payloads.len() * 4) as u16) } } impl Display for GracefulRestartCapability { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "GracefulRestartCapability: [")?; for value in &self.payloads { fmt::Display::fmt(value, f)?; } write!(f, " ]") } } // RFC8950 - Advertising IPv4 NLRI with IPv6 next hop. // GracefulRestartPayload represents the contents of the graceful restart cap. #[derive(Clone, Debug, PartialEq)] pub struct ExtendedNextHopEncodingCapability { pub afi_safi_nhafi: Vec<( AddressFamilyIdentifier, SubsequentAddressFamilyIdentifier, AddressFamilyIdentifier, )>, } impl WritablePacket for ExtendedNextHopEncodingCapability { fn to_wire(&self, _ctx: &ParserContext) -> Result, &'static str> { Ok(self .afi_safi_nhafi .iter() .flat_map(|e| { Into::>::into(e.0) .into_iter() .chain(vec![0x00, Into::::into(e.1)]) .chain(Into::>::into(e.2)) .collect::>() }) .collect::>()) } fn wire_len(&self, _ctx: &ParserContext) -> Result { Ok((self.afi_safi_nhafi.len() * 6) as u16) } } impl ReadablePacket for ExtendedNextHopEncodingCapability { fn from_wire<'a>( ctx: &ParserContext, buf: &'a [u8], ) -> IResult<&'a [u8], Self, BGPParserError<&'a [u8]>> where Self: Sized, { let (buf, tuples) = nom::combinator::complete(nom::multi::many0(nom::sequence::tuple(( |i| AddressFamilyIdentifier::from_wire(ctx, i), |i| { let (buf, _) = be_u8(i)?; // Eat the 0 byte. SubsequentAddressFamilyIdentifier::from_wire(ctx, buf) }, |i| AddressFamilyIdentifier::from_wire(ctx, i), ))))(buf)?; IResult::Ok(( buf, Self { afi_safi_nhafi: tuples, }, )) } } impl Display for ExtendedNextHopEncodingCapability { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "ExtendednextHopEncodingCapability [")?; for entry in &self.afi_safi_nhafi { write!(f, "afi: {}, safi: {}, nhafi: {}", entry.0, entry.1, entry.2)?; } write!(f, "]") } } #[cfg(test)] mod tests { use super::BGPCapability; use super::BGPCapabilityTypeValues; use super::BGPCapabilityValue; use super::ExtendedNextHopEncodingCapability; use super::FourByteASNCapability; use super::OpenOption; use crate::constants::AddressFamilyIdentifier::Ipv6; use crate::traits::ParserContext; use crate::traits::ReadablePacket; #[test] fn test_four_byte_asn_capability() { let bytes: &[u8] = &[0x41, 0x04, 0x00, 0x00, 0x00, 0x2a]; let ctx = &ParserContext::default() .four_octet_asn(true) .nlri_mode(Ipv6); let (buf, result) = BGPCapability::from_wire(ctx, bytes).unwrap(); assert_eq!( result, BGPCapability { cap_type: BGPCapabilityTypeValues::FOUR_BYTE_ASN, val: BGPCapabilityValue::FourByteASN(FourByteASNCapability { asn: 42 }) } ); assert_eq!(buf.len(), 0); } #[test] fn test_open_options<'a>() { let option_bytes: &[u8] = &[ 0x02, 0x06, 0x01, 0x04, 0x00, 0x01, 0x00, 0x01, 0x02, 0x02, 0x80, 0x00, 0x02, 0x02, 0x02, 0x00, 0x02, 0x02, 0x46, 0x00, 0x02, 0x06, 0x41, 0x04, 0x00, 0x00, 0x00, 0x2a, ]; let ctx = &ParserContext::default() .four_octet_asn(true) .nlri_mode(Ipv6); let (_buf, result) = nom::multi::many0(|buf: &'a [u8]| OpenOption::from_wire(ctx, buf))(option_bytes) .unwrap(); let expected_str = "[OpenOption { option_type: BGPOpenOptionType(2), oval: Capabilities(OpenOptionCapabilities { caps: [BGPCapability { cap_type: BGPCapabilityType(1), val: Multiprotocol(MultiprotocolCapability { afi: Ipv4, safi: Unicast }) }] }) }, OpenOption { option_type: BGPOpenOptionType(2), oval: Capabilities(OpenOptionCapabilities { caps: [BGPCapability { cap_type: BGPCapabilityType(128), val: UnknownCapability(UnknownCapability { cap_code: 128, payload: [] }) }] }) }, OpenOption { option_type: BGPOpenOptionType(2), oval: Capabilities(OpenOptionCapabilities { caps: [BGPCapability { cap_type: BGPCapabilityType(2), val: RouteRefresh(RouteRefreshCapability) }] }) }, OpenOption { option_type: BGPOpenOptionType(2), oval: Capabilities(OpenOptionCapabilities { caps: [BGPCapability { cap_type: BGPCapabilityType(70), val: UnknownCapability(UnknownCapability { cap_code: 70, payload: [] }) }] }) }, OpenOption { option_type: BGPOpenOptionType(2), oval: Capabilities(OpenOptionCapabilities { caps: [BGPCapability { cap_type: BGPCapabilityType(65), val: FourByteASN(FourByteASNCapability { asn: 42 }) }] }) }]"; assert_eq!(format!("{:?}", result), expected_str); } #[test] fn test_extended_next_hop_encoding_capability() { let bytes: Vec = vec![0x00, 0x01, 0x00, 0x01, 0x00, 0x02]; let ctx = &ParserContext::default() .four_octet_asn(true) .nlri_mode(Ipv6); let (_, cap) = ExtendedNextHopEncodingCapability::from_wire(ctx, &bytes).unwrap(); let expected_str = "ExtendednextHopEncodingCapability [afi: Ipv4, safi: Unicast, nhafi: Ipv6]"; assert_eq!(expected_str, cap.to_string()); } }