use std::convert::TryFrom;
use std::fmt;
use std::str::FromStr;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use routecore::asn::Asn;
use crate::repository::resources::{AsResources, IpResources};
use crate::repository::Cert;
use crate::repository::{
resources::{AsBlock, AsBlocks, AsBlocksBuilder, Ipv4Blocks, Ipv6Blocks},
roa::RoaIpAddress,
};
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct ResourceSet {
asn: AsBlocks,
#[cfg_attr(feature = "serde", serde(alias = "v4"))]
ipv4: Ipv4Blocks,
#[cfg_attr(feature = "serde", serde(alias = "v6"))]
ipv6: Ipv6Blocks,
}
impl ResourceSet {
pub fn new(asn: AsBlocks, ipv4: Ipv4Blocks, ipv6: Ipv6Blocks) -> Self {
ResourceSet { asn, ipv4, ipv6 }
}
pub fn from_strs(asn: &str, ipv4: &str, ipv6: &str) -> Result<Self, FromStrError> {
let asn = AsBlocks::from_str(asn).map_err(FromStrError::asn)?;
let ipv4 = Ipv4Blocks::from_str(ipv4).map_err(FromStrError::ipv4)?;
let ipv6 = Ipv6Blocks::from_str(ipv6).map_err(FromStrError::ipv6)?;
Ok(ResourceSet { asn, ipv4, ipv6 })
}
pub fn empty() -> ResourceSet {
Self::default()
}
pub fn all() -> ResourceSet {
ResourceSet {
asn: AsBlocks::all(),
ipv4: Ipv4Blocks::all(),
ipv6: Ipv6Blocks::all(),
}
}
pub fn is_empty(&self) -> bool {
self.asn.is_empty() && self.ipv4.is_empty() && self.ipv6.is_empty()
}
pub fn set_asn(&mut self, asn: AsBlocks) {
self.asn = asn;
}
pub fn set_ipv4(&mut self, ipv4: Ipv4Blocks) {
self.ipv4 = ipv4;
}
pub fn set_ipv6(&mut self, ipv6: Ipv6Blocks) {
self.ipv6 = ipv6;
}
pub fn asn(&self) -> &AsBlocks {
&self.asn
}
pub fn to_as_resources(&self) -> AsResources {
AsResources::blocks(self.asn.clone())
}
pub fn ipv4(&self) -> &Ipv4Blocks {
&self.ipv4
}
pub fn to_ip_resources_v4(&self) -> IpResources {
self.ipv4.to_ip_resources()
}
pub fn ipv6(&self) -> &Ipv6Blocks {
&self.ipv6
}
pub fn to_ip_resources_v6(&self) -> IpResources {
self.ipv6.to_ip_resources()
}
pub fn asn_opt(&self) -> Option<&AsBlocks> {
if self.asn.is_empty() {
None
} else {
Some(&self.asn)
}
}
pub fn ipv4_opt(&self) -> Option<&Ipv4Blocks> {
if self.ipv4.is_empty() {
None
} else {
Some(&self.ipv4)
}
}
pub fn ipv6_opt(&self) -> Option<&Ipv6Blocks> {
if self.ipv6.is_empty() {
None
} else {
Some(&self.ipv6)
}
}
pub fn contains(&self, other: &ResourceSet) -> bool {
self.asn.contains(other.asn())
&& self.ipv4.contains(&other.ipv4)
&& self.ipv6.contains(&other.ipv6)
}
pub fn contains_asn(&self, asn: Asn) -> bool {
let mut blocks = AsBlocksBuilder::new();
blocks.push(AsBlock::Id(asn));
let blocks = blocks.finalize();
self.asn.contains(&blocks)
}
pub fn contains_roa_address(&self, roa_address: &RoaIpAddress) -> bool {
self.ipv4.contains_roa(roa_address) || self.ipv6.contains_roa(roa_address)
}
pub fn union(&self, other: &ResourceSet) -> Self {
let asn = self.asn.union(&other.asn);
let ipv4 = self.ipv4.union(&other.ipv4).into();
let ipv6 = self.ipv6.union(&other.ipv6).into();
ResourceSet { asn, ipv4, ipv6 }
}
pub fn intersection(&self, other: &ResourceSet) -> Self {
let asn = self.asn.intersection(&other.asn);
let ipv4 = self.ipv4.intersection(&other.ipv4).into();
let ipv6 = self.ipv6.intersection(&other.ipv6).into();
ResourceSet { asn, ipv4, ipv6 }
}
pub fn difference(&self, other: &ResourceSet) -> ResourceDiff {
let added = ResourceSet {
asn: self.asn.difference(&other.asn),
ipv4: self.ipv4.difference(&other.ipv4).into(),
ipv6: self.ipv6.difference(&other.ipv6).into(),
};
let removed = ResourceSet {
asn: other.asn.difference(&self.asn),
ipv4: other.ipv4.difference(&self.ipv4).into(),
ipv6: other.ipv6.difference(&self.ipv6).into(),
};
ResourceDiff { added, removed }
}
}
impl Default for ResourceSet {
fn default() -> Self {
ResourceSet {
asn: AsBlocks::empty(),
ipv4: Ipv4Blocks::empty(),
ipv6: Ipv6Blocks::empty(),
}
}
}
impl fmt::Display for ResourceSet {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"asn: '{}', ipv4: '{}', ipv6: '{}'",
self.asn,
self.ipv4,
self.ipv6
)
}
}
impl TryFrom<&Cert> for ResourceSet {
type Error = InheritError;
fn try_from(cert: &Cert) -> Result<Self, Self::Error> {
let asn = match cert.as_resources().to_blocks() {
Ok(as_blocks) => as_blocks,
Err(_) => return Err(InheritError),
};
let ipv4 = match cert.v4_resources().to_blocks() {
Ok(blocks) => blocks,
Err(_) => return Err(InheritError),
}
.into();
let ipv6 = match cert.v6_resources().to_blocks() {
Ok(blocks) => blocks,
Err(_) => return Err(InheritError),
}
.into();
Ok(ResourceSet { asn, ipv4, ipv6 })
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct ResourceDiff {
added: ResourceSet,
removed: ResourceSet,
}
impl ResourceDiff {
pub fn is_empty(&self) -> bool {
self.added.is_empty() && self.removed.is_empty()
}
}
impl fmt::Display for ResourceDiff {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.is_empty() {
write!(f, "<no changes in resources>")?;
}
if !self.added.is_empty() {
write!(f, "Added:")?;
if !self.added.asn.is_empty() {
write!(f, " asn: {}", self.added.asn)?;
}
if !self.added.ipv4.is_empty() {
write!(f, " ipv4: {}", self.added.ipv4())?;
}
if !self.added.ipv6.is_empty() {
write!(f, " ipv6: {}", self.added.ipv6())?;
}
if !self.removed.is_empty() {
write!(f, " ")?;
}
}
if !self.removed.is_empty() {
write!(f, "Removed:")?;
if !self.removed.asn.is_empty() {
write!(f, " asn: {}", self.removed.asn)?;
}
if !self.removed.ipv4.is_empty() {
write!(f, " ipv4: {}", self.removed.ipv4())?;
}
if !self.removed.ipv6.is_empty() {
write!(f, " ipv6: {}", self.removed.ipv6())?;
}
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct InheritError;
impl fmt::Display for InheritError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "cannot determine resources for certificate using inherit")
}
}
impl std::error::Error for InheritError {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum FromStrError {
Asn(String),
Ipv4(String),
Ipv6(String)
}
impl FromStrError {
fn asn(e: super::asres::FromStrError) -> Self {
FromStrError::Asn(e.to_string())
}
fn ipv4(e: super::ipres::FromStrError) -> Self {
FromStrError::Ipv4(e.to_string())
}
fn ipv6(e: super::ipres::FromStrError) -> Self {
FromStrError::Ipv6(e.to_string())
}
}
impl fmt::Display for FromStrError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
FromStrError::Asn(e)
=> write!(f, "cannot parse ASN resources: {}", e),
FromStrError::Ipv4(e)
=> write!(f, "cannot parse IPv4 resources: {}", e),
FromStrError::Ipv6(e)
=> write!(f, "cannot parse IPv6 resources: {}", e),
}
}
}
impl std::error::Error for FromStrError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resource_set_intersection() {
let child_resources = ResourceSet::from_strs("AS65000", "10.0.0.0/8", "fd00::/8").unwrap();
let parent_resources = ResourceSet::all();
let intersection = parent_resources.intersection(&child_resources);
assert_eq!(intersection, child_resources);
}
#[test]
fn resource_set_difference() {
let set1_asns = "AS65000-AS65003, AS65005";
let set2_asns = "AS65000, AS65003, AS65005";
let asn_added = "AS65001-AS65002";
let set1_ipv4s = "10.0.0.0-10.4.5.6, 192.168.0.0";
let set2_ipv4s = "10.0.0.0/8, 192.168.0.0";
let ipv4_removed = "10.4.5.7-10.255.255.255";
let set1_ipv6s = "::1, 2001:db8::/32";
let set2_ipv6s = "::1, 2001:db8::/56";
let ipv6_added = "2001:db8:0:100::-2001:db8:ffff:ffff:ffff:ffff:ffff:ffff";
let set1 = ResourceSet::from_strs(set1_asns, set1_ipv4s, set1_ipv6s).unwrap();
let set2 = ResourceSet::from_strs(set2_asns, set2_ipv4s, set2_ipv6s).unwrap();
let diff = set1.difference(&set2);
let expected_diff = ResourceDiff {
added: ResourceSet::from_strs(asn_added, "", ipv6_added).unwrap(),
removed: ResourceSet::from_strs("", ipv4_removed, "").unwrap(),
};
assert!(!diff.is_empty());
assert_eq!(expected_diff, diff);
}
#[test]
fn resource_set_eq() {
let asns = "AS65000-AS65003, AS65005";
let ipv4s = "10.0.0.0/8, 192.168.0.0";
let ipv6s = "::1, 2001:db8::/32";
let resource_set = ResourceSet::from_strs(asns, ipv4s, ipv6s).unwrap();
let asns_2 = "AS65000-AS65003";
let ipv4s_2 = "192.168.0.0";
let ipv6s_2 = "2001:db8::/32";
let resource_set_asn_differs = ResourceSet::from_strs(asns_2, ipv4s, ipv6s).unwrap();
let resource_set_v4_differs = ResourceSet::from_strs(asns, ipv4s_2, ipv6s).unwrap();
let resource_set_v6_differs = ResourceSet::from_strs(asns, ipv4s, ipv6s_2).unwrap();
let resource_set_2 = ResourceSet::from_strs(asns_2, ipv4s_2, ipv6s_2).unwrap();
assert_ne!(resource_set, resource_set_asn_differs);
assert_ne!(resource_set, resource_set_v4_differs);
assert_ne!(resource_set, resource_set_v6_differs);
assert_ne!(resource_set, resource_set_2);
let default_set = ResourceSet::default();
let certified = ResourceSet::from_strs(
"",
"10.0.0.0/16, 192.168.0.0/16",
"2001:db8::/32, 2000:db8::/32",
)
.unwrap();
assert_ne!(default_set, certified);
assert_ne!(resource_set, certified);
}
#[test]
fn resource_set_equivalent() {
let asns_1 = "AS65000-AS65003, AS65005";
let ipv4_1 = "10.0.0.0/8, 192.168.0.0";
let ipv6_1 = "::1, 2001:db8::/32";
let asns_2 = "AS65005, AS65001-AS65003, AS65000";
let ipv4_2 = "192.168.0.0, 10.0.0.0/8, ";
let ipv6_2 = "2001:db8::/32, ::1";
let set_1 = ResourceSet::from_strs(asns_1, ipv4_1, ipv6_1).unwrap();
let set_2 = ResourceSet::from_strs(asns_2, ipv4_2, ipv6_2).unwrap();
assert_eq!(set_1, set_2);
}
#[cfg(feature = "serde")]
#[test]
fn serialize_deserialize_resource_set() {
let asns = "AS65000-AS65003, AS65005";
let ipv4s = "10.0.0.0/8, 192.168.0.0";
let ipv6s = "::1, 2001:db8::/32";
let set = ResourceSet::from_strs(asns, ipv4s, ipv6s).unwrap();
let json = serde_json::to_string(&set).unwrap();
let deser_set = serde_json::from_str(&json).unwrap();
assert_eq!(set, deser_set);
}
}