use super::capability::{Afi, Safi};
use super::cidr::Cidr;
use super::endec::Component;
use super::path::{self, AsPath, AsSegment, AsSegmentType, MpNextHop, Origin, PathAttributes};
use super::route::Routes;
use std::net::IpAddr;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct UpdateBuilder {
pub withdrawn_ipv4_routes: Routes,
pub withdrawn_ipv6_routes: Routes,
pub nlri_ipv4_routes: Routes,
pub nlri_ipv6_routes: Routes,
pub origin: Option<Origin>,
pub as_path: AsPath,
pub next_hop: Option<MpNextHop>,
pub other_path_attrs: PathAttributes,
pub enable_mp_bgp: bool,
}
impl UpdateBuilder {
#[must_use]
pub fn new(enable_mp_bgp: bool) -> Self {
Self {
enable_mp_bgp,
..Default::default()
}
}
#[must_use]
pub fn withdraw_ipv4_routes(mut self, routes: Routes) -> Self {
self.withdrawn_ipv4_routes = routes;
self
}
#[must_use]
pub fn withdraw_ipv6_routes(mut self, routes: Routes) -> Self {
self.withdrawn_ipv6_routes = routes;
self
}
#[must_use]
pub fn withdraw_route(mut self, route: Cidr) -> Self {
match route {
Cidr::V4(route) => self.withdrawn_ipv4_routes.0.push(route.into()),
Cidr::V6(route) => self.withdrawn_ipv6_routes.0.push(route.into()),
}
self
}
#[must_use]
pub fn add_ipv4_routes(mut self, routes: Routes) -> Self {
self.nlri_ipv4_routes = routes;
self
}
#[must_use]
pub fn add_ipv6_routes(mut self, routes: Routes) -> Self {
self.nlri_ipv6_routes = routes;
self
}
#[must_use]
pub fn add_route(mut self, route: Cidr) -> Self {
match route {
Cidr::V4(route) => self.nlri_ipv4_routes.0.push(route.into()),
Cidr::V6(route) => self.nlri_ipv6_routes.0.push(route.into()),
}
self
}
#[must_use]
pub const fn set_origin(mut self, origin: Origin) -> Self {
self.origin = Some(origin);
self
}
#[must_use]
pub fn set_as_path(mut self, type_: AsSegmentType, asns: Vec<u32>) -> Self {
let as4 = asns.iter().any(|&asn| asn > u32::from(u16::MAX));
let segment = AsSegment { type_, asns, as4 };
self.as_path.0.push(segment);
self
}
#[must_use]
pub const fn set_next_hop(mut self, next_hop: MpNextHop) -> Self {
self.next_hop = Some(next_hop);
self
}
#[must_use]
pub fn path_attribute(mut self, attr: path::Value) -> Self {
self.other_path_attrs.0.push(attr);
self
}
fn check_next_hop(&mut self) -> Result<(), crate::Error> {
if let Some(next_hop) = &self.next_hop {
if self.enable_mp_bgp {
Ok(())
} else if let MpNextHop::Single(IpAddr::V4(addr)) = next_hop {
self.other_path_attrs.0.push(path::Value {
flags: path::Flags::WELL_KNOWN_COMPLETE,
data: path::Data::NextHop(*addr),
});
Ok(())
} else {
Err(crate::Error::NoMpBgp)
}
} else if !self.nlri_ipv6_routes.is_empty() || !self.withdrawn_ipv6_routes.is_empty() {
Err(crate::Error::NoNextHop)
} else {
Ok(())
}
}
const fn make_mp_unreach_nlri(routes: Routes, afi: Afi) -> path::Value {
let mp_unreach_nlri = path::MpUnreachNlri {
afi,
safi: Safi::Unicast,
withdrawn_routes: routes,
};
path::Value {
flags: path::Flags::OPTIONAL_TRANSITIVE_EXTENDED,
data: path::Data::MpUnreachNlri(mp_unreach_nlri),
}
}
const fn make_mp_reach_nlri(routes: Routes, afi: Afi, next_hop: MpNextHop) -> path::Value {
let mp_reach_nlri = path::MpReachNlri {
afi,
safi: Safi::Unicast,
next_hop,
nlri: routes,
};
path::Value {
flags: path::Flags::OPTIONAL_TRANSITIVE_EXTENDED,
data: path::Data::MpReachNlri(mp_reach_nlri),
}
}
fn make_mp_unreach_update(
all_withdrawn_routes: Routes,
afi: Afi,
allowed_size: usize,
common_path_attributes: &PathAttributes,
updates: &mut Vec<super::Update>,
) {
let route_splits = all_withdrawn_routes.split_routes_to_allowed_size_rev(allowed_size);
let mut leftover = all_withdrawn_routes.0;
for end in route_splits {
let withdrawn_routes = leftover.split_off(end);
let mut this_path_attributes = common_path_attributes.clone();
this_path_attributes
.0
.push(Self::make_mp_unreach_nlri(withdrawn_routes.into(), afi));
updates.push(super::Update {
withdrawn_routes: Routes::default(),
path_attributes: this_path_attributes,
nlri: Routes::default(),
});
}
}
fn make_mp_reach_update(
all_nlri_routes: Routes,
afi: Afi,
allowed_size: usize,
common_path_attributes: &PathAttributes,
next_hop: MpNextHop,
updates: &mut Vec<super::Update>,
) {
let route_splits = all_nlri_routes.split_routes_to_allowed_size_rev(allowed_size);
let mut leftover = all_nlri_routes.0;
for end in route_splits {
let nlri_routes = leftover.split_off(end);
let mut this_path_attributes = common_path_attributes.clone();
this_path_attributes.0.push(Self::make_mp_reach_nlri(
nlri_routes.into(),
afi,
next_hop,
));
updates.push(super::Update {
withdrawn_routes: Routes::default(),
path_attributes: this_path_attributes,
nlri: Routes::default(),
});
}
}
pub fn build(mut self) -> Result<Vec<super::Update>, crate::Error> {
self.check_next_hop()?;
let Self {
withdrawn_ipv4_routes,
withdrawn_ipv6_routes,
nlri_ipv4_routes,
nlri_ipv6_routes,
origin,
as_path,
next_hop,
other_path_attrs: mut small_attrs,
enable_mp_bgp,
} = self;
if let Some(origin) = origin {
let pa = path::Value {
flags: path::Flags::WELL_KNOWN_COMPLETE,
data: path::Data::Origin(origin),
};
small_attrs.0.push(pa);
}
small_attrs.0.push(path::Value {
flags: path::Flags::WELL_KNOWN_COMPLETE,
data: path::Data::AsPath(as_path),
});
let mut updates = Vec::new();
if enable_mp_bgp {
let remaining_size = 4096 - 19 - 4 - 3 - small_attrs.encoded_len(); Self::make_mp_unreach_update(
withdrawn_ipv4_routes,
Afi::Ipv4,
remaining_size,
&small_attrs,
&mut updates,
);
Self::make_mp_unreach_update(
withdrawn_ipv6_routes,
Afi::Ipv6,
remaining_size,
&small_attrs,
&mut updates,
);
if let Some(next_hop) = next_hop {
let remaining_size =
4096 - 19 - 4 - 4 - next_hop.encoded_len() - small_attrs.encoded_len(); Self::make_mp_reach_update(
nlri_ipv4_routes,
Afi::Ipv4,
remaining_size,
&small_attrs,
next_hop,
&mut updates,
);
Self::make_mp_reach_update(
nlri_ipv6_routes,
Afi::Ipv6,
remaining_size,
&small_attrs,
next_hop,
&mut updates,
);
}
} else {
let remaining_size = 4096 - 19 - 4 - small_attrs.encoded_len(); let route_splits =
withdrawn_ipv4_routes.split_routes_to_allowed_size_rev(remaining_size);
let mut leftover = withdrawn_ipv4_routes.0;
for end in route_splits {
let withdrawn_routes = leftover.split_off(end);
updates.push(super::Update {
withdrawn_routes: withdrawn_routes.into(),
path_attributes: small_attrs.clone(),
nlri: Routes::default(),
});
}
if let Some(MpNextHop::Single(IpAddr::V4(next_hop))) = next_hop {
let remaining_size = remaining_size - 4 - 3; let route_splits =
nlri_ipv4_routes.split_routes_to_allowed_size_rev(remaining_size);
let mut leftover = nlri_ipv4_routes.0;
small_attrs.0.push(path::Value {
flags: path::Flags::WELL_KNOWN_COMPLETE,
data: path::Data::NextHop(next_hop),
});
for end in route_splits {
let nlri_routes = leftover.split_off(end);
updates.push(super::Update {
withdrawn_routes: Routes::default(),
path_attributes: small_attrs.clone(),
nlri: nlri_routes.into(),
});
}
}
}
Ok(updates)
}
}