/**
 * Copyright (C) 2015-2018 Virgil Security 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.
 *
 *     (3) Neither the name of the copyright holder 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 AUTHOR ''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 AUTHOR 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.
 *
 * Lead Maintainer: Virgil Security Inc. <support@virgilsecurity.com>
 */

/**
 * @file test_asn1_writer.cxx
 * @brief Covers class VirgilAsn1Writer
 */

#include "catch.hpp"

#include <virgil/crypto/VirgilByteArray.h>
#include <virgil/crypto/VirgilByteArrayUtils.h>
#include <virgil/crypto/VirgilCryptoException.h>
#include <virgil/crypto/foundation/asn1/VirgilAsn1Writer.h>

using virgil::crypto::VirgilByteArray;
using virgil::crypto::VirgilByteArrayUtils;
using virgil::crypto::VirgilCryptoException;
using virgil::crypto::foundation::asn1::VirgilAsn1Writer;

constexpr size_t kAsn1SizeMax = 10 * 1024 * 1024; // 10MB, really not maximum but good enough
constexpr size_t kAsn1LengthMax = kAsn1SizeMax - 2 /* minus 2 tags size */;

TEST_CASE("ASN.1 write: use small buffer", "[asn1-writer]") {
    VirgilAsn1Writer asn1Writer(1);

    SECTION ("with big integer positive") {
        int number = 0x7fffffff;
        asn1Writer.writeInteger(number);
        VirgilByteArray asn1 = asn1Writer.finish();
        REQUIRE(VirgilByteArrayUtils::bytesToHex(asn1) == "02047fffffff");
    }

    SECTION ("with big integer negative") {
        int number = -0x7fffffff;
        asn1Writer.writeInteger(number);
        VirgilByteArray asn1 = asn1Writer.finish();
        REQUIRE(VirgilByteArrayUtils::bytesToHex(asn1) == "020480000001");
    }

    SECTION ("with bool true") {
        asn1Writer.writeBool(true);
        VirgilByteArray asn1 = asn1Writer.finish();
        REQUIRE(VirgilByteArrayUtils::bytesToHex(asn1) == "0101ff");
    }

    SECTION ("with bool false") {
        asn1Writer.writeBool(false);
        VirgilByteArray asn1 = asn1Writer.finish();
        REQUIRE(VirgilByteArrayUtils::bytesToHex(asn1) == "010100");
    }

    SECTION ("with NULL") {
        asn1Writer.writeNull();
        VirgilByteArray asn1 = asn1Writer.finish();
        REQUIRE(VirgilByteArrayUtils::bytesToHex(asn1) == "0500");
    }

    SECTION ("with octet string") {
        asn1Writer.writeOctetString(VirgilByteArrayUtils::hexToBytes("112233445566778899aabbccddeeff"));
        VirgilByteArray asn1 = asn1Writer.finish();
        REQUIRE(VirgilByteArrayUtils::bytesToHex(asn1) == "040f112233445566778899aabbccddeeff");
    }

    SECTION ("with max octet string") {
        VirgilByteArray octetString = VirgilByteArray(kAsn1LengthMax, 0xAB);
        VirgilByteArray asn1Expected = VirgilByteArrayUtils::hexToBytes("04839ffffe");
        asn1Expected.insert(asn1Expected.end(), octetString.begin(), octetString.end());
        asn1Writer.writeOctetString(octetString);
        VirgilByteArray asn1 = asn1Writer.finish();
        REQUIRE(VirgilByteArrayUtils::bytesToHex(asn1) == VirgilByteArrayUtils::bytesToHex(asn1Expected));
    }

    SECTION ("with UTF8 string") {
        asn1Writer.writeUTF8String(VirgilByteArrayUtils::hexToBytes("4142434445464748494a4b4c4d4e4f"));
        VirgilByteArray asn1 = asn1Writer.finish();
        REQUIRE(VirgilByteArrayUtils::bytesToHex(asn1) == "0c0f4142434445464748494a4b4c4d4e4f");
    }

    SECTION ("with max UTF8 string") {
        VirgilByteArray utf8String = VirgilByteArray(kAsn1LengthMax, 0x41);
        VirgilByteArray asn1Expected = VirgilByteArrayUtils::hexToBytes("0c839ffffe");
        asn1Expected.insert(asn1Expected.end(), utf8String.begin(), utf8String.end());
        asn1Writer.writeUTF8String(utf8String);
        VirgilByteArray asn1 = asn1Writer.finish();
        REQUIRE(VirgilByteArrayUtils::bytesToHex(asn1) == VirgilByteArrayUtils::bytesToHex(asn1Expected));
    }

    SECTION ("with context tag over UTF8 string") {
        size_t len = asn1Writer.writeUTF8String(VirgilByteArrayUtils::hexToBytes("4142434445464748494a4b4c4d4e4f"));
        asn1Writer.writeContextTag(1, len);
        VirgilByteArray asn1 = asn1Writer.finish();
        REQUIRE(VirgilByteArrayUtils::bytesToHex(asn1) == "a1110c0f4142434445464748494a4b4c4d4e4f");
    }

    SECTION ("with corrupted context tag") {
        REQUIRE_THROWS(asn1Writer.writeContextTag(31, 0));
    }

    SECTION ("with max RAW buffer") {
        VirgilByteArray data = VirgilByteArray(kAsn1SizeMax, 0x41);
        asn1Writer.writeData(data);
        VirgilByteArray asn1 = asn1Writer.finish();
        REQUIRE(VirgilByteArrayUtils::bytesToHex(asn1) == VirgilByteArrayUtils::bytesToHex(data));
    }

    SECTION ("with OID") {
        std::string oid =
                VirgilByteArrayUtils::bytesToString(VirgilByteArrayUtils::hexToBytes("4142434445464748494a4b4c4d4e4f"));
        asn1Writer.writeOID(oid);
        VirgilByteArray asn1 = asn1Writer.finish();
        REQUIRE(VirgilByteArrayUtils::bytesToHex(asn1) == "060f4142434445464748494a4b4c4d4e4f");
    }

    SECTION ("with max OID") {
        std::string oid = VirgilByteArrayUtils::bytesToString(VirgilByteArray(kAsn1LengthMax, 0x41));
        VirgilByteArray asn1Expected = VirgilByteArrayUtils::hexToBytes("06839ffffe");
        asn1Expected.insert(asn1Expected.end(), oid.begin(), oid.end());
        asn1Writer.writeOID(oid);
        VirgilByteArray asn1 = asn1Writer.finish();
        REQUIRE(VirgilByteArrayUtils::bytesToHex(asn1) == VirgilByteArrayUtils::bytesToHex(asn1Expected));
    }

    SECTION ("with sequence over UTF8 string") {
        size_t len = asn1Writer.writeUTF8String(VirgilByteArrayUtils::hexToBytes("4142434445464748494a4b4c4d4e4f"));
        asn1Writer.writeSequence(len);
        VirgilByteArray asn1 = asn1Writer.finish();
        REQUIRE(VirgilByteArrayUtils::bytesToHex(asn1) == "30110c0f4142434445464748494a4b4c4d4e4f");
    }

    SECTION("with set") {
        std::vector<VirgilByteArray> set;
        for (size_t i = 0; i < 5; ++i) {
            set.push_back(VirgilByteArrayUtils::hexToBytes("30110c0f4142434445464748494a4b4c4d4e4f"));
        }
        asn1Writer.writeSet(set);
        VirgilByteArray asn1 = asn1Writer.finish();
        REQUIRE(VirgilByteArrayUtils::bytesToHex(asn1) ==
                "315f"
                        "30110c0f4142434445464748494a4b4c4d4e4f"
                        "30110c0f4142434445464748494a4b4c4d4e4f"
                        "30110c0f4142434445464748494a4b4c4d4e4f"
                        "30110c0f4142434445464748494a4b4c4d4e4f"
                        "30110c0f4142434445464748494a4b4c4d4e4f"
        );
    }

    SECTION("with max set") {
        VirgilByteArray utf8StringHead = VirgilByteArrayUtils::hexToBytes("0c82fffb");
        VirgilByteArray utf8StringBody = VirgilByteArray(kAsn1LengthMax - utf8StringHead.size(), 0x41);
        VirgilByteArray utf8String;
        utf8String.insert(utf8String.end(), utf8StringHead.begin(), utf8StringHead.end());
        utf8String.insert(utf8String.end(), utf8StringBody.begin(), utf8StringBody.end());

        VirgilByteArray asn1Expected = VirgilByteArrayUtils::hexToBytes("31839ffffe");
        asn1Expected.insert(asn1Expected.end(), utf8String.begin(), utf8String.end());

        std::vector<VirgilByteArray> set;
        set.push_back(utf8String);

        asn1Writer.writeSet(set);
        VirgilByteArray asn1 = asn1Writer.finish();
        REQUIRE(VirgilByteArrayUtils::bytesToHex(asn1) == VirgilByteArrayUtils::bytesToHex(asn1Expected));
    }
}

TEST_CASE("ASN.1 write: check step by step ASN.1 buffer grows", "[asn1-writer]") {
    VirgilAsn1Writer asn1Writer(1);
    size_t len = 0;
    REQUIRE_THROWS(for (; ;) { len += asn1Writer.writeSequence(len); });
}