Coverage Report

Created: 2025-06-13 06:43

/src/php-src/ext/standard/libavifinfo/avifinfo.c
Line
Count
Source (jump to first uncovered line)
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
0
static AvifInfoStatus AvifInfoInternalConvertStatus(AvifInfoInternalStatus s) {
28
0
  return (s == kFound)                         ? kAvifInfoOk
29
0
         : (s == kNotFound || s == kTruncated) ? kAvifInfoNotEnoughData
30
0
         : (s == kAborted)                     ? kAvifInfoTooComplex
31
0
                                               : kAvifInfoInvalidFile;
32
0
}
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
// AvifInfoInternalFeatures uses uint8_t to store values and the number of
38
// values is clamped to 32 to limit the stack size.
39
0
#define AVIFINFO_MAX_VALUE UINT8_MAX
40
0
#define AVIFINFO_UNDEFINED 0
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
46
// Reads an unsigned integer from 'input' with most significant bits first.
47
// 'input' must be at least 'num_bytes'-long.
48
static uint32_t AvifInfoInternalReadBigEndian(const uint8_t* input,
49
0
                                              uint32_t num_bytes) {
50
0
  uint32_t value = 0;
51
0
  for (uint32_t i = 0; i < num_bytes; ++i) {
52
0
    value = (value << 8) | input[i];
53
0
  }
54
0
  return value;
55
0
}
56
57
//------------------------------------------------------------------------------
58
// Convenience macros.
59
60
#if defined(AVIFINFO_LOG_ERROR)  // Toggle to log encountered issues.
61
static void AvifInfoInternalLogError(const char* file, int line,
62
                                     AvifInfoInternalStatus status) {
63
  const char* kStr[] = {"Found", "NotFound", "Truncated", "Invalid", "Aborted"};
64
  fprintf(stderr, "  %s:%d: %s\n", file, line, kStr[status]);
65
  // Set a breakpoint here to catch the first detected issue.
66
}
67
#define AVIFINFO_RETURN(check_status)                               \
68
  do {                                                              \
69
    const AvifInfoInternalStatus status_checked = (check_status);   \
70
    if (status_checked != kFound && status_checked != kNotFound) {  \
71
      AvifInfoInternalLogError(__FILE__, __LINE__, status_checked); \
72
    }                                                               \
73
    return status_checked;                                          \
74
  } while (0)
75
#else
76
#define AVIFINFO_RETURN(check_status) \
77
0
  do {                                \
78
0
    return (check_status);            \
79
0
  } while (0)
80
#endif
81
82
#define AVIFINFO_CHECK(check_condition, check_status)      \
83
0
  do {                                                     \
84
0
    if (!(check_condition)) AVIFINFO_RETURN(check_status); \
85
0
  } while (0)
86
#define AVIFINFO_CHECK_STATUS_IS(check_status, expected_status)            \
87
0
  do {                                                                     \
88
0
    const AvifInfoInternalStatus status_returned = (check_status);         \
89
0
    AVIFINFO_CHECK(status_returned == (expected_status), status_returned); \
90
0
  } while (0)
91
#define AVIFINFO_CHECK_FOUND(check_status) \
92
0
  AVIFINFO_CHECK_STATUS_IS((check_status), kFound)
93
#define AVIFINFO_CHECK_NOT_FOUND(check_status) \
94
0
  AVIFINFO_CHECK_STATUS_IS((check_status), kNotFound)
