// Copyright (C) 2013-2024 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 <dhcp/option6_client_fqdn.h>
#include <dns/name.h>
#include <util/buffer.h>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>

namespace {

using namespace isc;
using namespace isc::dhcp;

// RAII device to make sure that lenient parsing flag is reset to false on exit.
class LenientOptionParsing {
public:
    LenientOptionParsing(bool value) {
        Option::lenient_parsing_ = value;
    }

    ~LenientOptionParsing() {
        Option::lenient_parsing_ = false;
    }
};

// This test verifies that constructor accepts empty partial domain-name but
// does not accept empty fully qualified domain name.
TEST(Option6ClientFqdnTest, constructEmptyName) {
    // Create an instance of the source option.
    boost::scoped_ptr<Option6ClientFqdn> option;
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "",
                                           Option6ClientFqdn::PARTIAL))
    );
    ASSERT_TRUE(option);
    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
    EXPECT_TRUE(option->getDomainName().empty());
    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());

    // Constructor should not accept empty fully qualified domain name.
    EXPECT_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "",
                                   Option6ClientFqdn::FULL),
                 InvalidOption6FqdnDomainName);
    // This check is similar to previous one, but using domain-name comprising
    // a single space character. This should be treated as empty domain-name.
    EXPECT_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S, " ",
                                   Option6ClientFqdn::FULL),
                 InvalidOption6FqdnDomainName);

    // Try different constructor.
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O))
    );
    ASSERT_TRUE(option);
    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
    EXPECT_TRUE(option->getDomainName().empty());
    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
}

// This test verifies that copy constructor makes a copy of the option and
// the source option instance can be deleted (both instances don't share
// any resources).
TEST(Option6ClientFqdnTest, copyConstruct) {
    // Create an instance of the source option.
    boost::scoped_ptr<Option6ClientFqdn> option;
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
                                           "myhost.example.com",
                                           Option6ClientFqdn::FULL))
    );
    ASSERT_TRUE(option);

    // Use copy constructor to create a second instance of the option.
    boost::scoped_ptr<Option6ClientFqdn> option_copy;
    ASSERT_NO_THROW(
        option_copy.reset(new Option6ClientFqdn(*option))
    );
    ASSERT_TRUE(option_copy);

    // Copy construction should result in no shared resources between
    // two objects. In particular, pointer to implementation should not
    // be shared. Thus, we can release the source object now.
    option.reset();

    // Verify that all parameters have been copied to the target object.
    EXPECT_TRUE(option_copy->getFlag(Option6ClientFqdn::FLAG_S));
    EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_O));
    EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N));
    EXPECT_EQ("myhost.example.com.", option_copy->getDomainName());
    EXPECT_EQ(Option6ClientFqdn::FULL, option_copy->getDomainNameType());

    // Do another test with different parameters to verify that parameters
    // change when copied object is changed.

    // Create an option with different parameters.
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
                                           "example",
                                           Option6ClientFqdn::PARTIAL))
    );
    ASSERT_TRUE(option);

    // Call copy-constructor to copy the option.
    ASSERT_NO_THROW(
        option_copy.reset(new Option6ClientFqdn(*option))
    );
    ASSERT_TRUE(option_copy);

    option.reset();

    EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_S));
    EXPECT_TRUE(option_copy->getFlag(Option6ClientFqdn::FLAG_O));
    EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N));
    EXPECT_EQ("example", option_copy->getDomainName());
    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option_copy->getDomainNameType());
}

