Coverage Report

Created: 2026-04-01 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/ext/standard/libavifinfo/avifinfo.c
Line
Count
Source
1
// Copyright (c) 2021, Alliance for Open Media. All rights reserved
2
//
3
// This source code is subject to the terms of the BSD 2 Clause License and
4
// the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
5
// was not distributed with this source code in the LICENSE file, you can
6
// obtain it at www.aomedia.org/license/software. If the Alliance for Open
7
// Media Patent License 1.0 was not distributed with this source code in the
8
// PATENTS file, you can obtain it at www.aomedia.org/license/patent.
9
10
#include "avifinfo.h"
11
12
#include <stdint.h>
13
#include <stdio.h>
14
#include <string.h>
15
16
//------------------------------------------------------------------------------
17
18
// Status returned when reading the content of a box (or file).
19
typedef enum {
20
  kFound,     // Input correctly parsed and information retrieved.
21
  kNotFound,  // Input correctly parsed but information is missing or elsewhere.
22
  kTruncated,  // Input correctly parsed until missing bytes to continue.
23
  kAborted,  // Input correctly parsed until stopped to avoid timeout or crash.
24
  kInvalid,  // Input incorrectly parsed.
25
} AvifInfoInternalStatus;
26
27
20
static AvifInfoStatus AvifInfoInternalConvertStatus(AvifInfoInternalStatus s) {
28
20
  return (s == kFound)                         ? kAvifInfoOk
29
20
         : (s == kNotFound || s == kTruncated) ? kAvifInfoNotEnoughData
30
20
         : (s == kAborted)                     ? kAvifInfoTooComplex
31
16
                                               : kAvifInfoInvalidFile;
32
20
}
33
34
// uint32_t is used everywhere in this file. It is unlikely to be insufficient
35
// to parse AVIF headers.
36
#define AVIFINFO_MAX_SIZE UINT32_MAX
37
// Be reasonable. Avoid timeouts and out-of-memory.
38
#define AVIFINFO_MAX_NUM_BOXES 4096
39
// AvifInfoInternalFeatures uses uint8_t to store values.
40
0
#define AVIFINFO_MAX_VALUE UINT8_MAX
41
// Maximum number of stored associations. Past that, they are skipped.
42
0
#define AVIFINFO_MAX_TILES 16
43
0
#define AVIFINFO_MAX_PROPS 32
44
0
#define AVIFINFO_MAX_FEATURES 8
45
0
#define AVIFINFO_UNDEFINED 0
46
47
// Reads an unsigned integer from 'input' with most significant bits first.
48
// 'input' must be at least 'num_bytes'-long.
49
static uint32_t AvifInfoInternalReadBigEndian(const uint8_t* input,
50
19
                                              uint32_t num_bytes) {
51
19
  uint32_t value = 0;
52
95
  for (uint32_t i = 0; i < num_bytes; ++i) {
53
76
    value = (value << 8) | input[i];
54
76
  }
55
19
  return value;
56
19
}
57
58
//------------------------------------------------------------------------------
59
// Convenience macros.
60
61
#if defined(AVIFINFO_LOG_ERROR)  // Toggle to log encountered issues.
62
static void AvifInfoInternalLogError(const char* file, int line,
63
                                     AvifInfoInternalStatus status) {
64
  const char* kStr[] = {"Found", "NotFound", "Truncated", "Invalid", "Aborted"};
65
  fprintf(stderr, "  %s:%d: %s\n", file, line, kStr[status]);
66
  // Set a breakpoint here to catch the first detected issue.
67
}
68
#define AVIFINFO_RETURN(check_status)                               \
69
  do {                                                              \
70
    const AvifInfoInternalStatus status_checked = (check_status);   \
71
    if (status_checked != kFound && status_checked != kNotFound) {  \
72
      AvifInfoInternalLogError(__FILE__, __LINE__, status_checked); \
73
    }                                                               \
74
    return status_checked;                                          \
75
  } while (0)
76
#else
77
#define AVIFINFO_RETURN(check_status) \
78
26
  do {                                \
79
26
    return (check_status);            \
80
26
  } while (0)
81
#endif
82
83
#define AVIFINFO_CHECK(check_condition, check_status)      \
84
521
  do {                                                     \
85
521
    if (!(check_condition)) AVIFINFO_RETURN(check_status); \
86
521
  } while (0)
87
#define AVIFINFO_CHECK_STATUS_IS(check_status, expected_status)            \
88
159
  do {                                                                     \
89
159
    const AvifInfoInternalStatus status_returned = (check_status);         \
90
159
    AVIFINFO_CHECK(status_returned == (expected_status), status_returned); \
91
159
  } while (0)
92
#define AVIFINFO_CHECK_FOUND(check_status) \
93
159
  AVIFINFO_CHECK_STATUS_IS((check_status), kFound)
94
#define AVIFINFO_CHECK_NOT_FOUND(check_status) \
95
0
  AVIFINFO_CHECK_STATUS_IS((check_status), kNotFound)