95
96
//------------------------------------------------------------------------------
97
// Streamed input struct and helper functions.
98
99
typedef struct {
100
  void* stream;             // User-defined data.
101
  read_stream_t read;       // Used to fetch more bytes from the 'stream'.
102
  skip_stream_t skip;       // Used to advance the position in the 'stream'.
103
                            // Fallback to 'read' if 'skip' is null.
104
} AvifInfoInternalStream;
105
106
// Reads 'num_bytes' from the 'stream'. They are available at '*data'.
107
// 'num_bytes' must be greater than zero.
108
static AvifInfoInternalStatus AvifInfoInternalRead(
109
0
    AvifInfoInternalStream* stream, uint32_t num_bytes, const uint8_t** data) {
110
0
  *data = stream->read(stream->stream, num_bytes);
111
0
  AVIFINFO_CHECK(*data != NULL, kTruncated);
112
0
  return kFound;
113
0
}
114
115
// Skips 'num_bytes' from the 'stream'. 'num_bytes' can be zero.
116
static AvifInfoInternalStatus AvifInfoInternalSkip(
117
0
    AvifInfoInternalStream* stream, uint32_t num_bytes) {
118
  // Avoid a call to the user-defined function for nothing.
119
0
  if (num_bytes > 0) {
120
0
    if (stream->skip == NULL) {
121
0
      const uint8_t* unused;
122
0
      while (num_bytes > AVIFINFO_MAX_NUM_READ_BYTES) {
123
0
        AVIFINFO_CHECK_FOUND(
124
0
            AvifInfoInternalRead(stream, AVIFINFO_MAX_NUM_READ_BYTES, &unused));
125
0
        num_bytes -= AVIFINFO_MAX_NUM_READ_BYTES;
126
0
      }
127
0
      return AvifInfoInternalRead(stream, num_bytes, &unused);
128
0
    }
129
0
    stream->skip(stream->stream, num_bytes);
130
0
  }
131
0
  return kFound;
132
0
}
133
134
//------------------------------------------------------------------------------
135
// Features are parsed into temporary property associations.
136
137
typedef struct {
138
  uint8_t tile_item_id;
139
  uint8_t parent_item_id;
140
} AvifInfoInternalTile;  // Tile item id <-> parent item id associations.
141
142
typedef struct {
143
  uint8_t property_index;
144
  uint8_t item_id;
145
} AvifInfoInternalProp;  // Property index <-> item id associations.
146
147
typedef struct {
148
  uint8_t property_index;
149
  uint32_t width, height;
150
} AvifInfoInternalDimProp;  // Property <-> features associations.
151
152
typedef struct {
153
  uint8_t property_index;
154
  uint8_t bit_depth, num_channels;
155
} AvifInfoInternalChanProp;  // Property <-> features associations.
156
157
typedef struct {
158
  uint8_t has_primary_item;  // True if "pitm" was parsed.
159
  uint8_t has_alpha;         // True if an alpha "auxC" was parsed.
160
  uint8_t primary_item_id;
161
  AvifInfoFeatures primary_item_features;  // Deduced from the data below.
162
  uint8_t data_was_skipped;  // True if some loops/indices were skipped.
163
164
  uint8_t num_tiles;
165
  AvifInfoInternalTile tiles[AVIFINFO_MAX_TILES];
166
  uint8_t num_props;
167
  AvifInfoInternalProp props[AVIFINFO_MAX_PROPS];
168
  uint8_t num_dim_props;
169
  AvifInfoInternalDimProp dim_props[AVIFINFO_MAX_FEATURES];
170
  uint8_t num_chan_props;
171
  AvifInfoInternalChanProp chan_props[AVIFINFO_MAX_FEATURES];
172
} AvifInfoInternalFeatures;
173
174
// Generates the features of a given 'target_item_id' from internal features.
175
static AvifInfoInternalStatus AvifInfoInternalGetItemFeatures(
176
0
    AvifInfoInternalFeatures* f, uint32_t target_item_id, uint32_t tile_depth) {
177
0
  for (uint32_t prop_item = 0; prop_item < f->num_props; ++prop_item) {
178
0
    if (f->props[prop_item].item_id != target_item_id) continue;
179
0
    const uint32_t property_index = f->props[prop_item].property_index;
180
181
    // Retrieve the width and height of the primary item if not already done.
182
0
    if (target_item_id == f->primary_item_id &&
183
0
        (f->primary_item_features.width == AVIFINFO_UNDEFINED ||
184
0
         f->primary_item_features.height == AVIFINFO_UNDEFINED)) {
185
0
      for (uint32_t i = 0; i < f->num_dim_props; ++i) {
186
0
        if (f->dim_props[i].property_index != property_index) continue;
187
0
        f->primary_item_features.width = f->dim_props[i].width;
188
0
        f->primary_item_features.height = f->dim_props[i].height;
189
0
        if (f->primary_item_features.bit_depth != AVIFINFO_UNDEFINED &&
190
0
            f->primary_item_features.num_channels != AVIFINFO_UNDEFINED) {
191
0
          return kFound;
192
0
        }
193
0
        break;
194
0
      }
195
0
    }
196
    // Retrieve the bit depth and number of channels of the target item if not
197
    // already done.
198
0
    if (f->primary_item_features.bit_depth == AVIFINFO_UNDEFINED ||
199
0
        f->primary_item_features.num_channels == AVIFINFO_UNDEFINED) {
200
0
      for (uint32_t i = 0; i < f->num_chan_props; ++i) {
201
0
        if (f->chan_props[i].property_index != property_index) continue;
202
0
        f->primary_item_features.bit_depth = f->chan_props[i].bit_depth;
203
0
        f->primary_item_features.num_channels = f->chan_props[i].num_channels;
204
0
        if (f->primary_item_features.width != AVIFINFO_UNDEFINED &&
205
0
            f->primary_item_features.height != AVIFINFO_UNDEFINED) {
206
0
          return kFound;
207
0
        }
208
0
        break;
209
0
      }
210
0
    }
211
0
  }
212
213
  // Check for the bit_depth and num_channels in a tile if not yet found.
214
0
  for (uint32_t tile = 0; tile < f->num_tiles && tile_depth < 3; ++tile) {
215
0
    if (f->tiles[tile].parent_item_id != target_item_id) continue;
216
0
    AVIFINFO_CHECK_NOT_FOUND(AvifInfoInternalGetItemFeatures(
217
0
        f, f->tiles[tile].tile_item_id, tile_depth + 1));
218
0
  }
219
0
  AVIFINFO_RETURN(kNotFound);
220
0
}
221
222
// Generates the 'f->primary_item_features' from the AvifInfoInternalFeatures.
223
// Returns kNotFound if there is not enough information.
224
static AvifInfoInternalStatus AvifInfoInternalGetPrimaryItemFeatures(
225
0
    AvifInfoInternalFeatures* f) {
226
  // Nothing to do without the primary item ID.
227
0
  AVIFINFO_CHECK(f->has_primary_item, kNotFound);
228
  // Early exit.
229
0
  AVIFINFO_CHECK(f->num_dim_props > 0 && f->num_chan_props, kNotFound);
230
0
  AVIFINFO_CHECK_FOUND(
231
0
      AvifInfoInternalGetItemFeatures(f, f->primary_item_id, /*tile_depth=*/0));
232
233
  // "auxC" is parsed before the "ipma" properties so it is known now, if any.
234
0
  if (f->has_alpha) ++f->primary_item_features.num_channels;
235
0
  return kFound;
236
0
}
237
238
//------------------------------------------------------------------------------
239
// Box header parsing and various size checks.
240
241
typedef struct {
242
  uint32_t size;          // In bytes.
243
  uint8_t type[4];        // Four characters.
244
  uint32_t version;       // 0 or actual version if this is a full box.
245
  uint32_t flags;         // 0 or actual value if this is a full box.
246
  uint32_t content_size;  // 'size' minus the header size.
247
} AvifInfoInternalBox;
248
249
// Reads the header of a 'box' starting at the beginning of a 'stream'.
250
// 'num_remaining_bytes' is the remaining size of the container of the 'box'
251
// (either the file size itself or the content size of the parent of the 'box').
252
static AvifInfoInternalStatus AvifInfoInternalParseBox(
253
    AvifInfoInternalStream* stream, uint32_t num_remaining_bytes,
254
0
    uint32_t* num_parsed_boxes, AvifInfoInternalBox* box) {
255
0
  const uint8_t* data;
256
  // See ISO/IEC 14496-12:2012(E) 4.2
257
0
  uint32_t box_header_size = 8;  // box 32b size + 32b type (at least)
258
0
  AVIFINFO_CHECK(box_header_size <= num_remaining_bytes, kInvalid);
259
0
  AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data));
