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 // Read APIs 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 #if defined(__cplusplus) || defined(c_plusplus)
20 extern "C" {
21 #endif
22 
23 //------------------------------------------------------------------------------
24 // Helper method(s).
25 
26 // Handy MACRO.
27 #define SWITCH_ID_LIST(INDEX, LIST)                                           \
28   if (idx == (INDEX)) {                                                       \
29     const WebPChunk* const chunk = ChunkSearchList((LIST), nth,               \
30                                                    kChunks[(INDEX)].tag);     \
31     if (chunk) {                                                              \
32       *data = chunk->data_;                                                   \
33       return WEBP_MUX_OK;                                                     \
34     } else {                                                                  \
35       return WEBP_MUX_NOT_FOUND;                                              \
36     }                                                                         \
37   }
38 
MuxGet(const WebPMux * const mux,CHUNK_INDEX idx,uint32_t nth,WebPData * const data)39 static WebPMuxError MuxGet(const WebPMux* const mux, CHUNK_INDEX idx,
40                            uint32_t nth, WebPData* const data) {
41   assert(mux != NULL);
42   assert(!IsWPI(kChunks[idx].id));
43   WebPDataInit(data);
44 
45   SWITCH_ID_LIST(IDX_VP8X, mux->vp8x_);
46   SWITCH_ID_LIST(IDX_ICCP, mux->iccp_);
47   SWITCH_ID_LIST(IDX_ANIM, mux->anim_);
48   SWITCH_ID_LIST(IDX_EXIF, mux->exif_);
49   SWITCH_ID_LIST(IDX_XMP, mux->xmp_);
50   SWITCH_ID_LIST(IDX_UNKNOWN, mux->unknown_);
51   return WEBP_MUX_NOT_FOUND;
52 }
53 #undef SWITCH_ID_LIST
54 
55 // Fill the chunk with the given data (includes chunk header bytes), after some
56 // verifications.
ChunkVerifyAndAssign(WebPChunk * chunk,const uint8_t * data,size_t data_size,size_t riff_size,int copy_data)57 static WebPMuxError ChunkVerifyAndAssign(WebPChunk* chunk,
58                                          const uint8_t* data, size_t data_size,
59                                          size_t riff_size, int copy_data) {
60   uint32_t chunk_size;
61   WebPData chunk_data;
62 
63   // Sanity checks.
64   if (data_size < TAG_SIZE) return WEBP_MUX_NOT_ENOUGH_DATA;
65   chunk_size = GetLE32(data + TAG_SIZE);
66 
67   {
68     const size_t chunk_disk_size = SizeWithPadding(chunk_size);
69     if (chunk_disk_size > riff_size) return WEBP_MUX_BAD_DATA;
70     if (chunk_disk_size > data_size) return WEBP_MUX_NOT_ENOUGH_DATA;
71   }
72 
73   // Data assignment.
74   chunk_data.bytes = data + CHUNK_HEADER_SIZE;
75   chunk_data.size = chunk_size;
76   return ChunkAssignData(chunk, &chunk_data, copy_data, GetLE32(data + 0));
77 }
78 
MuxImageParse(const WebPChunk * const chunk,int copy_data,WebPMuxImage * const wpi)79 static int MuxImageParse(const WebPChunk* const chunk, int copy_data,
80                          WebPMuxImage* const wpi) {
81   const uint8_t* bytes = chunk->data_.bytes;
82   size_t size = chunk->data_.size;
83   const uint8_t* const last = bytes + size;
84   WebPChunk subchunk;
85   size_t subchunk_size;
86   ChunkInit(&subchunk);
87 
88   assert(chunk->tag_ == kChunks[IDX_ANMF].tag ||
89          chunk->tag_ == kChunks[IDX_FRGM].tag);
90   assert(!wpi->is_partial_);
91 
92   // ANMF/FRGM.
93   {
94     const size_t hdr_size = (chunk->tag_ == kChunks[IDX_ANMF].tag) ?
95         ANMF_CHUNK_SIZE : FRGM_CHUNK_SIZE;
96     const WebPData temp = { bytes, hdr_size };
97     // Each of ANMF and FRGM chunk contain a header at the beginning. So, its
98     // size should at least be 'hdr_size'.
99     if (size < hdr_size) goto Fail;
100     ChunkAssignData(&subchunk, &temp, copy_data, chunk->tag_);
101   }
102   ChunkSetNth(&subchunk, &wpi->header_, 1);
103   wpi->is_partial_ = 1;  // Waiting for ALPH and/or VP8/VP8L chunks.
104 
105   // Rest of the chunks.
106   subchunk_size = ChunkDiskSize(&subchunk) - CHUNK_HEADER_SIZE;
107   bytes += subchunk_size;
108   size -= subchunk_size;
109 
110   while (bytes != last) {
111     ChunkInit(&subchunk);
112     if (ChunkVerifyAndAssign(&subchunk, bytes, size, size,
113                              copy_data) != WEBP_MUX_OK) {
114       goto Fail;
115     }
116     switch (ChunkGetIdFromTag(subchunk.tag_)) {
117       case WEBP_CHUNK_ALPHA:
118         if (wpi->alpha_ != NULL) goto Fail;  // Consecutive ALPH chunks.
119         if (ChunkSetNth(&subchunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Fail;
120         wpi->is_partial_ = 1;  // Waiting for a VP8 chunk.
121         break;
122       case WEBP_CHUNK_IMAGE:
123         if (ChunkSetNth(&subchunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Fail;
124         wpi->is_partial_ = 0;  // wpi is completely filled.
125         break;
126       default:
127         goto Fail;
128         break;
129     }
130     subchunk_size = ChunkDiskSize(&subchunk);
131     bytes += subchunk_size;
132     size -= subchunk_size;
133   }
134   if (wpi->is_partial_) goto Fail;
135   return 1;
136 
137  Fail:
138   ChunkRelease(&subchunk);
139   return 0;
140 }
141 
142 //------------------------------------------------------------------------------
143 // Create a mux object from WebP-RIFF data.
144 
WebPMuxCreateInternal(const WebPData * bitstream,int copy_data,int version)145 WebPMux* WebPMuxCreateInternal(const WebPData* bitstream, int copy_data,
146                                int version) {
147   size_t riff_size;
148   uint32_t tag;
149   const uint8_t* end;
150   WebPMux* mux = NULL;
151   WebPMuxImage* wpi = NULL;
152   const uint8_t* data;
153   size_t size;
154   WebPChunk chunk;
155   ChunkInit(&chunk);
156 
157   // Sanity checks.
158   if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_MUX_ABI_VERSION)) {
159     return NULL;  // version mismatch
160   }
161   if (bitstream == NULL) return NULL;
162 
163   data = bitstream->bytes;
164   size = bitstream->size;
165 
166   if (data == NULL) return NULL;
167   if (size < RIFF_HEADER_SIZE) return NULL;
168   if (GetLE32(data + 0) != MKFOURCC('R', 'I', 'F', 'F') ||
169       GetLE32(data + CHUNK_HEADER_SIZE) != MKFOURCC('W', 'E', 'B', 'P')) {
170     return NULL;
171   }
172 
173   mux = WebPMuxNew();
174   if (mux == NULL) return NULL;
175 
176   if (size < RIFF_HEADER_SIZE + TAG_SIZE) goto Err;
177 
178   tag = GetLE32(data + RIFF_HEADER_SIZE);
179   if (tag != kChunks[IDX_VP8].tag &&
180       tag != kChunks[IDX_VP8L].tag &&
181       tag != kChunks[IDX_VP8X].tag) {
182     goto Err;  // First chunk should be VP8, VP8L or VP8X.
183   }
184 
185   riff_size = SizeWithPadding(GetLE32(data + TAG_SIZE));
186   if (riff_size > MAX_CHUNK_PAYLOAD || riff_size > size) {
187     goto Err;
188   } else {
189     if (riff_size < size) {  // Redundant data after last chunk.
190       size = riff_size;  // To make sure we don't read any data beyond mux_size.
191     }
192   }
193 
194   end = data + size;
195   data += RIFF_HEADER_SIZE;
196   size -= RIFF_HEADER_SIZE;
197 
198   wpi = (WebPMuxImage*)malloc(sizeof(*wpi));
199   if (wpi == NULL) goto Err;
200   MuxImageInit(wpi);
201 
202   // Loop over chunks.
203   while (data != end) {
204     size_t data_size;
205     WebPChunkId id;
206     WebPChunk** chunk_list;
207     if (ChunkVerifyAndAssign(&chunk, data, size, riff_size,
208                              copy_data) != WEBP_MUX_OK) {
209       goto Err;
210     }
211     data_size = ChunkDiskSize(&chunk);
212     id = ChunkGetIdFromTag(chunk.tag_);
213     switch (id) {
214       case WEBP_CHUNK_ALPHA:
215         if (wpi->alpha_ != NULL) goto Err;  // Consecutive ALPH chunks.
216         if (ChunkSetNth(&chunk, &wpi->alpha_, 1) != WEBP_MUX_OK) goto Err;
217         wpi->is_partial_ = 1;  // Waiting for a VP8 chunk.
218         break;
219       case WEBP_CHUNK_IMAGE:
220         if (ChunkSetNth(&chunk, &wpi->img_, 1) != WEBP_MUX_OK) goto Err;
221         wpi->is_partial_ = 0;  // wpi is completely filled.
222  PushImage:
223         // Add this to mux->images_ list.
224         if (MuxImagePush(wpi, &mux->images_) != WEBP_MUX_OK) goto Err;
225         MuxImageInit(wpi);  // Reset for reading next image.
226         break;
227       case WEBP_CHUNK_ANMF:
228 #ifdef WEBP_EXPERIMENTAL_FEATURES
229       case WEBP_CHUNK_FRGM:
230 #endif
231         if (wpi->is_partial_) goto Err;  // Previous wpi is still incomplete.
232         if (!MuxImageParse(&chunk, copy_data, wpi)) goto Err;
233         ChunkRelease(&chunk);
234         goto PushImage;
235         break;
236       default:  // A non-image chunk.
237         if (wpi->is_partial_) goto Err;  // Encountered a non-image chunk before
238                                          // getting all chunks of an image.
239         chunk_list = MuxGetChunkListFromId(mux, id);  // List to add this chunk.
240         if (chunk_list == NULL) chunk_list = &mux->unknown_;
241         if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err;
242         break;
243     }
244     data += data_size;
245     size -= data_size;
246     ChunkInit(&chunk);
247   }
248 
249   // Validate mux if complete.
250   if (MuxValidate(mux) != WEBP_MUX_OK) goto Err;
251 
252   MuxImageDelete(wpi);
253   return mux;  // All OK;
254 
255  Err:  // Something bad happened.
256   ChunkRelease(&chunk);
257   MuxImageDelete(wpi);
258   WebPMuxDelete(mux);
259   return NULL;
260 }
261 
262 //------------------------------------------------------------------------------
263 // Get API(s).
264 
WebPMuxGetFeatures(const WebPMux * mux,uint32_t * flags)265 WebPMuxError WebPMuxGetFeatures(const WebPMux* mux, uint32_t* flags) {
266   WebPData data;
267 
268   if (mux == NULL || flags == NULL) return WEBP_MUX_INVALID_ARGUMENT;
269   *flags = 0;
270 
271   // Check if VP8X chunk is present.
272   if (MuxGet(mux, IDX_VP8X, 1, &data) == WEBP_MUX_OK) {
273     if (data.size < CHUNK_SIZE_BYTES) return WEBP_MUX_BAD_DATA;
274     *flags = GetLE32(data.bytes);  // All OK. Fill up flags.
275   } else {
276     WebPMuxError err = MuxValidateForImage(mux);  // Check for single image.
277     if (err != WEBP_MUX_OK) return err;
278     if (MuxHasLosslessImages(mux->images_)) {
279       const WebPData* const vp8l_data = &mux->images_->img_->data_;
280       int has_alpha = 0;
281       if (!VP8LGetInfo(vp8l_data->bytes, vp8l_data->size, NULL, NULL,
282                        &has_alpha)) {
283         return WEBP_MUX_BAD_DATA;
284       }
285       if (has_alpha) {
286         *flags = ALPHA_FLAG;
287       }
288     }
289   }
290 
291   return WEBP_MUX_OK;
292 }
293 
EmitVP8XChunk(uint8_t * const dst,int width,int height,uint32_t flags)294 static uint8_t* EmitVP8XChunk(uint8_t* const dst, int width,
295                               int height, uint32_t flags) {
296   const size_t vp8x_size = CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE;
297   assert(width >= 1 && height >= 1);
298   assert(width <= MAX_CANVAS_SIZE && height <= MAX_CANVAS_SIZE);
299   assert(width * (uint64_t)height < MAX_IMAGE_AREA);
300   PutLE32(dst, MKFOURCC('V', 'P', '8', 'X'));
301   PutLE32(dst + TAG_SIZE, VP8X_CHUNK_SIZE);
302   PutLE32(dst + CHUNK_HEADER_SIZE, flags);
303   PutLE24(dst + CHUNK_HEADER_SIZE + 4, width - 1);
304   PutLE24(dst + CHUNK_HEADER_SIZE + 7, height - 1);
305   return dst + vp8x_size;
306 }
307 
308 // Assemble a single image WebP bitstream from 'wpi'.
SynthesizeBitstream(const WebPMuxImage * const wpi,WebPData * const bitstream)309 static WebPMuxError SynthesizeBitstream(const WebPMuxImage* const wpi,
310                                         WebPData* const bitstream) {
311   uint8_t* dst;
312 
313   // Allocate data.
314   const int need_vp8x = (wpi->alpha_ != NULL);
315   const size_t vp8x_size = need_vp8x ? CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE : 0;
316   const size_t alpha_size = need_vp8x ? ChunkDiskSize(wpi->alpha_) : 0;
317   // Note: No need to output ANMF/FRGM chunk for a single image.
318   const size_t size = RIFF_HEADER_SIZE + vp8x_size + alpha_size +
319                       ChunkDiskSize(wpi->img_);
320   uint8_t* const data = (uint8_t*)malloc(size);
321   if (data == NULL) return WEBP_MUX_MEMORY_ERROR;
322 
323   // Main RIFF header.
324   dst = MuxEmitRiffHeader(data, size);
325 
326   if (need_vp8x) {
327     int w, h;
328     WebPMuxError err;
329     assert(wpi->img_ != NULL);
330     err = MuxGetImageWidthHeight(wpi->img_, &w, &h);
331     if (err != WEBP_MUX_OK) {
332       free(data);
333       return err;
334     }
335     dst = EmitVP8XChunk(dst, w, h, ALPHA_FLAG);  // VP8X.
336     dst = ChunkListEmit(wpi->alpha_, dst);       // ALPH.
337   }
338 
339   // Bitstream.
340   dst = ChunkListEmit(wpi->img_, dst);
341   assert(dst == data + size);
342 
343   // Output.
344   bitstream->bytes = data;
345   bitstream->size = size;
346   return WEBP_MUX_OK;
347 }
348 
WebPMuxGetChunk(const WebPMux * mux,const char fourcc[4],WebPData * chunk_data)349 WebPMuxError WebPMuxGetChunk(const WebPMux* mux, const char fourcc[4],
350                              WebPData* chunk_data) {
351   CHUNK_INDEX idx;
352   if (mux == NULL || fourcc == NULL || chunk_data == NULL) {
353     return WEBP_MUX_INVALID_ARGUMENT;
354   }
355   idx = ChunkGetIndexFromFourCC(fourcc);
356   if (IsWPI(kChunks[idx].id)) {     // An image chunk.
357     return WEBP_MUX_INVALID_ARGUMENT;
358   } else if (idx != IDX_UNKNOWN) {  // A known chunk type.
359     return MuxGet(mux, idx, 1, chunk_data);
360   } else {                          // An unknown chunk type.
361     const WebPChunk* const chunk =
362         ChunkSearchList(mux->unknown_, 1, ChunkGetTagFromFourCC(fourcc));
363     if (chunk == NULL) return WEBP_MUX_NOT_FOUND;
364     *chunk_data = chunk->data_;
365     return WEBP_MUX_OK;
366   }
367 }
368 
MuxGetImageInternal(const WebPMuxImage * const wpi,WebPMuxFrameInfo * const info)369 static WebPMuxError MuxGetImageInternal(const WebPMuxImage* const wpi,
370                                         WebPMuxFrameInfo* const info) {
371   // Set some defaults for unrelated fields.
372   info->x_offset = 0;
373   info->y_offset = 0;
374   info->duration = 1;
375   // Extract data for related fields.
376   info->id = ChunkGetIdFromTag(wpi->img_->tag_);
377   return SynthesizeBitstream(wpi, &info->bitstream);
378 }
379 
MuxGetFrameFragmentInternal(const WebPMuxImage * const wpi,WebPMuxFrameInfo * const frame)380 static WebPMuxError MuxGetFrameFragmentInternal(const WebPMuxImage* const wpi,
381                                                 WebPMuxFrameInfo* const frame) {
382   const int is_frame = (wpi->header_->tag_ == kChunks[IDX_ANMF].tag);
383   const CHUNK_INDEX idx = is_frame ? IDX_ANMF : IDX_FRGM;
384   const WebPData* frame_frgm_data;
385 #ifndef WEBP_EXPERIMENTAL_FEATURES
386   if (!is_frame) return WEBP_MUX_INVALID_ARGUMENT;
387 #endif
388   assert(wpi->header_ != NULL);  // Already checked by WebPMuxGetFrame().
389   // Get frame/fragment chunk.
390   frame_frgm_data = &wpi->header_->data_;
391   if (frame_frgm_data->size < kChunks[idx].size) return WEBP_MUX_BAD_DATA;
392   // Extract info.
393   frame->x_offset = 2 * GetLE24(frame_frgm_data->bytes + 0);
394   frame->y_offset = 2 * GetLE24(frame_frgm_data->bytes + 3);
395   frame->duration = is_frame ? GetLE24(frame_frgm_data->bytes + 12) : 1;
396   frame->dispose_method =
397       is_frame ? (WebPMuxAnimDispose)(frame_frgm_data->bytes[15] & 1)
398                : WEBP_MUX_DISPOSE_NONE;
399   frame->id = ChunkGetIdFromTag(wpi->header_->tag_);
400   return SynthesizeBitstream(wpi, &frame->bitstream);
401 }
402 
WebPMuxGetFrame(const WebPMux * mux,uint32_t nth,WebPMuxFrameInfo * frame)403 WebPMuxError WebPMuxGetFrame(
404     const WebPMux* mux, uint32_t nth, WebPMuxFrameInfo* frame) {
405   WebPMuxError err;
406   WebPMuxImage* wpi;
407 
408   // Sanity checks.
409   if (mux == NULL || frame == NULL) {
410     return WEBP_MUX_INVALID_ARGUMENT;
411   }
412 
413   // Get the nth WebPMuxImage.
414   err = MuxImageGetNth((const WebPMuxImage**)&mux->images_, nth, &wpi);
415   if (err != WEBP_MUX_OK) return err;
416 
417   // Get frame info.
418   if (wpi->header_ == NULL) {
419     return MuxGetImageInternal(wpi, frame);
420   } else {
421     return MuxGetFrameFragmentInternal(wpi, frame);
422   }
423 }
424 
WebPMuxGetAnimationParams(const WebPMux * mux,WebPMuxAnimParams * params)425 WebPMuxError WebPMuxGetAnimationParams(const WebPMux* mux,
426                                        WebPMuxAnimParams* params) {
427   WebPData anim;
428   WebPMuxError err;
429 
430   if (mux == NULL || params == NULL) return WEBP_MUX_INVALID_ARGUMENT;
431 
432   err = MuxGet(mux, IDX_ANIM, 1, &anim);
433   if (err != WEBP_MUX_OK) return err;
434   if (anim.size < kChunks[WEBP_CHUNK_ANIM].size) return WEBP_MUX_BAD_DATA;
435   params->bgcolor = GetLE32(anim.bytes);
436   params->loop_count = GetLE16(anim.bytes + 4);
437 
438   return WEBP_MUX_OK;
439 }
440 
441 // Get chunk index from chunk id. Returns IDX_NIL if not found.
ChunkGetIndexFromId(WebPChunkId id)442 static CHUNK_INDEX ChunkGetIndexFromId(WebPChunkId id) {
443   int i;
444   for (i = 0; kChunks[i].id != WEBP_CHUNK_NIL; ++i) {
445     if (id == kChunks[i].id) return i;
446   }
447   return IDX_NIL;
448 }
449 
450 // Count number of chunks matching 'tag' in the 'chunk_list'.
451 // If tag == NIL_TAG, any tag will be matched.
CountChunks(const WebPChunk * const chunk_list,uint32_t tag)452 static int CountChunks(const WebPChunk* const chunk_list, uint32_t tag) {
453   int count = 0;
454   const WebPChunk* current;
455   for (current = chunk_list; current != NULL; current = current->next_) {
456     if (tag == NIL_TAG || current->tag_ == tag) {
457       count++;  // Count chunks whose tags match.
458     }
459   }
460   return count;
461 }
462 
WebPMuxNumChunks(const WebPMux * mux,WebPChunkId id,int * num_elements)463 WebPMuxError WebPMuxNumChunks(const WebPMux* mux,
464                               WebPChunkId id, int* num_elements) {
465   if (mux == NULL || num_elements == NULL) {
466     return WEBP_MUX_INVALID_ARGUMENT;
467   }
468 
469   if (IsWPI(id)) {
470     *num_elements = MuxImageCount(mux->images_, id);
471   } else {
472     WebPChunk* const* chunk_list = MuxGetChunkListFromId(mux, id);
473     if (chunk_list == NULL) {
474       *num_elements = 0;
475     } else {
476       const CHUNK_INDEX idx = ChunkGetIndexFromId(id);
477       *num_elements = CountChunks(*chunk_list, kChunks[idx].tag);
478     }
479   }
480 
481   return WEBP_MUX_OK;
482 }
483 
484 //------------------------------------------------------------------------------
485 
486 #if defined(__cplusplus) || defined(c_plusplus)
487 }    // extern "C"
488 #endif
489