/*
 * Copyright (C) 2018 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 <android/binder_parcel.h>
#include <android/binder_parcel_platform.h>
#include "parcel_internal.h"

#include "ibinder_internal.h"
#include "status_internal.h"

#include <limits>

#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <binder/Parcel.h>
#include <binder/ParcelFileDescriptor.h>
#include <utils/Unicode.h>

using ::android::IBinder;
using ::android::Parcel;
using ::android::sp;
using ::android::status_t;
using ::android::base::unique_fd;
using ::android::os::ParcelFileDescriptor;

template <typename T>
using ContiguousArrayAllocator = bool (*)(void* arrayData, int32_t length, T** outBuffer);

template <typename T>
using ArrayAllocator = bool (*)(void* arrayData, int32_t length);
template <typename T>
using ArrayGetter = T (*)(const void* arrayData, size_t index);
template <typename T>
using ArraySetter = void (*)(void* arrayData, size_t index, T value);

static binder_status_t WriteAndValidateArraySize(AParcel* parcel, bool isNullArray,
                                                 int32_t length) {
    // only -1 can be used to represent a null array
    if (length < -1) return STATUS_BAD_VALUE;

    if (!isNullArray && length < 0) {
        LOG(ERROR) << __func__ << ": non-null array but length is " << length;
        return STATUS_BAD_VALUE;
    }
    if (isNullArray && length > 0) {
        LOG(ERROR) << __func__ << ": null buffer cannot be for size " << length << " array.";
        return STATUS_BAD_VALUE;
    }

    Parcel* rawParcel = parcel->get();

    status_t status = rawParcel->writeInt32(length);
    if (status != STATUS_OK) return PruneStatusT(status);

    return STATUS_OK;
}

static binder_status_t ReadAndValidateArraySize(const AParcel* parcel, int32_t* length) {
    if (status_t status = parcel->get()->readInt32(length); status != STATUS_OK) {
        return PruneStatusT(status);
    }

    if (*length < -1) return STATUS_BAD_VALUE;  // libbinder_ndk reserves these
    if (*length <= 0) return STATUS_OK;         // null
    if (static_cast<size_t>(*length) > parcel->get()->dataAvail()) return STATUS_NO_MEMORY;

    return STATUS_OK;
}

template <typename T>
binder_status_t WriteArray(AParcel* parcel, const T* array, int32_t length) {
    binder_status_t status = WriteAndValidateArraySize(parcel, array == nullptr, length);
    if (status != STATUS_OK) return status;
    if (length <= 0) return STATUS_OK;

    int32_t size = 0;
    if (__builtin_smul_overflow(sizeof(T), length, &size)) return STATUS_NO_MEMORY;

    void* const data = parcel->get()->writeInplace(size);
    if (data == nullptr) return STATUS_NO_MEMORY;

    memcpy(data, array, size);

    return STATUS_OK;
}

// Each element in a char16_t array is converted to an int32_t (not packed).
template <>
binder_status_t WriteArray<char16_t>(AParcel* parcel, const char16_t* array, int32_t length) {
    binder_status_t status = WriteAndValidateArraySize(parcel, array == nullptr, length);
    if (status != STATUS_OK) return status;
    if (length <= 0) return STATUS_OK;

    int32_t size = 0;
    if (__builtin_smul_overflow(sizeof(char16_t), length, &size)) return STATUS_NO_MEMORY;

    Parcel* rawParcel = parcel->get();

    for (int32_t i = 0; i < length; i++) {
        status = rawParcel->writeChar(array[i]);

        if (status != STATUS_OK) return PruneStatusT(status);
    }

    return STATUS_OK;
}

template <typename T>
binder_status_t ReadArray(const AParcel* parcel, void* arrayData,
                          ContiguousArrayAllocator<T> allocator) {
    const Parcel* rawParcel = parcel->get();

    int32_t length;
    if (binder_status_t status = ReadAndValidateArraySize(parcel, &length); status != STATUS_OK) {
        return status;
    }

    T* array;
    if (!allocator(arrayData, length, &array)) return STATUS_NO_MEMORY;

    if (length <= 0) return STATUS_OK;
    if (array == nullptr) return STATUS_NO_MEMORY;

    int32_t size = 0;
    if (__builtin_smul_overflow(sizeof(T), length, &size)) return STATUS_NO_MEMORY;

    const void* data = rawParcel->readInplace(size);
    if (data == nullptr) return STATUS_NO_MEMORY;

    memcpy(array, data, size);

    return STATUS_OK;
}

// Each element in a char16_t array is converted to an int32_t (not packed)
template <>
binder_status_t ReadArray<char16_t>(const AParcel* parcel, void* arrayData,
                                    ContiguousArrayAllocator<char16_t> allocator) {
    const Parcel* rawParcel = parcel->get();

    int32_t length;
    if (binder_status_t status = ReadAndValidateArraySize(parcel, &length); status != STATUS_OK) {
        return status;
    }

    char16_t* array;
    if (!allocator(arrayData, length, &array)) return STATUS_NO_MEMORY;

    if (length <= 0) return STATUS_OK;
    if (array == nullptr) return STATUS_NO_MEMORY;

    int32_t size = 0;
    if (__builtin_smul_overflow(sizeof(char16_t), length, &size)) return STATUS_NO_MEMORY;

    for (int32_t i = 0; i < length; i++) {
        status_t status = rawParcel->readChar(array + i);

        if (status != STATUS_OK) return PruneStatusT(status);
    }

    return STATUS_OK;
}

template <typename T>
binder_status_t WriteArray(AParcel* parcel, const void* arrayData, int32_t length,
                           ArrayGetter<T> getter, status_t (Parcel::*write)(T)) {
    // we have no clue if arrayData represents a null object or not, we can only infer from length
    bool arrayIsNull = length < 0;
    binder_status_t status = WriteAndValidateArraySize(parcel, arrayIsNull, length);
    if (status != STATUS_OK) return status;
    if (length <= 0) return STATUS_OK;

    Parcel* rawParcel = parcel->get();

    for (int32_t i = 0; i < length; i++) {
        status = (rawParcel->*write)(getter(arrayData, i));

        if (status != STATUS_OK) return PruneStatusT(status);
    }

    return STATUS_OK;
}

template <typename T>
binder_status_t ReadArray(const AParcel* parcel, void* arrayData, ArrayAllocator<T> allocator,
                          ArraySetter<T> setter, status_t (Parcel::*read)(T*) const) {
    const Parcel* rawParcel = parcel->get();

    int32_t length;
    if (binder_status_t status = ReadAndValidateArraySize(parcel, &length); status != STATUS_OK) {
        return status;
    }

    if (!allocator(arrayData, length)) return STATUS_NO_MEMORY;

    if (length <= 0) return STATUS_OK;

    for (int32_t i = 0; i < length; i++) {
        T readTarget;
        status_t status = (rawParcel->*read)(&readTarget);
        if (status != STATUS_OK) return PruneStatusT(status);

        setter(arrayData, i, readTarget);
    }

    return STATUS_OK;
}

void AParcel_delete(AParcel* parcel) {
    delete parcel;
}

binder_status_t AParcel_setDataPosition(const AParcel* parcel, int32_t position) {
    if (position < 0) {
        return STATUS_BAD_VALUE;
    }

    parcel->get()->setDataPosition(position);
    return STATUS_OK;
}

int32_t AParcel_getDataPosition(const AParcel* parcel) {
    return parcel->get()->dataPosition();
}

void AParcel_markSensitive(const AParcel* parcel) {
    return parcel->get()->markSensitive();
}

binder_status_t AParcel_writeStrongBinder(AParcel* parcel, AIBinder* binder) {
    sp<IBinder> writeBinder = binder != nullptr ? binder->getBinder() : nullptr;
    return parcel->get()->writeStrongBinder(writeBinder);
}
binder_status_t AParcel_readStrongBinder(const AParcel* parcel, AIBinder** binder) {
    sp<IBinder> readBinder = nullptr;
    status_t status = parcel->get()->readNullableStrongBinder(&readBinder);
    if (status != STATUS_OK) {
        return PruneStatusT(status);
    }
    sp<AIBinder> ret = ABpBinder::lookupOrCreateFromBinder(readBinder);
    AIBinder_incStrong(ret.get());
    *binder = ret.get();
    return PruneStatusT(status);
}

binder_status_t AParcel_writeParcelFileDescriptor(AParcel* parcel, int fd) {
    if (fd < 0) {
        if (fd != -1) {
            return STATUS_UNKNOWN_ERROR;
        }
        return PruneStatusT(parcel->get()->writeInt32(0));  // null
    }
    status_t status = parcel->get()->writeInt32(1);  // not-null
    if (status != STATUS_OK) return PruneStatusT(status);

    status = parcel->get()->writeDupParcelFileDescriptor(fd);
    return PruneStatusT(status);
}

binder_status_t AParcel_readParcelFileDescriptor(const AParcel* parcel, int* fd) {
    std::optional<ParcelFileDescriptor> parcelFd;

    status_t status = parcel->get()->readParcelable(&parcelFd);
    if (status != STATUS_OK) return PruneStatusT(status);

    if (parcelFd) {
        *fd = parcelFd->release().release();
    } else {
        *fd = -1;
    }

    return STATUS_OK;
}

binder_status_t AParcel_writeStatusHeader(AParcel* parcel, const AStatus* status) {
    return PruneStatusT(status->get().writeToParcel(parcel->get()));
}
binder_status_t AParcel_readStatusHeader(const AParcel* parcel, AStatus** status) {
    ::android::binder::Status bstatus;
    binder_status_t ret = PruneStatusT(bstatus.readFromParcel(*parcel->get()));
    if (ret == STATUS_OK) {
        *status = new AStatus(std::move(bstatus));
    }
    return PruneStatusT(ret);
}

binder_status_t AParcel_writeString(AParcel* parcel, const char* string, int32_t length) {
    if (string == nullptr) {
        if (length != -1) {
            LOG(WARNING) << __func__ << ": null string must be used with length == -1.";
            return STATUS_BAD_VALUE;
        }

        status_t err = parcel->get()->writeInt32(-1);
        return PruneStatusT(err);
    }

    if (length < 0) {
        LOG(WARNING) << __func__ << ": Negative string length: " << length;
        return STATUS_BAD_VALUE;
    }

    const uint8_t* str8 = (uint8_t*)string;
    const ssize_t len16 = utf8_to_utf16_length(str8, length);

    if (len16 < 0 || len16 >= std::numeric_limits<int32_t>::max()) {
        LOG(WARNING) << __func__ << ": Invalid string length: " << len16;
        return STATUS_BAD_VALUE;
    }

    status_t err = parcel->get()->writeInt32(len16);
    if (err) {
        return PruneStatusT(err);
    }

    void* str16 = parcel->get()->writeInplace((len16 + 1) * sizeof(char16_t));
    if (str16 == nullptr) {
        return STATUS_NO_MEMORY;
    }

    utf8_to_utf16(str8, length, (char16_t*)str16, (size_t)len16 + 1);

    return STATUS_OK;
}

binder_status_t AParcel_readString(const AParcel* parcel, void* stringData,
                                   AParcel_stringAllocator allocator) {
    size_t len16;
    const char16_t* str16 = parcel->get()->readString16Inplace(&len16);

    if (str16 == nullptr) {
        if (allocator(stringData, -1, nullptr)) {
            return STATUS_OK;
        }

        return STATUS_UNEXPECTED_NULL;
    }

    ssize_t len8;

    if (len16 == 0) {
        len8 = 1;
    } else {
        len8 = utf16_to_utf8_length(str16, len16) + 1;
    }

    if (len8 <= 0 || len8 > std::numeric_limits<int32_t>::max()) {
        LOG(WARNING) << __func__ << ": Invalid string length: " << len8;
        return STATUS_BAD_VALUE;
    }

    char* str8;
    bool success = allocator(stringData, len8, &str8);

    if (!success || str8 == nullptr) {
        LOG(WARNING) << __func__ << ": AParcel_stringAllocator failed to allocate.";
        return STATUS_NO_MEMORY;
    }

    utf16_to_utf8(str16, len16, str8, len8);

    return STATUS_OK;
}

binder_status_t AParcel_writeStringArray(AParcel* parcel, const void* arrayData, int32_t length,
                                         AParcel_stringArrayElementGetter getter) {
    // we have no clue if arrayData represents a null object or not, we can only infer from length
    bool arrayIsNull = length < 0;
    binder_status_t status = WriteAndValidateArraySize(parcel, arrayIsNull, length);
    if (status != STATUS_OK) return status;
    if (length <= 0) return STATUS_OK;

    for (int32_t i = 0; i < length; i++) {
        int32_t elementLength = 0;
        const char* str = getter(arrayData, i, &elementLength);
        if (str == nullptr && elementLength != -1) return STATUS_BAD_VALUE;

        binder_status_t status = AParcel_writeString(parcel, str, elementLength);
        if (status != STATUS_OK) return status;
    }

    return STATUS_OK;
}

// This implements AParcel_stringAllocator for a string using an array, index, and element
// allocator.
struct StringArrayElementAllocationAdapter {
    void* arrayData;  // stringData from the NDK
    int32_t index;    // index into the string array
    AParcel_stringArrayElementAllocator elementAllocator;

    static bool Allocator(void* stringData, int32_t length, char** buffer) {
        StringArrayElementAllocationAdapter* adapter =
                static_cast<StringArrayElementAllocationAdapter*>(stringData);
        return adapter->elementAllocator(adapter->arrayData, adapter->index, length, buffer);
    }
};

binder_status_t AParcel_readStringArray(const AParcel* parcel, void* arrayData,
                                        AParcel_stringArrayAllocator allocator,
                                        AParcel_stringArrayElementAllocator elementAllocator) {
    int32_t length;
    if (binder_status_t status = ReadAndValidateArraySize(parcel, &length); status != STATUS_OK) {
        return status;
    }

    if (!allocator(arrayData, length)) return STATUS_NO_MEMORY;

    if (length == -1) return STATUS_OK;  // null string array

    StringArrayElementAllocationAdapter adapter{
            .arrayData = arrayData,
            .index = 0,
            .elementAllocator = elementAllocator,
    };

    for (; adapter.index < length; adapter.index++) {
        binder_status_t status = AParcel_readString(parcel, static_cast<void*>(&adapter),
                                                    StringArrayElementAllocationAdapter::Allocator);

        if (status != STATUS_OK) return status;
    }

    return STATUS_OK;
}

binder_status_t AParcel_writeParcelableArray(AParcel* parcel, const void* arrayData, int32_t length,
                                             AParcel_writeParcelableElement elementWriter) {
    // we have no clue if arrayData represents a null object or not, we can only infer from length
    bool arrayIsNull = length < 0;
    binder_status_t status = WriteAndValidateArraySize(parcel, arrayIsNull, length);
    if (status != STATUS_OK) return status;
    if (length <= 0) return STATUS_OK;

    for (int32_t i = 0; i < length; i++) {
        binder_status_t status = elementWriter(parcel, arrayData, i);
        if (status != STATUS_OK) return status;
    }

    return STATUS_OK;
}

binder_status_t AParcel_readParcelableArray(const AParcel* parcel, void* arrayData,
                                            AParcel_parcelableArrayAllocator allocator,
                                            AParcel_readParcelableElement elementReader) {
    int32_t length;
    if (binder_status_t status = ReadAndValidateArraySize(parcel, &length); status != STATUS_OK) {
        return status;
    }

    if (!allocator(arrayData, length)) return STATUS_NO_MEMORY;

    if (length == -1) return STATUS_OK;  // null array

    for (int32_t i = 0; i < length; i++) {
        binder_status_t status = elementReader(parcel, arrayData, i);
        if (status != STATUS_OK) return status;
    }

    return STATUS_OK;
}

// See gen_parcel_helper.py. These auto-generated read/write methods use the same types for
// libbinder and this library.
// @START
binder_status_t AParcel_writeInt32(AParcel* parcel, int32_t value) {
    status_t status = parcel->get()->writeInt32(value);
    return PruneStatusT(status);
}

binder_status_t AParcel_writeUint32(AParcel* parcel, uint32_t value) {
    status_t status = parcel->get()->writeUint32(value);
    return PruneStatusT(status);
}

binder_status_t AParcel_writeInt64(AParcel* parcel, int64_t value) {
    status_t status = parcel->get()->writeInt64(value);
    return PruneStatusT(status);
}

binder_status_t AParcel_writeUint64(AParcel* parcel, uint64_t value) {
    status_t status = parcel->get()->writeUint64(value);
    return PruneStatusT(status);
}

binder_status_t AParcel_writeFloat(AParcel* parcel, float value) {
    status_t status = parcel->get()->writeFloat(value);
    return PruneStatusT(status);
}

binder_status_t AParcel_writeDouble(AParcel* parcel, double value) {
    status_t status = parcel->get()->writeDouble(value);
    return PruneStatusT(status);
}

binder_status_t AParcel_writeBool(AParcel* parcel, bool value) {
    status_t status = parcel->get()->writeBool(value);
    return PruneStatusT(status);
}

binder_status_t AParcel_writeChar(AParcel* parcel, char16_t value) {
    status_t status = parcel->get()->writeChar(value);
    return PruneStatusT(status);
}

binder_status_t AParcel_writeByte(AParcel* parcel, int8_t value) {
    status_t status = parcel->get()->writeByte(value);
    return PruneStatusT(status);
}

binder_status_t AParcel_readInt32(const AParcel* parcel, int32_t* value) {
    status_t status = parcel->get()->readInt32(value);
    return PruneStatusT(status);
}

binder_status_t AParcel_readUint32(const AParcel* parcel, uint32_t* value) {
    status_t status = parcel->get()->readUint32(value);
    return PruneStatusT(status);
}

binder_status_t AParcel_readInt64(const AParcel* parcel, int64_t* value) {
    status_t status = parcel->get()->readInt64(value);
    return PruneStatusT(status);
}

binder_status_t AParcel_readUint64(const AParcel* parcel, uint64_t* value) {
    status_t status = parcel->get()->readUint64(value);
    return PruneStatusT(status);
}

binder_status_t AParcel_readFloat(const AParcel* parcel, float* value) {
    status_t status = parcel->get()->readFloat(value);
    return PruneStatusT(status);
}

binder_status_t AParcel_readDouble(const AParcel* parcel, double* value) {
    status_t status = parcel->get()->readDouble(value);
    return PruneStatusT(status);
}

binder_status_t AParcel_readBool(const AParcel* parcel, bool* value) {
    status_t status = parcel->get()->readBool(value);
    return PruneStatusT(status);
}

binder_status_t AParcel_readChar(const AParcel* parcel, char16_t* value) {
    status_t status = parcel->get()->readChar(value);
    return PruneStatusT(status);
}

binder_status_t AParcel_readByte(const AParcel* parcel, int8_t* value) {
    status_t status = parcel->get()->readByte(value);
    return PruneStatusT(status);
}

binder_status_t AParcel_writeInt32Array(AParcel* parcel, const int32_t* arrayData, int32_t length) {
    return WriteArray<int32_t>(parcel, arrayData, length);
}

binder_status_t AParcel_writeUint32Array(AParcel* parcel, const uint32_t* arrayData,
                                         int32_t length) {
    return WriteArray<uint32_t>(parcel, arrayData, length);
}

binder_status_t AParcel_writeInt64Array(AParcel* parcel, const int64_t* arrayData, int32_t length) {
    return WriteArray<int64_t>(parcel, arrayData, length);
}

binder_status_t AParcel_writeUint64Array(AParcel* parcel, const uint64_t* arrayData,
                                         int32_t length) {
    return WriteArray<uint64_t>(parcel, arrayData, length);
}

binder_status_t AParcel_writeFloatArray(AParcel* parcel, const float* arrayData, int32_t length) {
    return WriteArray<float>(parcel, arrayData, length);
}

binder_status_t AParcel_writeDoubleArray(AParcel* parcel, const double* arrayData, int32_t length) {
    return WriteArray<double>(parcel, arrayData, length);
}

binder_status_t AParcel_writeBoolArray(AParcel* parcel, const void* arrayData, int32_t length,
                                       AParcel_boolArrayGetter getter) {
    return WriteArray<bool>(parcel, arrayData, length, getter, &Parcel::writeBool);
}

binder_status_t AParcel_writeCharArray(AParcel* parcel, const char16_t* arrayData, int32_t length) {
    return WriteArray<char16_t>(parcel, arrayData, length);
}

binder_status_t AParcel_writeByteArray(AParcel* parcel, const int8_t* arrayData, int32_t length) {
    return WriteArray<int8_t>(parcel, arrayData, length);
}

binder_status_t AParcel_readInt32Array(const AParcel* parcel, void* arrayData,
                                       AParcel_int32ArrayAllocator allocator) {
    return ReadArray<int32_t>(parcel, arrayData, allocator);
}

binder_status_t AParcel_readUint32Array(const AParcel* parcel, void* arrayData,
                                        AParcel_uint32ArrayAllocator allocator) {
    return ReadArray<uint32_t>(parcel, arrayData, allocator);
}

binder_status_t AParcel_readInt64Array(const AParcel* parcel, void* arrayData,
                                       AParcel_int64ArrayAllocator allocator) {
    return ReadArray<int64_t>(parcel, arrayData, allocator);
}

binder_status_t AParcel_readUint64Array(const AParcel* parcel, void* arrayData,
                                        AParcel_uint64ArrayAllocator allocator) {
    return ReadArray<uint64_t>(parcel, arrayData, allocator);
}

binder_status_t AParcel_readFloatArray(const AParcel* parcel, void* arrayData,
                                       AParcel_floatArrayAllocator allocator) {
    return ReadArray<float>(parcel, arrayData, allocator);
}

binder_status_t AParcel_readDoubleArray(const AParcel* parcel, void* arrayData,
                                        AParcel_doubleArrayAllocator allocator) {
    return ReadArray<double>(parcel, arrayData, allocator);
}

binder_status_t AParcel_readBoolArray(const AParcel* parcel, void* arrayData,
                                      AParcel_boolArrayAllocator allocator,
                                      AParcel_boolArraySetter setter) {
    return ReadArray<bool>(parcel, arrayData, allocator, setter, &Parcel::readBool);
}

binder_status_t AParcel_readCharArray(const AParcel* parcel, void* arrayData,
                                      AParcel_charArrayAllocator allocator) {
    return ReadArray<char16_t>(parcel, arrayData, allocator);
}

binder_status_t AParcel_readByteArray(const AParcel* parcel, void* arrayData,
                                      AParcel_byteArrayAllocator allocator) {
    return ReadArray<int8_t>(parcel, arrayData, allocator);
}

bool AParcel_getAllowFds(const AParcel* parcel) {
    return parcel->get()->allowFds();
}

binder_status_t AParcel_reset(AParcel* parcel) {
    parcel->get()->freeData();
    return STATUS_OK;
}

int32_t AParcel_getDataSize(const AParcel* parcel) {
    return parcel->get()->dataSize();
}

binder_status_t AParcel_appendFrom(const AParcel* from, AParcel* to, int32_t start, int32_t size) {
    status_t status = to->get()->appendFrom(from->get(), start, size);
    return PruneStatusT(status);
}

AParcel* AParcel_create() {
    return new AParcel(nullptr);
}

binder_status_t AParcel_marshal(const AParcel* parcel, uint8_t* buffer, size_t start, size_t len) {
    if (parcel->get()->objectsCount()) {
        return STATUS_INVALID_OPERATION;
    }
    int32_t dataSize = AParcel_getDataSize(parcel);
    if (len > static_cast<size_t>(dataSize) || start > static_cast<size_t>(dataSize) - len) {
        return STATUS_BAD_VALUE;
    }
    const uint8_t* internalBuffer = parcel->get()->data();
    memcpy(buffer, internalBuffer + start, len);
    return STATUS_OK;
}

binder_status_t AParcel_unmarshal(AParcel* parcel, const uint8_t* buffer, size_t len) {
    status_t status = parcel->get()->setDataSize(len);
    if (status != ::android::OK) {
        return PruneStatusT(status);
    }
    parcel->get()->setDataPosition(0);

    void* raw = parcel->get()->writeInplace(len);
    if (raw == nullptr) {
        return STATUS_NO_MEMORY;
    }
    memcpy(raw, buffer, len);
    return STATUS_OK;
}

// @END
