Files
ferrix/crates/packet/src/wire.rs
T
Rayhaan JaufeerallyandClaude Fable 5.1 25597ab3ef packet, fib, bench: framing helpers, one netlink reader, a mesh with named phases
A code-quality pass over the periphery. The packet crate frames a
message with `begin_message` and `finish_message`, which the
exporter's hand-written UPDATE header can use instead of a bare type
code, and keeps the NLRI reader and writer together as a pair; the
rule that the body's prefixes are IPv4 unicast lives once; a session
with two-octet AS numbers no longer reserves the attributes' wire
copy it never keeps. The TCP diagnostics read netlink with the
netlink module's own attribute walk and helpers instead of copies of
them. The kernel follower learns what the kernel has in one named
step used at start and on relearning, keeps its batch buffers between
wakes, and builds its task in a constructor. The mesh benchmark,
which had grown six phases in one main, is a `Mesh` with named phases
and a `Receiver`, with one report of the sessions for both deadlines;
the collector capabilities, the KEEPALIVE bytes, the draining of a
session and the printing of a table row are helpers shared by the
benches, the examples and the integration tests. The MRT reader uses
the packet crate's attribute splitter.

Testing: a message framed by hand is the one `to_wire` writes; a
short tcp_info from an old kernel leaves the later fields zero; the
synthetic table is the same every run and every announcement decodes
to itself. The packing example gives the same figures on the RIS dump
before and after. The crates' tests, the integration tests, the
kernel tests and every lab phase pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-10 22:44:32 +02:00

199 lines
6.3 KiB
Rust

