Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cesr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ name = "stream"
harness = false
required-features = ["stream"]

[[bench]]
name = "serder"
harness = false
required-features = ["serder"]

[[example]]
name = "concurrent_parse"
required-features = ["stream"]
Expand Down
108 changes: 108 additions & 0 deletions cesr/benches/serder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//! Event-serialization benchmarks for the #79 backend seam.
//!
//! Measures the same production entry point (`serialize_with`) through both
//! backends on identical events, so `CodSpeed` tracks the direct backend's
//! win over the `serde_json` reference — and any regression in either.
//!
//! Fixtures are deterministic (fixed raw bytes) for stable `CodSpeed` input.

// The lints below fire only inside `codspeed-criterion-compat`'s
// `criterion_group!`/`criterion_main!` macro expansion — third-party macro code
// we cannot annotate per-item. Benches are host-only tooling, not shipped.
#![allow(
missing_docs,
clippy::disallowed_methods,
clippy::significant_drop_tightening,
reason = "fire only inside codspeed-criterion-compat macro expansion; not our code"
)]

use cesr::core::matter::builder::MatterBuilder;
use cesr::core::matter::code::{DigestCode, VerKeyCode};
use cesr::core::primitives::{Prefixer, Saider, Seqner, Tholder};
use cesr::keri::{ConfigTrait, Identifier, InceptionEvent, InteractionEvent, Seal};
use cesr::serder::{DirectJson, EventRef, SerdeJson, serialize_with};
use core::hint::black_box;
use criterion::{Criterion, criterion_group, criterion_main};

fn prefixer(byte: u8) -> Prefixer<'static> {
let built = MatterBuilder::new()
.with_code(VerKeyCode::Ed25519)
.with_raw(vec![byte; 32]);
if let Ok(b) = built
&& let Ok(m) = b.build()
{
return m;
}
unreachable!("fixed 32-byte raw always builds an Ed25519 prefixer")
}

fn saider(byte: u8) -> Saider<'static> {
let built = MatterBuilder::new()
.with_code(DigestCode::Blake3_256)
.with_raw(vec![byte; 32]);
if let Ok(b) = built
&& let Ok(m) = b.build()
{
return m;
}
unreachable!("fixed 32-byte raw always builds a Blake3 saider")
}

/// A representative inception: two keys, two next-key digests, one witness,
/// one config trait, two anchors.
fn fixture_icp() -> InceptionEvent {
InceptionEvent::new(
Identifier::Basic(prefixer(0)),
Seqner::new(0),
saider(1),
vec![prefixer(2), prefixer(3)],
Tholder::Simple(2),
vec![saider(4), saider(5)],
Tholder::Simple(2),
vec![prefixer(6)],
1,
vec![ConfigTrait::EstOnly],
vec![
Seal::Digest { d: saider(7) },
Seal::Source {
s: Seqner::new(3),
d: saider(8),
},
],
)
}

/// An anchor-heavy interaction: 16 digest seals (the value-array hot loop).
fn fixture_ixn() -> InteractionEvent {
let anchors = (0..16_u8).map(|i| Seal::Digest { d: saider(i) }).collect();
InteractionEvent::new(
Identifier::Basic(prefixer(0)),
Seqner::new(1),
saider(1),
saider(2),
anchors,
)
}

fn bench_serialize(c: &mut Criterion) {
let icp = fixture_icp();
let ixn = fixture_ixn();

let mut group = c.benchmark_group("serder_serialize");
group.bench_function("icp_serde_json", |b| {
b.iter(|| serialize_with(&SerdeJson, black_box(EventRef::Inception(&icp))));
});
group.bench_function("icp_direct", |b| {
b.iter(|| serialize_with(&DirectJson, black_box(EventRef::Inception(&icp))));
});
group.bench_function("ixn16_serde_json", |b| {
b.iter(|| serialize_with(&SerdeJson, black_box(EventRef::Interaction(&ixn))));
});
group.bench_function("ixn16_direct", |b| {
b.iter(|| serialize_with(&DirectJson, black_box(EventRef::Interaction(&ixn))));
});
group.finish();
}

criterion_group!(benches, bench_serialize);
criterion_main!(benches);
23 changes: 23 additions & 0 deletions cesr/src/serder/deserialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,14 @@ fn parse_weight(s: &str) -> Result<(u64, u64), SerderError> {
"invalid fraction denominator: {s}"
)),
})?;
if den == 0 {
return Err(SerderError::InvalidPrimitive {
field: "kt",
source: ValidationError::UnknownMatterCode(format!(
"zero denominator in weight: {s}"
)),
});
}
Ok((num, den))
} else {
let val: u64 = s.parse().map_err(|_| SerderError::InvalidPrimitive {
Expand Down Expand Up @@ -1307,6 +1315,21 @@ mod tests {
assert_eq!((n, d), (1, 1));
}

#[test]
fn parse_weight_rejects_zero_denominator() {
// Bug probe: "0/0" and "1/0" previously parsed into (0,0)/(1,0), and
// a (0,0) weight made re-serialization divide by zero (panic on
// untrusted-derived data).
assert!(matches!(
parse_weight("0/0"),
Err(SerderError::InvalidPrimitive { field: "kt", .. })
));
assert!(matches!(
parse_weight("1/0"),
Err(SerderError::InvalidPrimitive { field: "kt", .. })
));
}

// -----------------------------------------------------------------------
// Version-string validation at every public entry point
// -----------------------------------------------------------------------
Expand Down
6 changes: 6 additions & 0 deletions cesr/src/serder/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ pub enum SerderError {
max: u32,
},

/// A serialization backend reported a slot layout inconsistent with the
/// bytes it rendered — a bug in the backend, surfaced as a typed error so
/// a corrupt frame can never escape.
#[error("invalid event layout: {0}")]
InvalidEventLayout(&'static str),

/// Digest computation failed.
#[error("digest error: {0}")]
DigestError(String),
Expand Down
5 changes: 3 additions & 2 deletions cesr/src/serder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ pub use deserialize::{
};
pub use error::SerderError;
pub use serialize::{
SerializedEvent, serialize, serialize_delegated_inception, serialize_delegated_rotation,
serialize_inception, serialize_interaction, serialize_rotation,
DirectJson, EventLayout, EventRef, EventSerializer, SerdeJson, SerializedEvent, serialize,
serialize_delegated_inception, serialize_delegated_rotation, serialize_inception,
serialize_interaction, serialize_rotation, serialize_with,
};
pub use traits::{KeriDeserialize, KeriSerialize};
Loading
Loading