// Copyright 2014 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 "delta_encoder.h"

#include <vector>
#include "elf.h"
#include "gtest/gtest.h"

namespace {

template <typename T>
void AddRelocation(uint32_t addr,
                   uint32_t info,
                   int32_t addend,
                   std::vector<T>* relocations) {
  T relocation;
  relocation.r_offset = addr;
  relocation.r_info = info;
  relocation.r_addend = addend;
  relocations->push_back(relocation);
}

template <typename T>
bool CheckRelocation(uint32_t addr,
                     uint32_t info,
                     int32_t addend,
                     const T& relocation) {
  return relocation.r_offset == addr &&
      relocation.r_info == info &&
      relocation.r_addend == addend;
}

}  // namespace

namespace relocation_packer {

template <typename ELF>
static void encode() {
  std::vector<typename ELF::Rela> relocations;
  std::vector<typename ELF::Addr> packed;

  RelocationDeltaCodec<ELF> codec;

  codec.Encode(relocations, &packed);

  ASSERT_EQ(0U, packed.size());

  // Initial relocation.
  AddRelocation(0xf00d0000, 11U, 10000, &relocations);

  codec.Encode(relocations, &packed);

  // size of reloc table, size of group, flags, 3 fields, zero
  EXPECT_EQ(7U, packed.size());
  // One pair present.
  size_t ndx = 0;
  EXPECT_EQ(1U, packed[ndx++]);
  EXPECT_EQ(0xf00d0000, packed[ndx++]);
  EXPECT_EQ(1U, packed[ndx++]); // group_size
  EXPECT_EQ(8U, packed[ndx++]); // flags
  // Delta from the neutral element is zero
  EXPECT_EQ(0U, packed[ndx++]); // offset_delta
  EXPECT_EQ(11U, packed[ndx++]); // info
  EXPECT_EQ(10000U, packed[ndx++]); // addend_delta

  // Add a second relocation, 4 byte offset delta, 12 byte addend delta.
  // same info
  AddRelocation(0xf00d0004, 11U, 10012, &relocations);

  packed.clear();
  codec.Encode(relocations, &packed);

  ndx = 0;
  EXPECT_EQ(8U, packed.size());

  EXPECT_EQ(2U, packed[ndx++]); // relocs count
  EXPECT_EQ(0xf00cfffc, packed[ndx++]); // initial offset
  EXPECT_EQ(2U, packed[ndx++]); // group count
  EXPECT_EQ(11U, packed[ndx++]); // flags
  EXPECT_EQ(4U, packed[ndx++]); // group offset delta
  EXPECT_EQ(11U, packed[ndx++]); // info

  EXPECT_EQ(10000U, packed[ndx++]); // addend delta
  EXPECT_EQ(12U, packed[ndx++]); // addend delta

  // Add a third relocation, 4 byte offset delta, 12 byte addend delta.
  // different info
  AddRelocation(0xf00d0008, 41U, 10024, &relocations);

  // Add three more relocations, 8 byte offset deltas, -24 byte addend deltas.
  AddRelocation(0xf00d0010, 42U, 10000, &relocations);
  AddRelocation(0xf00d0018, 42U, 9976, &relocations);
  AddRelocation(0xf00d0020, 42U, 9952, &relocations);

  AddRelocation(0xf00d2028, 1042U, 0, &relocations);
  AddRelocation(0xf00d2030, 3442U, 0, &relocations);

  packed.clear();
  codec.Encode(relocations, &packed);

  ndx = 0;
  EXPECT_EQ(26U, packed.size());
  // Total number of relocs
  EXPECT_EQ(8U, packed[ndx++]);
  EXPECT_EQ(0xf00cfffc, packed[ndx++]);
  // 2 in first group
  EXPECT_EQ(2U, packed[ndx++]);
  EXPECT_EQ(11U, packed[ndx++]); //flags
  EXPECT_EQ(4U, packed[ndx++]); // group offset delta
  EXPECT_EQ(11U, packed[ndx++]); // info

  // Initial relocation.
  EXPECT_EQ(10000U, packed[ndx++]); // addend delta
  // Two relocations, 4 byte offset deltas, 12 byte addend deltas.
  EXPECT_EQ(12U, packed[ndx++]); // addend delta

  // second group has only one reloc
  EXPECT_EQ(1U, packed[ndx++]); // count
  EXPECT_EQ(8U, packed[ndx++]); // flags

  EXPECT_EQ(4U, packed[ndx++]); // offset delta
  EXPECT_EQ(41U, packed[ndx++]); // info
  EXPECT_EQ(12U, packed[ndx++]); // addend delta

  // next - 3 relocs grouped by info
  EXPECT_EQ(3U, packed[ndx++]); // count
  EXPECT_EQ(11U, packed[ndx++]); // flags
  EXPECT_EQ(8U, packed[ndx++]); // group offset delta
  EXPECT_EQ(42U, packed[ndx++]); // info
  // Three relocations, 8 byte offset deltas, -24 byte addend deltas.
  EXPECT_EQ(static_cast<typename ELF::Addr>(-24), packed[ndx++]);
  EXPECT_EQ(static_cast<typename ELF::Addr>(-24), packed[ndx++]);
  EXPECT_EQ(static_cast<typename ELF::Addr>(-24), packed[ndx++]);

  // and last - 2 relocations without addend
  EXPECT_EQ(2U, packed[ndx++]);
  EXPECT_EQ(0U, packed[ndx++]); // flags
  // offset_deltas and r_infos for next 2 relocations
  EXPECT_EQ(0x2008U, packed[ndx++]); // offset delta
  EXPECT_EQ(1042U, packed[ndx++]); // r_info
  EXPECT_EQ(0x8U, packed[ndx++]); // offset delta
  EXPECT_EQ(3442U, packed[ndx++]); // r_info

  EXPECT_EQ(packed.size(), ndx);
}

TEST(Delta, Encode32) {
  encode<ELF32_traits>();
}

TEST(Delta, Encode64) {
  encode<ELF64_traits>();
}

template <typename ELF>
static void decode() {
  std::vector<typename ELF::Addr> packed;
  std::vector<typename ELF::Rela> relocations;

  RelocationDeltaCodec<ELF> codec;
  codec.Decode(packed, &relocations);

  EXPECT_EQ(0U, relocations.size());

  // Six pairs.
  packed.push_back(6U); // count
  packed.push_back(0xc0ddfffc); // base offset
  packed.push_back(3U); // group count
  packed.push_back(11U); // flags
  packed.push_back(4U); // offset delta
  packed.push_back(11U); // info
  // Initial relocation.
  packed.push_back(10000U);
  // Two relocations, 4 byte offset deltas, 12 byte addend deltas.
  packed.push_back(12U); // addend
  packed.push_back(12U); // addend

  // Three relocations, 8 byte offset deltas, -24 byte addend deltas.
  packed.push_back(1U); // group count
  packed.push_back(9U); // flags
  packed.push_back(11U); // info

  packed.push_back(8U);
  packed.push_back(static_cast<typename ELF::Addr>(-24));
  // next group with 2 relocs
  packed.push_back(2U); // group count
  packed.push_back(11U); // flags
  packed.push_back(8U); // offset
  packed.push_back(42U); // info

  packed.push_back(static_cast<typename ELF::Addr>(-24)); // addend
  packed.push_back(static_cast<typename ELF::Addr>(-24)); // addend

  relocations.clear();
  codec.Decode(packed, &relocations);

  EXPECT_EQ(6U, relocations.size());
  // Initial relocation.
  EXPECT_TRUE(CheckRelocation(0xc0de0000, 11U, 10000, relocations[0]));
  // Two relocations, 4 byte offset deltas, 12 byte addend deltas.
  EXPECT_TRUE(CheckRelocation(0xc0de0004, 11U, 10012, relocations[1]));
  EXPECT_TRUE(CheckRelocation(0xc0de0008, 11U, 10024, relocations[2]));
  // Three relocations, 8 byte offset deltas, -24 byte addend deltas.
  EXPECT_TRUE(CheckRelocation(0xc0de0010, 11U, 10000, relocations[3]));
  EXPECT_TRUE(CheckRelocation(0xc0de0018, 42U, 9976, relocations[4]));
  EXPECT_TRUE(CheckRelocation(0xc0de0020, 42U, 9952, relocations[5]));
}

TEST(Delta, Decode32) {
  decode<ELF32_traits>();
}

TEST(Delta, Decode64) {
  decode<ELF64_traits>();
}

// TODO (dimitry): add more tests (fix by 19 January 2038 03:14:07 UTC)
// TODO (dimtiry): 1. Incorrect packed array for decode
// TODO (dimtiry): 2. Try to catch situation where it is likely to get series of groups with size 1

}  // namespace relocation_packer