#include "fuzz_cmn.h"

std::string BytesToHex(const uint8_t* data, size_t size) {
  std::string result = "{";
  if (data && size) {
    StringAppendF(&result, "0x%02X", data[0]);
    for (auto i = 1; i < size; i++) {
      StringAppendF(&result, ", 0x%02X", data[i]);
    }
  }
  result += "}";

  return result;
}

std::string BytesToHex(const bytes_t& data) {
  return BytesToHex(&data[0], data.size());
}

static std::vector<bytes_t> UnpackPackets(const uint8_t* Data, size_t Size) {
  std::vector<bytes_t> result;
  while (Size > 0) {
    auto s = *Data++;
    Size--;

    if (s > Size) {
      s = Size;
    }

    if (s > 0) {
      result.push_back(bytes_t(Data, Data + s));
    }

    Size -= s;
    Data += s;
  }

  return result;
}

static size_t PackPackets(const std::vector<bytes_t>& Packets, uint8_t* Data,
                          size_t MaxSize) {
  size_t TotalSize = 0;

  for (auto it = Packets.cbegin(); MaxSize > 0 && it != Packets.cend(); ++it) {
    auto s = it->size();
    if (s == 0) {
      // skip empty packets
      continue;
    }

    if (s > MaxSize - 1) {
      s = MaxSize - 1;
    }
    *Data++ = (uint8_t)s;
    MaxSize--;

    memcpy(Data, it->data(), s);
    MaxSize -= s;
    Data += s;

    TotalSize += (s + 1);
  }

  return TotalSize;
}

extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* Data, size_t Size,
                                          size_t MaxSize, uint Seed) {
  const uint MAX_PACKET_SIZE = 255;
  auto Packets = UnpackPackets(Data, Size);
  auto odd = Seed % 100;
  if (odd < 10 || Packets.size() == 0) {
    // ~10% chance to insert a new packet
    auto len = (Seed >> 8) % MAX_PACKET_SIZE;
    if (Packets.size() > 0) {
      auto pos = (Seed >> 16) % Packets.size();
      Packets.insert(Packets.begin() + pos, bytes_t(len));
    } else {
      Packets.push_back(bytes_t(len));
    }
  } else if (odd < 20 && Packets.size() > 1) {
    // ~10% chance to drop a packet
    auto pos = (Seed >> 16) % Packets.size();
    Packets.erase(Packets.begin() + pos);
  } else if (Packets.size() > 0) {
    // ~80% chance to mutate a packet, maximium length 255
    auto pos = (Seed >> 16) % Packets.size();
    auto& p = Packets[pos];

    auto size = p.size();
    p.resize(MAX_PACKET_SIZE);
    size = LLVMFuzzerMutate(p.data(), size, MAX_PACKET_SIZE);
    p.resize(size);
  }

  Fuzz_FixPackets(Packets, Seed);

  Size = PackPackets(Packets, Data, MaxSize);
  FUZZLOG("Packet size:%zu, data=%s", Packets.size(),
          BytesToHex(Data, Size).c_str());
  return Size;
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) {
  android::base::SetDefaultTag(fuzzer_name);

  if (Size > 0) {
    auto Packets = UnpackPackets(Data, Size);
    Fuzz_RunPackets(Packets);
  }

  if (__gcov_flush) {
    __gcov_flush();
  }

  return 0;
}

extern "C" int LLVMFuzzerInitialize(int* /*argc*/, char*** /*argv*/) {
  android::base::SetMinimumLogSeverity(android::base::DEBUG);
  FUZZLOG("Debugging output enabled");

  return 0;
}
