// Copyright (c) 2021 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 "base/debug/buffered_dwarf_reader.h"

#ifdef USE_SYMBOLIZE

#include "base/third_party/symbolize/symbolize.h"

#include <algorithm>
#include <cstring>

namespace base {
namespace debug {

BufferedDwarfReader::BufferedDwarfReader(int fd, uint64_t position)
    : fd_(fd), next_chunk_start_(position), last_chunk_start_(position) {}

size_t BufferedDwarfReader::ReadCString(uint64_t max_position,
                                        char* out,
                                        size_t out_size) {
  uint8_t character;
  size_t bytes_written = 0;
  do {
    if (!ReadInt8(character)) {
      return 0;
    }

    if (out && bytes_written < out_size) {
      out[bytes_written++] = character;
    }
  } while (character != '\0' && position() < max_position);

  if (out) {
    out[std::min(bytes_written, out_size - 1)] = '\0';
  }

  return bytes_written;
}

bool BufferedDwarfReader::ReadLeb128(uint64_t& value) {
  value = 0;
  uint8_t byte;
  int shift = 0;
  do {
    if (!ReadInt8(byte))
      return false;
    value |= static_cast<uint64_t>(byte & 0x7F) << shift;
    shift += 7;
  } while (byte & 0x80);
  return true;
}

bool BufferedDwarfReader::ReadLeb128(int64_t& value) {
  value = 0;
  uint8_t byte;
  int shift = 0;
  bool sign_bit = false;
  do {
    if (!ReadInt8(byte))
      return false;
    value |= static_cast<uint64_t>(byte & 0x7F) << shift;
    shift += 7;
    sign_bit = byte & 0x40;
  } while (byte & 0x80);
  constexpr int bits_in_output = sizeof(value) * 8;
  if ((shift < bits_in_output) && sign_bit) {
    value |= -(1 << shift);
  }
  return true;
}

bool BufferedDwarfReader::ReadInitialLength(bool& is_64bit, uint64_t& length) {
  uint32_t token_32bit;

  if (!ReadInt32(token_32bit)) {
    return false;
  }

  // Dwarf 3 introduced an extended length field that both indicates this is
  // DWARF-64 and changes how the size is encoded. 0xfffffff0 and higher are
  // reserved with 0xffffffff meaning it's the extended field with the
  // following 64-bits being the full length.
  if (token_32bit < 0xfffffff0) {
    length = token_32bit;
    is_64bit = false;
    return true;
  }

  if (token_32bit != 0xffffffff) {
    return false;
  }

  if (!ReadInt64(length)) {
    return false;
  }

  is_64bit = true;
  return true;
}

bool BufferedDwarfReader::ReadOffset(bool is_64bit, uint64_t& offset) {
  if (is_64bit) {
    if (!ReadInt64(offset)) {
      return false;
    }
  } else {
    uint32_t tmp;
    if (!ReadInt32(tmp)) {
      return false;
    }
    offset = tmp;
  }
  return true;
}

bool BufferedDwarfReader::ReadAddress(uint8_t address_size, uint64_t& address) {
  // Note `address_size` indicates the numbrer of bytes in the address.
  switch (address_size) {
    case 2: {
      uint16_t tmp;
      if (!ReadInt16(tmp))
        return false;
      address = tmp;
    } break;

    case 4: {
      uint32_t tmp;
      if (!ReadInt32(tmp))
        return false;
      address = tmp;
    } break;

    case 8: {
      uint64_t tmp;
      if (!ReadInt64(tmp))
        return false;
      address = tmp;
    } break;

    default:
      return false;
  }
  return true;
}

bool BufferedDwarfReader::ReadCommonHeader(bool& is_64bit,
                                           uint64_t& length,
                                           uint16_t& version,
                                           uint64_t& offset,
                                           uint8_t& address_size,
                                           uint64_t& end_position) {
  if (!ReadInitialLength(is_64bit, length)) {
    return false;
  }
  end_position = position() + length;

  if (!ReadInt16(version)) {
    return false;
  }

  if (!ReadOffset(is_64bit, offset)) {
    return false;
  }

  if (!ReadInt8(address_size)) {
    return false;
  }

  return true;
}

bool BufferedDwarfReader::BufferedRead(void* out, const size_t bytes) {
  size_t bytes_left = bytes;
  while (bytes_left > 0) {
    // Refresh the buffer.
    if (unconsumed_amount_ == 0) {
      unconsumed_amount_ =
          google::ReadFromOffset(fd_, buf_, sizeof(buf_), next_chunk_start_);
      if (unconsumed_amount_ == 0) {
        // Read error.
        return false;
      }

      last_chunk_start_ = next_chunk_start_;
      next_chunk_start_ = next_chunk_start_ + unconsumed_amount_;
      cursor_in_buffer_ = 0;
    }

    size_t to_copy = std::min(bytes_left, unconsumed_amount_);
    memcpy(out, &buf_[cursor_in_buffer_], to_copy);
    unconsumed_amount_ -= to_copy;
    cursor_in_buffer_ += to_copy;
    bytes_left -= to_copy;
  }
  return true;
}

}  // namespace debug
}  // namespace base

#endif