96
97
#if defined(AVIFINFO_ENABLE_DEBUG_LOG)
98
#define AVIF_DEBUG_LOG(...) printf(__VA_ARGS__)
99
#else
100
#define AVIF_DEBUG_LOG(...)
101
#endif
102
103
//------------------------------------------------------------------------------
104
// Streamed input struct and helper functions.
105
106
typedef struct {
107
  void* stream;             // User-defined data.
108
  read_stream_t read;       // Used to fetch more bytes from the 'stream'.
109
  skip_stream_t skip;       // Used to advance the position in the 'stream'.
110
                            // Fallback to 'read' if 'skip' is null.
111
  uint64_t num_read_bytes;  // Number of bytes read or skipped.
112
} AvifInfoInternalStream;
113
114
// Reads 'num_bytes' from the 'stream'. They are available at '*data'.
115
// 'num_bytes' must be greater than zero.
116
static AvifInfoInternalStatus AvifInfoInternalRead(
117
139
    AvifInfoInternalStream* stream, uint32_t num_bytes, const uint8_t** data) {
118
139
  *data = stream->read(stream->stream, num_bytes);
119
139
  AVIFINFO_CHECK(*data != NULL, kTruncated);
120
135
  stream->num_read_bytes += num_bytes;
121
135
  return kFound;
122
139
}
123
124
// Skips 'num_bytes' from the 'stream'. 'num_bytes' can be zero.
125
static AvifInfoInternalStatus AvifInfoInternalSkip(
126
0
    AvifInfoInternalStream* stream, uint32_t num_bytes) {
127
  // Avoid a call to the user-defined function for nothing.
128
0
  if (num_bytes > 0) {
129
0
    if (stream->skip == NULL) {
130
0
      const uint8_t* unused;
131
0
      while (num_bytes > AVIFINFO_MAX_NUM_READ_BYTES) {
132
0
        AVIFINFO_CHECK_FOUND(
133
0
            AvifInfoInternalRead(stream, AVIFINFO_MAX_NUM_READ_BYTES, &unused));
134
0
        num_bytes -= AVIFINFO_MAX_NUM_READ_BYTES;
135
0
      }
136
0
      return AvifInfoInternalRead(stream, num_bytes, &unused);
137
0
    }
138
0
    stream->skip(stream->stream, num_bytes);
139
0
    stream->num_read_bytes += num_bytes;
140
0
  }
141
0
  return kFound;
142
0
}
143
144
//------------------------------------------------------------------------------
145
// Features are parsed into temporary property associations.
146
147
typedef struct {
148
  uint8_t tile_item_id;
149
  uint8_t parent_item_id;
150
  uint8_t dimg_idx;      // Index of this association in the dimg box (0-based).
151
} AvifInfoInternalTile;  // Tile item id <-> parent item id associations.
152
153
typedef struct {
154
  uint8_t property_index;
155
  uint8_t item_id;
156
} AvifInfoInternalProp;  // Property index <-> item id associations.
157
158
typedef struct {
159
  uint8_t property_index;
160
  uint32_t width, height;
161
} AvifInfoInternalDimProp;  // Property <-> features associations.
162
163
typedef struct {
164
  uint8_t property_index;
165
  uint8_t bit_depth, num_channels;
166
} AvifInfoInternalChanProp;  // Property <-> features associations.
167
168
typedef struct {
169
  uint8_t has_primary_item;  // True if "pitm" was parsed.
170
  uint8_t has_alpha;    // True if an alpha "auxC" was parsed.
171
  // Index of the gain map auxC property.
172
  uint8_t gainmap_property_index;
173
  uint8_t primary_item_id;
174
  AvifInfoFeatures primary_item_features;  // Deduced from the data below.
175
  uint8_t data_was_skipped;  // True if some loops/indices were skipped.
176
  uint8_t tone_mapped_item_id;  // Id of the "tmap" box, > 0 if present.
177
  uint8_t iinf_parsed;  // True if the "iinf" (item info) box was parsed.
178
  uint8_t iref_parsed;  // True if the "iref" (item reference) box was parsed.
179
180
  uint8_t num_tiles;
181
  AvifInfoInternalTile tiles[AVIFINFO_MAX_TILES];
182
  uint8_t num_props;
183
  AvifInfoInternalProp props[AVIFINFO_MAX_PROPS];
184
  uint8_t num_dim_props;
185
  AvifInfoInternalDimProp dim_props[AVIFINFO_MAX_FEATURES];
186
  uint8_t num_chan_props;
187
  AvifInfoInternalChanProp chan_props[AVIFINFO_MAX_FEATURES];
188
} AvifInfoInternalFeatures;
189
190
// Generates the features of a given 'target_item_id' from internal features.
191
static AvifInfoInternalStatus AvifInfoInternalGetItemFeatures(
192
0
    AvifInfoInternalFeatures* f, uint32_t target_item_id, uint32_t tile_depth) {
193
0
  for (uint32_t prop_item = 0; prop_item < f->num_props; ++prop_item) {
194
0
    if (f->props[prop_item].item_id != target_item_id) continue;
195
0
    const uint32_t property_index = f->props[prop_item].property_index;
196
197
    // Retrieve the width and height of the primary item if not already done.
198
0
    if (target_item_id == f->primary_item_id &&
199
0
        (f->primary_item_features.width == AVIFINFO_UNDEFINED ||
200
0
         f->primary_item_features.height == AVIFINFO_UNDEFINED)) {
201
0
      for (uint32_t i = 0; i < f->num_dim_props; ++i) {
202
0
        if (f->dim_props[i].property_index != property_index) continue;
203
0
        f->primary_item_features.width = f->dim_props[i].width;
204
0
        f->primary_item_features.height = f->dim_props[i].height;
205
0
        if (f->primary_item_features.bit_depth != AVIFINFO_UNDEFINED &&
206
0
            f->primary_item_features.num_channels != AVIFINFO_UNDEFINED) {
207
0
          return kFound;
208
0
        }
209
0
        break;
210
0
      }
211
0
    }
212
    // Retrieve the bit depth and number of channels of the target item if not
213
    // already done.
214
0
    if (f->primary_item_features.bit_depth == AVIFINFO_UNDEFINED ||
215
0
        f->primary_item_features.num_channels == AVIFINFO_UNDEFINED) {
216
0
      for (uint32_t i = 0; i < f->num_chan_props; ++i) {
217
0
        if (f->chan_props[i].property_index != property_index) continue;
218
0
        f->primary_item_features.bit_depth = f->chan_props[i].bit_depth;
219
0
        f->primary_item_features.num_channels = f->chan_props[i].num_channels;
220
0
        if (f->primary_item_features.width != AVIFINFO_UNDEFINED &&
221
0
            f->primary_item_features.height != AVIFINFO_UNDEFINED) {
222
0
          return kFound;
223
0
        }
224
0
        break;
225
0
      }
226
0
    }
227
0
  }
228
229
  // Check for the bit_depth and num_channels in a tile if not yet found.
230
0
  for (uint32_t tile = 0; tile < f->num_tiles && tile_depth < 3; ++tile) {
231
0
    if (f->tiles[tile].parent_item_id != target_item_id) continue;
232
0
    AVIFINFO_CHECK_NOT_FOUND(AvifInfoInternalGetItemFeatures(
233
0
        f, f->tiles[tile].tile_item_id, tile_depth + 1));
234
0
  }
235
0
  AVIFINFO_RETURN(kNotFound);
236
0
}
237
238
// Generates the 'f->primary_item_features' from the AvifInfoInternalFeatures.
239
// Returns kNotFound if there is not enough information.
240
static AvifInfoInternalStatus AvifInfoInternalGetPrimaryItemFeatures(
241
0
    AvifInfoInternalFeatures* f) {
242
  // Nothing to do without the primary item ID.
243
0
  AVIFINFO_CHECK(f->has_primary_item, kNotFound);
244
  // Early exit.
245
0
  AVIFINFO_CHECK(f->num_dim_props > 0 && f->num_chan_props, kNotFound);
246
247
  // Look for a gain map.
248
  // HEIF scheme: gain map is a hidden input of a derived item.
249
0
  if (f->tone_mapped_item_id) {
250
0
    for (uint32_t tile = 0; tile < f->num_tiles; ++tile) {
251
0
      if (f->tiles[tile].parent_item_id == f->tone_mapped_item_id &&
252
0
          f->tiles[tile].dimg_idx == 1) {
253
0
        f->primary_item_features.has_gainmap = 1;
254
0
        f->primary_item_features.gainmap_item_id = f->tiles[tile].tile_item_id;
255
0
        break;
256
0
      }
257
0
    }
258
0
  }
259
  // Adobe scheme: gain map is an auxiliary item.
260
0
  if (!f->primary_item_features.has_gainmap && f->gainmap_property_index > 0) {
261
0
    for (uint32_t prop_item = 0; prop_item < f->num_props; ++prop_item) {
262
0
      if (f->props[prop_item].property_index == f->gainmap_property_index) {
263
0
        f->primary_item_features.has_gainmap = 1;
264
0
        f->primary_item_features.gainmap_item_id = f->props[prop_item].item_id;
265
0
        break;
266
0
      }
267
0
    }
268
0
  }
269
  // If the gain map has not been found but we haven't read all the relevant
270
  // metadata, we might still find one later and cannot stop now.
271
0
  if (!f->primary_item_features.has_gainmap &&
272
0
      (!f->iinf_parsed || (f->tone_mapped_item_id && !f->iref_parsed))) {
273
0
    return kNotFound;
274
0
  }
275
276
0
  AVIFINFO_CHECK_FOUND(
277
0
      AvifInfoInternalGetItemFeatures(f, f->primary_item_id, /*tile_depth=*/0));
278
279
  // "auxC" is parsed before the "ipma" properties so it is known now, if any.
280
0
  if (f->has_alpha) ++f->primary_item_features.num_channels;
281
0
  return kFound;
282
0
}
283
284
//------------------------------------------------------------------------------
285
// Box header parsing and various size checks.
286
287
typedef struct {
288
  uint32_t size;          // In bytes.
289
  uint8_t type[4];        // Four characters.
290
  uint32_t version;       // 0 or actual version if this is a full box.
291
  uint32_t flags;         // 0 or actual value if this is a full box.
292
  uint32_t content_size;  // 'size' minus the header size.
293
} AvifInfoInternalBox;
294
295
// Reads the header of a 'box' starting at the beginning of a 'stream'.
296
// 'num_remaining_bytes' is the remaining size of the container of the 'box'
297
// (either the file size itself or the content size of the parent of the 'box').
298
static AvifInfoInternalStatus AvifInfoInternalParseBox(
299
    int nesting_level, AvifInfoInternalStream* stream,
300
    uint32_t num_remaining_bytes, uint32_t* num_parsed_boxes,
301
20
    AvifInfoInternalBox* box) {
302
20
  const uint8_t* data;
303
  // See ISO/IEC 14496-12:2012(E) 4.2
304
20
  uint32_t box_header_size = 8;  // box 32b size + 32b type (at least)
305
20
  AVIFINFO_CHECK(box_header_size <= num_remaining_bytes, kInvalid);
306
20
  AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data));