//! What the readers and writers of the wire format share: the shape of a
//! parser's result and its errors, the fields that several messages and
//! attributes have in common, and the lengths that are written before what
//! they count.
use bytes::{BufMut, BytesMut};
use nom::Err::Failure;
use nom::IResult;
use nom::number::complete::{be_u8, be_u16, be_u32};
use bgp_primitives::constants::{Afi, AfiSafi, Safi};
use bgp_primitives::parser::{BgpParserError, EncodeError, ParserContext};
use crate::message::AS_TRANS;
/// A parser's result: what is left of the input, and what was read.
pub(crate) type Parsed<'a, T> = IResult<&'a [u8], T, BgpParserError<&'a [u8]>>;
/// The error for input that does not follow the format; `detail` says how.
pub(crate) fn malformed<'a>(detail: impl Into<String>) -> nom::Err<BgpParserError<&'a [u8]>> {
Failure(BgpParserError::Malformed(detail.into()))
}
/// Parses `buf` to its end as a list of `item`s. Unlike nom's `many0`,
/// which stops quietly at an item it cannot read, an error in an item is an
/// error of the list.
pub(crate) fn list<'a, T>(
mut buf: &'a [u8],
item: impl Fn(&'a [u8]) -> Parsed<'a, T>,
) -> Parsed<'a, Vec<T>> {
let mut items = Vec::new();
while !buf.is_empty() {
let (rest, parsed) = item(buf)?;
items.push(parsed);
buf = rest;
}
Ok((buf, items))
}
/// A parsed value, provided the parser used all of the input. `what` names
/// the input in the error.
pub(crate) fn whole<T>(
what: impl std::fmt::Display,
(rest, parsed): (&[u8], T),
) -> Result<T, nom::Err<BgpParserError<&[u8]>>> {
if rest.is_empty() {
Ok(parsed)
} else {
Err(malformed(format!(
"{} octets left over in {what}",
rest.len()
)))
}
}
/// `value` as a list of one or more records of `N` octets each: how the
/// community attributes and CLUSTER_LIST are laid out. An empty list is
/// malformed (RFC 7606 §7.8 for COMMUNITIES, and the others alike). `what`
/// names the attribute in the error.
pub(crate) fn records<'a, const N: usize>(
value: &'a [u8],
what: &str,
) -> Result<&'a [[u8; N]], nom::Err<BgpParserError<&'a [u8]>>> {
let (records, rest) = value.as_chunks::<N>();
if records.is_empty() || !rest.is_empty() {
return Err(malformed(format!(
"{what} is {} octets long, not a non-empty multiple of {N}",
value.len()
)));
}
Ok(records)
}
/// Parses an AS number as wide as the session's: four octets or two
/// (RFC 6793).
pub(crate) fn parse_asn(four_octet: bool, buf: &[u8]) -> Parsed<'_, u32> {
if four_octet {
be_u32(buf)
} else {
be_u16(buf).map(|(buf, asn)| (buf, u32::from(asn)))
}
}
/// Writes an AS number as wide as the session's. On a two-octet session
/// one that does not fit is written as AS_TRANS (RFC 6793 §4.2.2).
pub(crate) fn put_asn(four_octet: bool, asn: u32, out: &mut BytesMut) {
if four_octet {
out.put_u32(asn);
} else {
out.put_u16(u16::try_from(asn).unwrap_or(AS_TRANS));
}
}
/// Whether the prefixes in the body of an UPDATE carry path identifiers
/// on a session with `ctx`. The body holds IPv4 unicast routes and nothing
/// else (RFC 4271 §4.3), so that is the family whose ADD-PATH agreement
/// decides it; every other family's prefixes are in MP_REACH_NLRI and
/// MP_UNREACH_NLRI, which name their family.
pub(crate) fn body_has_path_id(ctx: &ParserContext) -> bool {
ctx.has_add_path(AfiSafi::IPV4_UNICAST)
}
/// Parses an address family as the multiprotocol attributes and most
/// capabilities name it: a two-octet AFI and a one-octet SAFI.
pub(crate) fn parse_afi_safi(buf: &[u8]) -> Parsed<'_, AfiSafi> {
let (buf, afi) = be_u16(buf)?;
let (buf, safi) = be_u8(buf)?;
Ok((buf, AfiSafi::new(Afi(afi), Safi(safi))))
}
/// Writes an address family as [`parse_afi_safi`] reads it.
pub(crate) fn put_afi_safi(family: AfiSafi, out: &mut BytesMut) {
out.put_u16(family.afi.0);
out.put_u8(family.safi.0);
}
/// Writes `length` into the field of `width` octets reserved at `at`, if
/// it is no more than `max`. `what` names the field in the error.
pub(crate) fn put_length(
out: &mut BytesMut,
at: usize,
width: usize,
length: usize,
max: usize,
what: &'static str,
) -> Result<(), EncodeError> {
if length > max {
return Err(EncodeError::TooLong {
what,
len: length,
max,
});
}
let bytes = length.to_be_bytes();
out[at..at + width].copy_from_slice(&bytes[bytes.len() - width..]);
Ok(())
}
/// Fills in the field of `width` octets reserved at `at` with how much
/// has been written after it, if that is no more than `max`: for a length
/// that is known only once what it counts has been written. `what` names
/// the field in the error.
pub(crate) fn fill_length(
out: &mut BytesMut,
at: usize,
width: usize,
max: usize,
what: &'static str,
) -> Result<(), EncodeError> {
let length = out.len() - at - width;
put_length(out, at, width, length, max, what)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_length_is_filled_in_after_what_it_counts() {
let mut out = BytesMut::new();
out.put_u8(0xaa);
let at = out.len();
out.put_u16(0);
out.put_slice(&[1, 2, 3]);
fill_length(&mut out, at, 2, u16::MAX.into(), "field").unwrap();
assert_eq!(&out[..], [0xaa, 0, 3, 1, 2, 3]);
let mut out = BytesMut::new();
out.put_u8(0);
out.put_slice(&[0; 300]);
assert_eq!(
fill_length(&mut out, 0, 1, u8::MAX.into(), "field"),
Err(EncodeError::TooLong {
what: "field",
len: 300,
max: 255,
})
);
}
#[test]
fn records_are_a_whole_number_of_them_and_at_least_one() {
assert_eq!(
records::<4>(&[1, 2, 3, 4, 5, 6, 7, 8], "X").unwrap().len(),
2
);
for value in [&[][..], &[1, 2, 3][..], &[1, 2, 3, 4, 5][..]] {
let error = records::<4>(value, "X").unwrap_err();
assert_eq!(
crate::message::describe(&error),
format!(
"X is {} octets long, not a non-empty multiple of 4",
value.len()
)
);
}
}
}