use std::{cmp::Ordering, fmt, net::IpAddr, ops::Deref, str::FromStr};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use rpki::repository::{
resources::{AsBlocks, Asn, IpBlocks, IpBlocksBuilder, Prefix, ResourceSet},
roa::RoaIpAddress,
};
use crate::daemon::ca::RoaInfo;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct RoaAggregateKey {
asn: AsNumber,
group: Option<u32>,
}
impl RoaAggregateKey {
pub fn new(asn: AsNumber, group: Option<u32>) -> Self {
RoaAggregateKey { asn, group }
}
pub fn asn(&self) -> AsNumber {
self.asn
}
pub fn group(&self) -> Option<u32> {
self.group
}
}
impl fmt::Display for RoaAggregateKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.group {
None => write!(f, "AS{}", self.asn),
Some(nr) => write!(f, "AS{}-{}", self.asn, nr),
}
}
}
impl FromStr for RoaAggregateKey {
type Err = RoaAggregateKeyFmtError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.split('-');
let asn_part = parts.next().ok_or_else(|| RoaAggregateKeyFmtError::string(s))?;
if !asn_part.starts_with("AS") || asn_part.len() < 3 {
return Err(RoaAggregateKeyFmtError::string(s));
}
let asn = AsNumber::from_str(&asn_part[2..]).map_err(|_| RoaAggregateKeyFmtError::string(s))?;
let group = if let Some(group) = parts.next() {
let group = u32::from_str(group).map_err(|_| RoaAggregateKeyFmtError::string(s))?;
Some(group)
} else {
None
};
if parts.next().is_some() {
Err(RoaAggregateKeyFmtError::string(s))
} else {
Ok(RoaAggregateKey { asn, group })
}
}
}
impl Serialize for RoaAggregateKey {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.to_string().serialize(s)
}
}
impl<'de> Deserialize<'de> for RoaAggregateKey {
fn deserialize<D>(d: D) -> Result<RoaAggregateKey, D::Error>
where
D: Deserializer<'de>,
{
let string = String::deserialize(d)?;
RoaAggregateKey::from_str(string.as_str()).map_err(de::Error::custom)
}
}
impl Ord for RoaAggregateKey {
fn cmp(&self, other: &Self) -> Ordering {
match self.asn.cmp(&other.asn) {
Ordering::Equal => self.group.cmp(&other.group),
Ordering::Greater => Ordering::Greater,
Ordering::Less => Ordering::Less,
}
}
}
impl PartialOrd for RoaAggregateKey {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RoaAggregateKeyFmtError(String);
impl fmt::Display for RoaAggregateKeyFmtError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Invalid ROA Group format ({})", self.0)
}
}
impl RoaAggregateKeyFmtError {
fn string(s: &str) -> Self {
RoaAggregateKeyFmtError(s.to_string())
}
}
#[derive(Clone, Copy, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct RoaPayload {
asn: AsNumber,
prefix: TypedPrefix,
#[serde(skip_serializing_if = "Option::is_none")]
max_length: Option<u8>,
}
impl RoaPayload {
pub fn new(asn: AsNumber, prefix: TypedPrefix, max_length: Option<u8>) -> Self {
RoaPayload {
asn,
prefix,
max_length,
}
}
pub fn into_explicit_max_length(self) -> Self {
RoaPayload {
asn: self.asn,
prefix: self.prefix,
max_length: Some(self.effective_max_length()),
}
}
pub fn as_roa_ip_address(&self) -> RoaIpAddress {
RoaIpAddress::new(*self.prefix.prefix(), self.max_length)
}
pub fn asn(&self) -> AsNumber {
self.asn
}
pub fn prefix(&self) -> TypedPrefix {
self.prefix
}
pub fn max_length(&self) -> Option<u8> {
self.max_length
}
pub fn effective_max_length(&self) -> u8 {
match self.max_length {
None => self.prefix.addr_len(),
Some(len) => len,
}
}
pub fn nr_of_specific_prefixes(&self) -> u128 {
let pfx_len = self.prefix.addr_len();
let max_len = self.effective_max_length();
1u128 << (max_len - pfx_len)
}
pub fn max_length_valid(&self) -> bool {
if let Some(max_length) = self.max_length {
match self.prefix {
TypedPrefix::V4(_) => max_length >= self.prefix.addr_len() && max_length <= 32,
TypedPrefix::V6(_) => max_length >= self.prefix.addr_len() && max_length <= 128,
}
} else {
true
}
}
pub fn includes(&self, other: &RoaPayload) -> bool {
self.asn == other.asn
&& self.prefix.matching_or_less_specific(&other.prefix)
&& self.effective_max_length() >= other.effective_max_length()
}
pub fn overlaps(&self, other: &RoaPayload) -> bool {
self.prefix.matching_or_less_specific(&other.prefix) || other.prefix.matching_or_less_specific(&self.prefix)
}
}
impl FromStr for RoaPayload {
type Err = AuthorizationFmtError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.split("=>");
let prefix_part = parts.next().ok_or_else(|| AuthorizationFmtError::auth(s))?;
let mut prefix_parts = prefix_part.split('-');
let prefix_str = prefix_parts.next().ok_or_else(|| AuthorizationFmtError::auth(s))?;
let prefix = TypedPrefix::from_str(prefix_str.trim())?;
let max_length = match prefix_parts.next() {
None => None,
Some(length_str) => Some(u8::from_str(length_str.trim()).map_err(|_| AuthorizationFmtError::auth(s))?),
};
let asn_str = parts.next().ok_or_else(|| AuthorizationFmtError::auth(s))?;
if parts.next().is_some() {
return Err(AuthorizationFmtError::auth(s));
}
let origin = AsNumber::from_str(asn_str.trim())?;
Ok(RoaPayload {
asn: origin,
prefix,
max_length,
})
}
}
impl fmt::Debug for RoaPayload {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", &self)
}
}
impl fmt::Display for RoaPayload {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.max_length {
None => write!(f, "{} => {}", self.prefix, self.asn),
Some(length) => write!(f, "{}-{} => {}", self.prefix, length, self.asn),
}
}
}
impl Ord for RoaPayload {
fn cmp(&self, other: &Self) -> Ordering {
let mut ordering = self.prefix.cmp(&other.prefix);
if ordering == Ordering::Equal {
ordering = self.effective_max_length().cmp(&other.effective_max_length());
}
if ordering == Ordering::Equal {
ordering = self.asn.cmp(&other.asn);
}
ordering
}
}
impl PartialOrd for RoaPayload {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl AsRef<TypedPrefix> for RoaPayload {
fn as_ref(&self) -> &TypedPrefix {
&self.prefix
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct RoaConfiguration {
#[serde(flatten)]
payload: RoaPayload,
#[serde(default)] comment: Option<String>,
}
impl RoaConfiguration {
pub fn new(payload: RoaPayload, comment: Option<String>) -> Self {
RoaConfiguration { payload, comment }
}
pub fn unpack(self) -> (RoaPayload, Option<String>) {
(self.payload, self.comment)
}
pub fn payload(&self) -> RoaPayload {
self.payload
}
pub fn comment(&self) -> Option<&String> {
self.comment.as_ref()
}
pub fn into_explicit_max_length(self) -> Self {
RoaConfiguration {
payload: self.payload.into_explicit_max_length(),
comment: self.comment,
}
}
}
impl fmt::Display for RoaConfiguration {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.payload)?;
if let Some(comment) = &self.comment {
write!(f, " # {}", comment)?;
}
Ok(())
}
}
impl FromStr for RoaConfiguration {
type Err = AuthorizationFmtError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.splitn(2, '#');
let payload_part = parts.next().ok_or_else(|| AuthorizationFmtError::auth(s))?;
let payload = RoaPayload::from_str(payload_part)?;
let comment = parts.next().map(|s| s.trim().to_string());
Ok(RoaConfiguration { payload, comment })
}
}
impl Ord for RoaConfiguration {
fn cmp(&self, other: &Self) -> Ordering {
self.payload.cmp(&other.payload)
}
}
impl PartialOrd for RoaConfiguration {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl From<RoaPayload> for RoaConfiguration {
fn from(payload: RoaPayload) -> Self {
RoaConfiguration { payload, comment: None }
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ConfiguredRoa {
#[serde(flatten)]
roa_configuration: RoaConfiguration,
roa_objects: Vec<RoaInfo>,
}
impl ConfiguredRoa {
pub fn new(roa_configuration: RoaConfiguration, roa_objects: Vec<RoaInfo>) -> Self {
ConfiguredRoa {
roa_configuration,
roa_objects,
}
}
pub fn roa_configuration(&self) -> &RoaConfiguration {
&self.roa_configuration
}
pub fn payload(&self) -> RoaPayload {
self.roa_configuration.payload
}
pub fn asn(&self) -> AsNumber {
self.roa_configuration.payload.asn()
}
pub fn prefix(&self) -> TypedPrefix {
self.roa_configuration.payload.prefix()
}
pub fn effective_max_length(&self) -> u8 {
self.roa_configuration.payload.effective_max_length()
}
pub fn nr_of_specific_prefixes(&self) -> u128 {
self.roa_configuration.payload.nr_of_specific_prefixes()
}
pub fn as_roa_ip_address(&self) -> RoaIpAddress {
self.roa_configuration.payload().as_roa_ip_address()
}
pub fn roa_objects(&self) -> &Vec<RoaInfo> {
&self.roa_objects
}
}
impl AsRef<RoaConfiguration> for ConfiguredRoa {
fn as_ref(&self) -> &RoaConfiguration {
&self.roa_configuration
}
}
impl fmt::Display for ConfiguredRoa {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.roa_configuration)
}
}
impl Ord for ConfiguredRoa {
fn cmp(&self, other: &Self) -> Ordering {
self.roa_configuration.cmp(&other.roa_configuration)
}
}
impl PartialOrd for ConfiguredRoa {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ConfiguredRoas(Vec<ConfiguredRoa>);
impl ConfiguredRoas {
pub fn unpack(self) -> Vec<ConfiguredRoa> {
self.0
}
}
impl fmt::Display for ConfiguredRoas {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for def in self.0.iter() {
writeln!(f, "{}", def)?;
}
Ok(())
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct RoaConfigurationUpdates {
added: Vec<RoaConfiguration>,
removed: Vec<RoaPayload>,
}
impl RoaConfigurationUpdates {
pub fn is_empty(&self) -> bool {
self.added.is_empty() && self.removed.is_empty()
}
pub fn new(added: Vec<RoaConfiguration>, removed: Vec<RoaPayload>) -> Self {
RoaConfigurationUpdates { added, removed }
}
pub fn into_explicit_max_length(self) -> Self {
let added = self.added.into_iter().map(|a| a.into_explicit_max_length()).collect();
let removed = self.removed.into_iter().map(|r| r.into_explicit_max_length()).collect();
RoaConfigurationUpdates { added, removed }
}
pub fn affected_prefixes(&self) -> ResourceSet {
let mut resources = ResourceSet::default();
for roa_config in &self.added {
resources = resources.union(&roa_config.payload().prefix().into());
}
for roa_payload in &self.removed {
resources = resources.union(&roa_payload.prefix().into());
}
resources
}
pub fn unpack(self) -> (Vec<RoaConfiguration>, Vec<RoaPayload>) {
(self.added, self.removed)
}
pub fn empty() -> Self {
Self::default()
}
pub fn add(&mut self, add: RoaConfiguration) {
self.added.push(add);
}
pub fn added(&self) -> &Vec<RoaConfiguration> {
&self.added
}
pub fn removed(&self) -> &Vec<RoaPayload> {
&self.removed
}
pub fn remove(&mut self, rem: RoaPayload) {
self.removed.push(rem);
}
}
impl fmt::Display for RoaConfigurationUpdates {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for a in &self.added {
writeln!(f, "A: {}", a)?;
}
for r in &self.removed {
writeln!(f, "R: {}", r)?;
}
Ok(())
}
}
impl FromStr for RoaConfigurationUpdates {
type Err = AuthorizationFmtError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut added = vec![];
let mut removed = vec![];
for line in s.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
} else if let Some(stripped) = line.strip_prefix("A:") {
let auth = RoaConfiguration::from_str(stripped.trim())?;
added.push(auth);
} else if let Some(stripped) = line.strip_prefix("R:") {
if let Some(payload_str) = stripped.split('#').next() {
let auth = RoaPayload::from_str(payload_str.trim())?;
removed.push(auth);
} else {
return Err(AuthorizationFmtError::delta(line));
}
} else {
return Err(AuthorizationFmtError::delta(line));
}
}
Ok(RoaConfigurationUpdates { added, removed })
}
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub enum TypedPrefix {
V4(Ipv4Prefix),
V6(Ipv6Prefix),
}
impl TypedPrefix {
pub fn prefix(&self) -> &Prefix {
self.as_ref()
}
pub fn ip_addr(&self) -> IpAddr {
match self {
TypedPrefix::V4(v4) => IpAddr::V4(v4.0.to_v4()),
TypedPrefix::V6(v6) => IpAddr::V6(v6.0.to_v6()),
}
}
fn matches_type(&self, other: &TypedPrefix) -> bool {
match &self {
TypedPrefix::V4(_) => match other {
TypedPrefix::V4(_) => true,
TypedPrefix::V6(_) => false,
},
TypedPrefix::V6(_) => match other {
TypedPrefix::V4(_) => false,
TypedPrefix::V6(_) => true,
},
}
}
pub fn matching_or_less_specific(&self, other: &TypedPrefix) -> bool {
self.matches_type(other)
&& self.prefix().min().le(&other.prefix().min())
&& self.prefix().max().ge(&other.prefix().max())
}
}
impl FromStr for TypedPrefix {
type Err = AuthorizationFmtError;
fn from_str(prefix: &str) -> Result<Self, Self::Err> {
if prefix.contains('.') {
Ok(TypedPrefix::V4(Ipv4Prefix(
Prefix::from_v4_str(prefix.trim()).map_err(|_| AuthorizationFmtError::pfx(prefix))?,
)))
} else {
Ok(TypedPrefix::V6(Ipv6Prefix(
Prefix::from_v6_str(prefix.trim()).map_err(|_| AuthorizationFmtError::pfx(prefix))?,
)))
}
}
}
impl fmt::Debug for TypedPrefix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", &self)
}
}
impl fmt::Display for TypedPrefix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
TypedPrefix::V4(pfx) => pfx.fmt(f),
TypedPrefix::V6(pfx) => pfx.fmt(f),
}
}
}
impl Ord for TypedPrefix {
fn cmp(&self, other: &Self) -> Ordering {
let mut ordering = self.addr().cmp(&other.addr());
if ordering == Ordering::Equal {
ordering = self.addr_len().cmp(&other.addr_len())
}
ordering
}
}
impl PartialOrd for TypedPrefix {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl AsRef<Prefix> for TypedPrefix {
fn as_ref(&self) -> &Prefix {
match self {
TypedPrefix::V4(v4) => &v4.0,
TypedPrefix::V6(v6) => &v6.0,
}
}
}
impl Deref for TypedPrefix {
type Target = Prefix;
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl Serialize for TypedPrefix {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.to_string().serialize(s)
}
}
impl<'de> Deserialize<'de> for TypedPrefix {
fn deserialize<D>(d: D) -> Result<TypedPrefix, D::Error>
where
D: Deserializer<'de>,
{
let string = String::deserialize(d)?;
TypedPrefix::from_str(string.as_str()).map_err(de::Error::custom)
}
}
impl From<TypedPrefix> for ResourceSet {
fn from(tp: TypedPrefix) -> ResourceSet {
match tp {
TypedPrefix::V4(v4) => {
let mut builder = IpBlocksBuilder::new();
builder.push(v4.0);
let blocks = builder.finalize();
ResourceSet::new(AsBlocks::empty(), blocks.into(), IpBlocks::empty().into())
}
TypedPrefix::V6(v6) => {
let mut builder = IpBlocksBuilder::new();
builder.push(v6.0);
let blocks = builder.finalize();
ResourceSet::new(AsBlocks::empty(), IpBlocks::empty().into(), blocks.into())
}
}
}
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct Ipv4Prefix(Prefix);
impl AsRef<Prefix> for Ipv4Prefix {
fn as_ref(&self) -> &Prefix {
&self.0
}
}
impl fmt::Display for Ipv4Prefix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}/{}", self.0.to_v4(), self.0.addr_len())
}
}
impl fmt::Debug for Ipv4Prefix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", &self)
}
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct Ipv6Prefix(Prefix);
impl AsRef<Prefix> for Ipv6Prefix {
fn as_ref(&self) -> &Prefix {
&self.0
}
}
impl fmt::Display for Ipv6Prefix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}/{}", self.0.to_v6(), self.0.addr_len())
}
}
impl fmt::Debug for Ipv6Prefix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", &self)
}
}
#[derive(Clone, Copy, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct AsNumber(u32);
impl AsNumber {
pub fn new(number: u32) -> Self {
AsNumber(number)
}
pub fn zero() -> Self {
AsNumber(0)
}
}
impl From<AsNumber> for Asn {
fn from(asn: AsNumber) -> Self {
Asn::from(asn.0)
}
}
impl FromStr for AsNumber {
type Err = AuthorizationFmtError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim();
let number = u32::from_str(s).map_err(|_| AuthorizationFmtError::asn(s))?;
Ok(AsNumber(number))
}
}
impl fmt::Debug for AsNumber {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", &self)
}
}
impl fmt::Display for AsNumber {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Ord for AsNumber {
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
impl PartialOrd for AsNumber {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum AuthorizationFmtError {
Pfx(String),
Asn(String),
Auth(String),
Delta(String),
}
impl fmt::Display for AuthorizationFmtError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AuthorizationFmtError::Pfx(s) => write!(f, "Invalid prefix string: {}", s),
AuthorizationFmtError::Asn(s) => write!(f, "Invalid asn in string: {}", s),
AuthorizationFmtError::Auth(s) => write!(f, "Invalid authorization string: {}", s),
AuthorizationFmtError::Delta(s) => write!(f, "Invalid authorization delta string: {}", s),
}
}
}
impl AuthorizationFmtError {
fn pfx(s: &str) -> Self {
AuthorizationFmtError::Pfx(s.to_string())
}
fn asn(s: &str) -> Self {
AuthorizationFmtError::Asn(s.to_string())
}
pub fn auth(s: &str) -> Self {
AuthorizationFmtError::Auth(s.to_string())
}
pub fn delta(s: &str) -> Self {
AuthorizationFmtError::Delta(s.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::{roa_configuration, roa_payload};
#[test]
fn parse_delta() {
let delta = concat!(
"# Some comment\n",
" # Indented comment\n",
"\n", "A: 192.168.0.0/16 => 64496 # ROA comment\n",
"A: 192.168.1.0/24 => 64496\n",
"R: 192.168.3.0/24 => 64496 # ignored comment for removed ROA\n",
);
let expected = {
let added = vec![
roa_configuration("192.168.0.0/16 => 64496 # ROA comment"),
roa_configuration("192.168.1.0/24 => 64496"),
];
let removed = vec![roa_payload("192.168.3.0/24 => 64496")];
RoaConfigurationUpdates::new(added, removed)
};
let parsed = RoaConfigurationUpdates::from_str(delta).unwrap();
assert_eq!(expected, parsed);
let re_parsed = RoaConfigurationUpdates::from_str(&parsed.to_string()).unwrap();
assert_eq!(parsed, re_parsed);
}
#[test]
fn parse_type_prefix() {
assert!(TypedPrefix::from_str("192.168.0.0/16").is_ok());
assert!(TypedPrefix::from_str("2001:db8::/32").is_ok());
}
#[test]
fn normalize_roa_definition_json() {
let def = roa_payload("192.168.0.0/16 => 64496");
let json = serde_json::to_string(&def).unwrap();
let expected = "{\"asn\":64496,\"prefix\":\"192.168.0.0/16\"}";
assert_eq!(json, expected);
let def = roa_payload("192.168.0.0/16-24 => 64496");
let json = serde_json::to_string(&def).unwrap();
let expected = "{\"asn\":64496,\"prefix\":\"192.168.0.0/16\",\"max_length\":24}";
assert_eq!(json, expected);
}
#[test]
fn serde_roa_configuration() {
fn parse_ser_de_print_configuration(s: &str) {
let def = roa_configuration(s);
let ser = serde_json::to_string(&def).unwrap();
let de = serde_json::from_str(&ser).unwrap();
assert_eq!(def, de);
assert_eq!(s, de.to_string().as_str())
}
parse_ser_de_print_configuration("192.168.0.0/16 => 64496 # comment");
parse_ser_de_print_configuration("192.168.0.0/16-24 => 64496");
parse_ser_de_print_configuration("2001:db8::/32 => 64496 # comment with extra #");
parse_ser_de_print_configuration("2001:db8::/32-48 => 64496");
}
#[test]
fn serde_roa_payload() {
fn parse_ser_de_print_payload(s: &str) {
let def = roa_payload(s);
let ser = serde_json::to_string(&def).unwrap();
let de = serde_json::from_str(&ser).unwrap();
assert_eq!(def, de);
assert_eq!(s, de.to_string().as_str())
}
parse_ser_de_print_payload("192.168.0.0/16 => 64496");
parse_ser_de_print_payload("192.168.0.0/16-24 => 64496");
parse_ser_de_print_payload("2001:db8::/32 => 64496");
parse_ser_de_print_payload("2001:db8::/32-48 => 64496");
}
#[test]
fn roa_max_length() {
fn valid_max_length(s: &str) {
let def = RoaPayload::from_str(s).unwrap();
assert!(def.max_length_valid())
}
fn invalid_max_length(s: &str) {
let def = RoaPayload::from_str(s).unwrap();
assert!(!def.max_length_valid())
}
valid_max_length("192.168.0.0/16 => 64496");
valid_max_length("192.168.0.0/16-16 => 64496");
valid_max_length("192.168.0.0/16-24 => 64496");
valid_max_length("192.168.0.0/16-32 => 64496");
valid_max_length("2001:db8::/32 => 64496");
valid_max_length("2001:db8::/32-32 => 64496");
valid_max_length("2001:db8::/32-48 => 64496");
valid_max_length("2001:db8::/32-128 => 64496");
invalid_max_length("192.168.0.0/16-15 => 64496");
invalid_max_length("192.168.0.0/16-33 => 64496");
invalid_max_length("2001:db8::/32-31 => 64496");
invalid_max_length("2001:db8::/32-129 => 64496");
}
#[test]
fn roa_includes() {
let covering = roa_payload("192.168.0.0/16-20 => 64496");
let included_no_ml = roa_payload("192.168.0.0/16 => 64496");
let included_more_specific = roa_payload("192.168.0.0/20 => 64496");
let allowing_more_specific = roa_payload("192.168.0.0/16-24 => 64496");
let more_specific = roa_payload("192.168.3.0/24 => 64496");
let other_asn = roa_payload("192.168.3.0/24 => 64497");
assert!(covering.includes(&included_no_ml));
assert!(covering.includes(&included_more_specific));
assert!(!covering.includes(&more_specific));
assert!(!covering.includes(&allowing_more_specific));
assert!(!covering.includes(&other_asn));
}
#[test]
fn roa_group_string() {
let roa_group_asn_only = RoaAggregateKey {
asn: AsNumber::new(0),
group: None,
};
let roa_group_asn_only_expected_str = "AS0";
assert_eq!(roa_group_asn_only.to_string().as_str(), roa_group_asn_only_expected_str);
let roa_group_asn_only_expected = RoaAggregateKey::from_str(roa_group_asn_only_expected_str).unwrap();
assert_eq!(roa_group_asn_only, roa_group_asn_only_expected)
}
#[test]
fn roa_nr_specific_pfx() {
fn check(def: &str, expected: u128) {
let def = roa_payload(def);
let calculated = def.nr_of_specific_prefixes();
assert_eq!(calculated, expected);
}
check("10.0.0.0/15-15 => 64496", 1);
check("10.0.0.0/15-16 => 64496", 2);
check("10.0.0.0/15-17 => 64496", 4);
check("10.0.0.0/15-18 => 64496", 8);
}
}