/*
 *  Copyright (c) 2025 The WebM project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

/*
 * Fuzzer for libvpx encoders
 * ==========================
 * Requirements
 * --------------
 * Requires Clang 6.0 or above as -fsanitize=fuzzer is used as a linker
 * option.

 * Steps to build
 * --------------
 * Clone libvpx repository
   $git clone https://chromium.googlesource.com/webm/libvpx

 * Create a directory in parallel to libvpx and change directory
   $mkdir vpx_enc_fuzzer
   $cd vpx_enc_fuzzer/

 * Enable sanitizers (Supported: address integer memory thread undefined)
   $source ../libvpx/tools/set_analyzer_env.sh address

 * Configure libvpx.
 * Note --size-limit and VPX_MAX_ALLOCABLE_MEMORY are defined to avoid
 * Out of memory errors when running generated fuzzer binary
   $../libvpx/configure --disable-unit-tests --size-limit=12288x12288 \
   --extra-cflags="-fsanitize=fuzzer-no-link \
   -DVPX_MAX_ALLOCABLE_MEMORY=1073741824" \
   --disable-webm-io --enable-debug --enable-vp8-encoder \
   --enable-vp9-encoder --disable-examples

 * Build libvpx
   $make -j32

 * Build vp9 fuzzer
   $ $CXX $CXXFLAGS -std=gnu++17 -Wall -Wextra -Wimplicit-fallthrough \
   -DENCODER=vp9 -fsanitize=fuzzer -I../libvpx -I. -Wl,--start-group \
   ../libvpx/examples/vpx_enc_fuzzer.cc -o ./vpx_enc_fuzzer_vp9 \
   ./libvpx.a -Wl,--end-group

 * ENCODER should be defined as vp9 or vp8 to enable vp9/vp8
 *
 * create a corpus directory and copy some ivf files there.
 * Based on which codec (vp8/vp9) is being tested, it is recommended to
 * have corresponding ivf files in corpus directory
 * Empty corpus directory also is acceptable, though not recommended
   $mkdir CORPUS && cp some-files CORPUS

 * Run fuzzing:
   $./vpx_enc_fuzzer_vp9 CORPUS

 * References:
 * http://llvm.org/docs/LibFuzzer.html
 * https://github.com/google/oss-fuzz
 */

#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "vpx/vp8cx.h"
#include "vpx/vpx_encoder.h"
#include "vpx_ports/mem_ops.h"
#include "third_party/nalloc/nalloc.h"

// fuzz header to have config options, before raw image data
#define FUZZ_HDR_SZ 32

#define VPXC_INTERFACE(name) VPXC_INTERFACE_(name)
#define VPXC_INTERFACE_(name) vpx_codec_##name##_cx()

extern "C" void usage_exit(void) { exit(EXIT_FAILURE); }

static int vpx_img_plane_width(const vpx_image_t *img, int plane) {
  if (plane > 0 && img->x_chroma_shift > 0)
    return (img->d_w + 1) >> img->x_chroma_shift;
  else
    return img->d_w;
}

static int vpx_img_plane_height(const vpx_image_t *img, int plane) {
  if (plane > 0 && img->y_chroma_shift > 0)
    return (img->d_h + 1) >> img->y_chroma_shift;
  else
    return img->d_h;
}

static int fuzz_vpx_img_read(vpx_image_t *img, const uint8_t *data,
                             size_t size) {
  int plane;
  // TODO: wtc - Need to clamp the sample values so that they are in range
  // For example, if the bit depth is 10, the sample values must be <= 1023.
  assert(img->bit_depth == 8);
  const size_t bytespp = (img->fmt & VPX_IMG_FMT_HIGHBITDEPTH) ? 2 : 1;

  if (size == 0) return 0;
  size_t used = 0;
  for (plane = 0; plane < 3; ++plane) {
    unsigned char *buf = img->planes[plane];
    const int stride = img->stride[plane];
    int w = vpx_img_plane_width(img, plane);
    const int h = vpx_img_plane_height(img, plane);
    int y;

    // Assuming that for nv12 we read all chroma data at once
    if (img->fmt == VPX_IMG_FMT_NV12 && plane > 1) break;
    // NV12 UV plane is interleaved, so it has twice the width of a subsampled
    // plane.
    if (img->fmt == VPX_IMG_FMT_NV12 && plane == 1) w = w * 2;

    for (y = 0; y < h; ++y) {
      size_t nb = bytespp * w;
      if (nb > size - used) {
        nb = size - used;
      }
      memcpy(buf, data, nb);
      memset(buf + nb, 0, bytespp * w - nb);
      buf += stride;
      data += nb;
      used += nb;
    }
  }

  return used;
}

