• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 PDFium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6 
7 #include "core/fxcodec/gif/cfx_gifcontext.h"
8 
9 #include <algorithm>
10 #include <utility>
11 
12 #include "core/fxcodec/cfx_codec_memory.h"
13 #include "core/fxcodec/gif/cfx_gif.h"
14 #include "core/fxcodec/gif/gifmodule.h"
15 #include "third_party/base/ptr_util.h"
16 #include "third_party/base/stl_util.h"
17 
18 namespace fxcodec {
19 
20 namespace {
21 
22 constexpr int32_t kGifInterlaceStep[4] = {8, 8, 4, 2};
23 
24 }  // namespace
25 
CFX_GifContext(GifModule * gif_module,GifModule::Delegate * delegate)26 CFX_GifContext::CFX_GifContext(GifModule* gif_module,
27                                GifModule::Delegate* delegate)
28     : gif_module_(gif_module), delegate_(delegate) {}
29 
30 CFX_GifContext::~CFX_GifContext() = default;
31 
RecordCurrentPosition(uint32_t * cur_pos)32 void CFX_GifContext::RecordCurrentPosition(uint32_t* cur_pos) {
33   delegate_->GifRecordCurrentPosition(*cur_pos);
34 }
35 
ReadScanline(int32_t row_num,uint8_t * row_buf)36 void CFX_GifContext::ReadScanline(int32_t row_num, uint8_t* row_buf) {
37   delegate_->GifReadScanline(row_num, row_buf);
38 }
39 
GetRecordPosition(uint32_t cur_pos,int32_t left,int32_t top,int32_t width,int32_t height,int32_t pal_num,CFX_GifPalette * pal,int32_t delay_time,bool user_input,int32_t trans_index,int32_t disposal_method,bool interlace)40 bool CFX_GifContext::GetRecordPosition(uint32_t cur_pos,
41                                        int32_t left,
42                                        int32_t top,
43                                        int32_t width,
44                                        int32_t height,
45                                        int32_t pal_num,
46                                        CFX_GifPalette* pal,
47                                        int32_t delay_time,
48                                        bool user_input,
49                                        int32_t trans_index,
50                                        int32_t disposal_method,
51                                        bool interlace) {
52   return delegate_->GifInputRecordPositionBuf(
53       cur_pos, FX_RECT(left, top, left + width, top + height), pal_num, pal,
54       delay_time, user_input, trans_index, disposal_method, interlace);
55 }
56 
ReadHeader()57 CFX_GifDecodeStatus CFX_GifContext::ReadHeader() {
58   CFX_GifDecodeStatus status = ReadGifSignature();
59   if (status != CFX_GifDecodeStatus::Success)
60     return status;
61   return ReadLogicalScreenDescriptor();
62 }
63 
GetFrame()64 CFX_GifDecodeStatus CFX_GifContext::GetFrame() {
65   CFX_GifDecodeStatus ret = CFX_GifDecodeStatus::Success;
66   while (true) {
67     switch (decode_status_) {
68       case GIF_D_STATUS_TAIL:
69         return CFX_GifDecodeStatus::Success;
70       case GIF_D_STATUS_SIG: {
71         uint8_t signature;
72         if (!ReadAllOrNone(&signature, sizeof(signature)))
73           return CFX_GifDecodeStatus::Unfinished;
74 
75         switch (signature) {
76           case GIF_SIG_EXTENSION:
77             SaveDecodingStatus(GIF_D_STATUS_EXT);
78             continue;
79           case GIF_SIG_IMAGE:
80             SaveDecodingStatus(GIF_D_STATUS_IMG_INFO);
81             continue;
82           case GIF_SIG_TRAILER:
83             SaveDecodingStatus(GIF_D_STATUS_TAIL);
84             return CFX_GifDecodeStatus::Success;
85           default:
86             if (!input_buffer_->IsEOF()) {
87               // The Gif File has non_standard Tag!
88               SaveDecodingStatus(GIF_D_STATUS_SIG);
89               continue;
90             }
91             // The Gif File Doesn't have Trailer Tag!
92             return CFX_GifDecodeStatus::Success;
93         }
94       }
95       case GIF_D_STATUS_EXT: {
96         uint8_t extension;
97         if (!ReadAllOrNone(&extension, sizeof(extension)))
98           return CFX_GifDecodeStatus::Unfinished;
99 
100         switch (extension) {
101           case GIF_BLOCK_CE:
102             SaveDecodingStatus(GIF_D_STATUS_EXT_CE);
103             continue;
104           case GIF_BLOCK_GCE:
105             SaveDecodingStatus(GIF_D_STATUS_EXT_GCE);
106             continue;
107           case GIF_BLOCK_PTE:
108             SaveDecodingStatus(GIF_D_STATUS_EXT_PTE);
109             continue;
110           default: {
111             int32_t status = GIF_D_STATUS_EXT_UNE;
112             if (extension == GIF_BLOCK_PTE) {
113               status = GIF_D_STATUS_EXT_PTE;
114             }
115             SaveDecodingStatus(status);
116             continue;
117           }
118         }
119       }
120       case GIF_D_STATUS_IMG_INFO: {
121         ret = DecodeImageInfo();
122         if (ret != CFX_GifDecodeStatus::Success)
123           return ret;
124 
125         continue;
126       }
127       case GIF_D_STATUS_IMG_DATA: {
128         uint8_t img_data_size;
129         size_t read_marker = input_buffer_->GetPosition();
130 
131         if (!ReadAllOrNone(&img_data_size, sizeof(img_data_size)))
132           return CFX_GifDecodeStatus::Unfinished;
133 
134         while (img_data_size != GIF_BLOCK_TERMINAL) {
135           if (!input_buffer_->Seek(input_buffer_->GetPosition() +
136                                    img_data_size)) {
137             input_buffer_->Seek(read_marker);
138             return CFX_GifDecodeStatus::Unfinished;
139           }
140 
141           // This saving of the scan state on partial reads is why
142           // ScanForTerminalMarker() cannot be used here.
143           SaveDecodingStatus(GIF_D_STATUS_IMG_DATA);
144           read_marker = input_buffer_->GetPosition();
145           if (!ReadAllOrNone(&img_data_size, sizeof(img_data_size)))
146             return CFX_GifDecodeStatus::Unfinished;
147         }
148 
149         SaveDecodingStatus(GIF_D_STATUS_SIG);
150         continue;
151       }
152       default: {
153         ret = DecodeExtension();
154         if (ret != CFX_GifDecodeStatus::Success)
155           return ret;
156         break;
157       }
158     }
159   }
160   return CFX_GifDecodeStatus::Success;
161 }
162 
LoadFrame(int32_t frame_num)163 CFX_GifDecodeStatus CFX_GifContext::LoadFrame(int32_t frame_num) {
164   if (!pdfium::IndexInBounds(images_, frame_num))
165     return CFX_GifDecodeStatus::Error;
166 
167   CFX_GifImage* gif_image = images_[static_cast<size_t>(frame_num)].get();
168   if (gif_image->image_info.height == 0)
169     return CFX_GifDecodeStatus::Error;
170 
171   uint32_t gif_img_row_bytes = gif_image->image_info.width;
172   if (gif_img_row_bytes == 0)
173     return CFX_GifDecodeStatus::Error;
174 
175   if (decode_status_ == GIF_D_STATUS_TAIL) {
176     gif_image->row_buffer.resize(gif_img_row_bytes);
177     CFX_GifGraphicControlExtension* gif_img_gce = gif_image->image_GCE.get();
178     int32_t loc_pal_num =
179         gif_image->image_info.local_flags.local_pal
180             ? (2 << gif_image->image_info.local_flags.pal_bits)
181             : 0;
182     CFX_GifPalette* pLocalPalette = gif_image->local_palettes.empty()
183                                         ? nullptr
184                                         : gif_image->local_palettes.data();
185     if (!gif_img_gce) {
186       bool bRes = GetRecordPosition(
187           gif_image->data_pos, gif_image->image_info.left,
188           gif_image->image_info.top, gif_image->image_info.width,
189           gif_image->image_info.height, loc_pal_num, pLocalPalette, 0, 0, -1, 0,
190           gif_image->image_info.local_flags.interlace);
191       if (!bRes) {
192         gif_image->row_buffer.clear();
193         return CFX_GifDecodeStatus::Error;
194       }
195     } else {
196       bool bRes = GetRecordPosition(
197           gif_image->data_pos, gif_image->image_info.left,
198           gif_image->image_info.top, gif_image->image_info.width,
199           gif_image->image_info.height, loc_pal_num, pLocalPalette,
200           static_cast<int32_t>(gif_image->image_GCE->delay_time),
201           gif_image->image_GCE->gce_flags.user_input,
202           gif_image->image_GCE->gce_flags.transparency
203               ? static_cast<int32_t>(gif_image->image_GCE->trans_index)
204               : -1,
205           static_cast<int32_t>(gif_image->image_GCE->gce_flags.disposal_method),
206           gif_image->image_info.local_flags.interlace);
207       if (!bRes) {
208         gif_image->row_buffer.clear();
209         return CFX_GifDecodeStatus::Error;
210       }
211     }
212 
213     if (gif_image->code_exp > GIF_MAX_LZW_EXP) {
214       gif_image->row_buffer.clear();
215       return CFX_GifDecodeStatus::Error;
216     }
217 
218     img_row_offset_ = 0;
219     img_row_avail_size_ = 0;
220     img_pass_num_ = 0;
221     gif_image->row_num = 0;
222     SaveDecodingStatus(GIF_D_STATUS_IMG_DATA);
223   }
224 
225   uint8_t img_data_size;
226   std::vector<uint8_t, FxAllocAllocator<uint8_t>> img_data;
227   size_t read_marker = input_buffer_->GetPosition();
228 
229   if (decode_status_ == GIF_D_STATUS_IMG_DATA) {
230     if (!ReadAllOrNone(&img_data_size, sizeof(img_data_size)))
231       return CFX_GifDecodeStatus::Unfinished;
232 
233     if (img_data_size != GIF_BLOCK_TERMINAL) {
234       img_data.resize(img_data_size);
235       if (!ReadAllOrNone(img_data.data(), img_data_size)) {
236         input_buffer_->Seek(read_marker);
237         return CFX_GifDecodeStatus::Unfinished;
238       }
239 
240       if (!lzw_decompressor_.get())
241         lzw_decompressor_ = CFX_LZWDecompressor::Create(
242             !gif_image->local_palettes.empty() ? gif_image->local_pallette_exp
243                                                : global_pal_exp_,
244             gif_image->code_exp);
245       SaveDecodingStatus(GIF_D_STATUS_IMG_DATA);
246       img_row_offset_ += img_row_avail_size_;
247       img_row_avail_size_ = gif_img_row_bytes - img_row_offset_;
248       CFX_GifDecodeStatus ret =
249           lzw_decompressor_.get()
250               ? lzw_decompressor_->Decode(
251                     img_data.data(), img_data_size,
252                     gif_image->row_buffer.data() + img_row_offset_,
253                     &img_row_avail_size_)
254               : CFX_GifDecodeStatus::Error;
255       if (ret == CFX_GifDecodeStatus::Error) {
256         DecodingFailureAtTailCleanup(gif_image);
257         return CFX_GifDecodeStatus::Error;
258       }
259 
260       while (ret != CFX_GifDecodeStatus::Error) {
261         if (ret == CFX_GifDecodeStatus::Success) {
262           ReadScanline(gif_image->row_num, gif_image->row_buffer.data());
263           gif_image->row_buffer.clear();
264           SaveDecodingStatus(GIF_D_STATUS_TAIL);
265           return CFX_GifDecodeStatus::Success;
266         }
267 
268         if (ret == CFX_GifDecodeStatus::Unfinished) {
269           read_marker = input_buffer_->GetPosition();
270           if (!ReadAllOrNone(&img_data_size, sizeof(img_data_size)))
271             return CFX_GifDecodeStatus::Unfinished;
272 
273           if (img_data_size != GIF_BLOCK_TERMINAL) {
274             img_data.resize(img_data_size);
275             if (!ReadAllOrNone(img_data.data(), img_data_size)) {
276               input_buffer_->Seek(read_marker);
277               return CFX_GifDecodeStatus::Unfinished;
278             }
279 
280             if (!lzw_decompressor_.get())
281               lzw_decompressor_ = CFX_LZWDecompressor::Create(
282                   !gif_image->local_palettes.empty()
283                       ? gif_image->local_pallette_exp
284                       : global_pal_exp_,
285                   gif_image->code_exp);
286             SaveDecodingStatus(GIF_D_STATUS_IMG_DATA);
287             img_row_offset_ += img_row_avail_size_;
288             img_row_avail_size_ = gif_img_row_bytes - img_row_offset_;
289             ret = lzw_decompressor_.get()
290                       ? lzw_decompressor_->Decode(
291                             img_data.data(), img_data_size,
292                             gif_image->row_buffer.data() + img_row_offset_,
293                             &img_row_avail_size_)
294                       : CFX_GifDecodeStatus::Error;
295           }
296         }
297 
298         if (ret == CFX_GifDecodeStatus::InsufficientDestSize) {
299           if (gif_image->image_info.local_flags.interlace) {
300             ReadScanline(gif_image->row_num, gif_image->row_buffer.data());
301             gif_image->row_num += kGifInterlaceStep[img_pass_num_];
302             if (gif_image->row_num >=
303                 static_cast<int32_t>(gif_image->image_info.height)) {
304               img_pass_num_++;
305               if (img_pass_num_ == FX_ArraySize(kGifInterlaceStep)) {
306                 DecodingFailureAtTailCleanup(gif_image);
307                 return CFX_GifDecodeStatus::Error;
308               }
309               gif_image->row_num = kGifInterlaceStep[img_pass_num_] / 2;
310             }
311           } else {
312             ReadScanline(gif_image->row_num++, gif_image->row_buffer.data());
313           }
314           img_row_offset_ = 0;
315           img_row_avail_size_ = gif_img_row_bytes;
316           ret = lzw_decompressor_.get()
317                     ? lzw_decompressor_->Decode(
318                           img_data.data(), img_data_size,
319                           gif_image->row_buffer.data() + img_row_offset_,
320                           &img_row_avail_size_)
321                     : CFX_GifDecodeStatus::Error;
322         }
323 
324         if (ret == CFX_GifDecodeStatus::InsufficientDestSize ||
325             ret == CFX_GifDecodeStatus::Error) {
326           DecodingFailureAtTailCleanup(gif_image);
327           return CFX_GifDecodeStatus::Error;
328         }
329       }
330     }
331     SaveDecodingStatus(GIF_D_STATUS_TAIL);
332   }
333   return CFX_GifDecodeStatus::Error;
334 }
335 
SetInputBuffer(RetainPtr<CFX_CodecMemory> codec_memory)336 void CFX_GifContext::SetInputBuffer(RetainPtr<CFX_CodecMemory> codec_memory) {
337   input_buffer_ = std::move(codec_memory);
338 }
339 
GetAvailInput() const340 uint32_t CFX_GifContext::GetAvailInput() const {
341   if (!input_buffer_)
342     return 0;
343 
344   return input_buffer_->GetSize() - input_buffer_->GetPosition();
345 }
346 
ReadAllOrNone(uint8_t * dest,uint32_t size)347 bool CFX_GifContext::ReadAllOrNone(uint8_t* dest, uint32_t size) {
348   if (!input_buffer_ || !dest)
349     return false;
350 
351   size_t read_marker = input_buffer_->GetPosition();
352   size_t read = input_buffer_->ReadBlock(dest, size);
353   if (read < size) {
354     input_buffer_->Seek(read_marker);
355     return false;
356   }
357 
358   return true;
359 }
360 
ReadGifSignature()361 CFX_GifDecodeStatus CFX_GifContext::ReadGifSignature() {
362   CFX_GifHeader header;
363   if (!ReadAllOrNone(reinterpret_cast<uint8_t*>(&header), 6))
364     return CFX_GifDecodeStatus::Unfinished;
365 
366   if (strncmp(header.signature, kGifSignature87, 6) != 0 &&
367       strncmp(header.signature, kGifSignature89, 6) != 0) {
368     return CFX_GifDecodeStatus::Error;
369   }
370 
371   return CFX_GifDecodeStatus::Success;
372 }
373 
ReadLogicalScreenDescriptor()374 CFX_GifDecodeStatus CFX_GifContext::ReadLogicalScreenDescriptor() {
375   CFX_GifLocalScreenDescriptor lsd;
376   size_t read_marker = input_buffer_->GetPosition();
377 
378   if (!ReadAllOrNone(reinterpret_cast<uint8_t*>(&lsd), sizeof(lsd)))
379     return CFX_GifDecodeStatus::Unfinished;
380 
381   if (lsd.global_flags.global_pal) {
382     uint32_t palette_count = unsigned(2 << lsd.global_flags.pal_bits);
383     if (lsd.bc_index >= palette_count)
384       return CFX_GifDecodeStatus::Error;
385     bc_index_ = lsd.bc_index;
386 
387     uint32_t palette_size = palette_count * sizeof(CFX_GifPalette);
388     std::vector<CFX_GifPalette> palette(palette_count);
389     if (!ReadAllOrNone(reinterpret_cast<uint8_t*>(palette.data()),
390                        palette_size)) {
391       // Roll back the read for the LSD
392       input_buffer_->Seek(read_marker);
393       return CFX_GifDecodeStatus::Unfinished;
394     }
395 
396     global_pal_exp_ = lsd.global_flags.pal_bits;
397     global_sort_flag_ = lsd.global_flags.sort_flag;
398     global_color_resolution_ = lsd.global_flags.color_resolution;
399     std::swap(global_palette_, palette);
400   }
401 
402   width_ = static_cast<int>(
403       FXWORD_GET_LSBFIRST(reinterpret_cast<uint8_t*>(&lsd.width)));
404   height_ = static_cast<int>(
405       FXWORD_GET_LSBFIRST(reinterpret_cast<uint8_t*>(&lsd.height)));
406 
407   return CFX_GifDecodeStatus::Success;
408 }
409 
SaveDecodingStatus(int32_t status)410 void CFX_GifContext::SaveDecodingStatus(int32_t status) {
411   decode_status_ = status;
412 }
413 
DecodeExtension()414 CFX_GifDecodeStatus CFX_GifContext::DecodeExtension() {
415   size_t read_marker = input_buffer_->GetPosition();
416 
417   switch (decode_status_) {
418     case GIF_D_STATUS_EXT_CE: {
419       if (!ScanForTerminalMarker()) {
420         input_buffer_->Seek(read_marker);
421         return CFX_GifDecodeStatus::Unfinished;
422       }
423       break;
424     }
425     case GIF_D_STATUS_EXT_PTE: {
426       CFX_GifPlainTextExtension gif_pte;
427       if (!ReadAllOrNone(reinterpret_cast<uint8_t*>(&gif_pte), sizeof(gif_pte)))
428         return CFX_GifDecodeStatus::Unfinished;
429 
430       graphic_control_extension_ = nullptr;
431       if (!ScanForTerminalMarker()) {
432         input_buffer_->Seek(read_marker);
433         return CFX_GifDecodeStatus::Unfinished;
434       }
435       break;
436     }
437     case GIF_D_STATUS_EXT_GCE: {
438       CFX_GifGraphicControlExtension gif_gce;
439       if (!ReadAllOrNone(reinterpret_cast<uint8_t*>(&gif_gce), sizeof(gif_gce)))
440         return CFX_GifDecodeStatus::Unfinished;
441 
442       if (!graphic_control_extension_.get())
443         graphic_control_extension_ =
444             pdfium::MakeUnique<CFX_GifGraphicControlExtension>();
445       graphic_control_extension_->block_size = gif_gce.block_size;
446       graphic_control_extension_->gce_flags = gif_gce.gce_flags;
447       graphic_control_extension_->delay_time =
448           FXWORD_GET_LSBFIRST(reinterpret_cast<uint8_t*>(&gif_gce.delay_time));
449       graphic_control_extension_->trans_index = gif_gce.trans_index;
450       break;
451     }
452     default: {
453       if (decode_status_ == GIF_D_STATUS_EXT_PTE)
454         graphic_control_extension_ = nullptr;
455       if (!ScanForTerminalMarker()) {
456         input_buffer_->Seek(read_marker);
457         return CFX_GifDecodeStatus::Unfinished;
458       }
459     }
460   }
461 
462   SaveDecodingStatus(GIF_D_STATUS_SIG);
463   return CFX_GifDecodeStatus::Success;
464 }
465 
DecodeImageInfo()466 CFX_GifDecodeStatus CFX_GifContext::DecodeImageInfo() {
467   if (width_ <= 0 || height_ <= 0)
468     return CFX_GifDecodeStatus::Error;
469 
470   size_t read_marker = input_buffer_->GetPosition();
471   CFX_CFX_GifImageInfo img_info;
472   if (!ReadAllOrNone(reinterpret_cast<uint8_t*>(&img_info), sizeof(img_info)))
473     return CFX_GifDecodeStatus::Unfinished;
474 
475   auto gif_image = pdfium::MakeUnique<CFX_GifImage>();
476   gif_image->image_info.left =
477       FXWORD_GET_LSBFIRST(reinterpret_cast<uint8_t*>(&img_info.left));
478   gif_image->image_info.top =
479       FXWORD_GET_LSBFIRST(reinterpret_cast<uint8_t*>(&img_info.top));
480   gif_image->image_info.width =
481       FXWORD_GET_LSBFIRST(reinterpret_cast<uint8_t*>(&img_info.width));
482   gif_image->image_info.height =
483       FXWORD_GET_LSBFIRST(reinterpret_cast<uint8_t*>(&img_info.height));
484   gif_image->image_info.local_flags = img_info.local_flags;
485   if (gif_image->image_info.left + gif_image->image_info.width > width_ ||
486       gif_image->image_info.top + gif_image->image_info.height > height_)
487     return CFX_GifDecodeStatus::Error;
488 
489   CFX_GifLocalFlags* gif_img_info_lf = &img_info.local_flags;
490   if (gif_img_info_lf->local_pal) {
491     gif_image->local_pallette_exp = gif_img_info_lf->pal_bits;
492     uint32_t loc_pal_count = unsigned(2 << gif_img_info_lf->pal_bits);
493     std::vector<CFX_GifPalette> loc_pal(loc_pal_count);
494     if (!ReadAllOrNone(reinterpret_cast<uint8_t*>(loc_pal.data()),
495                        loc_pal_count * sizeof(CFX_GifPalette))) {
496       input_buffer_->Seek(read_marker);
497       return CFX_GifDecodeStatus::Unfinished;
498     }
499 
500     gif_image->local_palettes = std::move(loc_pal);
501   }
502 
503   uint8_t code_size;
504   if (!ReadAllOrNone(&code_size, sizeof(code_size))) {
505     input_buffer_->Seek(read_marker);
506     return CFX_GifDecodeStatus::Unfinished;
507   }
508 
509   gif_image->code_exp = code_size;
510   RecordCurrentPosition(&gif_image->data_pos);
511   gif_image->data_pos += input_buffer_->GetPosition();
512   gif_image->image_GCE = nullptr;
513   if (graphic_control_extension_.get()) {
514     if (graphic_control_extension_->gce_flags.transparency) {
515       // Need to test that the color that is going to be transparent is actually
516       // in the palette being used.
517       if (graphic_control_extension_->trans_index >=
518           2 << (gif_image->local_palettes.empty()
519                     ? global_pal_exp_
520                     : gif_image->local_pallette_exp))
521         return CFX_GifDecodeStatus::Error;
522     }
523     gif_image->image_GCE = std::move(graphic_control_extension_);
524     graphic_control_extension_ = nullptr;
525   }
526 
527   images_.push_back(std::move(gif_image));
528   SaveDecodingStatus(GIF_D_STATUS_IMG_DATA);
529   return CFX_GifDecodeStatus::Success;
530 }
531 
DecodingFailureAtTailCleanup(CFX_GifImage * gif_image)532 void CFX_GifContext::DecodingFailureAtTailCleanup(CFX_GifImage* gif_image) {
533   gif_image->row_buffer.clear();
534   SaveDecodingStatus(GIF_D_STATUS_TAIL);
535 }
536 
ScanForTerminalMarker()537 bool CFX_GifContext::ScanForTerminalMarker() {
538   uint8_t data_size;
539 
540   if (!ReadAllOrNone(&data_size, sizeof(data_size)))
541     return false;
542 
543   while (data_size != GIF_BLOCK_TERMINAL) {
544     if (!input_buffer_->Seek(input_buffer_->GetPosition() + data_size) ||
545         !ReadAllOrNone(&data_size, sizeof(data_size))) {
546       return false;
547     }
548   }
549 
550   return true;
551 }
552 
553 }  // namespace fxcodec
554