// This test verifies that copy constructor makes a copy of the option, when
// domain-name is empty.
TEST(Option6ClientFqdnTest, copyConstructEmptyDomainName) {
    // Create an instance of the source option.
    boost::scoped_ptr<Option6ClientFqdn> option;
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S));
    );
    ASSERT_TRUE(option);

    // Use copy constructor to create a second instance of the option.
    boost::scoped_ptr<Option6ClientFqdn> option_copy;
    ASSERT_NO_THROW(
        option_copy.reset(new Option6ClientFqdn(*option))
    );
    ASSERT_TRUE(option_copy);

    // Copy construction should result in no shared resources between
    // two objects. In particular, pointer to implementation should not
    // be shared. Thus, we can release the source object now.
    option.reset();

    // Verify that all parameters have been copied to the target object.
    EXPECT_TRUE(option_copy->getFlag(Option6ClientFqdn::FLAG_S));
    EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_O));
    EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N));
    EXPECT_EQ("", option_copy->getDomainName());
    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option_copy->getDomainNameType());
}

// This test verifies that the option in the on-wire format is parsed correctly.
TEST(Option6ClientFqdnTest, constructFromWire) {
    const uint8_t in_data[] = {
        Option6ClientFqdn::FLAG_S,           // flags
        6, 77, 121, 104, 111, 115, 116,      // Myhost.
        7, 69, 120, 97, 109, 112, 108, 101,  // Example.
        3, 67, 111, 109, 0                   // Com.
    };
    size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
    OptionBuffer in_buf(in_data, in_data + in_data_size);

    // Create option instance. Check that constructor doesn't throw.
    boost::scoped_ptr<Option6ClientFqdn> option;
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end()))
    );
    ASSERT_TRUE(option);

    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
    EXPECT_EQ("myhost.example.com.", option->getDomainName());
    EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
}

// Verify that exception is thrown if the domain-name label is
// longer than 63.
TEST(Option6ClientFqdnTest, constructFromWireTooLongLabel) {
    OptionBuffer in_buf(Option6ClientFqdn::FLAG_S);
    in_buf.push_back(70);
    in_buf.insert(in_buf.end(), 70, 109);
    // Don't add a 0 i.e. use a partial name (vs fully qualified).

    LenientOptionParsing lop(false);
    EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
                 InvalidOption6FqdnDomainName);
    Option::lenient_parsing_ = true;
    EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
                 SkipThisOptionError);
}

// Verify that exception is thrown if the overall length of the domain-name
// is over 255.
TEST(Option6ClientFqdnTest, constructFromWireTooLongDomainName) {
    OptionBuffer in_buf(Option6ClientFqdn::FLAG_S);
    // Construct the FQDN from 26 labels, each having a size of 10.
    for (int i = 0; i < 26;  ++i) {
        // Append the length of each label.
        in_buf.push_back(10);
        // Append the actual label.
        in_buf.insert(in_buf.end(), 10, 109);
    }
    // Terminate FQDN with a dot.
    in_buf.push_back(0);

    LenientOptionParsing lop(false);
    EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
                 InvalidOption6FqdnDomainName);
    Option::lenient_parsing_ = true;
    EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
                 SkipThisOptionError);
}

// This test verifies that truncated option is rejected.
TEST(Option6ClientFqdnTest, constructFromWireTruncated) {
    // Empty buffer is invalid. It should be at least one octet long.
    OptionBuffer in_buf;
    ASSERT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
                 OutOfRange);
}

// This test verifies that the option in the on-wire format with partial
// domain-name is parsed correctly.
TEST(Option6ClientFqdnTest, constructFromWirePartial) {
    const uint8_t in_data[] = {
        Option6ClientFqdn::FLAG_N,           // flags
        6, 77, 121, 104, 111, 115, 116       // Myhost
    };
    size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
    OptionBuffer in_buf(in_data, in_data + in_data_size);

    // Create option instance. Check that constructor doesn't throw.
    boost::scoped_ptr<Option6ClientFqdn> option;
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end()))
    );
    ASSERT_TRUE(option);

    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_N));
    EXPECT_EQ("myhost", option->getDomainName());
    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
}

