use std::fmt;
use std::str::FromStr;
use rpki::repository::aspa::*;
use rpki::repository::resources::Asn;
pub type AspaCustomer = Asn;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct AspaDefinitionUpdates {
add_or_replace: Vec<AspaDefinition>,
remove: Vec<AspaCustomer>,
}
impl AspaDefinitionUpdates {
pub fn new(add_or_replace: Vec<AspaDefinition>, remove: Vec<AspaCustomer>) -> Self {
AspaDefinitionUpdates { add_or_replace, remove }
}
pub fn unpack(self) -> (Vec<AspaDefinition>, Vec<AspaCustomer>) {
(self.add_or_replace, self.remove)
}
}
impl fmt::Display for AspaDefinitionUpdates {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Update ASPA definitions: ")?;
if !self.add_or_replace.is_empty() {
write!(f, " add or replace:")?;
for definition in &self.add_or_replace {
write!(f, " {}", definition)?;
}
}
if !self.remove.is_empty() {
write!(f, " remove where customer ASN is:")?;
for as_id in &self.remove {
write!(f, " {}", as_id)?;
}
}
Ok(())
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct AspaDefinitionList(Vec<AspaDefinition>);
impl AspaDefinitionList {
pub fn new(definitions: Vec<AspaDefinition>) -> Self {
AspaDefinitionList(definitions)
}
}
impl fmt::Display for AspaDefinitionList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for def in self.0.iter() {
writeln!(f, "{}", def)?;
}
Ok(())
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct AspaDefinition {
customer: AspaCustomer,
providers: Vec<ProviderAs>,
}
impl AspaDefinition {
pub fn new(customer: AspaCustomer, providers: Vec<ProviderAs>) -> Self {
AspaDefinition { customer, providers }
}
pub fn unpack(self) -> (AspaCustomer, Vec<ProviderAs>) {
(self.customer, self.providers)
}
pub fn customer(&self) -> AspaCustomer {
self.customer
}
pub fn providers(&self) -> &Vec<ProviderAs> {
&self.providers
}
pub fn verify_update(&self, update: &AspaProvidersUpdate) -> Result<(), AspaProvidersUpdateConflict> {
let mut error = AspaProvidersUpdateConflict::default();
for removed in update.removed() {
if !self.providers.contains(removed) {
error.add_unknown(*removed)
}
}
for added in update.added() {
if self
.providers
.iter()
.any(|existing| existing.provider() == added.provider())
{
error.add_duplicate(*added)
}
}
if error.is_empty() {
Ok(())
} else {
Err(error)
}
}
pub fn apply_update(&mut self, update: &AspaProvidersUpdate) {
for removed in update.removed() {
self.providers.retain(|provider| provider != removed);
}
for added in update.added() {
self.providers.push(*added);
}
self.providers.sort_by_key(|p| p.provider());
}
}
impl fmt::Display for AspaDefinition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} => ", self.customer)?;
if self.providers.is_empty() {
write!(f, "<none>")?;
} else {
for i in 0..self.providers.len() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", self.providers[i])?;
}
}
Ok(())
}
}
impl FromStr for AspaDefinition {
type Err = AspaDefinitionFormatError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.split("=>");
let customer = {
let customer_str = parts.next().ok_or(AspaDefinitionFormatError::CustomerAsMissing)?;
AspaCustomer::from_str(customer_str.trim())
.map_err(|_| AspaDefinitionFormatError::CustomerAsInvalid(customer_str.trim().to_string()))?
};
let mut providers = {
let mut providers = vec![];
let providers_str = parts.next().unwrap_or("<none>");
if providers_str.trim() != "<none>" {
let provider_parts = providers_str.split(',');
for provider_part in provider_parts {
let provider = ProviderAs::from_str(provider_part.trim())
.map_err(|_| AspaDefinitionFormatError::ProviderAsInvalid(provider_part.trim().to_string()))?;
providers.push(provider);
}
}
providers
};
if parts.next().is_some() {
Err(AspaDefinitionFormatError::ExtraParts)
} else {
providers.sort_by_key(|p| p.provider());
match providers
.windows(2)
.find(|pair| pair[0].provider() == pair[1].provider())
{
Some(dup) => Err(AspaDefinitionFormatError::ProviderAsDuplicate(dup[0], dup[1])),
None => Ok(AspaDefinition::new(customer, providers)),
}
}
}
}
#[derive(Clone, Debug)]
pub enum AspaDefinitionFormatError {
CustomerAsMissing,
CustomerAsInvalid(String),
ProviderAsInvalid(String),
ProviderAsDuplicate(ProviderAs, ProviderAs),
ExtraParts,
}
impl fmt::Display for AspaDefinitionFormatError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ASPA configuration format invalid: ")?;
match self {
AspaDefinitionFormatError::CustomerAsMissing => write!(f, "customer AS missing"),
AspaDefinitionFormatError::CustomerAsInvalid(s) => write!(f, "cannot parse customer AS: {}", s),
AspaDefinitionFormatError::ProviderAsInvalid(s) => write!(f, "cannot parse provider AS: {}", s),
AspaDefinitionFormatError::ProviderAsDuplicate(l, r) => {
write!(f, "duplicate AS in provider list. Found {} and {}", l, r)
}
AspaDefinitionFormatError::ExtraParts => write!(f, "found more than one '=>'"),
}
}
}
impl std::error::Error for AspaDefinitionFormatError {}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct AspaProvidersUpdate {
added: Vec<ProviderAs>,
removed: Vec<ProviderAs>,
}
impl AspaProvidersUpdate {
pub fn new(added: Vec<ProviderAs>, removed: Vec<ProviderAs>) -> Self {
AspaProvidersUpdate { added, removed }
}
pub fn empty() -> Self {
AspaProvidersUpdate {
added: vec![],
removed: vec![],
}
}
pub fn is_empty(&self) -> bool {
self.added.is_empty() && self.removed.is_empty()
}
pub fn contains_changes(&self) -> bool {
!self.is_empty()
}
pub fn add(&mut self, provider: ProviderAs) {
self.added.push(provider);
}
pub fn remove(&mut self, provider: ProviderAs) {
self.removed.push(provider);
}
pub fn added(&self) -> &Vec<ProviderAs> {
&self.added
}
pub fn removed(&self) -> &Vec<ProviderAs> {
&self.removed
}
}
impl fmt::Display for AspaProvidersUpdate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.added.is_empty() {
write!(f, "adding providers:")?;
for added in &self.added {
write!(f, " {}", added)?;
}
write!(f, " ")?;
}
if !self.removed.is_empty() {
write!(f, "removing providers:")?;
for removed in &self.removed {
write!(f, " {}", removed)?;
}
}
Ok(())
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct AspaProvidersUpdateConflict {
duplicates: Vec<ProviderAs>,
unknowns: Vec<ProviderAs>,
}
impl AspaProvidersUpdateConflict {
pub fn add_duplicate(&mut self, provider: ProviderAs) {
self.duplicates.push(provider);
}
pub fn add_unknown(&mut self, provider: ProviderAs) {
self.unknowns.push(provider);
}
pub fn is_empty(&self) -> bool {
self.duplicates.is_empty() && self.unknowns.is_empty()
}
}
impl fmt::Display for AspaProvidersUpdateConflict {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.duplicates.is_empty() {
writeln!(f, "Cannot add the following duplicate provider(s): ")?;
for dup in &self.duplicates {
writeln!(f, " {}", dup)?;
}
}
if !self.unknowns.is_empty() {
writeln!(f, "Cannot remove the following unknown provider(s): ")?;
for unk in &self.unknowns {
writeln!(f, " {}", unk)?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn customer(s: &str) -> Asn {
Asn::from_str(s).unwrap()
}
fn provider(s: &str) -> ProviderAs {
ProviderAs::from_str(s).unwrap()
}
#[test]
fn aspa_configuration_to_from_str() {
let config = AspaDefinition::new(
customer("AS65000"),
vec![provider("AS65001"), provider("AS65002(v4)"), provider("AS65003(v6)")],
);
let config_str = "AS65000 => AS65001, AS65002(v4), AS65003(v6)";
let to_str = config.to_string();
assert_eq!(config_str, to_str.as_str());
let from_str = AspaDefinition::from_str(config_str).unwrap();
assert_eq!(config, from_str);
}
#[test]
fn aspa_configuration_empty_providers_from_str() {
let config = AspaDefinition::new(customer("AS65000"), vec![]);
let config_str = "AS65000 => <none>";
let to_str = config.to_string();
assert_eq!(config_str, to_str.as_str());
let from_str = AspaDefinition::from_str(config_str).unwrap();
assert_eq!(config, from_str);
}
}