307
19
  box->size = AvifInfoInternalReadBigEndian(data, sizeof(uint32_t));
308
19
  memcpy(box->type, data + 4, 4);
309
  // 'box->size==1' means 64-bit size should be read after the box type.
310
  // 'box->size==0' means this box extends to all remaining bytes.
311
19
  if (box->size == 1) {
312
0
    box_header_size += 8;
313
0
    AVIFINFO_CHECK(box_header_size <= num_remaining_bytes, kInvalid);
314
0
    AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data));
315
    // Stop the parsing if any box has a size greater than 4GB.
316
0
    AVIFINFO_CHECK(AvifInfoInternalReadBigEndian(data, sizeof(uint32_t)) == 0,
317
0
                   kAborted);
318
    // Read the 32 least-significant bits.
319
0
    box->size = AvifInfoInternalReadBigEndian(data + 4, sizeof(uint32_t));
320
19
  } else if (box->size == 0) {
321
    // ISO/IEC 14496-12 4.2.2:
322
    //   if size is 0, then this box shall be in a top-level box
323
    //   (i.e. not contained in another box)
324
0
    AVIFINFO_CHECK(nesting_level == 0, kInvalid);
325
0
    box->size = num_remaining_bytes;
326
0
  }
327
19
  AVIFINFO_CHECK(box->size >= box_header_size, kInvalid);
328
18
  AVIFINFO_CHECK(box->size <= num_remaining_bytes, kInvalid);
329
330
  // 16 bytes of usertype should be read here if the box type is 'uuid'.
331
  // 'uuid' boxes are skipped so usertype is part of the skipped body.
332
333
18
  const int has_fullbox_header =
334
18
      !memcmp(box->type, "meta", 4) || !memcmp(box->type, "pitm", 4) ||
335
18
      !memcmp(box->type, "ipma", 4) || !memcmp(box->type, "ispe", 4) ||
336
18
      !memcmp(box->type, "pixi", 4) || !memcmp(box->type, "iref", 4) ||
337
18
      !memcmp(box->type, "auxC", 4) || !memcmp(box->type, "iinf", 4) ||
338
18
      !memcmp(box->type, "infe", 4);
339
18
  if (has_fullbox_header) box_header_size += 4;
340
18
  AVIFINFO_CHECK(box->size >= box_header_size, kInvalid);
341
18
  box->content_size = box->size - box_header_size;
342
  // AvifInfoGetFeaturesStream() can be called on a full stream or on a stream
343
  // where the 'ftyp' box was already read. Do not count 'ftyp' boxes towards
344
  // AVIFINFO_MAX_NUM_BOXES, so that this function returns the same status in
345
  // both situations (because of the AVIFINFO_MAX_NUM_BOXES check that would
346
  // compare a different box count otherwise). This is fine because top-level
