xref: /php-src/ext/standard/libavifinfo/avifinfo.c (revision 38460c2c)
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 
AvifInfoInternalConvertStatus(AvifInfoInternalStatus s)27 static AvifInfoStatus AvifInfoInternalConvertStatus(AvifInfoInternalStatus s) {
28   return (s == kFound)                         ? kAvifInfoOk
29          : (s == kNotFound || s == kTruncated) ? kAvifInfoNotEnoughData
30          : (s == kAborted)                     ? kAvifInfoTooComplex
31                                                : kAvifInfoInvalidFile;
32 }
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 #define AVIFINFO_MAX_VALUE UINT8_MAX
40 #define AVIFINFO_UNDEFINED 0
41 // Maximum number of stored associations. Past that, they are skipped.
42 #define AVIFINFO_MAX_TILES 16
43 #define AVIFINFO_MAX_PROPS 32
44 #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.
AvifInfoInternalReadBigEndian(const uint8_t * input,uint32_t num_bytes)48 static uint32_t AvifInfoInternalReadBigEndian(const uint8_t* input,
49                                               uint32_t num_bytes) {
50   uint32_t value = 0;
51   for (uint32_t i = 0; i < num_bytes; ++i) {
52     value = (value << 8) | input[i];
53   }
54   return value;
55 }
56 
57 //------------------------------------------------------------------------------
58 // Convenience macros.
59 
60 #if defined(AVIFINFO_LOG_ERROR)  // Toggle to log encountered issues.
AvifInfoInternalLogError(const char * file,int line,AvifInfoInternalStatus status)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   do {                                \
78     return (check_status);            \
79   } while (0)
80 #endif
81 
82 #define AVIFINFO_CHECK(check_condition, check_status)      \
83   do {                                                     \
84     if (!(check_condition)) AVIFINFO_RETURN(check_status); \
85   } while (0)
86 #define AVIFINFO_CHECK_STATUS_IS(check_status, expected_status)            \
87   do {                                                                     \
88     const AvifInfoInternalStatus status_returned = (check_status);         \
89     AVIFINFO_CHECK(status_returned == (expected_status), status_returned); \
90   } while (0)
91 #define AVIFINFO_CHECK_FOUND(check_status) \
92   AVIFINFO_CHECK_STATUS_IS((check_status), kFound)
93 #define AVIFINFO_CHECK_NOT_FOUND(check_status) \
94   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.
AvifInfoInternalRead(AvifInfoInternalStream * stream,uint32_t num_bytes,const uint8_t ** data)108 static AvifInfoInternalStatus AvifInfoInternalRead(
109     AvifInfoInternalStream* stream, uint32_t num_bytes, const uint8_t** data) {
110   *data = stream->read(stream->stream, num_bytes);
111   AVIFINFO_CHECK(*data != NULL, kTruncated);
112   return kFound;
113 }
114 
115 // Skips 'num_bytes' from the 'stream'. 'num_bytes' can be zero.
AvifInfoInternalSkip(AvifInfoInternalStream * stream,uint32_t num_bytes)116 static AvifInfoInternalStatus AvifInfoInternalSkip(
117     AvifInfoInternalStream* stream, uint32_t num_bytes) {
118   // Avoid a call to the user-defined function for nothing.
119   if (num_bytes > 0) {
120     if (stream->skip == NULL) {
121       const uint8_t* unused;
122       while (num_bytes > AVIFINFO_MAX_NUM_READ_BYTES) {
123         AVIFINFO_CHECK_FOUND(
124             AvifInfoInternalRead(stream, AVIFINFO_MAX_NUM_READ_BYTES, &unused));
125         num_bytes -= AVIFINFO_MAX_NUM_READ_BYTES;
126       }
127       return AvifInfoInternalRead(stream, num_bytes, &unused);
128     }
129     stream->skip(stream->stream, num_bytes);
130   }
131   return kFound;
132 }
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.
AvifInfoInternalGetItemFeatures(AvifInfoInternalFeatures * f,uint32_t target_item_id,uint32_t tile_depth)175 static AvifInfoInternalStatus AvifInfoInternalGetItemFeatures(
176     AvifInfoInternalFeatures* f, uint32_t target_item_id, uint32_t tile_depth) {
177   for (uint32_t prop_item = 0; prop_item < f->num_props; ++prop_item) {
178     if (f->props[prop_item].item_id != target_item_id) continue;
179     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     if (target_item_id == f->primary_item_id &&
183         (f->primary_item_features.width == AVIFINFO_UNDEFINED ||
184          f->primary_item_features.height == AVIFINFO_UNDEFINED)) {
185       for (uint32_t i = 0; i < f->num_dim_props; ++i) {
186         if (f->dim_props[i].property_index != property_index) continue;
187         f->primary_item_features.width = f->dim_props[i].width;
188         f->primary_item_features.height = f->dim_props[i].height;
189         if (f->primary_item_features.bit_depth != AVIFINFO_UNDEFINED &&
190             f->primary_item_features.num_channels != AVIFINFO_UNDEFINED) {
191           return kFound;
192         }
193         break;
194       }
195     }
196     // Retrieve the bit depth and number of channels of the target item if not
197     // already done.
198     if (f->primary_item_features.bit_depth == AVIFINFO_UNDEFINED ||
199         f->primary_item_features.num_channels == AVIFINFO_UNDEFINED) {
200       for (uint32_t i = 0; i < f->num_chan_props; ++i) {
201         if (f->chan_props[i].property_index != property_index) continue;
202         f->primary_item_features.bit_depth = f->chan_props[i].bit_depth;
203         f->primary_item_features.num_channels = f->chan_props[i].num_channels;
204         if (f->primary_item_features.width != AVIFINFO_UNDEFINED &&
205             f->primary_item_features.height != AVIFINFO_UNDEFINED) {
206           return kFound;
207         }
208         break;
209       }
210     }
211   }
212 
213   // Check for the bit_depth and num_channels in a tile if not yet found.
214   for (uint32_t tile = 0; tile < f->num_tiles && tile_depth < 3; ++tile) {
215     if (f->tiles[tile].parent_item_id != target_item_id) continue;
216     AVIFINFO_CHECK_NOT_FOUND(AvifInfoInternalGetItemFeatures(
217         f, f->tiles[tile].tile_item_id, tile_depth + 1));
218   }
219   AVIFINFO_RETURN(kNotFound);
220 }
221 
222 // Generates the 'f->primary_item_features' from the AvifInfoInternalFeatures.
223 // Returns kNotFound if there is not enough information.
AvifInfoInternalGetPrimaryItemFeatures(AvifInfoInternalFeatures * f)224 static AvifInfoInternalStatus AvifInfoInternalGetPrimaryItemFeatures(
225     AvifInfoInternalFeatures* f) {
226   // Nothing to do without the primary item ID.
227   AVIFINFO_CHECK(f->has_primary_item, kNotFound);
228   // Early exit.
229   AVIFINFO_CHECK(f->num_dim_props > 0 && f->num_chan_props, kNotFound);
230   AVIFINFO_CHECK_FOUND(
231       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   if (f->has_alpha) ++f->primary_item_features.num_channels;
235   return kFound;
236 }
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').
AvifInfoInternalParseBox(AvifInfoInternalStream * stream,uint32_t num_remaining_bytes,uint32_t * num_parsed_boxes,AvifInfoInternalBox * box)252 static AvifInfoInternalStatus AvifInfoInternalParseBox(
253     AvifInfoInternalStream* stream, uint32_t num_remaining_bytes,
254     uint32_t* num_parsed_boxes, AvifInfoInternalBox* box) {
255   const uint8_t* data;
256   // See ISO/IEC 14496-12:2012(E) 4.2
257   uint32_t box_header_size = 8;  // box 32b size + 32b type (at least)
258   AVIFINFO_CHECK(box_header_size <= num_remaining_bytes, kInvalid);
259   AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data));
260   box->size = AvifInfoInternalReadBigEndian(data, sizeof(uint32_t));
261   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   if (box->size == 1) {
265     box_header_size += 8;
266     AVIFINFO_CHECK(box_header_size <= num_remaining_bytes, kInvalid);
267     AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data));
268     // Stop the parsing if any box has a size greater than 4GB.
269     AVIFINFO_CHECK(AvifInfoInternalReadBigEndian(data, sizeof(uint32_t)) == 0,
270                    kAborted);
271     // Read the 32 least-significant bits.
272     box->size = AvifInfoInternalReadBigEndian(data + 4, sizeof(uint32_t));
273   } else if (box->size == 0) {
274     box->size = num_remaining_bytes;
275   }
276   AVIFINFO_CHECK(box->size >= box_header_size, kInvalid);
277   AVIFINFO_CHECK(box->size <= num_remaining_bytes, kInvalid);
278 
279   const int has_fullbox_header =
280       !memcmp(box->type, "meta", 4) || !memcmp(box->type, "pitm", 4) ||
281       !memcmp(box->type, "ipma", 4) || !memcmp(box->type, "ispe", 4) ||
282       !memcmp(box->type, "pixi", 4) || !memcmp(box->type, "iref", 4) ||
283       !memcmp(box->type, "auxC", 4);
284   if (has_fullbox_header) box_header_size += 4;
285   AVIFINFO_CHECK(box->size >= box_header_size, kInvalid);
286   box->content_size = box->size - box_header_size;
287   // Avoid timeouts. The maximum number of parsed boxes is arbitrary.
288   ++*num_parsed_boxes;
289   AVIFINFO_CHECK(*num_parsed_boxes < 4096, kAborted);
290 
291   box->version = 0;
292   box->flags = 0;
293   if (has_fullbox_header) {
294     AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data));
295     box->version = AvifInfoInternalReadBigEndian(data, 1);
296     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     uint32_t is_parsable = 1;
301     if (!memcmp(box->type, "meta", 4)) is_parsable = (box->version <= 0);
302     if (!memcmp(box->type, "pitm", 4)) is_parsable = (box->version <= 1);
303     if (!memcmp(box->type, "ipma", 4)) is_parsable = (box->version <= 1);
304     if (!memcmp(box->type, "ispe", 4)) is_parsable = (box->version <= 0);
305     if (!memcmp(box->type, "pixi", 4)) is_parsable = (box->version <= 0);
306     if (!memcmp(box->type, "iref", 4)) is_parsable = (box->version <= 1);
307     if (!memcmp(box->type, "auxC", 4)) is_parsable = (box->version <= 0);
308     // Instead of considering this file as invalid, skip unparsable boxes.
309     if (!is_parsable) memcpy(box->type, "\0skp", 4);  // \0 so not a valid type
310   }
311   return kFound;
312 }
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.
ParseIpco(AvifInfoInternalStream * stream,uint32_t num_remaining_bytes,uint32_t * num_parsed_boxes,AvifInfoInternalFeatures * features)319 static AvifInfoInternalStatus ParseIpco(AvifInfoInternalStream* stream,
320                                         uint32_t num_remaining_bytes,
321                                         uint32_t* num_parsed_boxes,
322                                         AvifInfoInternalFeatures* features) {
323   uint32_t box_index = 1;  // 1-based index. Used for iterating over properties.
324   do {
325     AvifInfoInternalBox box;
326     AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes,
327                                                   num_parsed_boxes, &box));
328 
329     if (!memcmp(box.type, "ispe", 4)) {
330       // See ISO/IEC 23008-12:2017(E) 6.5.3.2
331       const uint8_t* data;
332       AVIFINFO_CHECK(box.content_size >= 8, kInvalid);
333       AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 8, &data));
334       const uint32_t width = AvifInfoInternalReadBigEndian(data + 0, 4);
335       const uint32_t height = AvifInfoInternalReadBigEndian(data + 4, 4);
336       AVIFINFO_CHECK(width != 0 && height != 0, kInvalid);
337       if (features->num_dim_props < AVIFINFO_MAX_FEATURES &&
338           box_index <= AVIFINFO_MAX_VALUE) {
339         features->dim_props[features->num_dim_props].property_index = box_index;
340         features->dim_props[features->num_dim_props].width = width;
341         features->dim_props[features->num_dim_props].height = height;
342         ++features->num_dim_props;
343       } else {
344         features->data_was_skipped = 1;
345       }
346       AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size - 8));
347     } else if (!memcmp(box.type, "pixi", 4)) {
348       // See ISO/IEC 23008-12:2017(E) 6.5.6.2
349       const uint8_t* data;
350       AVIFINFO_CHECK(box.content_size >= 1, kInvalid);
351       AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data));
352       const uint32_t num_channels = AvifInfoInternalReadBigEndian(data + 0, 1);
353       AVIFINFO_CHECK(num_channels >= 1, kInvalid);
354       AVIFINFO_CHECK(box.content_size >= 1 + num_channels, kInvalid);
355       AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data));
356       const uint32_t bit_depth = AvifInfoInternalReadBigEndian(data, 1);
357       AVIFINFO_CHECK(bit_depth >= 1, kInvalid);
358       for (uint32_t i = 1; i < num_channels; ++i) {
359         AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 1, &data));
360         // Bit depth should be the same for all channels.
361         AVIFINFO_CHECK(AvifInfoInternalReadBigEndian(data, 1) == bit_depth,
362                        kInvalid);
363         AVIFINFO_CHECK(i <= 32, kAborted);  // Be reasonable.
364       }
365       if (features->num_chan_props < AVIFINFO_MAX_FEATURES &&
366           box_index <= AVIFINFO_MAX_VALUE && bit_depth <= AVIFINFO_MAX_VALUE &&
367           num_channels <= AVIFINFO_MAX_VALUE) {
368         features->chan_props[features->num_chan_props].property_index =
369             box_index;
370         features->chan_props[features->num_chan_props].bit_depth = bit_depth;
371         features->chan_props[features->num_chan_props].num_channels =
372             num_channels;
373         ++features->num_chan_props;
374       } else {
375         features->data_was_skipped = 1;
376       }
377       AVIFINFO_CHECK_FOUND(
378           AvifInfoInternalSkip(stream, box.content_size - (1 + num_channels)));
379     } 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       const uint8_t* data;
384       AVIFINFO_CHECK(box.content_size >= 3, kInvalid);
385       AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 3, &data));
386       const int high_bitdepth = (data[2] & 0x40) != 0;
387       const int twelve_bit = (data[2] & 0x20) != 0;
388       const int monochrome = (data[2] & 0x10) != 0;
389       if (twelve_bit) {
390         AVIFINFO_CHECK(high_bitdepth, kInvalid);
391       }
392       if (features->num_chan_props < AVIFINFO_MAX_FEATURES &&
393           box_index <= AVIFINFO_MAX_VALUE) {
394         features->chan_props[features->num_chan_props].property_index =
395             box_index;
396         features->chan_props[features->num_chan_props].bit_depth =
397             high_bitdepth ? twelve_bit ? 12 : 10 : 8;
398         features->chan_props[features->num_chan_props].num_channels =
399             monochrome ? 1 : 3;
400         ++features->num_chan_props;
401       } else {
402         features->data_was_skipped = 1;
403       }
404       AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size - 3));
405     } 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       const char* kAlphaStr = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha";
409       const uint32_t kAlphaStrLength = 44;  // Includes terminating character.
410       if (box.content_size >= kAlphaStrLength) {
411         const uint8_t* data;
412         AVIFINFO_CHECK_FOUND(
413             AvifInfoInternalRead(stream, kAlphaStrLength, &data));
414         const char* const aux_type = (const char*)data;
415         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           features->has_alpha = 1;
419         }
420         AVIFINFO_CHECK_FOUND(
421             AvifInfoInternalSkip(stream, box.content_size - kAlphaStrLength));
422       } else {
423         AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
424       }
425     } else {
426       AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
427     }
428     ++box_index;
429     num_remaining_bytes -= box.size;
430   } while (num_remaining_bytes > 0);
431   AVIFINFO_RETURN(kNotFound);
432 }
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.
ParseIprp(AvifInfoInternalStream * stream,uint32_t num_remaining_bytes,uint32_t * num_parsed_boxes,AvifInfoInternalFeatures * features)436 static AvifInfoInternalStatus ParseIprp(AvifInfoInternalStream* stream,
437                                         uint32_t num_remaining_bytes,
438                                         uint32_t* num_parsed_boxes,
439                                         AvifInfoInternalFeatures* features) {
440   do {
441     AvifInfoInternalBox box;
442     AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes,
443                                                   num_parsed_boxes, &box));
444 
445     if (!memcmp(box.type, "ipco", 4)) {
446       AVIFINFO_CHECK_NOT_FOUND(
447           ParseIpco(stream, box.content_size, num_parsed_boxes, features));
448     } else if (!memcmp(box.type, "ipma", 4)) {
449       // See ISO/IEC 23008-12:2017(E) 9.3.2
450       uint32_t num_read_bytes = 4;
451       const uint8_t* data;
452       AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
453       AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data));
454       const uint32_t entry_count = AvifInfoInternalReadBigEndian(data, 4);
455       const uint32_t id_num_bytes = (box.version < 1) ? 2 : 4;
456       const uint32_t index_num_bytes = (box.flags & 1) ? 2 : 1;
457       const uint32_t essential_bit_mask = (box.flags & 1) ? 0x8000 : 0x80;
458 
459       for (uint32_t entry = 0; entry < entry_count; ++entry) {
460         if (entry >= AVIFINFO_MAX_PROPS ||
461             features->num_props >= AVIFINFO_MAX_PROPS) {
462           features->data_was_skipped = 1;
463           break;
464         }
465         num_read_bytes += id_num_bytes + 1;
466         AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
467         AVIFINFO_CHECK_FOUND(
468             AvifInfoInternalRead(stream, id_num_bytes + 1, &data));
469         const uint32_t item_id =
470             AvifInfoInternalReadBigEndian(data, id_num_bytes);
471         const uint32_t association_count =
472             AvifInfoInternalReadBigEndian(data + id_num_bytes, 1);
473 
474         uint32_t property;
475         for (property = 0; property < association_count; ++property) {
476           if (property >= AVIFINFO_MAX_PROPS ||
477               features->num_props >= AVIFINFO_MAX_PROPS) {
478             features->data_was_skipped = 1;
479             break;
480           }
481           num_read_bytes += index_num_bytes;
482           AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
483           AVIFINFO_CHECK_FOUND(
484               AvifInfoInternalRead(stream, index_num_bytes, &data));
485           const uint32_t value =
486               AvifInfoInternalReadBigEndian(data, index_num_bytes);
487           // const int essential = (value & essential_bit_mask);  // Unused.
488           const uint32_t property_index = (value & ~essential_bit_mask);
489           if (property_index <= AVIFINFO_MAX_VALUE &&
490               item_id <= AVIFINFO_MAX_VALUE) {
491             features->props[features->num_props].property_index =
492                 property_index;
493             features->props[features->num_props].item_id = item_id;
494             ++features->num_props;
495           } else {
496             features->data_was_skipped = 1;
497           }
498         }
499         if (property < association_count) break;  // Do not read garbage.
500       }
501 
502       // If all features are available now, do not look further.
503       AVIFINFO_CHECK_NOT_FOUND(
504           AvifInfoInternalGetPrimaryItemFeatures(features));
505 
506       AVIFINFO_CHECK_FOUND(
507           AvifInfoInternalSkip(stream, box.content_size - num_read_bytes));
508     } else {
509       AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
510     }
511     num_remaining_bytes -= box.size;
512   } while (num_remaining_bytes != 0);
513   AVIFINFO_RETURN(kNotFound);
514 }
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.
ParseIref(AvifInfoInternalStream * stream,uint32_t num_remaining_bytes,uint32_t * num_parsed_boxes,AvifInfoInternalFeatures * features)522 static AvifInfoInternalStatus ParseIref(AvifInfoInternalStream* stream,
523                                         uint32_t num_remaining_bytes,
524                                         uint32_t* num_parsed_boxes,
525                                         AvifInfoInternalFeatures* features) {
526   do {
527     AvifInfoInternalBox box;
528     AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes,
529                                                   num_parsed_boxes, &box));
530 
531     if (!memcmp(box.type, "dimg", 4)) {
532       // See ISO/IEC 14496-12:2015(E) 8.11.12.2
533       const uint32_t num_bytes_per_id = (box.version == 0) ? 2 : 4;
534       uint32_t num_read_bytes = num_bytes_per_id + 2;
535       const uint8_t* data;
536       AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
537       AVIFINFO_CHECK_FOUND(
538           AvifInfoInternalRead(stream, num_bytes_per_id + 2, &data));
539       const uint32_t from_item_id =
540           AvifInfoInternalReadBigEndian(data, num_bytes_per_id);
541       const uint32_t reference_count =
542           AvifInfoInternalReadBigEndian(data + num_bytes_per_id, 2);
543 
544       for (uint32_t i = 0; i < reference_count; ++i) {
545         if (i >= AVIFINFO_MAX_TILES) {
546           features->data_was_skipped = 1;
547           break;
548         }
549         num_read_bytes += num_bytes_per_id;
550         AVIFINFO_CHECK(box.content_size >= num_read_bytes, kInvalid);
551         AVIFINFO_CHECK_FOUND(
552             AvifInfoInternalRead(stream, num_bytes_per_id, &data));
553         const uint32_t to_item_id =
554             AvifInfoInternalReadBigEndian(data, num_bytes_per_id);
555         if (from_item_id <= AVIFINFO_MAX_VALUE &&
556             to_item_id <= AVIFINFO_MAX_VALUE &&
557             features->num_tiles < AVIFINFO_MAX_TILES) {
558           features->tiles[features->num_tiles].tile_item_id = to_item_id;
559           features->tiles[features->num_tiles].parent_item_id = from_item_id;
560           ++features->num_tiles;
561         } else {
562           features->data_was_skipped = 1;
563         }
564       }
565 
566       // If all features are available now, do not look further.
567       AVIFINFO_CHECK_NOT_FOUND(
568           AvifInfoInternalGetPrimaryItemFeatures(features));
569     } else {
570       AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
571     }
572     num_remaining_bytes -= box.size;
573   } while (num_remaining_bytes > 0);
574   AVIFINFO_RETURN(kNotFound);
575 }
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'.
ParseMeta(AvifInfoInternalStream * stream,uint32_t num_remaining_bytes,uint32_t * num_parsed_boxes,AvifInfoInternalFeatures * features)581 static AvifInfoInternalStatus ParseMeta(AvifInfoInternalStream* stream,
582                                         uint32_t num_remaining_bytes,
583                                         uint32_t* num_parsed_boxes,
584                                         AvifInfoInternalFeatures* features) {
585   do {
586     AvifInfoInternalBox box;
587     AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, num_remaining_bytes,
588                                                   num_parsed_boxes, &box));
589 
590     if (!memcmp(box.type, "pitm", 4)) {
591       // See ISO/IEC 14496-12:2015(E) 8.11.4.2
592       const uint32_t num_bytes_per_id = (box.version == 0) ? 2 : 4;
593       const uint8_t* data;
594       AVIFINFO_CHECK(num_bytes_per_id <= num_remaining_bytes, kInvalid);
595       AVIFINFO_CHECK_FOUND(
596           AvifInfoInternalRead(stream, num_bytes_per_id, &data));
597       const uint32_t primary_item_id =
598           AvifInfoInternalReadBigEndian(data, num_bytes_per_id);
599       AVIFINFO_CHECK(primary_item_id <= AVIFINFO_MAX_VALUE, kAborted);
600       features->has_primary_item = 1;
601       features->primary_item_id = primary_item_id;
602       AVIFINFO_CHECK_FOUND(
603           AvifInfoInternalSkip(stream, box.content_size - num_bytes_per_id));
604     } else if (!memcmp(box.type, "iprp", 4)) {
605       AVIFINFO_CHECK_NOT_FOUND(
606           ParseIprp(stream, box.content_size, num_parsed_boxes, features));
607     } else if (!memcmp(box.type, "iref", 4)) {
608       AVIFINFO_CHECK_NOT_FOUND(
609           ParseIref(stream, box.content_size, num_parsed_boxes, features));
610     } else {
611       AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
612     }
613     num_remaining_bytes -= box.size;
614   } 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   AVIFINFO_RETURN(features->data_was_skipped ? kAborted : kInvalid);
617 }
618 
619 //------------------------------------------------------------------------------
620 
621 // Parses a file 'stream'. The file type is checked through the "ftyp" box.
ParseFtyp(AvifInfoInternalStream * stream)622 static AvifInfoInternalStatus ParseFtyp(AvifInfoInternalStream* stream) {
623   AvifInfoInternalBox box;
624   uint32_t num_parsed_boxes = 0;
625   AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, AVIFINFO_MAX_SIZE,
626                                                 &num_parsed_boxes, &box));
627   AVIFINFO_CHECK(!memcmp(box.type, "ftyp", 4), kInvalid);
628   // Iterate over brands. See ISO/IEC 14496-12:2012(E) 4.3.1
629   AVIFINFO_CHECK(box.content_size >= 8, kInvalid);  // major_brand,minor_version
630   for (uint32_t i = 0; i + 4 <= box.content_size; i += 4) {
631     const uint8_t* data;
632     AVIFINFO_CHECK_FOUND(AvifInfoInternalRead(stream, 4, &data));
633     if (i == 4) continue;  // Skip minor_version.
634     if (!memcmp(data, "avif", 4) || !memcmp(data, "avis", 4)) {
635       AVIFINFO_CHECK_FOUND(
636           AvifInfoInternalSkip(stream, box.content_size - (i + 4)));
637       return kFound;
638     }
639     AVIFINFO_CHECK(i <= 32 * 4, kAborted);  // Be reasonable.
640   }
641   AVIFINFO_RETURN(kInvalid);  // No AVIF brand no good.
642 }
643 
644 // Parses a file 'stream'. 'features' are extracted from the "meta" box.
ParseFile(AvifInfoInternalStream * stream,uint32_t * num_parsed_boxes,AvifInfoInternalFeatures * features)645 static AvifInfoInternalStatus ParseFile(AvifInfoInternalStream* stream,
646                                         uint32_t* num_parsed_boxes,
647                                         AvifInfoInternalFeatures* features) {
648   while (1) {
649     AvifInfoInternalBox box;
650     AVIFINFO_CHECK_FOUND(AvifInfoInternalParseBox(stream, AVIFINFO_MAX_SIZE,
651                                                   num_parsed_boxes, &box));
652     if (!memcmp(box.type, "meta", 4)) {
653       return ParseMeta(stream, box.content_size, num_parsed_boxes, features);
654     } else {
655       AVIFINFO_CHECK_FOUND(AvifInfoInternalSkip(stream, box.content_size));
656     }
657   }
658   AVIFINFO_RETURN(kInvalid);  // No "meta" no good.
659 }
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 
AvifInfoInternalForwardRead(void * stream,size_t num_bytes)669 static const uint8_t* AvifInfoInternalForwardRead(void* stream,
670                                                   size_t num_bytes) {
671   AvifInfoInternalForward* forward = (AvifInfoInternalForward*)stream;
672   if (num_bytes > forward->data_size) return NULL;
673   const uint8_t* data = forward->data;
674   forward->data += num_bytes;
675   forward->data_size -= num_bytes;
676   return data;
677 }
678 
AvifInfoInternalForwardSkip(void * stream,size_t num_bytes)679 static void AvifInfoInternalForwardSkip(void* stream, size_t num_bytes) {
680   AvifInfoInternalForward* forward = (AvifInfoInternalForward*)stream;
681   if (num_bytes > forward->data_size) num_bytes = forward->data_size;
682   forward->data += num_bytes;
683   forward->data_size -= num_bytes;
684 }
685 
686 //------------------------------------------------------------------------------
687 // Fixed-size input public API
688 
AvifInfoIdentify(const uint8_t * data,size_t data_size)689 AvifInfoStatus AvifInfoIdentify(const uint8_t* data, size_t data_size) {
690   AvifInfoInternalForward stream;
691   stream.data = data;
692   stream.data_size = data_size;
693   // Forward null 'data' as a null 'stream' to handle it the same way.
694   return AvifInfoIdentifyStream(
695       (void*)&stream, (data == NULL) ? NULL : AvifInfoInternalForwardRead,
696       AvifInfoInternalForwardSkip);
697 }
698 
AvifInfoGetFeatures(const uint8_t * data,size_t data_size,AvifInfoFeatures * features)699 AvifInfoStatus AvifInfoGetFeatures(const uint8_t* data, size_t data_size,
700                                    AvifInfoFeatures* features) {
701   AvifInfoInternalForward stream;
702   stream.data = data;
703   stream.data_size = data_size;
704   return AvifInfoGetFeaturesStream(
705       (void*)&stream, (data == NULL) ? NULL : AvifInfoInternalForwardRead,
706       AvifInfoInternalForwardSkip, features);
707 }
708 
709 //------------------------------------------------------------------------------
710 // Streamed input API
711 
AvifInfoIdentifyStream(void * stream,read_stream_t read,skip_stream_t skip)712 AvifInfoStatus AvifInfoIdentifyStream(void* stream, read_stream_t read,
713                                     skip_stream_t skip) {
714   if (read == NULL) return kAvifInfoNotEnoughData;
715 
716   AvifInfoInternalStream internal_stream;
717   internal_stream.stream = stream;
718   internal_stream.read = read;
719   internal_stream.skip = skip;  // Fallbacks to 'read' if null.
720   return AvifInfoInternalConvertStatus(ParseFtyp(&internal_stream));
721 }
722 
AvifInfoGetFeaturesStream(void * stream,read_stream_t read,skip_stream_t skip,AvifInfoFeatures * features)723 AvifInfoStatus AvifInfoGetFeaturesStream(void* stream, read_stream_t read,
724                                     skip_stream_t skip,
725                                     AvifInfoFeatures* features) {
726   if (features != NULL) memset(features, 0, sizeof(*features));
727   if (read == NULL) return kAvifInfoNotEnoughData;
728 
729   AvifInfoInternalStream internal_stream;
730   internal_stream.stream = stream;
731   internal_stream.read = read;
732   internal_stream.skip = skip;  // Fallbacks to 'read' if null.
733   uint32_t num_parsed_boxes = 0;
734   AvifInfoInternalFeatures internal_features;
735   memset(&internal_features, AVIFINFO_UNDEFINED, sizeof(internal_features));
736 
737   // Go through all relevant boxes sequentially.
738   const AvifInfoInternalStatus status =
739       ParseFile(&internal_stream, &num_parsed_boxes, &internal_features);
740   if (status == kFound && features != NULL) {
741     memcpy(features, &internal_features.primary_item_features,
742            sizeof(*features));
743   }
744   return AvifInfoInternalConvertStatus(status);
745 }
746