// Copyright 2019 The Chromium OS 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 <cmath>
#include <cstddef>
#include <cstdint>
#include <map>
#include <string>
#include <utility>
#include <vector>

#include <base/logging.h>
#include <base/strings/string_util.h>
#include <brillo/dbus/data_serialization.h>
#include <dbus/string_util.h>
#include <fuzzer/FuzzedDataProvider.h>

namespace {
constexpr int kRandomMaxContainerSize = 8;
constexpr int kRandomMaxDataLength = 128;

typedef enum DataType {
  kUint8 = 0,
  kUint16,
  kUint32,
  kUint64,
  kInt16,
  kInt32,
  kInt64,
  kBool,
  kDouble,
  kString,
  kObjectPath,
  // A couple vector types.
  kVectorInt16,
  kVectorString,
  // A couple pair types.
  kPairBoolInt64,
  kPairUint32String,
  // A couple tuple types.
  kTupleUint16StringBool,
  kTupleDoubleInt32ObjectPath,
  // A couple map types.
  kMapInt32String,
  kMapDoubleBool,
  kMaxValue = kMapDoubleBool,
} DataType;

template <typename T>
void AppendValue(dbus::MessageWriter* writer, bool variant, const T& value) {
  if (variant)
    brillo::dbus_utils::AppendValueToWriterAsVariant(writer, value);
  else
    brillo::dbus_utils::AppendValueToWriter(writer, value);
}

template <typename T>
void GenerateIntAndAppendValue(FuzzedDataProvider* data_provider,
                               dbus::MessageWriter* writer,
                               bool variant) {
  AppendValue(writer, variant, data_provider->ConsumeIntegral<T>());
}

template <typename T>
void PopValue(dbus::MessageReader* reader, bool variant, T* value) {
  if (variant)
    brillo::dbus_utils::PopVariantValueFromReader(reader, value);
  else
    brillo::dbus_utils::PopValueFromReader(reader, value);
}

std::string GenerateValidUTF8(FuzzedDataProvider* data_provider) {
  // >= 0x80
  // Generates a random string and returns it if it is valid UTF8, if it is not
  // then it will strip it down to all the 7-bit ASCII chars and just return
  // that string.
  std::string str =
      data_provider->ConsumeRandomLengthString(kRandomMaxDataLength);
  if (base::IsStringUTF8(str))
    return str;
  for (auto it = str.begin(); it != str.end(); it++) {
    if (static_cast<uint8_t>(*it) >= 0x80) {
      // Might be invalid, remove it.
      it = str.erase(it);
      it--;
    }
  }
  return str;
}

}  // namespace

