diff --git a/tuple/include/tuple_sketch_impl.hpp b/tuple/include/tuple_sketch_impl.hpp index 3a9d2b75..d20853c6 100644 --- a/tuple/include/tuple_sketch_impl.hpp +++ b/tuple/include/tuple_sketch_impl.hpp @@ -590,6 +590,11 @@ compact_tuple_sketch compact_tuple_sketch::deserialize(const void* b (*summary).~S(); } } + const size_t bytes_consumed = ptr - base; + if (bytes_consumed != size) { + throw std::out_of_range("Unexpected buffer size: bytes consumed " + + std::to_string(bytes_consumed) + ", bytes available " + std::to_string(size)); + } const bool is_ordered = flags_byte & (1 << flags::IS_ORDERED); return compact_tuple_sketch(is_empty, is_ordered, seed_hash, theta, std::move(entries)); } diff --git a/tuple/test/tuple_sketch_test.cpp b/tuple/test/tuple_sketch_test.cpp index dd0c3f1d..2c7f8d8a 100644 --- a/tuple/test/tuple_sketch_test.cpp +++ b/tuple/test/tuple_sketch_test.cpp @@ -430,4 +430,16 @@ TEST_CASE("tuple sketch: deserialize bounds-checks each entry key", "[tuple_sket std::out_of_range); } +TEST_CASE("tuple sketch: deserialize rejects unconsumed bytes", "[tuple_sketch]") { + // A compact sketch serialized with a wider summary (double, 8 bytes) and then + // deserialized as a narrower summary (float, 4 bytes). The reader consumes less + // data than the entries occupy, so deserialization must fail if bytes remain + // after the last entry. + auto update_sketch = update_tuple_sketch::builder().build(); + update_sketch.update(1, 1.0); + auto bytes = update_sketch.compact().serialize(); + REQUIRE_THROWS_AS(compact_tuple_sketch::deserialize(bytes.data(), bytes.size()), + std::out_of_range); +} + } /* namespace datasketches */