// 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 "headerparser.h"
#include "logging.h"
#include "varint_bigendian.h"
#include "vcdiff_defs.h"

namespace open_vcdiff {

// *** Methods for ParseableChunk

void ParseableChunk::Advance(size_t number_of_bytes) {
  if (number_of_bytes > UnparsedSize()) {
    LOG(DFATAL) << "Internal error: position advanced by " << number_of_bytes
                << " bytes, current unparsed size " << UnparsedSize()
                << LOG_ENDL;
    position_ = end_;
    return;
  }
  position_ += number_of_bytes;
}

void ParseableChunk::SetPosition(const char* position) {
  if (position < start_) {
    LOG(DFATAL) << "Internal error: new data position " << position
                << " is beyond start of data " << start_ << LOG_ENDL;
    position_ = start_;
    return;
  }
  if (position > end_) {
    LOG(DFATAL) << "Internal error: new data position " << position
                << " is beyond end of data " << end_ << LOG_ENDL;
    position_ = end_;
    return;
  }
  position_ = position;
}

void ParseableChunk::FinishExcept(size_t number_of_bytes) {
  if (number_of_bytes > UnparsedSize()) {
    LOG(DFATAL) << "Internal error: specified number of remaining bytes "
                << number_of_bytes << " is greater than unparsed data size "
                << UnparsedSize() << LOG_ENDL;
    Finish();
    return;
  }
  position_ = end_ - number_of_bytes;
}

// *** Methods for VCDiffHeaderParser

VCDiffHeaderParser::VCDiffHeaderParser(const char* header_start,
                                       const char* data_end)
    : parseable_chunk_(header_start, data_end - header_start),
      return_code_(RESULT_SUCCESS),
      delta_encoding_length_(0),
      delta_encoding_start_(NULL) { }

bool VCDiffHeaderParser::ParseByte(unsigned char* value) {
  if (RESULT_SUCCESS != return_code_) {
    return false;
  }
  if (parseable_chunk_.Empty()) {
    return_code_ = RESULT_END_OF_DATA;
    return false;
  }
  *value = static_cast<unsigned char>(*parseable_chunk_.UnparsedData());
  parseable_chunk_.Advance(1);
  return true;
}

bool VCDiffHeaderParser::ParseInt32(const char* variable_description,
                                    int32_t* value) {
  if (RESULT_SUCCESS != return_code_) {
    return false;
  }
  int32_t parsed_value =
      VarintBE<int32_t>::Parse(parseable_chunk_.End(),
                               parseable_chunk_.UnparsedDataAddr());
  switch (parsed_value) {
    case RESULT_ERROR:
      LOG(ERROR) << "Expected " << variable_description
                 << "; found invalid variable-length integer" << LOG_ENDL;
      return_code_ = RESULT_ERROR;
      return false;
    case RESULT_END_OF_DATA:
      return_code_ = RESULT_END_OF_DATA;
      return false;
    default:
      *value = parsed_value;
      return true;
  }
}

// When an unsigned 32-bit integer is expected, parse a signed 64-bit value
// instead, then check the value limit.  The uint32_t type can't be parsed
// directly because two negative values are given special meanings (RESULT_ERROR
// and RESULT_END_OF_DATA) and could not be expressed in an unsigned format.
bool VCDiffHeaderParser::ParseUInt32(const char* variable_description,
                                     uint32_t* value) {
  if (RESULT_SUCCESS != return_code_) {
    return false;
  }
  int64_t parsed_value =
      VarintBE<int64_t>::Parse(parseable_chunk_.End(),
                               parseable_chunk_.UnparsedDataAddr());
  switch (parsed_value) {
    case RESULT_ERROR:
      LOG(ERROR) << "Expected " << variable_description
                 << "; found invalid variable-length integer" << LOG_ENDL;
      return_code_ = RESULT_ERROR;
      return false;
    case RESULT_END_OF_DATA:
      return_code_ = RESULT_END_OF_DATA;
      return false;
    default:
      if (parsed_value > 0xFFFFFFFF) {
        LOG(ERROR) << "Value of " << variable_description << "(" << parsed_value
                   << ") is too large for unsigned 32-bit integer" << LOG_ENDL;
        return_code_ = RESULT_ERROR;
        return false;
      }
      *value = static_cast<uint32_t>(parsed_value);
      return true;
  }
}

// A VCDChecksum represents an unsigned 32-bit value returned by adler32(),
// but isn't a uint32_t.
bool VCDiffHeaderParser::ParseChecksum(const char* variable_description,
                                       VCDChecksum* value) {
  uint32_t parsed_value = 0;
  if (!ParseUInt32(variable_description, &parsed_value)) {
    return false;
  }
  *value = static_cast<VCDChecksum>(parsed_value);
  return true;
}

bool VCDiffHeaderParser::ParseSize(const char* variable_description,
                                   size_t* value) {
  int32_t parsed_value = 0;
  if (!ParseInt32(variable_description, &parsed_value)) {
    return false;
  }
  *value = static_cast<size_t>(parsed_value);
  return true;
}

bool VCDiffHeaderParser::ParseSourceSegmentLengthAndPosition(
    size_t from_size,
    const char* from_boundary_name,
    const char* from_name,
    size_t* source_segment_length,
    size_t* source_segment_position) {
  // Verify the length and position values
  if (!ParseSize("source segment length", source_segment_length)) {
    return false;
  }
  // Guard against overflow by checking source length first
  if (*source_segment_length > from_size) {
    LOG(ERROR) << "Source segment length (" << *source_segment_length
               << ") is larger than " << from_name << " (" << from_size
               << ")" << LOG_ENDL;
    return_code_ = RESULT_ERROR;
    return false;
  }
  if (!ParseSize("source segment position", source_segment_position)) {
    return false;
  }
  if ((*source_segment_position >= from_size) &&
      (*source_segment_length > 0)) {
    LOG(ERROR) << "Source segment position (" << *source_segment_position
               << ") is past " << from_boundary_name
               << " (" << from_size << ")" << LOG_ENDL;
    return_code_ = RESULT_ERROR;
    return false;
  }
  const size_t source_segment_end = *source_segment_position +
                                    *source_segment_length;
  if (source_segment_end > from_size) {
    LOG(ERROR) << "Source segment end position (" << source_segment_end
               << ") is past " << from_boundary_name
               << " (" << from_size << ")" << LOG_ENDL;
    return_code_ = RESULT_ERROR;
    return false;
  }
  return true;
}

bool VCDiffHeaderParser::ParseWinIndicatorAndSourceSegment(
    size_t dictionary_size,
    size_t decoded_target_size,
    bool allow_vcd_target,
    unsigned char* win_indicator,
    size_t* source_segment_length,
    size_t* source_segment_position) {
  if (!ParseByte(win_indicator)) {
    return false;
  }
  unsigned char source_target_flags =
      *win_indicator & (VCD_SOURCE | VCD_TARGET);
  switch (source_target_flags) {
    case VCD_SOURCE:
      return ParseSourceSegmentLengthAndPosition(dictionary_size,
                                                 "end of dictionary",
                                                 "dictionary",
                                                 source_segment_length,
                                                 source_segment_position);
    case VCD_TARGET:
      if (!allow_vcd_target) {
        LOG(ERROR) << "Delta file contains VCD_TARGET flag, which is not "
                      "allowed by current decoder settings" << LOG_ENDL;
        return_code_ = RESULT_ERROR;
        return false;
      }
      return ParseSourceSegmentLengthAndPosition(decoded_target_size,
                                                 "current target position",
                                                 "target file",
                                                 source_segment_length,
                                                 source_segment_position);
    case VCD_SOURCE | VCD_TARGET:
      LOG(ERROR) << "Win_Indicator must not have both VCD_SOURCE"
                    " and VCD_TARGET set" << LOG_ENDL;
      return_code_ = RESULT_ERROR;
      return false;
    default:
      return true;
  }
}

bool VCDiffHeaderParser::ParseWindowLengths(size_t* target_window_length) {
  if (delta_encoding_start_) {
    LOG(DFATAL) << "Internal error: VCDiffHeaderParser::ParseWindowLengths "
                   "was called twice for the same delta window" << LOG_ENDL;
    return_code_ = RESULT_ERROR;
    return false;
  }
  if (!ParseSize("length of the delta encoding", &delta_encoding_length_)) {
    return false;
  }
  delta_encoding_start_ = UnparsedData();
  if (!ParseSize("size of the target window", target_window_length)) {
    return false;
  }
  return true;
}

const char* VCDiffHeaderParser::EndOfDeltaWindow() const {
  if (!delta_encoding_start_) {
    LOG(DFATAL) << "Internal error: VCDiffHeaderParser::GetDeltaWindowEnd "
                   "was called before ParseWindowLengths" << LOG_ENDL;
    return NULL;
  }
  return delta_encoding_start_ + delta_encoding_length_;
}

bool VCDiffHeaderParser::ParseDeltaIndicator() {
  unsigned char delta_indicator;
  if (!ParseByte(&delta_indicator)) {
    return false;
  }
  if (delta_indicator & (VCD_DATACOMP | VCD_INSTCOMP | VCD_ADDRCOMP)) {
    LOG(ERROR) << "Secondary compression of delta file sections "
                  "is not supported" << LOG_ENDL;
    return_code_ = RESULT_ERROR;
    return false;
  }
  return true;
}

bool VCDiffHeaderParser::ParseSectionLengths(
    bool has_checksum,
    size_t* add_and_run_data_length,
    size_t* instructions_and_sizes_length,
    size_t* addresses_length,
    VCDChecksum* checksum) {
  ParseSize("length of data for ADDs and RUNs", add_and_run_data_length);
  ParseSize("length of instructions section", instructions_and_sizes_length);
  ParseSize("length of addresses for COPYs", addresses_length);
  if (has_checksum) {
    ParseChecksum("Adler32 checksum value", checksum);
  }
  if (RESULT_SUCCESS != return_code_) {
    return false;
  }
  if (!delta_encoding_start_) {
    LOG(DFATAL) << "Internal error: VCDiffHeaderParser::ParseSectionLengths "
                   "was called before ParseWindowLengths" << LOG_ENDL;
    return_code_ = RESULT_ERROR;
    return false;
  }
  const size_t delta_encoding_header_length =
      UnparsedData() - delta_encoding_start_;
  if (delta_encoding_length_ !=
          (delta_encoding_header_length +
           *add_and_run_data_length +
           *instructions_and_sizes_length +
           *addresses_length)) {
    LOG(ERROR) << "The length of the delta encoding does not match "
                  "the size of the header plus the sizes of the data sections"
               << LOG_ENDL;
    return_code_ = RESULT_ERROR;
    return false;
  }
  return true;
}

}  // namespace open_vcdiff