260
0
  box->size = AvifInfoInternalReadBigEndian(data, sizeof(uint32_t));
261
0
  memcpy(box->type, data + 4, 4);
262
  // 'box->size==1' means 64-bit size should be read after the box type.
263
  // 'box->size==0' means this box extends to all remaining bytes.
264
0
  if (box->size == 1) {
265
0
    box_header_size += 8;
266
0
    AVIFINFO_CHECK(box_header_size <= num_remaining_bytes, kInvalid);
267
0
    AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data));
268
    // Stop the parsing if any box has a size greater than 4GB.
269
0
    AVIFINFO_CHECK(AvifInfoInternalReadBigEndian(data, sizeof(uint32_t)) == 0,
270
0
                   kAborted);
271
    // Read the 32 least-significant bits.
272
0
    box->size = AvifInfoInternalReadBigEndian(data + 4, sizeof(uint32_t));
273
0
  } else if (box->size == 0) {
274
0
    box->size = num_remaining_bytes;
275
0
  }
276
0
  AVIFINFO_CHECK(box->size >= box_header_size, kInvalid);
277
0
  AVIFINFO_CHECK(box->size <= num_remaining_bytes, kInvalid);
278
279
0
  const int has_fullbox_header =
280
0
      !memcmp(box->type, "meta", 4) || !memcmp(box->type, "pitm", 4) ||
281
0
      !memcmp(box->type, "ipma", 4) || !memcmp(box->type, "ispe", 4) ||
282
0
      !memcmp(box->type, "pixi", 4) || !memcmp(box->type, "iref", 4) ||
283
0
      !memcmp(box->type, "auxC", 4);
284
0
  if (has_fullbox_header) box_header_size += 4;
285
0
  AVIFINFO_CHECK(box->size >= box_header_size, kInvalid);
286
0
  box->content_size = box->size - box_header_size;
287
  // Avoid timeouts. The maximum number of parsed boxes is arbitrary.
288
0
  ++*num_parsed_boxes;
289
0
  AVIFINFO_CHECK(*num_parsed_boxes < 4096, kAborted);
290
291
0
  box->version = 0;
292
0
  box->flags = 0;
293
0
  if (has_fullbox_header) {
294
0
    AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data));
295
0
    box->version = AvifInfoInternalReadBigEndian(data, 1);
296
0
    box->flags = AvifInfoInternalReadBigEndian(data + 1, 3);
