diff --git a/Cargo.toml b/Cargo.toml index 648a5b8..e891ed9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ criterion = { version = "0.5.0", features = ["html_reports"] } dhat = "0.3.3" hyperloglog = "1.0.2" hyperloglogplus = "0.4.1" +postcard = { version = "1.1.1", features=["alloc"] } pprof = { version = "0.14.0", features = ["flamegraph", "criterion", "protobuf-codec"] } probabilistic-collections = "0.7.0" rand = "0.8.5" diff --git a/Makefile b/Makefile index fcd96fb..fdd65a2 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,15 @@ fuzz-estimator: fuzz-serde: RUSTFLAGS="-Z sanitizer=address" cargo +nightly fuzz run serde -- -max_len=65536 +fuzz-serde-json-array: + RUSTFLAGS="-Z sanitizer=address" cargo +nightly fuzz run serde_json_array -- -max_len=65536 + +fuzz-serde-postcard: + RUSTFLAGS="-Z sanitizer=address" cargo +nightly fuzz run serde_postcard -- -max_len=65536 + +fuzz-serde-roundtrip: + RUSTFLAGS="-Z sanitizer=address" cargo +nightly fuzz run serde_roundtrip -- -max_len=65536 + lint: cargo clippy --features with_serde -- -D warnings diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index ea1167a..fc0944d 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -10,6 +10,7 @@ cargo-fuzz = true [dependencies] cardinality-estimator = { path = "..", features = ["with_serde"] } libfuzzer-sys = "0.4" +postcard = { version = "1.1.1", features = ["alloc"] } serde_json = "1.0.115" wyhash = "0.5.0" @@ -26,3 +27,24 @@ path = "fuzz_targets/serde.rs" test = false doc = false bench = false + +[[bin]] +name = "serde_json_array" +path = "fuzz_targets/serde_json_array.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "serde_postcard" +path = "fuzz_targets/serde_postcard.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "serde_roundtrip" +path = "fuzz_targets/serde_roundtrip.rs" +test = false +doc = false +bench = false diff --git a/fuzz/fuzz_targets/serde_json_array.rs b/fuzz/fuzz_targets/serde_json_array.rs new file mode 100644 index 0000000..1e2ab93 --- /dev/null +++ b/fuzz/fuzz_targets/serde_json_array.rs @@ -0,0 +1,19 @@ +#![no_main] + +use serde_json::Value; +use cardinality_estimator::estimator::CardinalityEstimator; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + // pretty naiive version, u8s directly into each number position + let json: serde_json::Value = match data.len() { + 0 => Value::Array(vec![]), + 1 => Value::Array(vec![data[0].into()]), + _ => Value::Array(vec![data[0].into(), + Value::Array(data[1..].iter().map(|n| (*n).into()).collect())]), + }; + if let Ok(mut estimator) = serde_json::from_value::>(json) { + estimator.insert(&1); + assert!(estimator.estimate() > 0); + } +}); diff --git a/fuzz/fuzz_targets/serde_postcard.rs b/fuzz/fuzz_targets/serde_postcard.rs new file mode 100644 index 0000000..4313618 --- /dev/null +++ b/fuzz/fuzz_targets/serde_postcard.rs @@ -0,0 +1,11 @@ +#![no_main] + +use cardinality_estimator::estimator::CardinalityEstimator; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + if let Ok(mut estimator) = postcard::from_bytes::>(data) { + estimator.insert(&1); + assert!(estimator.estimate() > 0); + } +}); diff --git a/fuzz/fuzz_targets/serde_roundtrip.rs b/fuzz/fuzz_targets/serde_roundtrip.rs new file mode 100644 index 0000000..ec621b9 --- /dev/null +++ b/fuzz/fuzz_targets/serde_roundtrip.rs @@ -0,0 +1,15 @@ +#![no_main] + +use cardinality_estimator::estimator::CardinalityEstimator; +use libfuzzer_sys::fuzz_target; +use postcard::{to_allocvec, from_bytes}; + +fuzz_target!(|data: &[u8]| { + let mut estimator = CardinalityEstimator::::new(); + for d in data { + estimator.insert(&d); + } + let serialized = to_allocvec(&estimator).unwrap(); + let mut roundtripped: CardinalityEstimator = from_bytes(&serialized).unwrap(); + roundtripped.insert(&1); +}); diff --git a/src/serde.rs b/src/serde.rs index 3b4e4bc..f6c757f 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -110,6 +110,23 @@ pub mod tests { original_estimator.representation(), deserialized_estimator.representation() ); + + // run each case with postcard serialization as well + + let postcard_serialized = + postcard::to_allocvec(&original_estimator).expect("serialization failed"); + assert!( + !postcard_serialized.is_empty(), + "postcard_serialized bytes should not be empty" + ); + + let postcard_estimator: CardinalityEstimator = + postcard::from_bytes(&postcard_serialized).expect("deserialization failed"); + + assert_eq!( + original_estimator.representation(), + postcard_estimator.representation() + ); } #[test] @@ -130,5 +147,8 @@ pub mod tests { fn test_failed_deserialization(input: &[u8]) { let result: Result, _> = serde_json::from_slice(input); assert!(result.is_err()); + + let result: Result, _> = postcard::from_bytes(input); + assert!(result.is_err()); } }