// Copyright (C) 2011-2023 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <asiolink/io_address.h>
#include <dhcp/duid.h>
#include <exceptions/exceptions.h>

#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>

#include <iostream>
#include <sstream>

#include <arpa/inet.h>

using namespace std;
using namespace isc;
using namespace isc::dhcp;
using namespace isc::asiolink;

namespace {

using namespace isc::dhcp;

// This test verifies if the constructors are working as expected
// and process passed parameters.
TEST(DuidTest, constructor) {

    uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};

    vector<uint8_t> data2(data1, data1 + sizeof(data1));

    boost::scoped_ptr<DUID> duid1(new DUID(data1, sizeof(data1)));
    boost::scoped_ptr<DUID> duid2(new DUID(data2));

    vector<uint8_t> vecdata = duid1->getDuid();
    EXPECT_TRUE(data2 == vecdata);
    EXPECT_EQ(DUID::DUID_LLT, duid1->getType());

    vecdata = duid2->getDuid();
    EXPECT_TRUE(data2 == vecdata);

    EXPECT_EQ(DUID::DUID_LLT, duid2->getType());
}

// This test verifies if DUID size restrictions are implemented
// properly.
TEST(DuidTest, size) {

    // Ensure that our size constant is RFC-compliant.
    ASSERT_EQ(130, DUID::MAX_DUID_LEN);

    uint8_t data[DUID::MAX_DUID_LEN + 1];
    vector<uint8_t> data2;
    for (uint8_t i = 0; i < DUID::MAX_DUID_LEN + 1; ++i) {
        data[i] = i;
        if (i < DUID::MAX_DUID_LEN) {
            data2.push_back(i);
        }
    }
    ASSERT_EQ(data2.size(), DUID::MAX_DUID_LEN);

    boost::scoped_ptr<DUID> duidmaxsize1(new DUID(data, DUID::MAX_DUID_LEN));
    boost::scoped_ptr<DUID> duidmaxsize2(new DUID(data2));

    EXPECT_THROW(
        boost::scoped_ptr<DUID> toolarge1(new DUID(data, DUID::MAX_DUID_LEN + 1)),
        BadValue);

    // that's one too much
    data2.push_back(128);

    EXPECT_THROW(
        boost::scoped_ptr<DUID> toolarge2(new DUID(data2)),
        BadValue);

    // empty duids are not allowed
    vector<uint8_t> empty;
    EXPECT_THROW(
        boost::scoped_ptr<DUID> emptyDuid(new DUID(empty)),
        BadValue);

    EXPECT_THROW(
        boost::scoped_ptr<DUID> emptyDuid2(new DUID(data, 0)),
        BadValue);
}

// This test verifies if the implementation supports all defined
// DUID types.
TEST(DuidTest, getType) {
    uint8_t llt[] =     {0, 1, 2, 3, 4, 5, 6};
    uint8_t en[] =      {0, 2, 2, 3, 4, 5, 6};
    uint8_t ll[] =      {0, 3, 2, 3, 4, 5, 6};
    uint8_t uuid[] =    {0, 4, 2, 3, 4, 5, 6};
    uint8_t invalid[] = {0,55, 2, 3, 4, 5, 6};

    boost::scoped_ptr<DUID> duid_llt(new DUID(llt, sizeof(llt)));
    boost::scoped_ptr<DUID> duid_en(new DUID(en, sizeof(en)));
    boost::scoped_ptr<DUID> duid_ll(new DUID(ll, sizeof(ll)));
    boost::scoped_ptr<DUID> duid_uuid(new DUID(uuid, sizeof(uuid)));
    boost::scoped_ptr<DUID> duid_invalid(new DUID(invalid, sizeof(invalid)));

    EXPECT_EQ(DUID::DUID_LLT,     duid_llt->getType());
    EXPECT_EQ(DUID::DUID_EN,      duid_en->getType());
    EXPECT_EQ(DUID::DUID_LL,      duid_ll->getType());
    EXPECT_EQ(DUID::DUID_UUID,    duid_uuid->getType());
    EXPECT_EQ(DUID::DUID_UNKNOWN, duid_invalid->getType());
}