// This test verifies that the option in the on-wire format with empty
// domain-name is parsed correctly.
TEST(Option6ClientFqdnTest, constructFromWireEmpty) {
    OptionBuffer in_buf(Option6ClientFqdn::FLAG_S);
    // Create option instance. Check that constructor doesn't throw.
    boost::scoped_ptr<Option6ClientFqdn> option;
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end()))
    );
    ASSERT_TRUE(option);

    // domain-name field should be empty because on-wire data comprised
    // flags field only.
    EXPECT_TRUE(option->getDomainName().empty());
}

// This test verifies that assignment operator can be used to assign one
// instance of the option to another.
TEST(Option6ClientFqdnTest, assignment) {
    // Usually the smart pointer is used to declare options and call
    // constructor within assert. Thanks to this approach, the option instance
    // is in the function scope and only initialization is done within assert.
    // In this particular test we can't use smart pointers because we are
    // testing assignment operator like this:
    //
    //          option2 = option;
    //
    // The two asserts below do not create the instances that we will used to
    // test assignment. They just attempt to create instances of the options
    // with the same parameters as those that will be created for the actual
    // assignment test. If these asserts do not fail, we can create options
    // for the assignment test, do not surround them with asserts and be sure
    // they will not throw.
    ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
                                      "myhost.example.com",
                                      Option6ClientFqdn::FULL));

    ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_N,
                                      "myhost",
                                      Option6ClientFqdn::PARTIAL));

    // Create options with the same parameters as tested above.

    // Create first option.
    Option6ClientFqdn option(Option6ClientFqdn::FLAG_S,
                             "Myhost.Example.Com",
                             Option6ClientFqdn::FULL);

    // Verify that the values have been set correctly.
    ASSERT_TRUE(option.getFlag(Option6ClientFqdn::FLAG_S));
    ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_O));
    ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_N));
    ASSERT_EQ("myhost.example.com.", option.getDomainName());
    ASSERT_EQ(Option6ClientFqdn::FULL, option.getDomainNameType());

    // Create a second option.
    Option6ClientFqdn option2(Option6ClientFqdn::FLAG_N,
                              "myhost",
                              Option6ClientFqdn::PARTIAL);

    // Verify tha the values have been set correctly.
    ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_S));
    ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O));
    ASSERT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_N));
    ASSERT_EQ("myhost", option2.getDomainName());
    ASSERT_EQ(Option6ClientFqdn::PARTIAL, option2.getDomainNameType());


    // Make the assignment.
    ASSERT_NO_THROW(option2 = option);

    // Both options should now have the same values.
    EXPECT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_S));
    EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O));
    EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_N));
    EXPECT_EQ(option.getDomainName(), option2.getDomainName());
    EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType());
}

// This test verifies that assignment operator can be used to assign one
// instance of the option to another, when the domain-name is empty.
TEST(Option6ClientFqdnTest, assignmentEmptyDomainName) {
    ASSERT_NO_THROW(
        Option6ClientFqdn(static_cast<uint8_t>(Option6ClientFqdn::FLAG_S))
    );

    ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_N,
                                      "myhost",
                                      Option6ClientFqdn::PARTIAL));

    // Create options with the same parameters as tested above.

    // Create first option.
    Option6ClientFqdn option(Option6ClientFqdn::FLAG_S);

    // Verify that the values have been set correctly.
    ASSERT_TRUE(option.getFlag(Option6ClientFqdn::FLAG_S));
    ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_O));
    ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_N));
    ASSERT_EQ("", option.getDomainName());
    ASSERT_EQ(Option6ClientFqdn::PARTIAL, option.getDomainNameType());

    // Create a second option.
    Option6ClientFqdn option2(Option6ClientFqdn::FLAG_N,
                              "myhost",
                              Option6ClientFqdn::PARTIAL);

    // Verify that the values have been set correctly.
    ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_S));
    ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O));
    ASSERT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_N));
    ASSERT_EQ("myhost", option2.getDomainName());
    ASSERT_EQ(Option6ClientFqdn::PARTIAL, option2.getDomainNameType());


    // Make the assignment.
    ASSERT_NO_THROW(option2 = option);

    // Both options should now have the same values.
    EXPECT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_S));
    EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O));
    EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_N));
    EXPECT_EQ("", option2.getDomainName());
    EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType());
}