class Environment {
 public:
  Environment() {
    // Disable logging.
    logging::SetMinLogLevel(logging::LOG_FATAL);
  }
};

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
  static Environment env;
  FuzzedDataProvider data_provider(data, size);
  // Consume a random fraction of our data writing random things to a D-Bus
  // message, and then consume the remaining data reading randomly from that
  // same D-Bus message.  Given the templated nature of these functions and that
  // they support essentially an infinite amount of types, we are constraining
  // this to a fixed set of types defined above.
  std::unique_ptr<dbus::Response> message = dbus::Response::CreateEmpty();
  dbus::MessageWriter writer(message.get());

  int bytes_left_for_read =
      static_cast<int>(data_provider.ConsumeProbability<float>() * size);
  while (data_provider.remaining_bytes() > bytes_left_for_read) {
    DataType curr_type = data_provider.ConsumeEnum<DataType>();
    bool variant = data_provider.ConsumeBool();
    switch (curr_type) {
      case kUint8:
        GenerateIntAndAppendValue<uint8_t>(&data_provider, &writer, variant);
        break;
      case kUint16:
        GenerateIntAndAppendValue<uint16_t>(&data_provider, &writer, variant);
        break;
      case kUint32:
        GenerateIntAndAppendValue<uint32_t>(&data_provider, &writer, variant);
        break;
      case kUint64:
        GenerateIntAndAppendValue<uint64_t>(&data_provider, &writer, variant);
        break;
      case kInt16:
        GenerateIntAndAppendValue<int16_t>(&data_provider, &writer, variant);
        break;
      case kInt32:
        GenerateIntAndAppendValue<int32_t>(&data_provider, &writer, variant);
        break;
      case kInt64:
        GenerateIntAndAppendValue<int64_t>(&data_provider, &writer, variant);
        break;
      case kBool:
        AppendValue(&writer, variant, data_provider.ConsumeBool());
        break;
      case kDouble:
        AppendValue(&writer, variant,
                    data_provider.ConsumeProbability<double>());
        break;
      case kString:
        AppendValue(&writer, variant, GenerateValidUTF8(&data_provider));
        break;
      case kObjectPath: {
        std::string object_path =
            data_provider.ConsumeRandomLengthString(kRandomMaxDataLength);
        // If this isn't valid we'll hit a CHECK failure.
        if (dbus::IsValidObjectPath(object_path))
          AppendValue(&writer, variant, dbus::ObjectPath(object_path));
        break;
      }
      case kVectorInt16: {
        int vec_size = data_provider.ConsumeIntegralInRange<int>(
            0, kRandomMaxContainerSize);
        std::vector<int16_t> vec(vec_size);
        for (int i = 0; i < vec_size; i++)
          vec[i] = data_provider.ConsumeIntegral<int16_t>();
        AppendValue(&writer, variant, vec);
        break;
      }
      case kVectorString: {
        int vec_size = data_provider.ConsumeIntegralInRange<int>(
            0, kRandomMaxContainerSize);
        std::vector<std::string> vec(vec_size);
        for (int i = 0; i < vec_size; i++)
          vec[i] = GenerateValidUTF8(&data_provider);
        AppendValue(&writer, variant, vec);
        break;
      }
      case kPairBoolInt64:
        AppendValue(
            &writer, variant,
            std::pair<bool, int64_t>{data_provider.ConsumeBool(),
                                     data_provider.ConsumeIntegral<int64_t>()});
        break;
      case kPairUint32String:
        AppendValue(&writer, variant,
                    std::pair<uint32_t, std::string>{
                        data_provider.ConsumeIntegral<uint32_t>(),
                        GenerateValidUTF8(&data_provider)});
        break;
      case kTupleUint16StringBool:
        AppendValue(&writer, variant,
                    std::tuple<uint32_t, std::string, bool>{
                        data_provider.ConsumeIntegral<uint32_t>(),
                        GenerateValidUTF8(&data_provider),
                        data_provider.ConsumeBool()});
        break;
      case kTupleDoubleInt32ObjectPath: {
        std::string object_path =
            data_provider.ConsumeRandomLengthString(kRandomMaxDataLength);
        // If this isn't valid we'll hit a CHECK failure.
        if (dbus::IsValidObjectPath(object_path)) {
          AppendValue(&writer, variant,
                      std::tuple<double, int32_t, dbus::ObjectPath>{
                          data_provider.ConsumeProbability<double>(),
                          data_provider.ConsumeIntegral<int32_t>(),
                          dbus::ObjectPath(object_path)});
        }
        break;
      }
      case kMapInt32String: {
        int map_size = data_provider.ConsumeIntegralInRange<int>(
            0, kRandomMaxContainerSize);
        std::map<int32_t, std::string> map;
        for (int i = 0; i < map_size; i++)
          map[data_provider.ConsumeIntegral<int32_t>()] =
              GenerateValidUTF8(&data_provider);
        AppendValue(&writer, variant, map);
        break;
      }
      case kMapDoubleBool: {
        int map_size = data_provider.ConsumeIntegralInRange<int>(
            0, kRandomMaxContainerSize);
        std::map<double, bool> map;
        for (int i = 0; i < map_size; i++)
          map[data_provider.ConsumeProbability<double>()] =
              data_provider.ConsumeBool();
        AppendValue(&writer, variant, map);
        break;
      }
    }
  }

  dbus::MessageReader reader(message.get());
  while (data_provider.remaining_bytes()) {
    DataType curr_type = data_provider.ConsumeEnum<DataType>();
    bool variant = data_provider.ConsumeBool();
    switch (curr_type) {
      case kUint8: {
        uint8_t value;
        PopValue(&reader, variant, &value);
        break;
      }
      case kUint16: {
        uint16_t value;
        PopValue(&reader, variant, &value);
        break;
      }
      case kUint32: {
        uint32_t value;
        PopValue(&reader, variant, &value);
        break;
      }
      case kUint64: {
        uint64_t value;
        PopValue(&reader, variant, &value);
        break;
      }
      case kInt16: {
        int16_t value;
        PopValue(&reader, variant, &value);
        break;
      }
      case kInt32: {
        int32_t value;
        PopValue(&reader, variant, &value);
        break;
      }
      case kInt64: {
        int64_t value;
        PopValue(&reader, variant, &value);
        break;
      }
      case kBool: {
        bool value;
        PopValue(&reader, variant, &value);
        break;
      }
      case kDouble: {
        double value;
        PopValue(&reader, variant, &value);
        break;
      }
      case kString: {
        std::string value;
        PopValue(&reader, variant, &value);
        break;
      }
      case kObjectPath: {
        dbus::ObjectPath value;
        PopValue(&reader, variant, &value);
        break;
      }
      case kVectorInt16: {
        std::vector<int16_t> value;
        PopValue(&reader, variant, &value);
        break;
      }
      case kVectorString: {
        std::vector<std::string> value;
        PopValue(&reader, variant, &value);
        break;
      }
      case kPairBoolInt64: {
        std::pair<bool, int64_t> value;
        PopValue(&reader, variant, &value);
        break;
      }
      case kPairUint32String: {
        std::pair<uint32_t, std::string> value;
        PopValue(&reader, variant, &value);
        break;
      }
      case kTupleUint16StringBool: {
        std::tuple<uint16_t, std::string, bool> value;
        break;
      }
      case kTupleDoubleInt32ObjectPath: {
        std::tuple<double, int32_t, dbus::ObjectPath> value;
        PopValue(&reader, variant, &value);
        break;
      }
      case kMapInt32String: {
        std::map<int32_t, std::string> value;
        PopValue(&reader, variant, &value);
        break;
      }
      case kMapDoubleBool: {
        std::map<double, bool> value;
        PopValue(&reader, variant, &value);
        break;
      }
    }
  }

  return 0;
}