347
  // 'ftyp' boxes are just skipped anyway.
348
18
  if (nesting_level != 0 || memcmp(box->type, "ftyp", 4)) {
349
    // Avoid timeouts. The maximum number of parsed boxes is arbitrary.
350
14
    ++*num_parsed_boxes;
351
14
    AVIFINFO_CHECK(*num_parsed_boxes < AVIFINFO_MAX_NUM_BOXES, kAborted);
352
14
  }
353
354
18
  box->version = 0;
355
18
  box->flags = 0;
356
18
  if (has_fullbox_header) {
357
0
    AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data));
358
0
    box->version = AvifInfoInternalReadBigEndian(data, 1);
359
0
    box->flags = AvifInfoInternalReadBigEndian(data + 1, 3);
360
    // See AV1 Image File Format (AVIF) 8.1
361
    // at https://aomediacodec.github.io/av1-avif/#avif-boxes (available when
362
    // https://github.com/AOMediaCodec/av1-avif/pull/170 is merged).
363
0
    const uint32_t is_parsable =
364
0
        (!memcmp(box->type, "meta", 4) && box->version <= 0) ||
365
0
        (!memcmp(box->type, "pitm", 4) && box->version <= 1) ||
366
0
        (!memcmp(box->type, "ipma", 4) && box->version <= 1) ||
367
0
        (!memcmp(box->type, "ispe", 4) && box->version <= 0) ||
368
0
        (!memcmp(box->type, "pixi", 4) && box->version <= 0) ||
369
0
        (!memcmp(box->type, "iref", 4) && box->version <= 1) ||
370
0
        (!memcmp(box->type, "auxC", 4) && box->version <= 0) ||
371
0
        (!memcmp(box->type, "iinf", 4) && box->version <= 1) ||
372
0
        (!memcmp(box->type, "infe", 4) && box->version >= 2 &&
373
0
         box->version <= 3);
374
    // Instead of considering this file as invalid, skip unparsable boxes.
375
0
    if (!is_parsable) memcpy(box->type, "skip", 4);  // FreeSpaceBox
376
0
  }
377
18
  AVIF_DEBUG_LOG("%*c", nesting_level * 2, ' ');
378
18
  AVIF_DEBUG_LOG("Box type %.4s size %d\n", box->type, box->size);
379
18
  return kFound;
380
18
}
381
382
//------------------------------------------------------------------------------
383
384
// Parses a 'stream' of an "ipco" box into 'features'.
385
// "ispe" is used for width and height, "pixi" and "av1C" are used for bit depth
386
// and number of channels, and "auxC" is used for alpha.
387
static AvifInfoInternalStatus ParseIpco(int nesting_level,
388
                                        AvifInfoInternalStream* stream,
389
                                        uint32_t num_remaining_bytes,
390
                                        uint32_t* num_parsed_boxes,
391
0
                                        AvifInfoInternalFeatures* features) {
392
0
  uint32_t box_index = 1;  // 1-based index. Used for iterating over properties.
393
0
  do {
394
0
    AvifInfoInternalBox box;
395
0
    AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(
396
0
        nesting_level, stream, num_remaining_bytes, num_parsed_boxes, &box));
397
398
0
    if (!memcmp(box.type, "ispe", 4)) {
399
      // See ISO/IEC 23008-12:2017(E) 6.5.3.2
400
0
      const uint8_t* data;
401
0
      AVIFINFO_CHECK(box.content_size >= 8, kInvalid);
402
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data));
403
0
      const uint32_t width = AvifInfoInternalReadBigEndian(data + 0, 4);
404
0
      const uint32_t height = AvifInfoInternalReadBigEndian(data + 4, 4);
405
0
      AVIFINFO_CHECK(width != 0 && height != 0, kInvalid);
406
0
      if (features->num_dim_props < AVIFINFO_MAX_FEATURES &&
407
0
          box_index <= AVIFINFO_MAX_VALUE) {
408
0
        features->dim_props[features->num_dim_props].property_index = box_index;
409
0
        features->dim_props[features->num_dim_props].width = width;
410
0
        features->dim_props[features->num_dim_props].height = height;
411
0
        ++features->num_dim_props;
412
0
      } else {
413
0
        features->data_was_skipped = 1;
414
0
      }
415
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size - 8));
416
0
    } else if (!memcmp(box.type, "pixi", 4)) {
417
      // See ISO/IEC 23008-12:2017(E) 6.5.6.2
418
0
      const uint8_t* data;
419
0
      AVIFINFO_CHECK(box.content_size >= 1, kInvalid);
420
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data));
421
0
      const uint32_t num_channels = AvifInfoInternalReadBigEndian(data + 0, 1);
422
0
      AVIFINFO_CHECK(num_channels >= 1, kInvalid);
423
0
      AVIFINFO_CHECK(box.content_size >= 1 + num_channels, kInvalid);
424
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data));
425
0
      const uint32_t bit_depth = AvifInfoInternalReadBigEndian(data, 1);
426
0
      AVIFINFO_CHECK(bit_depth >= 1, kInvalid);
427
0
      for (uint32_t i = 1; i < num_channels; ++i) {
428
0
        AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data));
429
        // Bit depth should be the same for all channels.
430
0
        AVIFINFO_CHECK(AvifInfoInternalReadBigEndian(data, 1) == bit_depth,
431
0
                       kInvalid);
432
0
        AVIFINFO_CHECK(i <= 32, kAborted);  // Be reasonable.
433
0
      }
434
0
      if (features->num_chan_props < AVIFINFO_MAX_FEATURES &&
435
0
          box_index <= AVIFINFO_MAX_VALUE && bit_depth <= AVIFINFO_MAX_VALUE &&
436
0
          num_channels <= AVIFINFO_MAX_VALUE) {
437
0
        features->chan_props[features->num_chan_props].property_index =
438
0
            box_index;
439
0
        features->chan_props[features->num_chan_props].bit_depth = bit_depth;
440
0
        features->chan_props[features->num_chan_props].num_channels =
441
0
            num_channels;
442
0
        ++features->num_chan_props;
443
0
      } else {
444
0
        features->data_was_skipped = 1;
445
0
      }
446
0
      AVIFINFO_CHECK_FOUND(
447
0
          AvifInfoInternalSkip(stream, box.content_size - (1 + num_channels)));