// This test verifies that constructor will throw an exception if invalid
// DHCPv6 Client FQDN Option flags are specified.
TEST(Option6ClientFqdnTest, constructInvalidFlags) {
    // First, check that constructor does not throw an exception when
    // valid flags values are provided. That way we eliminate the issue
    // that constructor always throws exception.
    uint8_t flags = 0;
    ASSERT_NO_THROW(Option6ClientFqdn(flags, "myhost.example.com"));

    // Invalid flags: The maximal value is 0x7 when all flag bits are set
    // (00000111b). The flag value of 0x14 sets the bit from the Must Be
    // Zero (MBZ) bitset (00001100b).
    flags = 0x14;
    EXPECT_THROW(Option6ClientFqdn(flags, "myhost.example.com"),
                 InvalidOption6FqdnFlags);

    // According to RFC 4704, section 4.1. if the N bit is set the S bit MUST
    // be zero. If both are set, constructor is expected to throw.
    flags = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S;
    EXPECT_THROW(Option6ClientFqdn(flags, "myhost.example.com"),
                 InvalidOption6FqdnFlags);
}

// This test verifies that constructor which parses option from on-wire format
// will throw exception if parsed flags field is invalid.
TEST(Option6ClientFqdnTest, constructFromWireInvalidFlags) {
    // Create a buffer which holds flags field only. Set valid flag field at
    // at first to make sure that constructor doesn't always throw an exception.
    OptionBuffer in_buf(Option6ClientFqdn::FLAG_N);
    ASSERT_NO_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()));

    // Replace the flags with invalid value and verify that constructor throws
    // appropriate exception.
    in_buf[0] = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S;
    EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
                 InvalidOption6FqdnFlags);
}

// This test verifies that if invalid domain name is used the constructor
// will throw appropriate exception.
TEST(Option6ClientFqdnTest, constructInvalidName) {
    // First, check that constructor does not throw when valid domain name
    // is specified. That way we eliminate the possibility that constructor
    // always throws exception.
    ASSERT_NO_THROW(Option6ClientFqdn(0, "myhost.example.com"));

    // Specify invalid domain name and expect that exception is thrown.
    // Note in v6 the domain name is always encoded so this is not
    // covered by lenient-option-parsing.
    EXPECT_THROW(Option6ClientFqdn(0, "my...host.example.com"),
                 InvalidOption6FqdnDomainName);
}

// This test verifies that getFlag throws an exception if flag value other
// than FLAG_N, FLAG_S, FLAG_O is specified.
TEST(Option6ClientFqdnTest, getFlag) {
    boost::scoped_ptr<Option6ClientFqdn> option;
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(0, "myhost.example.com"))
    );
    ASSERT_TRUE(option);

    // The 0x3 (binary 011) specifies two distinct bits in the flags field.
    // This value is ambiguous for getFlag function and this function doesn't
    // know which flag the caller is attempting to check.
    EXPECT_THROW(option->getFlag(0x3), InvalidOption6FqdnFlags);
}

