From 41981801d5369434c1589e4d916b5e2d52e749cb Mon Sep 17 00:00:00 2001 From: Benjamin Brock Date: Wed, 29 Jul 2026 02:41:13 +0000 Subject: [PATCH 1/2] Read variable-length string attributes bsp_read_attribute_allocator sized its buffer with H5Tget_size(), which for a variable-length string type returns the size of a pointer rather than the length of the string, and H5Aread writes a pointer into that buffer. Branch on H5Tis_variable_str() so both layouts are read correctly. The fixed-size path is unchanged, so files written by earlier versions of Binsparse continue to be read. This lands ahead of the corresponding writer change so that no revision writes attributes it cannot read back. Co-Authored-By: Claude Opus 5 (1M context) --- include/binsparse/hdf5_wrapper.h | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/include/binsparse/hdf5_wrapper.h b/include/binsparse/hdf5_wrapper.h index f8b44df..7efaa59 100644 --- a/include/binsparse/hdf5_wrapper.h +++ b/include/binsparse/hdf5_wrapper.h @@ -288,6 +288,33 @@ bsp_read_attribute_allocator(char** string, hid_t f, const char* label, hid_t strtype = H5Aget_type(attribute); + // Older files store this attribute as a fixed-size string, handled below. + // A variable-length one has to be read through a pointer HDF5 allocates, + // since H5Tget_size would just give the size of that pointer. + if (H5Tis_variable_str(strtype) > 0) { + char* buffer = NULL; + + if (H5Aread(attribute, strtype, &buffer) < 0 || buffer == NULL) { + H5Aclose(attribute); + H5Tclose(strtype); + return BSP_ERROR_IO; + } + + size_t size = strlen(buffer); + *string = (char*) allocator.malloc(size + 1); + memcpy(*string, buffer, size + 1); + + // Return HDF5's copy to HDF5; it did not come from `allocator`. + hid_t space = H5Aget_space(attribute); + H5Dvlen_reclaim(strtype, space, H5P_DEFAULT, &buffer); + H5Sclose(space); + + H5Aclose(attribute); + H5Tclose(strtype); + + return BSP_SUCCESS; + } + size_t size = H5Tget_size(strtype); *string = (char*) allocator.malloc(size + 1); From 5050975907dc7f0dbbd53d63f36777fc7f82018c Mon Sep 17 00:00:00 2001 From: Benjamin Brock Date: Wed, 29 Jul 2026 02:41:35 +0000 Subject: [PATCH 2/2] Write string attributes as variable-length The binsparse JSON attribute was written with a fixed-size string type. h5py surfaces that as a numpy.bytes_ of dtype |S rather than as a str, so callers have to know to decode it. Write it as a variable-length UTF-8 string instead, which is what h5py itself produces and what Binsparse already uses for string datasets. The resulting datatype compares equal under H5Tequal to one written by h5py, and h5py now returns a str. Co-Authored-By: Claude Opus 5 (1M context) --- include/binsparse/hdf5_wrapper.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/include/binsparse/hdf5_wrapper.h b/include/binsparse/hdf5_wrapper.h index 7efaa59..4a2ce5d 100644 --- a/include/binsparse/hdf5_wrapper.h +++ b/include/binsparse/hdf5_wrapper.h @@ -260,15 +260,19 @@ static inline bsp_error_t bsp_read_array(bsp_array_t* array, hid_t f, static inline bsp_error_t bsp_write_attribute(hid_t f, const char* label, const char* string) { + // Variable-length, like the string datasets Binsparse writes. High-level + // bindings expose a fixed-size string attribute as raw bytes rather than as + // a string. hid_t strtype = H5Tcopy(H5T_C_S1); - H5Tset_size(strtype, strlen(string)); + H5Tset_size(strtype, H5T_VARIABLE); H5Tset_cset(strtype, H5T_CSET_UTF8); hid_t dataspace = H5Screate(H5S_SCALAR); hid_t attribute = H5Acreate2(f, label, strtype, dataspace, H5P_DEFAULT, H5P_DEFAULT); - H5Awrite(attribute, strtype, string); + // A variable-length write takes the address of the pointer, not the pointer. + H5Awrite(attribute, strtype, &string); H5Tclose(strtype); H5Aclose(attribute);