297
    // See AV1 Image File Format (AVIF) 8.1
298
    // at https://aomediacodec.github.io/av1-avif/#avif-boxes (available when
299
    // https://github.com/AOMediaCodec/av1-avif/pull/170 is merged).
300
0
    uint32_t is_parsable = 1;
301
0
    if (!memcmp(box->type, "meta", 4)) is_parsable = (box->version <= 0);
302
0
    if (!memcmp(box->type, "pitm", 4)) is_parsable = (box->version <= 1);
303
0
    if (!memcmp(box->type, "ipma", 4)) is_parsable = (box->version <= 1);
304
0
    if (!memcmp(box->type, "ispe", 4)) is_parsable = (box->version <= 0);
305
0
    if (!memcmp(box->type, "pixi", 4)) is_parsable = (box->version <= 0);
306
0
    if (!memcmp(box->type, "iref", 4)) is_parsable = (box->version <= 1);
307
0
    if (!memcmp(box->type, "auxC", 4)) is_parsable = (box->version <= 0);
308
    // Instead of considering this file as invalid, skip unparsable boxes.
309
0
    if (!is_parsable) memcpy(box->type, "\0skp", 4);  // \0 so not a valid type
310
0
  }
311
0
  return kFound;
312
0
}
313
314
//------------------------------------------------------------------------------
315
316
// Parses a 'stream' of an "ipco" box into 'features'.
317
// "ispe" is used for width and height, "pixi" and "av1C" are used for bit depth
318
// and number of channels, and "auxC" is used for alpha.
319
static AvifInfoInternalStatus ParseIpco(AvifInfoInternalStream* stream,
320
                                        uint32_t num_remaining_bytes,
321
                                        uint32_t* num_parsed_boxes,
322
0
                                        AvifInfoInternalFeatures* features) {
323
0
  uint32_t box_index = 1;  // 1-based index. Used for iterating over properties.
324
0
  do {
325
0
    AvifInfoInternalBox box;
326
0
    AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes,
327
0
                                                  num_parsed_boxes, &box));
328
329
0
    if (!memcmp(box.type, "ispe", 4)) {
330
      // See ISO/IEC 23008-12:2017(E) 6.5.3.2
331
0
      const uint8_t* data;
332
0
      AVIFINFO_CHECK(box.content_size >= 8, kInvalid);
333
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data));
334
0
      const uint32_t width = AvifInfoInternalReadBigEndian(data + 0, 4);
335
0
      const uint32_t height = AvifInfoInternalReadBigEndian(data + 4, 4);
336
0
      AVIFINFO_CHECK(width != 0 && height != 0, kInvalid);
337
0
      if (features->num_dim_props < AVIFINFO_MAX_FEATURES &&
338
0
          box_index <= AVIFINFO_MAX_VALUE) {
339
0
        features->dim_props[features->num_dim_props].property_index = box_index;
340
0
        features->dim_props[features->num_dim_props].width = width;
341
0
        features->dim_props[features->num_dim_props].height = height;
342
0
        ++features->num_dim_props;
343
0
      } else {
344
0
        features->data_was_skipped = 1;
345
0
      }
346
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size - 8));
347
0
    } else if (!memcmp(box.type, "pixi", 4)) {
348
      // See ISO/IEC 23008-12:2017(E) 6.5.6.2
349
0
      const uint8_t* data;
350
0
      AVIFINFO_CHECK(box.content_size >= 1, kInvalid);
351
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data));
352
0
      const uint32_t num_channels = AvifInfoInternalReadBigEndian(data + 0, 1);
353
0
      AVIFINFO_CHECK(num_channels >= 1, kInvalid);
354
0
      AVIFINFO_CHECK(box.content_size >= 1 + num_channels, kInvalid);
355
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data));
356
0
      const uint32_t bit_depth = AvifInfoInternalReadBigEndian(data, 1);
357
0
      AVIFINFO_CHECK(bit_depth >= 1, kInvalid);
358
0
      for (uint32_t i = 1; i < num_channels; ++i) {
359
0
        AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data));
360
        // Bit depth should be the same for all channels.
361
0
        AVIFINFO_CHECK(AvifInfoInternalReadBigEndian(data, 1) == bit_depth,
362
0
                       kInvalid);
363
0
        AVIFINFO_CHECK(i <= 32, kAborted);  // Be reasonable.
364
0
      }
365
0
      if (features->num_chan_props < AVIFINFO_MAX_FEATURES &&
366
0
          box_index <= AVIFINFO_MAX_VALUE && bit_depth <= AVIFINFO_MAX_VALUE &&
367
0
          num_channels <= AVIFINFO_MAX_VALUE) {
368
0
        features->chan_props[features->num_chan_props].property_index =
369
0
            box_index;
370
0
        features->chan_props[features->num_chan_props].bit_depth = bit_depth;
371
0
        features->chan_props[features->num_chan_props].num_channels =
372
0
            num_channels;
373
0
        ++features->num_chan_props;
374
0
      } else {
375
0
        features->data_was_skipped = 1;
376
0
      }
