// Copyright 2008 Google Inc.
// Author: Lincoln Smith
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <config.h>
#include "vcdecoder_test.h"
#include <string.h>  // strlen
#include "checksum.h"
#include "codetable.h"
#include "testing.h"
#include "varint_bigendian.h"
#include "vcdiff_defs.h"

namespace open_vcdiff {

const char VCDiffDecoderTest::kStandardFileHeader[] = {
    0xD6,  // 'V' | 0x80
    0xC3,  // 'C' | 0x80
    0xC4,  // 'D' | 0x80
    0x00,  // Draft standard version number
    0x00   // Hdr_Indicator: no custom code table, no compression
  };

const char VCDiffDecoderTest::kInterleavedFileHeader[] = {
    0xD6,  // 'V' | 0x80
    0xC3,  // 'C' | 0x80
    0xC4,  // 'D' | 0x80
    'S',   // SDCH version code
    0x00   // Hdr_Indicator: no custom code table, no compression
  };

const char VCDiffDecoderTest::kDictionary[] =
  "\"Just the place for a Snark!\" the Bellman cried,\n"
  "As he landed his crew with care;\n"
  "Supporting each man on the top of the tide\n"
  "By a finger entwined in his hair.\n";

const char VCDiffDecoderTest::kExpectedTarget[] =
  "\"Just the place for a Snark! I have said it twice:\n"
  "That alone should encourage the crew.\n"
  "Just the place for a Snark! I have said it thrice:\n"
  "What I tell you three times is true.\"\n";

VCDiffDecoderTest::VCDiffDecoderTest() : fuzzer_(0), fuzzed_byte_position_(0) {
  dictionary_ = kDictionary;
  expected_target_ = kExpectedTarget;
}

void VCDiffDecoderTest::SetUp() {
  InitializeDeltaFile();
}

void VCDiffDecoderTest::UseStandardFileHeader() {
  delta_file_header_.assign(kStandardFileHeader,
                            sizeof(kStandardFileHeader));
}

void VCDiffDecoderTest::UseInterleavedFileHeader() {
  delta_file_header_.assign(kInterleavedFileHeader,
                            sizeof(kInterleavedFileHeader));
}

void VCDiffDecoderTest::InitializeDeltaFile() {
  delta_file_ = delta_file_header_ + delta_window_header_ + delta_window_body_;
}

char VCDiffDecoderTest::GetByteFromStringLength(const char* s,
                                                int which_byte) {
  char varint_buf[VarintBE<int32_t>::kMaxBytes];
  VarintBE<int32_t>::Encode(static_cast<int32_t>(strlen(s)), varint_buf);
  return varint_buf[which_byte];
}

void VCDiffDecoderTest::AddChecksum(VCDChecksum checksum) {
  int32_t checksum_as_int32 = static_cast<int32_t>(checksum);
  delta_window_header_[0] |= VCD_CHECKSUM;
  VarintBE<int32_t>::AppendToString(checksum_as_int32, &delta_window_header_);
  // Adjust delta window size to include checksum.
  // This method wouldn't work if adding to the length caused the VarintBE
  // value to spill over into another byte.  Luckily, this test data happens
  // not to cause such an overflow.
  delta_window_header_[4] += VarintBE<int32_t>::Length(checksum_as_int32);
}

void VCDiffDecoderTest::ComputeAndAddChecksum() {
  AddChecksum(ComputeAdler32(expected_target_.data(),
                             expected_target_.size()));
}

// Write the maximum expressible positive 32-bit VarintBE
// (0x7FFFFFFF) at the given offset in the delta window.
void VCDiffDecoderTest::WriteMaxVarintAtOffset(int offset,
                                               int bytes_to_replace) {
  static const char kMaxVarint[] = { 0x87, 0xFF, 0xFF, 0xFF, 0x7F };
  delta_file_.replace(delta_file_header_.size() + offset,
                      bytes_to_replace,
                      kMaxVarint,
                      sizeof(kMaxVarint));
}

// Write a negative 32-bit VarintBE (0x80000000) at the given offset
// in the delta window.
void VCDiffDecoderTest::WriteNegativeVarintAtOffset(int offset,
                                                    int bytes_to_replace) {
  static const char kNegativeVarint[] = { 0x88, 0x80, 0x80, 0x80, 0x00 };
  delta_file_.replace(delta_file_header_.size() + offset,
                      bytes_to_replace,
                      kNegativeVarint,
                      sizeof(kNegativeVarint));
}

// Write a VarintBE that has too many continuation bytes
// at the given offset in the delta window.
void VCDiffDecoderTest::WriteInvalidVarintAtOffset(int offset,
                                                   int bytes_to_replace) {
  static const char kInvalidVarint[] = { 0x87, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F };
  delta_file_.replace(delta_file_header_.size() + offset,
                      bytes_to_replace,
                      kInvalidVarint,
                      sizeof(kInvalidVarint));
}

bool VCDiffDecoderTest::FuzzOneByteInDeltaFile() {
  static const struct Fuzzer {
    char _and;
    char _or;
    char _xor;
  } fuzzers[] = {
    { 0xff, 0x80, 0x00 },
    { 0xff, 0xff, 0x00 },
    { 0xff, 0x00, 0x80 },
    { 0xff, 0x00, 0xff },
    { 0xff, 0x01, 0x00 },
    { 0x7f, 0x00, 0x00 },
  };

  for (; fuzzer_ < (sizeof(fuzzers) / sizeof(fuzzers[0])); ++fuzzer_) {
    for (; fuzzed_byte_position_ < delta_file_.size();
         ++fuzzed_byte_position_) {
      char fuzzed_byte = (((delta_file_[fuzzed_byte_position_]
                             & fuzzers[fuzzer_]._and)
                             | fuzzers[fuzzer_]._or)
                             ^ fuzzers[fuzzer_]._xor);
      if (fuzzed_byte != delta_file_[fuzzed_byte_position_]) {
        delta_file_[fuzzed_byte_position_] = fuzzed_byte;
        ++fuzzed_byte_position_;
        return true;
      }
    }
    fuzzed_byte_position_ = 0;
  }
  return false;
}

const char VCDiffStandardDecoderTest::kWindowHeader[] = {
    VCD_SOURCE,  // Win_Indicator: take source from dictionary
    FirstByteOfStringLength(kDictionary),  // Source segment size
    SecondByteOfStringLength(kDictionary),
    0x00,  // Source segment position: start of dictionary
    0x79,  // Length of the delta encoding
    FirstByteOfStringLength(kExpectedTarget),  // Size of the target window
    SecondByteOfStringLength(kExpectedTarget),
    0x00,  // Delta_indicator (no compression)
    0x64,  // length of data for ADDs and RUNs
    0x0C,  // length of instructions section
    0x03  // length of addresses for COPYs
  };

const char VCDiffStandardDecoderTest::kWindowBody[] = {
    // Data for ADDs: 1st section (length 61)
    ' ', 'I', ' ', 'h', 'a', 'v', 'e', ' ', 's', 'a', 'i', 'd', ' ',
    'i', 't', ' ', 't', 'w', 'i', 'c', 'e', ':', '\n',
    'T', 'h', 'a', 't', ' ',
    'a', 'l', 'o', 'n', 'e', ' ', 's', 'h', 'o', 'u', 'l', 'd', ' ',
    'e', 'n', 'c', 'o', 'u', 'r', 'a', 'g', 'e', ' ',
    't', 'h', 'e', ' ', 'c', 'r', 'e', 'w', '.', '\n',
    // Data for ADDs: 2nd section (length 2)
    'h', 'r',
    // Data for ADDs: 3rd section (length 9)
    'W', 'h', 'a', 't', ' ',
    'I', ' ', 't', 'e',
    // Data for RUN: 4th section (length 1)
    'l',
    // Data for ADD: 4th section (length 27)
    ' ', 'y', 'o', 'u', ' ',
    't', 'h', 'r', 'e', 'e', ' ', 't', 'i', 'm', 'e', 's', ' ', 'i', 's', ' ',
    't', 'r', 'u', 'e', '.', '\"', '\n',
    // Instructions and sizes (length 13)
    0x13,  // VCD_COPY mode VCD_SELF, size 0
    0x1C,  // Size of COPY (28)
    0x01,  // VCD_ADD size 0
    0x3D,  // Size of ADD (61)
    0x23,  // VCD_COPY mode VCD_HERE, size 0
    0x2C,  // Size of COPY (44)
    0xCB,  // VCD_ADD size 2 + VCD_COPY mode NEAR(1), size 5
    0x0A,  // VCD_ADD size 9
    0x00,  // VCD_RUN size 0
    0x02,  // Size of RUN (2)
    0x01,  // VCD_ADD size 0
    0x1B,  // Size of ADD (27)
    // Addresses for COPYs (length 3)
    0x00,  // Start of dictionary
    0x58,  // HERE mode address for 2nd copy (27+61 back from here_address)
    0x2D   // NEAR(1) mode address for 2nd copy (45 after prior address)
  };

VCDiffStandardDecoderTest::VCDiffStandardDecoderTest() {
  UseStandardFileHeader();
  delta_window_header_.assign(kWindowHeader, sizeof(kWindowHeader));
  delta_window_body_.assign(kWindowBody, sizeof(kWindowBody));
}

const char VCDiffInterleavedDecoderTest::kWindowHeader[] = {
    VCD_SOURCE,  // Win_Indicator: take source from dictionary
    FirstByteOfStringLength(kDictionary),  // Source segment size
    SecondByteOfStringLength(kDictionary),
    0x00,  // Source segment position: start of dictionary
    0x79,  // Length of the delta encoding
    FirstByteOfStringLength(kExpectedTarget),  // Size of the target window
    SecondByteOfStringLength(kExpectedTarget),
    0x00,  // Delta_indicator (no compression)
    0x00,  // length of data for ADDs and RUNs (unused)
    0x73,  // length of interleaved section
    0x00  // length of addresses for COPYs (unused)
  };

const char VCDiffInterleavedDecoderTest::kWindowBody[] = {
    0x13,  // VCD_COPY mode VCD_SELF, size 0
    0x1C,  // Size of COPY (28)
    0x00,  // Address of COPY: Start of dictionary
    0x01,  // VCD_ADD size 0
    0x3D,  // Size of ADD (61)
    // Data for ADD (length 61)
    ' ', 'I', ' ', 'h', 'a', 'v', 'e', ' ', 's', 'a', 'i', 'd', ' ',
    'i', 't', ' ', 't', 'w', 'i', 'c', 'e', ':', '\n',
    'T', 'h', 'a', 't', ' ',
    'a', 'l', 'o', 'n', 'e', ' ', 's', 'h', 'o', 'u', 'l', 'd', ' ',
    'e', 'n', 'c', 'o', 'u', 'r', 'a', 'g', 'e', ' ',
    't', 'h', 'e', ' ', 'c', 'r', 'e', 'w', '.', '\n',
    0x23,  // VCD_COPY mode VCD_HERE, size 0
    0x2C,  // Size of COPY (44)
    0x58,  // HERE mode address (27+61 back from here_address)
    0xCB,  // VCD_ADD size 2 + VCD_COPY mode NEAR(1), size 5
    // Data for ADDs: 2nd section (length 2)
    'h', 'r',
    0x2D,  // NEAR(1) mode address (45 after prior address)
    0x0A,  // VCD_ADD size 9
    // Data for ADDs: 3rd section (length 9)
    'W', 'h', 'a', 't', ' ',
    'I', ' ', 't', 'e',
    0x00,  // VCD_RUN size 0
    0x02,  // Size of RUN (2)
    // Data for RUN: 4th section (length 1)
    'l',
    0x01,  // VCD_ADD size 0
    0x1B,  // Size of ADD (27)
    // Data for ADD: 4th section (length 27)
    ' ', 'y', 'o', 'u', ' ',
    't', 'h', 'r', 'e', 'e', ' ', 't', 'i', 'm', 'e', 's', ' ', 'i', 's', ' ',
    't', 'r', 'u', 'e', '.', '\"', '\n'
  };

VCDiffInterleavedDecoderTest::VCDiffInterleavedDecoderTest() {
  UseInterleavedFileHeader();
  delta_window_header_.assign(kWindowHeader, sizeof(kWindowHeader));
  delta_window_body_.assign(kWindowBody, sizeof(kWindowBody));
}

}  // namespace open_vcdiff