// This test verifies that flags can be modified and that incorrect flags
// are rejected.
TEST(Option6ClientFqdnTest, setFlag) {
    // Create option instance. Check that constructor doesn't throw.
    boost::scoped_ptr<Option6ClientFqdn> option;
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(0, "myhost.example.com"))
    );
    ASSERT_TRUE(option);

    // All flags should be set to 0 initially.
    ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
    ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
    ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));

    // Set N = 1
    ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, true));
    ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_N));

    // Set O = 1
    ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_O, true));
    ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));

    // Set S = 1, this should throw exception because S and N must not
    // be set in the same time.
    ASSERT_THROW(option->setFlag(Option6ClientFqdn::FLAG_S, true),
                 InvalidOption6FqdnFlags);

    // Set N = 0
    ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, false));
    ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));

    // Set S = 1, this should not result in exception because N has been
    // cleared.
    ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_S, true));
    ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));

    // Set N = 1, this should result in exception because S = 1
    ASSERT_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, true),
                 InvalidOption6FqdnFlags);

    // Set O = 0
    ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_O, false));
    ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));

    // Try out of bounds settings.
    uint8_t flags = 0;
    ASSERT_THROW(option->setFlag(flags, true), InvalidOption6FqdnFlags);

    flags = 0x14;
    ASSERT_THROW(option->setFlag(flags, true), InvalidOption6FqdnFlags);
}

// This test verifies that flags field of the option is set to 0 when resetFlags
// function is called.
TEST(Option6ClientFqdnTest, resetFlags) {
    boost::scoped_ptr<Option6ClientFqdn> option;
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S |
                                           Option6ClientFqdn::FLAG_O,
                                           "myhost.example.com",
                                           Option6ClientFqdn::FULL))
    );
    ASSERT_TRUE(option);

    // Check that flags we set in the constructor are set.
    ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
    ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
    ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));

    option->resetFlags();

    // After reset, all flags should be 0.
    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
}

// This test verifies that current domain-name can be replaced with a new
// domain-name.
TEST(Option6ClientFqdnTest, setDomainName) {
    boost::scoped_ptr<Option6ClientFqdn> option;
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
                                           "myhost.example.com",
                                           Option6ClientFqdn::FULL))
    );
    ASSERT_TRUE(option);
    ASSERT_EQ("myhost.example.com.", option->getDomainName());
    ASSERT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());

    // Partial domain-name.
    ASSERT_NO_THROW(option->setDomainName("Myhost",
                                          Option6ClientFqdn::PARTIAL));
    EXPECT_EQ("myhost", option->getDomainName());
    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());

    // Fully qualified domain-name.
    ASSERT_NO_THROW(option->setDomainName("Example.com",
                                          Option6ClientFqdn::FULL));
    EXPECT_EQ("example.com.", option->getDomainName());
    EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());

    // Empty domain name (partial). This should be successful.
    ASSERT_NO_THROW(option->setDomainName("", Option6ClientFqdn::PARTIAL));
    EXPECT_TRUE(option->getDomainName().empty());
    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());

    // Fully qualified domain-names must not be empty.
    EXPECT_THROW(option->setDomainName("", Option6ClientFqdn::FULL),
                 InvalidOption6FqdnDomainName);
    EXPECT_THROW(option->setDomainName(" ", Option6ClientFqdn::FULL),
                 InvalidOption6FqdnDomainName);
}

// This test verifies that current domain-name can be reset to empty one.
TEST(Option6ClientFqdnTest, resetDomainName) {
    boost::scoped_ptr<Option6ClientFqdn> option;
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
                                           "myhost.example.com",
                                           Option6ClientFqdn::FULL))
    );
    ASSERT_TRUE(option);
    ASSERT_EQ("myhost.example.com.", option->getDomainName());
    ASSERT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());

    // Set the domain-name to empty one.
    ASSERT_NO_THROW(option->resetDomainName());
    EXPECT_TRUE(option->getDomainName().empty());
}

// This test verifies on-wire format of the option is correctly created.
TEST(Option6ClientFqdnTest, pack) {
    // Create option instance. Check that constructor doesn't throw.
    const uint8_t flags = Option6ClientFqdn::FLAG_S;
    boost::scoped_ptr<Option6ClientFqdn> option;
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(flags, "myhost.example.com"))
    );
    ASSERT_TRUE(option);

    // Prepare on-wire format of the option.
    isc::util::OutputBuffer buf(10);
    ASSERT_NO_THROW(option->pack(buf));

    // Prepare reference data.
    const uint8_t ref_data[] = {
        0, 39, 0, 21,                        // header
        Option6ClientFqdn::FLAG_S,           // flags
        6, 109, 121, 104, 111, 115, 116,     // myhost.
        7, 101, 120, 97, 109, 112, 108, 101, // example.
        3, 99, 111, 109, 0                   // com.
    };
    size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);

    // Check if the buffer has the same length as the reference data,
    // so as they can be compared directly.
    ASSERT_EQ(ref_data_size, buf.getLength());
    EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
}