448
0
    } else if (!memcmp(box.type, "av1C", 4)) {
449
      // See AV1 Codec ISO Media File Format Binding 2.3.1
450
      // at https://aomediacodec.github.io/av1-isobmff/#av1c
451
      // Only parse the necessary third byte. Assume that the others are valid.
452
0
      const uint8_t* data;
453
0
      AVIFINFO_CHECK(box.content_size >= 3, kInvalid);
454
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 3, &data));
455
0
      const int high_bitdepth = (data[2] & 0x40) != 0;
456
0
      const int twelve_bit = (data[2] & 0x20) != 0;
457
0
      const int monochrome = (data[2] & 0x10) != 0;
458
0
      if (twelve_bit) {
459
0
        AVIFINFO_CHECK(high_bitdepth, kInvalid);
460
0
      }
461
0
      if (features->num_chan_props < AVIFINFO_MAX_FEATURES &&
462
0
          box_index <= AVIFINFO_MAX_VALUE) {
463
0
        features->chan_props[features->num_chan_props].property_index =
464
0
            box_index;
465
0
        features->chan_props[features->num_chan_props].bit_depth =
466
0
            high_bitdepth ? twelve_bit ? 12 : 10 : 8;
467
0
        features->chan_props[features->num_chan_props].num_channels =
468
0
            monochrome ? 1 : 3;
469
0
        ++features->num_chan_props;
470
0
      } else {
471
0
        features->data_was_skipped = 1;
472
0
      }
473
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size - 3));
474
0
    } else if (!memcmp(box.type, "auxC", 4)) {
475
      // See AV1 Image File Format (AVIF) 4
476
      // at https://aomediacodec.github.io/av1-avif/#auxiliary-images
477
0
      const char* kAlphaStr = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha";
478
0
      const uint32_t kAlphaStrLength = 44;  // Includes terminating character.
479
0
      const char* kGainmapStr = "urn:com:photo:aux:hdrgainmap";
480
0
      const uint32_t kGainmapStrLength = 29;  // Includes terminating character.
481
0
      uint32_t num_read_bytes = 0;
482
      // Check for a gain map or for an alpha plane. Start with the gain map
483
      // since the identifier is shorter.
484
0
      if (box.content_size >= kGainmapStrLength) {
485
0
        const uint8_t* data;
486
0
        AVIFINFO_CHECK_FOUND(
487
0
            AvifInfoInternalRead(stream, kGainmapStrLength, &data));
488
0
        num_read_bytes = kGainmapStrLength;
489
0
        const char* const aux_type = (const char*)data;
490
0
        if (strcmp(aux_type, kGainmapStr) == 0) {
491
          // Note: It is unlikely but it is possible that this gain map
492
          // does not belong to the primary item or a tile. Ignore this issue.
493
0
          if (box_index <= AVIFINFO_MAX_VALUE) {
494
0
            features->gainmap_property_index = (uint8_t)box_index;
495
0
          } else {
496
0
            features->data_was_skipped = 1;
497
0
          }
498
0
        } else if (box.content_size >= kAlphaStrLength &&
499
0
                   memcmp(aux_type, kAlphaStr, kGainmapStrLength) == 0) {
500
          // The beginning of the aux type matches the alpha aux type string.
501
          // Check the end as well.
502
0
          const uint8_t* data2;
503
0
          const uint32_t kEndLength = kAlphaStrLength - kGainmapStrLength;
504
0
          AVIFINFO_CHECK_FOUND(
505
0
              AvifInfoInternalRead(stream, kEndLength, &data2));
506
0
          num_read_bytes = kAlphaStrLength;
507
0
          if (strcmp((const char*)data2, &kAlphaStr[kGainmapStrLength]) == 0) {
508
            // Note: It is unlikely but it is possible that this alpha plane
509
            // does not belong to the primary item or a tile. Ignore this issue.
510
0
            features->has_alpha = 1;
511
0
          }
512
0
        }
513
0
      }
514
0
      AVIFINFO_CHECK_FOUND(
515
0
          AvifInfoInternalSkip(stream, box.content_size - num_read_bytes));
516
0
    } else {
517
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
518
0
    }
519
0
    ++box_index;
520
0
    num_remaining_bytes -= box.size;
521
0
  } while (num_remaining_bytes > 0);
522
0
  AVIFINFO_RETURN(kNotFound);
523
0
}
524
525
// Parses a 'stream' of an "iprp" box into 'features'. The "ipco" box contains
526
// the properties which are linked to items by the "ipma" box.
527
static AvifInfoInternalStatus ParseIprp(int nesting_level,
528
                                        AvifInfoInternalStream* stream,
529
                                        uint32_t num_remaining_bytes,
530
                                        uint32_t* num_parsed_boxes,
531
0
                                        AvifInfoInternalFeatures* features) {
532
0
  do {
533
0
    AvifInfoInternalBox box;
534
0
    AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(
535
0
        nesting_level, stream, num_remaining_bytes, num_parsed_boxes, &box));
536
537
0
    if (!memcmp(box.type, "ipco", 4)) {
538
0
      AVIFINFO_CHECK_NOT_FOUND(ParseIpco(nesting_level + 1, stream,
539
0
                                         box.content_size, num_parsed_boxes,
540
0
                                         features));
541
0
    } else if (!memcmp(box.type, "ipma", 4)) {
542
      // See ISO/IEC 23008-12:2017(E) 9.3.2
543
0
      uint32_t num_read_bytes = 4;
544
0
      const uint8_t* data;
545
0
      AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
546
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data));
547
0
      const uint32_t entry_count = AvifInfoInternalReadBigEndian(data, 4);
548
0
      const uint32_t id_num_bytes = (box.version < 1) ? 2 : 4;
549
0
      const uint32_t index_num_bytes = (box.flags & 1) ? 2 : 1;
550
0
      const uint32_t essential_bit_mask = (box.flags & 1) ? 0x8000 : 0x80;
