You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datasketches.apache.org by ti...@apache.org on 2026/07/28 07:06:55 UTC
(datasketches-rust) 06/08: test(serde): reject truncated public inputs
This is an automated email from the ASF dual-hosted git repository.
tisonkun pushed a commit to branch codex/analyze-crate-tests
in repository https://gitbox.apache.org/repos/asf/datasketches-rust.git
commit 95197af2fa42c2c5c69d38376a62115046ba6ea6
Author: tison <wa...@gmail.com>
AuthorDate: Tue Jul 28 14:46:04 2026 +0800
test(serde): reject truncated public inputs
---
datasketches/src/theta/sketch.rs | 49 ---------------------------
datasketches/src/tuple/sketch.rs | 39 ---------------------
datasketches/tests/serde_tests.rs | 25 ++++++++++++++
datasketches/tests/serde_tests/bloom.rs | 10 ++++++
datasketches/tests/serde_tests/countmin.rs | 9 +++++
datasketches/tests/serde_tests/cpc.rs | 11 ++++++
datasketches/tests/serde_tests/frequencies.rs | 9 +++++
datasketches/tests/serde_tests/hll.rs | 11 ++++++
datasketches/tests/serde_tests/tdigest.rs | 11 ++++++
datasketches/tests/serde_tests/theta.rs | 27 +++++++++++++++
datasketches/tests/serde_tests/tuple.rs | 25 ++++++++++++++
11 files changed, 138 insertions(+), 88 deletions(-)
diff --git a/datasketches/src/theta/sketch.rs b/datasketches/src/theta/sketch.rs
index 9ced2b4..cc78b9c 100644
--- a/datasketches/src/theta/sketch.rs
+++ b/datasketches/src/theta/sketch.rs
@@ -1135,53 +1135,4 @@ mod tests {
assert_compressed_round_trip(&theta, &compact);
}
-
- #[test]
- fn deserialize_rejects_seed_hash_mismatch() {
- let mut theta = ThetaSketchBuilder::default().seed(7).build();
- theta.update("apple");
- let bytes = theta.compact(true).serialize();
-
- let err = CompactThetaSketch::deserialize_with_seed(&bytes, 8).unwrap_err();
- assert_eq!(err.kind(), crate::error::ErrorKind::InvalidData);
- assert!(err.message().contains("incompatible seed hash"));
- }
-
- #[test]
- fn deserialize_rejects_invalid_family_id() {
- let mut theta = ThetaSketchBuilder::default().build();
- theta.update("apple");
- let mut bytes = theta.compact(true).serialize();
- bytes[2] = 0;
-
- let err = CompactThetaSketch::deserialize(&bytes).unwrap_err();
- assert_eq!(err.kind(), crate::error::ErrorKind::InvalidData);
- assert!(err.message().contains("invalid family"));
- }
-
- #[test]
- fn deserialize_rejects_unsupported_serial_version() {
- let mut theta = ThetaSketchBuilder::default().build();
- theta.update("apple");
- let mut bytes = theta.compact(true).serialize();
- bytes[1] = 99;
-
- let err = CompactThetaSketch::deserialize(&bytes).unwrap_err();
- assert_eq!(err.kind(), crate::error::ErrorKind::InvalidData);
- assert!(err.message().contains("unsupported serial version"));
- }
-
- #[test]
- fn deserialize_rejects_truncated_payload() {
- let mut theta = ThetaSketchBuilder::default().lg_k(5).build();
- for i in 0..5000 {
- theta.update(i);
- }
- let mut bytes = theta.compact(true).serialize();
- bytes.pop();
-
- let err = CompactThetaSketch::deserialize(&bytes).unwrap_err();
- assert_eq!(err.kind(), crate::error::ErrorKind::InvalidData);
- assert!(err.message().contains("insufficient data"));
- }
}
diff --git a/datasketches/src/tuple/sketch.rs b/datasketches/src/tuple/sketch.rs
index b43adeb..bb0832d 100644
--- a/datasketches/src/tuple/sketch.rs
+++ b/datasketches/src/tuple/sketch.rs
@@ -713,7 +713,6 @@ where
#[cfg(test)]
mod tests {
use super::*;
- use crate::error::ErrorKind;
use crate::tuple::policy::DefaultUpdatePolicy;
use crate::tuple::policy::SummaryPolicy;
use crate::tuple::policy::SummaryUpdatePolicy;
@@ -1026,42 +1025,4 @@ mod tests {
assert_eq!(restored.num_retained(), 50);
assert!(restored.iter().all(|(_, &s)| s == 2));
}
-
- #[test]
- fn deserialize_rejects_wrong_family() {
- let policy = DefaultUpdatePolicy::<u64>::default();
- let mut sketch = TupleSketchBuilder::new(policy).build();
- for i in 0..10 {
- sketch.update(i, 1u64);
- }
- let mut bytes = sketch.compact(true).serialize();
- bytes[2] = 3; // pretend it is a THETA sketch
- let err = CompactTupleSketch::<u64>::deserialize(&bytes).unwrap_err();
- assert_eq!(err.kind(), ErrorKind::InvalidData);
- }
-
- #[test]
- fn deserialize_rejects_seed_mismatch() {
- let policy = DefaultUpdatePolicy::<u64>::default();
- let mut sketch = TupleSketchBuilder::new(policy).build();
- for i in 0..10 {
- sketch.update(i, 1u64);
- }
- let bytes = sketch.compact(true).serialize();
- let err = CompactTupleSketch::<u64>::deserialize_with_seed(&bytes, 999).unwrap_err();
- assert_eq!(err.kind(), ErrorKind::InvalidData);
- }
-
- #[test]
- fn deserialize_rejects_truncated_summary() {
- let policy = DefaultUpdatePolicy::<u64>::default();
- let mut sketch = TupleSketchBuilder::new(policy).build();
- for i in 0..100 {
- sketch.update(i, 1u64);
- }
- let bytes = sketch.compact(true).serialize();
- let truncated = &bytes[..bytes.len() - 4]; // cut the last summary in half
- let err = CompactTupleSketch::<u64>::deserialize(truncated).unwrap_err();
- assert_eq!(err.kind(), ErrorKind::InvalidData);
- }
}
diff --git a/datasketches/tests/serde_tests.rs b/datasketches/tests/serde_tests.rs
index 92cdc42..bb8f866 100644
--- a/datasketches/tests/serde_tests.rs
+++ b/datasketches/tests/serde_tests.rs
@@ -17,6 +17,9 @@
use std::path::PathBuf;
+use datasketches::error::Error;
+use datasketches::error::ErrorKind;
+
pub fn serialization_test_data(sub_dir: &str, name: &str) -> PathBuf {
const SERDE_TESTS_DIR: &str = "tests/serde_tests";
@@ -42,6 +45,28 @@ pub fn serialization_test_data(sub_dir: &str, name: &str) -> PathBuf {
path
}
+pub fn assert_truncated_inputs_rejected<T>(
+ bytes: &[u8],
+ deserialize: impl Fn(&[u8]) -> Result<T, Error>,
+) {
+ assert!(!bytes.is_empty(), "valid serialization must not be empty");
+
+ for len in 0..bytes.len() {
+ match deserialize(&bytes[..len]) {
+ Ok(_) => panic!(
+ "deserializer accepted a truncated input of {len}/{} bytes",
+ bytes.len()
+ ),
+ Err(err) => assert_eq!(
+ err.kind(),
+ ErrorKind::InvalidData,
+ "unexpected error for a truncated input of {len}/{} bytes",
+ bytes.len()
+ ),
+ }
+ }
+}
+
#[cfg(feature = "bloom")]
#[path = "serde_tests/bloom.rs"]
mod bloom;
diff --git a/datasketches/tests/serde_tests/bloom.rs b/datasketches/tests/serde_tests/bloom.rs
index 5875b1f..13b4495 100644
--- a/datasketches/tests/serde_tests/bloom.rs
+++ b/datasketches/tests/serde_tests/bloom.rs
@@ -19,7 +19,9 @@ use std::fs;
use std::path::PathBuf;
use datasketches::bloom::BloomFilter;
+use datasketches::bloom::BloomFilterBuilder;
+use crate::assert_truncated_inputs_rejected;
use crate::serialization_test_data;
fn test_bloom_filter_file(path: PathBuf, expected_num_items: u64, expected_num_hashes: u16) {
@@ -219,3 +221,11 @@ fn test_cpp_bloom_n30000000_h5() {
let path = serialization_test_data("cpp_generated_files", "bf_n30000000_h5_cpp.sk");
test_bloom_filter_file(path, 30000000, 5);
}
+
+#[test]
+fn truncated_input_is_rejected() {
+ let mut filter = BloomFilterBuilder::with_size(64, 3).build();
+ filter.insert("value");
+
+ assert_truncated_inputs_rejected(&filter.serialize(), BloomFilter::deserialize);
+}
diff --git a/datasketches/tests/serde_tests/countmin.rs b/datasketches/tests/serde_tests/countmin.rs
index 49745f1..b0a5233 100644
--- a/datasketches/tests/serde_tests/countmin.rs
+++ b/datasketches/tests/serde_tests/countmin.rs
@@ -21,6 +21,7 @@ use datasketches::countmin::CountMinSketch;
use googletest::assert_that;
use googletest::prelude::contains_substring;
+use crate::assert_truncated_inputs_rejected;
use crate::serialization_test_data;
// This test validates binary format compatibility (deserialize + byte round-trip) for
@@ -66,3 +67,11 @@ fn test_deserialize_cpp_snapshot_with_wrong_seed() {
let err = CountMinSketch::<u64>::deserialize_with_seed(&bytes, 9000).unwrap_err();
assert_that!(err.message(), contains_substring("incompatible seed hash"));
}
+
+#[test]
+fn truncated_input_is_rejected() {
+ let mut sketch = CountMinSketch::<u64>::new(1, 3);
+ sketch.update("value");
+
+ assert_truncated_inputs_rejected(&sketch.serialize(), CountMinSketch::<u64>::deserialize);
+}
diff --git a/datasketches/tests/serde_tests/cpc.rs b/datasketches/tests/serde_tests/cpc.rs
index 7d90250..c30fc31 100644
--- a/datasketches/tests/serde_tests/cpc.rs
+++ b/datasketches/tests/serde_tests/cpc.rs
@@ -22,6 +22,7 @@ use datasketches::cpc::CpcSketch;
use googletest::assert_that;
use googletest::prelude::near;
+use crate::assert_truncated_inputs_rejected;
use crate::serialization_test_data;
fn test_sketch_file(path: PathBuf, expected_cardinality: usize) {
@@ -81,3 +82,13 @@ fn test_cpp_compatibility() {
test_sketch_file(path, n);
}
}
+
+#[test]
+fn truncated_input_is_rejected() {
+ let mut sketch = CpcSketch::new(4);
+ for value in 0..20 {
+ sketch.update(value);
+ }
+
+ assert_truncated_inputs_rejected(&sketch.serialize(), CpcSketch::deserialize);
+}
diff --git a/datasketches/tests/serde_tests/frequencies.rs b/datasketches/tests/serde_tests/frequencies.rs
index 07051ba..78bca03 100644
--- a/datasketches/tests/serde_tests/frequencies.rs
+++ b/datasketches/tests/serde_tests/frequencies.rs
@@ -24,6 +24,7 @@ use datasketches::error::ErrorKind;
use datasketches::frequencies::FrequentItemValue;
use datasketches::frequencies::FrequentItemsSketch;
+use crate::assert_truncated_inputs_rejected;
use crate::serialization_test_data;
#[derive(Debug, PartialEq, Eq, Hash)]
@@ -114,6 +115,14 @@ fn test_purged_to_empty_round_trip() {
assert!(restored.is_empty());
}
+#[test]
+fn truncated_input_is_rejected() {
+ let mut sketch = FrequentItemsSketch::<i64>::new(8);
+ sketch.update_with_count(42, 3);
+
+ assert_truncated_inputs_rejected(&sketch.serialize(), FrequentItemsSketch::<i64>::deserialize);
+}
+
#[test]
fn test_java_frequent_longs_compatibility() {
let test_cases = [0, 1, 10, 100, 1000, 10000, 100000, 1000000];
diff --git a/datasketches/tests/serde_tests/hll.rs b/datasketches/tests/serde_tests/hll.rs
index fb1e4fb..7c6db05 100644
--- a/datasketches/tests/serde_tests/hll.rs
+++ b/datasketches/tests/serde_tests/hll.rs
@@ -22,6 +22,7 @@ use datasketches::hash_value::natural_extend;
use datasketches::hll::HllSketch;
use datasketches::hll::HllType;
+use crate::assert_truncated_inputs_rejected;
use crate::serialization_test_data;
fn test_sketch_file(path: PathBuf, expected_cardinality: usize, expected_lg_k: u8) {
@@ -130,6 +131,16 @@ fn test_update_after_deserialize_list_mode() {
}
}
+#[test]
+fn truncated_input_is_rejected() {
+ let mut sketch = HllSketch::new(4, HllType::Hll8);
+ for value in 0..20 {
+ sketch.update(value);
+ }
+
+ assert_truncated_inputs_rejected(&sketch.serialize(), HllSketch::deserialize);
+}
+
#[test]
fn test_serialized_bytes_match_reference_files_for_coupon_modes() {
fn serialized_mode_name(bytes: &[u8]) -> &'static str {
diff --git a/datasketches/tests/serde_tests/tdigest.rs b/datasketches/tests/serde_tests/tdigest.rs
index bfe4d40..41ad177 100644
--- a/datasketches/tests/serde_tests/tdigest.rs
+++ b/datasketches/tests/serde_tests/tdigest.rs
@@ -23,6 +23,7 @@ use googletest::assert_that;
use googletest::prelude::eq;
use googletest::prelude::near;
+use crate::assert_truncated_inputs_rejected;
use crate::serialization_test_data;
fn test_sketch_file(path: PathBuf, n: u64, with_buffer: bool, is_f32: bool) {
@@ -131,6 +132,16 @@ fn test_deserialize_from_java_snapshots() {
}
}
+#[test]
+fn truncated_input_is_rejected() {
+ let mut digest = TDigestMut::new(10);
+ digest.update(1.0);
+ digest.update(2.0);
+ let bytes = digest.serialize();
+
+ assert_truncated_inputs_rejected(&bytes, |bytes| TDigestMut::deserialize(bytes, false));
+}
+
#[test]
fn test_empty() {
let mut td = TDigestMut::new(100);
diff --git a/datasketches/tests/serde_tests/theta.rs b/datasketches/tests/serde_tests/theta.rs
index a1206aa..c385d65 100644
--- a/datasketches/tests/serde_tests/theta.rs
+++ b/datasketches/tests/serde_tests/theta.rs
@@ -18,10 +18,13 @@
use std::fs;
use std::path::PathBuf;
+use datasketches::error::ErrorKind;
use datasketches::theta::CompactThetaSketch;
+use datasketches::theta::ThetaSketchBuilder;
use googletest::assert_that;
use googletest::prelude::near;
+use crate::assert_truncated_inputs_rejected;
use crate::serialization_test_data;
fn test_sketch_file(path: PathBuf, expected_cardinality: usize, use_compressed_round_trip: bool) {
@@ -113,3 +116,27 @@ fn test_cpp_compatibility() {
let path = serialization_test_data("cpp_generated_files", "theta_non_empty_no_entries_cpp.sk");
test_sketch_file(path, 0, false);
}
+
+#[test]
+fn malformed_input_is_rejected() {
+ let mut sketch = ThetaSketchBuilder::default().lg_k(5).build();
+ for value in 0..100 {
+ sketch.update(value);
+ }
+ let bytes = sketch.compact(true).serialize();
+
+ assert_truncated_inputs_rejected(&bytes, CompactThetaSketch::deserialize);
+
+ let err = CompactThetaSketch::deserialize_with_seed(&bytes, 8).unwrap_err();
+ assert_eq!(err.kind(), ErrorKind::InvalidData);
+
+ let mut wrong_family = bytes.clone();
+ wrong_family[2] = 0;
+ let err = CompactThetaSketch::deserialize(&wrong_family).unwrap_err();
+ assert_eq!(err.kind(), ErrorKind::InvalidData);
+
+ let mut unsupported_version = bytes;
+ unsupported_version[1] = 99;
+ let err = CompactThetaSketch::deserialize(&unsupported_version).unwrap_err();
+ assert_eq!(err.kind(), ErrorKind::InvalidData);
+}
diff --git a/datasketches/tests/serde_tests/tuple.rs b/datasketches/tests/serde_tests/tuple.rs
index 00a3ca2..e076617 100644
--- a/datasketches/tests/serde_tests/tuple.rs
+++ b/datasketches/tests/serde_tests/tuple.rs
@@ -18,10 +18,14 @@
use std::fs;
use std::path::PathBuf;
+use datasketches::error::ErrorKind;
use datasketches::tuple::CompactTupleSketch;
+use datasketches::tuple::DefaultUpdatePolicy;
+use datasketches::tuple::TupleSketchBuilder;
use googletest::assert_that;
use googletest::prelude::near;
+use crate::assert_truncated_inputs_rejected;
use crate::serialization_test_data;
fn test_sketch_file(path: PathBuf, expected_cardinality: usize) {
@@ -90,3 +94,24 @@ fn test_cpp_compatibility() {
test_sketch_file(path, n);
}
}
+
+#[test]
+fn malformed_input_is_rejected() {
+ let mut sketch = TupleSketchBuilder::new(DefaultUpdatePolicy::<u64>::default())
+ .lg_k(5)
+ .build();
+ for value in 0..100 {
+ sketch.update(value, 1);
+ }
+ let bytes = sketch.compact(true).serialize();
+
+ assert_truncated_inputs_rejected(&bytes, CompactTupleSketch::<u64>::deserialize);
+
+ let err = CompactTupleSketch::<u64>::deserialize_with_seed(&bytes, 8).unwrap_err();
+ assert_eq!(err.kind(), ErrorKind::InvalidData);
+
+ let mut wrong_family = bytes;
+ wrong_family[2] = 0;
+ let err = CompactTupleSketch::<u64>::deserialize(&wrong_family).unwrap_err();
+ assert_eq!(err.kind(), ErrorKind::InvalidData);
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datasketches.apache.org
For additional commands, e-mail: commits-help@datasketches.apache.org