From 3a25ba1db33ff5e91ac28afcda382ea1d1ba4e58 Mon Sep 17 00:00:00 2001 From: Brad Hards Date: Mon, 30 Dec 2024 19:46:32 +1100 Subject: [PATCH] Support custom avifImage properties in avifEncoder (#2510) Add avifImageAddOpaqueProperty() and avifImageAddUUIDProperty(). Check for basic validity of the written properties. Add tests. --- CHANGELOG.md | 3 +- CMakeLists.txt | 1 + include/avif/avif.h | 19 ++++- include/avif/internal.h | 5 ++ src/avif.c | 20 +++++ src/properties.c | 69 +++++++++++++++ src/read.c | 12 +++ src/write.c | 14 +++ tests/CMakeLists.txt | 3 + tests/gtest/avif_fuzztest_properties.cc | 109 ++++++++++++++++++++++++ tests/gtest/avifgainmaptest.cc | 74 ++++++++++++++++ tests/gtest/avifpropertytest.cc | 59 +++++++++++++ tests/gtest/avifpropinternaltest.cc | 54 ++++++++++++ 13 files changed, 440 insertions(+), 2 deletions(-) create mode 100644 src/properties.c create mode 100644 tests/gtest/avif_fuzztest_properties.cc create mode 100644 tests/gtest/avifpropinternaltest.cc diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a13fac21d..49dd005533 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ The changes are relative to the previous release, unless the baseline is specifi ### Added since 1.1.1 * Add the properties and numProperties fields to avifImage. They are filled by - the avifDecoder instance with the properties unrecognized by libavif. + the avifDecoder instance with the properties unrecognized by libavif. They are + written by the avifEncoder. ### Changed since 1.1.1 * avifenc: Allow large images to be encoded. diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e72f751d1..2c621ea148 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -375,6 +375,7 @@ set(AVIF_SRCS src/io.c src/mem.c src/obu.c + src/properties.c src/rawdata.c src/read.c src/reformat.c diff --git a/include/avif/avif.h b/include/avif/avif.h index 256abf61bb..427426a881 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h @@ -819,7 +819,8 @@ typedef struct avifImage // Other properties attached to this image item (primary or gainmap). // At decoding: Forwarded here as opaque byte sequences by the avifDecoder. - // At encoding: Ignored. + // At encoding: Set using avifImageAddOpaqueProperty() or avifImageAddUUIDProperty() and written by the avifEncoder + // in the order that they are added to the image. avifImageItemProperty * properties; // NULL only if numProperties is 0. size_t numProperties; @@ -853,6 +854,22 @@ AVIF_API avifResult avifImageAllocatePlanes(avifImage * image, avifPlanesFlags p AVIF_API void avifImageFreePlanes(avifImage * image, avifPlanesFlags planes); // Ignores already-freed planes AVIF_API void avifImageStealPlanes(avifImage * dstImage, avifImage * srcImage, avifPlanesFlags planes); +// Add arbitrary (opaque) properties to the image. +// Note: This is an advanced usage, intended for users with specific requirements who are familiar with the +// HEIF and ISO BMFF standards. Use of these functions for properties and boxes that are handled by +// libavif (e.g. ispe or meta) will likely result in invalid files, and should be avoided. +// If creating an ItemFullProperty, the version and flags values should be provided as the first four bytes of +// the data argument, and those four bytes included in the dataSize. +// Any properties will be added after the usual libavif descriptive properties, and before the libavif +// transformative properties (e.g. irot, imir, clap). Be aware that readers will apply transformative +// properties in the order they occur. +// Users of this API should consider calling avifParse() on the resulting file (i.e. the encoder output) to +// check that the arbitrary properties have not resulted in an invalid file. +AVIF_API avifResult avifImageAddOpaqueProperty(avifImage * image, const uint8_t boxtype[4], const uint8_t * data, size_t dataSize); +// This version adds an ItemProperty (or ItemFullProperty if version and flags are provided in data argument), using +// the user extension (uuid) mechanism, see ISO/IEC 14496-12:2022 Section 4.2. The box type is set to 'uuid'. +AVIF_API avifResult avifImageAddUUIDProperty(avifImage * image, const uint8_t uuid[16], const uint8_t * data, size_t dataSize); + // --------------------------------------------------------------------------- // Understanding maxThreads // diff --git a/include/avif/internal.h b/include/avif/internal.h index 8b6769d397..ccee529955 100644 --- a/include/avif/internal.h +++ b/include/avif/internal.h @@ -156,6 +156,11 @@ AVIF_API avifResult avifImagePushProperty(avifImage * image, const uint8_t * boxPayload, size_t boxPayloadSize); +// Check if the FourCC property value is a known value +AVIF_NODISCARD avifBool avifIsKnownPropertyType(const uint8_t boxtype[4]); +// Check if the extended property (UUID) is valid +AVIF_NODISCARD avifBool avifIsValidUUID(const uint8_t uuid[16]); + // --------------------------------------------------------------------------- #if defined(AVIF_ENABLE_EXPERIMENTAL_SAMPLE_TRANSFORM) diff --git a/src/avif.c b/src/avif.c index b7b6fd99f0..b3339ae9de 100644 --- a/src/avif.c +++ b/src/avif.c @@ -406,6 +406,26 @@ avifResult avifImagePushProperty(avifImage * image, const uint8_t boxtype[4], co return AVIF_RESULT_OK; } +avifResult avifImageAddOpaqueProperty(avifImage * image, const uint8_t boxtype[4], const uint8_t * data, size_t dataSize) +{ + const uint8_t uuid[16] = { 0 }; + // Do not allow adding properties that are also handled by libavif + if (avifIsKnownPropertyType(boxtype)) { + return AVIF_RESULT_INVALID_ARGUMENT; + } + return avifImagePushProperty(image, boxtype, uuid, data, dataSize); +} + +avifResult avifImageAddUUIDProperty(avifImage * image, const uint8_t uuid[16], const uint8_t * data, size_t dataSize) +{ + const uint8_t boxtype[4] = { 'u', 'u', 'i', 'd' }; + // Do not allow adding invalid UUIDs, or using uuid representation of properties that are also handled by libavif + if (!avifIsValidUUID(uuid)) { + return AVIF_RESULT_INVALID_ARGUMENT; + } + return avifImagePushProperty(image, boxtype, uuid, data, dataSize); +} + avifResult avifImageAllocatePlanes(avifImage * image, avifPlanesFlags planes) { if (image->width == 0 || image->height == 0) { diff --git a/src/properties.c b/src/properties.c new file mode 100644 index 0000000000..05ee7b9afe --- /dev/null +++ b/src/properties.c @@ -0,0 +1,69 @@ +// Copyright 2024 Brad Hards. All rights reserved. +// SPDX-License-Identifier: BSD-2-Clause + +#include "avif/internal.h" + +#include + +struct avifKnownProperty +{ + uint8_t fourcc[4]; +}; + +static const struct avifKnownProperty knownProperties[] = { + { { 'f', 't', 'y', 'p' } }, { { 'u', 'u', 'i', 'd' } }, { { 'm', 'e', 't', 'a' } }, { { 'h', 'd', 'l', 'r' } }, + { { 'p', 'i', 't', 'm' } }, { { 'd', 'i', 'n', 'f' } }, { { 'd', 'r', 'e', 'f' } }, { { 'i', 'd', 'a', 't' } }, + { { 'i', 'l', 'o', 'c' } }, { { 'i', 'i', 'n', 'f' } }, { { 'i', 'n', 'f', 'e' } }, { { 'i', 'p', 'r', 'p' } }, + { { 'i', 'p', 'c', 'o' } }, { { 'a', 'v', '1', 'C' } }, { { 'a', 'v', '2', 'C' } }, { { 'i', 's', 'p', 'e' } }, + { { 'p', 'i', 'x', 'i' } }, { { 'p', 'a', 's', 'p' } }, { { 'c', 'o', 'l', 'r' } }, { { 'a', 'u', 'x', 'C' } }, + { { 'c', 'l', 'a', 'p' } }, { { 'i', 'r', 'o', 't' } }, { { 'i', 'm', 'i', 'r' } }, { { 'c', 'l', 'l', 'i' } }, + { { 'c', 'c', 'l', 'v' } }, { { 'm', 'd', 'c', 'v' } }, { { 'a', 'm', 'v', 'e' } }, { { 'r', 'e', 'v', 'e' } }, + { { 'n', 'd', 'w', 't' } }, { { 'a', '1', 'o', 'p' } }, { { 'l', 's', 'e', 'l' } }, { { 'a', '1', 'l', 'x' } }, + { { 'c', 'm', 'i', 'n' } }, { { 'c', 'm', 'e', 'x' } }, { { 'i', 'p', 'm', 'a' } }, { { 'i', 'r', 'e', 'f' } }, + { { 'a', 'u', 'x', 'l' } }, { { 't', 'h', 'm', 'b' } }, { { 'd', 'i', 'm', 'g' } }, { { 'p', 'r', 'e', 'm' } }, + { { 'c', 'd', 's', 'c' } }, { { 'g', 'r', 'p', 'l' } }, { { 'a', 'l', 't', 'r' } }, { { 's', 't', 'e', 'r' } }, + { { 'm', 'd', 'a', 't' } }, +}; + +static const size_t numKnownProperties = sizeof(knownProperties) / sizeof(knownProperties[0]); + +static const size_t FOURCC_BYTES = 4; +static const size_t UUID_BYTES = 16; + +static const uint8_t ISO_UUID_SUFFIX[12] = { 0x00, 0x01, 0x00, 0x10, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9b, 0x71 }; + +avifBool avifIsKnownPropertyType(const uint8_t boxtype[4]) +{ + for (size_t i = 0; i < numKnownProperties; i++) { + if (memcmp(knownProperties[i].fourcc, boxtype, FOURCC_BYTES) == 0) { + return AVIF_TRUE; + } + } + return AVIF_FALSE; +} + +avifBool avifIsValidUUID(const uint8_t uuid[16]) +{ + // This check is to reject encoding a known property via the UUID mechanism + // See ISO/IEC 14496-12 Section 4.2.3 + for (size_t i = 0; i < numKnownProperties; i++) { + if ((memcmp(knownProperties[i].fourcc, uuid, FOURCC_BYTES) == 0) && + (memcmp(ISO_UUID_SUFFIX, uuid + FOURCC_BYTES, UUID_BYTES - FOURCC_BYTES) == 0)) { + return AVIF_FALSE; + } + } + // This check rejects UUIDs with unexpected variant field values, including Nil UUID and Max UUID. + // See RFC 9562 Section 4.1 + uint8_t variant = uuid[8] >> 4; + if ((variant < 0x08) || (variant > 0x0b)) { + return AVIF_FALSE; + } + // This check rejects UUIDs with unexpected version field values. + // See RFC 9562 Section 4.2 + uint8_t version = uuid[6] >> 4; + if ((version < 1) || (version > 8)) { + return AVIF_FALSE; + } + // The rest of a UUID is pretty much a bucket of bits, so assume its OK. + return AVIF_TRUE; +} diff --git a/src/read.c b/src/read.c index 2a8b4ce096..f744df8af7 100644 --- a/src/read.c +++ b/src/read.c @@ -6052,6 +6052,18 @@ avifResult avifDecoderReset(avifDecoder * decoder) } } + if (gainMapProperties) { + for (size_t i = 0; i < gainMapProperties->count; ++i) { + const avifProperty * property = &gainMapProperties->prop[i]; + if (property->isOpaque) { + AVIF_CHECKRES(avifImagePushProperty(decoder->image->gainMap->image, + property->type, + property->u.opaque.usertype, + property->u.opaque.boxPayload.data, + property->u.opaque.boxPayload.size)); + } + } + } return AVIF_RESULT_OK; } diff --git a/src/write.c b/src/write.c index b0482ea14f..b67216f84a 100644 --- a/src/write.c +++ b/src/write.c @@ -3003,6 +3003,20 @@ static avifResult avifRWStreamWriteProperties(avifItemPropertyDedup * const dedu // see https://github.com/AOMediaCodec/libavif/pull/2429 } + // write out any opaque properties from avifImageAddOpaqueProperty() or avifImageAddUUIDProperty() + for (size_t i = 0; i < itemMetadata->numProperties; i++) { + avifItemPropertyDedupStart(dedup); + const avifImageItemProperty * prop = &itemMetadata->properties[i]; + avifBoxMarker propMarker; + AVIF_CHECKRES(avifRWStreamWriteBox(&dedup->s, (const char *)prop->boxtype, AVIF_BOX_SIZE_TBD, &propMarker)); + if (memcmp(prop->boxtype, "uuid", 4) == 0) { + AVIF_CHECKRES(avifRWStreamWrite(&dedup->s, prop->usertype, 16)); + } + AVIF_CHECKRES(avifRWStreamWrite(&dedup->s, prop->boxPayload.data, prop->boxPayload.size)); + avifRWStreamFinishBox(&dedup->s, propMarker); + AVIF_CHECKRES(avifItemPropertyDedupFinish(dedup, s, &item->ipma, AVIF_FALSE)); + } + // Also write the transformative properties. if (item->itemCategory == AVIF_ITEM_COLOR) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 715746d394..ff50ae83b5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -136,6 +136,7 @@ if(AVIF_GTEST) add_avif_gtest_with_data(avifpng16bittest) add_avif_gtest_with_data(avifprogressivetest) add_avif_gtest_with_data(avifpropertytest) + add_avif_internal_gtest(avifpropinternaltest) add_avif_gtest(avifrangetest) add_avif_gtest_with_data(avifreadimagetest) add_avif_internal_gtest(avifrgbtest) @@ -217,6 +218,7 @@ if(AVIF_ENABLE_FUZZTEST) add_avif_fuzztest(avif_fuzztest_enc_dec) add_avif_fuzztest(avif_fuzztest_enc_dec_anim) add_avif_fuzztest(avif_fuzztest_enc_dec_incr gtest/avifincrtest_helpers.cc) + add_avif_fuzztest(avif_fuzztest_properties) add_avif_fuzztest(avif_fuzztest_read_image) add_avif_fuzztest(avif_fuzztest_yuvrgb) else() @@ -358,6 +360,7 @@ if(AVIF_CODEC_AVM_ENABLED) avifiostatstest avifmetadatatest avifprogressivetest + avifpropertytest avifrangetest avify4mtest PROPERTIES DISABLED True diff --git a/tests/gtest/avif_fuzztest_properties.cc b/tests/gtest/avif_fuzztest_properties.cc new file mode 100644 index 0000000000..81459e293c --- /dev/null +++ b/tests/gtest/avif_fuzztest_properties.cc @@ -0,0 +1,109 @@ +// Copyright 2024 Google LLC +// SPDX-License-Identifier: BSD-2-Clause + +#include +#include +#include +#include +#include + +#include "avif/avif.h" +#include "avif/internal.h" +#include "avif_fuzztest_helpers.h" +#include "aviftest_helpers.h" +#include "fuzztest/fuzztest.h" +#include "gtest/gtest.h" + +namespace avif { +namespace testutil { +namespace { + +struct TestProp { + std::vector fourcc; + std::vector uuid; + std::vector body; +}; + +static std::vector UUID_4CC = {'u', 'u', 'i', 'd'}; + +void PropsValid(ImagePtr image, EncoderPtr encoder, DecoderPtr decoder, + std::vector testProps) { + ImagePtr decoded_image(avifImageCreateEmpty()); + ASSERT_NE(image.get(), nullptr); + ASSERT_NE(encoder.get(), nullptr); + ASSERT_NE(decoder.get(), nullptr); + ASSERT_NE(decoded_image.get(), nullptr); + + for (TestProp testProp : testProps) { + if (testProp.fourcc == UUID_4CC) { + ASSERT_EQ( + avifImageAddUUIDProperty(image.get(), testProp.uuid.data(), + testProp.body.data(), testProp.body.size()), + AVIF_RESULT_OK); + } else { + ASSERT_EQ(avifImageAddOpaqueProperty(image.get(), testProp.fourcc.data(), + testProp.body.data(), + testProp.body.size()), + AVIF_RESULT_OK); + } + } + + AvifRwData encoded_data; + const avifResult encoder_result = + avifEncoderWrite(encoder.get(), image.get(), &encoded_data); + ASSERT_EQ(encoder_result, AVIF_RESULT_OK) + << avifResultToString(encoder_result); + + const avifResult decoder_result = avifDecoderReadMemory( + decoder.get(), decoded_image.get(), encoded_data.data, encoded_data.size); + ASSERT_EQ(decoder_result, AVIF_RESULT_OK) + << avifResultToString(decoder_result); + + ASSERT_EQ(decoder->image->numProperties, testProps.size()); + for (size_t i = 0; i < testProps.size(); i++) { + TestProp testProp = testProps[i]; + const avifImageItemProperty& decodeProp = decoder->image->properties[i]; + EXPECT_EQ(std::string(decodeProp.boxtype, decodeProp.boxtype + 4), + std::string(testProp.fourcc.data(), testProp.fourcc.data() + 4)); + EXPECT_EQ(std::vector( + decodeProp.boxPayload.data, + decodeProp.boxPayload.data + decodeProp.boxPayload.size), + testProp.body); + } +} + +inline auto ArbitraryProp() { + auto fourcc = fuzztest::Arbitrary>().WithSize(4); + auto uuid = + fuzztest::Arbitrary>().WithSize(16); // ignored + auto body = fuzztest::Arbitrary>(); + // Don't return known properties. + return fuzztest::Filter( + [](TestProp prop) { + return !avifIsKnownPropertyType(prop.fourcc.data()); + }, + fuzztest::StructOf(fourcc, uuid, body)); +} + +inline auto ArbitraryUUIDProp() { + auto fourcc = fuzztest::Just(UUID_4CC); + auto uuid = fuzztest::Arbitrary>().WithSize(16); + auto body = fuzztest::Arbitrary>(); + // Don't use invalid UUIDs + return fuzztest::Filter( + [](TestProp prop) { return avifIsValidUUID(prop.uuid.data()); }, + fuzztest::StructOf(fourcc, uuid, body)); +} + +inline auto ArbitraryProps() { + return fuzztest::VectorOf( + fuzztest::OneOf(ArbitraryProp(), ArbitraryUUIDProp())); +} + +FUZZ_TEST(PropertiesAvifFuzzTest, PropsValid) + .WithDomains(ArbitraryAvifImage(), ArbitraryAvifEncoder(), + ArbitraryAvifDecoder(), ArbitraryProps()); + +} // namespace +} // namespace testutil +} // namespace avif diff --git a/tests/gtest/avifgainmaptest.cc b/tests/gtest/avifgainmaptest.cc index 14e6f5c42a..f9932fb665 100644 --- a/tests/gtest/avifgainmaptest.cc +++ b/tests/gtest/avifgainmaptest.cc @@ -1191,6 +1191,80 @@ TEST(ToneMapTest, ToneMapImageSameHeadroom) { } } +TEST(GainMapTest, OpaqueProperties) { + ImagePtr image = CreateTestImageWithGainMap(/*base_rendition_is_hdr=*/false); + ASSERT_NE(image, nullptr); + + std::vector abcd_data({0, 0, 0, 1, 'a', 'b', 'c'}); + std::vector efgh_data({'e', 'h'}); + uint8_t uuid[16] = {0x95, 0x96, 0xf1, 0xad, 0xb8, 0xab, 0x4a, 0xfc, + 0x9e, 0xfc, 0x83, 0x87, 0xac, 0x79, 0x37, 0xda}; + std::vector uuid_data({'x', 'y', 'z'}); + ASSERT_EQ(avifImageAddOpaqueProperty(image.get(), (uint8_t*)"abcd", + abcd_data.data(), abcd_data.size()), + AVIF_RESULT_OK); + ASSERT_EQ( + avifImageAddOpaqueProperty(image.get()->gainMap->image, (uint8_t*)"efgh", + efgh_data.data(), efgh_data.size()), + AVIF_RESULT_OK); + // Should not be added + ASSERT_EQ( + avifImageAddOpaqueProperty(image.get()->gainMap->image, (uint8_t*)"mdat", + efgh_data.data(), efgh_data.size()), + AVIF_RESULT_INVALID_ARGUMENT); + ASSERT_EQ(avifImageAddUUIDProperty(image.get()->gainMap->image, uuid, + uuid_data.data(), uuid_data.size()), + AVIF_RESULT_OK); + ASSERT_NE(image, nullptr); + + EncoderPtr encoder(avifEncoderCreate()); + ASSERT_NE(encoder, nullptr); + testutil::AvifRwData encoded; + avifResult result = avifEncoderWrite(encoder.get(), image.get(), &encoded); + ASSERT_EQ(result, AVIF_RESULT_OK) + << avifResultToString(result) << " " << encoder->diag.error; + + DecoderPtr decoder(avifDecoderCreate()); + ASSERT_NE(decoder, nullptr); + decoder->imageContentToDecode |= AVIF_IMAGE_CONTENT_GAIN_MAP; + + result = avifDecoderSetIOMemory(decoder.get(), encoded.data, encoded.size); + ASSERT_EQ(result, AVIF_RESULT_OK) + << avifResultToString(result) << " " << decoder->diag.error; + + result = avifDecoderParse(decoder.get()); + ASSERT_EQ(result, AVIF_RESULT_OK) + << avifResultToString(result) << " " << decoder->diag.error; + ASSERT_NE(decoder->image, nullptr); + ASSERT_EQ(decoder->image->numProperties, 1u); + + const avifImageItemProperty& abcd = decoder->image->properties[0]; + EXPECT_EQ(std::string(abcd.boxtype, abcd.boxtype + 4), "abcd"); + EXPECT_EQ(std::vector(abcd.boxPayload.data, + abcd.boxPayload.data + abcd.boxPayload.size), + abcd_data); + + ASSERT_NE(decoder->image->gainMap, nullptr); + ASSERT_NE(decoder->image->gainMap->image, nullptr); + ASSERT_EQ(decoder->image->gainMap->image->numProperties, 2u); + const avifImageItemProperty& efgh = + decoder->image->gainMap->image->properties[0]; + EXPECT_EQ(std::string(efgh.boxtype, efgh.boxtype + 4), "efgh"); + EXPECT_EQ(std::vector(efgh.boxPayload.data, + efgh.boxPayload.data + efgh.boxPayload.size), + efgh_data); + + const avifImageItemProperty& uuidProp = + decoder->image->gainMap->image->properties[1]; + EXPECT_EQ(std::string(uuidProp.boxtype, uuidProp.boxtype + 4), "uuid"); + EXPECT_EQ(std::vector(uuidProp.usertype, uuidProp.usertype + 16), + std::vector(uuid, uuid + 16)); + EXPECT_EQ( + std::vector(uuidProp.boxPayload.data, + uuidProp.boxPayload.data + uuidProp.boxPayload.size), + uuid_data); +} + class CreateGainMapTest : public testing::TestWithParam abcd_data({0, 0, 0, 1, 'a', 'b', 'c'}); + std::vector efgh_data({'e', 'h'}); + uint8_t uuid[16] = {0x95, 0x96, 0xf1, 0xad, 0xb8, 0xab, 0x4a, 0xfc, + 0x9e, 0xfc, 0x83, 0x87, 0xac, 0x79, 0x37, 0xda}; + std::vector uuid_data({'x', 'y', 'z'}); + ASSERT_EQ(avifImageAddOpaqueProperty(image.get(), (uint8_t*)"abcd", + abcd_data.data(), abcd_data.size()), + AVIF_RESULT_OK); + ASSERT_EQ(avifImageAddOpaqueProperty(image.get(), (uint8_t*)"efgh", + efgh_data.data(), efgh_data.size()), + AVIF_RESULT_OK); + // Should not be added + ASSERT_EQ(avifImageAddOpaqueProperty(image.get(), (uint8_t*)"mdat", + efgh_data.data(), efgh_data.size()), + AVIF_RESULT_INVALID_ARGUMENT); + ASSERT_EQ(avifImageAddUUIDProperty(image.get(), uuid, uuid_data.data(), + uuid_data.size()), + AVIF_RESULT_OK); + ASSERT_NE(image, nullptr); + testutil::FillImageGradient(image.get()); + + EncoderPtr encoder(avifEncoderCreate()); + ASSERT_NE(encoder, nullptr); + testutil::AvifRwData encoded; + avifResult result = avifEncoderWrite(encoder.get(), image.get(), &encoded); + ASSERT_EQ(result, AVIF_RESULT_OK) << avifResultToString(result); + + DecoderPtr decoder(avifDecoderCreate()); + ASSERT_NE(decoder, nullptr); + ASSERT_EQ(avifDecoderSetIOMemory(decoder.get(), encoded.data, encoded.size), + AVIF_RESULT_OK); + ASSERT_EQ(avifDecoderParse(decoder.get()), AVIF_RESULT_OK); + ASSERT_EQ(decoder->image->numProperties, 3u); + + const avifImageItemProperty& abcd = decoder->image->properties[0]; + EXPECT_EQ(std::string(abcd.boxtype, abcd.boxtype + 4), "abcd"); + EXPECT_EQ(std::vector(abcd.boxPayload.data, + abcd.boxPayload.data + abcd.boxPayload.size), + abcd_data); + + const avifImageItemProperty& efgh = decoder->image->properties[1]; + EXPECT_EQ(std::string(efgh.boxtype, efgh.boxtype + 4), "efgh"); + EXPECT_EQ(std::vector(efgh.boxPayload.data, + efgh.boxPayload.data + efgh.boxPayload.size), + efgh_data); + + const avifImageItemProperty& uuidProp = decoder->image->properties[2]; + EXPECT_EQ(std::string(uuidProp.boxtype, uuidProp.boxtype + 4), "uuid"); + EXPECT_EQ(std::vector(uuidProp.usertype, uuidProp.usertype + 16), + std::vector(uuid, uuid + 16)); + EXPECT_EQ( + std::vector(uuidProp.boxPayload.data, + uuidProp.boxPayload.data + uuidProp.boxPayload.size), + uuid_data); +} + //------------------------------------------------------------------------------ } // namespace diff --git a/tests/gtest/avifpropinternaltest.cc b/tests/gtest/avifpropinternaltest.cc new file mode 100644 index 0000000000..be8fdb37cf --- /dev/null +++ b/tests/gtest/avifpropinternaltest.cc @@ -0,0 +1,54 @@ +// Copyright 2024 Google LLC +// SPDX-License-Identifier: BSD-2-Clause + +#include + +#include "avif/internal.h" +#include "aviftest_helpers.h" +#include "gtest/gtest.h" + +namespace avif { +namespace { + +TEST(InternalPropertiesTest, KnownFound) { + const uint8_t FTYP[4]{'f', 't', 'y', 'p'}; + ASSERT_TRUE(avifIsKnownPropertyType(FTYP)); + const uint8_t MDAT[4]{'m', 'd', 'a', 't'}; + ASSERT_TRUE(avifIsKnownPropertyType(MDAT)); + const uint8_t ISPE[4]{'i', 's', 'p', 'e'}; + ASSERT_TRUE(avifIsKnownPropertyType(ISPE)); +} + +TEST(InternalPropertiesTest, UnknownNotFound) { + const uint8_t SIEP[4]{'s', 'i', 'e', 'p'}; + ASSERT_FALSE(avifIsKnownPropertyType(SIEP)); + const uint8_t MTXF[4]{'m', 't', 'x', 'f'}; + ASSERT_FALSE(avifIsKnownPropertyType(MTXF)); +} + +TEST(InternalPropertiesTest, UuidValid) { + const uint8_t uuid[16]{0x98, 0x10, 0xd7, 0xfc, 0xa5, 0xd2, 0x4c, 0x4b, + 0x9a, 0x4f, 0x05, 0x99, 0x02, 0xf4, 0x9b, 0xfd}; + ASSERT_TRUE(avifIsValidUUID(uuid)); +} + +TEST(InternalPropertiesTest, UuidInvalidISO) { + const uint8_t uuid[16]{'m', 'd', 'a', 't', 0x00, 0x01, 0x00, 0x10, + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}; + ASSERT_FALSE(avifIsValidUUID(uuid)); +} + +TEST(InternalPropertiesTest, UuidInvalidVariant) { + const uint8_t uuid[16]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + ASSERT_FALSE(avifIsValidUUID(uuid)); +} + +TEST(InternalPropertiesTest, UuidInvalidVersion) { + const uint8_t uuid[16]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, + 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + ASSERT_FALSE(avifIsValidUUID(uuid)); +} + +} // namespace +} // namespace avif