/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * 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 <stddef.h>
#include <stdint.h>
#include <string.h>

#include "perfetto/ext/base/hash.h"

#include "perfetto/protozero/packed_repeated_fields.h"
#include "src/protozero/filtering/filter_bytecode_parser.h"

namespace protozero {
namespace {

// This function gives a little help to the fuzzer. The bytecode is really a
// sequence of varint-encoded uint32 words, with a FNV1a checksum at the end.
// It's very unlikely that the fuzzer on its own can work out the checksum, so
// most fuzzer inputs are doomed to fail the checksum verification.
// This takes the fuzzer input and builds a more plausible bytecode.
void LoadBytecodeWithChecksum(FilterBytecodeParser* parser,
                              const uint8_t* data,
                              size_t size) {
  protozero::PackedVarInt words;
  perfetto::base::Hash hasher;
  for (size_t i = 0; i < size; i += sizeof(uint32_t)) {
    uint32_t word = 0;
    memcpy(&word, data, sizeof(uint32_t));
    words.Append(word);
    hasher.Update(word);
  }
  words.Append(static_cast<uint32_t>(hasher.digest()));
  parser->Load(words.data(), words.size());
}

int FuzzBytecodeParser(const uint8_t* data, size_t size) {
  FilterBytecodeParser parser;
  parser.set_suppress_logs_for_fuzzer(true);

  if (size > 4 && data[0] < 192) {
    // 75% of the times use the LoadBytecodeWithChecksum() which helps the
    // fuzzer passing the checksum verification.
    LoadBytecodeWithChecksum(&parser, data + 1, size - 1);
  } else {
    // In the remaining 25%, pass completely arbitrary inputs.
    parser.Load(data, size);
  }

  // Smoke testing with known problematic values
  for (uint32_t msg_index = 0; msg_index < 3; ++msg_index) {
    parser.Query(msg_index, 0);
    parser.Query(msg_index, 1);
    parser.Query(msg_index, 127);
    parser.Query(msg_index, 128);
    parser.Query(msg_index, 129);
    parser.Query(msg_index, 65536);
    parser.Query(msg_index, 65536);
    parser.Query(msg_index, 1u << 28);
    parser.Query(msg_index, 1u << 31);
  }

  // Query using the random data at the end of the random buffer.
  if (size > 8) {
    uint32_t msg_index = 0;
    uint32_t field_id = 0;
    memcpy(&msg_index, &data[size - 8], 4);
    memcpy(&field_id, &data[size - 4], 4);
    parser.Query(msg_index, field_id);
  }

  return 0;
}

}  // namespace
}  // namespace protozero

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
  return protozero::FuzzBytecodeParser(data, size);
}