551
552
0
      for (uint32_t entry = 0; entry < entry_count; ++entry) {
553
0
        if (entry >= AVIFINFO_MAX_PROPS ||
554
0
            features->num_props >= AVIFINFO_MAX_PROPS) {
555
0
          features->data_was_skipped = 1;
556
0
          break;
557
0
        }
558
0
        num_read_bytes += id_num_bytes + 1;
559
0
        AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
560
0
        AVIFINFO_CHECK_FOUND(
561
0
            AvifInfoInternalRead(stream, id_num_bytes + 1, &data));
562
0
        const uint32_t item_id =
563
0
            AvifInfoInternalReadBigEndian(data, id_num_bytes);
564
0
        const uint32_t association_count =
565
0
            AvifInfoInternalReadBigEndian(data + id_num_bytes, 1);
566
567
0
        uint32_t property;
568
0
        for (property = 0; property < association_count; ++property) {
569
0
          if (property >= AVIFINFO_MAX_PROPS ||
570
0
              features->num_props >= AVIFINFO_MAX_PROPS) {
571
0
            features->data_was_skipped = 1;
572
0
            break;
573
0
          }
574
0
          num_read_bytes += index_num_bytes;
575
0
          AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
576
0
          AVIFINFO_CHECK_FOUND(
577
0
              AvifInfoInternalRead(stream, index_num_bytes, &data));
578
0
          const uint32_t value =
579
0
              AvifInfoInternalReadBigEndian(data, index_num_bytes);
580
          // const int essential = (value & essential_bit_mask);  // Unused.
581
0
          const uint32_t property_index = (value & ~essential_bit_mask);
582
0
          if (property_index <= AVIFINFO_MAX_VALUE &&
583
0
              item_id <= AVIFINFO_MAX_VALUE) {
584
0
            features->props[features->num_props].property_index =
585
0
                property_index;
586
0
            features->props[features->num_props].item_id = item_id;
587
0
            ++features->num_props;
588
0
          } else {
589
0
            features->data_was_skipped = 1;
590
0
          }
591
0
        }
592
0
        if (property < association_count) break;  // Do not read garbage.
593
0
      }
594
595
      // If all features are available now, do not look further.
596
0
      AVIFINFO_CHECK_NOT_FOUND(
597
0
          AvifInfoInternalGetPrimaryItemFeatures(features));
598
599
      // Mostly if 'data_was_skipped'.
600
0
      AVIFINFO_CHECK_FOUND(
601
0
          AvifInfoInternalSkip(stream, box.content_size - num_read_bytes));
602
0
    } else {
603
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
604
0
    }
605
0
    num_remaining_bytes -= box.size;
606
0
  } while (num_remaining_bytes != 0);
607
0
  AVIFINFO_RETURN(kNotFound);
608
0
}
609
610
//------------------------------------------------------------------------------
611
612
// Parses a 'stream' of an "iref" box into 'features'.
613
// The "dimg" boxes contain links between tiles and their parent items, which
614
// can be used to infer bit depth and number of channels for the primary item
615
// when the latter does not have these properties.
616
static AvifInfoInternalStatus ParseIref(int nesting_level,
617
                                        AvifInfoInternalStream* stream,
618
                                        uint32_t num_remaining_bytes,
619
                                        uint32_t* num_parsed_boxes,
620
0
                                        AvifInfoInternalFeatures* features) {
621
0
  features->iref_parsed = 1;
622
623
0
  while (num_remaining_bytes > 0) {
624
0
    AvifInfoInternalBox box;
625
0
    AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(
626
0
        nesting_level, stream, num_remaining_bytes, num_parsed_boxes, &box));
627
628
0
    if (!memcmp(box.type, "dimg", 4)) {
629
      // See ISO/IEC 14496-12:2015(E) 8.11.12.2
630
0
      const uint32_t num_bytes_per_id = (box.version == 0) ? 2 : 4;
631
0
      uint32_t num_read_bytes = num_bytes_per_id + 2;
632
0
      const uint8_t* data;
633
0
      AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
634
0
      AVIFINFO_CHECK_FOUND(
635
0
          AvifInfoInternalRead(stream, num_bytes_per_id + 2, &data));
636
0
      const uint32_t from_item_id =
637
0
          AvifInfoInternalReadBigEndian(data, num_bytes_per_id);
638
0
      const uint32_t reference_count =
639
0
          AvifInfoInternalReadBigEndian(data + num_bytes_per_id, 2);
640
641
0
      for (uint32_t i = 0; i < reference_count; ++i) {
642
0
        if (i >= AVIFINFO_MAX_TILES) {
643
0
          features->data_was_skipped = 1;
644
0
          break;
645
0
        }
646
0
        num_read_bytes += num_bytes_per_id;
647
0
        AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
648
0
        AVIFINFO_CHECK_FOUND(
649
0
            AvifInfoInternalRead(stream, num_bytes_per_id, &data));
650
0
        const uint32_t to_item_id =
651
0
            AvifInfoInternalReadBigEndian(data, num_bytes_per_id);
652
0
        if (from_item_id <= AVIFINFO_MAX_VALUE &&
653
0
            to_item_id <= AVIFINFO_MAX_VALUE &&
654
0
            features->num_tiles < AVIFINFO_MAX_TILES) {
655
0
          features->tiles[features->num_tiles].tile_item_id = to_item_id;
656
0
          features->tiles[features->num_tiles].parent_item_id = from_item_id;
657
0
          features->tiles[features->num_tiles].dimg_idx = i;
658
0
          ++features->num_tiles;
659
0
        } else {
660
0
          features->data_was_skipped = 1;
661
0
        }
662
0
      }
663
664
      // If all features are available now, do not look further.
665
0
      AVIFINFO_CHECK_NOT_FOUND(
666
0
          AvifInfoInternalGetPrimaryItemFeatures(features));
667
668
      // Mostly if 'data_was_skipped'.
669
0
      AVIFINFO_CHECK_FOUND(
670
0
          AvifInfoInternalSkip(stream, box.content_size - num_read_bytes));
671
0
    } else {
672
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
673
0
    }
674
0
    num_remaining_bytes -= box.size;
675
0
  }
676
0
  AVIFINFO_RETURN(kNotFound);
