// Copyright (C) 2012-2017 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 <iostream>
#include <sstream>
#include <arpa/inet.h>
#include <gtest/gtest.h>

#include <asiolink/io_address.h>
#include <dhcp/option.h>
#include <dhcp/dhcp6.h>

#include <boost/scoped_ptr.hpp>

#include "../localized_option.h"
#include "../perf_pkt6.h"

using namespace std;
using namespace isc;
using namespace isc::dhcp;
using namespace isc::perfdhcp;

typedef PerfPkt6::LocalizedOptionPtr LocalizedOptionPtr;

namespace {

class PerfPkt6Test : public ::testing::Test {
public:
    PerfPkt6Test() {
    }

    /// \brief Returns captured SOLICIT packet.
    ///
    /// Captured SOLICIT packet with transid=0x3d79fb and options: client-id,
    /// in_na, dns-server, elapsed-time, option-request
    /// This code was autogenerated
    /// (see src/bin/dhcp6/tests/iface_mgr_unittest.c),
    /// but we spent some time to make is less ugly than it used to be.
    ///
    /// \return pointer to Pkt6 that represents received SOLICIT
    PerfPkt6* capture() {
        uint8_t data[98];
        data[0]  = 1;
        data[1]  = 1;       data[2]  = 2;     data[3] = 3;      data[4]  = 0;
        data[5]  = 1;       data[6]  = 0;     data[7] = 14;     data[8]  = 0;
        data[9]  = 1;       data[10] = 0;     data[11] = 1;     data[12] = 21;
        data[13] = 158;     data[14] = 60;    data[15] = 22;    data[16] = 0;
        data[17] = 30;      data[18] = 140;   data[19] = 155;   data[20] = 115;
        data[21] = 73;      data[22] = 0;     data[23] = 3;     data[24] = 0;
        data[25] = 40;      data[26] = 0;     data[27] = 0;     data[28] = 0;
        data[29] = 1;       data[30] = 255;   data[31] = 255;   data[32] = 255;
        data[33] = 255;     data[34] = 255;   data[35] = 255;   data[36] = 255;
        data[37] = 255;     data[38] = 0;     data[39] = 5;     data[40] = 0;
        data[41] = 24;      data[42] = 32;    data[43] = 1;     data[44] = 13;
        data[45] = 184;     data[46] = 0;     data[47] = 1;     data[48] = 0;
        data[49] = 0;       data[50] = 0;     data[51] = 0;     data[52] = 0;
        data[53] = 0;       data[54] = 0;     data[55] = 0;     data[56] = 18;
        data[57] = 52;      data[58] = 255;   data[59] = 255;   data[60] = 255;
        data[61] = 255;     data[62] = 255;   data[63] = 255;   data[64] = 255;
        data[65] = 255;     data[66] = 0;     data[67] = 23;    data[68] = 0;
        data[69] = 16;      data[70] = 32;    data[71] = 1;     data[72] = 13;
        data[73] = 184;     data[74] = 0;     data[75] = 1;     data[76] = 0;
        data[77] = 0;       data[78] = 0;     data[79] = 0;     data[80] = 0;
        data[81] = 0;       data[82] = 0;     data[83] = 0;     data[84] = 221;
        data[85] = 221;     data[86] = 0;     data[87] = 8;     data[88] = 0;
        data[89] = 2;       data[90] = 0;     data[91] = 100;   data[92] = 0;
        data[93] = 6;       data[94] = 0;     data[95] = 2;     data[96] = 0;
        data[97] = 23;

        PerfPkt6* pkt = new PerfPkt6(data, sizeof(data));

        return (pkt);
    }

