From be9987f7438206c285287efb0aac357460011b98 Mon Sep 17 00:00:00 2001 From: Ties de Kock Date: Sun, 8 Feb 2026 21:30:14 +0100 Subject: [PATCH 1/3] add into_raw_record_iter benchmark --- benches/internals.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/benches/internals.rs b/benches/internals.rs index 8d27e47a..bc143825 100644 --- a/benches/internals.rs +++ b/benches/internals.rs @@ -91,6 +91,19 @@ pub fn criterion_benchmark(c: &mut Criterion) { }) }); + c.bench_function("updates into_raw_record_iter", |b| { + b.iter(|| { + let mut reader = black_box(&updates[..]); + + BgpkitParser::from_reader(&mut reader) + .into_raw_record_iter() + .take(RECORD_LIMIT) + .for_each(|x| { + black_box(x); + }); + }) + }); + c.bench_function("rib into_record_iter", |b| { b.iter(|| { let mut reader = black_box(&rib_dump[..]); @@ -129,6 +142,19 @@ pub fn criterion_benchmark(c: &mut Criterion) { }); }) }); + + c.bench_function("rib into_raw_record_iter", |b| { + b.iter(|| { + let mut reader = black_box(&rib_dump[..]); + + BgpkitParser::from_reader(&mut reader) + .into_raw_record_iter() + .take(RECORD_LIMIT) + .for_each(|x| { + black_box(x); + }); + }) + }); } criterion_group! { From 6ee084cbb6de6ba1e2719b8a652bfd7fe73ccbcb Mon Sep 17 00:00:00 2001 From: Ties de Kock Date: Sun, 8 Feb 2026 22:18:58 +0100 Subject: [PATCH 2/3] use zerocopy for header parsing --- Cargo.toml | 2 + src/parser/mrt/mrt_header.rs | 111 +++++++++++++++++++++++++---------- 2 files changed, 82 insertions(+), 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6d64ff73..4046fdeb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ serde = { version = "1.0", features = ["derive"], optional = true } # Parser dependencies # ####################### bytes = { version = "1.7", optional = true } +zerocopy = { version = "0.8", features = ["derive"], optional = true } hex = { version = "0.4.3", optional = true } # bmp/openbmp parsing oneio = { version = "0.20.0", default-features = false, features = ["http", "gz", "bz"], optional = true } regex = { version = "1", optional = true } # used in parser filter @@ -60,6 +61,7 @@ parser = [ "bytes", "chrono", "regex", + "zerocopy", ] cli = [ "clap", diff --git a/src/parser/mrt/mrt_header.rs b/src/parser/mrt/mrt_header.rs index d6277d91..36a28b49 100644 --- a/src/parser/mrt/mrt_header.rs +++ b/src/parser/mrt/mrt_header.rs @@ -1,7 +1,71 @@ use crate::models::{CommonHeader, EntryType}; use crate::ParserError; -use bytes::{Buf, BufMut, Bytes, BytesMut}; +use bytes::Bytes; use std::io::Read; +use zerocopy::big_endian::{U16, U32}; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +/// On-wire MRT common header layout (12 bytes, network byte order). +#[derive(IntoBytes, FromBytes, KnownLayout, Immutable)] +#[repr(C)] +struct RawMrtCommonHeader { + timestamp: U32, + entry_type: U16, + entry_subtype: U16, + length: U32, +} + +const _: () = assert!(size_of::() == 12); + +/// On-wire MRT header with microseconds included (16 bytes, network byte order) +#[derive(IntoBytes, FromBytes, KnownLayout, Immutable)] +#[repr(C)] +struct RawMrtEtCommonHeader { + timestamp: U32, + entry_type: U16, + entry_subtype: U16, + length: U32, + microseconds: U32, +} + +const _: () = assert!(size_of::() == 16); + +enum RawMrtHeader { + Standard(RawMrtCommonHeader), + Et(RawMrtEtCommonHeader), +} + +impl From<&CommonHeader> for RawMrtHeader { + fn from(header: &CommonHeader) -> Self { + match header.microsecond_timestamp { + None => RawMrtHeader::Standard(RawMrtCommonHeader { + timestamp: U32::new(header.timestamp), + entry_type: U16::new(header.entry_type as u16), + entry_subtype: U16::new(header.entry_subtype), + length: U32::new(header.length), + }), + Some(microseconds) => RawMrtHeader::Et(RawMrtEtCommonHeader { + timestamp: U32::new(header.timestamp), + entry_type: U16::new(header.entry_type as u16), + entry_subtype: U16::new(header.entry_subtype), + // Internally, we use the length of the MRT payload. + // However in the header, the length inclused the space used by the extra timestamp + // data. + length: U32::new(header.length + 4), + microseconds: U32::new(microseconds), + }), + } + } +} + +impl RawMrtHeader { + fn as_bytes(&self) -> &[u8] { + match self { + RawMrtHeader::Standard(raw) => raw.as_bytes(), + RawMrtHeader::Et(raw) => raw.as_bytes(), + } + } +} /// Result of parsing a common header, including the raw bytes. pub struct ParsedHeader { @@ -56,14 +120,16 @@ pub fn parse_common_header(input: &mut T) -> Result(input: &mut T) -> Result { let mut base_bytes = [0u8; 12]; input.read_exact(&mut base_bytes)?; - let mut data = &base_bytes[..]; - let timestamp = data.get_u32(); - let entry_type_raw = data.get_u16(); - let entry_type = EntryType::try_from(entry_type_raw)?; - let entry_subtype = data.get_u16(); + // Single bounds check via zerocopy instead of four sequential cursor reads. + let raw = RawMrtCommonHeader::ref_from_bytes(&base_bytes) + .expect("base_bytes is exactly 12 bytes with no alignment requirement"); + + let timestamp = raw.timestamp.get(); + let entry_type = EntryType::try_from(raw.entry_type.get())?; + let entry_subtype = raw.entry_subtype.get(); // the length field does not include the length of the common header - let mut length = data.get_u32(); + let mut length = raw.length.get(); let (microsecond_timestamp, raw_bytes) = match &entry_type { EntryType::BGP4MP_ET => { @@ -76,15 +142,11 @@ pub fn parse_common_header_with_bytes(input: &mut T) -> Result (None, Bytes::copy_from_slice(&base_bytes)), }; @@ -103,21 +165,8 @@ pub fn parse_common_header_with_bytes(input: &mut T) -> Result Bytes { - let mut bytes = BytesMut::new(); - bytes.put_slice(&self.timestamp.to_be_bytes()); - bytes.put_u16(self.entry_type as u16); - bytes.put_u16(self.entry_subtype); - - match self.microsecond_timestamp { - None => bytes.put_u32(self.length), - Some(microseconds) => { - // When the microsecond timestamp is present, the length must be adjusted to account - // for the stace used by the extra timestamp data. - bytes.put_u32(self.length + 4); - bytes.put_u32(microseconds); - } - }; - bytes.freeze() + let raw = RawMrtHeader::from(self); + Bytes::copy_from_slice(raw.as_bytes()) } } From 96a948fcb55ce1d84e9bf9d80d8dd865673c7706 Mon Sep 17 00:00:00 2001 From: Mingwei Zhang Date: Mon, 9 Feb 2026 13:40:13 -0800 Subject: [PATCH 3/3] minor spelling fix Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/parser/mrt/mrt_header.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser/mrt/mrt_header.rs b/src/parser/mrt/mrt_header.rs index 36a28b49..70bbf898 100644 --- a/src/parser/mrt/mrt_header.rs +++ b/src/parser/mrt/mrt_header.rs @@ -49,7 +49,7 @@ impl From<&CommonHeader> for RawMrtHeader { entry_type: U16::new(header.entry_type as u16), entry_subtype: U16::new(header.entry_subtype), // Internally, we use the length of the MRT payload. - // However in the header, the length inclused the space used by the extra timestamp + // However in the header, the length includes the space used by the extra timestamp // data. length: U32::new(header.length + 4), microseconds: U32::new(microseconds),