/*
 * Copyright (C) 2014, 2016 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "config.h"
#include "IDBSerialization.h"

#if ENABLE(INDEXED_DATABASE)

#include "IDBKeyData.h"
#include "IDBKeyPath.h"
#include "KeyedCoding.h"

#if USE(GLIB)
#include <glib.h>
#include <wtf/glib/GRefPtr.h>
#endif

namespace WebCore {

enum class KeyPathType { Null, String, Array };

RefPtr<SharedBuffer> serializeIDBKeyPath(const Optional<IDBKeyPath>& keyPath)
{
    auto encoder = KeyedEncoder::encoder();

    if (keyPath) {
        auto visitor = WTF::makeVisitor([&](const String& string) {
            encoder->encodeEnum("type", KeyPathType::String);
            encoder->encodeString("string", string);
        }, [&](const Vector<String>& vector) {
            encoder->encodeEnum("type", KeyPathType::Array);
            encoder->encodeObjects("array", vector.begin(), vector.end(), [](WebCore::KeyedEncoder& encoder, const String& string) {
                encoder.encodeString("string", string);
            });
        });
        WTF::visit(visitor, keyPath.value());
    } else
        encoder->encodeEnum("type", KeyPathType::Null);

    return encoder->finishEncoding();
}

bool deserializeIDBKeyPath(const uint8_t* data, size_t size, Optional<IDBKeyPath>& result)
{
    if (!data || !size)
        return false;

    auto decoder = KeyedDecoder::decoder(data, size);

    KeyPathType type;
    bool succeeded = decoder->decodeEnum("type", type, [](KeyPathType value) {
        return value == KeyPathType::Null || value == KeyPathType::String || value == KeyPathType::Array;
    });
    if (!succeeded)
        return false;

    switch (type) {
    case KeyPathType::Null:
        break;
    case KeyPathType::String: {
        String string;
        if (!decoder->decodeString("string", string))
            return false;
        result = IDBKeyPath(WTFMove(string));
        break;
    }
    case KeyPathType::Array: {
        Vector<String> vector;
        succeeded = decoder->decodeObjects("array", vector, [](KeyedDecoder& decoder, String& result) {
            return decoder.decodeString("string", result);
        });
        if (!succeeded)
            return false;
        result = IDBKeyPath(WTFMove(vector));
        break;
    }
    }
    return true;
}

static bool isLegacySerializedIDBKeyData(const uint8_t* data, size_t size)
{
#if USE(CF)
    UNUSED_PARAM(size);

    // This is the magic character that begins serialized PropertyLists, and tells us whether
    // the key we're looking at is an old-style key.
    static const uint8_t legacySerializedKeyVersion = 'b';
    if (data[0] == legacySerializedKeyVersion)
        return true;
#elif USE(GLIB)
    // KeyedEncoderGLib uses a GVariant dictionary, so check if the given data is a valid GVariant dictionary.
    GRefPtr<GBytes> bytes = adoptGRef(g_bytes_new(data, size));
    GRefPtr<GVariant> variant = g_variant_new_from_bytes(G_VARIANT_TYPE("a{sv}"), bytes.get(), FALSE);
    return g_variant_is_normal_form(variant.get());
#else
    UNUSED_PARAM(data);
    UNUSED_PARAM(size);
#endif
    return false;
}


/*
The IDBKeyData serialization format is as follows:
[1 byte version header][Key Buffer]

The Key Buffer serialization format is as follows:
[1 byte key type][Type specific data]

Type specific serialization formats are as follows for each of the types:
Min:
[0 bytes]

Number:
[8 bytes representing a double encoded in little endian]

Date:
[8 bytes representing a double encoded in little endian]

String:
[4 bytes representing string "length" in little endian]["length" number of 2-byte pairs representing ECMAScript 16-bit code units]

Binary:
[8 bytes representing the "size" of the binary blob]["size" bytes]

Array:
[8 bytes representing the "length" of the key array]["length" individual Key Buffer entries]

Max:
[0 bytes]
*/