    /// \brief Returns truncated SOLICIT packet.
    ///
    /// Returns truncated SOLICIT packet which will be used for
    /// negative tests: e.g. pack options out of packet.
    ///
    /// \return pointer to Pkt6 that represents truncated SOLICIT
    PerfPkt6* captureTruncated() {
        uint8_t data[17];
        data[0]  = 1;
        data[1]  = 1;       data[2]  = 2;     data[3] = 3;      data[4]  = 0;
        data[5]  = 1;       data[6]  = 0;     data[7] = 14;     data[8]  = 0;
        data[9]  = 1;       data[10] = 0;     data[11] = 1;     data[12] = 21;
        data[13] = 158;     data[14] = 60;    data[15] = 22;    data[16] = 0;

        PerfPkt6* pkt = new PerfPkt6(data, sizeof(data));

        return (pkt);
    }


};

TEST_F(PerfPkt6Test, Constructor) {
    // Data to be used to create packet.
    uint8_t data[] = { 0, 1, 2, 3, 4, 5 };

    // Test constructor to be used for incoming messages.
    // Use default (1) offset value and don't specify transaction id.
    boost::scoped_ptr<PerfPkt6> pkt1(new PerfPkt6(data, sizeof(data)));
    EXPECT_EQ(sizeof(data), pkt1->data_.size());
    EXPECT_EQ(0, memcmp(&pkt1->data_[0], data, sizeof(data)));
    EXPECT_EQ(1, pkt1->getTransidOffset());

    // Test constructor to be used for outgoing messages.
    // Use non-zero offset and specify transaction id.
    const size_t offset_transid = 10;
    const uint32_t transid = 0x010203;
    boost::scoped_ptr<PerfPkt6> pkt2(new PerfPkt6(data, sizeof(data),
                                                  offset_transid, transid));
    EXPECT_EQ(sizeof(data), pkt2->data_.size());
    EXPECT_EQ(0, memcmp(&pkt2->data_[0], data, sizeof(data)));
    EXPECT_EQ(0x010203, pkt2->getTransid());
    EXPECT_EQ(10, pkt2->getTransidOffset());
}

TEST_F(PerfPkt6Test, RawPackUnpack) {
    // Create first packet.
    boost::scoped_ptr<PerfPkt6> pkt1(capture());

    // Create some input buffers to initialize options.
    uint8_t buf_elapsed_time[] = { 1, 1 };
    uint8_t buf_duid[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };

    // Create options.
    const size_t offset_elapsed_time = 86;
    OptionBuffer vec_elapsed_time(buf_elapsed_time,
                                  buf_elapsed_time + sizeof(buf_elapsed_time));
    LocalizedOptionPtr pkt1_elapsed_time(new LocalizedOption(Option::V6,
                                                             D6O_ELAPSED_TIME,
                                                             vec_elapsed_time,
                                                             offset_elapsed_time));
    const size_t offset_duid = 4;
    OptionBuffer vec_duid(buf_duid, buf_duid + sizeof(buf_duid));
    LocalizedOptionPtr pkt1_duid(new LocalizedOption(Option::V6,
                                                     D6O_CLIENTID,
                                                     vec_duid,
                                                     offset_duid));

    // Add option to packet and create on-wire format from added options.
    // Contents of options will override contents of packet buffer.
    ASSERT_NO_THROW(pkt1->addOption(pkt1_elapsed_time));
    ASSERT_NO_THROW(pkt1->addOption(pkt1_duid));
    ASSERT_TRUE(pkt1->rawPack());

    // Reset so as we can reuse them for another packet.
    vec_elapsed_time.clear();
    vec_duid.clear();

    // Get output buffer from packet 1 to create new packet
    // that will be later validated.
    util::OutputBuffer pkt1_output = pkt1->getBuffer();
    ASSERT_EQ(pkt1_output.getLength(), pkt1->data_.size());
    const uint8_t* pkt1_output_data = static_cast<const uint8_t*>
        (pkt1_output.getData());
    boost::scoped_ptr<PerfPkt6> pkt2(new PerfPkt6(pkt1_output_data,
                                                  pkt1_output.getLength()));

    // Create objects specifying options offset in a packet.
    // Offsets will inform pkt2 object where to read data from.
    LocalizedOptionPtr pkt2_elapsed_time(new LocalizedOption(Option::V6,
                                                             D6O_ELAPSED_TIME,
                                                             vec_elapsed_time,
                                                             offset_elapsed_time));
    LocalizedOptionPtr pkt2_duid(new LocalizedOption(Option::V6,
                                                     D6O_CLIENTID,
                                                     vec_duid,
                                                     offset_duid));
    // Add options to packet to pass their offsets.
    pkt2->addOption(pkt2_elapsed_time);
    pkt2->addOption(pkt2_duid);

    // Unpack: get relevant parts of buffer data into option objects.
    ASSERT_TRUE(pkt2->rawUnpack());

    // Once option data is stored in options objects we pull it out.
    pkt2_elapsed_time = boost::dynamic_pointer_cast<LocalizedOption>
        (pkt2->getOption(D6O_ELAPSED_TIME));
    pkt2_duid = boost::dynamic_pointer_cast<LocalizedOption>
        (pkt2->getOption(D6O_CLIENTID));

    // Check if options are present. They have to be there since
    // we have added them ourselfs.
    ASSERT_TRUE(pkt2_elapsed_time);
    ASSERT_TRUE(pkt2_duid);

    // Expecting option contents be the same as original.
    OptionBuffer pkt2_elapsed_time_data = pkt2_elapsed_time->getData();
    OptionBuffer pkt2_duid_data = pkt2_duid->getData();
    EXPECT_EQ(0x0101, pkt2_elapsed_time->getUint16());
    EXPECT_TRUE(std::equal(pkt2_duid_data.begin(),
                           pkt2_duid_data.end(),
                           buf_duid));
}

TEST_F(PerfPkt6Test, InvalidOptions) {
    // Create packet.
    boost::scoped_ptr<PerfPkt6> pkt1(capture());
    OptionBuffer vec_server_id;
    vec_server_id.resize(10);
    // Testing invalid offset of the option (greater than packet size)
    const size_t offset_serverid[] = { 150, 85 };
    LocalizedOptionPtr pkt1_serverid(new LocalizedOption(Option::V6,
                                                         D6O_SERVERID,
                                                         vec_server_id,
                                                         offset_serverid[0]));
    pkt1->addOption(pkt1_serverid);
    // Pack has to fail due to invalid offset.
    EXPECT_FALSE(pkt1->rawPack());

    // Create packet.
    boost::scoped_ptr<PerfPkt6> pkt2(capture());
    // Testing offset of the option (lower than packet size but
    // tail of the option out of bounds).
    LocalizedOptionPtr pkt2_serverid(new LocalizedOption(Option::V6,
                                                         D6O_SERVERID,
                                                         vec_server_id,
                                                         offset_serverid[1]));
    pkt2->addOption(pkt2_serverid);
    // Pack must fail due to invalid offset.
    EXPECT_FALSE(pkt2->rawPack());
}


TEST_F(PerfPkt6Test, TruncatedPacket) {
    cout << "Testing parsing options from truncated packet."
         << "This may produce spurious errors" << endl;

    // Create truncated (in the middle of duid options)
    boost::scoped_ptr<PerfPkt6> pkt1(captureTruncated());
    OptionBuffer vec_duid;
    vec_duid.resize(30);
    const size_t offset_duid = 4;
    LocalizedOptionPtr pkt1_duid(new LocalizedOption(Option::V6,
                                                     D6O_CLIENTID,
                                                     vec_duid,
                                                     offset_duid));
    pkt1->addOption(pkt1_duid);
    // Pack/unpack must fail because length of the option read from buffer
    // will extend over the actual packet length.
    EXPECT_FALSE(pkt1->rawUnpack());
    EXPECT_FALSE(pkt1->rawPack());
}

TEST_F(PerfPkt6Test, PackTransactionId) {
    uint8_t data[100];
    memset(&data, 0, sizeof(data));

    const size_t offset_transid[] = { 50, 100 };
    const uint32_t transid = 0x010203;

    // Create dummy packet that is simply filled with zeros.
    boost::scoped_ptr<PerfPkt6> pkt1(new PerfPkt6(data,
                                                  sizeof(data),
                                                  offset_transid[0],
                                                  transid));

    // Reference data are non zero so we can detect them in dummy packet.
    uint8_t ref_data[3] = { 1, 2, 3 };

    // This will store given transaction id in the packet data at
    // offset of 50.
    ASSERT_TRUE(pkt1->rawPack());

    // Get the output buffer so we can validate it.
    util::OutputBuffer out_buf = pkt1->getBuffer();
    ASSERT_EQ(sizeof(data), out_buf.getLength());
    const uint8_t *out_buf_data = static_cast<const uint8_t*>
        (out_buf.getData());

    // Try to make clang static analyzer happy.
    ASSERT_LE(offset_transid[0], out_buf.getLength());

    // Validate transaction id.
    EXPECT_EQ(0, memcmp(ref_data, out_buf_data + offset_transid[0], 3));


    // Out of bounds transaction id offset.
    boost::scoped_ptr<PerfPkt6> pkt2(new PerfPkt6(data,
                                                  sizeof(data),
                                                  offset_transid[1],
                                                  transid));
    cout << "Testing out of bounds offset. "
        "This may produce spurious errors ..." << endl;
    EXPECT_FALSE(pkt2->rawPack());
}

TEST_F(PerfPkt6Test, UnpackTransactionId) {
    // Initialize data for dummy packet (zeros only).
    uint8_t data[100] = { 0 };

    // Generate transaction id = 0x010203 and inject at offset = 50.
    for (uint8_t i = 50; i <  53; ++i) {
        data[i] = i - 49;
    }
    // Create packet and point out that transaction id is at offset 50.
    const size_t offset_transid[] = { 50, 300 };
    boost::scoped_ptr<PerfPkt6> pkt1(new PerfPkt6(data,
                                                  sizeof(data),
                                                  offset_transid[0]));

    // Get transaction id out of buffer and store in class member.
    ASSERT_TRUE(pkt1->rawUnpack());
    // Test value of transaction id.
    EXPECT_EQ(0x010203, pkt1->getTransid());

    // Out of bounds transaction id offset.
    boost::scoped_ptr<PerfPkt6> pkt2(new PerfPkt6(data,
                                                  sizeof(data),
                                                  offset_transid[1]));
    cout << "Testing out of bounds offset. "
        "This may produce spurious errors ..." << endl;
    EXPECT_FALSE(pkt2->rawUnpack());

}

}