377
0
      AVIFINFO_CHECK_FOUND(
378
0
          AvifInfoInternalSkip(stream, box.content_size - (1 + num_channels)));
379
0
    } else if (!memcmp(box.type, "av1C", 4)) {
380
      // See AV1 Codec ISO Media File Format Binding 2.3.1
381
      // at https://aomediacodec.github.io/av1-isobmff/#av1c
382
      // Only parse the necessary third byte. Assume that the others are valid.
383
0
      const uint8_t* data;
384
0
      AVIFINFO_CHECK(box.content_size >= 3, kInvalid);
385
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 3, &data));
386
0
      const int high_bitdepth = (data[2] & 0x40) != 0;
387
0
      const int twelve_bit = (data[2] & 0x20) != 0;
388
0
      const int monochrome = (data[2] & 0x10) != 0;
389
0
      if (twelve_bit) {
390
0
        AVIFINFO_CHECK(high_bitdepth, kInvalid);
391
0
      }
392
0
      if (features->num_chan_props < AVIFINFO_MAX_FEATURES &&
393
0
          box_index <= AVIFINFO_MAX_VALUE) {
394
0
        features->chan_props[features->num_chan_props].property_index =
395
0
            box_index;
396
0
        features->chan_props[features->num_chan_props].bit_depth =
397
0
            high_bitdepth ? twelve_bit ? 12 : 10 : 8;
398
0
        features->chan_props[features->num_chan_props].num_channels =
399
0
            monochrome ? 1 : 3;
400
0
        ++features->num_chan_props;
401
0
      } else {
402
0
        features->data_was_skipped = 1;
403
0
      }
404
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size - 3));
405
0
    } else if (!memcmp(box.type, "auxC", 4)) {
406
      // See AV1 Image File Format (AVIF) 4
407
      // at https://aomediacodec.github.io/av1-avif/#auxiliary-images
408
0
      const char* kAlphaStr = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha";
409
0
      const uint32_t kAlphaStrLength = 44;  // Includes terminating character.
410
0
      if (box.content_size >= kAlphaStrLength) {
411
0
        const uint8_t* data;
412
0
        AVIFINFO_CHECK_FOUND(
413
0
            AvifInfoInternalRead(stream, kAlphaStrLength, &data));
414
0
        const char* const aux_type = (const char*)data;
415
0
        if (strcmp(aux_type, kAlphaStr) == 0) {
416
          // Note: It is unlikely but it is possible that this alpha plane does
417
          //       not belong to the primary item or a tile. Ignore this issue.
418
0
          features->has_alpha = 1;
419
0
        }
420
0
        AVIFINFO_CHECK_FOUND(
421
0
            AvifInfoInternalSkip(stream, box.content_size - kAlphaStrLength));
422
0
      } else {
423
0
        AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
424
0
      }
425
0
    } else {
426
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
427
0
    }
428
0
    ++box_index;
429
0
    num_remaining_bytes -= box.size;
430
0
  } while (num_remaining_bytes > 0);
431
0
  AVIFINFO_RETURN(kNotFound);
432
0
}
433
434
// Parses a 'stream' of an "iprp" box into 'features'. The "ipco" box contain
435
// the properties which are linked to items by the "ipma" box.
436
static AvifInfoInternalStatus ParseIprp(AvifInfoInternalStream* stream,
437
                                        uint32_t num_remaining_bytes,
438
                                        uint32_t* num_parsed_boxes,
439
0
                                        AvifInfoInternalFeatures* features) {
440
0
  do {
441
0
    AvifInfoInternalBox box;
442
0
    AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes,
443
0
                                                  num_parsed_boxes, &box));
444
445
0
    if (!memcmp(box.type, "ipco", 4)) {
446
0
      AVIFINFO_CHECK_NOT_FOUND(
447
0
          ParseIpco(stream, box.content_size, num_parsed_boxes, features));
448
0
    } else if (!memcmp(box.type, "ipma", 4)) {
449
      // See ISO/IEC 23008-12:2017(E) 9.3.2
450
0
      uint32_t num_read_bytes = 4;
451
0
      const uint8_t* data;
452
0
      AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
453
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data));
454
0
      const uint32_t entry_count = AvifInfoInternalReadBigEndian(data, 4);
455
0
      const uint32_t id_num_bytes = (box.version < 1) ? 2 : 4;
456
0
      const uint32_t index_num_bytes = (box.flags & 1) ? 2 : 1;
457
0
      const uint32_t essential_bit_mask = (box.flags & 1) ? 0x8000 : 0x80;
