// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "sql/recover_module/record.h"

#include <cstddef>
#include <limits>
#include <type_traits>

#include "base/check_op.h"
#include "base/notreached.h"
#include "sql/recover_module/integers.h"
#include "sql/recover_module/payload.h"
#include "third_party/sqlite/sqlite3.h"

namespace sql {
namespace recover {

RecordReader::RecordReader(LeafPayloadReader* payload_reader, int column_count)
    : payload_reader_(payload_reader), column_count_(column_count) {
  DCHECK(payload_reader != nullptr);
  DCHECK_GT(column_count, 0);
  value_headers_.reserve(column_count);
}

RecordReader::~RecordReader() = default;

namespace {

// Value type indicating a null.
constexpr int kNullType = 0;
// Value type indicating a 1-byte signed integer.
constexpr int kInt1Type = 1;
// Value type indicating a 2-byte signed big-endian integer.
constexpr int kInt2Type = 2;
// Value type indicating a 3-byte signed big-endian integer.
constexpr int kInt3Type = 3;
// Value type indicating a 4-byte signed big-endian integer.
constexpr int kInt4Type = 4;
// Value type indicating a 6-byte signed big-endian integer.
constexpr int kInt6Type = 5;
// Value type indicating an 8-byte signed big-endian integer.
constexpr int kInt8Type = 6;
// Value type indicating a big-endian IEEE 754 64-bit floating point number.
constexpr int kDoubleType = 7;
// Value type indicating the integer 0 (zero).
constexpr int kIntZeroType = 8;
// Value type indicating the integer 1 (one).
constexpr int kIntOneType = 9;
// Value types greater than or equal to this indicate blobs or text.
constexpr int kMinBlobOrStringType = 12;

// The return value of ParseHeaderType below.
struct ParsedHeaderType {
  // True for the special value used to communicate a parsing error.
  bool IsInvalid() const {
    return type == ValueType::kNull && has_inline_value;
  }

  const ValueType type;
  const int64_t size;
  const int8_t inline_value;
  const bool has_inline_value;
};

// Decodes a type identifier in a SQLite record header.
//
// The type identifier includes the type and the size.
//
// Returns {kNull, 1} when parsing fails. Null values never require any extra
// bytes, so this special return value will never occur during normal
// processing.
ParsedHeaderType ParseHeaderType(int64_t encoded_type) {
  static constexpr int8_t kNoInlineValue = 0;

  if (encoded_type == kNullType)
    return {ValueType::kNull, 0, kNoInlineValue, false};
  if (encoded_type == kInt1Type)
    return {ValueType::kInteger, 1, kNoInlineValue, false};
  if (encoded_type == kInt2Type)
    return {ValueType::kInteger, 2, kNoInlineValue, false};
  if (encoded_type == kInt3Type)
    return {ValueType::kInteger, 3, kNoInlineValue, false};
  if (encoded_type == kInt4Type)
    return {ValueType::kInteger, 4, kNoInlineValue, false};
  if (encoded_type == kInt6Type)
    return {ValueType::kInteger, 6, kNoInlineValue, false};
  if (encoded_type == kInt8Type)
    return {ValueType::kInteger, 8, kNoInlineValue, false};
  if (encoded_type == kDoubleType)
    return {ValueType::kFloat, 8, kNoInlineValue, false};
  if (encoded_type == kIntZeroType)
    return {ValueType::kInteger, 0, 0, true};
  if (encoded_type == kIntOneType)
    return {ValueType::kInteger, 0, 1, true};

  if (encoded_type < kMinBlobOrStringType) {
    // Types between |kIntOneType| and |kMinBlobOrStringType| are reserved for
    // SQLite internal usage, and should not appear in persistent databases.
    // This shows database corruption.
    return {ValueType::kNull, 0, kNoInlineValue, true};
  }

  // Blobs and texts take alternating numbers starting at 12.
  encoded_type -= kMinBlobOrStringType;
  const ValueType value_type =
      (encoded_type & 1) == 0 ? ValueType::kBlob : ValueType::kText;
  const int64_t value_size = encoded_type >> 1;
  return {value_type, value_size, kNoInlineValue, false};
}

}  // namespace

bool RecordReader::Initialize() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // The size of |value_headers_| is used in DCHECKs to track whether
  // Initialize() succeeded.
  value_headers_.clear();

  int64_t next_value_offset = InitializeHeaderBuffer();
  if (next_value_offset == 0)
    return false;

  const uint8_t* header_pointer = header_buffer_.data();
  const uint8_t* header_end = header_buffer_.data() + header_buffer_.size();

  for (int i = 0; i < column_count_; ++i) {
    int64_t encoded_type;
    if (header_pointer == header_end) {
      // SQLite versions built with SQLITE_ENABLE_NULL_TRIM don't store trailing
      // null type IDs in the header.
      encoded_type = kNullType;
    } else {
      std::tie(encoded_type, header_pointer) =
          ParseVarint(header_pointer, header_end);
    }

    ParsedHeaderType parsed_type = ParseHeaderType(encoded_type);
    if (parsed_type.IsInvalid()) {
      // Parsing failed. The record is corrupted.
      return false;
    }
    value_headers_.emplace_back(next_value_offset, parsed_type.size,
                                parsed_type.type, parsed_type.inline_value,
                                parsed_type.has_inline_value);

    next_value_offset += parsed_type.size;
  }