677
0
}
678
679
//------------------------------------------------------------------------------
680
681
// Parses a 'stream' of an "iinf" box into 'features'.
682
static AvifInfoInternalStatus ParseIinf(int nesting_level,
683
                                        AvifInfoInternalStream* stream,
684
                                        uint32_t num_remaining_bytes,
685
                                        uint32_t box_version,
686
                                        uint32_t* num_parsed_boxes,
687
0
                                        AvifInfoInternalFeatures* features) {
688
0
  features->iinf_parsed = 1;
689
690
0
  const uint32_t num_bytes_per_entry_count = box_version == 0 ? 2 : 4;
691
0
  AVIFINFO_CHECK(num_bytes_per_entry_count <= num_remaining_bytes, kInvalid);
692
0
  const uint8_t* data;
693
0
  AVIFINFO_CHECK_FOUND(
694
0
      AvifInfoInternalRead(stream, num_bytes_per_entry_count, &data));
695
0
  num_remaining_bytes -= num_bytes_per_entry_count;
696
0
  const uint32_t entry_count =
697
0
      AvifInfoInternalReadBigEndian(data, num_bytes_per_entry_count);
698
699
0
  for (int i = 0; i < entry_count; ++i) {
700
0
    AvifInfoInternalBox box;
701
0
    AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(
702
0
        nesting_level, stream, num_remaining_bytes, num_parsed_boxes, &box));
703
704
0
    if (!memcmp(box.type, "infe", 4)) {
705
      // See ISO/IEC 14496-12:2015(E) 8.11.6.2
706
0
      const uint32_t num_bytes_per_id = (box.version == 2) ? 2 : 4;
707
0
      const uint8_t* data;
708
      // item_ID (16 or 32) + item_protection_index (16) + item_type (32).
709
0
      AVIFINFO_CHECK(num_bytes_per_id + 2 + 4 <= box.content_size, kInvalid);
710
0
      AVIFINFO_CHECK_FOUND(
711
0
          AvifInfoInternalRead(stream, num_bytes_per_id, &data));
712
0
      const uint32_t item_id =
713
0
          AvifInfoInternalReadBigEndian(data, num_bytes_per_id);
714
715
      // Skip item_protection_index.
716
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, 2));
717
718
0
      const uint8_t* item_type;
719
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &item_type));
720
0
      if (!memcmp(item_type, "tmap", 4)) {
721
        // Tone Mapped Image: indicates the presence of a gain map.
722
0
        if (item_id <= AVIFINFO_MAX_VALUE) {
723
0
          features->tone_mapped_item_id = (uint8_t)item_id;
724
0
        } else {
725
0
          features->data_was_skipped = 1;
726
0
        }
727
0
      }
728
729
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(
730
0
          stream, box.content_size - (num_bytes_per_id + 2 + 4)));
731
0
    } else {
732
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
733
0
    }
734
735
0
    num_remaining_bytes -= box.size;
736
0
    if (num_remaining_bytes == 0) break;  // Ignore entry_count bigger than box.
737
0
  }
738
0
  AVIFINFO_RETURN(kNotFound);
739
0
}
740
741
//------------------------------------------------------------------------------
742
743
// Parses a 'stream' of a "meta" box. It looks for the primary item ID in the
744
// "pitm" box and recurses into other boxes to find its 'features'.
745
static AvifInfoInternalStatus ParseMeta(int nesting_level,
746
                                        AvifInfoInternalStream* stream,
747
                                        uint32_t num_remaining_bytes,
748
                                        uint32_t* num_parsed_boxes,
749
0
                                        AvifInfoInternalFeatures* features) {
750
0
  do {
751
0
    AvifInfoInternalBox box;
752
0
    AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(
753
0
        nesting_level, stream, num_remaining_bytes, num_parsed_boxes, &box));
754
0
    if (!memcmp(box.type, "pitm", 4)) {
755
      // See ISO/IEC 14496-12:2015(E) 8.11.4.2
756
0
      const uint32_t num_bytes_per_id = (box.version == 0) ? 2 : 4;
757
0
      const uint64_t primary_item_id_location = stream->num_read_bytes;
758
0
      const uint8_t* data;
759
0
      AVIFINFO_CHECK(num_bytes_per_id <= num_remaining_bytes, kInvalid);
760
0
      AVIFINFO_CHECK_FOUND(
761
0
          AvifInfoInternalRead(stream, num_bytes_per_id, &data));
762
0
      const uint32_t primary_item_id =
763
0
          AvifInfoInternalReadBigEndian(data, num_bytes_per_id);
764
0
      AVIFINFO_CHECK(primary_item_id <= AVIFINFO_MAX_VALUE, kAborted);
765
0
      features->has_primary_item = 1;
766
0
      features->primary_item_id = primary_item_id;
767
0
      features->primary_item_features.primary_item_id_location =
768
0
          primary_item_id_location;
769
0
      features->primary_item_features.primary_item_id_bytes = num_bytes_per_id;
770
0
      AVIFINFO_CHECK_FOUND(
771
0
          AvifInfoInternalSkip(stream, box.content_size - num_bytes_per_id));
772
0
    } else if (!memcmp(box.type, "iprp", 4)) {
773
0
      AVIFINFO_CHECK_NOT_FOUND(ParseIprp(nesting_level + 1, stream,
774
0
                                         box.content_size, num_parsed_boxes,
775
0
                                         features));
776
0
    } else if (!memcmp(box.type, "iref", 4)) {
777
0
      AVIFINFO_CHECK_NOT_FOUND(ParseIref(nesting_level + 1, stream,
778
0
                                         box.content_size, num_parsed_boxes,
779
0
                                         features));
780
0
    } else if (!memcmp(box.type, "iinf", 4)) {
781
0
      AVIFINFO_CHECK_NOT_FOUND(ParseIinf(nesting_level + 1, stream,
782
0
                                         box.content_size, box.version,
783
0
                                         num_parsed_boxes, features));
784
0
    } else {
785
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
786
0
    }
787
0
    num_remaining_bytes -= box.size;
788
0
  } while (num_remaining_bytes != 0);
789
  // According to ISO/IEC 14496-12:2012(E) 8.11.1.1 there is at most one "meta".
790
0
  AVIFINFO_RETURN(features->data_was_skipped ? kAborted : kInvalid);
791
0
}
792
793
//------------------------------------------------------------------------------
794
795
// Parses a file 'stream'. The file type is checked through the "ftyp" box.
796
20
static AvifInfoInternalStatus ParseFtyp(AvifInfoInternalStream* stream) {
797
20
  AvifInfoInternalBox box;
798
20
  uint32_t num_parsed_boxes = 0;
799
20
  const int nesting_level = 0;
800
20
  AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(
801
18
      nesting_level, stream, AVIFINFO_MAX_SIZE, &num_parsed_boxes, &box));