458
459
0
      for (uint32_t entry = 0; entry < entry_count; ++entry) {
460
0
        if (entry >= AVIFINFO_MAX_PROPS ||
461
0
            features->num_props >= AVIFINFO_MAX_PROPS) {
462
0
          features->data_was_skipped = 1;
463
0
          break;
464
0
        }
465
0
        num_read_bytes += id_num_bytes + 1;
466
0
        AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
467
0
        AVIFINFO_CHECK_FOUND(
468
0
            AvifInfoInternalRead(stream, id_num_bytes + 1, &data));
469
0
        const uint32_t item_id =
470
0
            AvifInfoInternalReadBigEndian(data, id_num_bytes);
471
0
        const uint32_t association_count =
472
0
            AvifInfoInternalReadBigEndian(data + id_num_bytes, 1);
473
474
0
        uint32_t property;
475
0
        for (property = 0; property < association_count; ++property) {
476
0
          if (property >= AVIFINFO_MAX_PROPS ||
477
0
              features->num_props >= AVIFINFO_MAX_PROPS) {
478
0
            features->data_was_skipped = 1;
479
0
            break;
480
0
          }
481
0
          num_read_bytes += index_num_bytes;
482
0
          AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
483
0
          AVIFINFO_CHECK_FOUND(
484
0
              AvifInfoInternalRead(stream, index_num_bytes, &data));
485
0
          const uint32_t value =
486
0
              AvifInfoInternalReadBigEndian(data, index_num_bytes);
487
          // const int essential = (value & essential_bit_mask);  // Unused.
488
0
          const uint32_t property_index = (value & ~essential_bit_mask);
489
0
          if (property_index <= AVIFINFO_MAX_VALUE &&
490
0
              item_id <= AVIFINFO_MAX_VALUE) {
491
0
            features->props[features->num_props].property_index =
492
0
                property_index;
493
0
            features->props[features->num_props].item_id = item_id;
494
0
            ++features->num_props;
495
0
          } else {
496
0
            features->data_was_skipped = 1;
497
0
          }
498
0
        }
499
0
        if (property < association_count) break;  // Do not read garbage.
500
0
      }
501
502
      // If all features are available now, do not look further.
503
0
      AVIFINFO_CHECK_NOT_FOUND(
504
0
          AvifInfoInternalGetPrimaryItemFeatures(features));
505
506
0
      AVIFINFO_CHECK_FOUND(
507
0
          AvifInfoInternalSkip(stream, box.content_size - num_read_bytes));
508
0
    } else {
509
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
510
0
    }
511
0
    num_remaining_bytes -= box.size;
512
0
  } while (num_remaining_bytes != 0);
513
0
  AVIFINFO_RETURN(kNotFound);
514
0
}
515
516
//------------------------------------------------------------------------------
517
518
// Parses a 'stream' of an "iref" box into 'features'.
519
// The "dimg" boxes contain links between tiles and their parent items, which
520
// can be used to infer bit depth and number of channels for the primary item
521
// when the latter does not have these properties.
522
static AvifInfoInternalStatus ParseIref(AvifInfoInternalStream* stream,
523
                                        uint32_t num_remaining_bytes,
524
                                        uint32_t* num_parsed_boxes,
525
0
                                        AvifInfoInternalFeatures* features) {
526
0
  do {
527
0
    AvifInfoInternalBox box;
528
0
    AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes,
529
0
                                                  num_parsed_boxes, &box));
530
531
0
    if (!memcmp(box.type, "dimg", 4)) {
532
      // See ISO/IEC 14496-12:2015(E) 8.11.12.2
533
0
      const uint32_t num_bytes_per_id = (box.version == 0) ? 2 : 4;
534
0
      uint32_t num_read_bytes = num_bytes_per_id + 2;
535
0
      const uint8_t* data;
536
0
      AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
537
0
      AVIFINFO_CHECK_FOUND(
538
0
          AvifInfoInternalRead(stream, num_bytes_per_id + 2, &data));
539
0
      const uint32_t from_item_id =
540
0
          AvifInfoInternalReadBigEndian(data, num_bytes_per_id);
541
0
      const uint32_t reference_count =
542
0
          AvifInfoInternalReadBigEndian(data + num_bytes_per_id, 2);
543
544
0
      for (uint32_t i = 0; i < reference_count; ++i) {
545
0
        if (i >= AVIFINFO_MAX_TILES) {
546
0
          features->data_was_skipped = 1;
547
0
          break;
548
0
        }
549
0
        num_read_bytes += num_bytes_per_id;
550
0
        AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
551
0
        AVIFINFO_CHECK_FOUND(
552
0
            AvifInfoInternalRead(stream, num_bytes_per_id, &data));
553
0
        const uint32_t to_item_id =
554
0
            AvifInfoInternalReadBigEndian(data, num_bytes_per_id);
555
0
        if (from_item_id <= AVIFINFO_MAX_VALUE &&
556
0
            to_item_id <= AVIFINFO_MAX_VALUE &&
557
0
            features->num_tiles < AVIFINFO_MAX_TILES) {
558
0
          features->tiles[features->num_tiles].tile_item_id = to_item_id;
559
0
          features->tiles[features->num_tiles].parent_item_id = from_item_id;
560
0
          ++features->num_tiles;
561
0
        } else {
562
0
          features->data_was_skipped = 1;
563
0
        }
564
0
      }
565
566
      // If all features are available now, do not look further.
567
0
      AVIFINFO_CHECK_NOT_FOUND(
568
0
          AvifInfoInternalGetPrimaryItemFeatures(features));
569
0
    } else {
570
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
571
0
    }
