|  | // 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 "avifinfo.h" | 
|  |  | 
|  | #include <stdint.h> | 
|  | #include <stdio.h> | 
|  | #include <string.h> | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  |  | 
|  | // Status returned when reading the content of a box (or file). | 
|  | typedef enum { | 
|  | kFound,     // Input correctly parsed and information retrieved. | 
|  | kNotFound,  // Input correctly parsed but information is missing or elsewhere. | 
|  | kTruncated,  // Input correctly parsed until missing bytes to continue. | 
|  | kAborted,  // Input correctly parsed until stopped to avoid timeout or crash. | 
|  | kInvalid,  // Input incorrectly parsed. | 
|  | } AvifInfoInternalStatus; | 
|  |  | 
|  | static AvifInfoStatus AvifInfoInternalConvertStatus(AvifInfoInternalStatus s) { | 
|  | return (s == kFound)                         ? kAvifInfoOk | 
|  | : (s == kNotFound || s == kTruncated) ? kAvifInfoNotEnoughData | 
|  | : (s == kAborted)                     ? kAvifInfoTooComplex | 
|  | : kAvifInfoInvalidFile; | 
|  | } | 
|  |  | 
|  | // uint32_t is used everywhere in this file. It is unlikely to be insufficient | 
|  | // to parse AVIF headers. | 
|  | #define AVIFINFO_MAX_SIZE UINT32_MAX | 
|  | // Be reasonable. Avoid timeouts and out-of-memory. | 
|  | #define AVIFINFO_MAX_NUM_BOXES 4096 | 
|  | // AvifInfoInternalFeatures uses uint8_t to store values. | 
|  | #define AVIFINFO_MAX_VALUE UINT8_MAX | 
|  | // Maximum number of stored associations. Past that, they are skipped. | 
|  | #define AVIFINFO_MAX_TILES 16 | 
|  | #define AVIFINFO_MAX_PROPS 32 | 
|  | #define AVIFINFO_MAX_FEATURES 8 | 
|  | #define AVIFINFO_UNDEFINED 0 | 
|  |  | 
|  | // Reads an unsigned integer from 'input' with most significant bits first. | 
|  | // 'input' must be at least 'num_bytes'-long. | 
|  | static uint32_t AvifInfoInternalReadBigEndian(const uint8_t* input, | 
|  | uint32_t num_bytes) { | 
|  | uint32_t value = 0; | 
|  | for (uint32_t i = 0; i < num_bytes; ++i) { | 
|  | value = (value << 8) | input[i]; | 
|  | } | 
|  | return value; | 
|  | } | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  | // Convenience macros. | 
|  |  | 
|  | #if defined(AVIFINFO_LOG_ERROR)  // Toggle to log encountered issues. | 
|  | static void AvifInfoInternalLogError(const char* file, int line, | 
|  | AvifInfoInternalStatus status) { | 
|  | const char* kStr[] = {"Found", "NotFound", "Truncated", "Invalid", "Aborted"}; | 
|  | fprintf(stderr, "  %s:%d: %s\n", file, line, kStr[status]); | 
|  | // Set a breakpoint here to catch the first detected issue. | 
|  | } | 
|  | #define AVIFINFO_RETURN(check_status)                               \ | 
|  | do {                                                              \ | 
|  | const AvifInfoInternalStatus status_checked = (check_status);   \ | 
|  | if (status_checked != kFound && status_checked != kNotFound) {  \ | 
|  | AvifInfoInternalLogError(__FILE__, __LINE__, status_checked); \ | 
|  | }                                                               \ | 
|  | return status_checked;                                          \ | 
|  | } while (0) | 
|  | #else | 
|  | #define AVIFINFO_RETURN(check_status) \ | 
|  | do {                                \ | 
|  | return (check_status);            \ | 
|  | } while (0) | 
|  | #endif | 
|  |  | 
|  | #define AVIFINFO_CHECK(check_condition, check_status)      \ | 
|  | do {                                                     \ | 
|  | if (!(check_condition)) AVIFINFO_RETURN(check_status); \ | 
|  | } while (0) | 
|  | #define AVIFINFO_CHECK_STATUS_IS(check_status, expected_status)            \ | 
|  | do {                                                                     \ | 
|  | const AvifInfoInternalStatus status_returned = (check_status);         \ | 
|  | AVIFINFO_CHECK(status_returned == (expected_status), status_returned); \ | 
|  | } while (0) | 
|  | #define AVIFINFO_CHECK_FOUND(check_status) \ | 
|  | AVIFINFO_CHECK_STATUS_IS((check_status), kFound) | 
|  | #define AVIFINFO_CHECK_NOT_FOUND(check_status) \ | 
|  | AVIFINFO_CHECK_STATUS_IS((check_status), kNotFound) | 
|  |  | 
|  | #if defined(AVIFINFO_ENABLE_DEBUG_LOG) | 
|  | #define AVIF_DEBUG_LOG(...) printf(__VA_ARGS__) | 
|  | #else | 
|  | #define AVIF_DEBUG_LOG(...) | 
|  | #endif | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  | // Streamed input struct and helper functions. | 
|  |  | 
|  | typedef struct { | 
|  | void* stream;             // User-defined data. | 
|  | read_stream_t read;       // Used to fetch more bytes from the 'stream'. | 
|  | skip_stream_t skip;       // Used to advance the position in the 'stream'. | 
|  | // Fallback to 'read' if 'skip' is null. | 
|  | uint64_t num_read_bytes;  // Number of bytes read or skipped. | 
|  | } AvifInfoInternalStream; | 
|  |  | 
|  | // Reads 'num_bytes' from the 'stream'. They are available at '*data'. | 
|  | // 'num_bytes' must be greater than zero. | 
|  | static AvifInfoInternalStatus AvifInfoInternalRead( | 
|  | AvifInfoInternalStream* stream, uint32_t num_bytes, const uint8_t** data) { | 
|  | *data = stream->read(stream->stream, num_bytes); | 
|  | AVIFINFO_CHECK(*data != NULL, kTruncated); | 
|  | stream->num_read_bytes += num_bytes; | 
|  | return kFound; | 
|  | } | 
|  |  | 
|  | // Skips 'num_bytes' from the 'stream'. 'num_bytes' can be zero. | 
|  | static AvifInfoInternalStatus AvifInfoInternalSkip( | 
|  | AvifInfoInternalStream* stream, uint32_t num_bytes) { | 
|  | // Avoid a call to the user-defined function for nothing. | 
|  | if (num_bytes > 0) { | 
|  | if (stream->skip == NULL) { | 
|  | const uint8_t* unused; | 
|  | while (num_bytes > AVIFINFO_MAX_NUM_READ_BYTES) { | 
|  | AVIFINFO_CHECK_FOUND( | 
|  | AvifInfoInternalRead(stream, AVIFINFO_MAX_NUM_READ_BYTES, &unused)); | 
|  | num_bytes -= AVIFINFO_MAX_NUM_READ_BYTES; | 
|  | } | 
|  | return AvifInfoInternalRead(stream, num_bytes, &unused); | 
|  | } | 
|  | stream->skip(stream->stream, num_bytes); | 
|  | stream->num_read_bytes += num_bytes; | 
|  | } | 
|  | return kFound; | 
|  | } | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  | // Features are parsed into temporary property associations. | 
|  |  | 
|  | typedef struct { | 
|  | uint8_t tile_item_id; | 
|  | uint8_t parent_item_id; | 
|  | uint8_t dimg_idx;      // Index of this association in the dimg box (0-based). | 
|  | } AvifInfoInternalTile;  // Tile item id <-> parent item id associations. | 
|  |  | 
|  | typedef struct { | 
|  | uint8_t property_index; | 
|  | uint8_t item_id; | 
|  | } AvifInfoInternalProp;  // Property index <-> item id associations. | 
|  |  | 
|  | typedef struct { | 
|  | uint8_t property_index; | 
|  | uint32_t width, height; | 
|  | } AvifInfoInternalDimProp;  // Property <-> features associations. | 
|  |  | 
|  | typedef struct { | 
|  | uint8_t property_index; | 
|  | uint8_t bit_depth, num_channels; | 
|  | } AvifInfoInternalChanProp;  // Property <-> features associations. | 
|  |  | 
|  | typedef struct { | 
|  | uint8_t has_primary_item;  // True if "pitm" was parsed. | 
|  | uint8_t has_alpha;    // True if an alpha "auxC" was parsed. | 
|  | // Index of the gain map auxC property. | 
|  | uint8_t gainmap_property_index; | 
|  | uint8_t primary_item_id; | 
|  | AvifInfoFeatures primary_item_features;  // Deduced from the data below. | 
|  | uint8_t data_was_skipped;  // True if some loops/indices were skipped. | 
|  | uint8_t tone_mapped_item_id;  // Id of the "tmap" box, > 0 if present. | 
|  | uint8_t iinf_parsed;  // True if the "iinf" (item info) box was parsed. | 
|  |  | 
|  | uint8_t num_tiles; | 
|  | AvifInfoInternalTile tiles[AVIFINFO_MAX_TILES]; | 
|  | uint8_t num_props; | 
|  | AvifInfoInternalProp props[AVIFINFO_MAX_PROPS]; | 
|  | uint8_t num_dim_props; | 
|  | AvifInfoInternalDimProp dim_props[AVIFINFO_MAX_FEATURES]; | 
|  | uint8_t num_chan_props; | 
|  | AvifInfoInternalChanProp chan_props[AVIFINFO_MAX_FEATURES]; | 
|  | } AvifInfoInternalFeatures; | 
|  |  | 
|  | // Generates the features of a given 'target_item_id' from internal features. | 
|  | static AvifInfoInternalStatus AvifInfoInternalGetItemFeatures( | 
|  | AvifInfoInternalFeatures* f, uint32_t target_item_id, uint32_t tile_depth) { | 
|  | for (uint32_t prop_item = 0; prop_item < f->num_props; ++prop_item) { | 
|  | if (f->props[prop_item].item_id != target_item_id) continue; | 
|  | const uint32_t property_index = f->props[prop_item].property_index; | 
|  |  | 
|  | // Retrieve the width and height of the primary item if not already done. | 
|  | if (target_item_id == f->primary_item_id && | 
|  | (f->primary_item_features.width == AVIFINFO_UNDEFINED || | 
|  | f->primary_item_features.height == AVIFINFO_UNDEFINED)) { | 
|  | for (uint32_t i = 0; i < f->num_dim_props; ++i) { | 
|  | if (f->dim_props[i].property_index != property_index) continue; | 
|  | f->primary_item_features.width = f->dim_props[i].width; | 
|  | f->primary_item_features.height = f->dim_props[i].height; | 
|  | if (f->primary_item_features.bit_depth != AVIFINFO_UNDEFINED && | 
|  | f->primary_item_features.num_channels != AVIFINFO_UNDEFINED) { | 
|  | return kFound; | 
|  | } | 
|  | break; | 
|  | } | 
|  | } | 
|  | // Retrieve the bit depth and number of channels of the target item if not | 
|  | // already done. | 
|  | if (f->primary_item_features.bit_depth == AVIFINFO_UNDEFINED || | 
|  | f->primary_item_features.num_channels == AVIFINFO_UNDEFINED) { | 
|  | for (uint32_t i = 0; i < f->num_chan_props; ++i) { | 
|  | if (f->chan_props[i].property_index != property_index) continue; | 
|  | f->primary_item_features.bit_depth = f->chan_props[i].bit_depth; | 
|  | f->primary_item_features.num_channels = f->chan_props[i].num_channels; | 
|  | if (f->primary_item_features.width != AVIFINFO_UNDEFINED && | 
|  | f->primary_item_features.height != AVIFINFO_UNDEFINED) { | 
|  | return kFound; | 
|  | } | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Check for the bit_depth and num_channels in a tile if not yet found. | 
|  | for (uint32_t tile = 0; tile < f->num_tiles && tile_depth < 3; ++tile) { | 
|  | if (f->tiles[tile].parent_item_id != target_item_id) continue; | 
|  | AVIFINFO_CHECK_NOT_FOUND(AvifInfoInternalGetItemFeatures( | 
|  | f, f->tiles[tile].tile_item_id, tile_depth + 1)); | 
|  | } | 
|  | AVIFINFO_RETURN(kNotFound); | 
|  | } | 
|  |  | 
|  | // Generates the 'f->primary_item_features' from the AvifInfoInternalFeatures. | 
|  | // Returns kNotFound if there is not enough information. | 
|  | static AvifInfoInternalStatus AvifInfoInternalGetPrimaryItemFeatures( | 
|  | AvifInfoInternalFeatures* f) { | 
|  | // Nothing to do without the primary item ID. | 
|  | AVIFINFO_CHECK(f->has_primary_item, kNotFound); | 
|  | // Early exit. | 
|  | AVIFINFO_CHECK(f->num_dim_props > 0 && f->num_chan_props, kNotFound); | 
|  |  | 
|  | // Look for a gain map. | 
|  | // HEIF scheme: gain map is a hidden input of a derived item. | 
|  | if (f->tone_mapped_item_id) { | 
|  | for (uint32_t tile = 0; tile < f->num_tiles; ++tile) { | 
|  | if (f->tiles[tile].parent_item_id == f->tone_mapped_item_id && | 
|  | f->tiles[tile].dimg_idx == 1) { | 
|  | f->primary_item_features.has_gainmap = 1; | 
|  | f->primary_item_features.gainmap_item_id = f->tiles[tile].tile_item_id; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | // Adobe scheme: gain map is an auxiliary item. | 
|  | if (!f->primary_item_features.has_gainmap && f->gainmap_property_index > 0) { | 
|  | for (uint32_t prop_item = 0; prop_item < f->num_props; ++prop_item) { | 
|  | if (f->props[prop_item].property_index == f->gainmap_property_index) { | 
|  | f->primary_item_features.has_gainmap = 1; | 
|  | f->primary_item_features.gainmap_item_id = f->props[prop_item].item_id; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | // If the gain map has not been found but we haven't read all the relevant | 
|  | // metadata, we might still find one later and cannot stop now. | 
|  | if (!f->primary_item_features.has_gainmap && !f->iinf_parsed) { | 
|  | return kNotFound; | 
|  | } | 
|  |  | 
|  | AVIFINFO_CHECK_FOUND( | 
|  | AvifInfoInternalGetItemFeatures(f, f->primary_item_id, /*tile_depth=*/0)); | 
|  |  | 
|  | // "auxC" is parsed before the "ipma" properties so it is known now, if any. | 
|  | if (f->has_alpha) ++f->primary_item_features.num_channels; | 
|  | return kFound; | 
|  | } | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  | // Box header parsing and various size checks. | 
|  |  | 
|  | typedef struct { | 
|  | uint32_t size;          // In bytes. | 
|  | uint8_t type[4];        // Four characters. | 
|  | uint32_t version;       // 0 or actual version if this is a full box. | 
|  | uint32_t flags;         // 0 or actual value if this is a full box. | 
|  | uint32_t content_size;  // 'size' minus the header size. | 
|  | } AvifInfoInternalBox; | 
|  |  | 
|  | // Reads the header of a 'box' starting at the beginning of a 'stream'. | 
|  | // 'num_remaining_bytes' is the remaining size of the container of the 'box' | 
|  | // (either the file size itself or the content size of the parent of the 'box'). | 
|  | static AvifInfoInternalStatus AvifInfoInternalParseBox( | 
|  | int nesting_level, AvifInfoInternalStream* stream, | 
|  | uint32_t num_remaining_bytes, uint32_t* num_parsed_boxes, | 
|  | AvifInfoInternalBox* box) { | 
|  | const uint8_t* data; | 
|  | // See ISO/IEC 14496-12:2012(E) 4.2 | 
|  | uint32_t box_header_size = 8;  // box 32b size + 32b type (at least) | 
|  | AVIFINFO_CHECK(box_header_size <= num_remaining_bytes, kInvalid); | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data)); | 
|  | box->size = AvifInfoInternalReadBigEndian(data, sizeof(uint32_t)); | 
|  | memcpy(box->type, data + 4, 4); | 
|  | // 'box->size==1' means 64-bit size should be read after the box type. | 
|  | // 'box->size==0' means this box extends to all remaining bytes. | 
|  | if (box->size == 1) { | 
|  | box_header_size += 8; | 
|  | AVIFINFO_CHECK(box_header_size <= num_remaining_bytes, kInvalid); | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data)); | 
|  | // Stop the parsing if any box has a size greater than 4GB. | 
|  | AVIFINFO_CHECK(AvifInfoInternalReadBigEndian(data, sizeof(uint32_t)) == 0, | 
|  | kAborted); | 
|  | // Read the 32 least-significant bits. | 
|  | box->size = AvifInfoInternalReadBigEndian(data + 4, sizeof(uint32_t)); | 
|  | } else if (box->size == 0) { | 
|  | box->size = num_remaining_bytes; | 
|  | } | 
|  | AVIFINFO_CHECK(box->size >= box_header_size, kInvalid); | 
|  | AVIFINFO_CHECK(box->size <= num_remaining_bytes, kInvalid); | 
|  |  | 
|  | const int has_fullbox_header = | 
|  | !memcmp(box->type, "meta", 4) || !memcmp(box->type, "pitm", 4) || | 
|  | !memcmp(box->type, "ipma", 4) || !memcmp(box->type, "ispe", 4) || | 
|  | !memcmp(box->type, "pixi", 4) || !memcmp(box->type, "iref", 4) || | 
|  | !memcmp(box->type, "auxC", 4) || !memcmp(box->type, "iinf", 4) || | 
|  | !memcmp(box->type, "infe", 4); | 
|  | if (has_fullbox_header) box_header_size += 4; | 
|  | AVIFINFO_CHECK(box->size >= box_header_size, kInvalid); | 
|  | box->content_size = box->size - box_header_size; | 
|  | // AvifInfoGetFeaturesStream() can be called on a full stream or on a stream | 
|  | // where the 'ftyp' box was already read. Do not count 'ftyp' boxes towards | 
|  | // AVIFINFO_MAX_NUM_BOXES, so that this function returns the same status in | 
|  | // both situations (because of the AVIFINFO_MAX_NUM_BOXES check that would | 
|  | // compare a different box count otherwise). This is fine because top-level | 
|  | // 'ftyp' boxes are just skipped anyway. | 
|  | if (nesting_level != 0 || memcmp(box->type, "ftyp", 4)) { | 
|  | // Avoid timeouts. The maximum number of parsed boxes is arbitrary. | 
|  | ++*num_parsed_boxes; | 
|  | AVIFINFO_CHECK(*num_parsed_boxes < AVIFINFO_MAX_NUM_BOXES, kAborted); | 
|  | } | 
|  |  | 
|  | box->version = 0; | 
|  | box->flags = 0; | 
|  | if (has_fullbox_header) { | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data)); | 
|  | box->version = AvifInfoInternalReadBigEndian(data, 1); | 
|  | box->flags = AvifInfoInternalReadBigEndian(data + 1, 3); | 
|  | // See AV1 Image File Format (AVIF) 8.1 | 
|  | // at https://aomediacodec.github.io/av1-avif/#avif-boxes (available when | 
|  | // https://github.com/AOMediaCodec/av1-avif/pull/170 is merged). | 
|  | const uint32_t is_parsable = | 
|  | (!memcmp(box->type, "meta", 4) && box->version <= 0) || | 
|  | (!memcmp(box->type, "pitm", 4) && box->version <= 1) || | 
|  | (!memcmp(box->type, "ipma", 4) && box->version <= 1) || | 
|  | (!memcmp(box->type, "ispe", 4) && box->version <= 0) || | 
|  | (!memcmp(box->type, "pixi", 4) && box->version <= 0) || | 
|  | (!memcmp(box->type, "iref", 4) && box->version <= 1) || | 
|  | (!memcmp(box->type, "auxC", 4) && box->version <= 0) || | 
|  | (!memcmp(box->type, "iinf", 4) && box->version <= 1) || | 
|  | (!memcmp(box->type, "infe", 4) && box->version >= 2 && | 
|  | box->version <= 3); | 
|  | // Instead of considering this file as invalid, skip unparsable boxes. | 
|  | if (!is_parsable) memcpy(box->type, "\0skp", 4);  // \0 so not a valid type | 
|  | } | 
|  | AVIF_DEBUG_LOG("%*c", nesting_level * 2, ' '); | 
|  | AVIF_DEBUG_LOG("Box type %.4s size %d\n", box->type, box->size); | 
|  | return kFound; | 
|  | } | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  |  | 
|  | // Parses a 'stream' of an "ipco" box into 'features'. | 
|  | // "ispe" is used for width and height, "pixi" and "av1C" are used for bit depth | 
|  | // and number of channels, and "auxC" is used for alpha. | 
|  | static AvifInfoInternalStatus ParseIpco(int nesting_level, | 
|  | AvifInfoInternalStream* stream, | 
|  | uint32_t num_remaining_bytes, | 
|  | uint32_t* num_parsed_boxes, | 
|  | AvifInfoInternalFeatures* features) { | 
|  | uint32_t box_index = 1;  // 1-based index. Used for iterating over properties. | 
|  | do { | 
|  | AvifInfoInternalBox box; | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( | 
|  | nesting_level, stream, num_remaining_bytes, num_parsed_boxes, &box)); | 
|  |  | 
|  | if (!memcmp(box.type, "ispe", 4)) { | 
|  | // See ISO/IEC 23008-12:2017(E) 6.5.3.2 | 
|  | const uint8_t* data; | 
|  | AVIFINFO_CHECK(box.content_size >= 8, kInvalid); | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data)); | 
|  | const uint32_t width = AvifInfoInternalReadBigEndian(data + 0, 4); | 
|  | const uint32_t height = AvifInfoInternalReadBigEndian(data + 4, 4); | 
|  | AVIFINFO_CHECK(width != 0 && height != 0, kInvalid); | 
|  | if (features->num_dim_props < AVIFINFO_MAX_FEATURES && | 
|  | box_index <= AVIFINFO_MAX_VALUE) { | 
|  | features->dim_props[features->num_dim_props].property_index = box_index; | 
|  | features->dim_props[features->num_dim_props].width = width; | 
|  | features->dim_props[features->num_dim_props].height = height; | 
|  | ++features->num_dim_props; | 
|  | } else { | 
|  | features->data_was_skipped = 1; | 
|  | } | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size - 8)); | 
|  | } else if (!memcmp(box.type, "pixi", 4)) { | 
|  | // See ISO/IEC 23008-12:2017(E) 6.5.6.2 | 
|  | const uint8_t* data; | 
|  | AVIFINFO_CHECK(box.content_size >= 1, kInvalid); | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data)); | 
|  | const uint32_t num_channels = AvifInfoInternalReadBigEndian(data + 0, 1); | 
|  | AVIFINFO_CHECK(num_channels >= 1, kInvalid); | 
|  | AVIFINFO_CHECK(box.content_size >= 1 + num_channels, kInvalid); | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data)); | 
|  | const uint32_t bit_depth = AvifInfoInternalReadBigEndian(data, 1); | 
|  | AVIFINFO_CHECK(bit_depth >= 1, kInvalid); | 
|  | for (uint32_t i = 1; i < num_channels; ++i) { | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data)); | 
|  | // Bit depth should be the same for all channels. | 
|  | AVIFINFO_CHECK(AvifInfoInternalReadBigEndian(data, 1) == bit_depth, | 
|  | kInvalid); | 
|  | AVIFINFO_CHECK(i <= 32, kAborted);  // Be reasonable. | 
|  | } | 
|  | if (features->num_chan_props < AVIFINFO_MAX_FEATURES && | 
|  | box_index <= AVIFINFO_MAX_VALUE && bit_depth <= AVIFINFO_MAX_VALUE && | 
|  | num_channels <= AVIFINFO_MAX_VALUE) { | 
|  | features->chan_props[features->num_chan_props].property_index = | 
|  | box_index; | 
|  | features->chan_props[features->num_chan_props].bit_depth = bit_depth; | 
|  | features->chan_props[features->num_chan_props].num_channels = | 
|  | num_channels; | 
|  | ++features->num_chan_props; | 
|  | } else { | 
|  | features->data_was_skipped = 1; | 
|  | } | 
|  | AVIFINFO_CHECK_FOUND( | 
|  | AvifInfoInternalSkip(stream, box.content_size - (1 + num_channels))); | 
|  | } else if (!memcmp(box.type, "av1C", 4)) { | 
|  | // See AV1 Codec ISO Media File Format Binding 2.3.1 | 
|  | // at https://aomediacodec.github.io/av1-isobmff/#av1c | 
|  | // Only parse the necessary third byte. Assume that the others are valid. | 
|  | const uint8_t* data; | 
|  | AVIFINFO_CHECK(box.content_size >= 3, kInvalid); | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 3, &data)); | 
|  | const int high_bitdepth = (data[2] & 0x40) != 0; | 
|  | const int twelve_bit = (data[2] & 0x20) != 0; | 
|  | const int monochrome = (data[2] & 0x10) != 0; | 
|  | if (twelve_bit) { | 
|  | AVIFINFO_CHECK(high_bitdepth, kInvalid); | 
|  | } | 
|  | if (features->num_chan_props < AVIFINFO_MAX_FEATURES && | 
|  | box_index <= AVIFINFO_MAX_VALUE) { | 
|  | features->chan_props[features->num_chan_props].property_index = | 
|  | box_index; | 
|  | features->chan_props[features->num_chan_props].bit_depth = | 
|  | high_bitdepth ? twelve_bit ? 12 : 10 : 8; | 
|  | features->chan_props[features->num_chan_props].num_channels = | 
|  | monochrome ? 1 : 3; | 
|  | ++features->num_chan_props; | 
|  | } else { | 
|  | features->data_was_skipped = 1; | 
|  | } | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size - 3)); | 
|  | } else if (!memcmp(box.type, "auxC", 4)) { | 
|  | // See AV1 Image File Format (AVIF) 4 | 
|  | // at https://aomediacodec.github.io/av1-avif/#auxiliary-images | 
|  | const char* kAlphaStr = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"; | 
|  | const uint32_t kAlphaStrLength = 44;  // Includes terminating character. | 
|  | const char* kGainmapStr = "urn:com:photo:aux:hdrgainmap"; | 
|  | const uint32_t kGainmapStrLength = 29;  // Includes terminating character. | 
|  | uint32_t num_read_bytes = 0; | 
|  | // Check for a gain map or for an alpha plane. Start with the gain map | 
|  | // since the identifier is shorter. | 
|  | if (box.content_size >= kGainmapStrLength) { | 
|  | const uint8_t* data; | 
|  | AVIFINFO_CHECK_FOUND( | 
|  | AvifInfoInternalRead(stream, kGainmapStrLength, &data)); | 
|  | num_read_bytes = kGainmapStrLength; | 
|  | const char* const aux_type = (const char*)data; | 
|  | if (strcmp(aux_type, kGainmapStr) == 0) { | 
|  | // Note: It is unlikely but it is possible that this gain map | 
|  | // does not belong to the primary item or a tile. Ignore this issue. | 
|  | features->gainmap_property_index = box_index; | 
|  | } else if (box.content_size >= kAlphaStrLength && | 
|  | memcmp(aux_type, kAlphaStr, kGainmapStrLength) == 0) { | 
|  | // The beginning of the aux type matches the alpha aux type string. | 
|  | // Check the end as well. | 
|  | const uint8_t* data2; | 
|  | const uint32_t kEndLength = kAlphaStrLength - kGainmapStrLength; | 
|  | AVIFINFO_CHECK_FOUND( | 
|  | AvifInfoInternalRead(stream, kEndLength, &data2)); | 
|  | num_read_bytes = kAlphaStrLength; | 
|  | if (strcmp((const char*)data2, &kAlphaStr[kGainmapStrLength]) == 0) { | 
|  | // Note: It is unlikely but it is possible that this alpha plane | 
|  | // does not belong to the primary item or a tile. Ignore this issue. | 
|  | features->has_alpha = 1; | 
|  | } | 
|  | } | 
|  | } | 
|  | AVIFINFO_CHECK_FOUND( | 
|  | AvifInfoInternalSkip(stream, box.content_size - num_read_bytes)); | 
|  | } else { | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size)); | 
|  | } | 
|  | ++box_index; | 
|  | num_remaining_bytes -= box.size; | 
|  | } while (num_remaining_bytes > 0); | 
|  | AVIFINFO_RETURN(kNotFound); | 
|  | } | 
|  |  | 
|  | // Parses a 'stream' of an "iprp" box into 'features'. The "ipco" box contain | 
|  | // the properties which are linked to items by the "ipma" box. | 
|  | static AvifInfoInternalStatus ParseIprp(int nesting_level, | 
|  | AvifInfoInternalStream* stream, | 
|  | uint32_t num_remaining_bytes, | 
|  | uint32_t* num_parsed_boxes, | 
|  | AvifInfoInternalFeatures* features) { | 
|  | do { | 
|  | AvifInfoInternalBox box; | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( | 
|  | nesting_level, stream, num_remaining_bytes, num_parsed_boxes, &box)); | 
|  |  | 
|  | if (!memcmp(box.type, "ipco", 4)) { | 
|  | AVIFINFO_CHECK_NOT_FOUND(ParseIpco(nesting_level + 1, stream, | 
|  | box.content_size, num_parsed_boxes, | 
|  | features)); | 
|  | } else if (!memcmp(box.type, "ipma", 4)) { | 
|  | // See ISO/IEC 23008-12:2017(E) 9.3.2 | 
|  | uint32_t num_read_bytes = 4; | 
|  | const uint8_t* data; | 
|  | AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid); | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data)); | 
|  | const uint32_t entry_count = AvifInfoInternalReadBigEndian(data, 4); | 
|  | const uint32_t id_num_bytes = (box.version < 1) ? 2 : 4; | 
|  | const uint32_t index_num_bytes = (box.flags & 1) ? 2 : 1; | 
|  | const uint32_t essential_bit_mask = (box.flags & 1) ? 0x8000 : 0x80; | 
|  |  | 
|  | for (uint32_t entry = 0; entry < entry_count; ++entry) { | 
|  | if (entry >= AVIFINFO_MAX_PROPS || | 
|  | features->num_props >= AVIFINFO_MAX_PROPS) { | 
|  | features->data_was_skipped = 1; | 
|  | break; | 
|  | } | 
|  | num_read_bytes += id_num_bytes + 1; | 
|  | AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid); | 
|  | AVIFINFO_CHECK_FOUND( | 
|  | AvifInfoInternalRead(stream, id_num_bytes + 1, &data)); | 
|  | const uint32_t item_id = | 
|  | AvifInfoInternalReadBigEndian(data, id_num_bytes); | 
|  | const uint32_t association_count = | 
|  | AvifInfoInternalReadBigEndian(data + id_num_bytes, 1); | 
|  |  | 
|  | uint32_t property; | 
|  | for (property = 0; property < association_count; ++property) { | 
|  | if (property >= AVIFINFO_MAX_PROPS || | 
|  | features->num_props >= AVIFINFO_MAX_PROPS) { | 
|  | features->data_was_skipped = 1; | 
|  | break; | 
|  | } | 
|  | num_read_bytes += index_num_bytes; | 
|  | AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid); | 
|  | AVIFINFO_CHECK_FOUND( | 
|  | AvifInfoInternalRead(stream, index_num_bytes, &data)); | 
|  | const uint32_t value = | 
|  | AvifInfoInternalReadBigEndian(data, index_num_bytes); | 
|  | // const int essential = (value & essential_bit_mask);  // Unused. | 
|  | const uint32_t property_index = (value & ~essential_bit_mask); | 
|  | if (property_index <= AVIFINFO_MAX_VALUE && | 
|  | item_id <= AVIFINFO_MAX_VALUE) { | 
|  | features->props[features->num_props].property_index = | 
|  | property_index; | 
|  | features->props[features->num_props].item_id = item_id; | 
|  | ++features->num_props; | 
|  | } else { | 
|  | features->data_was_skipped = 1; | 
|  | } | 
|  | } | 
|  | if (property < association_count) break;  // Do not read garbage. | 
|  | } | 
|  |  | 
|  | // If all features are available now, do not look further. | 
|  | AVIFINFO_CHECK_NOT_FOUND( | 
|  | AvifInfoInternalGetPrimaryItemFeatures(features)); | 
|  |  | 
|  | // Mostly if 'data_was_skipped'. | 
|  | AVIFINFO_CHECK_FOUND( | 
|  | AvifInfoInternalSkip(stream, box.content_size - num_read_bytes)); | 
|  | } else { | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size)); | 
|  | } | 
|  | num_remaining_bytes -= box.size; | 
|  | } while (num_remaining_bytes != 0); | 
|  | AVIFINFO_RETURN(kNotFound); | 
|  | } | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  |  | 
|  | // Parses a 'stream' of an "iref" box into 'features'. | 
|  | // The "dimg" boxes contain links between tiles and their parent items, which | 
|  | // can be used to infer bit depth and number of channels for the primary item | 
|  | // when the latter does not have these properties. | 
|  | static AvifInfoInternalStatus ParseIref(int nesting_level, | 
|  | AvifInfoInternalStream* stream, | 
|  | uint32_t num_remaining_bytes, | 
|  | uint32_t* num_parsed_boxes, | 
|  | AvifInfoInternalFeatures* features) { | 
|  | do { | 
|  | AvifInfoInternalBox box; | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( | 
|  | nesting_level, stream, num_remaining_bytes, num_parsed_boxes, &box)); | 
|  |  | 
|  | if (!memcmp(box.type, "dimg", 4)) { | 
|  | // See ISO/IEC 14496-12:2015(E) 8.11.12.2 | 
|  | const uint32_t num_bytes_per_id = (box.version == 0) ? 2 : 4; | 
|  | uint32_t num_read_bytes = num_bytes_per_id + 2; | 
|  | const uint8_t* data; | 
|  | AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid); | 
|  | AVIFINFO_CHECK_FOUND( | 
|  | AvifInfoInternalRead(stream, num_bytes_per_id + 2, &data)); | 
|  | const uint32_t from_item_id = | 
|  | AvifInfoInternalReadBigEndian(data, num_bytes_per_id); | 
|  | const uint32_t reference_count = | 
|  | AvifInfoInternalReadBigEndian(data + num_bytes_per_id, 2); | 
|  |  | 
|  | for (uint32_t i = 0; i < reference_count; ++i) { | 
|  | if (i >= AVIFINFO_MAX_TILES) { | 
|  | features->data_was_skipped = 1; | 
|  | break; | 
|  | } | 
|  | num_read_bytes += num_bytes_per_id; | 
|  | AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid); | 
|  | AVIFINFO_CHECK_FOUND( | 
|  | AvifInfoInternalRead(stream, num_bytes_per_id, &data)); | 
|  | const uint32_t to_item_id = | 
|  | AvifInfoInternalReadBigEndian(data, num_bytes_per_id); | 
|  | if (from_item_id <= AVIFINFO_MAX_VALUE && | 
|  | to_item_id <= AVIFINFO_MAX_VALUE && | 
|  | features->num_tiles < AVIFINFO_MAX_TILES) { | 
|  | features->tiles[features->num_tiles].tile_item_id = to_item_id; | 
|  | features->tiles[features->num_tiles].parent_item_id = from_item_id; | 
|  | features->tiles[features->num_tiles].dimg_idx = i; | 
|  | ++features->num_tiles; | 
|  | } else { | 
|  | features->data_was_skipped = 1; | 
|  | } | 
|  | } | 
|  |  | 
|  | // If all features are available now, do not look further. | 
|  | AVIFINFO_CHECK_NOT_FOUND( | 
|  | AvifInfoInternalGetPrimaryItemFeatures(features)); | 
|  |  | 
|  | // Mostly if 'data_was_skipped'. | 
|  | AVIFINFO_CHECK_FOUND( | 
|  | AvifInfoInternalSkip(stream, box.content_size - num_read_bytes)); | 
|  | } else { | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size)); | 
|  | } | 
|  | num_remaining_bytes -= box.size; | 
|  | } while (num_remaining_bytes > 0); | 
|  | AVIFINFO_RETURN(kNotFound); | 
|  | } | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  |  | 
|  | // Parses a 'stream' of an "iinf" box into 'features'. | 
|  | static AvifInfoInternalStatus ParseIinf(int nesting_level, | 
|  | AvifInfoInternalStream* stream, | 
|  | uint32_t num_remaining_bytes, | 
|  | uint32_t box_version, | 
|  | uint32_t* num_parsed_boxes, | 
|  | AvifInfoInternalFeatures* features) { | 
|  | features->iinf_parsed = 1; | 
|  |  | 
|  | const uint32_t num_bytes_per_entry_count = box_version == 0 ? 2 : 4; | 
|  | AVIFINFO_CHECK(num_bytes_per_entry_count <= num_remaining_bytes, kInvalid); | 
|  | const uint8_t* data; | 
|  | AVIFINFO_CHECK_FOUND( | 
|  | AvifInfoInternalRead(stream, num_bytes_per_entry_count, &data)); | 
|  | num_remaining_bytes -= num_bytes_per_entry_count; | 
|  | const uint32_t entry_count = | 
|  | AvifInfoInternalReadBigEndian(data, num_bytes_per_entry_count); | 
|  |  | 
|  | for (int i = 0; i < entry_count; ++i) { | 
|  | AvifInfoInternalBox box; | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( | 
|  | nesting_level, stream, num_remaining_bytes, num_parsed_boxes, &box)); | 
|  |  | 
|  | if (!memcmp(box.type, "infe", 4)) { | 
|  | // See ISO/IEC 14496-12:2015(E) 8.11.6.2 | 
|  | uint32_t num_read_bytes = 0; | 
|  |  | 
|  | const uint32_t num_bytes_per_id = (box.version == 2) ? 2 : 4; | 
|  | const uint8_t* data; | 
|  | // item_ID (16 or 32) + item_protection_index (16) + item_type (32). | 
|  | AVIFINFO_CHECK((num_bytes_per_id + 6) <= num_remaining_bytes, kInvalid); | 
|  | AVIFINFO_CHECK_FOUND( | 
|  | AvifInfoInternalRead(stream, num_bytes_per_id, &data)); | 
|  | num_read_bytes += num_bytes_per_id; | 
|  | const uint32_t item_id = | 
|  | AvifInfoInternalReadBigEndian(data, num_bytes_per_id); | 
|  |  | 
|  | // Skip item_protection_index | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, 2)); | 
|  | num_read_bytes += 2; | 
|  |  | 
|  | const uint8_t* item_type; | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &item_type)); | 
|  | num_read_bytes += 4; | 
|  | if (!memcmp(item_type, "tmap", 4)) { | 
|  | // Tone Mapped Image: indicates the presence of a gain map. | 
|  | features->tone_mapped_item_id = item_id; | 
|  | } | 
|  |  | 
|  | AVIFINFO_CHECK_FOUND( | 
|  | AvifInfoInternalSkip(stream, box.content_size - num_read_bytes)); | 
|  | } else { | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size)); | 
|  | } | 
|  |  | 
|  | num_remaining_bytes -= box.size; | 
|  | if (num_remaining_bytes <= 0) break; | 
|  | } | 
|  | AVIFINFO_RETURN(kNotFound); | 
|  | } | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  |  | 
|  | // Parses a 'stream' of a "meta" box. It looks for the primary item ID in the | 
|  | // "pitm" box and recurses into other boxes to find its 'features'. | 
|  | static AvifInfoInternalStatus ParseMeta(int nesting_level, | 
|  | AvifInfoInternalStream* stream, | 
|  | uint32_t num_remaining_bytes, | 
|  | uint32_t* num_parsed_boxes, | 
|  | AvifInfoInternalFeatures* features) { | 
|  | do { | 
|  | AvifInfoInternalBox box; | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( | 
|  | nesting_level, stream, num_remaining_bytes, num_parsed_boxes, &box)); | 
|  | if (!memcmp(box.type, "pitm", 4)) { | 
|  | // See ISO/IEC 14496-12:2015(E) 8.11.4.2 | 
|  | const uint32_t num_bytes_per_id = (box.version == 0) ? 2 : 4; | 
|  | const uint64_t primary_item_id_location = stream->num_read_bytes; | 
|  | const uint8_t* data; | 
|  | AVIFINFO_CHECK(num_bytes_per_id <= num_remaining_bytes, kInvalid); | 
|  | AVIFINFO_CHECK_FOUND( | 
|  | AvifInfoInternalRead(stream, num_bytes_per_id, &data)); | 
|  | const uint32_t primary_item_id = | 
|  | AvifInfoInternalReadBigEndian(data, num_bytes_per_id); | 
|  | AVIFINFO_CHECK(primary_item_id <= AVIFINFO_MAX_VALUE, kAborted); | 
|  | features->has_primary_item = 1; | 
|  | features->primary_item_id = primary_item_id; | 
|  | features->primary_item_features.primary_item_id_location = | 
|  | primary_item_id_location; | 
|  | features->primary_item_features.primary_item_id_bytes = num_bytes_per_id; | 
|  | AVIFINFO_CHECK_FOUND( | 
|  | AvifInfoInternalSkip(stream, box.content_size - num_bytes_per_id)); | 
|  | } else if (!memcmp(box.type, "iprp", 4)) { | 
|  | AVIFINFO_CHECK_NOT_FOUND(ParseIprp(nesting_level + 1, stream, | 
|  | box.content_size, num_parsed_boxes, | 
|  | features)); | 
|  | } else if (!memcmp(box.type, "iref", 4)) { | 
|  | AVIFINFO_CHECK_NOT_FOUND(ParseIref(nesting_level + 1, stream, | 
|  | box.content_size, num_parsed_boxes, | 
|  | features)); | 
|  | } else if (!memcmp(box.type, "iinf", 4)) { | 
|  | AVIFINFO_CHECK_NOT_FOUND(ParseIinf(nesting_level + 1, stream, | 
|  | box.content_size, box.version, | 
|  | num_parsed_boxes, features)); | 
|  | } else { | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size)); | 
|  | } | 
|  | num_remaining_bytes -= box.size; | 
|  | } while (num_remaining_bytes != 0); | 
|  | // According to ISO/IEC 14496-12:2012(E) 8.11.1.1 there is at most one "meta". | 
|  | AVIFINFO_RETURN(features->data_was_skipped ? kAborted : kInvalid); | 
|  | } | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  |  | 
|  | // Parses a file 'stream'. The file type is checked through the "ftyp" box. | 
|  | static AvifInfoInternalStatus ParseFtyp(AvifInfoInternalStream* stream) { | 
|  | AvifInfoInternalBox box; | 
|  | uint32_t num_parsed_boxes = 0; | 
|  | const int nesting_level = 0; | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( | 
|  | nesting_level, stream, AVIFINFO_MAX_SIZE, &num_parsed_boxes, &box)); | 
|  | AVIFINFO_CHECK(!memcmp(box.type, "ftyp", 4), kInvalid); | 
|  | // Iterate over brands. See ISO/IEC 14496-12:2012(E) 4.3.1 | 
|  | AVIFINFO_CHECK(box.content_size >= 8, kInvalid);  // major_brand,minor_version | 
|  | for (uint32_t i = 0; i + 4 <= box.content_size; i += 4) { | 
|  | const uint8_t* data; | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data)); | 
|  | if (i == 4) continue;  // Skip minor_version. | 
|  | if (!memcmp(data, "avif", 4) || !memcmp(data, "avis", 4)) { | 
|  | AVIFINFO_CHECK_FOUND( | 
|  | AvifInfoInternalSkip(stream, box.content_size - (i + 4))); | 
|  | return kFound; | 
|  | } | 
|  | AVIFINFO_CHECK(i <= 32 * 4, kAborted);  // Be reasonable. | 
|  | } | 
|  | AVIFINFO_RETURN(kInvalid);  // No AVIF brand no good. | 
|  | } | 
|  |  | 
|  | // Parses a file 'stream'. 'features' are extracted from the "meta" box. | 
|  | static AvifInfoInternalStatus ParseFile(AvifInfoInternalStream* stream, | 
|  | uint32_t* num_parsed_boxes, | 
|  | AvifInfoInternalFeatures* features) { | 
|  | while (1) { | 
|  | AvifInfoInternalBox box; | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox( | 
|  | /*nesting_level=*/0, stream, AVIFINFO_MAX_SIZE, num_parsed_boxes, | 
|  | &box)); | 
|  | if (!memcmp(box.type, "meta", 4)) { | 
|  | return ParseMeta(/*nesting_level=*/1, stream, box.content_size, | 
|  | num_parsed_boxes, features); | 
|  | } else { | 
|  | AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size)); | 
|  | } | 
|  | } | 
|  | AVIFINFO_RETURN(kInvalid);  // No "meta" no good. | 
|  | } | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  | // Helpers for converting the fixed-size input public API to the streamed one. | 
|  |  | 
|  | typedef struct { | 
|  | const uint8_t* data; | 
|  | size_t data_size; | 
|  | } AvifInfoInternalForward; | 
|  |  | 
|  | static const uint8_t* AvifInfoInternalForwardRead(void* stream, | 
|  | size_t num_bytes) { | 
|  | AvifInfoInternalForward* forward = (AvifInfoInternalForward*)stream; | 
|  | if (num_bytes > forward->data_size) return NULL; | 
|  | const uint8_t* data = forward->data; | 
|  | forward->data += num_bytes; | 
|  | forward->data_size -= num_bytes; | 
|  | return data; | 
|  | } | 
|  |  | 
|  | static void AvifInfoInternalForwardSkip(void* stream, size_t num_bytes) { | 
|  | AvifInfoInternalForward* forward = (AvifInfoInternalForward*)stream; | 
|  | if (num_bytes > forward->data_size) num_bytes = forward->data_size; | 
|  | forward->data += num_bytes; | 
|  | forward->data_size -= num_bytes; | 
|  | } | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  | // Fixed-size input public API | 
|  |  | 
|  | AvifInfoStatus AvifInfoIdentify(const uint8_t* data, size_t data_size) { | 
|  | AvifInfoInternalForward stream; | 
|  | stream.data = data; | 
|  | stream.data_size = data_size; | 
|  | // Forward null 'data' as a null 'stream' to handle it the same way. | 
|  | return AvifInfoIdentifyStream( | 
|  | (void*)&stream, (data == NULL) ? NULL : AvifInfoInternalForwardRead, | 
|  | AvifInfoInternalForwardSkip); | 
|  | } | 
|  |  | 
|  | AvifInfoStatus AvifInfoGetFeatures(const uint8_t* data, size_t data_size, | 
|  | AvifInfoFeatures* features) { | 
|  | const AvifInfoStatus status = AvifInfoIdentify(data, data_size); | 
|  | if (status != kAvifInfoOk) { | 
|  | if (features != NULL) memset(features, 0, sizeof(*features)); | 
|  | return status; | 
|  | } | 
|  | AvifInfoInternalForward stream; | 
|  | stream.data = data; | 
|  | stream.data_size = data_size; | 
|  | return AvifInfoGetFeaturesStream( | 
|  | (void*)&stream, (data == NULL) ? NULL : AvifInfoInternalForwardRead, | 
|  | AvifInfoInternalForwardSkip, features); | 
|  | } | 
|  |  | 
|  | //------------------------------------------------------------------------------ | 
|  | // Streamed input API | 
|  |  | 
|  | AvifInfoStatus AvifInfoIdentifyStream(void* stream, read_stream_t read, | 
|  | skip_stream_t skip) { | 
|  | if (read == NULL) return kAvifInfoNotEnoughData; | 
|  |  | 
|  | AvifInfoInternalStream internal_stream; | 
|  | internal_stream.stream = stream; | 
|  | internal_stream.read = read; | 
|  | internal_stream.skip = skip;  // Fallbacks to 'read' if null. | 
|  | internal_stream.num_read_bytes = 0; | 
|  | return AvifInfoInternalConvertStatus(ParseFtyp(&internal_stream)); | 
|  | } | 
|  |  | 
|  | AvifInfoStatus AvifInfoGetFeaturesStream(void* stream, read_stream_t read, | 
|  | skip_stream_t skip, | 
|  | AvifInfoFeatures* features) { | 
|  | if (features != NULL) memset(features, 0, sizeof(*features)); | 
|  | if (read == NULL) return kAvifInfoNotEnoughData; | 
|  |  | 
|  | AvifInfoInternalStream internal_stream; | 
|  | internal_stream.stream = stream; | 
|  | internal_stream.read = read; | 
|  | internal_stream.skip = skip;  // Fallbacks to 'read' if null. | 
|  | internal_stream.num_read_bytes = 0; | 
|  | uint32_t num_parsed_boxes = 0; | 
|  | AvifInfoInternalFeatures internal_features; | 
|  | memset(&internal_features, AVIFINFO_UNDEFINED, sizeof(internal_features)); | 
|  |  | 
|  | // Go through all relevant boxes sequentially. | 
|  | const AvifInfoInternalStatus status = | 
|  | ParseFile(&internal_stream, &num_parsed_boxes, &internal_features); | 
|  | if (status == kFound && features != NULL) { | 
|  | memcpy(features, &internal_features.primary_item_features, | 
|  | sizeof(*features)); | 
|  | } | 
|  | return AvifInfoInternalConvertStatus(status); | 
|  | } |