#define DECODE_TEST_C
#define _DEFAULT_SOURCE 1
#if defined(__linux__)
#  define _GNU_SOURCE 1 /* for memmem on linux */
#endif
#define _BSD_SOURCE 1
#ifdef __STDC_ALLOC_LIB__
#  define __STDC_WANT_LIB_EXT2__ 1 /* for strdup */
#else
#  define _USE_BSD 1
#endif

extern unsigned int loglevel;

#define IS_DECODER
#include <stdlib.h>
// #include "common.h"
// CLANG_DIAG_IGNORE (-Wpragma-pack)
#include "decode.c"
#include "common.c"
// CLANG_DIAG_RESTORE
#include "tests_common.h"

static void
read_literal_length_tests (void)
{
  struct _test
  {
    const char *bits;
    unsigned char opcode;
    int result;
  } tests[] = {
    { "00000101", 5, 8 },
    { "00000001", 14, 17 },
    { "1000000010000000", 0xf0, 146 },
  };
  for (int i = 0; i < ARRAY_SIZE (tests); i++)
    {
      Bit_Chain bitchain = strtobt (tests[i].bits);
      int result = read_literal_length (&bitchain, tests[i].opcode);
      if (result == tests[i].result)
        {
          if (i == ARRAY_SIZE (tests) - 1)
            ok ("read_literal_length");
          else
            pass ();
        }
      else
        fail ("read_literal_length [%d]: %d", i, result);
      bitfree (&bitchain);
    }
}

static void
read_compressed_bytes_tests (void)
{
  struct _test
  {
    const char *bits;
    unsigned char opcode;
    unsigned mask;
    int result;
  } tests[] = {
    { "11011101", 5, 7, 7 },
    { "11011101", 5, 1, 3 },
    { "0000000010000000", 4, 1, 386 },
  };
  for (int i = 0; i < ARRAY_SIZE (tests); i++)
    {
      Bit_Chain bitchain = strtobt (tests[i].bits);
      int result
          = read_compressed_bytes (&bitchain, tests[i].opcode, tests[i].mask);
      if (result == tests[i].result)
        {
          if (i == ARRAY_SIZE (tests) - 1)
            ok ("read_compressed_bytes");
          else
            pass ();
        }
      else
        fail ("read_compressed_bytes [%d]: %d", i, result);
      bitfree (&bitchain);
    }
}

static void
two_byte_offset_tests (void)
{
  struct _test
  {
    const char *bits;
    int offset;
    BITCODE_RC result;
  } tests[] = {
    { "1111000000000111", 509, 0xf0 },
    { "1111000000000110", 445, 0xf0 },
    { "1100000000000110", 433, 0xc0 },
  };
  for (int i = 0; i < ARRAY_SIZE (tests); i++)
    {
      Bit_Chain bitchain = strtobt (tests[i].bits);
      int offset = 0;
      BITCODE_RC result = two_byte_offset (&bitchain, 1, &offset);
      if (result == tests[i].result && offset == tests[i].offset)
        {
          if (i == ARRAY_SIZE (tests) - 1)
            ok ("two_byte_offset");
          else
            pass ();
        }
      else
        fail ("two_byte_offset [%d]: %d, 0x%x", i, offset, result);
      bitfree (&bitchain);
    }
}

