/*
 * Copyright (C) 2019 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.
 */
#define FUZZ_LOG_TAG "hwbinder"

#include "hwbinder.h"
#include "util.h"

#include <android-base/logging.h>
#include <hwbinder/Parcel.h>

#include "../../Utils.h"

using ::android::status_t;
using ::android::HexString;

// TODO: support scatter-gather types

std::ostream& operator<<(std::ostream& os, const ::android::sp<::android::hardware::IBinder>& binder) {
    os << binder.get();
    return os;
}

#define PARCEL_READ_OPT_STATUS(T, FUN) \
    PARCEL_READ_NO_STATUS(T, FUN), PARCEL_READ_WITH_STATUS(T, FUN)

#define PARCEL_READ_NO_STATUS(T, FUN)                                            \
    [](const ::android::hardware::Parcel& p, FuzzedDataProvider& /*provider*/) { \
        FUZZ_LOG() << "about to read " #T " using " #FUN " with no status";      \
        T t = p.FUN();                                                           \
        FUZZ_LOG() << #T " value: " << t;                                        \
    }

#define PARCEL_READ_WITH_STATUS(T, FUN)                                          \
    [](const ::android::hardware::Parcel& p, FuzzedDataProvider& /*provider*/) { \
        FUZZ_LOG() << "about to read " #T " using " #FUN " with status";         \
        T t;                                                                     \
        status_t status = p.FUN(&t);                                             \
        FUZZ_LOG() << #T " status: " << status << " value: " << t;               \
    }