static const uint8_t SIDBKeyVersion = 0x00;
enum class SIDBKeyType : uint8_t {
    Min = 0x00,
    Number = 0x20,
    Date = 0x40,
    String = 0x60,
    Binary = 0x80,
    Array = 0xA0,
    Max = 0xFF,
};

static SIDBKeyType serializedTypeForKeyType(IndexedDB::KeyType type)
{
    switch (type) {
    case IndexedDB::KeyType::Min:
        return SIDBKeyType::Min;
    case IndexedDB::KeyType::Number:
        return SIDBKeyType::Number;
    case IndexedDB::KeyType::Date:
        return SIDBKeyType::Date;
    case IndexedDB::KeyType::String:
        return SIDBKeyType::String;
    case IndexedDB::KeyType::Binary:
        return SIDBKeyType::Binary;
    case IndexedDB::KeyType::Array:
        return SIDBKeyType::Array;
    case IndexedDB::KeyType::Max:
        return SIDBKeyType::Max;
    case IndexedDB::KeyType::Invalid:
        RELEASE_ASSERT_NOT_REACHED();
    };

    RELEASE_ASSERT_NOT_REACHED();
}

#if CPU(BIG_ENDIAN) || CPU(MIDDLE_ENDIAN) || CPU(NEEDS_ALIGNED_ACCESS)
template <typename T> static void writeLittleEndian(Vector<char>& buffer, T value)
{
    for (unsigned i = 0; i < sizeof(T); i++) {
        buffer.append(value & 0xFF);
        value >>= 8;
    }
}

template <typename T> static bool readLittleEndian(const uint8_t*& ptr, const uint8_t* end, T& value)
{
    if (ptr > end - sizeof(value))
        return false;

    value = 0;
    for (size_t i = 0; i < sizeof(T); i++)
        value += ((T)*ptr++) << (i * 8);
    return true;
}
#else
template <typename T> static void writeLittleEndian(Vector<char>& buffer, T value)
{
    buffer.append(reinterpret_cast<uint8_t*>(&value), sizeof(value));
}

template <typename T> static bool readLittleEndian(const uint8_t*& ptr, const uint8_t* end, T& value)
{
    if (ptr > end - sizeof(value))
        return false;

    value = *reinterpret_cast<const T*>(ptr);
    ptr += sizeof(T);

    return true;
}
#endif

static void writeDouble(Vector<char>& data, double d)
{
    writeLittleEndian(data, *reinterpret_cast<uint64_t*>(&d));
}

static bool readDouble(const uint8_t*& data, const uint8_t* end, double& d)
{
    return readLittleEndian(data, end, *reinterpret_cast<uint64_t*>(&d));
}

static void encodeKey(Vector<char>& data, const IDBKeyData& key)
{
    SIDBKeyType type = serializedTypeForKeyType(key.type());
    data.append(static_cast<char>(type));

    switch (type) {
    case SIDBKeyType::Number:
        writeDouble(data, key.number());
        break;
    case SIDBKeyType::Date:
        writeDouble(data, key.date());
        break;
    case SIDBKeyType::String: {
        auto string = key.string();
        uint32_t length = string.length();
        writeLittleEndian(data, length);

        for (size_t i = 0; i < length; ++i)
            writeLittleEndian(data, string[i]);

        break;
    }
    case SIDBKeyType::Binary: {
        auto& buffer = key.binary();
        uint64_t size = buffer.size();
        writeLittleEndian(data, size);

        auto* bufferData = buffer.data();
        ASSERT(bufferData || !size);
        if (bufferData)
            data.append(bufferData->data(), bufferData->size());

        break;
    }
    case SIDBKeyType::Array: {
        auto& array = key.array();
        uint64_t size = array.size();
        writeLittleEndian(data, size);
        for (auto& key : array)
            encodeKey(data, key);

        break;
    }
    case SIDBKeyType::Min:
    case SIDBKeyType::Max:
        break;
    }
}

