// Copyright 2016 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef TESTING_FUZZERS_XFA_CODEC_FUZZER_H_
#define TESTING_FUZZERS_XFA_CODEC_FUZZER_H_

#include <memory>
#include <utility>

#include "core/fxcodec/fx_codec.h"
#include "core/fxcodec/progressive_decoder.h"
#include "core/fxcrt/cfx_read_only_span_stream.h"
#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/fx_safe_types.h"
#include "core/fxcrt/retain_ptr.h"
#include "core/fxcrt/span.h"
#include "core/fxge/dib/cfx_dibitmap.h"
#include "core/fxge/dib/fx_dib.h"

// Support up to 64 MB. This prevents trivial OOM when MSAN is on and
// time outs.
const int kXFACodecFuzzerPixelLimit = 64000000;

class XFACodecFuzzer {
 public:
  // PRECONDITIONS: `data` must be valid for `size` bytes.
  UNSAFE_BUFFER_USAGE static int Fuzz(const uint8_t* data,
                                      size_t size,
                                      FXCODEC_IMAGE_TYPE type) {
    auto decoder = std::make_unique<ProgressiveDecoder>();
    // SAFETY: required from caller, enforced by UNSAFE_BUFFER_USAGE.
    auto source = pdfium::MakeRetain<CFX_ReadOnlySpanStream>(
        UNSAFE_BUFFERS(pdfium::span(data, size)));
    CFX_DIBAttribute attr;
    FXCODEC_STATUS status =
        decoder->LoadImageInfo(std::move(source), type, &attr, true);
    if (status != FXCODEC_STATUS::kFrameReady) {
      return 0;
    }

    // Skipping very large images, since they will take a long time and may lead
    // to OOM.
    FX_SAFE_UINT32 bitmap_size = decoder->GetHeight();
    bitmap_size *= decoder->GetWidth();
    bitmap_size *= GetCompsFromFormat(decoder->GetBitmapFormat());
    if (!bitmap_size.IsValid() || bitmap_size.ValueOrDie() == 0 ||
        bitmap_size.ValueOrDie() > kXFACodecFuzzerPixelLimit) {
      return 0;
    }

    auto bitmap = pdfium::MakeRetain<CFX_DIBitmap>();
    if (!bitmap->Create(decoder->GetWidth(), decoder->GetHeight(),
                        decoder->GetBitmapFormat())) {
      return 0;
    }

    size_t frames;
    std::tie(status, frames) = decoder->GetFrames();
    if (status != FXCODEC_STATUS::kDecodeReady || frames == 0) {
      return 0;
    }

    status = decoder->StartDecode(std::move(bitmap));
    while (status == FXCODEC_STATUS::kDecodeToBeContinued) {
      status = decoder->ContinueDecode();
    }

    return 0;
  }
};

#endif  // TESTING_FUZZERS_XFA_CODEC_FUZZER_H_
