summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcpsrv/tests/host_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/dhcpsrv/tests/host_unittest.cc')
-rw-r--r--src/lib/dhcpsrv/tests/host_unittest.cc1310
1 files changed, 1310 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/tests/host_unittest.cc b/src/lib/dhcpsrv/tests/host_unittest.cc
new file mode 100644
index 0000000..5bafd8b
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/host_unittest.cc
@@ -0,0 +1,1310 @@
+// Copyright (C) 2014-2021 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 <dhcpsrv/host.h>
+#include <dhcp/option_space.h>
+#include <util/encode/hex.h>
+#include <util/range_utilities.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+#include <cstdlib>
+#include <unordered_set>
+#include <sstream>
+#include <boost/functional/hash.hpp>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::data;
+
+namespace {
+
+/// @brief Holds a type of the last identifier in @c IdentifierType enum.
+///
+/// This value must be updated when new identifiers are added to the enum.
+const Host::IdentifierType LAST_IDENTIFIER_TYPE = Host::IDENT_CLIENT_ID;
+
+// This test verifies that it is possible to create IPv6 address
+// reservation.
+TEST(IPv6ResrvTest, constructorAddress) {
+ IPv6Resrv resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::cafe"));
+ EXPECT_EQ("2001:db8:1::cafe", resrv.getPrefix().toText());
+ EXPECT_EQ(128, resrv.getPrefixLen());
+ EXPECT_EQ(IPv6Resrv::TYPE_NA, resrv.getType());
+}
+
+// This test verifies that it is possible to create IPv6 prefix
+// reservation.
+TEST(IPv6ResrvTest, constructorPrefix) {
+ IPv6Resrv resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"), 64);
+ EXPECT_EQ("2001:db8:1::", resrv.getPrefix().toText());
+ EXPECT_EQ(64, resrv.getPrefixLen());
+ EXPECT_EQ(IPv6Resrv::TYPE_PD, resrv.getType());
+}
+
+// This test verifies that the toText() function prints correctly.
+TEST(IPv6ResrvTest, toText) {
+ IPv6Resrv resrv_prefix(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"), 64);
+ EXPECT_EQ("2001:db8:1::/64", resrv_prefix.toText());
+
+ IPv6Resrv resrv_address(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:111::23"));
+ EXPECT_EQ("2001:db8:111::23", resrv_address.toText());
+}
+
+// This test verifies that invalid prefix is rejected.
+TEST(IPv6ResrvTest, constructorInvalidPrefix) {
+ // IPv4 address is invalid for IPv6 reservation.
+ EXPECT_THROW(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("10.0.0.1"), 128),
+ isc::BadValue);
+ // Multicast address is invalid for IPv6 reservation.
+ EXPECT_THROW(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("ff02:1::2"), 128),
+ isc::BadValue);
+}
+
+// This test verifies that invalid prefix length is rejected.
+TEST(IPv6ResrvTest, constructiorInvalidPrefixLength) {
+ ASSERT_NO_THROW(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"),
+ 128));
+ EXPECT_THROW(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"), 129),
+ isc::BadValue);
+ EXPECT_THROW(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"), 244),
+ isc::BadValue);
+ EXPECT_THROW(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::"), 64),
+ isc::BadValue);
+}
+
+// This test verifies that it is possible to modify prefix and its
+// length in an existing reservation.
+TEST(IPv6ResrvTest, setPrefix) {
+ // Create a reservation using an address and prefix length 128.
+ IPv6Resrv resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1"));
+ ASSERT_EQ("2001:db8:1::1", resrv.getPrefix().toText());
+ ASSERT_EQ(128, resrv.getPrefixLen());
+ ASSERT_EQ(IPv6Resrv::TYPE_NA, resrv.getType());
+
+ // Modify the reservation to use a prefix having a length of 48.
+ ASSERT_NO_THROW(resrv.set(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 48));
+ EXPECT_EQ("2001:db8::", resrv.getPrefix().toText());
+ EXPECT_EQ(48, resrv.getPrefixLen());
+ EXPECT_EQ(IPv6Resrv::TYPE_PD, resrv.getType());
+
+ // IPv4 address is invalid for IPv6 reservation.
+ EXPECT_THROW(resrv.set(IPv6Resrv::TYPE_NA, IOAddress("10.0.0.1"), 128),
+ isc::BadValue);
+ // IPv6 multicast address is invalid for IPv6 reservation.
+ EXPECT_THROW(resrv.set(IPv6Resrv::TYPE_NA, IOAddress("ff02::1:2"), 128),
+ isc::BadValue);
+ // Prefix length greater than 128 is invalid.
+ EXPECT_THROW(resrv.set(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"), 129),
+ isc::BadValue);
+}
+
+// This test checks that the equality operators work fine.
+TEST(IPv6ResrvTest, equal) {
+ EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64) ==
+ IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64));
+
+ EXPECT_FALSE(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64) !=
+ IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64));
+
+ EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")) ==
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")));
+ EXPECT_FALSE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")) !=
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")));
+
+ EXPECT_FALSE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")) ==
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::2")));
+ EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")) !=
+ IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::2")));
+
+ EXPECT_FALSE(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64) ==
+ IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 48));
+ EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64) !=
+ IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 48));
+
+ EXPECT_FALSE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1"), 128) ==
+ IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::1"), 128));
+ EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1"), 128) !=
+ IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::1"), 128));
+}
+
+/// @brief Test fixture class for @c Host.
+class HostTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Re-initializes random number generator.
+ HostTest() {
+ srand(1);
+ }
+
+ /// @brief Checks if the reservation is in the range of reservations.
+ ///
+ /// @param resrv Reservation to be searched for.
+ /// @param range Range of reservations returned by the @c Host object
+ /// in which the reservation will be searched.
+ ///
+ /// @return true if reservation exists, false otherwise.
+ bool
+ reservationExists(const IPv6Resrv& resrv, const IPv6ResrvRange& range) {
+ for (IPv6ResrvIterator it = range.first; it != range.second;
+ ++it) {
+ if (resrv == it->second) {
+ return (true);
+ }
+ }
+ return (false);
+ }
+
+ /// @brief Returns upper bound of the supported identifier types.
+ ///
+ /// Some unit tests verify the @c Host class behavior for all
+ /// supported identifier types. The unit test needs to iterate
+ /// over all supported identifier types and thus it must be
+ /// aware of the upper bound of the @c Host::IdentifierType
+ /// enum. The upper bound is the numeric representation of the
+ /// last identifier type plus 1.
+ unsigned int
+ identifierTypeUpperBound() const {
+ return (static_cast<unsigned int>(LAST_IDENTIFIER_TYPE) + 1);
+ }
+};
+
+// This test verifies that correct identifier name is returned for
+// a given identifier name and that an error is reported for an
+// unsupported identifier name.
+TEST_F(HostTest, getIdentifier) {
+ EXPECT_EQ(Host::IDENT_HWADDR, Host::getIdentifierType("hw-address"));
+ EXPECT_EQ(Host::IDENT_DUID, Host::getIdentifierType("duid"));
+ EXPECT_EQ(Host::IDENT_CIRCUIT_ID, Host::getIdentifierType("circuit-id"));
+ EXPECT_EQ(Host::IDENT_CLIENT_ID, Host::getIdentifierType("client-id"));
+ EXPECT_EQ(Host::IDENT_FLEX, Host::getIdentifierType("flex-id"));
+
+ EXPECT_THROW(Host::getIdentifierType("unsupported"), isc::BadValue);
+}
+
+// This test verifies that it is possible to create a Host object
+// using hardware address in the textual format.
+TEST_F(HostTest, createFromHWAddrString) {
+ HostPtr host;
+ ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"),
+ "somehost.example.org",
+ std::string(), std::string(),
+ IOAddress("192.0.0.2"),
+ "server-hostname.example.org",
+ "bootfile.efi", AuthKey("12345678"))));
+ // The HW address should be set to non-null.
+ HWAddrPtr hwaddr = host->getHWAddress();
+ ASSERT_TRUE(hwaddr);
+
+ EXPECT_EQ("hwtype=1 01:02:03:04:05:06", hwaddr->toText());
+
+ // DUID should be null if hardware address is in use.
+ EXPECT_FALSE(host->getDuid());
+ EXPECT_EQ(1, host->getIPv4SubnetID());
+ EXPECT_EQ(2, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.3", host->getIPv4Reservation().toText());
+ EXPECT_EQ("somehost.example.org", host->getHostname());
+ EXPECT_EQ("192.0.0.2", host->getNextServer().toText());
+ EXPECT_EQ("server-hostname.example.org", host->getServerHostname());
+ EXPECT_EQ("bootfile.efi", host->getBootFileName());
+ EXPECT_EQ("12345678", host->getKey().toText());
+ EXPECT_FALSE(host->getContext());
+
+ // Use invalid identifier name
+ EXPECT_THROW(Host("01:02:03:04:05:06", "bogus", SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"), "somehost.example.org"),
+ isc::BadValue);
+
+ // Use invalid HW address.
+ EXPECT_THROW(Host("01:0203040506", "hw-address", SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"), "somehost.example.org"),
+ isc::BadValue);
+}
+
+// This test verifies that it is possible to create Host object using
+// a DUID in the textual format.
+TEST_F(HostTest, createFromDUIDString) {
+ HostPtr host;
+ ASSERT_NO_THROW(host.reset(new Host("a1:b2:c3:d4:e5:06", "duid",
+ SubnetID(10), SubnetID(20),
+ IOAddress("192.0.2.5"),
+ "me.example.org")));
+
+ // DUID should be set to non-null value.
+ DuidPtr duid = host->getDuid();
+ ASSERT_TRUE(duid);
+
+ EXPECT_EQ("a1:b2:c3:d4:e5:06", duid->toText());
+
+ // Hardware address must be null if DUID is in use.
+ EXPECT_FALSE(host->getHWAddress());
+
+ EXPECT_EQ(10, host->getIPv4SubnetID());
+ EXPECT_EQ(20, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_EQ("me.example.org", host->getHostname());
+ EXPECT_FALSE(host->getContext());
+
+ // Use invalid DUID.
+ EXPECT_THROW(Host("bogus", "duid", SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"), "somehost.example.org"),
+ isc::BadValue);
+
+ // Empty DUID is also not allowed.
+ EXPECT_THROW(Host("", "duid", SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"), "somehost.example.org"),
+ isc::BadValue);
+}
+
+// This test verifies that it is possible to create Host object using
+// hardware address in the binary format.
+TEST_F(HostTest, createFromHWAddrBinary) {
+ HostPtr host;
+ // Prepare the hardware address in binary format.
+ const uint8_t hwaddr_data[] = {
+ 0xaa, 0xab, 0xca, 0xda, 0xbb, 0xee
+ };
+ ASSERT_NO_THROW(host.reset(new Host(hwaddr_data,
+ sizeof(hwaddr_data),
+ Host::IDENT_HWADDR,
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"),
+ "somehost.example.org",
+ std::string(), std::string(),
+ IOAddress("192.0.0.2"),
+ "server-hostname.example.org",
+ "bootfile.efi", AuthKey("0abc1234"))));
+
+ // Hardware address should be non-null.
+ HWAddrPtr hwaddr = host->getHWAddress();
+ ASSERT_TRUE(hwaddr);
+
+ EXPECT_EQ("hwtype=1 aa:ab:ca:da:bb:ee", hwaddr->toText());
+
+ // DUID should be null if hardware address is in use.
+ EXPECT_FALSE(host->getDuid());
+ EXPECT_EQ(1, host->getIPv4SubnetID());
+ EXPECT_EQ(2, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.3", host->getIPv4Reservation().toText());
+ EXPECT_EQ("somehost.example.org", host->getHostname());
+ EXPECT_EQ("192.0.0.2", host->getNextServer().toText());
+ EXPECT_EQ("server-hostname.example.org", host->getServerHostname());
+ EXPECT_EQ("bootfile.efi", host->getBootFileName());
+ EXPECT_EQ("0ABC1234", host->getKey().toText());
+ EXPECT_FALSE(host->getContext());
+}
+
+// This test verifies that it is possible to create a Host object using
+// DUID in the binary format.
+TEST_F(HostTest, createFromDuidBinary) {
+ HostPtr host;
+ // Prepare DUID binary.
+ const uint8_t duid_data[] = {
+ 1, 2, 3, 4, 5, 6
+ };
+ ASSERT_NO_THROW(host.reset(new Host(duid_data,
+ sizeof(duid_data),
+ Host::IDENT_DUID,
+ SubnetID(10), SubnetID(20),
+ IOAddress("192.0.2.5"),
+ "me.example.org")));
+ // DUID should be non null.
+ DuidPtr duid = host->getDuid();
+ ASSERT_TRUE(duid);
+
+ EXPECT_EQ("01:02:03:04:05:06", duid->toText());
+
+ // Hardware address should be null if DUID is in use.
+ EXPECT_FALSE(host->getHWAddress());
+ EXPECT_EQ(10, host->getIPv4SubnetID());
+ EXPECT_EQ(20, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_EQ("me.example.org", host->getHostname());
+ EXPECT_FALSE(host->getContext());
+}
+
+// This test verifies that it is possible create Host instance using all
+// supported identifiers in a binary format.
+TEST_F(HostTest, createFromIdentifierBinary) {
+ HostPtr host;
+ // Iterate over all supported identifier types.
+ for (unsigned int i = 0; i < identifierTypeUpperBound(); ++i) {
+ const Host::IdentifierType type = static_cast<Host::IdentifierType>(i);
+ // Create identifier of variable length and fill with random values.
+ std::vector<uint8_t> identifier(random() % 14 + 6);
+ util::fillRandom(identifier.begin(), identifier.end());
+
+ // Try to create a Host instance using this identifier.
+ ASSERT_NO_THROW(host.reset(new Host(&identifier[0], identifier.size(),
+ type, SubnetID(10), SubnetID(20),
+ IOAddress("192.0.2.5"),
+ "me.example.org")));
+
+ // Retrieve identifier from Host instance and check if it is correct.
+ const std::vector<uint8_t>& identifier_returned = host->getIdentifier();
+ EXPECT_TRUE(identifier_returned == identifier);
+ EXPECT_EQ(type, host->getIdentifierType());
+
+ EXPECT_EQ(10, host->getIPv4SubnetID());
+ EXPECT_EQ(20, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_EQ("me.example.org", host->getHostname());
+ EXPECT_FALSE(host->getContext());
+ }
+}
+
+// This test verifies that it is possible to create Host instance using
+// all supported identifiers in hexadecimal format.
+TEST_F(HostTest, createFromIdentifierHex) {
+ HostPtr host;
+ // Iterate over all supported identifiers.
+ for (unsigned int i = 0; i < identifierTypeUpperBound(); ++i) {
+ const Host::IdentifierType type = static_cast<Host::IdentifierType>(i);
+ // Create identifier of a variable length.
+ std::vector<uint8_t> identifier(random() % 14 + 6);
+ util::fillRandom(identifier.begin(), identifier.end());
+
+ // HW address is a special case, because it must contain colons
+ // between consecutive octets.
+ HWAddrPtr hwaddr;
+ if (type == Host::IDENT_HWADDR) {
+ hwaddr.reset(new HWAddr(identifier, HTYPE_ETHER));
+ }
+
+ // Convert identifier to hexadecimal representation.
+ const std::string identifier_hex = (hwaddr ?
+ hwaddr->toText(false) :
+ util::encode::encodeHex(identifier));
+ const std::string identifier_name = Host::getIdentifierName(type);
+
+ // Try to create Host instance.
+ ASSERT_NO_THROW(host.reset(new Host(identifier_hex, identifier_name,
+ SubnetID(10), SubnetID(20),
+ IOAddress("192.0.2.5"),
+ "me.example.org")))
+ << "test failed for " << identifier_name << "="
+ << identifier_hex;
+
+ // Retrieve the identifier from the Host instance and verify if it
+ // is correct.
+ const std::vector<uint8_t>& identifier_returned = host->getIdentifier();
+ EXPECT_TRUE(identifier_returned == identifier);
+ EXPECT_EQ(type, host->getIdentifierType());
+
+ EXPECT_EQ(10, host->getIPv4SubnetID());
+ EXPECT_EQ(20, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_EQ("me.example.org", host->getHostname());
+ EXPECT_FALSE(host->getContext());
+ }
+}
+
+// This test verifies that it is possible to create Host instance using
+// identifiers specified as text in quotes.
+TEST_F(HostTest, createFromIdentifierString) {
+ HostPtr host;
+ // It is not allowed to specify HW address or DUID as a string in quotes.
+ for (unsigned int i = 2; i < identifierTypeUpperBound(); ++i) {
+ const Host::IdentifierType type = static_cast<Host::IdentifierType>(i);
+ const std::string identifier_name = Host::getIdentifierName(type);
+
+ // Construct unique identifier for a host. This is a string
+ // consisting of a word "identifier", hyphen and the name of
+ // the identifier, e.g. "identifier-hw-address".
+ std::ostringstream identifier_without_quotes;
+ identifier_without_quotes << "identifier-" << identifier_name;
+
+ // Insert quotes to the identifier to indicate to the Host
+ // constructor that it is encoded as a text.
+ std::ostringstream identifier;
+ identifier << "'" << identifier_without_quotes.str() << "'";
+
+ ASSERT_NO_THROW(host.reset(new Host(identifier.str(), identifier_name,
+ SubnetID(10), SubnetID(20),
+ IOAddress("192.0.2.5"),
+ "me.example.org")))
+ << "test failed for " << identifier_name << "="
+ << identifier.str();
+
+ // Get the identifier from the Host and convert it back to the string
+ // format, so as it can be compared with the identifier used during
+ // Host object construction.
+ const std::vector<uint8_t>& identifier_returned = host->getIdentifier();
+ const std::string identifier_returned_str(identifier_returned.begin(),
+ identifier_returned.end());
+ // Exclude quotes in comparison. Quotes should have been removed.
+ EXPECT_EQ(identifier_without_quotes.str(), identifier_returned_str);
+ EXPECT_EQ(type, host->getIdentifierType());
+
+ EXPECT_EQ(10, host->getIPv4SubnetID());
+ EXPECT_EQ(20, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_EQ("me.example.org", host->getHostname());
+ EXPECT_FALSE(host->getContext());
+ }
+}
+
+// This test verifies that it is possible to override a host identifier
+// using setIdentifier method with an identifier specified in
+// hexadecimal format.
+TEST_F(HostTest, setIdentifierHex) {
+ HostPtr host;
+ // Iterate over all supported identifiers.
+ for (unsigned int i = 0; i < identifierTypeUpperBound(); ++i) {
+
+ // In order to test that setIdentifier replaces an existing
+ // identifier we have to initialize Host with a different
+ // identifier first. We pick the next identifier after the
+ // one we want to set. If 'i' points to the last one, we
+ // use the first one.
+ unsigned int j = (i + 1) % identifierTypeUpperBound();
+
+ Host::IdentifierType type = static_cast<Host::IdentifierType>(j);
+ // Create identifier of a variable length.
+ std::vector<uint8_t> identifier(random() % 14 + 6);
+ util::fillRandom(identifier.begin(), identifier.end());
+
+ // HW address is a special case, because it must contain colons
+ // between consecutive octets.
+ HWAddrPtr hwaddr;
+ if (type == Host::IDENT_HWADDR) {
+ hwaddr.reset(new HWAddr(identifier, HTYPE_ETHER));
+ }
+
+ // Convert identifier to hexadecimal representation.
+ std::string identifier_hex = (hwaddr ?
+ hwaddr->toText(false) :
+ util::encode::encodeHex(identifier));
+ std::string identifier_name = Host::getIdentifierName(type);
+
+ // Try to create Host instance.
+ ASSERT_NO_THROW(host.reset(new Host(identifier_hex, identifier_name,
+ SubnetID(10), SubnetID(20),
+ IOAddress("192.0.2.5"),
+ "me.example.org")))
+ << "test failed for " << identifier_name << "="
+ << identifier_hex;
+
+ // Retrieve the identifier from the Host instance and verify if it
+ // is correct.
+ std::vector<uint8_t> identifier_returned = host->getIdentifier();
+ EXPECT_TRUE(identifier_returned == identifier);
+ EXPECT_EQ(type, host->getIdentifierType());
+
+ EXPECT_EQ(10, host->getIPv4SubnetID());
+ EXPECT_EQ(20, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_EQ("me.example.org", host->getHostname());
+ EXPECT_FALSE(host->getContext());
+
+ // Now use another identifier.
+ type = static_cast<Host::IdentifierType>(i);
+ // Create identifier of a variable length.
+ identifier.resize(random() % 14 + 6);
+ util::fillRandom(identifier.begin(), identifier.end());
+
+ hwaddr.reset();
+ if (type == Host::IDENT_HWADDR) {
+ hwaddr.reset(new HWAddr(identifier, HTYPE_ETHER));
+ }
+
+ // Convert identifier to hexadecimal representation.
+ identifier_hex = (hwaddr ? hwaddr->toText(false) :
+ util::encode::encodeHex(identifier));
+ identifier_name = Host::getIdentifierName(type);
+
+ // Try to replace identifier for a host.
+ ASSERT_NO_THROW(host->setIdentifier(identifier_hex, identifier_name))
+ << "test failed for " << identifier_name << "="
+ << identifier_hex;
+
+ // Retrieve the identifier from the Host instance and verify if it
+ // is correct.
+ identifier_returned = host->getIdentifier();
+ EXPECT_TRUE(identifier_returned == identifier);
+ EXPECT_EQ(type, host->getIdentifierType());
+
+ EXPECT_EQ(10, host->getIPv4SubnetID());
+ EXPECT_EQ(20, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_EQ("me.example.org", host->getHostname());
+ EXPECT_FALSE(host->getContext());
+ }
+}
+
+// This test verifies that it is possible to override a host identifier
+// using setIdentifier method with an identifier specified in binary
+// format.
+TEST_F(HostTest, setIdentifierBinary) {
+ HostPtr host;
+ // Iterate over all supported identifier types.
+ for (unsigned int i = 0; i < identifierTypeUpperBound(); ++i) {
+
+ // In order to test that setIdentifier replaces an existing
+ // identifier we have to initialize Host with a different
+ // identifier first. We pick the next identifier after the
+ // one we want to set. If 'i' points to the last one, we
+ // use the first one.
+ unsigned int j = (i + 1) % identifierTypeUpperBound();
+
+ Host::IdentifierType type = static_cast<Host::IdentifierType>(j);
+ // Create identifier of variable length and fill with random values.
+ std::vector<uint8_t> identifier(random() % 14 + 6);
+ util::fillRandom(identifier.begin(), identifier.end());
+
+ // Try to create a Host instance using this identifier.
+ ASSERT_NO_THROW(host.reset(new Host(&identifier[0], identifier.size(),
+ type, SubnetID(10), SubnetID(20),
+ IOAddress("192.0.2.5"),
+ "me.example.org")));
+
+ // Retrieve identifier from Host instance and check if it is correct.
+ std::vector<uint8_t> identifier_returned = host->getIdentifier();
+ EXPECT_TRUE(identifier_returned == identifier);
+ EXPECT_EQ(type, host->getIdentifierType());
+
+ EXPECT_EQ(10, host->getIPv4SubnetID());
+ EXPECT_EQ(20, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_EQ("me.example.org", host->getHostname());
+ EXPECT_FALSE(host->getContext());
+
+ type = static_cast<Host::IdentifierType>(i);
+ // Create identifier of variable length and fill with random values.
+ identifier.resize(random() % 14 + 6);
+ util::fillRandom(identifier.begin(), identifier.end());
+
+ // Try to set new identifier.
+ ASSERT_NO_THROW(host->setIdentifier(&identifier[0], identifier.size(),
+ type));
+
+ // Retrieve identifier from Host instance and check if it is correct.
+ identifier_returned = host->getIdentifier();
+ EXPECT_TRUE(identifier_returned == identifier);
+ EXPECT_EQ(type, host->getIdentifierType());
+
+ EXPECT_EQ(10, host->getIPv4SubnetID());
+ EXPECT_EQ(20, host->getIPv6SubnetID());
+ EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+ EXPECT_EQ("me.example.org", host->getHostname());
+ EXPECT_FALSE(host->getContext());
+ }
+}
+
+// This test verifies that the IPv6 reservations of a different type can
+// be added for the host.
+TEST_F(HostTest, addReservations) {
+ HostPtr host;
+ ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"))));
+
+ EXPECT_FALSE(host->hasIPv6Reservation());
+
+ // Add 4 reservations: 2 for NAs, 2 for PDs
+ ASSERT_NO_THROW(
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::cafe")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:1::"), 64));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:2::"), 64));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1")));
+ );
+
+ EXPECT_TRUE(host->hasIPv6Reservation());
+
+ // Check that reservations exist.
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::cafe"))));
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:1::"),
+ 64)));
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:2::"),
+ 64)));
+ EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1"))));
+
+ // Get only NA reservations.
+ IPv6ResrvRange addresses = host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+ ASSERT_EQ(2, std::distance(addresses.first, addresses.second));
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::cafe")),
+ addresses));
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1")),
+ addresses));
+
+
+ // Get only PD reservations.
+ IPv6ResrvRange prefixes = host->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+ ASSERT_EQ(2, std::distance(prefixes.first, prefixes.second));
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:1::"), 64),
+ prefixes));
+ EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:2::"), 64),
+ prefixes));
+}
+
+// This test checks that various modifiers may be used to replace the current
+// values of the Host class.
+TEST_F(HostTest, setValues) {
+ HostPtr host;
+ ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"),
+ "some-host.eXAMple.org")));
+
+ ASSERT_EQ(1, host->getIPv4SubnetID());
+ ASSERT_EQ(2, host->getIPv6SubnetID());
+ ASSERT_EQ("192.0.2.3", host->getIPv4Reservation().toText());
+ ASSERT_EQ("some-host.eXAMple.org", host->getHostname());
+ ASSERT_EQ("some-host.example.org", host->getLowerHostname());
+ ASSERT_FALSE(host->getContext());
+ ASSERT_FALSE(host->getNegative());
+
+ host->setIPv4SubnetID(SubnetID(123));
+ host->setIPv6SubnetID(SubnetID(234));
+ host->setIPv4Reservation(IOAddress("10.0.0.1"));
+ host->setHostname("other-host.eXAMple.org");
+ host->setNextServer(IOAddress("192.0.2.2"));
+ host->setServerHostname("server-hostname.example.org");
+ host->setBootFileName("bootfile.efi");
+ const std::vector<uint8_t>& random_value(AuthKey::getRandomKeyString());
+ host->setKey(AuthKey(random_value));
+ std::string user_context = "{ \"foo\": \"bar\" }";
+ host->setContext(Element::fromJSON(user_context));
+ host->setNegative(true);
+
+ EXPECT_EQ(123, host->getIPv4SubnetID());
+ EXPECT_EQ(234, host->getIPv6SubnetID());
+ EXPECT_EQ("10.0.0.1", host->getIPv4Reservation().toText());
+ EXPECT_EQ("other-host.eXAMple.org", host->getHostname());
+ ASSERT_EQ("other-host.example.org", host->getLowerHostname());
+ EXPECT_EQ("192.0.2.2", host->getNextServer().toText());
+ EXPECT_EQ("server-hostname.example.org", host->getServerHostname());
+ EXPECT_EQ("bootfile.efi", host->getBootFileName());
+ EXPECT_EQ(random_value, host->getKey().getAuthKey());
+ ASSERT_TRUE(host->getContext());
+ EXPECT_EQ(user_context, host->getContext()->str());
+ EXPECT_TRUE(host->getNegative());
+
+ // Remove IPv4 reservation.
+ host->removeIPv4Reservation();
+ EXPECT_EQ(IOAddress::IPV4_ZERO_ADDRESS(), host->getIPv4Reservation());
+
+ // An IPv6 address can't be used for IPv4 reservations.
+ EXPECT_THROW(host->setIPv4Reservation(IOAddress("2001:db8:1::1")),
+ isc::BadValue);
+ // Zero address can't be set, the removeIPv4Reservation should be
+ // used instead.
+ EXPECT_THROW(host->setIPv4Reservation(IOAddress::IPV4_ZERO_ADDRESS()),
+ isc::BadValue);
+ // Broadcast address can't be set.
+ EXPECT_THROW(host->setIPv4Reservation(IOAddress::IPV4_BCAST_ADDRESS()),
+ isc::BadValue);
+
+ // Broadcast and IPv6 are invalid addresses for next server.
+ EXPECT_THROW(host->setNextServer(asiolink::IOAddress::IPV4_BCAST_ADDRESS()),
+ isc::BadValue);
+ EXPECT_THROW(host->setNextServer(IOAddress("2001:db8:1::1")),
+ isc::BadValue);
+}
+
+// Test that Host constructors initialize client classes from string.
+TEST_F(HostTest, clientClassesFromConstructor) {
+ HostPtr host;
+ // Prepare the hardware address in binary format.
+ const uint8_t hwaddr_data[] = {
+ 0xaa, 0xab, 0xca, 0xda, 0xbb, 0xee
+ };
+
+ // Try the "from binary" constructor.
+ ASSERT_NO_THROW(host.reset(new Host(hwaddr_data,
+ sizeof(hwaddr_data),
+ Host::IDENT_HWADDR,
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"),
+ "somehost.example.org",
+ "alpha, , beta",
+ "gamma")));
+
+ EXPECT_TRUE(host->getClientClasses4().contains("alpha"));
+ EXPECT_TRUE(host->getClientClasses4().contains("beta"));
+ EXPECT_FALSE(host->getClientClasses4().contains("gamma"));
+ EXPECT_TRUE(host->getClientClasses6().contains("gamma"));
+ EXPECT_FALSE(host->getClientClasses6().contains("alpha"));
+ EXPECT_FALSE(host->getClientClasses6().contains("beta"));
+
+ // Try the "from string" constructor.
+ ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"),
+ "somehost.example.org",
+ "alpha, beta, gamma",
+ "beta, gamma")));
+
+ EXPECT_TRUE(host->getClientClasses4().contains("alpha"));
+ EXPECT_TRUE(host->getClientClasses4().contains("beta"));
+ EXPECT_TRUE(host->getClientClasses4().contains("gamma"));
+ EXPECT_FALSE(host->getClientClasses6().contains("alpha"));
+ EXPECT_TRUE(host->getClientClasses6().contains("beta"));
+ EXPECT_TRUE(host->getClientClasses6().contains("gamma"));
+}
+
+// Test that new client classes can be added for the Host.
+TEST_F(HostTest, addClientClasses) {
+ HostPtr host;
+ ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"))));
+
+ EXPECT_FALSE(host->getClientClasses4().contains("foo"));
+ EXPECT_FALSE(host->getClientClasses6().contains("foo"));
+ EXPECT_FALSE(host->getClientClasses4().contains("bar"));
+ EXPECT_FALSE(host->getClientClasses6().contains("bar"));
+
+ host->addClientClass4("foo");
+ host->addClientClass6("bar");
+ EXPECT_TRUE(host->getClientClasses4().contains("foo"));
+ EXPECT_FALSE(host->getClientClasses6().contains("foo"));
+ EXPECT_FALSE(host->getClientClasses4().contains("bar"));
+ EXPECT_TRUE(host->getClientClasses6().contains("bar"));
+
+ host->addClientClass4("bar");
+ host->addClientClass6("foo");
+ EXPECT_TRUE(host->getClientClasses4().contains("foo"));
+ EXPECT_TRUE(host->getClientClasses6().contains("foo"));
+ EXPECT_TRUE(host->getClientClasses4().contains("bar"));
+ EXPECT_TRUE(host->getClientClasses6().contains("bar"));
+}
+
+// This test checks that it is possible to add DHCPv4 options for a host.
+TEST_F(HostTest, addOptions4) {
+ Host host("01:02:03:04:05:06", "hw-address", SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"));
+
+ // Differentiate options by their codes (100-109)
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(host.getCfgOption4()->add(option, false, DHCP4_OPTION_SPACE));
+ }
+
+ // Add 7 options to another option space. The option codes partially overlap
+ // with option codes that we have added to dhcp4 option space.
+ for (uint16_t code = 105; code < 112; ++code) {
+ OptionPtr option(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(host.getCfgOption4()->add(option, false, "isc"));
+ }
+
+ // Get options from the Subnet and check if all 10 are there.
+ OptionContainerPtr options = host.getCfgOption4()->getAll(DHCP4_OPTION_SPACE);
+ ASSERT_TRUE(options);
+ ASSERT_EQ(10, options->size());
+
+ // It should be possible to retrieve DHCPv6 options but the container
+ // should be empty.
+ OptionContainerPtr options6 = host.getCfgOption6()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_TRUE(options6);
+ EXPECT_TRUE(options6->empty());
+
+ // Also make sure that for dhcp4 option space no DHCPv6 options are
+ // returned. This is to check that containers for DHCPv4 and DHCPv6
+ // options do not share information.
+ options6 = host.getCfgOption6()->getAll(DHCP4_OPTION_SPACE);
+ ASSERT_TRUE(options6);
+ EXPECT_TRUE(options6->empty());
+
+ // Validate codes of options added to dhcp4 option space.
+ uint16_t expected_code = 100;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ options = host.getCfgOption4()->getAll("isc");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(7, options->size());
+
+ // Validate codes of options added to isc option space.
+ expected_code = 105;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ // Try to get options from a non-existing option space.
+ options = host.getCfgOption4()->getAll("abcd");
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+}
+
+// This test checks that it is possible to add DHCPv6 options for a host.
+TEST_F(HostTest, addOptions6) {
+ Host host("01:02:03:04:05:06", "hw-address", SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"));
+
+ // Differentiate options by their codes (100-109)
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(host.getCfgOption6()->add(option, false, DHCP6_OPTION_SPACE));
+ }
+
+ // Add 7 options to another option space. The option codes partially overlap
+ // with option codes that we have added to dhcp6 option space.
+ for (uint16_t code = 105; code < 112; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(host.getCfgOption6()->add(option, false, "isc"));
+ }
+
+ // Get options from the Subnet and check if all 10 are there.
+ OptionContainerPtr options = host.getCfgOption6()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_TRUE(options);
+ ASSERT_EQ(10, options->size());
+
+ // It should be possible to retrieve DHCPv4 options but the container
+ // should be empty.
+ OptionContainerPtr options4 = host.getCfgOption4()->getAll(DHCP4_OPTION_SPACE);
+ ASSERT_TRUE(options4);
+ EXPECT_TRUE(options4->empty());
+
+ // Also make sure that for dhcp6 option space no DHCPv4 options are
+ // returned. This is to check that containers for DHCPv4 and DHCPv6
+ // options do not share information.
+ options4 = host.getCfgOption4()->getAll(DHCP6_OPTION_SPACE);
+ ASSERT_TRUE(options4);
+ EXPECT_TRUE(options4->empty());
+
+ // Validate codes of options added to dhcp6 option space.
+ uint16_t expected_code = 100;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ options = host.getCfgOption6()->getAll("isc");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(7, options->size());
+
+ // Validate codes of options added to isc option space.
+ expected_code = 105;
+ for (OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option_);
+ EXPECT_EQ(expected_code, option_desc->option_->getType());
+ ++expected_code;
+ }
+
+ // Try to get options from a non-existing option space.
+ options = host.getCfgOption6()->getAll("abcd");
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+}
+
+// This test verifies that it is possible to retrieve a textual
+// representation of the host identifier.
+TEST_F(HostTest, getIdentifierAsText) {
+ // HW address
+ Host host1("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"));
+ EXPECT_EQ("hwaddr=010203040506", host1.getIdentifierAsText());
+
+ // DUID
+ Host host2("0a:0b:0c:0d:0e:0f:ab:cd:ef", "duid",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"));
+ EXPECT_EQ("duid=0A0B0C0D0E0FABCDEF",
+ host2.getIdentifierAsText());
+
+ // Circuit id.
+ Host host3("'marcin's-home'", "circuit-id",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"));
+ EXPECT_EQ("circuit-id=6D617263696E27732D686F6D65",
+ host3.getIdentifierAsText());
+}
+
+// This test verifies that conversion of the identifier type to a
+// name works correctly.
+TEST_F(HostTest, getIdentifierName) {
+ EXPECT_EQ("hw-address", Host::getIdentifierName(Host::IDENT_HWADDR));
+
+}
+
+// This test checks that Host object is correctly described in the
+// textual format using the toText method.
+TEST_F(HostTest, toText) {
+ HostPtr host;
+ ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"),
+ "myhost.example.com")));
+
+ // Add 4 reservations: 2 for NAs, 2 for PDs.
+ ASSERT_NO_THROW(
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::cafe")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:1::"), 64));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:2::"), 64));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1")));
+ );
+
+ // Add invisible user context
+ std::string user_context = "{ \"foo\": \"bar\" }";
+ host->setContext(Element::fromJSON(user_context));
+
+ // Make sure that the output is correct,
+ EXPECT_EQ("hwaddr=010203040506 ipv4_subnet_id=1 ipv6_subnet_id=2"
+ " hostname=myhost.example.com"
+ " ipv4_reservation=192.0.2.3"
+ " siaddr=(no)"
+ " sname=(empty)"
+ " file=(empty)"
+ " key=(empty)"
+ " ipv6_reservation0=2001:db8:1::cafe"
+ " ipv6_reservation1=2001:db8:1::1"
+ " ipv6_reservation2=2001:db8:1:1::/64"
+ " ipv6_reservation3=2001:db8:1:2::/64",
+ host->toText());
+
+ // Reset some of the data and make sure that the output is affected.
+ host->setHostname("");
+ host->removeIPv4Reservation();
+ host->setIPv4SubnetID(SUBNET_ID_UNUSED);
+ host->setNegative(true);
+
+ EXPECT_EQ("hwaddr=010203040506 ipv6_subnet_id=2"
+ " hostname=(empty) ipv4_reservation=(no)"
+ " siaddr=(no)"
+ " sname=(empty)"
+ " file=(empty)"
+ " key=(empty)"
+ " ipv6_reservation0=2001:db8:1::cafe"
+ " ipv6_reservation1=2001:db8:1::1"
+ " ipv6_reservation2=2001:db8:1:1::/64"
+ " ipv6_reservation3=2001:db8:1:2::/64"
+ " negative cached",
+ host->toText());
+
+ // Create host identified by DUID, instead of HWADDR, with a very
+ // basic configuration.
+ ASSERT_NO_THROW(host.reset(new Host("11:12:13:14:15", "duid",
+ SUBNET_ID_UNUSED, SUBNET_ID_UNUSED,
+ IOAddress::IPV4_ZERO_ADDRESS(),
+ "myhost")));
+
+ EXPECT_EQ("duid=1112131415 hostname=myhost ipv4_reservation=(no)"
+ " siaddr=(no)"
+ " sname=(empty)"
+ " file=(empty)"
+ " key=(empty)"
+ " ipv6_reservations=(none)", host->toText());
+
+ // Add some classes.
+ host->addClientClass4("modem");
+ host->addClientClass4("router");
+
+ EXPECT_EQ("duid=1112131415 hostname=myhost ipv4_reservation=(no)"
+ " siaddr=(no)"
+ " sname=(empty)"
+ " file=(empty)"
+ " key=(empty)"
+ " ipv6_reservations=(none)"
+ " dhcp4_class0=modem dhcp4_class1=router",
+ host->toText());
+
+ host->addClientClass6("hub");
+ host->addClientClass6("device");
+
+ // Note that now classes are in insert order.
+ EXPECT_EQ("duid=1112131415 hostname=myhost ipv4_reservation=(no)"
+ " siaddr=(no)"
+ " sname=(empty)"
+ " file=(empty)"
+ " key=(empty)"
+ " ipv6_reservations=(none)"
+ " dhcp4_class0=modem dhcp4_class1=router"
+ " dhcp6_class0=hub dhcp6_class1=device",
+ host->toText());
+
+ // Create global host identified by DUID, with a very basic configuration.
+ ASSERT_NO_THROW(host.reset(new Host("11:12:13:14:15", "duid",
+ SUBNET_ID_GLOBAL, SUBNET_ID_GLOBAL,
+ IOAddress::IPV4_ZERO_ADDRESS(),
+ "myhost")));
+
+ EXPECT_EQ("duid=1112131415 ipv4_subnet_id=0 ipv6_subnet_id=0 "
+ "hostname=myhost ipv4_reservation=(no)"
+ " siaddr=(no)"
+ " sname=(empty)"
+ " file=(empty)"
+ " key=(empty)"
+ " ipv6_reservations=(none)", host->toText());
+
+}
+
+// This test checks that Host object is correctly unparsed,
+TEST_F(HostTest, unparse) {
+ boost::scoped_ptr<Host> host;
+ ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"),
+ "myhost.example.com")));
+
+ // Add 4 reservations: 2 for NAs, 2 for PDs.
+ ASSERT_NO_THROW(
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::cafe")));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:1::"), 64));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+ IOAddress("2001:db8:1:2::"), 64));
+ host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+ IOAddress("2001:db8:1::1")));
+ );
+
+ // Add user context
+ std::string user_context = "{ \"comment\": \"a host reservation\" }";
+ host->setContext(Element::fromJSON(user_context));
+
+ // Make sure that the output is correct,
+ EXPECT_EQ("{ "
+ "\"boot-file-name\": \"\", "
+ "\"client-classes\": [ ], "
+ "\"hostname\": \"myhost.example.com\", "
+ "\"hw-address\": \"01:02:03:04:05:06\", "
+ "\"ip-address\": \"192.0.2.3\", "
+ "\"next-server\": \"0.0.0.0\", "
+ "\"option-data\": [ ], "
+ "\"server-hostname\": \"\", "
+ "\"user-context\": { \"comment\": \"a host reservation\" } "
+ "}",
+ host->toElement4()->str());
+
+ EXPECT_EQ("{ "
+ "\"client-classes\": [ ], "
+ "\"hostname\": \"myhost.example.com\", "
+ "\"hw-address\": \"01:02:03:04:05:06\", "
+ "\"ip-addresses\": [ \"2001:db8:1::cafe\", \"2001:db8:1::1\" ], "
+ "\"option-data\": [ ], "
+ "\"prefixes\": [ \"2001:db8:1:1::/64\", \"2001:db8:1:2::/64\" ], "
+ "\"user-context\": { \"comment\": \"a host reservation\" } "
+ "}",
+ host->toElement6()->str());
+
+ // Reset some of the data and make sure that the output is affected.
+ host->setHostname("");
+ host->removeIPv4Reservation();
+ host->setIPv4SubnetID(SUBNET_ID_UNUSED);
+
+ EXPECT_EQ("{ "
+ "\"boot-file-name\": \"\", "
+ "\"client-classes\": [ ], "
+ "\"hostname\": \"\", "
+ "\"hw-address\": \"01:02:03:04:05:06\", "
+ "\"next-server\": \"0.0.0.0\", "
+ "\"option-data\": [ ], "
+ "\"server-hostname\": \"\", "
+ "\"user-context\": { \"comment\": \"a host reservation\" } "
+ "}",
+ host->toElement4()->str());
+
+ EXPECT_EQ("{ "
+ "\"client-classes\": [ ], "
+ "\"hostname\": \"\", "
+ "\"hw-address\": \"01:02:03:04:05:06\", "
+ "\"ip-addresses\": [ \"2001:db8:1::cafe\", \"2001:db8:1::1\" ], "
+ "\"option-data\": [ ], "
+ "\"prefixes\": [ \"2001:db8:1:1::/64\", \"2001:db8:1:2::/64\" ], "
+ "\"user-context\": { \"comment\": \"a host reservation\" } "
+ "}",
+ host->toElement6()->str());
+
+ // Create host identified by DUID, instead of HWADDR, with a very
+ // basic configuration.
+ ASSERT_NO_THROW(host.reset(new Host("11:12:13:14:15", "duid",
+ SUBNET_ID_UNUSED, SUBNET_ID_UNUSED,
+ IOAddress::IPV4_ZERO_ADDRESS(),
+ "myhost")));
+
+ EXPECT_EQ("{ "
+ "\"boot-file-name\": \"\", "
+ "\"client-classes\": [ ], "
+ "\"duid\": \"11:12:13:14:15\", "
+ "\"hostname\": \"myhost\", "
+ "\"next-server\": \"0.0.0.0\", "
+ "\"option-data\": [ ], "
+ "\"server-hostname\": \"\" "
+ "}",
+ host->toElement4()->str());
+
+ EXPECT_EQ("{ "
+ "\"client-classes\": [ ], "
+ "\"duid\": \"11:12:13:14:15\", "
+ "\"hostname\": \"myhost\", "
+ "\"ip-addresses\": [ ], "
+ "\"option-data\": [ ], "
+ "\"prefixes\": [ ] "
+ "}",
+ host->toElement6()->str());
+
+ // Add some classes.
+ host->addClientClass4("modem");
+ host->addClientClass4("router");
+ // Set invisible negative cache.
+ host->setNegative(true);
+
+ EXPECT_EQ("{ "
+ "\"boot-file-name\": \"\", "
+ "\"client-classes\": [ \"modem\", \"router\" ], "
+ "\"duid\": \"11:12:13:14:15\", "
+ "\"hostname\": \"myhost\", "
+ "\"next-server\": \"0.0.0.0\", "
+ "\"option-data\": [ ], "
+ "\"server-hostname\": \"\" "
+ "}",
+ host->toElement4()->str());
+
+ EXPECT_EQ("{ "
+ "\"client-classes\": [ ], "
+ "\"duid\": \"11:12:13:14:15\", "
+ "\"hostname\": \"myhost\", "
+ "\"ip-addresses\": [ ], "
+ "\"option-data\": [ ], "
+ "\"prefixes\": [ ] "
+ "}",
+ host->toElement6()->str());
+
+ // Now the classes are in defined order (vs. alphabetical order).
+ host->addClientClass6("hub");
+ host->addClientClass6("device");
+
+ EXPECT_EQ("{ "
+ "\"boot-file-name\": \"\", "
+ "\"client-classes\": [ \"modem\", \"router\" ], "
+ "\"duid\": \"11:12:13:14:15\", "
+ "\"hostname\": \"myhost\", "
+ "\"next-server\": \"0.0.0.0\", "
+ "\"option-data\": [ ], "
+ "\"server-hostname\": \"\" "
+ "}",
+ host->toElement4()->str());
+
+ EXPECT_EQ("{ "
+ "\"client-classes\": [ \"hub\", \"device\" ], "
+ "\"duid\": \"11:12:13:14:15\", "
+ "\"hostname\": \"myhost\", "
+ "\"ip-addresses\": [ ], "
+ "\"option-data\": [ ], "
+ "\"prefixes\": [ ] "
+ "}",
+ host->toElement6()->str());
+}
+
+// Test verifies if the host can store HostId properly.
+TEST_F(HostTest, hostId) {
+ HostPtr host;
+ ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"),
+ "myhost.example.com")));
+ EXPECT_EQ(0, host->getHostId());
+
+ EXPECT_NO_THROW(host->setHostId(12345));
+
+ EXPECT_EQ(12345, host->getHostId());
+}
+
+// Test verifies if we can modify the host keys.
+TEST_F(HostTest, keys) {
+ HostPtr host;
+ ASSERT_NO_THROW(host.reset(new Host("01:02:03:04:05:06", "hw-address",
+ SubnetID(1), SubnetID(2),
+ IOAddress("192.0.2.3"),
+ "myhost.example.com")));
+ // Key must be empty
+ EXPECT_EQ(0, host->getKey().getAuthKey().size());
+ EXPECT_EQ("", host->getKey().toText());
+
+ // now set to random value
+ const std::vector<uint8_t>& random_key(AuthKey::getRandomKeyString());
+ host->setKey(AuthKey(random_key));
+ EXPECT_EQ(random_key, host->getKey().getAuthKey());
+}
+
+// Test verifies if getRandomKeyString can generate 1000 keys which are random
+TEST_F(HostTest, randomKeys) {
+ // use hashtable and set size to 1000
+ std::unordered_set<std::vector<uint8_t>,
+ boost::hash<std::vector<uint8_t>>> keys;
+
+ int dup_element = 0;
+ const uint16_t max_iter = 1000;
+ uint16_t iter_num = 0;
+ size_t max_hash_size = 1000;
+
+ keys.reserve(max_hash_size);
+
+ for (iter_num = 0; iter_num < max_iter; iter_num++) {
+ std::vector<uint8_t> key = AuthKey::getRandomKeyString();
+ if (keys.count(key)) {
+ dup_element++;
+ break;
+ }
+
+ keys.insert(key);
+ }
+
+ EXPECT_EQ(0, dup_element);
+}
+
+// Test performs basic functionality test of the AuthKey class
+TEST(AuthKeyTest, basicTest) {
+ // Call the constructor with default argument
+ // Default constructor should generate random string of 16 bytes
+ AuthKey defaultKey;
+ ASSERT_EQ(16, defaultKey.getAuthKey().size());
+
+ AuthKey longKey("0123456789abcdef1122334455667788");
+ ASSERT_EQ(16, longKey.getAuthKey().size());
+
+ // Check the setters for valid and invalid string
+ std::string key16ByteStr = "000102030405060708090A0B0C0D0E0F";
+ std::vector<uint8_t> key16ByteBin = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf
+ };
+ std::string key18ByteStr = "0123456789abcdefgh";
+
+ AuthKey defaultTestKey;
+
+ defaultTestKey.setAuthKey(key16ByteStr);
+ ASSERT_EQ(16, defaultTestKey.getAuthKey().size());
+ ASSERT_EQ(key16ByteStr, defaultTestKey.toText());
+ ASSERT_EQ(key16ByteBin, defaultTestKey.getAuthKey());
+
+ ASSERT_THROW(defaultTestKey.setAuthKey(key18ByteStr), BadValue);
+}
+
+} // end of anonymous namespace