RefPtr<SharedBuffer> serializeIDBKeyData(const IDBKeyData& key)
{
    Vector<char> data;
    data.append(SIDBKeyVersion);

    encodeKey(data, key);
    return SharedBuffer::create(WTFMove(data));
}

static WARN_UNUSED_RETURN bool decodeKey(const uint8_t*& data, const uint8_t* end, IDBKeyData& result)
{
    if (!data || data >= end)
        return false;

    SIDBKeyType type = static_cast<SIDBKeyType>(data++[0]);
    switch (type) {
    case SIDBKeyType::Min:
        result = IDBKeyData::minimum();
        return true;
    case SIDBKeyType::Max:
        result = IDBKeyData::maximum();
        return true;
    case SIDBKeyType::Number: {
        double d;
        if (!readDouble(data, end, d))
            return false;

        result.setNumberValue(d);
        return true;
    }
    case SIDBKeyType::Date: {
        double d;
        if (!readDouble(data, end, d))
            return false;

        result.setDateValue(d);
        return true;
    }
    case SIDBKeyType::String: {
        uint32_t length;
        if (!readLittleEndian(data, end, length))
            return false;

        if (static_cast<uint64_t>(end - data) < length * 2)
            return false;

        Vector<UChar> buffer;
        buffer.reserveInitialCapacity(length);
        for (size_t i = 0; i < length; i++) {
            uint16_t ch;
            if (!readLittleEndian(data, end, ch))
                return false;
            buffer.uncheckedAppend(ch);
        }

        result.setStringValue(String::adopt(WTFMove(buffer)));

        return true;
    }
    case SIDBKeyType::Binary: {
        uint64_t size64;
        if (!readLittleEndian(data, end, size64))
            return false;

        if (static_cast<uint64_t>(end - data) < size64)
            return false;

        if (size64 > std::numeric_limits<size_t>::max())
            return false;

        size_t size = static_cast<size_t>(size64);
        Vector<uint8_t> dataVector;

        dataVector.append(data, size);
        data += size;

        result.setBinaryValue(ThreadSafeDataBuffer::create(WTFMove(dataVector)));
        return true;
    }
    case SIDBKeyType::Array: {
        uint64_t size64;
        if (!readLittleEndian(data, end, size64))
            return false;

        if (size64 > std::numeric_limits<size_t>::max())
            return false;

        size_t size = static_cast<size_t>(size64);
        Vector<IDBKeyData> array;
        array.reserveInitialCapacity(size);

        for (size_t i = 0; i < size; ++i) {
            IDBKeyData keyData;
            if (!decodeKey(data, end, keyData))
                return false;

            ASSERT(keyData.isValid());
            array.uncheckedAppend(WTFMove(keyData));
        }

        result.setArrayValue(array);

        return true;
    }
    default:
        LOG_ERROR("decodeKey encountered unexpected type: %i", (int)type);
        return false;
    }
}

bool deserializeIDBKeyData(const uint8_t* data, size_t size, IDBKeyData& result)
{
    if (!data || !size)
        return false;

    if (isLegacySerializedIDBKeyData(data, size)) {
        auto decoder = KeyedDecoder::decoder(data, size);
        return IDBKeyData::decode(*decoder, result);
    }

    // Verify this is a SerializedIDBKey version we understand.
    const uint8_t* current = data;
    const uint8_t* end = data + size;
    if (current++[0] != SIDBKeyVersion)
        return false;

    if (decodeKey(current, end, result)) {
        // Even if we successfully decoded a key, the deserialize is only successful
        // if we actually consumed all input data.
        return current == end;
    }

    return false;
}

} // namespace WebCore

#endif // ENABLE(INDEXED_DATABASE)
