1// Copyright 2011 Google Inc. All Rights Reserved.
2//
3// Use of this source code is governed by a BSD-style license
4// that can be found in the COPYING file in the root of the source
5// tree. An additional intellectual property rights grant can be found
6// in the file PATENTS. All contributing project authors may
7// be found in the AUTHORS file in the root of the source tree.
8// -----------------------------------------------------------------------------
9//
10// Internal objects and utils for mux.
11//
12// Authors: Urvang (urvang@google.com)
13//          Vikas (vikasa@google.com)
14
15#include <assert.h>
16#include "./muxi.h"
17#include "../utils/utils.h"
18
19#define UNDEFINED_CHUNK_SIZE ((uint32_t)(-1))
20
21const ChunkInfo kChunks[] = {
22  { MKFOURCC('V', 'P', '8', 'X'),  WEBP_CHUNK_VP8X,    VP8X_CHUNK_SIZE },
23  { MKFOURCC('I', 'C', 'C', 'P'),  WEBP_CHUNK_ICCP,    UNDEFINED_CHUNK_SIZE },
24  { MKFOURCC('A', 'N', 'I', 'M'),  WEBP_CHUNK_ANIM,    ANIM_CHUNK_SIZE },
25  { MKFOURCC('A', 'N', 'M', 'F'),  WEBP_CHUNK_ANMF,    ANMF_CHUNK_SIZE },
26  { MKFOURCC('A', 'L', 'P', 'H'),  WEBP_CHUNK_ALPHA,   UNDEFINED_CHUNK_SIZE },
27  { MKFOURCC('V', 'P', '8', ' '),  WEBP_CHUNK_IMAGE,   UNDEFINED_CHUNK_SIZE },
28  { MKFOURCC('V', 'P', '8', 'L'),  WEBP_CHUNK_IMAGE,   UNDEFINED_CHUNK_SIZE },
29  { MKFOURCC('E', 'X', 'I', 'F'),  WEBP_CHUNK_EXIF,    UNDEFINED_CHUNK_SIZE },
30  { MKFOURCC('X', 'M', 'P', ' '),  WEBP_CHUNK_XMP,     UNDEFINED_CHUNK_SIZE },
31  { NIL_TAG,                       WEBP_CHUNK_UNKNOWN, UNDEFINED_CHUNK_SIZE },
32
33  { NIL_TAG,                       WEBP_CHUNK_NIL,     UNDEFINED_CHUNK_SIZE }
34};
35
36//------------------------------------------------------------------------------
37
38int WebPGetMuxVersion(void) {
39  return (MUX_MAJ_VERSION << 16) | (MUX_MIN_VERSION << 8) | MUX_REV_VERSION;
40}
41
42//------------------------------------------------------------------------------
43// Life of a chunk object.
44
45void ChunkInit(WebPChunk* const chunk) {
46  assert(chunk);
47  memset(chunk, 0, sizeof(*chunk));
48  chunk->tag_ = NIL_TAG;
49}
50
51WebPChunk* ChunkRelease(WebPChunk* const chunk) {
52  WebPChunk* next;
53  if (chunk == NULL) return NULL;
54  if (chunk->owner_) {
55    WebPDataClear(&chunk->data_);
56  }
57  next = chunk->next_;
58  ChunkInit(chunk);
59  return next;
60}
61
62//------------------------------------------------------------------------------
63// Chunk misc methods.
64
65CHUNK_INDEX ChunkGetIndexFromTag(uint32_t tag) {
66  int i;
67  for (i = 0; kChunks[i].tag != NIL_TAG; ++i) {
68    if (tag == kChunks[i].tag) return (CHUNK_INDEX)i;
69  }
70  return IDX_UNKNOWN;
71}
72
73WebPChunkId ChunkGetIdFromTag(uint32_t tag) {
74  int i;
75  for (i = 0; kChunks[i].tag != NIL_TAG; ++i) {
76    if (tag == kChunks[i].tag) return kChunks[i].id;
77  }
78  return WEBP_CHUNK_UNKNOWN;
79}
80
81uint32_t ChunkGetTagFromFourCC(const char fourcc[4]) {
82  return MKFOURCC(fourcc[0], fourcc[1], fourcc[2], fourcc[3]);
83}
84
85CHUNK_INDEX ChunkGetIndexFromFourCC(const char fourcc[4]) {
86  const uint32_t tag = ChunkGetTagFromFourCC(fourcc);
87  return ChunkGetIndexFromTag(tag);
88}
89
90//------------------------------------------------------------------------------
91// Chunk search methods.
92
93// Returns next chunk in the chunk list with the given tag.
94static WebPChunk* ChunkSearchNextInList(WebPChunk* chunk, uint32_t tag) {
95  while (chunk != NULL && chunk->tag_ != tag) {
96    chunk = chunk->next_;
97  }
98  return chunk;
99}
100
101WebPChunk* ChunkSearchList(WebPChunk* first, uint32_t nth, uint32_t tag) {
102  uint32_t iter = nth;
103  first = ChunkSearchNextInList(first, tag);
104  if (first == NULL) return NULL;
105
106  while (--iter != 0) {
107    WebPChunk* next_chunk = ChunkSearchNextInList(first->next_, tag);
108    if (next_chunk == NULL) break;
109    first = next_chunk;
110  }
111  return ((nth > 0) && (iter > 0)) ? NULL : first;
112}
113
114// Outputs a pointer to 'prev_chunk->next_',
115//   where 'prev_chunk' is the pointer to the chunk at position (nth - 1).
116// Returns true if nth chunk was found.
117static int ChunkSearchListToSet(WebPChunk** chunk_list, uint32_t nth,
118                                WebPChunk*** const location) {
119  uint32_t count = 0;
120  assert(chunk_list != NULL);
121  *location = chunk_list;
122
123  while (*chunk_list != NULL) {
124    WebPChunk* const cur_chunk = *chunk_list;
125    ++count;
126    if (count == nth) return 1;  // Found.
127    chunk_list = &cur_chunk->next_;
128    *location = chunk_list;
129  }
130
131  // *chunk_list is ok to be NULL if adding at last location.
132  return (nth == 0 || (count == nth - 1)) ? 1 : 0;
133}
134
135//------------------------------------------------------------------------------
136// Chunk writer methods.
137
138WebPMuxError ChunkAssignData(WebPChunk* chunk, const WebPData* const data,
139                             int copy_data, uint32_t tag) {
140  // For internally allocated chunks, always copy data & make it owner of data.
141  if (tag == kChunks[IDX_VP8X].tag || tag == kChunks[IDX_ANIM].tag) {
142    copy_data = 1;
143  }
144
145  ChunkRelease(chunk);
146
147  if (data != NULL) {
148    if (copy_data) {        // Copy data.
149      if (!WebPDataCopy(data, &chunk->data_)) return WEBP_MUX_MEMORY_ERROR;
150      chunk->owner_ = 1;    // Chunk is owner of data.
151    } else {                // Don't copy data.
152      chunk->data_ = *data;
153    }
154  }
155  chunk->tag_ = tag;
156  return WEBP_MUX_OK;
157}
158
159WebPMuxError ChunkSetNth(WebPChunk* chunk, WebPChunk** chunk_list,
160                         uint32_t nth) {
161  WebPChunk* new_chunk;
162
163  if (!ChunkSearchListToSet(chunk_list, nth, &chunk_list)) {
164    return WEBP_MUX_NOT_FOUND;
165  }
166
167  new_chunk = (WebPChunk*)WebPSafeMalloc(1ULL, sizeof(*new_chunk));
168  if (new_chunk == NULL) return WEBP_MUX_MEMORY_ERROR;
169  *new_chunk = *chunk;
170  chunk->owner_ = 0;
171  new_chunk->next_ = *chunk_list;
172  *chunk_list = new_chunk;
173  return WEBP_MUX_OK;
174}
175
176//------------------------------------------------------------------------------
177// Chunk deletion method(s).
178
179WebPChunk* ChunkDelete(WebPChunk* const chunk) {
180  WebPChunk* const next = ChunkRelease(chunk);
181  WebPSafeFree(chunk);
182  return next;
183}
184
185void ChunkListDelete(WebPChunk** const chunk_list) {
186  while (*chunk_list != NULL) {
187    *chunk_list = ChunkDelete(*chunk_list);
188  }
189}
190
191//------------------------------------------------------------------------------
192// Chunk serialization methods.
193
194static uint8_t* ChunkEmit(const WebPChunk* const chunk, uint8_t* dst) {
195  const size_t chunk_size = chunk->data_.size;
196  assert(chunk);
197  assert(chunk->tag_ != NIL_TAG);
198  PutLE32(dst + 0, chunk->tag_);
199  PutLE32(dst + TAG_SIZE, (uint32_t)chunk_size);
200  assert(chunk_size == (uint32_t)chunk_size);
201  memcpy(dst + CHUNK_HEADER_SIZE, chunk->data_.bytes, chunk_size);
202  if (chunk_size & 1)
203    dst[CHUNK_HEADER_SIZE + chunk_size] = 0;  // Add padding.
204  return dst + ChunkDiskSize(chunk);
205}
206
207uint8_t* ChunkListEmit(const WebPChunk* chunk_list, uint8_t* dst) {
208  while (chunk_list != NULL) {
209    dst = ChunkEmit(chunk_list, dst);
210    chunk_list = chunk_list->next_;
211  }
212  return dst;
213}
214
215size_t ChunkListDiskSize(const WebPChunk* chunk_list) {
216  size_t size = 0;
217  while (chunk_list != NULL) {
218    size += ChunkDiskSize(chunk_list);
219    chunk_list = chunk_list->next_;
220  }
221  return size;
222}
223
224//------------------------------------------------------------------------------
225// Life of a MuxImage object.
226
227void MuxImageInit(WebPMuxImage* const wpi) {
228  assert(wpi);
229  memset(wpi, 0, sizeof(*wpi));
230}
231
232WebPMuxImage* MuxImageRelease(WebPMuxImage* const wpi) {
233  WebPMuxImage* next;
234  if (wpi == NULL) return NULL;
235  ChunkDelete(wpi->header_);
236  ChunkDelete(wpi->alpha_);
237  ChunkDelete(wpi->img_);
238  ChunkListDelete(&wpi->unknown_);
239
240  next = wpi->next_;
241  MuxImageInit(wpi);
242  return next;
243}
244
245//------------------------------------------------------------------------------
246// MuxImage search methods.
247
248// Get a reference to appropriate chunk list within an image given chunk tag.
249static WebPChunk** GetChunkListFromId(const WebPMuxImage* const wpi,
250                                      WebPChunkId id) {
251  assert(wpi != NULL);
252  switch (id) {
253    case WEBP_CHUNK_ANMF:  return (WebPChunk**)&wpi->header_;
254    case WEBP_CHUNK_ALPHA: return (WebPChunk**)&wpi->alpha_;
255    case WEBP_CHUNK_IMAGE: return (WebPChunk**)&wpi->img_;
256    default: return NULL;
257  }
258}
259
260int MuxImageCount(const WebPMuxImage* wpi_list, WebPChunkId id) {
261  int count = 0;
262  const WebPMuxImage* current;
263  for (current = wpi_list; current != NULL; current = current->next_) {
264    if (id == WEBP_CHUNK_NIL) {
265      ++count;  // Special case: count all images.
266    } else {
267      const WebPChunk* const wpi_chunk = *GetChunkListFromId(current, id);
268      if (wpi_chunk != NULL) {
269        const WebPChunkId wpi_chunk_id = ChunkGetIdFromTag(wpi_chunk->tag_);
270        if (wpi_chunk_id == id) ++count;  // Count images with a matching 'id'.
271      }
272    }
273  }
274  return count;
275}
276
277// Outputs a pointer to 'prev_wpi->next_',
278//   where 'prev_wpi' is the pointer to the image at position (nth - 1).
279// Returns true if nth image was found.
280static int SearchImageToGetOrDelete(WebPMuxImage** wpi_list, uint32_t nth,
281                                    WebPMuxImage*** const location) {
282  uint32_t count = 0;
283  assert(wpi_list);
284  *location = wpi_list;
285
286  if (nth == 0) {
287    nth = MuxImageCount(*wpi_list, WEBP_CHUNK_NIL);
288    if (nth == 0) return 0;  // Not found.
289  }
290
291  while (*wpi_list != NULL) {
292    WebPMuxImage* const cur_wpi = *wpi_list;
293    ++count;
294    if (count == nth) return 1;  // Found.
295    wpi_list = &cur_wpi->next_;
296    *location = wpi_list;
297  }
298  return 0;  // Not found.
299}
300
301//------------------------------------------------------------------------------
302// MuxImage writer methods.
303
304WebPMuxError MuxImagePush(const WebPMuxImage* wpi, WebPMuxImage** wpi_list) {
305  WebPMuxImage* new_wpi;
306
307  while (*wpi_list != NULL) {
308    WebPMuxImage* const cur_wpi = *wpi_list;
309    if (cur_wpi->next_ == NULL) break;
310    wpi_list = &cur_wpi->next_;
311  }
312
313  new_wpi = (WebPMuxImage*)WebPSafeMalloc(1ULL, sizeof(*new_wpi));
314  if (new_wpi == NULL) return WEBP_MUX_MEMORY_ERROR;
315  *new_wpi = *wpi;
316  new_wpi->next_ = NULL;
317
318  if (*wpi_list != NULL) {
319    (*wpi_list)->next_ = new_wpi;
320  } else {
321    *wpi_list = new_wpi;
322  }
323  return WEBP_MUX_OK;
324}
325
326//------------------------------------------------------------------------------
327// MuxImage deletion methods.
328
329WebPMuxImage* MuxImageDelete(WebPMuxImage* const wpi) {
330  // Delete the components of wpi. If wpi is NULL this is a noop.
331  WebPMuxImage* const next = MuxImageRelease(wpi);
332  WebPSafeFree(wpi);
333  return next;
334}
335
336WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth) {
337  assert(wpi_list);
338  if (!SearchImageToGetOrDelete(wpi_list, nth, &wpi_list)) {
339    return WEBP_MUX_NOT_FOUND;
340  }
341  *wpi_list = MuxImageDelete(*wpi_list);
342  return WEBP_MUX_OK;
343}
344
345//------------------------------------------------------------------------------
346// MuxImage reader methods.
347
348WebPMuxError MuxImageGetNth(const WebPMuxImage** wpi_list, uint32_t nth,
349                            WebPMuxImage** wpi) {
350  assert(wpi_list);
351  assert(wpi);
352  if (!SearchImageToGetOrDelete((WebPMuxImage**)wpi_list, nth,
353                                (WebPMuxImage***)&wpi_list)) {
354    return WEBP_MUX_NOT_FOUND;
355  }
356  *wpi = (WebPMuxImage*)*wpi_list;
357  return WEBP_MUX_OK;
358}
359
360//------------------------------------------------------------------------------
361// MuxImage serialization methods.
362
363// Size of an image.
364size_t MuxImageDiskSize(const WebPMuxImage* const wpi) {
365  size_t size = 0;
366  if (wpi->header_ != NULL) size += ChunkDiskSize(wpi->header_);
367  if (wpi->alpha_ != NULL) size += ChunkDiskSize(wpi->alpha_);
368  if (wpi->img_ != NULL) size += ChunkDiskSize(wpi->img_);
369  if (wpi->unknown_ != NULL) size += ChunkListDiskSize(wpi->unknown_);
370  return size;
371}
372
373// Special case as ANMF chunk encapsulates other image chunks.
374static uint8_t* ChunkEmitSpecial(const WebPChunk* const header,
375                                 size_t total_size, uint8_t* dst) {
376  const size_t header_size = header->data_.size;
377  const size_t offset_to_next = total_size - CHUNK_HEADER_SIZE;
378  assert(header->tag_ == kChunks[IDX_ANMF].tag);
379  PutLE32(dst + 0, header->tag_);
380  PutLE32(dst + TAG_SIZE, (uint32_t)offset_to_next);
381  assert(header_size == (uint32_t)header_size);
382  memcpy(dst + CHUNK_HEADER_SIZE, header->data_.bytes, header_size);
383  if (header_size & 1) {
384    dst[CHUNK_HEADER_SIZE + header_size] = 0;  // Add padding.
385  }
386  return dst + ChunkDiskSize(header);
387}
388
389uint8_t* MuxImageEmit(const WebPMuxImage* const wpi, uint8_t* dst) {
390  // Ordering of chunks to be emitted is strictly as follows:
391  // 1. ANMF chunk (if present).
392  // 2. ALPH chunk (if present).
393  // 3. VP8/VP8L chunk.
394  assert(wpi);
395  if (wpi->header_ != NULL) {
396    dst = ChunkEmitSpecial(wpi->header_, MuxImageDiskSize(wpi), dst);
397  }
398  if (wpi->alpha_ != NULL) dst = ChunkEmit(wpi->alpha_, dst);
399  if (wpi->img_ != NULL) dst = ChunkEmit(wpi->img_, dst);
400  if (wpi->unknown_ != NULL) dst = ChunkListEmit(wpi->unknown_, dst);
401  return dst;
402}
403
404//------------------------------------------------------------------------------
405// Helper methods for mux.
406
407int MuxHasAlpha(const WebPMuxImage* images) {
408  while (images != NULL) {
409    if (images->has_alpha_) return 1;
410    images = images->next_;
411  }
412  return 0;
413}
414
415uint8_t* MuxEmitRiffHeader(uint8_t* const data, size_t size) {
416  PutLE32(data + 0, MKFOURCC('R', 'I', 'F', 'F'));
417  PutLE32(data + TAG_SIZE, (uint32_t)size - CHUNK_HEADER_SIZE);
418  assert(size == (uint32_t)size);
419  PutLE32(data + TAG_SIZE + CHUNK_SIZE_BYTES, MKFOURCC('W', 'E', 'B', 'P'));
420  return data + RIFF_HEADER_SIZE;
421}
422
423WebPChunk** MuxGetChunkListFromId(const WebPMux* mux, WebPChunkId id) {
424  assert(mux != NULL);
425  switch (id) {
426    case WEBP_CHUNK_VP8X:    return (WebPChunk**)&mux->vp8x_;
427    case WEBP_CHUNK_ICCP:    return (WebPChunk**)&mux->iccp_;
428    case WEBP_CHUNK_ANIM:    return (WebPChunk**)&mux->anim_;
429    case WEBP_CHUNK_EXIF:    return (WebPChunk**)&mux->exif_;
430    case WEBP_CHUNK_XMP:     return (WebPChunk**)&mux->xmp_;
431    default:                 return (WebPChunk**)&mux->unknown_;
432  }
433}
434
435static int IsNotCompatible(int feature, int num_items) {
436  return (feature != 0) != (num_items > 0);
437}
438
439#define NO_FLAG ((WebPFeatureFlags)0)
440
441// Test basic constraints:
442// retrieval, maximum number of chunks by index (use -1 to skip)
443// and feature incompatibility (use NO_FLAG to skip).
444// On success returns WEBP_MUX_OK and stores the chunk count in *num.
445static WebPMuxError ValidateChunk(const WebPMux* const mux, CHUNK_INDEX idx,
446                                  WebPFeatureFlags feature,
447                                  uint32_t vp8x_flags,
448                                  int max, int* num) {
449  const WebPMuxError err =
450      WebPMuxNumChunks(mux, kChunks[idx].id, num);
451  if (err != WEBP_MUX_OK) return err;
452  if (max > -1 && *num > max) return WEBP_MUX_INVALID_ARGUMENT;
453  if (feature != NO_FLAG && IsNotCompatible(vp8x_flags & feature, *num)) {
454    return WEBP_MUX_INVALID_ARGUMENT;
455  }
456  return WEBP_MUX_OK;
457}
458
459WebPMuxError MuxValidate(const WebPMux* const mux) {
460  int num_iccp;
461  int num_exif;
462  int num_xmp;
463  int num_anim;
464  int num_frames;
465  int num_vp8x;
466  int num_images;
467  int num_alpha;
468  uint32_t flags;
469  WebPMuxError err;
470
471  // Verify mux is not NULL.
472  if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT;
473
474  // Verify mux has at least one image.
475  if (mux->images_ == NULL) return WEBP_MUX_INVALID_ARGUMENT;
476
477  err = WebPMuxGetFeatures(mux, &flags);
478  if (err != WEBP_MUX_OK) return err;
479
480  // At most one color profile chunk.
481  err = ValidateChunk(mux, IDX_ICCP, ICCP_FLAG, flags, 1, &num_iccp);
482  if (err != WEBP_MUX_OK) return err;
483
484  // At most one EXIF metadata.
485  err = ValidateChunk(mux, IDX_EXIF, EXIF_FLAG, flags, 1, &num_exif);
486  if (err != WEBP_MUX_OK) return err;
487
488  // At most one XMP metadata.
489  err = ValidateChunk(mux, IDX_XMP, XMP_FLAG, flags, 1, &num_xmp);
490  if (err != WEBP_MUX_OK) return err;
491
492  // Animation: ANIMATION_FLAG, ANIM chunk and ANMF chunk(s) are consistent.
493  // At most one ANIM chunk.
494  err = ValidateChunk(mux, IDX_ANIM, NO_FLAG, flags, 1, &num_anim);
495  if (err != WEBP_MUX_OK) return err;
496  err = ValidateChunk(mux, IDX_ANMF, NO_FLAG, flags, -1, &num_frames);
497  if (err != WEBP_MUX_OK) return err;
498
499  {
500    const int has_animation = !!(flags & ANIMATION_FLAG);
501    if (has_animation && (num_anim == 0 || num_frames == 0)) {
502      return WEBP_MUX_INVALID_ARGUMENT;
503    }
504    if (!has_animation && (num_anim == 1 || num_frames > 0)) {
505      return WEBP_MUX_INVALID_ARGUMENT;
506    }
507  }
508
509  // Verify either VP8X chunk is present OR there is only one elem in
510  // mux->images_.
511  err = ValidateChunk(mux, IDX_VP8X, NO_FLAG, flags, 1, &num_vp8x);
512  if (err != WEBP_MUX_OK) return err;
513  err = ValidateChunk(mux, IDX_VP8, NO_FLAG, flags, -1, &num_images);
514  if (err != WEBP_MUX_OK) return err;
515  if (num_vp8x == 0 && num_images != 1) return WEBP_MUX_INVALID_ARGUMENT;
516
517  // ALPHA_FLAG & alpha chunk(s) are consistent.
518  if (MuxHasAlpha(mux->images_)) {
519    if (num_vp8x > 0) {
520      // VP8X chunk is present, so it should contain ALPHA_FLAG.
521      if (!(flags & ALPHA_FLAG)) return WEBP_MUX_INVALID_ARGUMENT;
522    } else {
523      // VP8X chunk is not present, so ALPH chunks should NOT be present either.
524      err = WebPMuxNumChunks(mux, WEBP_CHUNK_ALPHA, &num_alpha);
525      if (err != WEBP_MUX_OK) return err;
526      if (num_alpha > 0) return WEBP_MUX_INVALID_ARGUMENT;
527    }
528  } else {  // Mux doesn't need alpha. So, ALPHA_FLAG should NOT be present.
529    if (flags & ALPHA_FLAG) return WEBP_MUX_INVALID_ARGUMENT;
530  }
531
532  return WEBP_MUX_OK;
533}
534
535#undef NO_FLAG
536
537//------------------------------------------------------------------------------
538
539