diff --git a/src/openzl/common/stream.c b/src/openzl/common/stream.c index 455f75bf2..59dc55571 100644 --- a/src/openzl/common/stream.c +++ b/src/openzl/common/stream.c @@ -15,12 +15,7 @@ #include "openzl/zl_data.h" // ZL_DataID #include "openzl/zl_errors.h" // ZL_Report -typedef struct { - int mId; - int mValue; -} IntMeta; - -DECLARE_VECTOR_TYPE(IntMeta) +DECLARE_VECTOR_TYPE(Stream_IntMetadata) struct Stream_s { // exposed publicly as ZL_Data ZL_Refcount buffer; @@ -33,8 +28,8 @@ struct Stream_s { // exposed publicly as ZL_Data size_t bufferUsed; // in bytes ZL_Refcount stringLens; // ZL_Type_string only. int writeCommitted; - size_t lastCommmited; // tracks the eltCount of most recent commit - VECTOR(IntMeta) intMetas; // Metadata (arbitrary ID+Ints) + size_t lastCommmited; // tracks the eltCount of most recent commit + VECTOR(Stream_IntMetadata) intMetas; // Metadata (arbitrary ID+Ints) Arena* alloc; }; @@ -417,7 +412,7 @@ ZL_Report STREAM_refStreamWithoutRefCount(Stream* s, const Stream* ref) return ZL_REPORT_ERROR(allocation, "Failed to reserve metadata"); } for (size_t pos = 0; pos < meta_size; pos++) { - IntMeta e = VECTOR_AT(ref->intMetas, pos); + Stream_IntMetadata e = VECTOR_AT(ref->intMetas, pos); if (!VECTOR_PUSHBACK(s->intMetas, e)) { return ZL_REPORT_ERROR(allocation, "Failed to copy metadata"); } @@ -1011,7 +1006,7 @@ static ZL_Report STREAM_copyIntMetas(Stream* dst, const Stream* src) VECTOR_RESERVE(dst->intMetas, meta_size), meta_size, allocation); for (size_t pos = 0; pos < meta_size; pos++) { - IntMeta e = VECTOR_AT(src->intMetas, pos); + Stream_IntMetadata e = VECTOR_AT(src->intMetas, pos); ZL_ERR_IF_NOT(VECTOR_PUSHBACK(dst->intMetas, e), allocation); } @@ -1070,12 +1065,12 @@ ZL_Report STREAM_consume(Stream* data, size_t eltCount) // findIntMeta() : // @return index of the Int Metadata of provided @id // @return -1 if not found -static int findIntMeta(VECTOR(IntMeta) m, int id) +static int findIntMeta(VECTOR(Stream_IntMetadata) m, int id) { size_t const nbIntMetas = VECTOR_SIZE(m); // Scan backward, find latest .id if multiple present for (int pos = (int)nbIntMetas - 1; pos >= 0; pos--) { - if (VECTOR_DATA(m)[pos].mId == id) + if (VECTOR_DATA(m)[pos].id == id) return pos; } // not found @@ -1093,7 +1088,9 @@ ZL_Report STREAM_setIntMetadata(Stream* s, int mId, int mValue) streamParameter_invalid, "Int Metadata ID already present"); ZL_ERR_IF_NOT( - VECTOR_PUSHBACK(s->intMetas, ((IntMeta){ mId, mValue })), + VECTOR_PUSHBACK( + s->intMetas, + ((Stream_IntMetadata){ .id = mId, .value = mValue })), allocation); return ZL_returnSuccess(); } @@ -1110,10 +1107,41 @@ ZL_IntMetadata STREAM_getIntMetadata(const Stream* s, int mId) }; return (ZL_IntMetadata){ .isPresent = 1, - .mValue = VECTOR_DATA(s->intMetas)[idx].mValue, + .mValue = VECTOR_DATA(s->intMetas)[idx].value, }; } +size_t STREAM_numIntMetadata(const Stream* s) +{ + ZL_ASSERT_NN(s); + return VECTOR_SIZE(s->intMetas); +} + +ZL_Report STREAM_copyIntMetadata( + Stream_IntMetadata* dst, + const Stream* src, + size_t expectedEntries) +{ + ZL_RESULT_DECLARE_SCOPE_REPORT(NULL); + ZL_ASSERT_NN(src); + ZL_ERR_IF_NE( + expectedEntries, + VECTOR_SIZE(src->intMetas), + streamParameter_invalid, + "Metadata entry count does not match the stream"); + if (expectedEntries == 0) { + return ZL_returnSuccess(); + } + ZL_ERR_IF_NULL( + dst, + streamParameter_invalid, + "Metadata destination is NULL for a non-empty stream"); + for (size_t i = 0; i < expectedEntries; ++i) { + dst[i] = VECTOR_AT(src->intMetas, i); + } + return ZL_returnSuccess(); +} + int STREAM_hasBuffer(const Stream* s) { return !ZL_Refcount_null(&s->buffer); diff --git a/src/openzl/common/stream.h b/src/openzl/common/stream.h index d0caa2051..d4c984bd8 100644 --- a/src/openzl/common/stream.h +++ b/src/openzl/common/stream.h @@ -22,6 +22,20 @@ ZL_BEGIN_C_DECLS DECLARE_VECTOR_POINTERS_TYPE(ZL_Data) DECLARE_VECTOR_CONST_POINTERS_TYPE(ZL_Data) +/** + * One integer metadata entry attached to a Stream. + * + * This type is exposed by the internal Stream interface so callers that need + * to snapshot a stream can preserve both the metadata identifier and value. + * The public lookup API returns only the value for a caller-supplied ID. + */ +typedef struct { + /** Identifier passed to STREAM_setIntMetadata(). */ + int id; + /** Integer value associated with this entry's identifier. */ + int value; +} Stream_IntMetadata; + /** * Internal Stream interface. * @@ -238,6 +252,26 @@ size_t STREAM_byteCapacity(const Stream* s); ZL_Report STREAM_setIntMetadata(Stream* s, int mId, int mValue); ZL_IntMetadata STREAM_getIntMetadata(const Stream* s, int mId); +/** Number of integer metadata entries attached to @p s. */ +size_t STREAM_numIntMetadata(const Stream* s); + +/** + * Copy every integer metadata entry from @p src into @p dst. + * + * @p dst must have capacity for at least @p expectedEntries entries. + * @p expectedEntries must exactly equal STREAM_numIntMetadata(src). @p dst + * may be NULL only when @p expectedEntries is zero. Invalid arguments return + * an error without modifying @p dst. + * + * Metadata lookup semantics do not depend on entry order. This function + * nevertheless preserves stable stream order, so callers that hash or compare + * the copied representation bytewise will distinguish different orders. + */ +ZL_Report STREAM_copyIntMetadata( + Stream_IntMetadata* dst, + const Stream* src, + size_t expectedEntries); + /** * Hash the content of all streams provided in @p streams. * Only meaningful when all streams have been committed. diff --git a/tests/unittest/common/test_stream.cpp b/tests/unittest/common/test_stream.cpp index 195192244..137563d33 100644 --- a/tests/unittest/common/test_stream.cpp +++ b/tests/unittest/common/test_stream.cpp @@ -29,7 +29,30 @@ TEST(Stream, intMetadata) // test what happens when requesting a non-present metadata id ASSERT_EQ(ZL_Data_getIntMetadata(s, 3).isPresent, 0); + ASSERT_EQ(STREAM_numIntMetadata(s), 2u); + Stream_IntMetadata metadata[2]; + ASSERT_ZS_VALID(STREAM_copyIntMetadata(metadata, s, 2)); + EXPECT_EQ(metadata[0].id, 1); + EXPECT_EQ(metadata[0].value, 1001); + EXPECT_EQ(metadata[1].id, 2); + EXPECT_EQ(metadata[1].value, 2002); + + const Stream_IntMetadata sentinel = { 7, 7007 }; + Stream_IntMetadata rejected = sentinel; + EXPECT_TRUE(ZL_isError(STREAM_copyIntMetadata(&rejected, s, 1))); + EXPECT_EQ(rejected.id, sentinel.id); + EXPECT_EQ(rejected.value, sentinel.value); + EXPECT_TRUE(ZL_isError(STREAM_copyIntMetadata(&rejected, s, 3))); + EXPECT_EQ(rejected.id, sentinel.id); + EXPECT_EQ(rejected.value, sentinel.value); + EXPECT_TRUE(ZL_isError(STREAM_copyIntMetadata(nullptr, s, 2))); + STREAM_free(s); + + ZL_Data* const empty = STREAM_create(kZeroID); + ASSERT_NE(empty, nullptr); + EXPECT_ZS_VALID(STREAM_copyIntMetadata(nullptr, empty, 0)); + STREAM_free(empty); } // check byteSize function