From 4e5a54ce49848252c7bef038286f7d5939974387 Mon Sep 17 00:00:00 2001 From: Lee Rhodes Date: Thu, 29 Oct 2026 15:33:30 -0700 Subject: [PATCH] Include the header in the tdigest serialize buffer size tdigest::serialize(header_size_bytes, with_buffer) sized the vector by get_serialized_size_bytes() alone but wrote the sketch starting at header_size_bytes, so a non-zero header wrote past the end of the vector. The size now includes the header, as in the other sketches. Co-Authored-By: Claude Opus 5.5 --- tdigest/include/tdigest_impl.hpp | 2 +- tdigest/test/tdigest_test.cpp | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/tdigest/include/tdigest_impl.hpp b/tdigest/include/tdigest_impl.hpp index 958c8c40..ab9da04c 100644 --- a/tdigest/include/tdigest_impl.hpp +++ b/tdigest/include/tdigest_impl.hpp @@ -378,7 +378,7 @@ size_t tdigest::get_serialized_size_bytes(bool with_buffer) const { template auto tdigest::serialize(unsigned header_size_bytes, bool with_buffer) const -> vector_bytes { if (!with_buffer) const_cast(this)->compress(); // side effect - vector_bytes bytes(get_serialized_size_bytes(with_buffer), 0, buffer_.get_allocator()); + vector_bytes bytes(header_size_bytes + get_serialized_size_bytes(with_buffer), 0, buffer_.get_allocator()); uint8_t* ptr = bytes.data() + header_size_bytes; *ptr++ = get_preamble_longs(); *ptr++ = SERIAL_VERSION; diff --git a/tdigest/test/tdigest_test.cpp b/tdigest/test/tdigest_test.cpp index 30b1fc1a..8c8535ad 100644 --- a/tdigest/test/tdigest_test.cpp +++ b/tdigest/test/tdigest_test.cpp @@ -18,6 +18,7 @@ */ #include +#include #include #include #include @@ -347,6 +348,27 @@ TEST_CASE("serialize deserialize bytes many values with buffer", "[tdigest]") { REQUIRE(td.get_quantile(0.5) == deserialized_td.get_quantile(0.5)); } +TEST_CASE("serialize bytes with header", "[tdigest]") { + const unsigned header_size_bytes = 7; + auto check = [header_size_bytes](const tdigest& td, bool with_buffer) { + auto bytes = td.serialize(header_size_bytes, with_buffer); + auto bytes_no_header = td.serialize(0, with_buffer); + REQUIRE(bytes.size() == header_size_bytes + bytes_no_header.size()); + REQUIRE(bytes.size() == header_size_bytes + td.get_serialized_size_bytes(with_buffer)); + REQUIRE(std::equal(bytes_no_header.begin(), bytes_no_header.end(), bytes.begin() + header_size_bytes)); + auto deserialized_td = tdigest::deserialize(bytes.data() + header_size_bytes, bytes.size() - header_size_bytes); + REQUIRE(td.get_total_weight() == deserialized_td.get_total_weight()); + }; + tdigest td(100); + check(td, false); + td.update(123); + check(td, false); + check(td, true); + for (int i = 0; i < 10000; ++i) td.update(i); + check(td, true); + check(td, false); +} + TEST_CASE("serialize deserialize steam and bytes equivalence empty", "[tdigest]") { tdigest td(100); std::stringstream s(std::ios::in | std::ios::out | std::ios::binary);