// This test verifies on-wire format of the option with partial domain name
// is correctly created.
TEST(Option6ClientFqdnTest, packPartial) {
    // Create option instance. Check that constructor doesn't throw.
    const uint8_t flags = Option6ClientFqdn::FLAG_S;
    boost::scoped_ptr<Option6ClientFqdn> option;
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(flags, "myhost",
                                           Option6ClientFqdn::PARTIAL))
    );
    ASSERT_TRUE(option);

    // Prepare on-wire format of the option.
    isc::util::OutputBuffer buf(10);
    ASSERT_NO_THROW(option->pack(buf));

    // Prepare reference data.
    const uint8_t ref_data[] = {
        0, 39, 0, 8,                         // header
        Option6ClientFqdn::FLAG_S,           // flags
        6, 109, 121, 104, 111, 115, 116      // myhost
    };
    size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);

    // Check if the buffer has the same length as the reference data,
    // so as they can be compared directly.
    ASSERT_EQ(ref_data_size, buf.getLength());
    EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
}

// This test verifies that it is possible to encode the option which carries
// empty domain-name in the wire format.
TEST(Option6ClientFqdnTest, packEmpty) {
    // Create option instance. Check that constructor doesn't throw.
    const uint8_t flags = Option6ClientFqdn::FLAG_S;
    boost::scoped_ptr<Option6ClientFqdn> option;
    ASSERT_NO_THROW(
                    option.reset(new Option6ClientFqdn(flags))
    );
    ASSERT_TRUE(option);

    // Prepare on-wire format of the option.
    isc::util::OutputBuffer buf(5);
    ASSERT_NO_THROW(option->pack(buf));

    // Prepare reference data.
    const uint8_t ref_data[] = {
        0, 39, 0, 1,                         // header
        Option6ClientFqdn::FLAG_S            // flags
    };
    size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);

    // Check if the buffer has the same length as the reference data,
    // so as they can be compared directly.
    ASSERT_EQ(ref_data_size, buf.getLength());
    EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
}

// This test verifies that on-wire option data holding fully qualified domain
// name is parsed correctly.
TEST(Option6ClientFqdnTest, unpack) {
    // Create option instance. Check that constructor doesn't throw.
    boost::scoped_ptr<Option6ClientFqdn> option;
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
                                           "myhost",
                                           Option6ClientFqdn::PARTIAL))
    );
    ASSERT_TRUE(option);
    // Make sure that the parameters have been set correctly. Later in this
    // test we will check that they will be replaced with new values when
    // unpack is called.
    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
    EXPECT_EQ("myhost", option->getDomainName());
    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());

    const uint8_t in_data[] = {
        Option6ClientFqdn::FLAG_S,           // flags
        6, 77, 121, 104, 111, 115, 116,      // Myhost.
        7, 69, 120, 97, 109, 112, 108, 101,  // Example.
        3, 99, 111, 109, 0                   // com.
    };
    size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
    OptionBuffer in_buf(in_data, in_data + in_data_size);

    // Initialize new values from the on-wire format.
    ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end()));

    // Check that new values are correct.
    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
    EXPECT_EQ("myhost.example.com.", option->getDomainName());
    EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
}

