Skip to content

Commit

Permalink
NewType wrapper for human readable communities
Browse files Browse the repository at this point in the history
- remove `human_serde` feature
- HumanReadableCommunity type with Serializer impl
- regular derive(Serialize) on Community
- generic communities() and all_communities() on Update
  • Loading branch information
density215 committed Dec 21, 2023
1 parent 0022cf8 commit 5c7bc2f
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 45 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ default = ["octseq"]
bgp = ["bytes", "log", "octseq", "const-str"]
bmp = ["bgp", "bytes", "chrono", "log", "octseq"]
std = []
human_serde = []

10 changes: 5 additions & 5 deletions src/bgp/aspath.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ use std::{error, fmt};

use crate::asn::{Asn, LargeAsnError};

#[cfg(feature = "human_serde")]
#[cfg(feature = "serde")]
use serde::ser::SerializeSeq;

#[cfg(feature = "human_serde")]
#[cfg(feature = "serde")]
use serde::{Serialize, Serializer};

use octseq::builder::{infallible, EmptyBuilder, FromBuilder, OctetsBuilder};
Expand All @@ -29,7 +29,7 @@ use crate::util::parser::ParseError;

pub type OwnedHop = Hop<Vec<u8>>;

#[cfg(feature = "human_serde")]
#[cfg(feature = "serde")]
pub trait SerializeForOperators: Serialize {
fn serialize_for_operator<S>(
&self,
Expand Down Expand Up @@ -420,7 +420,7 @@ impl fmt::Display for HopPath {
}
}

#[cfg(feature = "human_serde")]
#[cfg(feature = "serde")]
impl Serialize for HopPath {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
Expand All @@ -434,7 +434,7 @@ impl Serialize for HopPath {
}
}

#[cfg(feature = "human_serde")]
#[cfg(feature = "serde")]
impl SerializeForOperators for HopPath {
fn serialize_for_operator<S>(
&self,
Expand Down
92 changes: 81 additions & 11 deletions src/bgp/communities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ use std::str::FromStr;

use crate::asn::{Asn, Asn16, ParseAsnError};

#[cfg(feature = "human_serde")]
#[cfg(feature = "serde")]
use serde::{Serialize, Serializer};

#[cfg(feature = "human_serde")]
#[cfg(feature = "serde")]
pub trait SerializeForOperators: Serialize {
fn serialize_for_operator<S>(
&self,
Expand All @@ -108,10 +108,11 @@ pub trait SerializeForOperators: Serialize {
S: Serializer;
}

//--- Community --------------------------------------------------------------
//------------ Community -----------------------------------------------------

/// Standard and Extended/Large Communities variants.
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, )]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum Community {
Standard(StandardCommunity),
Extended(ExtendedCommunity),
Expand Down Expand Up @@ -228,14 +229,83 @@ impl Display for Community {
}
}

#[cfg(feature = "human_serde")]
impl Serialize for Community {
//------------ HumanReadableCommunity ---------------------------------------0

/// Wrapper around Community with a special Serde Serializer implementation
/// that produces better human-readable output and leaves out some details.
/// Example output JSON can be found in the Rotonda docs repo:
/// https://github.com/NLnetLabs/rotonda-doc/
///
/// the communities() and all_communities() on the Update struct will need to
/// have this type included when calling these methods, like so:
/// `my_update.communities::<HumanReadableCommunity>`.
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, )]
pub struct HumanReadableCommunity(pub Community);

impl From<Community> for HumanReadableCommunity {
fn from(value: Community) -> Self {
HumanReadableCommunity(value)
}
}

impl From<[u8; 4]> for HumanReadableCommunity {
fn from(raw: [u8; 4]) -> HumanReadableCommunity {
HumanReadableCommunity(Community::Standard(StandardCommunity(raw)))
}
}

impl From<StandardCommunity> for HumanReadableCommunity {
fn from(value: StandardCommunity) -> Self {
HumanReadableCommunity(Community::Standard(value))
}
}

impl From<Wellknown> for HumanReadableCommunity {
fn from(wk: Wellknown) -> Self {
HumanReadableCommunity(Community::Standard(wk.into()))
}
}

impl From<ExtendedCommunity> for HumanReadableCommunity {
fn from(value: ExtendedCommunity) -> Self {
HumanReadableCommunity(Community::Extended(value))
}
}

impl From<LargeCommunity> for HumanReadableCommunity {
fn from(value: LargeCommunity) -> Self {
HumanReadableCommunity(Community::Large(value))
}
}

impl From<Ipv6ExtendedCommunity> for HumanReadableCommunity {
fn from(value: Ipv6ExtendedCommunity) -> Self {
HumanReadableCommunity(Community::Ipv6Extended(value))
}
}

impl FromStr for HumanReadableCommunity {
type Err = ParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Community::from_str(s).map(Self)
}
}

impl Display for HumanReadableCommunity {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}

#[cfg(feature = "serde")]
impl Serialize for HumanReadableCommunity {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
match &self {
match &self.0 {
Community::Standard(c) => {
c.serialize_for_operator(serializer)
}
Expand Down Expand Up @@ -538,7 +608,7 @@ impl Display for StandardCommunity {

}

#[cfg(feature = "human_serde")]
#[cfg(feature = "serde")]
impl SerializeForOperators for StandardCommunity {
fn serialize_for_operator<S>(
&self,
Expand Down Expand Up @@ -1055,7 +1125,7 @@ impl Display for ExtendedCommunity {

// Serialize

#[cfg(feature = "human_serde")]
#[cfg(feature = "serde")]
impl SerializeForOperators for ExtendedCommunity {
fn serialize_for_operator<S>(
&self,
Expand Down Expand Up @@ -1445,7 +1515,7 @@ impl Display for LargeCommunity {

// Serialize

#[cfg(feature = "human_serde")]
#[cfg(feature = "serde")]
impl SerializeForOperators for LargeCommunity {
fn serialize_for_operator<S>(
&self,
Expand Down Expand Up @@ -1513,8 +1583,8 @@ impl From<ParseAsnError> for ParseError {
}

// Types used only by our own human_serialize feature to structure the
// serialized output differently than is done by the default Serializer.
#[cfg(feature = "human_serde")]
// serialized output differently than is done by derive(Serialize).
#[cfg(feature = "serde")]
mod ser {
#[derive(serde::Serialize)]
#[serde(rename = "value")]
Expand Down
60 changes: 35 additions & 25 deletions src/bgp/message/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ use crate::bgp::message::nlri::{self,
};

use core::ops::Range;
use std::marker::PhantomData;
use std::net::Ipv4Addr;
use crate::util::parser::ParseError;


use crate::bgp::communities::{
Community,
ExtendedCommunity, Ipv6ExtendedCommunity,
LargeCommunity
LargeCommunity,
};

/// BGP UPDATE message, variant of the [`Message`] enum.
Expand Down Expand Up @@ -747,8 +747,8 @@ impl<Octs: Octets> UpdateMessage<Octs> {
//--- Communities --------------------------------------------------------

/// Returns an iterator over Standard Communities (RFC1997), if any.
pub fn communities(&self)
-> Result<Option<CommunityIter<Octs::Range<'_>>>, ParseError>
pub fn communities<T: From<[u8; 4]>>(&self)
-> Result<Option<CommunityIter<Octs::Range<'_>, T>>, ParseError>
{
if let Some(WireformatPathAttribute::Communities(epa)) = self.path_attributes()?.get(PathAttributeType::Communities) {
let mut p = epa.value_into_parser();
Expand Down Expand Up @@ -799,22 +799,26 @@ impl<Octs: Octets> UpdateMessage<Octs> {
/// Returns an optional `Vec` containing all conventional, Extended and
/// Large communities, if any, or None if none of the three appear in the
/// path attributes of this message.
pub fn all_communities(&self) -> Result<Option<Vec<Community>>, ParseError> {
let mut res = Vec::<Community>::new();
pub fn all_communities<T>(&self) -> Result<Option<Vec<T>>, ParseError>
where T: From<[u8; 4]> +
From<ExtendedCommunity> +
From<LargeCommunity> +
From<Ipv6ExtendedCommunity> {
let mut res: Vec<T> = Vec::new();

if let Some(c) = self.communities()? {
res.append(&mut c.collect::<Vec<_>>());
}
if let Some(c) = self.ext_communities()? {
res.append(&mut c.map(Community::Extended).collect::<Vec<_>>());
res.append(&mut c.map(|c| c.into()).collect::<Vec<_>>());
}
if let Some(c) = self.ipv6_ext_communities()? {
res.append(
&mut c.map(Community::Ipv6Extended).collect::<Vec<_>>()
&mut c.map(|c| c.into()).collect::<Vec<_>>()
);
}
if let Some(c) = self.large_communities()? {
res.append(&mut c.map(Community::Large).collect::<Vec<_>>());
res.append(&mut c.map(|c| c.into()).collect::<Vec<_>>());
}

if res.is_empty() {
Expand All @@ -823,7 +827,6 @@ impl<Octs: Octets> UpdateMessage<Octs> {
Ok(Some(res))
}
}

}


Expand Down Expand Up @@ -1124,30 +1127,34 @@ pub enum FourOctetAsn {

/// Iterator for BGP UPDATE Communities.
///
/// Returns values of enum [`Community`], wrapping [`StandardCommunity`],
/// [`ExtendedCommunity`], [`LargeCommunity`] and well-known communities.
pub struct CommunityIter<Octs: Octets> {
/// Returns values of enum or struct [`T`], where T wraps or has variants
/// [`StandardCommunity`], [`ExtendedCommunity`], [`LargeCommunity`] or a
/// well-known community. T are most notably `Community` or
/// `HumanReadableCommunity`. In most cases you will need to explicitly state
/// the type of T.
pub struct CommunityIter<Octs: Octets, T> {
slice: Octs,
pos: usize,
_t: PhantomData<T>
}

impl<Octs: Octets> CommunityIter<Octs> {
impl<Octs: Octets, T: From<[u8; 4]>> CommunityIter<Octs, T> {
fn new(slice: Octs) -> Self {
CommunityIter { slice, pos: 0 }
CommunityIter { slice, pos: 0, _t: PhantomData }
}

fn get_community(&mut self) -> Community {
fn get_community(&mut self) -> T {
let mut buf = [0u8; 4];
buf[..].copy_from_slice(&self.slice.as_ref()[self.pos..self.pos+4]);
self.pos += 4;
buf.into()
}
}

impl<Octs: Octets> Iterator for CommunityIter<Octs> {
type Item = Community;
impl<Octs: Octets, T: From<[u8; 4]>> Iterator for CommunityIter<Octs, T> {
type Item = T;

fn next(&mut self) -> Option<Community> {
fn next(&mut self) -> Option<T> {
if self.pos == self.slice.as_ref().len() {
return None
}
Expand Down Expand Up @@ -1961,6 +1968,7 @@ mod tests {

#[test]
fn pa_communities() {
use crate::bgp::communities::Community;
// BGP UPDATE with 9 path attributes for 1 NLRI with Path Id,
// includes both normal communities and extended communities.
let buf = vec![
Expand Down Expand Up @@ -1996,11 +2004,11 @@ mod tests {
))
);

assert!(upd.communities().unwrap().is_some());
for c in upd.communities().unwrap().unwrap() {
assert!(upd.communities::<Community>().unwrap().is_some());
for c in upd.communities::<Community>().unwrap().unwrap() {
println!("{:?}", c);
}
assert!(upd.communities().unwrap().unwrap()
assert!(upd.communities::<Community>().unwrap().unwrap()
.eq([
StandardCommunity::new(42.into(), Tag::new(518)).into(),
Wellknown::NoExport.into(),
Expand Down Expand Up @@ -2081,6 +2089,8 @@ mod tests {

#[test]
fn chained_community_iters() {
use crate::bgp::communities::Community;

let buf = vec![
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
Expand All @@ -2105,10 +2115,10 @@ mod tests {
let upd: UpdateMessage<_> = Message::from_octets(&buf, Some(sc))
.unwrap().try_into().unwrap();

for c in upd.all_communities().unwrap().unwrap() {
for c in upd.all_communities::<Community>().unwrap().unwrap() {
println!("{}", c);
}
assert!(upd.all_communities().unwrap().unwrap()
assert!(upd.all_communities::<Community>().unwrap().unwrap()
.eq(&[
StandardCommunity::new(42.into(), Tag::new(518)).into(),
Wellknown::NoExport.into(),
Expand Down Expand Up @@ -2255,7 +2265,7 @@ mod tests {

// the MP_REACH_NLRI currently ends up as a ::Invalid path attribute
// variant, so the call to .mp_announcements() yields a Ok(None) and thus
// the second unwrap fails. Therefor, ignore for now:
// the second unwrap fails. Therefore, ignore for now:
#[ignore = "need to rethink this one because of API change"]
#[test]
fn unknown_afi_safi_announcements() {
Expand Down
6 changes: 3 additions & 3 deletions src/bgp/message/update_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1444,7 +1444,7 @@ mod tests {

use octseq::Parser;

use crate::addr::Prefix;
use crate::{addr::Prefix, bgp::communities::Community};
use crate::asn::Asn;
//use crate::bgp::communities::Wellknown;
use crate::bgp::message::nlri::BasicNlri;
Expand Down Expand Up @@ -1743,7 +1743,7 @@ mod tests {
a_cnt += pdu.announcements().unwrap().count();
assert!(pdu.local_pref().unwrap().is_some());
assert!(pdu.multi_exit_disc().unwrap().is_some());
assert_eq!(pdu.communities().unwrap().unwrap().count(), 300);
assert_eq!(pdu.communities::<Community>().unwrap().unwrap().count(), 300);
}

assert_eq!(a_cnt, prefixes_num.try_into().unwrap());
Expand Down Expand Up @@ -2187,7 +2187,7 @@ mod tests {
assert!(prev_type_code < type_code);
prev_type_code = type_code;
}
assert_eq!(pdu.communities().unwrap().unwrap().count(), 2);
assert_eq!(pdu.communities::<Community>().unwrap().unwrap().count(), 2);
}

// TODO also do fn check(raw: Bytes)
Expand Down

0 comments on commit 5c7bc2f

Please sign in to comment.