static void
decompress_R2004_section_tests (void)
{
  int result;
  static Bit_Chain src, dec = { 0 };
  // from example_2004 via DEBUG
  unsigned char comp_auxh_bin[225] = {
    // very bad compression indeed
    0x00, 0x01, 0xff, 0x88, 0x01, 0x21, 0x00, 0x1d, 0x00, 0x19, 0x00, 0x00,
    0x00, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x02, 0x00, 0x50, 0x00, 0x01,
    0x16, 0x00, 0x2e, 0x00, 0x5c, 0x00, 0x01, 0x04, 0x00, 0x65, 0x05, 0x5f,
    0x03, 0x02, 0x00, 0x01, 0x58, 0x01, 0xbc, 0x00, 0x00, 0x07, 0x01, 0x00,
    0x00, 0x02, 0x00, 0x07, 0x00, 0xea, 0x74, 0x25, 0x00, 0x9a, 0xe6, 0x33,
    0x04, 0xb0, 0x82, 0x25, 0x00, 0xe0, 0x1c, 0xf7, 0x01, 0xe9, 0x0b, 0x9a,
    0x08, 0x0d, 0x00, 0x54, 0x08, 0x94, 0x03, 0x5c, 0x17, 0x5d, 0x02, 0x11,
    0x50, 0x01, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xce, 0x0c,
    0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00
  };
  unsigned char decomp_auxh_bin[123]
      = { 0xff, 0x88, 0x01, 0x21, 0x00, 0x1d, 0x00, 0x19, 0x00, 0x00, 0x00,
          0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x16, 0x00, 0x2e, 0x00, 0x16, 0x00, 0x2e, 0x00, 0x04, 0x00,
          0x65, 0x05, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x07, 0x00, 0xea, 0x74, 0x25,
          0x00, 0x9a, 0xe6, 0x33, 0x04, 0xb0, 0x82, 0x25, 0x00, 0xe0, 0x1c,
          0xf7, 0x01, 0xe9, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x0d, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00 };
  unsigned char comp_ofs_bin[181]
      = { 0x00, 0x04, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xb0,
          0x82, 0x25, 0x00, 0xe0, 0x1c, 0xf7, 0x01, 0x00, 0x00, 0x00, 0x00,
          0x04, 0x32, 0x54, 0x01, 0x01, 0x00, 0x00, 0x00, 0x64, 0x8e, 0x01,
          0x00, 0x02, 0x70, 0x02, 0x01, 0xff, 0xff, 0xff, 0xff, 0x74, 0x02,
          0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x14, 0x00, 0x0a,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x11, 0x00, 0x00 };
  unsigned char decomp_ofs_bin[53]
      = { 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xb0, 0x82, 0x25,
          0x00, 0xe0, 0x1c, 0xf7, 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x32,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
          0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
          0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 };

  src.chain = comp_auxh_bin;
  src.size = sizeof comp_auxh_bin;
  src.bit = 0;
  src.byte = 0UL;
  /* The real decoder allocates dec at max_decomp_size (0x7400=29696).
   * auxh: the LZ77 stream ends with a >C 29564 copy at dec[116], giving
   * end=29680 < 29696.  The test was broken: using sizeof(expected)=123
   * caused DWG_ERR_VALUEOUTOFBOUNDS.  Use 29696 like the real decoder. */
  bit_chain_alloc_size (&dec, 29696);
  dec.size = 29696;
  dec.bit = 0;
  dec.byte = 0UL;
  result = decompress_R2004_section (&src, &dec);
  if (result == 0
      && memcmp (dec.chain, decomp_auxh_bin, sizeof decomp_auxh_bin) == 0)
    pass ();
  else
    fail ("decompress_R2004_section auxh %d", result);

  src.chain = comp_ofs_bin;
  src.size = sizeof comp_ofs_bin;
  src.byte = 0;
  /* ofs: the LZ77 stream ends with a >C 30137 copy landing at dec[30192],
   * then 13 literal bytes -> total 30205.  32768 covers both sections. */
  bit_chain_alloc_size (&dec, 32768);
  dec.size = 32768;
  dec.byte = 0;
  result = decompress_R2004_section (&src, &dec);
  if (result == 0
      && memcmp (dec.chain, decomp_ofs_bin, sizeof decomp_ofs_bin) == 0)
    ok ("decompress_R2004_section");
  else
    fail ("decompress_R2004_section ofs %d", result);
}

/* Regression tests for CWE-125 OOB reads in decompress_r2007().
 *
 * Test B: read_literal_length() 0xFFFF tail-walk.
 *   opcode 0x0f enters the length==0x17 path; the 0xff-filled tail keeps
 *   n==0xFFFF in the do/while loop.  Unguarded: walks past the allocation.
 *   With fix: returns DWG_ERR_INTERNALERROR before the first OOB read.
 *
 * Test A: read_instructions() case-0 two-byte read at the buffer boundary.
 *   Stream ends exactly after the instruction opcode (case 0 needs 2 more
 *   bytes that aren't there).  With fix: returns DWG_ERR_INTERNALERROR.
 *
 * src_size = 8*251 = 2008 mirrors calloc(block_count=8, 251) in
 * read_data_page(); size_comp is an attacker-controlled page-header field.
 */
static void
decompress_r2007_tests (void)
{
  /* Test B */
  {
    const size_t block_count = 8;
    const size_t src_size = block_count * 251; /* 2008 */
    BITCODE_RC *src = (BITCODE_RC *)calloc (block_count, 251);
    BITCODE_RC *dst = (BITCODE_RC *)calloc (1, 1 << 20);
    int r;

    memset (src, 0xff, src_size);
    src[0] = 0x0f; /* read_literal_length: opcode+8==0x17, then 0xff-loop */

    r = decompress_r2007 (dst, 1 << 20, src, (unsigned)src_size,
                          dst + (1 << 20));
    if (r == (int)DWG_ERR_INTERNALERROR)
      pass ();
    else
      fail ("decompress_r2007 B (read_literal_length OOB): r=%d", r);
    free (src);
    free (dst);
  }

  /* Test A */
  {
    /* Stream: opcode 0x02 => literal length 10, 10 literal bytes, then
     * instruction opcode 0x05 (>>4 == 0, needs 2 more bytes) at src_end. */
    const unsigned char stream[]
        = { 0x02, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 0x05 };
    BITCODE_RC *src = (BITCODE_RC *)calloc (1, sizeof stream);
    BITCODE_RC *dst = (BITCODE_RC *)calloc (1, 4096);
    int r;

    memcpy (src, stream, sizeof stream);

    r = decompress_r2007 (dst, 4096, src, (unsigned)sizeof stream, dst + 4096);
    if (r == (int)DWG_ERR_INTERNALERROR)
      ok ("decompress_r2007 (A read_instructions, B read_literal_length OOB)");
    else
      fail ("decompress_r2007 A (read_instructions OOB): r=%d", r);
    free (src);
    free (dst);
  }
}

int
main (int argc, char const *argv[])
{
  loglevel = is_make_silent () ? 0 : 2;

  read_literal_length_tests ();
  read_compressed_bytes_tests ();
  two_byte_offset_tests ();
  decompress_R2004_section_tests ();
  decompress_r2007_tests ();

  return numfailed () ? 1 : 0;
}