// This test checks that the DUID instance can be created from the textual
// format and that error is reported if the textual format is invalid.
TEST(DuidTest, fromText) {
    boost::scoped_ptr<DUID> duid;
    // DUID with only decimal digits.
    ASSERT_NO_THROW(
        duid.reset(new DUID(DUID::fromText("00:01:02:03:04:05:06")))
    );
    EXPECT_EQ("00:01:02:03:04:05:06", duid->toText());
    // DUID with some hexadecimal digits (upper case and lower case).
    ASSERT_NO_THROW(
        duid.reset(new DUID(DUID::fromText("00:aa:bb:CD:ee:EF:ab")))
    );
    EXPECT_EQ("00:aa:bb:cd:ee:ef:ab", duid->toText());
    // DUID with one digit for a particular byte.
    ASSERT_NO_THROW(
        duid.reset(new DUID(DUID::fromText("00:a:bb:D:ee:EF:ab")))
    );
    EXPECT_EQ("00:0a:bb:0d:ee:ef:ab", duid->toText());
    // Repeated colon sign is not allowed.
    EXPECT_THROW(
        duid.reset(new DUID(DUID::fromText("00::bb:D:ee:EF:ab"))),
        isc::BadValue
    );
    // DUID with excessive number of digits for one of the bytes.
    EXPECT_THROW(
       duid.reset(new DUID(DUID::fromText("00:01:021:03:04:05:06"))),
       isc::BadValue
    );
}

// Test checks if the toText() returns valid texual representation
TEST(DuidTest, toText) {
    uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe};

    DUID duid(data1, sizeof(data1));
    EXPECT_EQ("00:01:02:03:04:ff:fe", duid.toText());
}

// This test verifies that empty DUID returns proper value
TEST(DuidTest, empty) {
    DuidPtr empty;
    EXPECT_NO_THROW(empty.reset(new DUID(DUID::EMPTY())));

    // This method must return something
    ASSERT_TRUE(empty);

    // Ok, technically empty is not really empty, it's just type 0 (DUID_UNKNOWN)
    // followed by a single byte with value of 0.
    EXPECT_EQ(empty->getDuid().size(), 3);
    EXPECT_EQ(empty->getDuid(), std::vector<uint8_t>({0, 0, 0}));
    EXPECT_EQ("00:00:00", empty->toText());

    EXPECT_TRUE(*empty == DUID::EMPTY());

    uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe};
    DUID duid(data1, sizeof(data1));

    EXPECT_FALSE(duid == DUID::EMPTY());
}

// This test checks if the comparison operators are sane.
TEST(DuidTest, operators) {
    uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
    uint8_t data2[] = {0, 1, 2, 3, 4};
    uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different
    uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1

    boost::scoped_ptr<DUID> duid1(new DUID(data1, sizeof(data1)));
    boost::scoped_ptr<DUID> duid2(new DUID(data2, sizeof(data2)));
    boost::scoped_ptr<DUID> duid3(new DUID(data3, sizeof(data3)));
    boost::scoped_ptr<DUID> duid4(new DUID(data4, sizeof(data4)));

    EXPECT_TRUE(*duid1 == *duid4);
    EXPECT_FALSE(*duid1 == *duid2);
    EXPECT_FALSE(*duid1 == *duid3);

    EXPECT_FALSE(*duid1 != *duid4);
    EXPECT_TRUE(*duid1 != *duid2);
    EXPECT_TRUE(*duid1 != *duid3);
}

// This test verifies if the ClientId constructors are working properly
// and passed parameters are used
TEST(ClientIdTest, constructor) {
    IOAddress addr2("192.0.2.1");
    IOAddress addr3("2001:db8:1::1");

    uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
    vector<uint8_t> data2(data1, data1 + sizeof(data1));

    // checks for C-style constructor (uint8_t * + len)
    boost::scoped_ptr<ClientId> id1(new ClientId(data1, sizeof(data1)));
    vector<uint8_t> vecdata = id1->getClientId();
    EXPECT_TRUE(data2 == vecdata);

    // checks for vector-based constructor
    boost::scoped_ptr<ClientId> id2(new ClientId(data2));
    vecdata = id2->getClientId();
    EXPECT_TRUE(data2 == vecdata);
}