802
18
  AVIFINFO_CHECK(!memcmp(box.type, "ftyp", 4), kInvalid);
803
  // Iterate over brands. See ISO/IEC 14496-12:2012(E) 4.3.1
804
4
  AVIFINFO_CHECK(box.content_size >= 8, kInvalid);  // major_brand,minor_version
805
120
  for (uint32_t i = 0; i + 4 <= box.content_size; i += 4) {
806
119
    const uint8_t* data;
807
119
    AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data));
808
116
    if (i == 4) continue;  // Skip minor_version.
809
112
    if (!memcmp(data, "avif", 4) || !memcmp(data, "avis", 4)) {
810
0
      AVIFINFO_CHECK_FOUND(
811
0
          AvifInfoInternalSkip(stream, box.content_size - (i + 4)));
812
0
      return kFound;
813
0
    }
814
112
    AVIFINFO_CHECK(i <= 32 * 4, kAborted);  // Be reasonable.
815
112
  }
816
1
  AVIFINFO_RETURN(kInvalid);  // No AVIF brand no good.
817
1
}
818
819
// Parses a file 'stream'. 'features' are extracted from the "meta" box.
820
static AvifInfoInternalStatus ParseFile(AvifInfoInternalStream* stream,
821
                                        uint32_t* num_parsed_boxes,
822
0
                                        AvifInfoInternalFeatures* features) {
823
0
  while (1) {
824
0
    AvifInfoInternalBox box;
825
0
    AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(
826
0
        /*nesting_level=*/0, stream, AVIFINFO_MAX_SIZE, num_parsed_boxes,
827
0
        &box));
828
0
    if (!memcmp(box.type, "meta", 4)) {
829
0
      return ParseMeta(/*nesting_level=*/1, stream, box.content_size,
830
0
                       num_parsed_boxes, features);
831
0
    } else {
832
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
833
0
    }
834
0
  }
835
0
}
836
837
//------------------------------------------------------------------------------
838
// Helpers for converting the fixed-size input public API to the streamed one.
839
840
typedef struct {
841
  const uint8_t* data;
842
  size_t data_size;
843
} AvifInfoInternalForward;
844
845
static const uint8_t* AvifInfoInternalForwardRead(void* stream,
846
0
                                                  size_t num_bytes) {
847
0
  AvifInfoInternalForward* forward = (AvifInfoInternalForward*)stream;
848
0
  if (num_bytes > forward->data_size) return NULL;
849
0
  const uint8_t* data = forward->data;
850
0
  forward->data += num_bytes;
851
0
  forward->data_size -= num_bytes;
852
0
  return data;
853
0
}
854
855
0
static void AvifInfoInternalForwardSkip(void* stream, size_t num_bytes) {
856
0
  AvifInfoInternalForward* forward = (AvifInfoInternalForward*)stream;
857
0
  if (num_bytes > forward->data_size) num_bytes = forward->data_size;
858
0
  forward->data += num_bytes;
859
0
  forward->data_size -= num_bytes;
860
0
}
861
862
//------------------------------------------------------------------------------
863
// Fixed-size input public API
864
865
0
AvifInfoStatus AvifInfoIdentify(const uint8_t* data, size_t data_size) {
866
0
  AvifInfoInternalForward stream;
867
0
  stream.data = data;
868
0
  stream.data_size = data_size;
869
  // Forward null 'data' as a null 'stream' to handle it the same way.
870
0
  return AvifInfoIdentifyStream(
871
0
      (void*)&stream, (data == NULL) ? NULL : AvifInfoInternalForwardRead,
872
0
      AvifInfoInternalForwardSkip);
873
0
}
874
875
AvifInfoStatus AvifInfoGetFeatures(const uint8_t* data, size_t data_size,
876
0
                                   AvifInfoFeatures* features) {
877
0
  const AvifInfoStatus status = AvifInfoIdentify(data, data_size);
878
0
  if (status != kAvifInfoOk) {
879
0
    if (features != NULL) memset(features, 0, sizeof(*features));
880
0
    return status;
881
0
  }
882
0
  AvifInfoInternalForward stream;
883
0
  stream.data = data;
884
0
  stream.data_size = data_size;
885
0
  return AvifInfoGetFeaturesStream(
886
0
      (void*)&stream, (data == NULL) ? NULL : AvifInfoInternalForwardRead,
887
0
      AvifInfoInternalForwardSkip, features);
888
0
}
889
890
//------------------------------------------------------------------------------
891
// Streamed input API
892
893
AvifInfoStatus AvifInfoIdentifyStream(void* stream, read_stream_t read,
894
20
                                      skip_stream_t skip) {
895
20
  if (read == NULL) return kAvifInfoNotEnoughData;
896
897
20
  AvifInfoInternalStream internal_stream;
898
20
  internal_stream.stream = stream;
899
20
  internal_stream.read = read;
900
20
  internal_stream.skip = skip;  // Fallbacks to 'read' if null.
901
20
  internal_stream.num_read_bytes = 0;
902
20
  return AvifInfoInternalConvertStatus(ParseFtyp(&internal_stream));
903
20
}
904
905
AvifInfoStatus AvifInfoGetFeaturesStream(void* stream, read_stream_t read,
906
                                         skip_stream_t skip,
907
0
                                         AvifInfoFeatures* features) {
908
0
  if (features != NULL) memset(features, 0, sizeof(*features));
909
0
  if (read == NULL) return kAvifInfoNotEnoughData;
910
911
0
  AvifInfoInternalStream internal_stream;
912
0
  internal_stream.stream = stream;
913
0
  internal_stream.read = read;
914
0
  internal_stream.skip = skip;  // Fallbacks to 'read' if null.
915
0
  internal_stream.num_read_bytes = 0;
916
0
  uint32_t num_parsed_boxes = 0;
917
0
  AvifInfoInternalFeatures internal_features;
918
0
  memset(&internal_features, AVIFINFO_UNDEFINED, sizeof(internal_features));
919
920
  // Go through all relevant boxes sequentially.
921
0
  const AvifInfoInternalStatus status =
922
0
      ParseFile(&internal_stream, &num_parsed_boxes, &internal_features);
923
0
  if (status == kFound && features != NULL) {
924
0
    memcpy(features, &internal_features.primary_item_features,
925
0
           sizeof(*features));
926
0
  }
927
0
  return AvifInfoInternalConvertStatus(status);
928
0
}