|  | // Copyright (c) 2021, Alliance for Open Media. All rights reserved | 
|  | // | 
|  | // This source code is subject to the terms of the BSD 2 Clause License and | 
|  | // the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License | 
|  | // was not distributed with this source code in the LICENSE file, you can | 
|  | // obtain it at www.aomedia.org/license/software. If the Alliance for Open | 
|  | // Media Patent License 1.0 was not distributed with this source code in the | 
|  | // PATENTS file, you can obtain it at www.aomedia.org/license/patent. | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <cstddef> | 
|  | #include <cstdint> | 
|  | #include <cstdlib> | 
|  |  | 
|  | #include "avifinfo.h" | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  | // Stream definition. | 
|  |  | 
|  | struct StreamData { | 
|  | const uint8_t* data; | 
|  | size_t data_size; | 
|  | }; | 
|  |  | 
|  | static const uint8_t* StreamRead(void* stream, size_t num_bytes) { | 
|  | if (stream == nullptr) std::abort(); | 
|  | if (num_bytes < 1 || num_bytes > AVIFINFO_MAX_NUM_READ_BYTES) std::abort(); | 
|  |  | 
|  | StreamData* stream_data = reinterpret_cast<StreamData*>(stream); | 
|  | if (num_bytes > stream_data->data_size) return nullptr; | 
|  | const uint8_t* data = stream_data->data; | 
|  | stream_data->data += num_bytes; | 
|  | stream_data->data_size -= num_bytes; | 
|  | return data; | 
|  | } | 
|  |  | 
|  | static void StreamSkip(void* stream, size_t num_bytes) { | 
|  | if (stream == nullptr) std::abort(); | 
|  | if (num_bytes < 1) std::abort(); | 
|  |  | 
|  | StreamData* stream_data = reinterpret_cast<StreamData*>(stream); | 
|  | num_bytes = std::min(num_bytes, stream_data->data_size); | 
|  | stream_data->data += num_bytes; | 
|  | stream_data->data_size -= num_bytes; | 
|  | } | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  |  | 
|  | static bool Equals(const AvifInfoFeatures& lhs, const AvifInfoFeatures& rhs) { | 
|  | return lhs.width == rhs.width && lhs.height == rhs.height && | 
|  | lhs.bit_depth == rhs.bit_depth && | 
|  | lhs.num_channels == rhs.num_channels && | 
|  | lhs.has_gainmap == rhs.has_gainmap && | 
|  | lhs.gainmap_item_id == rhs.gainmap_item_id && | 
|  | lhs.primary_item_id_location == rhs.primary_item_id_location && | 
|  | lhs.primary_item_id_bytes == rhs.primary_item_id_bytes; | 
|  | } | 
|  |  | 
|  | // Test a random bitstream of random size, whether it is valid or not. | 
|  | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t data_size) { | 
|  | AvifInfoStatus previous_status_identity = kAvifInfoNotEnoughData; | 
|  | AvifInfoStatus previous_status_features = kAvifInfoNotEnoughData; | 
|  | AvifInfoFeatures previous_features = {0}; | 
|  |  | 
|  | // Check the consistency of the returned status and features: | 
|  | // for a given size and a status that is not kAvifInfoNotEnoughData, any | 
|  | // bigger size (of the same data) should return the same status and features. | 
|  | for (size_t size = 0; size < data_size; ++size) { | 
|  | if (size > 1024 || previous_status_features != kAvifInfoNotEnoughData) { | 
|  | // The behavior is unlikely to change: save computing resources. | 
|  | size = std::min(data_size, size * 2); | 
|  | } | 
|  |  | 
|  | // Simple raw pointer API. | 
|  | AvifInfoFeatures features; | 
|  | const AvifInfoStatus status_identity = AvifInfoIdentify(data, size); | 
|  | const AvifInfoStatus status_features = | 
|  | AvifInfoGetFeatures(data, size, &features); | 
|  |  | 
|  | // Once a status different than kAvifInfoNotEnoughData is returned, it | 
|  | // should not change even with more input bytes. | 
|  | if ((previous_status_identity != kAvifInfoNotEnoughData && | 
|  | status_identity != previous_status_identity) || | 
|  | (previous_status_features != kAvifInfoNotEnoughData && | 
|  | status_features != previous_status_features)) { | 
|  | std::abort(); | 
|  | } | 
|  |  | 
|  | // Check the features. | 
|  | if (status_features == previous_status_features) { | 
|  | if (!Equals(features, previous_features)) { | 
|  | std::abort(); | 
|  | } | 
|  | } else if (status_features == kAvifInfoOk) { | 
|  | if (status_identity != kAvifInfoOk) { | 
|  | std::abort(); | 
|  | } | 
|  | if (features.width == 0u || features.height == 0u || | 
|  | features.bit_depth == 0u || features.num_channels == 0u || | 
|  | (!features.has_gainmap && features.gainmap_item_id) || | 
|  | !features.primary_item_id_location != | 
|  | !features.primary_item_id_bytes) { | 
|  | std::abort(); | 
|  | } | 
|  | if (features.primary_item_id_location && | 
|  | features.primary_item_id_location + features.primary_item_id_bytes > | 
|  | size) { | 
|  | std::abort(); | 
|  | } | 
|  | } else { | 
|  | if (features.width != 0u || features.height != 0u || | 
|  | features.bit_depth != 0u || features.num_channels != 0u || | 
|  | features.has_gainmap != 0u || features.gainmap_item_id != 0u || | 
|  | features.primary_item_id_location != 0u || | 
|  | features.primary_item_id_bytes != 0u) { | 
|  | std::abort(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Stream API. | 
|  | AvifInfoFeatures features_stream; | 
|  | StreamData stream_identity = {data, size}; | 
|  | StreamData stream_features = {data, size}; | 
|  | const AvifInfoStatus status_identity_stream = | 
|  | AvifInfoIdentifyStream(&stream_identity, StreamRead, StreamSkip); | 
|  | const AvifInfoStatus status_features_stream = AvifInfoGetFeaturesStream( | 
|  | &stream_features, StreamRead, StreamSkip, &features_stream); | 
|  | // Both API should have exactly the same behavior, errors included. | 
|  | if (status_identity_stream != status_identity) { | 
|  | std::abort(); | 
|  | } | 
|  | // AvifInfoGetFeaturesStream() should only be called if | 
|  | // AvifInfoIdentifyStream() was a success. Call it anyway to make sure it | 
|  | // does not crash, but only check the returned status and features when it | 
|  | // makes sense. | 
|  | if (status_identity_stream == kAvifInfoOk && | 
|  | (status_features_stream != status_features || | 
|  | !Equals(features_stream, features))) { | 
|  | std::abort(); | 
|  | } | 
|  |  | 
|  | // Another way of calling the stream API: reuse the stream object that was | 
|  | // used for AvifInfoIdentifyStream(). | 
|  | AvifInfoFeatures features_stream_reused; | 
|  | StreamData stream_reused = stream_identity; | 
|  | const AvifInfoStatus status_features_reused_stream = | 
|  | AvifInfoGetFeaturesStream(&stream_reused, StreamRead, StreamSkip, | 
|  | &features_stream_reused); | 
|  | // AvifInfoGetFeaturesStream() should only be called if | 
|  | // AvifInfoIdentifyStream() was a success. Call it anyway to make sure it | 
|  | // does not crash, but only check the returned status and features when it | 
|  | // makes sense. | 
|  | if (status_identity_stream == kAvifInfoOk) { | 
|  | if (status_features_reused_stream != status_features_stream) { | 
|  | std::abort(); | 
|  | } | 
|  | if (status_features_reused_stream == kAvifInfoOk) { | 
|  | // Offset any location-dependent feature as described in avifinfo.h. | 
|  | features_stream_reused.primary_item_id_location += | 
|  | size - stream_identity.data_size; | 
|  | } | 
|  | if (!Equals(features_stream_reused, features_stream)) { | 
|  | std::abort(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Another way of calling the stream API: no provided user-defined skip | 
|  | // method. | 
|  | AvifInfoFeatures features_no_skip; | 
|  | StreamData stream_identity_no_skip = {data, size}; | 
|  | StreamData stream_features_no_skip = {data, size}; | 
|  | const AvifInfoStatus status_identity_no_skip = AvifInfoIdentifyStream( | 
|  | &stream_identity_no_skip, StreamRead, /*skip=*/nullptr); | 
|  | const AvifInfoStatus status_features_no_skip = | 
|  | AvifInfoGetFeaturesStream(&stream_features_no_skip, StreamRead, | 
|  | /*skip=*/nullptr, &features_no_skip); | 
|  | // There may be some difference in status. For example, a valid or invalid | 
|  | // status could be returned just after skipping some bytes. If the skip | 
|  | // argument is omitted, these bytes will be read instead. If some of these | 
|  | // bytes are missing, kAvifInfoNotEnoughData will be returned instead of the | 
|  | // expected success or failure status. | 
|  | if (status_identity_no_skip != status_identity_stream && | 
|  | !(status_identity_no_skip == kAvifInfoNotEnoughData && | 
|  | status_identity_stream == kAvifInfoOk)) { | 
|  | std::abort(); | 
|  | } | 
|  | if (status_identity_stream == kAvifInfoOk) { | 
|  | if ((status_features_no_skip != status_features && | 
|  | !(status_features_no_skip == kAvifInfoNotEnoughData && | 
|  | status_features != kAvifInfoOk)) || | 
|  | !Equals(features_no_skip, features)) { | 
|  | std::abort(); | 
|  | } | 
|  | } | 
|  |  | 
|  | previous_status_identity = status_identity; | 
|  | previous_status_features = status_features; | 
|  | previous_features = features; | 
|  | } | 
|  | return 0; | 
|  | } |