use core::net::Ipv4Addr;
use crate::error::{CrafterError, Result};
use crate::field::Field;
use crate::packet::Packet;
use crate::protocols::ip::shared::IPPROTO_OSPF;
use crate::protocols::ospf::OspfChecksumStatus;
use super::constants::{
OSPFV3_HEADER_LEN, OSPFV3_TYPE_DATABASE_DESCRIPTION, OSPFV3_TYPE_HELLO,
OSPFV3_TYPE_LINK_STATE_ACK, OSPFV3_TYPE_LINK_STATE_REQUEST, OSPFV3_TYPE_LINK_STATE_UPDATE,
};
use super::hello::Ospfv3Hello;
use super::lsa::{
decode_ospfv3_lsa_headers, Ospfv3LinkStateUpdate, Ospfv3Lsa, Ospfv3LsaBody, Ospfv3LsaHeader,
Ospfv3NetworkLsa, Ospfv3RouterInterface, Ospfv3RouterLsa, OSPFV3_LSA_HEADER_LEN,
};
use super::packet::{
Ospfv3DatabaseDescription, Ospfv3LinkStateAck, Ospfv3LinkStateRequest,
Ospfv3LinkStateRequestEntry,
};
use super::{Ospfv3, Ospfv3Body};
const OSPFV3_LSA_ROUTER: u16 = 0x2001;
const OSPFV3_LSA_NETWORK: u16 = 0x2002;
const OSPFV3_HELLO_FIXED_LEN: usize = 20;
const OSPFV3_DD_FIXED_LEN: usize = 12;
const OSPFV3_LSR_ENTRY_LEN: usize = 12;
const OSPFV3_LSU_COUNT_LEN: usize = 4;
const OSPFV3_ROUTER_LSA_FIXED_LEN: usize = 4;
const OSPFV3_ROUTER_LSA_INTERFACE_LEN: usize = 16;
const OSPFV3_NETWORK_LSA_FIXED_LEN: usize = 4;
const OSPFV3_NETWORK_LSA_ROUTER_LEN: usize = 4;
const OSPFV3_CHECKSUM_OFFSET: usize = 12;
#[allow(dead_code)]
pub(crate) fn append_ospfv3_packet(packet: Packet, bytes: &[u8]) -> Result<Packet> {
append_ospfv3_packet_with_checksum_validation(packet, bytes, true)
}
pub(crate) fn append_ospfv3_packet_with_checksum_validation(
mut packet: Packet,
bytes: &[u8],
validate_checksum: bool,
) -> Result<Packet> {
if bytes.len() < OSPFV3_HEADER_LEN {
return Err(CrafterError::buffer_too_short(
"ospfv3 header",
OSPFV3_HEADER_LEN,
bytes.len(),
));
}
let version = bytes[0];
let packet_type = bytes[1];
let packet_length = u16::from_be_bytes([bytes[2], bytes[3]]);
let router_id = Ipv4Addr::new(bytes[4], bytes[5], bytes[6], bytes[7]);
let area_id = Ipv4Addr::new(bytes[8], bytes[9], bytes[10], bytes[11]);
let checksum = u16::from_be_bytes([bytes[12], bytes[13]]);
let instance_id = bytes[14];
let reserved = bytes[15];
let declared = packet_length as usize;
let body_end = if (OSPFV3_HEADER_LEN..=bytes.len()).contains(&declared) {
declared
} else {
bytes.len()
};
let body_bytes = &bytes[OSPFV3_HEADER_LEN..body_end];
let body = match packet_type {
OSPFV3_TYPE_HELLO => Ospfv3Body::Hello(decode_ospfv3_hello(body_bytes)?),
OSPFV3_TYPE_DATABASE_DESCRIPTION => {
Ospfv3Body::DatabaseDescription(decode_ospfv3_database_description(body_bytes)?)
}
OSPFV3_TYPE_LINK_STATE_REQUEST => {
Ospfv3Body::LinkStateRequest(decode_ospfv3_link_state_request(body_bytes)?)
}
OSPFV3_TYPE_LINK_STATE_UPDATE => {
Ospfv3Body::LinkStateUpdate(decode_ospfv3_link_state_update(body_bytes)?)
}
OSPFV3_TYPE_LINK_STATE_ACK => {
Ospfv3Body::LinkStateAck(decode_ospfv3_link_state_ack(body_bytes)?)
}
other => Ospfv3Body::Unknown {
type_code: other,
body: body_bytes.to_vec(),
},
};
let checksum_status = if validate_checksum {
match ipv6_pseudo_header(&packet) {
Some(pseudo_header) => {
let mut zeroed = bytes[..body_end].to_vec();
zeroed[OSPFV3_CHECKSUM_OFFSET] = 0;
zeroed[OSPFV3_CHECKSUM_OFFSET + 1] = 0;
let computed = pseudo_header.checksum(&zeroed);
if computed == checksum {
OspfChecksumStatus::Valid
} else {
OspfChecksumStatus::Invalid
}
}
None => OspfChecksumStatus::NotChecked,
}
} else {
OspfChecksumStatus::NotChecked
};
let ospfv3 = Ospfv3 {
version: Field::user(version),
packet_type: Field::user(packet_type),
packet_length: Field::user(packet_length),
router_id: Field::user(router_id),
area_id: Field::user(area_id),
checksum: Field::user(checksum),
instance_id: Field::user(instance_id),
reserved: Field::user(reserved),
body,
checksum_status,
};
packet = packet.push(ospfv3);
Ok(packet)
}
fn ipv6_pseudo_header(packet: &Packet) -> Option<crate::packet::TransportChecksumContext> {
(0..packet.len()).rev().find_map(|index| {
packet
.get(index)
.and_then(|layer| layer.transport_checksum_context(IPPROTO_OSPF))
})
}
fn decode_ospfv3_hello(body: &[u8]) -> Result<Ospfv3Hello> {
if body.len() < OSPFV3_HELLO_FIXED_LEN {
return Err(CrafterError::buffer_too_short(
"ospfv3 hello",
OSPFV3_HELLO_FIXED_LEN,
body.len(),
));
}
let interface_id = u32::from_be_bytes([body[0], body[1], body[2], body[3]]);
let router_priority = body[4];
let options = (u32::from(body[5]) << 16) | (u32::from(body[6]) << 8) | u32::from(body[7]);
let hello_interval = u16::from_be_bytes([body[8], body[9]]);
let router_dead_interval = u16::from_be_bytes([body[10], body[11]]);
let designated_router = Ipv4Addr::new(body[12], body[13], body[14], body[15]);
let backup_designated_router = Ipv4Addr::new(body[16], body[17], body[18], body[19]);
let neighbor_region = &body[OSPFV3_HELLO_FIXED_LEN..];
if neighbor_region.len() % 4 != 0 {
return Err(CrafterError::invalid_field_value(
"ospfv3.hello.neighbors",
"neighbor list length must be a multiple of 4",
));
}
let neighbors: Vec<Ipv4Addr> = neighbor_region
.chunks_exact(4)
.map(|chunk| Ipv4Addr::new(chunk[0], chunk[1], chunk[2], chunk[3]))
.collect();
Ok(Ospfv3Hello::new()
.interface_id(interface_id)
.router_priority(router_priority)
.options(options)
.hello_interval(hello_interval)
.router_dead_interval(router_dead_interval)
.designated_router(designated_router)
.backup_designated_router(backup_designated_router)
.neighbors(neighbors))
}
fn decode_ospfv3_database_description(body: &[u8]) -> Result<Ospfv3DatabaseDescription> {
if body.len() < OSPFV3_DD_FIXED_LEN {
return Err(CrafterError::buffer_too_short(
"ospfv3 database description",
OSPFV3_DD_FIXED_LEN,
body.len(),
));
}
let reserved1 = body[0];
let options = (u32::from(body[1]) << 16) | (u32::from(body[2]) << 8) | u32::from(body[3]);
let interface_mtu = u16::from_be_bytes([body[4], body[5]]);
let reserved2 = body[6];
let flags = body[7];
let dd_sequence_number = u32::from_be_bytes([body[8], body[9], body[10], body[11]]);
let lsa_headers = decode_ospfv3_lsa_headers(&body[OSPFV3_DD_FIXED_LEN..])?;
Ok(Ospfv3DatabaseDescription::new()
.reserved1(reserved1)
.options(options)
.interface_mtu(interface_mtu)
.reserved2(reserved2)
.flags(flags)
.dd_sequence_number(dd_sequence_number)
.lsa_headers(lsa_headers))
}
fn decode_ospfv3_link_state_request(body: &[u8]) -> Result<Ospfv3LinkStateRequest> {
if body.len() % OSPFV3_LSR_ENTRY_LEN != 0 {
return Err(CrafterError::invalid_field_value(
"ospfv3.link_state_request.entries",
"request list length must be a multiple of 12",
));
}
let entries: Vec<Ospfv3LinkStateRequestEntry> = body
.chunks_exact(OSPFV3_LSR_ENTRY_LEN)
.map(|chunk| {
let ls_type = u16::from_be_bytes([chunk[2], chunk[3]]);
let link_state_id = Ipv4Addr::new(chunk[4], chunk[5], chunk[6], chunk[7]);
let advertising_router = Ipv4Addr::new(chunk[8], chunk[9], chunk[10], chunk[11]);
Ospfv3LinkStateRequestEntry::new(ls_type, link_state_id, advertising_router)
})
.collect();
Ok(Ospfv3LinkStateRequest::new().requests(entries))
}
fn decode_ospfv3_link_state_ack(body: &[u8]) -> Result<Ospfv3LinkStateAck> {
let lsa_headers = decode_ospfv3_lsa_headers(body)?;
Ok(Ospfv3LinkStateAck::new().lsa_headers(lsa_headers))
}
fn decode_ospfv3_link_state_update(body: &[u8]) -> Result<Ospfv3LinkStateUpdate> {
if body.len() < OSPFV3_LSU_COUNT_LEN {
return Err(CrafterError::buffer_too_short(
"ospfv3 link state update",
OSPFV3_LSU_COUNT_LEN,
body.len(),
));
}
let num_lsas = u32::from_be_bytes([body[0], body[1], body[2], body[3]]);
let mut rest = &body[OSPFV3_LSU_COUNT_LEN..];
let mut lsas: Vec<Ospfv3Lsa> = Vec::with_capacity(num_lsas as usize);
let mut parsed: u64 = 0;
while !rest.is_empty() && parsed < u64::from(num_lsas) {
let (header, length) = Ospfv3LsaHeader::decode(rest)?;
if length < OSPFV3_LSA_HEADER_LEN {
return Err(CrafterError::invalid_field_value(
"ospfv3.lsa.length",
"LSA length is below the 20-octet header minimum",
));
}
if length > rest.len() {
return Err(CrafterError::buffer_too_short(
"ospfv3 lsa",
length,
rest.len(),
));
}
let lsa_body = &rest[OSPFV3_LSA_HEADER_LEN..length];
let decoded_body = decode_ospfv3_lsa_body(header.ls_type_value(), lsa_body)?;
lsas.push(Ospfv3Lsa::new(header, decoded_body));
rest = &rest[length..];
parsed += 1;
}
Ok(Ospfv3LinkStateUpdate::new().num_lsas(num_lsas).lsas(lsas))
}
fn decode_ospfv3_lsa_body(ls_type: u16, body: &[u8]) -> Result<Ospfv3LsaBody> {
match ls_type {
OSPFV3_LSA_ROUTER => Ok(Ospfv3LsaBody::Router(decode_ospfv3_router_lsa_body(body)?)),
OSPFV3_LSA_NETWORK => Ok(Ospfv3LsaBody::Network(decode_ospfv3_network_lsa_body(
body,
)?)),
_ => Ok(Ospfv3LsaBody::Raw(body.to_vec())),
}
}
fn decode_ospfv3_router_lsa_body(body: &[u8]) -> Result<Ospfv3RouterLsa> {
if body.len() < OSPFV3_ROUTER_LSA_FIXED_LEN {
return Err(CrafterError::buffer_too_short(
"ospfv3 router-lsa",
OSPFV3_ROUTER_LSA_FIXED_LEN,
body.len(),
));
}
let flags = body[0];
let options = (u32::from(body[1]) << 16) | (u32::from(body[2]) << 8) | u32::from(body[3]);
let interface_region = &body[OSPFV3_ROUTER_LSA_FIXED_LEN..];
if interface_region.len() % OSPFV3_ROUTER_LSA_INTERFACE_LEN != 0 {
return Err(CrafterError::invalid_field_value(
"ospfv3.router_lsa.interfaces",
"interface description list length must be a multiple of 16",
));
}
let interfaces: Vec<Ospfv3RouterInterface> = interface_region
.chunks_exact(OSPFV3_ROUTER_LSA_INTERFACE_LEN)
.map(|chunk| {
let if_type = chunk[0];
let reserved = chunk[1];
let metric = u16::from_be_bytes([chunk[2], chunk[3]]);
let interface_id = u32::from_be_bytes([chunk[4], chunk[5], chunk[6], chunk[7]]);
let neighbor_interface_id =
u32::from_be_bytes([chunk[8], chunk[9], chunk[10], chunk[11]]);
let neighbor_router_id = Ipv4Addr::new(chunk[12], chunk[13], chunk[14], chunk[15]);
Ospfv3RouterInterface::new(
if_type,
metric,
interface_id,
neighbor_interface_id,
neighbor_router_id,
)
.reserved(reserved)
})
.collect();
Ok(Ospfv3RouterLsa::new()
.flags(flags)
.options(options)
.interfaces(interfaces))
}
fn decode_ospfv3_network_lsa_body(body: &[u8]) -> Result<Ospfv3NetworkLsa> {
if body.len() < OSPFV3_NETWORK_LSA_FIXED_LEN {
return Err(CrafterError::buffer_too_short(
"ospfv3 network-lsa",
OSPFV3_NETWORK_LSA_FIXED_LEN,
body.len(),
));
}
let reserved = body[0];
let options = (u32::from(body[1]) << 16) | (u32::from(body[2]) << 8) | u32::from(body[3]);
let router_region = &body[OSPFV3_NETWORK_LSA_FIXED_LEN..];
if router_region.len() % OSPFV3_NETWORK_LSA_ROUTER_LEN != 0 {
return Err(CrafterError::invalid_field_value(
"ospfv3.network_lsa.attached_routers",
"attached router list length must be a multiple of 4",
));
}
let attached_routers: Vec<Ipv4Addr> = router_region
.chunks_exact(OSPFV3_NETWORK_LSA_ROUTER_LEN)
.map(|chunk| Ipv4Addr::new(chunk[0], chunk[1], chunk[2], chunk[3]))
.collect();
Ok(Ospfv3NetworkLsa::new()
.reserved(reserved)
.options(options)
.attached_routers(attached_routers))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packet::{NetworkLayer, Packet};
use crate::protocols::ip::v6::Ipv6;
use crate::protocols::ospf::{
Ospfv3, OSPFV3_HEADER_LEN, OSPFV3_TYPE_DATABASE_DESCRIPTION, OSPFV3_TYPE_HELLO,
OSPFV3_TYPE_LINK_STATE_ACK, OSPFV3_TYPE_LINK_STATE_REQUEST, OSPFV3_TYPE_LINK_STATE_UPDATE,
OSPF_VERSION_3,
};
use core::net::Ipv6Addr;
fn round_trip(ospfv3: Ospfv3, type_code: u8, check_body: impl Fn(&Ospfv3Body)) {
let src: Ipv6Addr = "2001:db8::1".parse().unwrap();
let dst: Ipv6Addr = "2001:db8::2".parse().unwrap();
let bytes = (Ipv6::new().src(src).dst(dst) / ospfv3)
.compile()
.expect("Ipv6 / Ospfv3 compiles");
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes.as_bytes())
.expect("the default registry decodes the OSPFv3 packet over IPv6");
let layer = decoded
.layer::<Ospfv3>()
.expect("the decoded packet exposes a typed Ospfv3 layer");
assert_eq!(layer.packet_type_value(), type_code);
check_body(&layer.body);
let recompiled = decoded
.compile()
.expect("the decoded OSPFv3 packet re-compiles");
assert_eq!(recompiled.as_bytes(), bytes.as_bytes());
}
#[test]
fn ospfv3_decode_hello_with_two_neighbors_round_trips() {
let neighbors = [Ipv4Addr::new(192, 0, 2, 3), Ipv4Addr::new(192, 0, 2, 4)];
let ospfv3 = Ospfv3::hello()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.with_hello(|h| {
*h = h
.clone()
.interface_id(0x0000_0005)
.router_priority(1)
.options(0x0000_0013)
.designated_router(Ipv4Addr::new(192, 0, 2, 1))
.backup_designated_router(Ipv4Addr::new(192, 0, 2, 2))
.neighbors(neighbors);
});
round_trip(ospfv3, OSPFV3_TYPE_HELLO, |body| {
let hello = match body {
Ospfv3Body::Hello(hello) => hello,
other => panic!("expected a typed Hello body, got {other:?}"),
};
assert_eq!(hello.neighbors_value(), neighbors.as_slice());
assert_eq!(hello.designated_router_value(), Ipv4Addr::new(192, 0, 2, 1));
});
}
#[test]
fn ospfv3_decode_database_description_with_two_headers_round_trips() {
let ospfv3 = Ospfv3::database_description()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.with_database_description(|d| {
*d = d
.clone()
.interface_mtu(1500)
.options(0x0000_0013)
.dd_sequence_number(0x0000_1a2b)
.more(true)
.master(true)
.lsa_header(
Ospfv3LsaHeader::new()
.ls_type(0x2001)
.link_state_id(Ipv4Addr::new(192, 0, 2, 1))
.advertising_router(Ipv4Addr::new(192, 0, 2, 1))
.ls_sequence_number(0x8000_0001),
)
.lsa_header(
Ospfv3LsaHeader::new()
.ls_type(0x2002)
.link_state_id(Ipv4Addr::new(192, 0, 2, 2))
.advertising_router(Ipv4Addr::new(198, 51, 100, 7))
.ls_sequence_number(0x8000_0002),
);
});
round_trip(ospfv3, OSPFV3_TYPE_DATABASE_DESCRIPTION, |body| {
let dd = match body {
Ospfv3Body::DatabaseDescription(dd) => dd,
other => panic!("expected a typed Database Description body, got {other:?}"),
};
assert_eq!(dd.lsa_headers_value().len(), 2);
assert!(dd.is_more());
assert!(dd.is_master());
assert!(!dd.is_init());
assert_eq!(dd.interface_mtu_value(), 1500);
});
}
#[test]
fn ospfv3_decode_link_state_request_with_two_entries_round_trips() {
let ospfv3 = Ospfv3::link_state_request()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.with_link_state_request(|r| {
*r = r
.clone()
.request(Ospfv3LinkStateRequestEntry::new(
0x2001,
Ipv4Addr::new(192, 0, 2, 1),
Ipv4Addr::new(192, 0, 2, 1),
))
.request(Ospfv3LinkStateRequestEntry::new(
0x2002,
Ipv4Addr::new(192, 0, 2, 2),
Ipv4Addr::new(198, 51, 100, 7),
));
});
round_trip(ospfv3, OSPFV3_TYPE_LINK_STATE_REQUEST, |body| {
let lsr = match body {
Ospfv3Body::LinkStateRequest(lsr) => lsr,
other => panic!("expected a typed Link State Request body, got {other:?}"),
};
assert_eq!(lsr.entries_value().len(), 2);
assert_eq!(lsr.entries_value()[0].ls_type_value(), 0x2001);
assert_eq!(
lsr.entries_value()[1].advertising_router_value(),
Ipv4Addr::new(198, 51, 100, 7)
);
});
}
#[test]
fn ospfv3_decode_link_state_ack_with_two_headers_round_trips() {
let ospfv3 = Ospfv3::link_state_ack()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.with_link_state_ack(|a| {
*a = a
.clone()
.lsa_header(
Ospfv3LsaHeader::new()
.ls_type(0x2001)
.link_state_id(Ipv4Addr::new(192, 0, 2, 1))
.advertising_router(Ipv4Addr::new(192, 0, 2, 1))
.ls_sequence_number(0x8000_0001),
)
.lsa_header(
Ospfv3LsaHeader::new()
.ls_type(0x2002)
.link_state_id(Ipv4Addr::new(192, 0, 2, 2))
.advertising_router(Ipv4Addr::new(198, 51, 100, 7))
.ls_sequence_number(0x8000_0002),
);
});
round_trip(ospfv3, OSPFV3_TYPE_LINK_STATE_ACK, |body| {
let ack = match body {
Ospfv3Body::LinkStateAck(ack) => ack,
other => panic!("expected a typed Link State Acknowledgment body, got {other:?}"),
};
assert_eq!(ack.lsa_headers_value().len(), 2);
assert_eq!(ack.lsa_headers_value()[0].ls_type_value(), 0x2001);
});
}
#[test]
fn ospfv3_decode_link_state_update_with_router_lsa_round_trips() {
let router_lsa = Ospfv3Lsa::new(
Ospfv3LsaHeader::new()
.ls_type(0x2001)
.link_state_id(Ipv4Addr::new(0, 0, 0, 0))
.advertising_router(Ipv4Addr::new(192, 0, 2, 1))
.ls_sequence_number(0x8000_0001),
Ospfv3LsaBody::Router(Ospfv3RouterLsa::new().options(0x0000_0013).interface(
Ospfv3RouterInterface::new(
1,
10,
0x0000_0005,
0x0000_0006,
Ipv4Addr::new(192, 0, 2, 2),
),
)),
);
let ospfv3 = Ospfv3::link_state_update()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0])
.with_link_state_update(|u| *u = u.clone().lsa(router_lsa));
round_trip(ospfv3, OSPFV3_TYPE_LINK_STATE_UPDATE, |body| {
let lsu = match body {
Ospfv3Body::LinkStateUpdate(lsu) => lsu,
other => panic!("expected a typed Link State Update body, got {other:?}"),
};
assert_eq!(lsu.lsas_value().len(), 1);
let lsa = &lsu.lsas_value()[0];
assert_eq!(lsa.header.ls_type_value(), 0x2001);
let router = match &lsa.body {
Ospfv3LsaBody::Router(router) => router,
other => panic!("expected a typed Router-LSA body, got {other:?}"),
};
assert_eq!(router.interfaces_value().len(), 1);
assert_eq!(
router.interfaces_value()[0].neighbor_router_id_value(),
Ipv4Addr::new(192, 0, 2, 2)
);
});
}
#[test]
fn ospfv3_decode_short_header_is_a_structured_error() {
let err = append_ospfv3_packet(Packet::new(), &[0u8; 8])
.expect_err("a short OSPFv3 header is rejected");
match err {
CrafterError::BufferTooShort {
context,
required,
available,
} => {
assert_eq!(context, "ospfv3 header");
assert_eq!(required, OSPFV3_HEADER_LEN);
assert_eq!(available, 8);
}
other => panic!("expected BufferTooShort, got {other:?}"),
}
}
#[test]
fn ospfv3_decode_records_checksum_status() {
use crate::registry::ProtocolRegistry;
let src: Ipv6Addr = "2001:db8::1".parse().unwrap();
let dst: Ipv6Addr = "2001:db8::2".parse().unwrap();
let bytes = (Ipv6::new().src(src).dst(dst)
/ Ospfv3::hello()
.router_id([192, 0, 2, 1])
.area_id([0, 0, 0, 0]))
.compile()
.expect("Ipv6 / Ospfv3 Hello compiles");
let decoded = Packet::decode_from_l3(NetworkLayer::Ipv6, bytes.as_bytes())
.expect("the default registry decodes the OSPFv3 Hello over IPv6");
let ospfv3 = decoded
.layer::<Ospfv3>()
.expect("the decoded packet exposes a typed Ospfv3 layer");
assert_eq!(ospfv3.version_value(), OSPF_VERSION_3);
assert_eq!(ospfv3.checksum_status(), OspfChecksumStatus::Valid);
assert_eq!(
decoded.compile().expect("re-compiles").as_bytes(),
bytes.as_bytes()
);
let registry = ProtocolRegistry::with_builtin_bindings().checksum_validation(false);
let decoded_no_check = registry
.decode_from_l3(NetworkLayer::Ipv6, bytes.as_bytes())
.expect("the no-check registry decodes the OSPFv3 Hello over IPv6");
let ospfv3_no_check = decoded_no_check
.layer::<Ospfv3>()
.expect("the decoded packet exposes a typed Ospfv3 layer");
assert_eq!(
ospfv3_no_check.checksum_status(),
OspfChecksumStatus::NotChecked
);
assert_eq!(
decoded_no_check.compile().expect("re-compiles").as_bytes(),
bytes.as_bytes()
);
}
}