572
0
    num_remaining_bytes -= box.size;
573
0
  } while (num_remaining_bytes > 0);
574
0
  AVIFINFO_RETURN(kNotFound);
575
0
}
576
577
//------------------------------------------------------------------------------
578
579
// Parses a 'stream' of a "meta" box. It looks for the primary item ID in the
580
// "pitm" box and recurses into other boxes to find its 'features'.
581
static AvifInfoInternalStatus ParseMeta(AvifInfoInternalStream* stream,
582
                                        uint32_t num_remaining_bytes,
583
                                        uint32_t* num_parsed_boxes,
584
0
                                        AvifInfoInternalFeatures* features) {
585
0
  do {
586
0
    AvifInfoInternalBox box;
587
0
    AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes,
588
0
                                                  num_parsed_boxes, &box));
589
590
0
    if (!memcmp(box.type, "pitm", 4)) {
591
      // See ISO/IEC 14496-12:2015(E) 8.11.4.2
592
0
      const uint32_t num_bytes_per_id = (box.version == 0) ? 2 : 4;
593
0
      const uint8_t* data;
594
0
      AVIFINFO_CHECK(num_bytes_per_id <= num_remaining_bytes, kInvalid);
595
0
      AVIFINFO_CHECK_FOUND(
596
0
          AvifInfoInternalRead(stream, num_bytes_per_id, &data));
597
0
      const uint32_t primary_item_id =
598
0
          AvifInfoInternalReadBigEndian(data, num_bytes_per_id);
599
0
      AVIFINFO_CHECK(primary_item_id <= AVIFINFO_MAX_VALUE, kAborted);
600
0
      features->has_primary_item = 1;
601
0
      features->primary_item_id = primary_item_id;
602
0
      AVIFINFO_CHECK_FOUND(
603
0
          AvifInfoInternalSkip(stream, box.content_size - num_bytes_per_id));
604
0
    } else if (!memcmp(box.type, "iprp", 4)) {
605
0
      AVIFINFO_CHECK_NOT_FOUND(
606
0
          ParseIprp(stream, box.content_size, num_parsed_boxes, features));
607
0
    } else if (!memcmp(box.type, "iref", 4)) {
608
0
      AVIFINFO_CHECK_NOT_FOUND(
609
0
          ParseIref(stream, box.content_size, num_parsed_boxes, features));
610
0
    } else {
611
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
612
0
    }
613
0
    num_remaining_bytes -= box.size;
614
0
  } while (num_remaining_bytes != 0);
615
  // According to ISO/IEC 14496-12:2012(E) 8.11.1.1 there is at most one "meta".
616
0
  AVIFINFO_RETURN(features->data_was_skipped ? kAborted : kInvalid);
617
0
}
618
619
//------------------------------------------------------------------------------
620
621
// Parses a file 'stream'. The file type is checked through the "ftyp" box.
622
0
static AvifInfoInternalStatus ParseFtyp(AvifInfoInternalStream* stream) {
623
0
  AvifInfoInternalBox box;
624
0
  uint32_t num_parsed_boxes = 0;
625
0
  AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, AVIFINFO_MAX_SIZE,
626
0
                                                &num_parsed_boxes, &box));
627
0
  AVIFINFO_CHECK(!memcmp(box.type, "ftyp", 4), kInvalid);
628
  // Iterate over brands. See ISO/IEC 14496-12:2012(E) 4.3.1
629
0
  AVIFINFO_CHECK(box.content_size >= 8, kInvalid);  // major_brand,minor_version
630
0
  for (uint32_t i = 0; i + 4 <= box.content_size; i += 4) {
631
0
    const uint8_t* data;
632
0
    AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data));
633
0
    if (i == 4) continue;  // Skip minor_version.
634
0
    if (!memcmp(data, "avif", 4) || !memcmp(data, "avis", 4)) {
635
0
      AVIFINFO_CHECK_FOUND(
636
0
          AvifInfoInternalSkip(stream, box.content_size - (i + 4)));
637
0
      return kFound;
638
0
    }
639
0
    AVIFINFO_CHECK(i <= 32 * 4, kAborted);  // Be reasonable.
640
0
  }
641
0
  AVIFINFO_RETURN(kInvalid);  // No AVIF brand no good.