// This test verifies that on-wire option data holding partial domain name
// is parsed correctly.
TEST(Option6ClientFqdnTest, unpackPartial) {
    // Create option instance. Check that constructor doesn't throw.
    boost::scoped_ptr<Option6ClientFqdn> option;
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
                                           "myhost.example.com"))
    );
    ASSERT_TRUE(option);
    // Make sure that the parameters have been set correctly. Later in this
    // test we will check that they will be replaced with new values when
    // unpack is called.
    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
    EXPECT_EQ("myhost.example.com.", option->getDomainName());
    EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());

    const uint8_t in_data[] = {
        Option6ClientFqdn::FLAG_S,           // flags
        6, 77, 121, 104, 111, 115, 116       // Myhost
    };
    size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
    OptionBuffer in_buf(in_data, in_data + in_data_size);

    // Initialize new values from the on-wire format.
    ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end()));

    // Check that new values are correct.
    EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
    EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
    EXPECT_EQ("myhost", option->getDomainName());
    EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
}

// This test verifies that the empty buffer is rejected when decoding an option
// from on-wire format.
TEST(Option6ClientFqdnTest, unpackTruncated) {
    boost::scoped_ptr<Option6ClientFqdn> option;
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O))
    );
    ASSERT_TRUE(option);

    // Empty buffer is invalid. It should be at least 1 octet long.
    OptionBuffer in_buf;
    EXPECT_THROW(option->unpack(in_buf.begin(), in_buf.end()), OutOfRange);
}

// This test verifies that string representation of the option returned by
// toText method is correctly formatted.
TEST(Option6ClientFqdnTest, toText) {
    // Create option instance. Check that constructor doesn't throw.
    uint8_t flags = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_O;
    boost::scoped_ptr<Option6ClientFqdn> option;
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(flags,
                                           "myhost.example.com"))
    );
    ASSERT_TRUE(option);

    // The base indentation of the option will be set to 2. It should appear
    // as follows.
    std::string ref_string =
        "  type=39(CLIENT_FQDN), flags: (N=1, O=1, S=0), "
        "domain-name='myhost.example.com.' (full)";
    const int indent = 2;
    EXPECT_EQ(ref_string, option->toText(indent));

    // Create another option with different parameters:
    // - flags set to 0
    // - domain-name is now partial, not fully qualified
    // Also, remove base indentation.
    flags = 0;
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(flags, "myhost",
                                           Option6ClientFqdn::PARTIAL))
    );
    ref_string =
        "type=39(CLIENT_FQDN), flags: (N=0, O=0, S=0), "
        "domain-name='myhost' (partial)";
    EXPECT_EQ(ref_string, option->toText());
}

// This test verifies that the correct length of the option in on-wire
// format is returned.
TEST(Option6ClientFqdnTest, len) {
    // Create option instance. Check that constructor doesn't throw.
    boost::scoped_ptr<Option6ClientFqdn> option;
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(0, "myhost.example.com"))
    );
    ASSERT_TRUE(option);
    // This option comprises a header (4 octets), flag field (1 octet),
    // and wire representation of the domain name (length equal to the
    // length of the string representation of the domain name + 1).
    EXPECT_EQ(25, option->len());

    // Use different domain name to check if the length also changes
    // as expected.
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(0, "example.com"))
    );
    ASSERT_TRUE(option);
    EXPECT_EQ(18, option->len());

    // Let's check that the size will change when domain name of a different
    // size is used.
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(0, "example.com"))
    );
    ASSERT_TRUE(option);
    EXPECT_EQ(18, option->len());

    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(0, "myhost",
                                           Option6ClientFqdn::PARTIAL))
    );
    ASSERT_TRUE(option);
    EXPECT_EQ(12, option->len());

    // Another test for partial domain name but this time using
    // two labels.
    ASSERT_NO_THROW(
        option.reset(new Option6ClientFqdn(0, "myhost.example",
                                           Option6ClientFqdn::PARTIAL))
    );
    ASSERT_TRUE(option);
    EXPECT_EQ(20, option->len());

}

} // anonymous namespace
