#![cfg(feature = "slurm")]
use std::{borrow, error, fmt, io, ops};
use std::str::FromStr;
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use crate::crypto::keys::KeyIdentifier;
use crate::resources::addr::{MaxLenPrefix, Prefix};
use crate::resources::asn::Asn;
use crate::rtr::payload as rtr;
use crate::rtr::pdu::{KeyInfoError, ProviderAsns, RouterKeyInfo};
use crate::util::base64;
#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct SlurmFile {
#[serde(rename = "slurmVersion")]
version: SlurmVersion,
#[serde(rename = "validationOutputFilters")]
pub filters: ValidationOutputFilters,
#[serde(rename = "locallyAddedAssertions")]
pub assertions: LocallyAddedAssertions,
}
impl SlurmFile {
pub fn new(
filters: ValidationOutputFilters,
assertions: LocallyAddedAssertions,
) -> Self {
if assertions.aspa.is_some() || filters.aspa.is_some() {
SlurmFile {
version: SlurmVersion::v2(),
filters, assertions,
}
} else {
SlurmFile {
version: SlurmVersion::v1(),
filters, assertions,
}
}
}
pub fn from_reader(
reader: impl io::Read
) -> Result<Self, serde_json::Error> {
serde_json::from_reader(reader)
}
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
serde_json::to_string(self).expect("serialization failed")
}
pub fn to_string_pretty(&self) -> String {
serde_json::to_string_pretty(self).expect("serialization failed")
}
pub fn to_writer(&self, writer: impl io::Write) -> Result<(), io::Error> {
serde_json::to_writer(writer, self).map_err(Into::into)
}
pub fn to_writer_pretty(
&self, writer: impl io::Write
) -> Result<(), io::Error> {
serde_json::to_writer_pretty(writer, self).map_err(Into::into)
}
pub fn drop_payload(&self, payload: &rtr::Payload) -> bool {
self.filters.drop_payload(payload)
}
}
impl FromStr for SlurmFile {
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(s)
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq)]
#[serde(try_from = "u8")]
struct SlurmVersion {
version: u8
}
impl Default for SlurmVersion {
fn default() -> SlurmVersion {
Self::v2()
}
}
impl SlurmVersion {
pub fn v1() -> SlurmVersion {
SlurmVersion { version: 1 }
}
pub fn v2() -> SlurmVersion {
SlurmVersion { version: 2 }
}
}
impl TryFrom<u8> for SlurmVersion {
type Error = &'static str;
fn try_from(value: u8) -> Result<Self, Self::Error> {
if value == 1 {
Ok(Self::v1())
} else if value == 2 {
Ok(Self::v2())
} else {
Err("slurmVersion must be 1 or 2")
}
}
}
impl Serialize for SlurmVersion {
fn serialize<S: serde::Serializer>(
&self, serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.serialize_u8(self.version)
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ValidationOutputFilters {
#[serde(rename = "prefixFilters")]
pub prefix: Vec<PrefixFilter>,
#[serde(rename = "bgpsecFilters")]
pub bgpsec: Vec<BgpsecFilter>,
#[serde(rename = "aspaFilters")]
pub aspa: Option<Vec<AspaFilter>>,
}
impl ValidationOutputFilters {
pub fn new(
prefix: impl Into<Vec<PrefixFilter>>,
bgpsec: impl Into<Vec<BgpsecFilter>>,
) -> Self {
ValidationOutputFilters {
prefix: prefix.into(),
bgpsec: bgpsec.into(),
aspa: None,
}
}
pub fn drop_payload(&self, payload: &rtr::Payload) -> bool {
for prefix in &self.prefix {
if prefix.drop_payload(payload) {
return true
}
}
false
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct PrefixFilter {
#[serde(skip_serializing_if = "Option::is_none")]
pub prefix: Option<Prefix>,
#[serde(with = "self::serde_opt_asn")]
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub asn: Option<Asn>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
}
impl PrefixFilter {
pub fn new(
prefix: Option<Prefix>, asn: Option<Asn>, comment: Option<String>
) -> Self {
PrefixFilter { prefix, asn, comment }
}
pub fn drop_origin(&self, origin: rtr::RouteOrigin) -> bool {
let drop_prefix = self.prefix.map(|self_prefix| {
self_prefix.covers(origin.prefix.prefix())
});
let drop_asn = self.asn.map(|self_asn| {
self_asn == origin.asn
});
match (drop_prefix, drop_asn) {
(Some(prefix), Some(asn)) => prefix && asn,
(Some(prefix), None) => prefix,
(None, Some(asn)) => asn,
(None, None) => false
}
}
pub fn drop_payload(&self, payload: &rtr::Payload) -> bool {
match payload {
rtr::Payload::Origin(origin) => self.drop_origin(*origin),
_ => false
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct BgpsecFilter {
#[serde(rename = "SKI")]
#[serde(with = "self::serde_opt_key_identifier")]
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub ski: Option<KeyIdentifier>,
#[serde(with = "self::serde_opt_asn")]
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub asn: Option<Asn>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
}
impl BgpsecFilter {
pub fn new(
ski: Option<KeyIdentifier>, asn: Option<Asn>, comment: Option<String>,
) -> Self {
BgpsecFilter { ski, asn, comment }
}
pub fn drop_router_key(&self, key: &rtr::RouterKey) -> bool {
let drop_ski = self.ski.map(|self_ski| {
self_ski == key.key_identifier
});
let drop_asn = self.asn.map(|self_asn| {
self_asn == key.asn
});
match (drop_ski, drop_asn) {
(Some(ski), Some(asn)) => ski && asn,
(Some(ski), None) => ski,
(None, Some(asn)) => asn,
(None, None) => false
}
}
pub fn drop_payload(&self, payload: &rtr::Payload) -> bool {
match payload {
rtr::Payload::RouterKey(key) => self.drop_router_key(key),
_ => false
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct AspaFilter {
#[serde(with = "self::serde_opt_asn")]
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "customerAsid")]
pub customer_asid: Option<Asn>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
}
impl AspaFilter {
pub fn new(
customer_asid: Option<Asn>, comment: Option<String>
) -> Self {
AspaFilter { customer_asid, comment }
}
pub fn drop_aspa(&self, aspa: &rtr::Aspa) -> bool {
let drop_vap = self.customer_asid.map(|self_asid| {
self_asid == aspa.customer
});
drop_vap.unwrap_or(false)
}
pub fn drop_payload(&self, payload: &rtr::Payload) -> bool {
match payload {
rtr::Payload::Aspa(aspa) => self.drop_aspa(aspa),
_ => false
}
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct LocallyAddedAssertions {
#[serde(rename = "prefixAssertions")]
pub prefix: Vec<PrefixAssertion>,
#[serde(rename = "bgpsecAssertions")]
pub bgpsec: Vec<BgpsecAssertion>,
#[serde(rename = "aspaAssertions")]
pub aspa: Option<Vec<AspaAssertion>>,
}
impl LocallyAddedAssertions {
pub fn new(
prefix: impl Into<Vec<PrefixAssertion>>,
bgpsec: impl Into<Vec<BgpsecAssertion>>,
) -> Self {
LocallyAddedAssertions {
prefix: prefix.into(),
bgpsec: bgpsec.into(),
aspa: None,
}
}
pub fn iter_payload(&self) -> impl Iterator<Item = rtr::Payload> + '_ {
let aspa = match &self.aspa {
None => <&[AspaAssertion]>::default(),
Some(a) => a
};
self.prefix.iter().map(|item| item.to_payload()).chain(
self.bgpsec.iter().map(|item| item.to_payload()).chain(
aspa.iter().map(|item| item.to_payload())
)
)
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct PrefixAssertion {
pub prefix: MaxLenPrefix,
pub asn: Asn,
pub comment: Option<String>,
}
impl PrefixAssertion {
pub fn new(
prefix: MaxLenPrefix,
asn: Asn,
comment: Option<String>,
) -> Self {
PrefixAssertion { prefix, asn, comment }
}
fn to_payload(&self) -> rtr::Payload {
rtr::Payload::origin(self.prefix, self.asn)
}
}
impl<'de> Deserialize<'de> for PrefixAssertion {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D
) -> Result<Self, D::Error> {
use serde::de;
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
enum Fields { Prefix, Asn, MaxPrefixLength, Comment }
struct StructVisitor;
impl<'de> de::Visitor<'de> for StructVisitor {
type Value = PrefixAssertion;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("PrefixAssertion object")
}
fn visit_map<V: de::MapAccess<'de>>(
self, mut map: V
) -> Result<Self::Value, V::Error> {
let mut prefix = None;
let mut asn: Option<u32> = None;
let mut max_len = None;
let mut comment = None;
while let Some(key) = map.next_key()? {
match key {
Fields::Prefix => {
if prefix.is_some() {
return Err(
de::Error::duplicate_field("prefix")
);
}
prefix = Some(map.next_value()?);
}
Fields::Asn => {
if asn.is_some() {
return Err(
de::Error::duplicate_field("asn")
);
}
asn = Some(map.next_value()?);
}
Fields::MaxPrefixLength => {
if max_len.is_some() {
return Err(
de::Error::duplicate_field("maxPrefixLen")
);
}
max_len = Some(map.next_value()?);
}
Fields::Comment => {
if comment.is_some() {
return Err(
de::Error::duplicate_field("comment")
);
}
comment = Some(map.next_value()?);
}
}
}
let prefix: Prefix = prefix.ok_or_else(|| {
de::Error::missing_field("prefix")
})?;
let asn = asn.ok_or_else(|| {
de::Error::missing_field("asn")
})?;
let prefix = MaxLenPrefix::new(prefix, max_len).map_err(
de::Error::custom
)?;
Ok(PrefixAssertion { prefix, asn: asn.into(), comment })
}
}
const FIELDS: &[&str] = &[
"prefix", "asn", "maxPrefixLen", "comment"
];
deserializer.deserialize_struct(
"PrefixAssertion", FIELDS, StructVisitor
)
}
}
impl Serialize for PrefixAssertion {
fn serialize<S: serde::Serializer>(
&self, serializer: S
) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeStruct;
let field_num = match
(self.prefix.max_len().is_some(), self.comment.is_some())
{
(true, true) => 4,
(true, false) | (false, true) => 3,
(false, false) => 2
};
let mut serializer = serializer.serialize_struct(
"PrefixAssertion", field_num,
)?;
serializer.serialize_field(
"prefix", &self.prefix.prefix(),
)?;
serializer.serialize_field(
"asn", &self.asn.into_u32()
)?;
if let Some(max_len) = self.prefix.max_len() {
serializer.serialize_field(
"maxPrefixLength", &max_len
)?;
}
if let Some(comment) = self.comment.as_ref() {
serializer.serialize_field(
"comment", comment.as_str()
)?;
}
serializer.end()
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct BgpsecAssertion {
#[serde(with = "self::serde_asn")]
pub asn: Asn,
#[serde(rename = "SKI")]
#[serde(with = "self::serde_key_identifier")]
pub ski: KeyIdentifier,
#[serde(rename = "routerPublicKey")]
pub router_public_key: Base64KeyInfo,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
}
impl BgpsecAssertion {
pub fn new(
asn: Asn,
ski: KeyIdentifier,
router_public_key: Base64KeyInfo,
comment: Option<String>,
) -> Self {
BgpsecAssertion { asn, ski, router_public_key, comment }
}
fn to_payload(&self) -> rtr::Payload {
rtr::Payload::router_key(
self.ski, self.asn, self.router_public_key.0.clone()
)
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct AspaAssertion {
pub customer_asn: Asn,
pub provider_asns: ProviderAsns,
pub comment: Option<String>,
}
impl AspaAssertion {
pub fn new(
customer_asn: Asn,
provider_asns: ProviderAsns,
comment: Option<String>,
) -> Self {
AspaAssertion { customer_asn, provider_asns, comment }
}
fn to_payload(&self) -> rtr::Payload {
rtr::Payload::aspa(self.customer_asn, self.provider_asns.clone())
}
}
impl<'de> Deserialize<'de> for AspaAssertion {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D
) -> Result<Self, D::Error> {
use serde::de;
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
enum Fields { CustomerAsn, ProviderAsns, Comment }
struct StructVisitor;
impl<'de> de::Visitor<'de> for StructVisitor {
type Value = AspaAssertion;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("AspaAssertion object")
}
fn visit_map<V: de::MapAccess<'de>>(
self, mut map: V
) -> Result<Self::Value, V::Error> {
let mut customer_asn: Option<u32> = None;
let mut provider_asns = None;
let mut comment = None;
while let Some(key) = map.next_key()? {
match key {
Fields::CustomerAsn => {
if customer_asn.is_some() {
return Err(
de::Error::duplicate_field("customerAsn")
);
}
customer_asn = Some(map.next_value()?);
}
Fields::ProviderAsns => {
if provider_asns.is_some() {
return Err(
de::Error::duplicate_field("providerAsns")
);
}
let v:Vec<u32> = map.next_value::<Vec<u32>>()?;
if let Ok(providers) = ProviderAsns::try_from_iter
(v.iter().map(|i| Asn::from_u32(*i))) {
provider_asns = Some(providers);
} else {
return Err(
de::Error::custom("Invalid field")
);
}
}
Fields::Comment => {
if comment.is_some() {
return Err(
de::Error::duplicate_field("comment")
);
}
comment = Some(map.next_value()?);
}
}
}
let customer_asn: u32 = customer_asn.ok_or_else(|| {
de::Error::missing_field("customerAsn")
})?;
let provider_asns = provider_asns.ok_or_else(|| {
de::Error::missing_field("providerAsns")
})?;
Ok(AspaAssertion {
customer_asn: customer_asn.into(),
provider_asns,
comment
})
}
}
const FIELDS: &[&str] = &[
"customerAsn", "providerAsns", "comment"
];
deserializer.deserialize_struct(
"AspaAssertion", FIELDS, StructVisitor
)
}
}
impl Serialize for AspaAssertion {
fn serialize<S: serde::Serializer>(
&self, serializer: S
) -> Result<S::Ok, S::Error> {
use serde::ser::SerializeStruct;
let field_num = match
self.comment.is_some()
{
true => 3,
false => 2
};
let mut serializer = serializer.serialize_struct(
"AspaAssertion", field_num,
)?;
serializer.serialize_field(
"customerAsn", &self.customer_asn.into_u32(),
)?;
serializer.serialize_field(
"providerAsns", &self.provider_asns.iter().map(|a| a.into_u32()).collect::<Vec<u32>>(),
)?;
if let Some(comment) = self.comment.as_ref() {
serializer.serialize_field(
"comment", comment.as_str()
)?;
}
serializer.end()
}
}
#[derive(Clone, Eq, Hash)]
pub struct Base64KeyInfo(RouterKeyInfo);
impl TryFrom<Vec<u8>> for Base64KeyInfo {
type Error = KeyInfoError;
fn try_from(src: Vec<u8>) -> Result<Self, Self::Error> {
RouterKeyInfo::try_from(src).map(Base64KeyInfo)
}
}
impl TryFrom<Bytes> for Base64KeyInfo {
type Error = KeyInfoError;
fn try_from(src: Bytes) -> Result<Self, Self::Error> {
RouterKeyInfo::try_from(src).map(Base64KeyInfo)
}
}
impl From<Base64KeyInfo> for RouterKeyInfo {
fn from(src: Base64KeyInfo) -> Self {
src.0
}
}
impl From<Base64KeyInfo> for Bytes {
fn from(src: Base64KeyInfo) -> Self {
src.0.into_bytes()
}
}
impl FromStr for Base64KeyInfo {
type Err = ParseBase64KeyInfoError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(base64::Slurm.decode(s)?.try_into()?)
}
}
impl ops::Deref for Base64KeyInfo {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.0.as_ref()
}
}
impl AsRef<[u8]> for Base64KeyInfo {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl borrow::Borrow<[u8]> for Base64KeyInfo {
fn borrow(&self) -> &[u8] {
self.0.as_ref()
}
}
impl<T: AsRef<[u8]>> PartialEq<T> for Base64KeyInfo {
fn eq(&self, other: &T) -> bool {
self.0.as_slice().eq(other.as_ref())
}
}
impl fmt::Display for Base64KeyInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
base64::Slurm.display(self.0.as_ref()).fmt(f)
}
}
impl fmt::Debug for Base64KeyInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("Base64KeyInfo")
.field(&format_args!("{self}"))
.finish()
}
}
impl Serialize for Base64KeyInfo {
fn serialize<S: serde::Serializer>(
&self, serializer: S
) -> Result<S::Ok, S::Error> {
serializer.collect_str(&format_args!("{self}"))
}
}
impl<'de> Deserialize<'de> for Base64KeyInfo {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D
) -> Result<Self, D::Error> {
struct Visitor;
impl serde::de::Visitor<'_> for Visitor {
type Value = Base64KeyInfo;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a Base64 string")
}
fn visit_str<E: serde::de::Error>(
self, v: &str
) -> Result<Self::Value, E> {
Base64KeyInfo::from_str(v).map_err(E::custom)
}
}
deserializer.deserialize_str(Visitor)
}
}
mod serde_asn {
use super::Asn;
pub fn serialize<S: serde::Serializer>(
asn: &Asn, serializer: S
) -> Result<S::Ok, S::Error> {
serializer.serialize_u32(asn.into_u32())
}
pub fn deserialize<'de, D: serde::Deserializer<'de>>(
deserializer: D
) -> Result<Asn, D::Error> {
<u32 as serde::Deserialize>::deserialize(deserializer).map(Into::into)
}
}
mod serde_opt_asn {
use super::Asn;
pub fn serialize<S: serde::Serializer>(
asn: &Option<Asn>, serializer: S
) -> Result<S::Ok, S::Error> {
match asn.as_ref() {
Some(asn) => serializer.serialize_u32(asn.into_u32()),
None => serializer.serialize_none()
}
}
pub fn deserialize<'de, D: serde::Deserializer<'de>>(
deserializer: D
) -> Result<Option<Asn>, D::Error> {
<Option::<u32> as serde::Deserialize>
::deserialize(deserializer).map(|ok| ok.map(Into::into))
}
}
mod serde_key_identifier {
use std::fmt;
use crate::util::base64;
use super::KeyIdentifier;
pub fn serialize<S: serde::Serializer>(
key_id: &KeyIdentifier, serializer: S
) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&base64::Slurm.encode(key_id.as_slice()))
}
pub fn deserialize<'de, D: serde::Deserializer<'de>>(
deserializer: D
) -> Result<KeyIdentifier, D::Error> {
struct Visitor;
impl serde::de::Visitor<'_> for Visitor {
type Value = KeyIdentifier;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a Base64-encoded key identifier")
}
fn visit_str<E: serde::de::Error>(
self, v: &str
) -> Result<Self::Value, E> {
if v.len() != 27 {
return Err(E::custom("invalid length for key identifier"))
}
let mut buf = [0u8; 21];
let len = base64::Slurm.decode_slice(
v, &mut buf
).map_err(E::custom)?;
KeyIdentifier::try_from(&buf[..len]).map_err(|_| {
E::custom("invalid length for key identifier")
})
}
}
deserializer.deserialize_str(Visitor)
}
}
mod serde_opt_key_identifier {
use super::KeyIdentifier;
pub fn serialize<S: serde::Serializer>(
key_id: &Option<KeyIdentifier>, serializer: S
) -> Result<S::Ok, S::Error> {
match key_id.as_ref() {
Some(key_id) => {
super::serde_key_identifier::serialize(key_id, serializer)
}
None => serializer.serialize_none()
}
}
pub fn deserialize<'de, D: serde::Deserializer<'de>>(
deserializer: D
) -> Result<Option<KeyIdentifier>, D::Error> {
super::serde_key_identifier::deserialize(deserializer).map(Some)
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum ParseBase64KeyInfoError {
Base64(base64::DecodeError),
KeyInfo(KeyInfoError)
}
impl From<base64::DecodeError> for ParseBase64KeyInfoError {
fn from(src: base64::DecodeError) -> ParseBase64KeyInfoError {
ParseBase64KeyInfoError::Base64(src)
}
}
impl From<KeyInfoError> for ParseBase64KeyInfoError {
fn from(src: KeyInfoError) -> ParseBase64KeyInfoError {
ParseBase64KeyInfoError::KeyInfo(src)
}
}
impl fmt::Display for ParseBase64KeyInfoError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ParseBase64KeyInfoError::Base64(ref inner) => inner.fmt(f),
ParseBase64KeyInfoError::KeyInfo(ref inner) => inner.fmt(f),
}
}
}
impl error::Error for ParseBase64KeyInfoError { }
#[cfg(test)]
mod test {
use super::*;
#[test]
fn base64_binary_from_str() {
assert_eq!(
b"foo",
Base64KeyInfo::from_str("Zm9v").unwrap().as_ref()
);
}
#[test]
fn base64_binary_display() {
assert_eq!(
format!("{}",
Base64KeyInfo::try_from(Vec::from(b"foo".as_ref())).unwrap()
),
"Zm9v"
);
}
#[test]
fn parse_empty_slurm_file() {
let json = r##"
{
"slurmVersion": 1,
"validationOutputFilters": {
"prefixFilters": [],
"bgpsecFilters": []
},
"locallyAddedAssertions": {
"prefixAssertions": [],
"bgpsecAssertions": []
}
}
"##;
let exceptions = SlurmFile::from_str(json).unwrap();
assert_eq!(0, exceptions.filters.prefix.len());
assert_eq!(0, exceptions.filters.bgpsec.len());
assert_eq!(0, exceptions.assertions.prefix.len());
assert_eq!(0, exceptions.assertions.bgpsec.len());
}
#[test]
fn parse_empty_slurm_file_v2() {
let json = r##"
{
"slurmVersion": 2,
"validationOutputFilters": {
"prefixFilters": [],
"bgpsecFilters": [],
"aspaFilters": []
},
"locallyAddedAssertions": {
"prefixAssertions": [],
"bgpsecAssertions": [],
"aspaAssertions": []
}
}
"##;
let exceptions = SlurmFile::from_str(json).unwrap();
assert_eq!(0, exceptions.filters.prefix.len());
assert_eq!(0, exceptions.filters.bgpsec.len());
assert_eq!(0, exceptions.assertions.prefix.len());
assert_eq!(0, exceptions.assertions.bgpsec.len());
}
fn full_slurm() -> SlurmFile {
SlurmFile::new(
ValidationOutputFilters::new(
[
PrefixFilter::new(
Some(Prefix::new_v4(
[192, 0, 2, 0].into(), 24
).unwrap()),
None,
Some(
String::from("All VRPs encompassed by prefix")
)
),
PrefixFilter::new(
None,
Some(64496.into()),
Some(String::from("All VRPs matching ASN"))
),
PrefixFilter::new(
Some(Prefix::new_v4(
[198, 51, 100, 0].into(), 24
).unwrap()),
Some(64497.into()),
Some(String::from(
"All VRPs encompassed by prefix, matching ASN"
))
),
],
[
BgpsecFilter::new(
None,
Some(64496.into()),
Some(String::from("All keys for ASN"))
),
BgpsecFilter::new(
Some(KeyIdentifier::from(*b"12345678901234567890")),
None,
Some(String::from("Key matching Router SKI"))
),
BgpsecFilter::new(
Some(KeyIdentifier::from(*b"deadbeatdeadbeatdead")),
Some(64497.into()),
Some(String::from("Key for ASN matching SKI"))
),
],
),
LocallyAddedAssertions::new(
[
PrefixAssertion::new(
Prefix::new_v4(
[198, 51, 100, 0].into(), 24
).unwrap().into(),
64496.into(),
Some(String::from("My other important route"))
),
PrefixAssertion::new(
MaxLenPrefix::new(
Prefix::new_v6(
[0x2001, 0x0db8, 0, 0, 0, 0, 0, 0].into(),
32
).unwrap(),
Some(48),
).unwrap(),
64496.into(),
Some(String::from("My de-aggregated route"))
),
],
[
BgpsecAssertion::new(
64496.into(),
KeyIdentifier::from(*b"12345678901234567890"),
Bytes::from(b"blubb".as_ref()).try_into().unwrap(),
None,
),
],
),
)
}
#[test]
fn parse_full_slurm_file() {
assert_eq!(
SlurmFile::from_str(
include_str!("../test-data/slurm/full.json")
).unwrap(),
full_slurm()
);
}
fn full_slurm_v2() -> SlurmFile {
SlurmFile::new(
ValidationOutputFilters {
prefix: vec![
PrefixFilter::new(
Some(Prefix::new_v4(
[192, 0, 2, 0].into(), 24
).unwrap()),
None,
Some(
String::from("All VRPs encompassed by prefix")
)
),
PrefixFilter::new(
None,
Some(64496.into()),
Some(String::from("All VRPs matching ASN"))
),
PrefixFilter::new(
Some(Prefix::new_v4(
[198, 51, 100, 0].into(), 24
).unwrap()),
Some(64497.into()),
Some(String::from(
"All VRPs encompassed by prefix, matching ASN"
))
),
],
bgpsec: vec![
BgpsecFilter::new(
None,
Some(64496.into()),
Some(String::from("All keys for ASN"))
),
BgpsecFilter::new(
Some(KeyIdentifier::from(*b"12345678901234567890")),
None,
Some(String::from("Key matching Router SKI"))
),
BgpsecFilter::new(
Some(KeyIdentifier::from(*b"deadbeatdeadbeatdead")),
Some(64497.into()),
Some(String::from("Key for ASN matching SKI"))
),
],
aspa: Some(vec![
AspaFilter::new(
Some(64496.into()),
Some(String::from("ASPAs matching Customer ASID 64496"))
)
]),
},
LocallyAddedAssertions {
prefix: vec![
PrefixAssertion::new(
Prefix::new_v4(
[198, 51, 100, 0].into(), 24
).unwrap().into(),
64496.into(),
Some(String::from("My other important route"))
),
PrefixAssertion::new(
MaxLenPrefix::new(
Prefix::new_v6(
[0x2001, 0x0db8, 0, 0, 0, 0, 0, 0].into(),
32
).unwrap(),
Some(48),
).unwrap(),
64496.into(),
Some(String::from("My de-aggregated route"))
),
],
bgpsec: vec![
BgpsecAssertion::new(
64496.into(),
KeyIdentifier::from(*b"12345678901234567890"),
Bytes::from(b"blubb".as_ref()).try_into().unwrap(),
None,
),
],
aspa: Some(vec![
AspaAssertion::new(
64496.into(),
ProviderAsns::try_from_iter(
[64497.into(), 64498.into()]
).unwrap(),
Some(String::from(
"Locally assert 64497 and 64498 are providers \
for 64496"
))
)
])
},
)
}
#[test]
fn parse_full_slurm_file_v2() {
assert_eq!(
SlurmFile::from_str(
include_str!("../test-data/slurm/full_v2.json")
).unwrap(),
full_slurm_v2()
);
}
#[test]
fn ser_de_slurm_file() {
assert_eq!(
SlurmFile::from_str(&full_slurm().to_string_pretty()).unwrap(),
full_slurm()
)
}
#[test]
fn parse_bad_slurm_files() {
assert!(
SlurmFile::from_str(
r##"
{
"slurmVersion": 0,
"validationOutputFilters": {
"prefixFilters": [],
"bgpsecFilters": []
},
"locallyAddedAssertions": {
"prefixAssertions": [
{
"asn": 64496,
"prefix": "198.51.100.0/24",
"maxPrefixLength": 20,
"comment": "invalid max len"
},
],
"bgpsecAssertions": []
}
}
"##
).is_err()
);
assert!(
SlurmFile::from_str(
r##"
{
"slurmVersion": 2,
"validationOutputFilters": {
"prefixFilters": [],
"bgpsecFilters": []
},
"locallyAddedAssertions": {
"prefixAssertions": [
{
"asn": 64496,
"prefix": "198.51.100.0/24",
"maxPrefixLength": 20,
"comment": "invalid max len"
},
],
"bgpsecAssertions": []
}
}
"##
).is_err()
);
assert!(
SlurmFile::from_str(
r##"
{
"slurmVersion": 1,
"validationOutputFilters": {
"prefixFilters": [],
"bgpsecFilters": []
},
"locallyAddedAssertions": {
"prefixAssertions": [
{
"asn": 64496,
"prefix": "198.51.100.0/24",
"maxPrefixLength": 20,
"comment": "invalid max len"
},
],
"bgpsecAssertions": []
}
}
"##
).is_err()
);
assert!(
SlurmFile::from_str(
r##"
{
"slurmVersion": 1,
"validationOutputFilters": {
"prefixFilters": [],
"bgpsecFilters": []
},
"locallyAddedAssertions": {
"prefixAssertions": [
{
"asn": 64496,
"prefix": "198.51.100.0/44",
"maxPrefixLength": 46,
"comment": "invalid prefix len"
},
],
"bgpsecAssertions": []
}
}
"##
).is_err()
);
assert!(
SlurmFile::from_str(
r##"
{
"slurmVersion": 1,
"validationOutputFilters": {
"prefixFilters": [],
"bgpsecFilters": []
},
"locallyAddedAssertions": {
"prefixAssertions": [
{
"asn": 64496,
"prefix": "198.51.100.0/16",
"maxPrefixLength": 16,
"comment": "non-zero"
},
],
"bgpsecAssertions": []
}
}
"##
).is_err()
);
}
}