  DCHECK_EQ(value_headers_.size(), static_cast<size_t>(column_count_));
  return true;
}

ValueType RecordReader::GetValueType(int column_index) const {
  DCHECK(IsInitialized());
  DCHECK_GE(column_index, 0);
  DCHECK_LT(static_cast<size_t>(column_index), value_headers_.size());

  return value_headers_[column_index].type;
}

namespace {

// Deallocates buffers passed to sqlite3_result_{blob,text}64().
void ValueBytesDeleter(void* buffer) {
  DCHECK(buffer != nullptr);
  uint8_t* value_bytes = reinterpret_cast<uint8_t*>(buffer);
  delete[] value_bytes;
}

}  // namespace

bool RecordReader::ReadValue(int column_index,
                             sqlite3_context* receiver) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(IsInitialized());
  DCHECK_GE(column_index, 0);
  DCHECK_LT(static_cast<size_t>(column_index), value_headers_.size());
  DCHECK(receiver != nullptr);

  const ValueHeader& header = value_headers_[column_index];

  const int64_t offset = header.offset;
  const int64_t size = header.size;

  if (header.type == ValueType::kNull) {
    DCHECK_EQ(size, 0);
    DCHECK(!header.has_inline_value);

    sqlite3_result_null(receiver);
    return true;
  }

  if (header.type == ValueType::kInteger) {
    if (header.has_inline_value) {
      sqlite3_result_int(receiver, header.inline_value);
      return true;
    }

    uint8_t value_bytes[8];
    DCHECK_GT(size, 0);
    DCHECK_LE(size, static_cast<int64_t>(sizeof(value_bytes)));
    // SQLite integers are big-endian, so the least significant bytes are at the
    // end of the integer's buffer.
    uint8_t* const first_read_byte = value_bytes + 8 - size;
    if (!payload_reader_->ReadPayload(offset, size, first_read_byte))
      return false;

    // Sign-extend the number.
    const uint8_t sign_byte = (*first_read_byte & 0x80) ? 0xff : 0;
    for (uint8_t* sign_extended_byte = &value_bytes[0];
         sign_extended_byte < first_read_byte; ++sign_extended_byte) {
      *sign_extended_byte = sign_byte;
    }

    const int64_t value = LoadBigEndianInt64(value_bytes);
    sqlite3_result_int64(receiver, value);
    return true;
  }

  if (header.type == ValueType::kFloat) {
    DCHECK_EQ(header.size, static_cast<int64_t>(sizeof(double)));
    DCHECK(!header.has_inline_value);

    union {
      double fp;
      int64_t integer;
      uint8_t bytes[8];
    } value;
    static_assert(sizeof(double) == 8,
                  "double is not the correct type to represent SQLite floats");
    if (!payload_reader_->ReadPayload(header.offset, sizeof(double),
                                      reinterpret_cast<uint8_t*>(&value))) {
      return false;
    }
    // SQLite's doubles are big-endian.
    value.integer = LoadBigEndianInt64(value.bytes);
    sqlite3_result_double(receiver, value.fp);
    return true;
  }

  if (header.type == ValueType::kBlob || header.type == ValueType::kText) {
    DCHECK_GE(header.size, 0);
    DCHECK(!header.has_inline_value);

    uint8_t* const value_bytes = new uint8_t[size];
    if (!payload_reader_->ReadPayload(offset, size, value_bytes)) {
      delete[] value_bytes;
      return false;
    }
    if (header.type == ValueType::kBlob) {
      sqlite3_result_blob64(receiver, value_bytes, static_cast<uint64_t>(size),
                            &ValueBytesDeleter);
    } else {
      DCHECK_EQ(header.type, ValueType::kText);

      const unsigned char encoding = SQLITE_UTF8;
      sqlite3_result_text64(receiver, reinterpret_cast<char*>(value_bytes),
                            static_cast<uint64_t>(size), &ValueBytesDeleter,
                            encoding);
    }
    return true;
  }

  NOTREACHED() << "Invalid value type";
  return false;
}

void RecordReader::Reset() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  value_headers_.clear();
}

int64_t RecordReader::InitializeHeaderBuffer() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  const uint8_t* const inline_payload_start =
      payload_reader_->ReadInlinePayload();
  if (inline_payload_start == nullptr) {
    // Read failure.
    return 0;
  }

  const int64_t inline_payload_size = payload_reader_->inline_payload_size();
  const uint8_t* const inline_payload_end =
      inline_payload_start + inline_payload_size;
  int64_t header_size;
  const uint8_t* payload_header_start;
  std::tie(header_size, payload_header_start) =
      ParseVarint(inline_payload_start, inline_payload_end);

  if (header_size < 0 || header_size > payload_reader_->payload_size()) {
    // The header is bigger than the entire record. This record is corrupted.
    return 0;
  }

  int header_size_size = payload_header_start - inline_payload_start;
  static_assert(std::numeric_limits<int>::max() > kMaxVarintSize,
                "The |header_size_size| computation above may overflow");

  // The header size varint is included in the header size computation.
  const int64_t header_data_size = header_size - header_size_size;
  header_buffer_.resize(header_data_size);
  if (!payload_reader_->ReadPayload(header_size_size, header_data_size,
                                    header_buffer_.data())) {
    // Read failure.
    return 0;
  }

  return header_size;
}

}  // namespace recover
}  // namespace sql