use core::net::Ipv4Addr;
use crate::checksum::fletcher16_checkbytes;
use crate::field::Field;
use crate::{CrafterError, Result};
pub mod external;
pub mod network;
pub mod nssa;
pub mod opaque;
pub mod router;
pub mod summary;
pub use external::{OspfAsExternalLsa, OspfExternalTos, OSPF_AS_EXTERNAL_FLAG_E};
pub use network::OspfNetworkLsa;
pub use nssa::{OspfNssaLsa, OSPF_OPTIONS_NP};
pub use opaque::{
opaque_id, opaque_link_state_id, opaque_type, OspfOpaqueLsa, OspfOpaqueTlv, OspfTeLinkTlv,
OSPF_OPAQUE_TYPE_ROUTER_INFORMATION, OSPF_RI_TLV_ROUTER_FUNCTIONAL_CAPABILITIES,
OSPF_RI_TLV_ROUTER_INFORMATIONAL_CAPABILITIES, OSPF_TE_LINK_TYPE_MULTI_ACCESS,
OSPF_TE_LINK_TYPE_POINT_TO_POINT, OSPF_TE_OPAQUE_TYPE, OSPF_TE_SUBTLV_LINK_ID,
OSPF_TE_SUBTLV_LINK_TYPE, OSPF_TE_SUBTLV_LOCAL_INTERFACE_IP, OSPF_TE_SUBTLV_MAX_BANDWIDTH,
OSPF_TE_SUBTLV_REMOTE_INTERFACE_IP, OSPF_TE_SUBTLV_TE_METRIC, OSPF_TE_TLV_LINK,
OSPF_TE_TLV_ROUTER_ADDRESS,
};
pub use router::{
ospf_router_link_type_name, OspfRouterLink, OspfRouterLinkTos, OspfRouterLsa,
OSPF_ROUTER_LINK_POINT_TO_POINT, OSPF_ROUTER_LINK_STUB, OSPF_ROUTER_LINK_TRANSIT,
OSPF_ROUTER_LINK_VIRTUAL, OSPF_ROUTER_LSA_FLAG_B, OSPF_ROUTER_LSA_FLAG_E,
OSPF_ROUTER_LSA_FLAG_V,
};
pub use summary::{OspfSummaryLsa, OspfSummaryTos};
pub const OSPF_LSA_ROUTER: u8 = 1;
pub const OSPF_LSA_NETWORK: u8 = 2;
pub const OSPF_LSA_SUMMARY_IP: u8 = 3;
pub const OSPF_LSA_SUMMARY_ASBR: u8 = 4;
pub const OSPF_LSA_AS_EXTERNAL: u8 = 5;
pub const OSPF_LSA_NSSA: u8 = 7;
pub const OSPF_LSA_OPAQUE_LINK_LOCAL: u8 = 9;
pub const OSPF_LSA_OPAQUE_AREA: u8 = 10;
pub const OSPF_LSA_OPAQUE_AS: u8 = 11;
pub fn ospf_lsa_type_name(ls_type: u8) -> &'static str {
match ls_type {
OSPF_LSA_ROUTER => "Router",
OSPF_LSA_NETWORK => "Network",
OSPF_LSA_SUMMARY_IP => "Summary-IP",
OSPF_LSA_SUMMARY_ASBR => "Summary-ASBR",
OSPF_LSA_AS_EXTERNAL => "AS-External",
OSPF_LSA_NSSA => "NSSA",
OSPF_LSA_OPAQUE_LINK_LOCAL => "Opaque-LinkLocal",
OSPF_LSA_OPAQUE_AREA => "Opaque-Area",
OSPF_LSA_OPAQUE_AS => "Opaque-AS",
_ => "Unknown",
}
}
pub const OSPF_LSA_HEADER_LEN: usize = 20;
const OSPF_LSA_INITIAL_SEQUENCE_NUMBER: u32 = 0x8000_0001;
const OSPF_LSA_CHECKSUM_OFFSET: usize = 16;
const OSPF_LSA_CHECKSUM_START: usize = 2;
#[derive(Debug, Clone)]
pub struct OspfLsaHeader {
ls_age: Field<u16>,
options: Field<u8>,
ls_type: Field<u8>,
link_state_id: Field<Ipv4Addr>,
advertising_router: Field<Ipv4Addr>,
ls_sequence_number: Field<u32>,
ls_checksum: Field<u16>,
length: Field<u16>,
}
impl OspfLsaHeader {
pub fn new() -> Self {
Self {
ls_age: Field::defaulted(0),
options: Field::defaulted(0),
ls_type: Field::unset(),
link_state_id: Field::unset(),
advertising_router: Field::unset(),
ls_sequence_number: Field::defaulted(OSPF_LSA_INITIAL_SEQUENCE_NUMBER),
ls_checksum: Field::unset(),
length: Field::unset(),
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn from_decoded_parts(
ls_age: u16,
options: u8,
ls_type: u8,
link_state_id: Ipv4Addr,
advertising_router: Ipv4Addr,
ls_sequence_number: u32,
ls_checksum: u16,
length: u16,
) -> Self {
Self {
ls_age: Field::user(ls_age),
options: Field::user(options),
ls_type: Field::user(ls_type),
link_state_id: Field::user(link_state_id),
advertising_router: Field::user(advertising_router),
ls_sequence_number: Field::user(ls_sequence_number),
ls_checksum: Field::user(ls_checksum),
length: Field::user(length),
}
}
pub fn ls_age(mut self, ls_age: u16) -> Self {
self.ls_age.set_user(ls_age);
self
}
pub fn options(mut self, options: u8) -> Self {
self.options.set_user(options);
self
}
pub fn ls_type(mut self, ls_type: u8) -> Self {
self.ls_type.set_user(ls_type);
self
}
pub fn link_state_id(mut self, link_state_id: impl Into<Ipv4Addr>) -> Self {
self.link_state_id.set_user(link_state_id.into());
self
}
pub fn advertising_router(mut self, advertising_router: impl Into<Ipv4Addr>) -> Self {
self.advertising_router.set_user(advertising_router.into());
self
}
pub fn ls_sequence_number(mut self, ls_sequence_number: u32) -> Self {
self.ls_sequence_number.set_user(ls_sequence_number);
self
}
pub fn ls_checksum(mut self, ls_checksum: u16) -> Self {
self.ls_checksum.set_user(ls_checksum);
self
}
pub fn length(mut self, length: u16) -> Self {
self.length.set_user(length);
self
}
pub fn ls_age_value(&self) -> u16 {
self.ls_age.value().copied().unwrap_or(0)
}
pub fn options_value(&self) -> u8 {
self.options.value().copied().unwrap_or(0)
}
pub fn ls_type_value(&self) -> u8 {
self.ls_type.value().copied().unwrap_or(0)
}
pub fn link_state_id_value(&self) -> Ipv4Addr {
self.link_state_id
.value()
.copied()
.unwrap_or(Ipv4Addr::UNSPECIFIED)
}
pub fn advertising_router_value(&self) -> Ipv4Addr {
self.advertising_router
.value()
.copied()
.unwrap_or(Ipv4Addr::UNSPECIFIED)
}
pub fn ls_sequence_number_value(&self) -> u32 {
self.ls_sequence_number
.value()
.copied()
.unwrap_or(OSPF_LSA_INITIAL_SEQUENCE_NUMBER)
}
pub fn ls_checksum_value(&self) -> Option<u16> {
self.ls_checksum.value().copied()
}
pub fn length_value(&self) -> Option<u16> {
self.length.value().copied()
}
pub fn summary(&self) -> String {
let length = match self.length_value() {
Some(length) => length.to_string(),
None => "auto".to_string(),
};
format!(
"LSA(type={}, id={}, adv={}, seq=0x{:08x}, age={}, len={})",
ospf_lsa_type_name(self.ls_type_value()),
self.link_state_id_value(),
self.advertising_router_value(),
self.ls_sequence_number_value(),
self.ls_age_value(),
length,
)
}
pub fn encode_with_body(&self, body: &[u8], out: &mut Vec<u8>) {
let start = out.len();
out.extend_from_slice(&self.ls_age_value().to_be_bytes());
out.push(self.options_value());
out.push(self.ls_type_value());
out.extend_from_slice(&self.link_state_id_value().octets());
out.extend_from_slice(&self.advertising_router_value().octets());
out.extend_from_slice(&self.ls_sequence_number_value().to_be_bytes());
let pinned_checksum = self.ls_checksum.value().copied();
out.extend_from_slice(&pinned_checksum.unwrap_or(0).to_be_bytes());
let length = self
.length
.value()
.copied()
.unwrap_or((OSPF_LSA_HEADER_LEN + body.len()) as u16);
out.extend_from_slice(&length.to_be_bytes());
out.extend_from_slice(body);
if pinned_checksum.is_none() {
let protected = &out[start + OSPF_LSA_CHECKSUM_START..];
let checkbytes = fletcher16_checkbytes(
protected,
OSPF_LSA_CHECKSUM_OFFSET - OSPF_LSA_CHECKSUM_START,
);
out[start + OSPF_LSA_CHECKSUM_OFFSET..start + OSPF_LSA_CHECKSUM_OFFSET + 2]
.copy_from_slice(&checkbytes);
}
}
pub fn decode(bytes: &[u8]) -> Result<(OspfLsaHeader, usize)> {
if bytes.len() < OSPF_LSA_HEADER_LEN {
return Err(CrafterError::buffer_too_short(
"ospf lsa header",
OSPF_LSA_HEADER_LEN,
bytes.len(),
));
}
let ls_age = u16::from_be_bytes([bytes[0], bytes[1]]);
let options = bytes[2];
let ls_type = bytes[3];
let link_state_id = Ipv4Addr::new(bytes[4], bytes[5], bytes[6], bytes[7]);
let advertising_router = Ipv4Addr::new(bytes[8], bytes[9], bytes[10], bytes[11]);
let ls_sequence_number = u32::from_be_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
let ls_checksum = u16::from_be_bytes([bytes[16], bytes[17]]);
let length = u16::from_be_bytes([bytes[18], bytes[19]]);
let header = OspfLsaHeader::from_decoded_parts(
ls_age,
options,
ls_type,
link_state_id,
advertising_router,
ls_sequence_number,
ls_checksum,
length,
);
Ok((header, length as usize))
}
}
impl Default for OspfLsaHeader {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub enum OspfLsaBody {
Router(OspfRouterLsa),
Network(OspfNetworkLsa),
Summary(OspfSummaryLsa),
AsExternal(OspfAsExternalLsa),
Nssa(OspfNssaLsa),
Opaque(OspfOpaqueLsa),
Raw(Vec<u8>),
}
impl OspfLsaBody {
pub(crate) fn encoded_len(&self) -> usize {
match self {
OspfLsaBody::Router(router) => router.encoded_len(),
OspfLsaBody::Network(network) => network.encoded_len(),
OspfLsaBody::Summary(summary) => summary.encoded_len(),
OspfLsaBody::AsExternal(external) => external.encoded_len(),
OspfLsaBody::Nssa(nssa) => nssa.encoded_len(),
OspfLsaBody::Opaque(opaque) => opaque.encoded_len(),
OspfLsaBody::Raw(body) => body.len(),
}
}
pub(crate) fn encode(&self, out: &mut Vec<u8>) {
match self {
OspfLsaBody::Router(router) => router.encode(out),
OspfLsaBody::Network(network) => network.encode(out),
OspfLsaBody::Summary(summary) => summary.encode(out),
OspfLsaBody::AsExternal(external) => external.encode(out),
OspfLsaBody::Nssa(nssa) => nssa.encode(out),
OspfLsaBody::Opaque(opaque) => opaque.encode(out),
OspfLsaBody::Raw(body) => out.extend_from_slice(body),
}
}
}
#[derive(Debug, Clone)]
pub struct OspfLsa {
pub header: OspfLsaHeader,
pub body: OspfLsaBody,
}
impl OspfLsa {
pub fn new(header: OspfLsaHeader, body: OspfLsaBody) -> Self {
Self { header, body }
}
pub(crate) fn encoded_len(&self) -> usize {
OSPF_LSA_HEADER_LEN + self.body.encoded_len()
}
pub(crate) fn encode(&self, out: &mut Vec<u8>) {
let mut body_bytes = Vec::with_capacity(self.body.encoded_len());
self.body.encode(&mut body_bytes);
self.header.encode_with_body(&body_bytes, out);
}
}
pub(crate) fn decode_lsa_headers(mut bytes: &[u8]) -> Result<Vec<OspfLsaHeader>> {
let mut headers = Vec::new();
while !bytes.is_empty() {
if bytes.len() < OSPF_LSA_HEADER_LEN {
return Err(CrafterError::buffer_too_short(
"ospf lsa header",
OSPF_LSA_HEADER_LEN,
bytes.len(),
));
}
let (header, _declared_len) = OspfLsaHeader::decode(bytes)?;
headers.push(header);
bytes = &bytes[OSPF_LSA_HEADER_LEN..];
}
Ok(headers)
}
pub(crate) fn encode_lsa_headers(headers: &[OspfLsaHeader], out: &mut Vec<u8>) {
for header in headers {
header.encode_with_body(&[], out);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::checksum::fletcher16_valid;
#[test]
fn ospf_lsa_header_round_trips_with_auto_length_and_checksum() {
let body = [0xde, 0xad, 0xbe, 0xef, 0x01, 0x02];
let header = OspfLsaHeader::new()
.ls_age(0)
.options(0x22)
.ls_type(OSPF_LSA_ROUTER)
.link_state_id(Ipv4Addr::new(192, 0, 2, 1))
.advertising_router(Ipv4Addr::new(192, 0, 2, 1))
.ls_sequence_number(0x8000_0001);
let mut out = Vec::new();
header.encode_with_body(&body, &mut out);
assert_eq!(out.len(), OSPF_LSA_HEADER_LEN + body.len());
let expected_len = (OSPF_LSA_HEADER_LEN + body.len()) as u16;
assert_eq!(&out[18..20], &expected_len.to_be_bytes());
assert!(
fletcher16_valid(&out),
"auto-filled Fletcher checksum should validate over the LSA"
);
let (decoded, declared_len) = OspfLsaHeader::decode(&out).expect("LSA header decodes");
assert_eq!(declared_len, OSPF_LSA_HEADER_LEN + body.len());
assert_eq!(decoded.ls_age_value(), 0);
assert_eq!(decoded.options_value(), 0x22);
assert_eq!(decoded.ls_type_value(), OSPF_LSA_ROUTER);
assert_eq!(decoded.link_state_id_value(), Ipv4Addr::new(192, 0, 2, 1));
assert_eq!(
decoded.advertising_router_value(),
Ipv4Addr::new(192, 0, 2, 1)
);
assert_eq!(decoded.ls_sequence_number_value(), 0x8000_0001);
assert_eq!(decoded.length_value(), Some(expected_len));
let mut reencoded = Vec::new();
decoded.encode_with_body(&body, &mut reencoded);
assert_eq!(reencoded, out);
}
#[test]
fn ospf_lsa_header_user_checksum_survives_encode() {
let body = [0x00, 0x01, 0x02, 0x03];
let header = OspfLsaHeader::new()
.ls_type(OSPF_LSA_NETWORK)
.link_state_id(Ipv4Addr::new(192, 0, 2, 1))
.advertising_router(Ipv4Addr::new(192, 0, 2, 2))
.ls_checksum(0xBEEF);
let mut out = Vec::new();
header.encode_with_body(&body, &mut out);
assert_eq!(&out[16..18], &0xBEEFu16.to_be_bytes());
assert_eq!(
&out[18..20],
&((OSPF_LSA_HEADER_LEN + body.len()) as u16).to_be_bytes()
);
}
#[test]
fn ospf_lsa_header_decode_rejects_short_buffer() {
let short = [0u8; OSPF_LSA_HEADER_LEN - 1];
let err = OspfLsaHeader::decode(&short).expect_err("a short buffer must error");
match err {
CrafterError::BufferTooShort {
context,
required,
available,
} => {
assert_eq!(context, "ospf lsa header");
assert_eq!(required, OSPF_LSA_HEADER_LEN);
assert_eq!(available, OSPF_LSA_HEADER_LEN - 1);
}
other => panic!("expected BufferTooShort, got {other:?}"),
}
}
#[test]
fn ospf_lsa_type_name_and_summary_render_expected_labels() {
assert_eq!(ospf_lsa_type_name(OSPF_LSA_ROUTER), "Router");
assert_eq!(ospf_lsa_type_name(OSPF_LSA_NETWORK), "Network");
assert_eq!(ospf_lsa_type_name(OSPF_LSA_SUMMARY_IP), "Summary-IP");
assert_eq!(ospf_lsa_type_name(OSPF_LSA_SUMMARY_ASBR), "Summary-ASBR");
assert_eq!(ospf_lsa_type_name(OSPF_LSA_AS_EXTERNAL), "AS-External");
assert_eq!(ospf_lsa_type_name(OSPF_LSA_NSSA), "NSSA");
assert_eq!(
ospf_lsa_type_name(OSPF_LSA_OPAQUE_LINK_LOCAL),
"Opaque-LinkLocal"
);
assert_eq!(ospf_lsa_type_name(OSPF_LSA_OPAQUE_AREA), "Opaque-Area");
assert_eq!(ospf_lsa_type_name(OSPF_LSA_OPAQUE_AS), "Opaque-AS");
assert_eq!(ospf_lsa_type_name(0), "Unknown");
assert_eq!(ospf_lsa_type_name(6), "Unknown");
let header = OspfLsaHeader::new()
.ls_type(OSPF_LSA_ROUTER)
.link_state_id(Ipv4Addr::new(192, 0, 2, 1))
.advertising_router(Ipv4Addr::new(198, 51, 100, 7))
.ls_sequence_number(0x8000_0001)
.ls_age(0);
let summary = header.summary();
assert!(
summary.contains("Router"),
"summary should contain the LS type name: {summary}"
);
assert!(
summary.contains("198.51.100.7"),
"summary should contain the advertising router: {summary}"
);
}
#[test]
fn ospf_lsa_headers_list_round_trips_and_rejects_partial_trailer() {
let headers = [
OspfLsaHeader::new()
.ls_type(OSPF_LSA_ROUTER)
.link_state_id(Ipv4Addr::new(192, 0, 2, 1))
.advertising_router(Ipv4Addr::new(192, 0, 2, 1))
.ls_sequence_number(0x8000_0001),
OspfLsaHeader::new()
.ls_type(OSPF_LSA_NETWORK)
.link_state_id(Ipv4Addr::new(192, 0, 2, 2))
.advertising_router(Ipv4Addr::new(198, 51, 100, 7))
.ls_sequence_number(0x8000_0002),
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_0003),
];
let mut out = Vec::new();
encode_lsa_headers(&headers, &mut out);
assert_eq!(out.len(), 3 * OSPF_LSA_HEADER_LEN);
let decoded = decode_lsa_headers(&out).expect("LSA header list decodes");
assert_eq!(decoded.len(), 3);
for (original, parsed) in headers.iter().zip(decoded.iter()) {
assert_eq!(parsed.ls_age_value(), original.ls_age_value());
assert_eq!(parsed.options_value(), original.options_value());
assert_eq!(parsed.ls_type_value(), original.ls_type_value());
assert_eq!(parsed.link_state_id_value(), original.link_state_id_value());
assert_eq!(
parsed.advertising_router_value(),
original.advertising_router_value()
);
assert_eq!(
parsed.ls_sequence_number_value(),
original.ls_sequence_number_value()
);
assert_eq!(parsed.length_value(), Some(OSPF_LSA_HEADER_LEN as u16));
}
let partial = &out[..2 * OSPF_LSA_HEADER_LEN + 10];
assert_eq!(partial.len(), 50);
let err = decode_lsa_headers(partial).expect_err("a partial trailing header must error");
match err {
CrafterError::BufferTooShort {
context,
required,
available,
} => {
assert_eq!(context, "ospf lsa header");
assert_eq!(required, OSPF_LSA_HEADER_LEN);
assert_eq!(available, 10);
}
other => panic!("expected BufferTooShort, got {other:?}"),
}
}
}