// clang-format off
std::vector<ParcelRead<::android::hardware::Parcel>> HWBINDER_PARCEL_READ_FUNCTIONS {
    PARCEL_READ_NO_STATUS(size_t, dataSize),
    PARCEL_READ_NO_STATUS(size_t, dataAvail),
    PARCEL_READ_NO_STATUS(size_t, dataPosition),
    PARCEL_READ_NO_STATUS(size_t, dataCapacity),
    [] (const ::android::hardware::Parcel& p, FuzzedDataProvider& provider) {
        // aborts on larger values
        size_t pos = provider.ConsumeIntegralInRange<size_t>(0, INT32_MAX);
        FUZZ_LOG() << "about to setDataPosition: " << pos;
        p.setDataPosition(pos);
        FUZZ_LOG() << "setDataPosition done";
    },
    [] (const ::android::hardware::Parcel& p, FuzzedDataProvider& provider) {
        FUZZ_LOG() << "about to enforceInterface";
        bool okay = p.enforceInterface(provider.ConsumeRandomLengthString().c_str());
        FUZZ_LOG() << "enforceInterface status: " << okay;
    },
    PARCEL_READ_NO_STATUS(size_t, objectsCount),
    [] (const ::android::hardware::Parcel& p, FuzzedDataProvider& provider) {
        // Read at least a bit. Unbounded allocation would OOM.
        size_t length = provider.ConsumeIntegralInRange<size_t>(0, 1024);
        FUZZ_LOG() << "about to read";
        std::vector<uint8_t> data (length);
        status_t status = p.read(data.data(), length);
        FUZZ_LOG() << "read status: " << status << " data: " << HexString(data.data(), data.size());
    },
    [] (const ::android::hardware::Parcel& p, FuzzedDataProvider& provider) {
        size_t length = provider.ConsumeIntegral<size_t>();
        FUZZ_LOG() << "about to read";
        const void* inplace = p.readInplace(length);
        FUZZ_LOG() << "read status: " << (inplace ? HexString(inplace, length) : "null");
    },
    PARCEL_READ_WITH_STATUS(int8_t, readInt8),
    PARCEL_READ_WITH_STATUS(uint8_t, readUint8),
    PARCEL_READ_WITH_STATUS(int16_t, readInt16),
    PARCEL_READ_WITH_STATUS(uint16_t, readUint16),
    PARCEL_READ_OPT_STATUS(int32_t, readInt32),
    PARCEL_READ_OPT_STATUS(uint32_t, readUint32),
    PARCEL_READ_OPT_STATUS(int64_t, readInt64),
    PARCEL_READ_OPT_STATUS(uint64_t, readUint64),
    PARCEL_READ_OPT_STATUS(float, readFloat),
    PARCEL_READ_OPT_STATUS(double, readDouble),
    PARCEL_READ_OPT_STATUS(bool, readBool),
    [] (const ::android::hardware::Parcel& p, FuzzedDataProvider& /*provider*/) {
        FUZZ_LOG() << "about to readCString";
        const char* str = p.readCString();
        FUZZ_LOG() << "readCString " << (str ? str : "<null>");
    },
    PARCEL_READ_OPT_STATUS(::android::String16, readString16),
    PARCEL_READ_WITH_STATUS(std::unique_ptr<::android::String16>, readString16),
    [] (const ::android::hardware::Parcel& p, FuzzedDataProvider& /*provider*/) {
        FUZZ_LOG() << "about to readString16Inplace";
        size_t outSize = 0;
        const char16_t* str = p.readString16Inplace(&outSize);
        FUZZ_LOG() << "readString16Inplace: " << HexString(str, sizeof(char16_t) * outSize);
    },
    PARCEL_READ_OPT_STATUS(::android::sp<::android::hardware::IBinder>, readStrongBinder),
    PARCEL_READ_WITH_STATUS(::android::sp<::android::hardware::IBinder>, readNullableStrongBinder),
    [] (const ::android::hardware::Parcel& p, FuzzedDataProvider& provider) {
        size_t size = provider.ConsumeIntegral<size_t>();
        FUZZ_LOG() << "about to readBuffer";
        size_t handle = 0;
        const void* data = nullptr;
        status_t status = p.readBuffer(size, &handle, &data);
        FUZZ_LOG() << "readBuffer status: " << status << " handle: " << handle << " data: " << data;

        // should be null since we don't create any IPC objects
        CHECK(data == nullptr) << data;
    },
    [] (const ::android::hardware::Parcel& p, FuzzedDataProvider& provider) {
        size_t size = provider.ConsumeIntegral<size_t>();
        FUZZ_LOG() << "about to readNullableBuffer";
        size_t handle = 0;
        const void* data = nullptr;
        status_t status = p.readNullableBuffer(size, &handle, &data);
        FUZZ_LOG() << "readNullableBuffer status: " << status << " handle: " << handle << " data: " << data;

        // should be null since we don't create any IPC objects
        CHECK(data == nullptr) << data;
    },
    [] (const ::android::hardware::Parcel& p, FuzzedDataProvider& provider) {
        size_t size = provider.ConsumeIntegral<size_t>();
        FUZZ_LOG() << "about to readEmbeddedBuffer";
        size_t handle = 0;
        size_t parent_buffer_handle = 0;
        size_t parent_offset = 3;
        const void* data = nullptr;
        status_t status = p.readEmbeddedBuffer(size, &handle, parent_buffer_handle, parent_offset, &data);
        FUZZ_LOG() << "readEmbeddedBuffer status: " << status << " handle: " << handle << " data: " << data;

        // should be null since we don't create any IPC objects
        CHECK(data == nullptr) << data;
    },
    [] (const ::android::hardware::Parcel& p, FuzzedDataProvider& provider) {
        size_t size = provider.ConsumeIntegral<size_t>();
        FUZZ_LOG() << "about to readNullableEmbeddedBuffer";
        size_t handle = 0;
        size_t parent_buffer_handle = 0;
        size_t parent_offset = 3;
        const void* data = nullptr;
        status_t status = p.readNullableEmbeddedBuffer(size, &handle, parent_buffer_handle, parent_offset, &data);
        FUZZ_LOG() << "readNullableEmbeddedBuffer status: " << status << " handle: " << handle << " data: " << data;

        // should be null since we don't create any IPC objects
        CHECK(data == nullptr) << data;
    },
    [] (const ::android::hardware::Parcel& p, FuzzedDataProvider& /*provider*/) {
        FUZZ_LOG() << "about to readNativeHandleNoDup";
        const native_handle_t* handle = nullptr;
        status_t status = p.readNativeHandleNoDup(&handle);
        FUZZ_LOG() << "readNativeHandleNoDup status: " << status << " handle: " << handle;

        // should be null since we don't create any IPC objects
        CHECK(handle == nullptr) << handle;
        CHECK(status != ::android::OK);
    },
};
// clang-format on
