/*
 * Copyright 2016 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 "ProtoFuzzerMutator.h"
#include <iostream>

using std::cerr;
using std::cout;
using std::endl;
using std::make_unique;
using std::unordered_map;
using namespace std::placeholders;

namespace android {
namespace vts {
namespace fuzzer {

ProtoFuzzerMutator::ProtoFuzzerMutator(
    Random &rand, unordered_map<string, TypeSpec> predefined_types,
    ProtoFuzzerMutatorConfig mutator_config)
    : rand_(rand),
      predefined_types_(predefined_types),
      mutator_config_(mutator_config) {
  // Default function used for mutation/random generation. Used for types for
  // which the notion of mutation/random generation is not defined, e.g.
  // TYPE_HANDLE, TYPE_HIDL_CALLBACK.
  VarTransformFn default_transform =
      [](const VariableSpecificationMessage &var_spec) {
        return VariableSpecificationMessage{var_spec};
      };

  // Initialize random_gen_fns_ and mutate_fns_ tables.
  random_gen_fns_[TYPE_ARRAY] =
      std::bind(&ProtoFuzzerMutator::ArrayRandomGen, this, _1);
  mutate_fns_[TYPE_ARRAY] =
      std::bind(&ProtoFuzzerMutator::ArrayMutate, this, _1);

  random_gen_fns_[TYPE_ENUM] =
      std::bind(&ProtoFuzzerMutator::EnumRandomGen, this, _1);
  mutate_fns_[TYPE_ENUM] = std::bind(&ProtoFuzzerMutator::EnumMutate, this, _1);

  random_gen_fns_[TYPE_HANDLE] = default_transform;
  mutate_fns_[TYPE_HANDLE] = default_transform;

  random_gen_fns_[TYPE_HIDL_CALLBACK] = default_transform;
  mutate_fns_[TYPE_HIDL_CALLBACK] = default_transform;

  random_gen_fns_[TYPE_HIDL_INTERFACE] = default_transform;
  mutate_fns_[TYPE_HIDL_INTERFACE] = default_transform;

  random_gen_fns_[TYPE_HIDL_MEMORY] = default_transform;
  mutate_fns_[TYPE_HIDL_MEMORY] = default_transform;

  // Interpret masks as enums.
  random_gen_fns_[TYPE_MASK] =
      std::bind(&ProtoFuzzerMutator::EnumRandomGen, this, _1);
  mutate_fns_[TYPE_MASK] = std::bind(&ProtoFuzzerMutator::EnumMutate, this, _1);

  random_gen_fns_[TYPE_POINTER] = default_transform;
  mutate_fns_[TYPE_POINTER] = default_transform;

  random_gen_fns_[TYPE_SCALAR] =
      std::bind(&ProtoFuzzerMutator::ScalarRandomGen, this, _1);
  mutate_fns_[TYPE_SCALAR] =
      std::bind(&ProtoFuzzerMutator::ScalarMutate, this, _1);

  random_gen_fns_[TYPE_STRING] =
      std::bind(&ProtoFuzzerMutator::StringRandomGen, this, _1);
  mutate_fns_[TYPE_STRING] =
      std::bind(&ProtoFuzzerMutator::StringMutate, this, _1);

  random_gen_fns_[TYPE_STRUCT] =
      std::bind(&ProtoFuzzerMutator::StructRandomGen, this, _1);
  mutate_fns_[TYPE_STRUCT] =
      std::bind(&ProtoFuzzerMutator::StructMutate, this, _1);

  random_gen_fns_[TYPE_UNION] =
      std::bind(&ProtoFuzzerMutator::UnionRandomGen, this, _1);
  mutate_fns_[TYPE_UNION] =
      std::bind(&ProtoFuzzerMutator::UnionMutate, this, _1);

  random_gen_fns_[TYPE_VECTOR] =
      std::bind(&ProtoFuzzerMutator::VectorRandomGen, this, _1);
  mutate_fns_[TYPE_VECTOR] =
      std::bind(&ProtoFuzzerMutator::VectorMutate, this, _1);
}

// TODO(trong): add a mutator config option which controls how an interface is
// selected.
const CompSpec *ProtoFuzzerMutator::RandomSelectIface(const IfaceDescTbl &tbl) {
  size_t rand_idx = rand_(tbl.size());
  auto it = tbl.begin();
  std::advance(it, rand_idx);
  return it->second.comp_spec_;
}

ExecSpec ProtoFuzzerMutator::RandomGen(const IfaceDescTbl &tbl,
                                       size_t num_calls) {
  cerr << "Generating a random execution." << endl;
  ExecSpec result{};
  for (size_t i = 0; i < num_calls; ++i) {
    const CompSpec *comp_spec = RandomSelectIface(tbl);
    string iface_name = comp_spec->component_name();
    const IfaceSpec &iface_spec = comp_spec->interface();

    // Generate a random interface function call.
    FuncCall rand_call{};
    rand_call.set_hidl_interface_name(iface_name);
    size_t num_apis = iface_spec.api_size();
    size_t rand_api_idx = rand_(num_apis);
    FuncSpec rand_api = RandomGen(iface_spec.api(rand_api_idx));
    *rand_call.mutable_api() = rand_api;
    *result.add_function_call() = rand_call;
  }
  return result;
}

void ProtoFuzzerMutator::Mutate(const IfaceDescTbl &tbl, ExecSpec *exec_spec) {
  // Mutate a randomly chosen function call with probability
  // odds_for/(odds_for + odds_against).
  uint64_t odds_for = mutator_config_.func_mutated_.first;
  uint64_t odds_against = mutator_config_.func_mutated_.second;
  uint64_t rand_num = rand_(odds_for + odds_against);

  if (rand_num < odds_for) {
    // Mutate a random function in execution.
    size_t idx = rand_(exec_spec->function_call_size());
    const FuncSpec &rand_api = exec_spec->function_call(idx).api();
    *exec_spec->mutable_function_call(idx)->mutable_api() = Mutate(rand_api);
  } else {
    // Generate a random function call in place of randomly chosen function in
    // execution.
    const CompSpec *comp_spec = RandomSelectIface(tbl);
    string iface_name = comp_spec->component_name();
    const IfaceSpec &iface_spec = comp_spec->interface();

    size_t func_idx = rand_(exec_spec->function_call_size());
    size_t blueprint_idx = rand_(iface_spec.api_size());
    FuncCall *func_call = exec_spec->mutable_function_call(func_idx);
    func_call->set_hidl_interface_name(iface_name);
    *func_call->mutable_api() = RandomGen(iface_spec.api(blueprint_idx));
  }
}

FuncSpec ProtoFuzzerMutator::RandomGen(const FuncSpec &func_spec) {
  FuncSpec result{func_spec};
  // We'll repopulate arg field.
  result.clear_arg();
  result.clear_return_type_hidl();
  for (const auto &var_spec : func_spec.arg()) {
    VarInstance rand_var_spec = RandomGen(var_spec);
    auto *new_var = result.add_arg();
    new_var->Swap(&rand_var_spec);
  }
  return result;
}

FuncSpec ProtoFuzzerMutator::Mutate(const FuncSpec &func_spec) {
  FuncSpec result{func_spec};
  size_t num_args = result.arg_size();
  if (num_args > 0) {
    size_t rand_arg_idx = rand_(num_args);
    VarInstance rand_arg = Mutate(result.arg(rand_arg_idx));
    result.mutable_arg(rand_arg_idx)->Swap(&rand_arg);
  }
  return result;
}

static VariableSpecificationMessage Transform(
    const VariableSpecificationMessage &var_spec,
    unordered_map<VariableType, VarTransformFn> &transform_fns) {
  auto type = var_spec.type();
  auto transform_fn = transform_fns.find(type);
  if (transform_fn == transform_fns.end()) {
    cerr << "Transformation function not found for type: " << type << endl;
    std::abort();
  }
  return transform_fn->second(var_spec);
}

VarInstance ProtoFuzzerMutator::RandomGen(const VarSpec &var_spec) {
  return Transform(var_spec, random_gen_fns_);
}

VarInstance ProtoFuzzerMutator::Mutate(const VarInstance &var_instance) {
  return Transform(var_instance, mutate_fns_);
}

const TypeSpec &ProtoFuzzerMutator::FindPredefinedType(string name) {
  auto type_spec = predefined_types_.find(name);
  if (type_spec == predefined_types_.end()) {
    cerr << "Predefined type not found: " << name << endl;
    std::abort();
  }
  return type_spec->second;
}

}  // namespace fuzzer
}  // namespace vts
}  // namespace android