use std::{error, fmt};
use std::cmp::Ordering;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::str::FromStr;
use rpki::uri;
use rpki::ca::publication::Base64;
use rpki::repository::resources::{
AsBlocks, Asn, IpBlocks, IpBlocksBuilder, Prefix, ResourceSet,
};
use rpki::repository::roa::{Roa, RoaIpAddress};
use rpki::repository::x509::{Serial, Time, Validity};
use rpki::rrdp::Hash;
use serde::de;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::bgp::BgpAnalysisSuggestion;
use super::ca::Revocation;
#[derive(Clone, Copy, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct RoaPayload {
pub asn: AsNumber,
pub prefix: TypedPrefix,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_length: Option<u8>,
}
impl RoaPayload {
fn set_explicit_max_length(&mut self) {
self.max_length = Some(self.effective_max_length());
}
pub fn into_explicit_max_length(self) -> Self {
Self {
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 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 PartialOrd for RoaPayload {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
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 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 fmt::Debug for RoaPayload {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "RoaPayload({})", &self)
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub struct RoaPayloadJsonMapKey(RoaPayload);
impl RoaPayloadJsonMapKey {
pub fn asn(self) -> AsNumber {
self.0.asn
}
}
impl From<RoaPayload> for RoaPayloadJsonMapKey {
fn from(def: RoaPayload) -> Self {
RoaPayloadJsonMapKey(def)
}
}
impl From<RoaPayloadJsonMapKey> for RoaPayload {
fn from(auth: RoaPayloadJsonMapKey) -> Self {
auth.0
}
}
impl AsRef<RoaPayload> for RoaPayloadJsonMapKey {
fn as_ref(&self) -> &RoaPayload {
&self.0
}
}
impl fmt::Display for RoaPayloadJsonMapKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl Serialize for RoaPayloadJsonMapKey {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.to_string().serialize(s)
}
}
impl<'de> Deserialize<'de> for RoaPayloadJsonMapKey {
fn deserialize<D>(d: D) -> Result<RoaPayloadJsonMapKey, D::Error>
where
D: Deserializer<'de>,
{
Ok(Self(
RoaPayload::from_str(
&String::deserialize(d)?
).map_err(de::Error::custom)?
))
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct RoaConfiguration {
#[serde(flatten)]
pub payload: RoaPayload,
#[serde(default)] pub comment: Option<String>,
}
impl RoaConfiguration {
pub fn set_explicit_max_length(&mut self) {
self.payload.set_explicit_max_length();
}
}
impl From<RoaPayload> for RoaConfiguration {
fn from(payload: RoaPayload) -> Self {
RoaConfiguration {
payload,
comment: None,
}
}
}
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 PartialOrd for RoaConfiguration {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for RoaConfiguration {
fn cmp(&self, other: &Self) -> Ordering {
self.payload.cmp(&other.payload)
}
}
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(())
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct RoaInfo {
pub authorizations: Vec<RoaPayloadJsonMapKey>,
pub validity: Validity,
pub serial: Serial,
pub uri: uri::Rsync,
pub base64: Base64,
pub hash: Hash,
}
impl RoaInfo {
pub fn new(authorizations: Vec<RoaPayloadJsonMapKey>, roa: Roa) -> Self {
let validity = roa.cert().validity();
let serial = roa.cert().serial_number();
let uri = roa.cert().signed_object().unwrap().clone(); let base64 = Base64::from(&roa);
let hash = base64.to_hash();
RoaInfo {
authorizations,
validity,
serial,
uri,
base64,
hash,
}
}
pub fn expires(&self) -> Time {
self.validity.not_after()
}
pub fn revoke(&self) -> Revocation {
Revocation::new(self.serial, self.validity.not_after())
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ConfiguredRoa {
#[serde(flatten)]
pub roa_configuration: RoaConfiguration,
pub roa_objects: Vec<RoaInfo>,
}
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))
}
}
impl fmt::Display for ConfiguredRoa {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.roa_configuration)
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ConfiguredRoas(Vec<ConfiguredRoa>);
impl ConfiguredRoas {
pub fn into_vec(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 {
pub added: Vec<RoaConfiguration>,
pub removed: Vec<RoaPayload>,
}
impl RoaConfigurationUpdates {
pub fn is_empty(&self) -> bool {
self.added.is_empty() && self.removed.is_empty()
}
pub fn set_explicit_max_length(&mut self) {
self.added.iter_mut().for_each(|x| x.set_explicit_max_length());
self.removed.iter_mut().for_each(|x| x.set_explicit_max_length());
}
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
}
}
impl From<BgpAnalysisSuggestion> for RoaConfigurationUpdates {
fn from(suggestion: BgpAnalysisSuggestion) -> Self {
let mut added: Vec<RoaConfiguration> = vec![];
let mut removed: Vec<RoaPayload> = vec![];
for announcement in suggestion.not_found
.into_iter()
.chain(suggestion.invalid_asn.into_iter())
.chain(suggestion.invalid_length.into_iter())
{
added.push(RoaConfiguration {
payload: announcement.into(),
comment: None
});
}
for stale in suggestion.stale {
removed.push(stale.roa_configuration.payload);
}
for suggestion in suggestion.too_permissive.into_iter() {
removed.push(suggestion.current.roa_configuration.payload);
for payload in suggestion.new.into_iter() {
added.push(RoaConfiguration { payload, comment: None });
}
}
for as0_redundant in suggestion.as0_redundant.into_iter() {
removed.push(as0_redundant.roa_configuration.payload);
}
for redundant in suggestion.redundant.into_iter() {
removed.push(redundant.roa_configuration.payload);
}
RoaConfigurationUpdates { added, removed }
}
}
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 })
}
}
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(())
}
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub enum TypedPrefix {
V4(Ipv4Prefix),
V6(Ipv6Prefix),
}
impl TypedPrefix {
pub fn prefix(self) -> Prefix {
match self {
Self::V4(prefix) => prefix.into(),
Self::V6(prefix) => prefix.into(),
}
}
pub fn ip_addr(self) -> IpAddr {
match self {
Self::V4(v4) => v4.addr().into(),
Self::V6(v6) => v6.addr().into(),
}
}
pub fn addr_len(self) -> u8 {
match self {
Self::V4(v4) => v4.addr_len(),
Self::V6(v6) => v6.addr_len(),
}
}
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 From<Ipv4Prefix> for TypedPrefix {
fn from(prefix: Ipv4Prefix) -> Self {
TypedPrefix::V4(prefix)
}
}
impl From<Ipv6Prefix> for TypedPrefix {
fn from(prefix: Ipv6Prefix) -> Self {
TypedPrefix::V6(prefix)
}
}
impl From<TypedPrefix> for ResourceSet {
fn from(tp: TypedPrefix) -> ResourceSet {
match tp {
TypedPrefix::V4(v4) => {
let mut builder = IpBlocksBuilder::new();
builder.push(Prefix::from(v4));
let blocks = builder.finalize();
ResourceSet::new(
AsBlocks::empty(),
blocks.into(),
IpBlocks::empty().into(),
)
}
TypedPrefix::V6(v6) => {
let mut builder = IpBlocksBuilder::new();
builder.push(Prefix::from(v6));
let blocks = builder.finalize();
ResourceSet::new(
AsBlocks::empty(),
IpBlocks::empty().into(),
blocks.into(),
)
}
}
}
}
impl FromStr for TypedPrefix {
type Err = AuthorizationFmtError;
fn from_str(prefix: &str) -> Result<Self, Self::Err> {
if prefix.contains('.') {
Ok(TypedPrefix::V4(Ipv4Prefix::from(
Prefix::from_v4_str(prefix.trim())
.map_err(|_| AuthorizationFmtError::pfx(prefix))?,
)))
} else {
Ok(TypedPrefix::V6(Ipv6Prefix::from(
Prefix::from_v6_str(prefix.trim())
.map_err(|_| AuthorizationFmtError::pfx(prefix))?,
)))
}
}
}
impl PartialOrd for TypedPrefix {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for TypedPrefix {
fn cmp(&self, other: &Self) -> Ordering {
let mut ordering = self.prefix().addr().cmp(&other.prefix().addr());
if ordering == Ordering::Equal {
ordering = self.addr_len().cmp(&other.addr_len())
}
ordering
}
}
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 fmt::Debug for TypedPrefix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", &self)
}
}
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 Serialize for TypedPrefix {
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.to_string().serialize(s)
}
}
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Ipv4Prefix {
addr: Ipv4Addr,
addr_len: u8,
}
impl Ipv4Prefix {
pub fn addr(self) -> Ipv4Addr {
self.addr
}
pub fn addr_len(self) -> u8 {
self.addr_len
}
pub fn resize(self, addr_len: u8) -> Self {
if addr_len >= 32 {
Self {
addr: self.addr,
addr_len: 32,
}
}
else {
Self {
addr: Ipv4Addr::from_bits(
self.addr.to_bits() & !(u32::MAX >> addr_len)
),
addr_len
}
}
}
}
impl Default for Ipv4Prefix {
fn default() -> Self {
Self { addr: Ipv4Addr::UNSPECIFIED, addr_len: 0 }
}
}
impl FromStr for Ipv4Prefix {
type Err = ParsePrefixError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let Some((addr, len)) = s.split_once('/') else {
return Err(ParsePrefixError(()))
};
let addr = Ipv4Addr::from_str(addr).map_err(|_| {
ParsePrefixError(())
})?;
let addr_len = u8::from_str(len).map_err(|_| {
ParsePrefixError(())
})?;
if addr_len > 32 {
return Err(ParsePrefixError(()));
}
if addr.to_bits().trailing_zeros() < (32 - addr_len).into() {
return Err(ParsePrefixError(()));
}
Ok(Self { addr, addr_len })
}
}
impl fmt::Display for Ipv4Prefix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}/{}", self.addr, self.addr_len)
}
}
impl fmt::Debug for Ipv4Prefix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", &self)
}
}
impl From<Prefix> for Ipv4Prefix {
fn from(prefix: Prefix) -> Self {
Self {
addr: prefix.to_v4(),
addr_len: prefix.addr_len(),
}
}
}
impl From<Ipv4Prefix> for Prefix {
fn from(src: Ipv4Prefix) -> Self {
Self::new(src.addr, src.addr_len)
}
}
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Ipv6Prefix {
addr: Ipv6Addr,
addr_len: u8,
}
impl Ipv6Prefix {
pub fn addr(self) -> Ipv6Addr {
self.addr
}
pub fn addr_len(self) -> u8 {
self.addr_len
}
pub fn resize(self, addr_len: u8) -> Self {
if addr_len >= 128 {
Self {
addr: self.addr,
addr_len: 128,
}
}
else {
Self {
addr: Ipv6Addr::from_bits(
self.addr.to_bits() & !(u128::MAX >> addr_len)
),
addr_len
}
}
}
}
impl Default for Ipv6Prefix {
fn default() -> Self {
Self { addr: Ipv6Addr::UNSPECIFIED, addr_len: 0 }
}
}
impl FromStr for Ipv6Prefix {
type Err = ParsePrefixError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let Some((addr, len)) = s.split_once('/') else {
return Err(ParsePrefixError(()))
};
let addr = Ipv6Addr::from_str(addr).map_err(|_| {
ParsePrefixError(())
})?;
let addr_len = u8::from_str(len).map_err(|_| {
ParsePrefixError(())
})?;
if addr_len > 128 {
return Err(ParsePrefixError(()));
}
if addr.to_bits().trailing_zeros() < (128 - addr_len).into() {
return Err(ParsePrefixError(()));
}
Ok(Self { addr, addr_len })
}
}
impl fmt::Display for Ipv6Prefix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}/{}", self.addr, self.addr_len)
}
}
impl fmt::Debug for Ipv6Prefix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", &self)
}
}
impl From<Prefix> for Ipv6Prefix {
fn from(prefix: Prefix) -> Self {
Self {
addr: prefix.to_v6(),
addr_len: prefix.addr_len(),
}
}
}
impl From<Ipv6Prefix> for Prefix {
fn from(src: Ipv6Prefix) -> Self {
Self::new(src.addr, src.addr_len)
}
}
#[derive(
Clone, Copy, Deserialize, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize,
)]
pub struct AsNumber(u32);
impl AsNumber {
pub const AS0: Self = Self::from_u32(0);
pub const fn from_u32(number: u32) -> Self {
AsNumber(number)
}
}
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::Display for AsNumber {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl fmt::Debug for AsNumber {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", &self)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum AuthorizationFmtError {
Pfx(String),
Asn(String),
Auth(String),
Delta(String),
}
impl AuthorizationFmtError {
fn pfx(s: &str) -> Self {
AuthorizationFmtError::Pfx(s.to_string())
}
fn asn(s: &str) -> Self {
AuthorizationFmtError::Asn(s.to_string())
}
fn auth(s: &str) -> Self {
AuthorizationFmtError::Auth(s.to_string())
}
fn delta(s: &str) -> Self {
AuthorizationFmtError::Delta(s.to_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 error::Error for AuthorizationFmtError { }
#[derive(Debug)]
pub struct ParsePrefixError(());
impl fmt::Display for ParsePrefixError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("invalid prefix")
}
}
impl error::Error for ParsePrefixError { }
#[cfg(test)]
mod tests {
use crate::commons::test::{roa_configuration, roa_payload};
use super::*;
#[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 { 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_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);
}
}