// Copyright 2017 The Chromium Authors. All rights reserved.
// Copyright (C) 2018 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:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * 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.
//    * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
// OWNER OR 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.

#pragma once

#if ENABLE(WEB_AUTHN)

#include <stdint.h>
#include <wtf/Noncopyable.h>
#include <wtf/StdMap.h>
#include <wtf/Vector.h>
#include <wtf/text/WTFString.h>

namespace cbor {

// A class for Concise Binary Object Representation (CBOR) values.
// This does not support:
//  * Floating-point numbers.
//  * Indefinite-length encodings.
class WEBCORE_EXPORT CBORValue {
    WTF_MAKE_NONCOPYABLE(CBORValue);
public:
    struct CTAPLess {
        // Comparison predicate to order keys in a dictionary as required by the
        // Client-to-Authenticator Protocol (CTAP) spec 2.0.
        //
        // The sort order defined in CTAP is:
        //   * If the major types are different, the one with the lower value in
        //     numerical order sorts earlier.
        //   * If two keys have different lengths, the shorter one sorts earlier.
        //   * If two keys have the same length, the one with the lower value in
        //     (byte-wise) lexical order sorts earlier.
        //
        // See section 6 of https://fidoalliance.org/specs/fido-v2.0-rd-20170927/
        // fido-client-to-authenticator-protocol-v2.0-rd-20170927.html.
        //
        // THE CTAP SORT ORDER IMPLEMENTED HERE DIFFERS FROM THE CANONICAL CBOR
        // ORDER defined in https://tools.ietf.org/html/rfc7049#section-3.9, in that
        // the latter sorts purely by serialised key and doesn't specify that major
        // types are compared first. Thus the shortest key sorts first by the RFC
        // rules (irrespective of the major type), but may not by CTAP rules.
        bool operator()(const CBORValue& a, const CBORValue& b) const
        {
            ASSERT((a.isInteger() || a.isString()) && (b.isInteger() || b.isString()));
            if (a.type() != b.type())
                return a.type() < b.type();
            switch (a.type()) {
            case Type::Unsigned:
                return a.getInteger() < b.getInteger();
            case Type::Negative:
                return a.getInteger() > b.getInteger();
            case Type::String: {
                const auto& aStr = a.getString();
                const size_t aLength = aStr.length();
                const auto& bStr = b.getString();
                const size_t bLength = bStr.length();

                if (aLength != bLength)
                    return aLength < bLength;
                return WTF::codePointCompareLessThan(aStr, bStr);
            }
            default:
                break;
            }

            ASSERT_NOT_REACHED();
            return false;
        }
    };

    using BinaryValue = Vector<uint8_t>;
    using ArrayValue = Vector<CBORValue>;
    using MapValue = StdMap<CBORValue, CBORValue, CTAPLess>;

    enum class Type {
        Unsigned = 0,
        Negative = 1,
        ByteString = 2,
        String = 3,
        Array = 4,
        Map = 5,
        SimpleValue = 7,
        None = -1,
    };

    enum class SimpleValue {
        FalseValue = 20,
        TrueValue = 21,
        NullValue = 22,
        Undefined = 23,
    };

    CBORValue(CBORValue&& that);
    CBORValue(); // A NONE value.

    explicit CBORValue(Type);
    explicit CBORValue(int);
    explicit CBORValue(int64_t);
    explicit CBORValue(uint64_t) = delete;

    explicit CBORValue(const BinaryValue&);
    explicit CBORValue(BinaryValue&&);

    explicit CBORValue(const char*);
    explicit CBORValue(String&&);
    explicit CBORValue(const String&);

    explicit CBORValue(const ArrayValue&);
    explicit CBORValue(ArrayValue&&);

    explicit CBORValue(const MapValue&);
    explicit CBORValue(MapValue&&);

    explicit CBORValue(SimpleValue);
    explicit CBORValue(bool);

    CBORValue& operator=(CBORValue&&);

    ~CBORValue();

    // CBORValue's copy constructor and copy assignment operator are deleted.
    // Use this to obtain a deep copy explicitly.
    CBORValue clone() const;

    // Returns the type of the value stored by the current Value object.
    Type type() const { return m_type; }

    // Returns true if the current object represents a given type.
    bool isType(Type type) const { return type == m_type; }
    bool isNone() const { return type() == Type::None; }
    bool isUnsigned() const { return type() == Type::Unsigned; }
    bool isNegative() const { return type() == Type::Negative; }
    bool isInteger() const { return isUnsigned() || isNegative(); }
    bool isByteString() const { return type() == Type::ByteString; }
    bool isString() const { return type() == Type::String; }
    bool isArray() const { return type() == Type::Array; }
    bool isMap() const { return type() == Type::Map; }
    bool isSimple() const { return type() == Type::SimpleValue; }
    bool isBool() const { return isSimple() && (m_simpleValue == SimpleValue::TrueValue || m_simpleValue == SimpleValue::FalseValue); }

    // These will all fatally assert if the type doesn't match.
    SimpleValue getSimpleValue() const;
    bool getBool() const;
    const int64_t& getInteger() const;
    const int64_t& getUnsigned() const;
    const int64_t& getNegative() const;
    const BinaryValue& getByteString() const;
    // Returned string may contain NUL characters.
    const String& getString() const;
    const ArrayValue& getArray() const;
    const MapValue& getMap() const;

private:
    Type m_type;

    union {
        SimpleValue m_simpleValue;
        int64_t m_integerValue;
        BinaryValue m_byteStringValue;
        String m_stringValue;
        ArrayValue m_arrayValue;
        MapValue m_mapValue;
    };

    void internalMoveConstructFrom(CBORValue&&);
    void internalCleanup();
};

} // namespace cbor

#endif // ENABLE(WEB_AUTHN)
