use crate::models::Asn;
use num_enum::{FromPrimitive, IntoPrimitive};
use std::fmt::{Display, Formatter};
use std::net::{Ipv4Addr, Ipv6Addr};
#[derive(Debug, PartialEq, Copy, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
pub enum MetaCommunity {
Plain(Community),
Extended(ExtendedCommunity),
Ipv6Extended(Ipv6AddrExtCommunity),
Large(LargeCommunity),
}
#[derive(Debug, PartialEq, Copy, Clone, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Community {
NoExport,
NoAdvertise,
NoExportSubConfed,
Custom(Asn, u16),
}
#[derive(Debug, PartialEq, Clone, Copy, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LargeCommunity {
pub global_admin: u32,
pub local_data: [u32; 2],
}
impl LargeCommunity {
pub fn new(global_admin: u32, local_data: [u32; 2]) -> LargeCommunity {
LargeCommunity {
global_admin,
local_data,
}
}
}
#[derive(Debug, FromPrimitive, IntoPrimitive, PartialEq, Eq, Hash, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(u8)]
pub enum ExtendedCommunityType {
TransitiveTwoOctetAs = 0x00,
TransitiveIpv4Addr = 0x01,
TransitiveFourOctetAs = 0x02,
TransitiveOpaque = 0x03,
NonTransitiveTwoOctetAs = 0x40,
NonTransitiveIpv4Addr = 0x41,
NonTransitiveFourOctetAs = 0x42,
NonTransitiveOpaque = 0x43,
#[num_enum(catch_all)]
Unknown(u8),
}
#[derive(Debug, PartialEq, Clone, Copy, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ExtendedCommunity {
TransitiveTwoOctetAs(TwoOctetAsExtCommunity),
TransitiveIpv4Addr(Ipv4AddrExtCommunity),
TransitiveFourOctetAs(FourOctetAsExtCommunity),
TransitiveOpaque(OpaqueExtCommunity),
NonTransitiveTwoOctetAs(TwoOctetAsExtCommunity),
NonTransitiveIpv4Addr(Ipv4AddrExtCommunity),
NonTransitiveFourOctetAs(FourOctetAsExtCommunity),
NonTransitiveOpaque(OpaqueExtCommunity),
FlowSpecTrafficRate(FlowSpecTrafficRate),
FlowSpecTrafficAction(FlowSpecTrafficAction),
FlowSpecRedirect(TwoOctetAsExtCommunity),
FlowSpecTrafficMarking(FlowSpecTrafficMarking),
Raw([u8; 8]),
}
impl ExtendedCommunity {
pub const fn community_type(&self) -> ExtendedCommunityType {
use ExtendedCommunityType::*;
match self {
ExtendedCommunity::TransitiveTwoOctetAs(_) => TransitiveTwoOctetAs,
ExtendedCommunity::TransitiveIpv4Addr(_) => TransitiveIpv4Addr,
ExtendedCommunity::TransitiveFourOctetAs(_) => TransitiveFourOctetAs,
ExtendedCommunity::TransitiveOpaque(_) => TransitiveOpaque,
ExtendedCommunity::NonTransitiveTwoOctetAs(_) => NonTransitiveTwoOctetAs,
ExtendedCommunity::NonTransitiveIpv4Addr(_) => NonTransitiveIpv4Addr,
ExtendedCommunity::NonTransitiveFourOctetAs(_) => NonTransitiveFourOctetAs,
ExtendedCommunity::NonTransitiveOpaque(_) => NonTransitiveOpaque,
ExtendedCommunity::FlowSpecTrafficRate(_) => NonTransitiveTwoOctetAs,
ExtendedCommunity::FlowSpecTrafficAction(_) => NonTransitiveTwoOctetAs,
ExtendedCommunity::FlowSpecRedirect(_) => NonTransitiveTwoOctetAs,
ExtendedCommunity::FlowSpecTrafficMarking(_) => NonTransitiveTwoOctetAs,
ExtendedCommunity::Raw(buffer) => Unknown(buffer[0]),
}
}
}
#[derive(Debug, PartialEq, Clone, Copy, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Ipv6AddrExtCommunity {
pub community_type: ExtendedCommunityType,
pub subtype: u8,
pub global_admin: Ipv6Addr,
pub local_admin: [u8; 2],
}
#[derive(Debug, PartialEq, Clone, Copy, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TwoOctetAsExtCommunity {
pub subtype: u8,
pub global_admin: Asn,
pub local_admin: [u8; 4],
}
#[derive(Debug, PartialEq, Clone, Copy, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FourOctetAsExtCommunity {
pub subtype: u8,
pub global_admin: Asn,
pub local_admin: [u8; 2],
}
#[derive(Debug, PartialEq, Clone, Copy, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Ipv4AddrExtCommunity {
pub subtype: u8,
pub global_admin: Ipv4Addr,
pub local_admin: [u8; 2],
}
#[derive(Debug, PartialEq, Clone, Copy, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct OpaqueExtCommunity {
pub subtype: u8,
pub value: [u8; 6],
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FlowSpecTrafficRate {
pub as_number: u16,
pub rate_bytes_per_sec: f32,
}
impl PartialEq for FlowSpecTrafficRate {
fn eq(&self, other: &Self) -> bool {
self.as_number == other.as_number
&& self.rate_bytes_per_sec.to_bits() == other.rate_bytes_per_sec.to_bits()
}
}
impl Eq for FlowSpecTrafficRate {}
#[derive(Debug, PartialEq, Clone, Copy, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FlowSpecTrafficAction {
pub as_number: u16,
pub terminal: bool,
pub sample: bool,
}
#[derive(Debug, PartialEq, Clone, Copy, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FlowSpecTrafficMarking {
pub as_number: u16,
pub dscp: u8,
}
impl FlowSpecTrafficRate {
pub fn new(as_number: u16, rate_bytes_per_sec: f32) -> Self {
Self {
as_number,
rate_bytes_per_sec,
}
}
pub fn discard(as_number: u16) -> Self {
Self {
as_number,
rate_bytes_per_sec: 0.0,
}
}
}
impl FlowSpecTrafficAction {
pub fn new(as_number: u16, terminal: bool, sample: bool) -> Self {
Self {
as_number,
terminal,
sample,
}
}
}
impl FlowSpecTrafficMarking {
pub fn new(as_number: u16, dscp: u8) -> Self {
Self {
as_number,
dscp: dscp & 0x3F,
} }
}
struct ToHexString<'a>(&'a [u8]);
impl Display for ToHexString<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for byte in self.0 {
write!(f, "{byte:02X}")?;
}
Ok(())
}
}
impl Display for Community {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Community::NoExport => write!(f, "no-export"),
Community::NoAdvertise => write!(f, "no-advertise"),
Community::NoExportSubConfed => write!(f, "no-export-sub-confed"),
Community::Custom(asn, value) => write!(f, "{asn}:{value}"),
}
}
}
impl Display for LargeCommunity {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:{}:{}",
self.global_admin, self.local_data[0], self.local_data[1]
)
}
}
impl Display for ExtendedCommunity {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let ec_type = u8::from(self.community_type());
match self {
ExtendedCommunity::TransitiveTwoOctetAs(ec)
| ExtendedCommunity::NonTransitiveTwoOctetAs(ec) => {
write!(
f,
"{}:{}:{}:{}",
ec_type,
ec.subtype,
ec.global_admin,
ToHexString(&ec.local_admin)
)
}
ExtendedCommunity::TransitiveIpv4Addr(ec)
| ExtendedCommunity::NonTransitiveIpv4Addr(ec) => {
write!(
f,
"{}:{}:{}:{}",
ec_type,
ec.subtype,
ec.global_admin,
ToHexString(&ec.local_admin)
)
}
ExtendedCommunity::TransitiveFourOctetAs(ec)
| ExtendedCommunity::NonTransitiveFourOctetAs(ec) => {
write!(
f,
"{}:{}:{}:{}",
ec_type,
ec.subtype,
ec.global_admin,
ToHexString(&ec.local_admin)
)
}
ExtendedCommunity::TransitiveOpaque(ec)
| ExtendedCommunity::NonTransitiveOpaque(ec) => {
write!(f, "{}:{}:{}", ec_type, ec.subtype, ToHexString(&ec.value))
}
ExtendedCommunity::FlowSpecTrafficRate(rate) => {
write!(
f,
"rate:{} bytes/sec (AS {})",
rate.rate_bytes_per_sec, rate.as_number
)
}
ExtendedCommunity::FlowSpecTrafficAction(action) => {
let mut flags = Vec::new();
if action.terminal {
flags.push("terminal");
}
if action.sample {
flags.push("sample");
}
write!(f, "action:{} (AS {})", flags.join(","), action.as_number)
}
ExtendedCommunity::FlowSpecRedirect(redirect) => {
write!(
f,
"redirect:AS{}:{}",
redirect.global_admin,
ToHexString(&redirect.local_admin)
)
}
ExtendedCommunity::FlowSpecTrafficMarking(marking) => {
write!(f, "mark:DSCP{} (AS {})", marking.dscp, marking.as_number)
}
ExtendedCommunity::Raw(ec) => {
write!(f, "{}", ToHexString(ec))
}
}
}
}
impl Display for Ipv6AddrExtCommunity {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}:{}:{}:{}",
u8::from(self.community_type),
self.subtype,
self.global_admin,
ToHexString(&self.local_admin)
)
}
}
impl Display for MetaCommunity {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
MetaCommunity::Plain(c) => write!(f, "{c}"),
MetaCommunity::Extended(c) => write!(f, "{c}"),
MetaCommunity::Large(c) => write!(f, "{c}"),
MetaCommunity::Ipv6Extended(c) => write!(f, "{c}"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_large_community_new() {
let global_admin = 56;
let local_data = [3, 4];
let large_comm = LargeCommunity::new(global_admin, local_data);
assert_eq!(large_comm.global_admin, global_admin);
assert_eq!(large_comm.local_data, local_data);
}
#[test]
fn test_extended_community_community_type() {
let two_octet_as_ext_comm = TwoOctetAsExtCommunity {
subtype: 0,
global_admin: Asn::new_32bit(0),
local_admin: [0; 4],
};
let extended_community = ExtendedCommunity::TransitiveTwoOctetAs(two_octet_as_ext_comm);
assert_eq!(
extended_community.community_type(),
ExtendedCommunityType::TransitiveTwoOctetAs
);
}
#[test]
fn test_display_community() {
assert_eq!(format!("{}", Community::NoExport), "no-export");
assert_eq!(format!("{}", Community::NoAdvertise), "no-advertise");
assert_eq!(
format!("{}", Community::NoExportSubConfed),
"no-export-sub-confed"
);
assert_eq!(
format!("{}", Community::Custom(Asn::new_32bit(64512), 100)),
"64512:100"
);
}
#[test]
fn test_display_large_community() {
let large_community = LargeCommunity::new(1, [2, 3]);
assert_eq!(format!("{large_community}"), "1:2:3");
}
#[test]
fn test_display_extended_community() {
let two_octet_as_ext_comm = TwoOctetAsExtCommunity {
subtype: 0,
global_admin: Asn::new_32bit(0),
local_admin: [0; 4],
};
let extended_community = ExtendedCommunity::TransitiveTwoOctetAs(two_octet_as_ext_comm);
assert_eq!(format!("{extended_community}"), "0:0:0:00000000");
let two_octet_as_ext_comm = TwoOctetAsExtCommunity {
subtype: 0,
global_admin: Asn::new_32bit(0),
local_admin: [0; 4],
};
let extended_community = ExtendedCommunity::NonTransitiveTwoOctetAs(two_octet_as_ext_comm);
assert_eq!(format!("{extended_community}"), "64:0:0:00000000");
let ipv4_ext_comm = Ipv4AddrExtCommunity {
subtype: 1,
global_admin: "192.168.1.1".parse().unwrap(),
local_admin: [5, 6],
};
let extended_community = ExtendedCommunity::TransitiveIpv4Addr(ipv4_ext_comm);
assert_eq!(format!("{extended_community}"), "1:1:192.168.1.1:0506");
let ipv4_ext_comm = Ipv4AddrExtCommunity {
subtype: 1,
global_admin: "192.168.1.1".parse().unwrap(),
local_admin: [5, 6],
};
let extended_community = ExtendedCommunity::NonTransitiveIpv4Addr(ipv4_ext_comm);
assert_eq!(format!("{extended_community}"), "65:1:192.168.1.1:0506");
let four_octet_as_ext_comm = FourOctetAsExtCommunity {
subtype: 2,
global_admin: Asn::new_32bit(64512),
local_admin: [7, 8],
};
let extended_community = ExtendedCommunity::TransitiveFourOctetAs(four_octet_as_ext_comm);
assert_eq!(format!("{extended_community}"), "2:2:64512:0708");
let four_octet_as_ext_comm = FourOctetAsExtCommunity {
subtype: 2,
global_admin: Asn::new_32bit(64512),
local_admin: [7, 8],
};
let extended_community =
ExtendedCommunity::NonTransitiveFourOctetAs(four_octet_as_ext_comm);
assert_eq!(format!("{extended_community}"), "66:2:64512:0708");
let opaque_ext_comm = OpaqueExtCommunity {
subtype: 3,
value: [9, 10, 11, 12, 13, 14],
};
let extended_community = ExtendedCommunity::TransitiveOpaque(opaque_ext_comm);
assert_eq!(format!("{extended_community}"), "3:3:090A0B0C0D0E");
let opaque_ext_comm = OpaqueExtCommunity {
subtype: 3,
value: [9, 10, 11, 12, 13, 14],
};
let extended_community = ExtendedCommunity::NonTransitiveOpaque(opaque_ext_comm);
assert_eq!(format!("{extended_community}"), "67:3:090A0B0C0D0E");
let raw_ext_comm = [0, 1, 2, 3, 4, 5, 6, 7];
let extended_community = ExtendedCommunity::Raw(raw_ext_comm);
assert_eq!(format!("{extended_community}"), "0001020304050607");
}
#[test]
fn test_display_ipv6_addr_ext_community() {
let ipv6_addr_ext_comm = Ipv6AddrExtCommunity {
community_type: ExtendedCommunityType::TransitiveTwoOctetAs,
subtype: 0,
global_admin: "2001:db8::8a2e:370:7334".parse().unwrap(),
local_admin: [0, 1],
};
assert_eq!(
format!("{ipv6_addr_ext_comm}"),
"0:0:2001:db8::8a2e:370:7334:0001"
);
}
#[test]
fn test_display_meta_community() {
let large_community = LargeCommunity::new(1, [2, 3]);
let meta_community = MetaCommunity::Large(large_community);
assert_eq!(format!("{meta_community}"), "1:2:3");
}
#[test]
fn test_to_hex_string() {
assert_eq!(format!("{}", ToHexString(&[])), "");
assert_eq!(format!("{}", ToHexString(&[0x0A])), "0A");
assert_eq!(format!("{}", ToHexString(&[0x0A, 0x0B, 0x0C])), "0A0B0C");
assert_eq!(format!("{}", ToHexString(&[0x00])), "00");
assert_eq!(format!("{}", ToHexString(&[0x10])), "10");
assert_eq!(
format!("{}", ToHexString(&[0x00, 0x0F, 0x10, 0xFF])),
"000F10FF"
);
}
#[test]
#[cfg(feature = "serde")]
fn test_serde() {
let meta_community = MetaCommunity::Large(LargeCommunity::new(1, [2, 3]));
let serialized = serde_json::to_string(&meta_community).unwrap();
let deserialized: MetaCommunity = serde_json::from_str(&serialized).unwrap();
assert_eq!(meta_community, deserialized);
let meta_community = MetaCommunity::Extended(ExtendedCommunity::TransitiveTwoOctetAs(
TwoOctetAsExtCommunity {
subtype: 0,
global_admin: Asn::new_32bit(0),
local_admin: [0; 4],
},
));
let serialized = serde_json::to_string(&meta_community).unwrap();
let deserialized: MetaCommunity = serde_json::from_str(&serialized).unwrap();
assert_eq!(meta_community, deserialized);
let meta_community = MetaCommunity::Plain(Community::NoExport);
let serialized = serde_json::to_string(&meta_community).unwrap();
let deserialized: MetaCommunity = serde_json::from_str(&serialized).unwrap();
assert_eq!(meta_community, deserialized);
let meta_community = MetaCommunity::Ipv6Extended(Ipv6AddrExtCommunity {
community_type: ExtendedCommunityType::TransitiveTwoOctetAs,
subtype: 0,
global_admin: "2001:db8::8a2e:370:7334".parse().unwrap(),
local_admin: [0, 1],
});
let serialized = serde_json::to_string(&meta_community).unwrap();
let deserialized: MetaCommunity = serde_json::from_str(&serialized).unwrap();
assert_eq!(meta_community, deserialized);
}
#[test]
fn test_flowspec_traffic_rate() {
let rate = FlowSpecTrafficRate::new(64512, 1000.0);
assert_eq!(rate.as_number, 64512);
assert_eq!(rate.rate_bytes_per_sec, 1000.0);
let discard = FlowSpecTrafficRate::discard(64512);
assert_eq!(discard.rate_bytes_per_sec, 0.0);
}
#[test]
fn test_flowspec_traffic_action() {
let action = FlowSpecTrafficAction::new(64512, true, false);
assert_eq!(action.as_number, 64512);
assert!(action.terminal);
assert!(!action.sample);
}
#[test]
fn test_flowspec_traffic_marking() {
let marking = FlowSpecTrafficMarking::new(64512, 46); assert_eq!(marking.as_number, 64512);
assert_eq!(marking.dscp, 46);
let masked = FlowSpecTrafficMarking::new(64512, 255);
assert_eq!(masked.dscp, 63); }
#[test]
fn test_flowspec_community_display() {
let rate = ExtendedCommunity::FlowSpecTrafficRate(FlowSpecTrafficRate::new(64512, 1000.0));
assert_eq!(format!("{}", rate), "rate:1000 bytes/sec (AS 64512)");
let action =
ExtendedCommunity::FlowSpecTrafficAction(FlowSpecTrafficAction::new(64512, true, true));
assert_eq!(format!("{}", action), "action:terminal,sample (AS 64512)");
let marking =
ExtendedCommunity::FlowSpecTrafficMarking(FlowSpecTrafficMarking::new(64512, 46));
assert_eq!(format!("{}", marking), "mark:DSCP46 (AS 64512)");
}
}