642
0
}
643
644
// Parses a file 'stream'. 'features' are extracted from the "meta" box.
645
static AvifInfoInternalStatus ParseFile(AvifInfoInternalStream* stream,
646
                                        uint32_t* num_parsed_boxes,
647
0
                                        AvifInfoInternalFeatures* features) {
648
0
  while (1) {
649
0
    AvifInfoInternalBox box;
650
0
    AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, AVIFINFO_MAX_SIZE,
651
0
                                                  num_parsed_boxes, &box));
652
0
    if (!memcmp(box.type, "meta", 4)) {
653
0
      return ParseMeta(stream, box.content_size, num_parsed_boxes, features);
654
0
    } else {
655
0
      AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
656
0
    }
657
0
  }
658
0
  AVIFINFO_RETURN(kInvalid);  // No "meta" no good.
659
0
}
660
661
//------------------------------------------------------------------------------
662
// Helpers for converting the fixed-size input public API to the streamed one.
663
664
typedef struct {
665
  const uint8_t* data;
666
  size_t data_size;
667
} AvifInfoInternalForward;
668
669
static const uint8_t* AvifInfoInternalForwardRead(void* stream,
670
0
                                                  size_t num_bytes) {
671
0
  AvifInfoInternalForward* forward = (AvifInfoInternalForward*)stream;
672
0
  if (num_bytes > forward->data_size) return NULL;
673
0
  const uint8_t* data = forward->data;
674
0
  forward->data += num_bytes;
675
0
  forward->data_size -= num_bytes;
676
0
  return data;
677
0
}
678
679
0
static void AvifInfoInternalForwardSkip(void* stream, size_t num_bytes) {
680
0
  AvifInfoInternalForward* forward = (AvifInfoInternalForward*)stream;
681
0
  if (num_bytes > forward->data_size) num_bytes = forward->data_size;
682
0
  forward->data += num_bytes;
683
0
  forward->data_size -= num_bytes;
684
0
}
685
686
//------------------------------------------------------------------------------
687
// Fixed-size input public API
688
689
0
AvifInfoStatus AvifInfoIdentify(const uint8_t* data, size_t data_size) {
690
0
  AvifInfoInternalForward stream;
691
0
  stream.data = data;
692
0
  stream.data_size = data_size;
693
  // Forward null 'data' as a null 'stream' to handle it the same way.
694
0
  return AvifInfoIdentifyStream(
695
0
      (void*)&stream, (data == NULL) ? NULL : AvifInfoInternalForwardRead,
696
0
      AvifInfoInternalForwardSkip);
697
0
}
698
699
AvifInfoStatus AvifInfoGetFeatures(const uint8_t* data, size_t data_size,
700
0
                                   AvifInfoFeatures* features) {
701
0
  AvifInfoInternalForward stream;
702
0
  stream.data = data;
703
0
  stream.data_size = data_size;
704
0
  return AvifInfoGetFeaturesStream(
705
0
      (void*)&stream, (data == NULL) ? NULL : AvifInfoInternalForwardRead,
706
0
      AvifInfoInternalForwardSkip, features);
707
0
}
708
709
//------------------------------------------------------------------------------
710
// Streamed input API
711
712
AvifInfoStatus AvifInfoIdentifyStream(void* stream, read_stream_t read,
713
0
                                    skip_stream_t skip) {
714
0
  if (read == NULL) return kAvifInfoNotEnoughData;
715
716
0
  AvifInfoInternalStream internal_stream;
717
0
  internal_stream.stream = stream;
718
0
  internal_stream.read = read;
719
0
  internal_stream.skip = skip;  // Fallbacks to 'read' if null.
720
0
  return AvifInfoInternalConvertStatus(ParseFtyp(&internal_stream));
721
0
}
722
723
AvifInfoStatus AvifInfoGetFeaturesStream(void* stream, read_stream_t read,
724
                                    skip_stream_t skip,
725
0
                                    AvifInfoFeatures* features) {
726
0
  if (features != NULL) memset(features, 0, sizeof(*features));
727
0
  if (read == NULL) return kAvifInfoNotEnoughData;
728
729
0
  AvifInfoInternalStream internal_stream;
730
0
  internal_stream.stream = stream;
731
0
  internal_stream.read = read;
732
0
  internal_stream.skip = skip;  // Fallbacks to 'read' if null.
733
0
  uint32_t num_parsed_boxes = 0;
734
0
  AvifInfoInternalFeatures internal_features;
735
0
  memset(&internal_features, AVIFINFO_UNDEFINED, sizeof(internal_features));
736
737
  // Go through all relevant boxes sequentially.
738
0
  const AvifInfoInternalStatus status =
739
0
      ParseFile(&internal_stream, &num_parsed_boxes, &internal_features);
740
0
  if (status == kFound && features != NULL) {
741
0
    memcpy(features, &internal_features.primary_item_features,
742
0
           sizeof(*features));
743
0
  }
744
0
  return AvifInfoInternalConvertStatus(status);
745
0
}