// Check that client-id sizes are reasonable
TEST(ClientIdTest, size) {
    // Ensure that our size constant is RFC-compliant.
    ASSERT_EQ(255, ClientId::MAX_CLIENT_ID_LEN);

    uint8_t data[ClientId::MAX_CLIENT_ID_LEN + 1];
    vector<uint8_t> data2;
    for (uint16_t i = 0; i < ClientId::MAX_CLIENT_ID_LEN + 1; ++i) {
        data[i] = static_cast<uint8_t>(i);
        if (i < ClientId::MAX_CLIENT_ID_LEN) {
            data2.push_back(i);
        }
    }
    ASSERT_EQ(data2.size(), ClientId::MAX_CLIENT_ID_LEN);

    boost::scoped_ptr<ClientId> duidmaxsize1(new ClientId(data, ClientId::MAX_CLIENT_ID_LEN));
    boost::scoped_ptr<ClientId> duidmaxsize2(new ClientId(data2));

    EXPECT_THROW(
        boost::scoped_ptr<ClientId> toolarge1(new ClientId(data, ClientId::MAX_CLIENT_ID_LEN + 1)),
        BadValue);

    // that's one too much
    data2.push_back(0);

    EXPECT_THROW(
        boost::scoped_ptr<ClientId> toolarge2(new ClientId(data2)),
        BadValue);

    // empty client-ids are not allowed
    vector<uint8_t> empty;
    EXPECT_THROW(
        boost::scoped_ptr<ClientId> empty_client_id1(new ClientId(empty)),
        BadValue);

    EXPECT_THROW(
        boost::scoped_ptr<ClientId> empty_client_id2(new ClientId(data, 0)),
        BadValue);

    // client-id must be at least 2 bytes long
    vector<uint8_t> shorty(1,17); // just a single byte with value 17
    EXPECT_THROW(
        boost::scoped_ptr<ClientId> too_short_client_id1(new ClientId(shorty)),
        BadValue);
    EXPECT_THROW(
        boost::scoped_ptr<ClientId> too_short_client_id1(new ClientId(data, 1)),
        BadValue);
}

// This test checks if the comparison operators are sane.
TEST(ClientIdTest, operators) {
    uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
    uint8_t data2[] = {0, 1, 2, 3, 4};
    uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different
    uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1

    boost::scoped_ptr<ClientId> id1(new ClientId(data1, sizeof(data1)));
    boost::scoped_ptr<ClientId> id2(new ClientId(data2, sizeof(data2)));
    boost::scoped_ptr<ClientId> id3(new ClientId(data3, sizeof(data3)));
    boost::scoped_ptr<ClientId> id4(new ClientId(data4, sizeof(data4)));

    EXPECT_TRUE(*id1 == *id4);
    EXPECT_FALSE(*id1 == *id2);
    EXPECT_FALSE(*id1 == *id3);

    EXPECT_FALSE(*id1 != *id4);
    EXPECT_TRUE(*id1 != *id2);
    EXPECT_TRUE(*id1 != *id3);
}

// Test checks if the toText() returns valid texual representation
TEST(ClientIdTest, toText) {
    uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe};

    ClientId clientid(data1, sizeof(data1));
    EXPECT_EQ("00:01:02:03:04:ff:fe", clientid.toText());
}

// This test checks that the ClientId instance can be created from the textual
// format and that error is reported if the textual format is invalid.
TEST(ClientIdTest, fromText) {
    ClientIdPtr cid;
    // ClientId with only decimal digits.
    ASSERT_NO_THROW(
        cid = ClientId::fromText("00:01:02:03:04:05:06")
    );
    EXPECT_EQ("00:01:02:03:04:05:06", cid->toText());
    // ClientId with some hexadecimal digits (upper case and lower case).
    ASSERT_NO_THROW(
        cid = ClientId::fromText("00:aa:bb:CD:ee:EF:ab")
    );
    EXPECT_EQ("00:aa:bb:cd:ee:ef:ab", cid->toText());
    // ClientId with one digit for a particular byte.
    ASSERT_NO_THROW(
        cid = ClientId::fromText("00:a:bb:D:ee:EF:ab")
    );
    EXPECT_EQ("00:0a:bb:0d:ee:ef:ab", cid->toText());
    // ClientId without any colons is allowed.
    ASSERT_NO_THROW(
        cid = ClientId::fromText("0010abcdee");
    );
    EXPECT_EQ("00:10:ab:cd:ee", cid->toText());
    // Repeated colon sign in the ClientId is not allowed.
    EXPECT_THROW(
        ClientId::fromText("00::bb:D:ee:EF:ab"),
        isc::BadValue

    );
    // ClientId with excessive number of digits for one of the bytes.
    EXPECT_THROW(
        ClientId::fromText("00:01:021:03:04:05:06"),
        isc::BadValue
    );
    // ClientId  with two spaces between the colons should not be allowed.
    EXPECT_THROW(
        ClientId::fromText("00:01:  :03:04:05:06"),
        isc::BadValue
    );

    // ClientId  with one space between the colons should not be allowed.
    EXPECT_THROW(
        ClientId::fromText("00:01: :03:04:05:06"),
        isc::BadValue
    );

    // ClientId  with three spaces between the colons should not be allowed.
    EXPECT_THROW(
        ClientId::fromText("00:01:   :03:04:05:06"),
        isc::BadValue
    );
}


} // end of anonymous namespace
