blob: fc3bc2f93bbb202697b2393970409c68cdfd5a9c [file]
// Copyright 2022 Google LLC
// SPDX-License-Identifier: BSD-2-Clause
// Compare non-incremental and incremental decode results of an arbitrary byte
// sequence.
#include <algorithm>
#include <cstdint>
#include <string>
#include "avif/avif.h"
#include "avif_fuzztest_helpers.h"
#include "avifincrtest_helpers.h"
#include "fuzztest/fuzztest.h"
#include "gtest/gtest.h"
using ::fuzztest::Arbitrary;
using ::fuzztest::BitFlagCombinationOf;
namespace avif {
namespace testutil {
namespace {
//------------------------------------------------------------------------------
struct DecoderInput {
const uint8_t* available_bytes;
size_t available_size;
size_t read_size;
};
// A custom reader is necessary to get the number of bytes read by libavif.
// See avifIOReadFunc() documentation.
avifResult AvifIoRead(struct avifIO* io, uint32_t read_flags, uint64_t offset,
size_t size, avifROData* out) {
DecoderInput* data = reinterpret_cast<DecoderInput*>(io->data);
if (read_flags != 0 || !data || data->available_size < offset) {
return AVIF_RESULT_IO_ERROR;
}
out->data = data->available_bytes + offset;
out->size =
std::min(size, data->available_size - static_cast<size_t>(offset));
data->read_size =
std::max(data->read_size, static_cast<size_t>(offset) + out->size);
return AVIF_RESULT_OK;
}
//------------------------------------------------------------------------------
void DecodeIncr(const std::string& arbitrary_bytes, bool is_persistent,
bool give_size_hint,
avifImageContentTypeFlags content_to_decode,
bool use_nth_image_api) {
ASSERT_FALSE(GetSeedDataDirs().empty()); // Make sure seeds are available.
ImagePtr reference(avifImageCreateEmpty());
ASSERT_NE(reference.get(), nullptr);
DecoderInput data = {reinterpret_cast<const uint8_t*>(arbitrary_bytes.data()),
arbitrary_bytes.size(), 0};
avifIO io = {.destroy = nullptr,
.read = AvifIoRead,
.write = nullptr,
.sizeHint = arbitrary_bytes.size(),
.persistent = AVIF_TRUE,
.data = &data};
DecoderPtr decoder(avifDecoderCreate());
ASSERT_NE(decoder.get(), nullptr);
avifDecoderSetIO(decoder.get(), &io);
// OSS-Fuzz limits the allocated memory to 2560 MB.
constexpr uint32_t kMaxMem = 2560u * 1024 * 1024;
// Consider at most four planes of 16-bit samples.
constexpr uint32_t kMaxImageSize =
kMaxMem / (AVIF_PLANE_COUNT_YUV + 1) / sizeof(uint16_t);
// Reduce the limit further to include pixel buffer copies and other memory
// allocations.
constexpr uint32_t kImageSizeLimit = kMaxImageSize / 4;
// avifDecoderParse returns AVIF_RESULT_NOT_IMPLEMENTED if kImageSizeLimit is
// bigger than AVIF_DEFAULT_IMAGE_SIZE_LIMIT.
static_assert(kImageSizeLimit <= AVIF_DEFAULT_IMAGE_SIZE_LIMIT,
"Too big an image size limit");
decoder->imageSizeLimit = kImageSizeLimit;
// This can lead to AVIF_RESULT_NO_CONTENT.
decoder->imageContentToDecode = content_to_decode;
avifResult result = avifDecoderRead(decoder.get(), reference.get());
// AVIF_RESULT_INTERNAL_ERROR means a broken invariant and should not happen.
ASSERT_NE(result, AVIF_RESULT_INTERNAL_ERROR);
if (result != AVIF_RESULT_OK) return;
// Avoid timeouts by discarding big images decoded many times.
if (reference->width * reference->height * data.read_size > 8 * 1024 * 1024) {
return;
}
// decodeIncrementally() will fail if there are leftover bytes.
const avifRWData encoded_data = {const_cast<uint8_t*>(data.available_bytes),
data.read_size};
// No clue on whether encoded_data is tiled so use a lower bound of a single
// tile for the whole image.
// Note that an AVIF tile is at most as high as an AV1 frame
// (aomediacodec.github.io/av1-spec says max_frame_height_minus_1 < 65536)
// but libavif successfully decodes AVIF files with dimensions unrelated to
// the underlying AV1 frame (for example a 1x1000000 AVIF for a 1x1 AV1).
// Otherwise we could use the minimum of reference->height and 65536u below.
const uint32_t max_cell_height = reference->height;
result = DecodeIncrementally(
encoded_data, decoder.get(), is_persistent, give_size_hint,
use_nth_image_api, *reference, max_cell_height,
/*enable_fine_incremental_check=*/false, /*expect_whole_file_read=*/true,
/*expect_parse_success_from_partial_file=*/false);
ASSERT_NE(result, AVIF_RESULT_INTERNAL_ERROR);
}
FUZZ_TEST(DecodeAvifFuzzTest, DecodeIncr)
.WithDomains(ArbitraryImageWithSeeds({AVIF_APP_FILE_FORMAT_AVIF}),
/*is_persistent=*/Arbitrary<bool>(),
/*give_size_hint=*/Arbitrary<bool>(),
BitFlagCombinationOf<avifImageContentTypeFlags>(
{AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA,
AVIF_IMAGE_CONTENT_GAIN_MAP,
AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS}),
/*use_nth_image_api=*/Arbitrary<bool>());
//------------------------------------------------------------------------------
} // namespace
} // namespace testutil
} // namespace avif