static int encode_frame(vpx_codec_ctx_t *codec, vpx_image_t *img,
                        int frame_index, int flags, FILE *out,
                        vpx_enc_deadline_t quality) {
  int got_pkts = 0;
  vpx_codec_iter_t iter = nullptr;
  const vpx_codec_cx_pkt_t *pkt = nullptr;
  const vpx_codec_err_t res =
      vpx_codec_encode(codec, img, frame_index, 1, flags, quality);
  if (res != VPX_CODEC_OK) return 0;

  while ((pkt = vpx_codec_get_cx_data(codec, &iter)) != nullptr) {
    got_pkts = 1;

    if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) {
      if (fwrite(pkt->data.frame.buf, 1, pkt->data.frame.sz, out) !=
          pkt->data.frame.sz)
        return 0;
    }
  }

  return got_pkts;
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
  if (size <= FUZZ_HDR_SZ) {
    return 0;
  }
  nalloc_init(nullptr);

  int keyframe_interval = 0;
  int frame_count = 0;
  vpx_codec_ctx_t codec;
  vpx_image_t raw;
  vpx_codec_enc_cfg_t cfg;
  vpx_enc_deadline_t quality = VPX_DL_GOOD_QUALITY;

  if ((data[0] & 0x80) != 0) {
    keyframe_interval = 8;
  }
  if ((data[0] & 0x40) != 0) {
    quality = VPX_DL_REALTIME;
  } else if ((data[0] & 0x20) != 0) {
    quality = VPX_DL_BEST_QUALITY;
  }
  const int max_frames = (quality == VPX_DL_BEST_QUALITY) ? 150 : 300;

  if (vpx_codec_enc_config_default(VPXC_INTERFACE(ENCODER), &cfg, 0)) abort();
  FILE *out = fopen("/dev/null", "wb");

  switch (data[0] & 0x1F) {
    case 0:
      cfg.g_w = 64;
      cfg.g_h = 1;
      break;
    case 1:
      cfg.g_w = 1;
      cfg.g_h = 48;
      break;
    case 2:
      cfg.g_w = 1;
      cfg.g_h = 1;
      break;
    case 3:
      cfg.g_w = 4;
      cfg.g_h = 4;
      break;
    case 4:
      cfg.g_w = 16;
      cfg.g_h = 16;
      break;
    default:
      cfg.g_w = 64;
      cfg.g_h = 48;
      break;
  }
  cfg.g_timebase.num = 1;
  cfg.g_timebase.den = 30;  // fps
  cfg.rc_target_bitrate = 200;
  cfg.g_error_resilient = 1;

  if (vpx_codec_enc_init(&codec, VPXC_INTERFACE(ENCODER), &cfg, 0)) {
    return 0;
  }

  if (!vpx_img_alloc(&raw, VPX_IMG_FMT_I420, cfg.g_w, cfg.g_h, 1)) {
    goto fail;
  }

  nalloc_start(data, size);
  // We may want to add more config options (for more complex encoders as seen
  // in the examples) in the future while still maintaining the same format (so
  // that generated corpus is still valid). So we reserve FUZZ_HDR_SZ=32 bytes
  // for this even if we just use one byte so far.
  data += FUZZ_HDR_SZ;
  size -= FUZZ_HDR_SZ;

  // Encode frames.
  for (int i = 0; i < max_frames; ++i) {
    int flags = 0;
    size_t size_read = fuzz_vpx_img_read(&raw, data, size);
    if (size_read == 0) break;
    data += size_read;
    size -= size_read;
    if (keyframe_interval > 0 && frame_count % keyframe_interval == 0)
      flags |= VPX_EFLAG_FORCE_KF;
    encode_frame(&codec, &raw, frame_count++, flags, out, quality);
  }

  // Flush encoder.
  while (encode_frame(&codec, nullptr, -1, 0, out, quality)) {
  }

fail:
  nalloc_end();
  vpx_img_free(&raw);
  vpx_codec_destroy(&codec);
  fclose(out);
  return 0;
}
