use core::net::Ipv4Addr;
use crate::field::Field;
const OSPF_SUMMARY_LSA_MASK_LEN: usize = 4;
const OSPF_SUMMARY_LSA_TOS_LEN: usize = 4;
const OSPF_SUMMARY_LSA_METRIC_MAX: u32 = 0x00ff_ffff;
#[derive(Debug, Clone)]
pub struct OspfSummaryTos {
tos: u8,
metric: u32,
}
impl OspfSummaryTos {
pub fn new(tos: u8, metric: u32) -> Self {
Self { tos, metric }
}
pub fn tos_value(&self) -> u8 {
self.tos
}
pub fn metric_value(&self) -> u32 {
self.metric
}
}
#[derive(Debug, Clone)]
pub struct OspfSummaryLsa {
network_mask: Field<Ipv4Addr>,
entries: Vec<OspfSummaryTos>,
}
impl OspfSummaryLsa {
pub fn new() -> Self {
Self {
network_mask: Field::unset(),
entries: vec![OspfSummaryTos::new(0, 0)],
}
}
pub(crate) fn from_decoded_parts(network_mask: Ipv4Addr, entries: Vec<OspfSummaryTos>) -> Self {
Self {
network_mask: Field::user(network_mask),
entries,
}
}
pub fn network_mask(mut self, network_mask: impl Into<Ipv4Addr>) -> Self {
self.network_mask.set_user(network_mask.into());
self
}
pub fn metric(mut self, metric: u32) -> Self {
match self.entries.first_mut() {
Some(entry) => {
entry.tos = 0;
entry.metric = metric;
}
None => self.entries.push(OspfSummaryTos::new(0, metric)),
}
self
}
pub fn tos_entry(mut self, tos: u8, metric: u32) -> Self {
self.entries.push(OspfSummaryTos::new(tos, metric));
self
}
pub fn network_mask_value(&self) -> Ipv4Addr {
self.network_mask
.value()
.copied()
.unwrap_or(Ipv4Addr::UNSPECIFIED)
}
pub fn entries_value(&self) -> &[OspfSummaryTos] {
&self.entries
}
pub fn summary(&self) -> String {
let metric = self
.entries
.first()
.map(|entry| (entry.metric & OSPF_SUMMARY_LSA_METRIC_MAX).to_string())
.unwrap_or_else(|| "none".to_string());
format!(
"mask={} metric={} tos={}",
self.network_mask_value(),
metric,
self.entries.len(),
)
}
pub(crate) fn encoded_len(&self) -> usize {
OSPF_SUMMARY_LSA_MASK_LEN + self.entries.len() * OSPF_SUMMARY_LSA_TOS_LEN
}
pub(crate) fn encode(&self, out: &mut Vec<u8>) {
out.extend_from_slice(&self.network_mask_value().octets());
for entry in &self.entries {
out.push(entry.tos);
let metric = entry.metric & OSPF_SUMMARY_LSA_METRIC_MAX;
out.push((metric >> 16) as u8);
out.push((metric >> 8) as u8);
out.push(metric as u8);
}
}
}
impl Default for OspfSummaryLsa {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::checksum::fletcher16_valid;
use crate::protocols::ospf::lsa::{
OspfLsa, OspfLsaBody, OspfLsaHeader, OSPF_LSA_HEADER_LEN, OSPF_LSA_SUMMARY_IP,
};
use crate::protocols::ospf::packet::link_state_update::OspfLinkStateUpdate;
#[test]
fn ospf_summary_lsa_type3_round_trips_in_lsu() {
let metric = 0x000a_0b0c;
let summary = OspfSummaryLsa::new()
.network_mask(Ipv4Addr::new(255, 255, 255, 0))
.metric(metric);
assert_eq!(
summary.network_mask_value(),
Ipv4Addr::new(255, 255, 255, 0)
);
assert_eq!(summary.entries_value().len(), 1);
assert_eq!(summary.entries_value()[0].tos_value(), 0);
assert_eq!(summary.entries_value()[0].metric_value(), metric);
let mut body = Vec::new();
summary.encode(&mut body);
assert_eq!(body.len(), summary.encoded_len());
let expected: Vec<u8> = vec![
255, 255, 255, 0, 0x00, 0x0a, 0x0b, 0x0c,
];
assert_eq!(body, expected);
assert_eq!(&body[0..4], &[255, 255, 255, 0]);
assert_eq!(body[4], 0x00);
assert_eq!(&body[5..8], &[0x0a, 0x0b, 0x0c]);
let lsa = OspfLsa::new(
OspfLsaHeader::new()
.ls_type(OSPF_LSA_SUMMARY_IP)
.link_state_id(Ipv4Addr::new(198, 51, 100, 0))
.advertising_router(Ipv4Addr::new(192, 0, 2, 1))
.ls_sequence_number(0x8000_0001),
OspfLsaBody::Summary(summary),
);
let lsu = OspfLinkStateUpdate::new().lsa(lsa);
let mut update = Vec::new();
lsu.encode(&mut update);
assert_eq!(&update[0..4], &1u32.to_be_bytes());
let lsa_bytes = &update[4..];
assert_eq!(lsa_bytes.len(), OSPF_LSA_HEADER_LEN + expected.len());
let expected_lsa_len = (OSPF_LSA_HEADER_LEN + expected.len()) as u16;
assert_eq!(&lsa_bytes[18..20], &expected_lsa_len.to_be_bytes());
assert_eq!(&lsa_bytes[OSPF_LSA_HEADER_LEN..], expected.as_slice());
assert!(
fletcher16_valid(lsa_bytes),
"auto-filled Fletcher checksum should validate over the Summary-LSA"
);
}
}