Parcourir la source

[master] Merge branch 'trac3560'

Marcin Siodelski il y a 10 ans
Parent
commit
fb5e1883b0

+ 1 - 1
src/lib/dhcp/Makefile.am

@@ -14,7 +14,7 @@ CLEANFILES = *.gcno *.gcda
 
 lib_LTLIBRARIES = libkea-dhcp++.la
 libkea_dhcp___la_SOURCES  =
-libkea_dhcp___la_SOURCES += classify.h
+libkea_dhcp___la_SOURCES += classify.cc classify.h
 libkea_dhcp___la_SOURCES += dhcp6.h dhcp4.h
 libkea_dhcp___la_SOURCES += duid.cc duid.h
 libkea_dhcp___la_SOURCES += hwaddr.cc hwaddr.h

+ 41 - 0
src/lib/dhcp/classify.cc

@@ -0,0 +1,41 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/classify.h>
+#include <util/strutil.h>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/constants.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+ClientClasses::ClientClasses(const std::string& class_names)
+    : std::set<ClientClass>() {
+    std::vector<std::string> split_text;
+    boost::split(split_text, class_names, boost::is_any_of(","),
+                 boost::algorithm::token_compress_off);
+    for (int i = 0; i < split_text.size(); ++i) {
+        std::string trimmed = util::str::trim(split_text[i]);
+        // Ignore empty class names.
+        if (!trimmed.empty()) {
+            insert(ClientClass(trimmed));
+        }
+    }
+}
+    
+} // end of namespace isc::dhcp
+} // end of namespace isc
+

+ 11 - 0
src/lib/dhcp/classify.h

@@ -53,6 +53,17 @@ namespace dhcp {
     /// documentation. See  http://www.cplusplus.com/reference/set/set/.
     class ClientClasses : public std::set<ClientClass> {
     public:
+
+        /// @brief Default constructor.
+        ClientClasses() : std::set<ClientClass>() {
+        }
+
+        /// @brief Constructor from comma separated values.
+        ///
+        /// @param class_names A string containing a client classes separated
+        /// with commas. The class names are trimmed before insertion to the set.
+        ClientClasses(const std::string& class_names);
+
         /// @brief returns if class x belongs to the defined classes
         ///
         /// @param x client class to be checked

+ 35 - 18
src/lib/dhcp/duid.cc

@@ -20,6 +20,7 @@
 #include <boost/algorithm/string/constants.hpp>
 #include <boost/algorithm/string/split.hpp>
 #include <iomanip>
+#include <cctype>
 #include <sstream>
 #include <vector>
 
@@ -30,20 +31,20 @@ namespace dhcp {
 
 DUID::DUID(const std::vector<uint8_t>& duid) {
     if (duid.size() > MAX_DUID_LEN) {
-        isc_throw(OutOfRange, "DUID too large");
+        isc_throw(isc::BadValue, "DUID too large");
     }
     if (duid.empty()) {
-        isc_throw(OutOfRange, "Empty DUIDs are not allowed");
+        isc_throw(isc::BadValue, "Empty DUIDs are not allowed");
     }
     duid_ = duid;
 }
 
 DUID::DUID(const uint8_t* data, size_t len) {
     if (len > MAX_DUID_LEN) {
-        isc_throw(OutOfRange, "DUID too large");
+        isc_throw(isc::BadValue, "DUID too large");
     }
     if (len == 0) {
-        isc_throw(OutOfRange, "Empty DUIDs/Client-ids not allowed");
+        isc_throw(isc::BadValue, "Empty DUIDs/Client-ids not allowed");
     }
 
     duid_ = std::vector<uint8_t>(data, data + len);
@@ -58,19 +59,35 @@ DUID::decode(const std::string& text) {
 
     std::ostringstream s;
     for (size_t i = 0; i < split_text.size(); ++i) {
-        // If there are multiple tokens and the current one is empty, it
-        // means that two consecutive colons were specified. This is not
-        // allowed for client identifier.
-        if ((split_text.size() > 1) && split_text[i].empty()) {
-            isc_throw(isc::BadValue, "invalid identifier '" << text << "': "
-                      << " tokens must be separated with a single colon");
-
-        } else if (split_text[i].size() == 1) {
-            s << "0";
-
-        } else if (split_text[i].size() > 2) {
-            isc_throw(isc::BadValue, "invalid identifier '" << text << "'");
+        // Check that only hexadecimal digits are used.
+        size_t ch_index = 0;
+        while (ch_index < split_text[i].length()) {
+            if (!isxdigit(split_text[i][ch_index])) {
+                isc_throw(isc::BadValue, "invalid value '"
+                          << split_text[i][ch_index] << "' in"
+                          << " DUID '" << text << "'");
+            }
+            ++ch_index;
         }
+
+        if (split_text.size() > 1) {
+            // If there are multiple tokens and the current one is empty, it
+            // means that two consecutive colons were specified. This is not
+            // allowed for client identifier.
+            if (split_text[i].empty()) {
+                isc_throw(isc::BadValue, "invalid identifier '"
+                          << text << "': " << " tokens must be"
+                          " separated with a single colon");
+            } else if (split_text[i].size() > 2) {
+                isc_throw(isc::BadValue, "invalid identifier '"
+                          << text << "'");
+            }
+        }
+
+        if (split_text[i].size() % 2) {
+                s << "0";
+        }
+
         s << split_text[i];
     }
 
@@ -133,7 +150,7 @@ bool DUID::operator!=(const DUID& other) const {
 ClientId::ClientId(const std::vector<uint8_t>& clientid)
     : DUID(clientid) {
     if (clientid.size() < MIN_CLIENT_ID_LEN) {
-        isc_throw(OutOfRange, "client-id is too short (" << clientid.size()
+        isc_throw(isc::BadValue, "client-id is too short (" << clientid.size()
                   << "), at least 2 is required");
     }
 }
@@ -142,7 +159,7 @@ ClientId::ClientId(const std::vector<uint8_t>& clientid)
 ClientId::ClientId(const uint8_t *clientid, size_t len)
     : DUID(clientid, len) {
     if (len < MIN_CLIENT_ID_LEN) {
-        isc_throw(OutOfRange, "client-id is too short (" << len
+        isc_throw(isc::BadValue, "client-id is too short (" << len
                   << "), at least 2 is required");
     }
 }

+ 2 - 2
src/lib/dhcp/duid.h

@@ -98,8 +98,8 @@ class DUID {
     /// representing bytes of DUID must be separated by colons. Usually the
     /// single byte is represented by two hexadecimal digits. However, this
     /// function allows one digit per byte. In this case, a zero is prepended
-    /// before the conversion. For example, a DUID 0:1:2::4:5 equals to
-    /// 00:01:02:00:04:05.
+    /// before the conversion. For example, a DUID 0:1:2:3:4:5 equals to
+    /// 00:01:02:03:04:05.
     ///
     /// @param text DUID in the hexadecimal format with digits representing
     /// individual bytes separated by colons.

+ 2 - 2
src/lib/dhcp/hwaddr.cc

@@ -34,14 +34,14 @@ HWAddr::HWAddr()
 HWAddr::HWAddr(const uint8_t* hwaddr, size_t len, uint16_t htype)
     :hwaddr_(hwaddr, hwaddr + len), htype_(htype) {
     if (len > MAX_HWADDR_LEN) {
-        isc_throw(InvalidParameter, "hwaddr length exceeds MAX_HWADDR_LEN");
+        isc_throw(isc::BadValue, "hwaddr length exceeds MAX_HWADDR_LEN");
     }
 }
 
 HWAddr::HWAddr(const std::vector<uint8_t>& hwaddr, uint16_t htype)
     :hwaddr_(hwaddr), htype_(htype) {
     if (hwaddr.size() > MAX_HWADDR_LEN) {
-        isc_throw(InvalidParameter,
+        isc_throw(isc::BadValue,
             "address vector size exceeds MAX_HWADDR_LEN");
     }
 }

+ 38 - 3
src/lib/dhcp/tests/classify_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -36,8 +36,7 @@ TEST(ClassifyTest, ClientClasses) {
     EXPECT_FALSE(classes.contains(""));
     EXPECT_FALSE(classes.contains("alpha"));
     EXPECT_FALSE(classes.contains("beta"));
-    EXPECT_FALSE(classes.contains("gamma"));
-
+    EXPECT_FALSE(classes.contains("gamma")); 
     classes.insert("beta");
     EXPECT_FALSE(classes.contains(""));
     EXPECT_FALSE(classes.contains("alpha"));
@@ -50,3 +49,39 @@ TEST(ClassifyTest, ClientClasses) {
     EXPECT_TRUE (classes.contains("beta"));
     EXPECT_TRUE (classes.contains("gamma"));
 }
+
+// Check if ClientClasses object can be created from the string of comma
+// separated values.
+TEST(ClassifyTest, ClientClassesFromString) {
+    {
+        ClientClasses classes("alpha, beta, gamma");
+        EXPECT_EQ(3, classes.size());
+        EXPECT_FALSE(classes.contains(""));
+        EXPECT_TRUE(classes.contains("alpha"));
+        EXPECT_TRUE(classes.contains("beta"));
+        EXPECT_TRUE(classes.contains("gamma"));
+    }
+
+    {
+        ClientClasses classes("alpha, , beta ,");
+        EXPECT_EQ(2, classes.size());
+        EXPECT_TRUE(classes.contains("alpha"));
+        EXPECT_FALSE(classes.contains(""));
+        EXPECT_TRUE(classes.contains("beta"));
+    }
+
+    {
+        ClientClasses classes("");
+        EXPECT_TRUE(classes.empty());
+    }
+
+    {
+        ClientClasses classes("    ");
+        EXPECT_TRUE(classes.empty());
+    }
+
+    {
+        ClientClasses classes(", ,, ,");
+        EXPECT_TRUE(classes.empty());
+    }
+}

+ 32 - 10
src/lib/dhcp/tests/duid_unittest.cc

@@ -84,24 +84,24 @@ TEST(DuidTest, size) {
 
     EXPECT_THROW(
         scoped_ptr<DUID> toolarge1(new DUID(data, MAX_DUID_LEN + 1)),
-        OutOfRange);
+        BadValue);
 
     // that's one too much
     data2.push_back(128);
 
     EXPECT_THROW(
         scoped_ptr<DUID> toolarge2(new DUID(data2)),
-        OutOfRange);
+        BadValue);
 
     // empty duids are not allowed
     vector<uint8_t> empty;
     EXPECT_THROW(
         scoped_ptr<DUID> emptyDuid(new DUID(empty)),
-        OutOfRange);
+        BadValue);
 
     EXPECT_THROW(
         scoped_ptr<DUID> emptyDuid2(new DUID(data, 0)),
-        OutOfRange);
+        BadValue);
 }
 
 // This test verifies if the implementation supports all defined
@@ -222,33 +222,33 @@ TEST(ClientIdTest, size) {
 
     EXPECT_THROW(
         scoped_ptr<ClientId> toolarge1(new ClientId(data, MAX_CLIENT_ID_LEN + 1)),
-        OutOfRange);
+        BadValue);
 
     // that's one too much
     data2.push_back(128);
 
     EXPECT_THROW(
         scoped_ptr<ClientId> toolarge2(new ClientId(data2)),
-        OutOfRange);
+        BadValue);
 
     // empty client-ids are not allowed
     vector<uint8_t> empty;
     EXPECT_THROW(
         scoped_ptr<ClientId> empty_client_id1(new ClientId(empty)),
-        OutOfRange);
+        BadValue);
 
     EXPECT_THROW(
         scoped_ptr<ClientId> empty_client_id2(new ClientId(data, 0)),
-        OutOfRange);
+        BadValue);
 
     // client-id must be at least 2 bytes long
     vector<uint8_t> shorty(1,17); // just a single byte with value 17
     EXPECT_THROW(
         scoped_ptr<ClientId> too_short_client_id1(new ClientId(shorty)),
-        OutOfRange);
+        BadValue);
     EXPECT_THROW(
         scoped_ptr<ClientId> too_short_client_id1(new ClientId(data, 1)),
-        OutOfRange);
+        BadValue);
 }
 
 // This test checks if the comparison operators are sane.
@@ -299,6 +299,11 @@ TEST(ClientIdTest, fromText) {
         cid = ClientId::fromText("00:a:bb:D:ee:EF:ab")
     );
     EXPECT_EQ("00:0a:bb:0d:ee:ef:ab", cid->toText());
+    // ClientId without any colons is allowed.
+    ASSERT_NO_THROW(
+        cid = ClientId::fromText("0010abcdee");
+    );
+    EXPECT_EQ("00:10:ab:cd:ee", cid->toText());
     // Repeated colon sign in the ClientId is not allowed.
     EXPECT_THROW(
         ClientId::fromText("00::bb:D:ee:EF:ab"),
@@ -310,6 +315,23 @@ TEST(ClientIdTest, fromText) {
         ClientId::fromText("00:01:021:03:04:05:06"),
         isc::BadValue
     );
+    // ClientId  with two spaces between the colons should not be allowed.
+    EXPECT_THROW(
+        ClientId::fromText("00:01:  :03:04:05:06"),
+        isc::BadValue
+    );
+
+    // ClientId  with one space between the colons should not be allowed.
+    EXPECT_THROW(
+        ClientId::fromText("00:01: :03:04:05:06"),
+        isc::BadValue
+    );
+
+    // ClientId  with three spaces between the colons should not be allowed.
+    EXPECT_THROW(
+        ClientId::fromText("00:01:   :03:04:05:06"),
+        isc::BadValue
+    );
 }
 
 

+ 2 - 2
src/lib/dhcp/tests/hwaddr_unittest.cc

@@ -60,10 +60,10 @@ TEST(HWAddrTest, constructor) {
 
     // Check that over the limit data length throws exception 
     EXPECT_THROW(HWAddr(&big_data_vector[0], big_data_vector.size(), HTYPE_ETHER), 
-        InvalidParameter);
+        BadValue);
 
     // Check that over the limit vector throws exception
-    EXPECT_THROW(HWAddr(big_data_vector, HTYPE_ETHER), InvalidParameter);
+    EXPECT_THROW(HWAddr(big_data_vector, HTYPE_ETHER), BadValue);
 }
 
 // This test checks if the comparison operators are sane.

+ 2 - 0
src/lib/dhcpsrv/Makefile.am

@@ -58,6 +58,7 @@ libkea_dhcpsrv_la_SOURCES += dbaccess_parser.cc dbaccess_parser.h
 libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libkea_dhcpsrv_la_SOURCES += dhcp_config_parser.h
 libkea_dhcpsrv_la_SOURCES += dhcp_parsers.cc dhcp_parsers.h
+libkea_dhcpsrv_la_SOURCES += host.cc host.h
 libkea_dhcpsrv_la_SOURCES += key_from_key.h
 libkea_dhcpsrv_la_SOURCES += lease.cc lease.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
@@ -76,6 +77,7 @@ libkea_dhcpsrv_la_SOURCES += option_space_container.h
 libkea_dhcpsrv_la_SOURCES += pool.cc pool.h
 libkea_dhcpsrv_la_SOURCES += srv_config.cc srv_config.h
 libkea_dhcpsrv_la_SOURCES += subnet.cc subnet.h
+libkea_dhcpsrv_la_SOURCES += subnet_id.h
 libkea_dhcpsrv_la_SOURCES += triplet.h
 libkea_dhcpsrv_la_SOURCES += utils.h
 

+ 141 - 0
src/lib/dhcpsrv/host.cc

@@ -0,0 +1,141 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcpsrv/host.h>
+#include <util/strutil.h>
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace dhcp {
+
+IPv6Resrv::IPv6Resrv(const asiolink::IOAddress& prefix,
+                     const uint8_t prefix_len)
+    : prefix_(asiolink::IOAddress("::")), prefix_len_(128) {
+    // Validate and set the actual values.
+    set(prefix, prefix_len);
+}
+
+void
+IPv6Resrv::set(const asiolink::IOAddress& prefix, const uint8_t prefix_len) {
+    if (!prefix.isV6() || prefix.isV6Multicast()) {
+        isc_throw(isc::BadValue, "invalid prefix '" << prefix
+                  << " for new IPv6 reservation");
+
+    } else if (prefix_len > 128) {
+        isc_throw(isc::BadValue, "invalid prefix length '"
+                  << static_cast<int>(prefix_len)
+                  << "' for new IPv6 reservation");
+    }
+
+    prefix_ = prefix;
+    prefix_len_ = prefix_len;
+}
+
+bool
+IPv6Resrv::operator==(const IPv6Resrv& other) const {
+    return (prefix_ == other.prefix_ &&
+            prefix_len_ == other.prefix_len_);
+}
+
+bool
+IPv6Resrv::operator!=(const IPv6Resrv& other) const {
+    return (!operator==(other));
+}
+
+Host::Host(const uint8_t* identifier, const size_t identifier_len,
+           const IdentifierType& identifier_type,
+           const SubnetID ipv4_subnet_id, const SubnetID ipv6_subnet_id,
+           const asiolink::IOAddress& ipv4_reservation,
+           const std::string& hostname,
+           const std::string& dhcp4_client_classes,
+           const std::string& dhcp6_client_classes)
+    : hw_address_(), duid_(), ipv4_subnet_id_(ipv4_subnet_id),
+      ipv6_subnet_id_(ipv6_subnet_id), ipv4_reservation_(ipv4_reservation),
+       hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
+       dhcp6_client_classes_(dhcp6_client_classes) {
+
+    // Initialize HWAddr or DUID
+    setIdentifier(identifier, identifier_len, identifier_type);
+}
+
+Host::Host(const std::string& identifier, const std::string& identifier_name,
+           const SubnetID ipv4_subnet_id, const SubnetID ipv6_subnet_id,
+           const asiolink::IOAddress& ipv4_reservation,
+           const std::string& hostname,
+           const std::string& dhcp4_client_classes,
+           const std::string& dhcp6_client_classes)
+    : hw_address_(), duid_(), ipv4_subnet_id_(ipv4_subnet_id),
+      ipv6_subnet_id_(ipv6_subnet_id), ipv4_reservation_(ipv4_reservation),
+      hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
+      dhcp6_client_classes_(dhcp6_client_classes) {
+
+    // Initialize HWAddr or DUID
+    setIdentifier(identifier, identifier_name);
+}
+
+void
+Host::setIdentifier(const uint8_t* identifier, const size_t len,
+                    const IdentifierType& type) {
+    switch (type) {
+    case IDENT_HWADDR:
+        hw_address_ = HWAddrPtr(new HWAddr(identifier, len, HTYPE_ETHER));
+        duid_.reset();
+        break;
+    case IDENT_DUID:
+        duid_ = DuidPtr(new DUID(identifier, len));
+        hw_address_.reset();
+        break;
+    default:
+        isc_throw(isc::BadValue, "invalid client identifier type '"
+                  << static_cast<int>(type) << "' when creating host "
+                  " instance");
+    }
+}
+
+void
+Host::setIdentifier(const std::string& identifier, const std::string& name) {
+    if (name == "hw-address") {
+        hw_address_ = HWAddrPtr(new HWAddr(HWAddr::fromText(identifier)));
+        duid_.reset();
+    } else if (name == "duid") {
+        duid_ = DuidPtr(new DUID(DUID::fromText(identifier)));
+        hw_address_.reset();
+    } else {
+        isc_throw(isc::BadValue, "invalid client identifier type '"
+                  << name << "' when creating host instance");
+    }
+}
+
+void
+Host::addReservation(const IPv6Resrv& reservation) {
+    ipv6_reservations_.insert(IPv6ResrvTuple(reservation.getType(),
+                                             reservation));
+}
+
+IPv6ResrvRange
+Host::getIPv6Reservations(const IPv6Resrv::Type& type) const {
+    return (ipv6_reservations_.equal_range(type));
+}
+
+void
+Host::addClientClassInternal(ClientClasses& classes,
+                             const std::string& class_name) {
+    std::string trimmed = util::str::trim(class_name);
+    if (!class_name.empty()) {
+        classes.insert(ClientClass(class_name));
+    }
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc

+ 414 - 0
src/lib/dhcpsrv/host.h

@@ -0,0 +1,414 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef HOST_H
+#define HOST_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/classify.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/subnet_id.h>
+#include <list>
+#include <map>
+#include <string>
+#include <utility>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief IPv6 reservation for a host.
+///
+/// This class represents a reservation for a host of a single IPv6
+/// address or prefix (in @c Host object).
+///
+/// The class holds the address and prefix length, a value of 128
+/// for the latter implying that the reservation is for a single
+/// IPv6 address.
+class IPv6Resrv {
+public:
+
+    /// @brief Type of the reservation.
+    ///
+    /// Currently supported types are NA and PD.
+    enum Type {
+        TYPE_NA,
+        TYPE_PD
+    };
+
+    /// @brief Constructor.
+    ///
+    /// Creates a reservation from the IPv6 address and prefix length
+    /// value. If the prefix length is not specified, the default value
+    /// of 128 is used. This value indicates that the reservation is made
+    /// for an IPv6 address.
+    ///
+    /// @param prefix Address or prefix to be reserved.
+    /// @param prefix_len Prefix length.
+    ///
+    /// @throw isc::BadValue if prefix is not IPv6 prefix, is a
+    /// multicast address or the prefix length is greater than 128.
+    IPv6Resrv(const asiolink::IOAddress& prefix,
+              const uint8_t prefix_len = 128);
+
+    /// @brief Returns prefix for the reservation.
+    const asiolink::IOAddress& getPrefix() const {
+        return (prefix_);
+    }
+
+    /// @brief Returns prefix length.
+    uint8_t getPrefixLen() const {
+        return (prefix_len_);
+    }
+
+    /// @brief Returns reservation type.
+    ///
+    /// The type of reservation is determined using a prefix length.
+    ///
+    /// @return NA for prefix length equal to 128, PD otherwise.
+    Type getType() const {
+        return (prefix_len_ == 128 ? TYPE_NA : TYPE_PD);
+    }
+
+    /// @brief Sets a new prefix and prefix length.
+    ///
+    /// @param prefix New prefix.
+    /// @param prefix_len New prefix length.
+    ///
+    /// @throw isc::BadValue if prefix is not IPv6 prefix, is a
+    /// multicast address or the prefix length is greater than 128.
+    void set(const asiolink::IOAddress& prefix, const uint8_t prefix_len);
+
+    /// @brief Equality operator.
+    ///
+    /// @param other Reservation to compare to.
+    bool operator==(const IPv6Resrv& other) const;
+
+    /// @brief Inequality operator.
+    ///
+    /// @param other Reservation to compare to.
+    bool operator!=(const IPv6Resrv& other) const;
+
+private:
+    asiolink::IOAddress prefix_; ///< Prefix
+    uint8_t prefix_len_;         ///< Prefix length.
+
+};
+
+/// @brief Collection of IPv6 reservations for the host.
+typedef std::multimap<IPv6Resrv::Type, IPv6Resrv> IPv6ResrvCollection;
+typedef IPv6ResrvCollection::const_iterator IPv6ResrvIterator;
+typedef std::pair<IPv6Resrv::Type, IPv6Resrv> IPv6ResrvTuple;
+typedef std::pair<IPv6ResrvIterator, IPv6ResrvIterator> IPv6ResrvRange;
+
+/// @brief Represents a device with IPv4 and/or IPv6 reservations.
+///
+/// This class represents a network device which can be identified
+/// by a unique property, such as MAC address on the interface or
+/// client identifier (DUID), and for which some resources are statically
+/// assigned:
+/// - IPv4 address which the device obtains when it contacts a DHCPv4 server
+/// - IPv6 address(es) which the device obtains when it contacts a DHCPv6
+/// server
+/// - IPv6 prefix(es) obtained when the device contacts the DHCPv6 server
+/// and requests allocation of prefixes using prefix delegation mechanism
+/// - hostname which is used for dynamic DNS updates for both DHCPv4 and
+/// DHCPv6 exchanges.
+/// - client classes which the client is associated with
+/// - DHCP options specifically configured for the device
+///
+/// Note, that "host" in this context has a different meaning from
+/// host construed as device attached to a network with (possibly) multiple
+/// interfaces. For the MAC address based reservations, each interface on a
+/// network device maps to a single @c Host object as each @c Host object
+/// contains at most one MAC address. So, it is possible that a single
+/// device is associated with multiple distinct @c Host objects.
+///
+/// A DHCPv6 DUID is common for all interfaces on a device. Therefore, for
+/// DUID based reservations a @c Host object may represent a network device with
+/// multiple interfaces. However, since @c Host objects are grouped by
+/// subnets to which device's interfaces are connected a single instance of
+/// @c Host object usually defines reservations for a single interface.
+///
+/// The @c Host object combines reservations for both IPv4 and IPv6 resources
+/// to allow for correlation of the information about the dual stack devices
+/// using DHCPv4 and DHCPv6 respectively. For example: both the DHCPv4 and
+/// DHCPv6 servers may use the same database for storing host reservations, so
+/// the information about the DHCPv4 reservations are available for the
+/// DHCPv6 server and vice versa. Also, this approach allows for reserving
+/// common resources such as host name for DHCPv4 and DHCPv6 clients.
+///
+/// @todo This class offers basic functionality for storing host information.
+/// It will need to be extended to allow for the following operations:
+/// - store DHCPv4 and DHCPv6 options for the host,
+/// - remove and replace IPv6 reservations
+/// - remove and replace client classes
+/// - disable IPv4 reservation without a need to set it to the 0.0.0.0 address
+/// Note that the last three operations are mainly required for managing
+/// host reservations which will be implemented later.
+class Host {
+public:
+
+    /// @brief Type of the host identifier.
+    ///
+    /// Currently hardware address assigned to an interface and the
+    /// DHCPv6 client's DUID are supported.
+    enum IdentifierType {
+        IDENT_HWADDR,
+        IDENT_DUID
+    };
+
+    /// @brief Constructor.
+    ///
+    /// Creates a @c Host object using an identifier in a binary format. This
+    /// is most useful in cases where the identifier is obtained from the
+    /// database. The constructor will create an instance of the @c HWAddr
+    /// or @c DUID object depending on the identifier type.
+    ///
+    /// @param identifier Pointer to the binary value holding an identifier.
+    /// @param identifier_len Length of the identifier.
+    /// @param identifier_type Type of the identifier (hardware address or
+    /// DUID).
+    /// @param ipv4_subnet_id Identifier of the IPv4 subnet to which the host
+    /// is connected.
+    /// @param ipv6_subnet_id Identifier of the IPv6 subnet to which the host
+    /// is connected.
+    /// @param ipv4_reservation An IPv4 address reserved for the client. If
+    /// this address is set to 0, there is no reservation.
+    /// @param hostname Hostname to be allocated to both DHCPv4 and DHCPv6
+    /// clients. This is empty string if hostname is not allocated.
+    /// @param dhcp4_client_classes A string holding DHCPv4 client class names
+    /// separated by commas. The names get trimmed by this constructor.
+    /// @param dhcp6_client_classes A string holding DHCPv6 client class names
+    /// separated by commas. The names get trimmed by this constructor.
+    ///
+    /// @throw BadValue if the provided values are invalid. In particular,
+    /// if the identifier is invalid.
+    Host(const uint8_t* identifier, const size_t identifier_len,
+         const IdentifierType& identifier_type,
+         const SubnetID ipv4_subnet_id, const SubnetID ipv6_subnet_id,
+         const asiolink::IOAddress& ipv4_reservation,
+         const std::string& hostname = "",
+         const std::string& dhcp4_client_classes = "",
+         const std::string& dhcp6_client_classes = "");
+
+    /// @brief Constructor.
+    ///
+    /// Creates @c Host object using an identifier in a textual format. This
+    /// is useful in cases when the reservation is specified in the server
+    /// configuration file, where:
+    /// - MAC address is specified as: "01:02:03:04:05:06"
+    /// - DUID is specified as: "010203040506abcd"
+    ///
+    /// @param identifier Identifier in the textual format. The expected formats
+    /// for the hardware address and DUID have been shown above.
+    /// @param identifier_name One of "hw-address" or "duid"
+    /// @param ipv4_subnet_id Identifier of the IPv4 subnet to which the host
+    /// is connected.
+    /// @param ipv6_subnet_id Identifier of the IPv6 subnet to which the host
+    /// is connected.
+    /// @param ipv4_reservation An IPv4 address reserved for the client. If
+    /// this address is set to 0, there is no reservation.
+    /// @param hostname Hostname to be allocated to both DHCPv4 and DHCPv6
+    /// clients. This is empty string if hostname is not allocated.
+    /// @param dhcp4_client_classes A string holding DHCPv4 client class names
+    /// separated by commas. The names get trimmed by this constructor.
+    /// @param dhcp6_client_classes A string holding DHCPv6 client class names
+    /// separated by commas. The names get trimmed by this constructor.
+    ///
+    /// @throw BadValue if the provided values are invalid. In particular,
+    /// if the identifier is invalid.
+    Host(const std::string& identifier, const std::string& identifier_name,
+         const SubnetID ipv4_subnet_id, const SubnetID ipv6_subnet_id,
+         const asiolink::IOAddress& ipv4_reservation,
+         const std::string& hostname = "",
+         const std::string& dhcp4_client_classes = "",
+         const std::string& dhcp6_client_classes = "");
+
+    /// @brief Replaces currently used identifier with a new identifier.
+    ///
+    /// This method initializes hardware address or DUID (@c hw_address_ or
+    /// @c duid_ respectively). The other (not initialized member) is
+    /// deallocated.
+    ///
+    /// This method is called by the @c Host constructor.
+    ///
+    /// @param identifier Pointer to the new identifier in the textual format.
+    /// @param len Length of the identifier that the @c identifier points to.
+    /// @param type Identifier type.
+    ///
+    /// @throw BadValue if the identifier is invalid.
+    void setIdentifier(const uint8_t* identifier, const size_t len,
+                       const IdentifierType& type);
+
+    /// @brief Replaces currently used identifier with a new identifier.
+    ///
+    /// This method initializes hardware address or DUID (@c hw_address_ or
+    /// @c duid_ respectively). The other (not initialized member) is
+    /// deallocated.
+    ///
+    /// This method is called by the @c Host constructor.
+    ///
+    /// @param identifier Pointer to the new identifier in the textual format.
+    /// @param name One of "hw-address" or "duid".
+    ///
+    /// @throw BadValue if the identifier is invalid.
+    void setIdentifier(const std::string& identifier, const std::string& name);
+
+    /// @brief Returns hardware address for which the reservations are made.
+    ///
+    /// @return Pointer to the @c HWAddr structure or null if the reservation
+    /// is not associated with a hardware address.
+    HWAddrPtr getHWAddress() const {
+        return (hw_address_);
+    }
+
+    /// @brief Returns DUID for which the reservations are made.
+    ///
+    /// @return Pointer to the @c DUID structure or null if the reservation
+    /// is not associated with a DUID.
+    DuidPtr getDuid() const {
+        return (duid_);
+    }
+
+    /// @brief Sets new IPv4 subnet identifier.
+    ///
+    /// @param ipv4_subnet_id New subnet identifier.
+    void setIPv4SubnetID(const SubnetID ipv4_subnet_id) {
+        ipv4_subnet_id_ = ipv4_subnet_id;
+    }
+
+    /// @brief Sets new IPv6 subnet identifier.
+    ///
+    /// @param ipv6_subnet_id New subnet identifier.
+    void setIPv6SubnetID(const SubnetID ipv6_subnet_id) {
+        ipv6_subnet_id_ = ipv6_subnet_id;
+    }
+
+    /// @brief Returns subnet identifier for IPv4 reservation.
+    SubnetID getIPv4SubnetID() const {
+        return (ipv4_subnet_id_);
+    }
+
+    /// @brief Returns subnet identifier for IPv6 reservations.
+    SubnetID getIPv6SubnetID() const {
+        return (ipv6_subnet_id_);
+    }
+
+    /// @brief Sets new IPv4 reservation.
+    ///
+    /// The new reservation removes a previous reservation.
+    ///
+    /// @param address Address to be reserved for the client.
+    void setIPv4Reservation(const asiolink::IOAddress& address) {
+        ipv4_reservation_ = address;
+    }
+
+    /// @brief Returns reserved IPv4 address.
+    ///
+    /// @return IPv4 address or 0.0.0.0 if no IPv4 reservation specified.
+    const asiolink::IOAddress& getIPv4Reservation() const {
+        return (ipv4_reservation_);
+    }
+
+    /// @brief Adds new IPv6 reservation.
+    ///
+    /// @param reservation New IPv6 reservation to be appended.
+    void addReservation(const IPv6Resrv& reservation);
+
+    /// @brief Returns IPv6 reservations of a specified type.
+    ///
+    /// @param type Type of the reservations to be returned (NA or PD).
+    ///
+    /// @return A range of iterators pointing to the reservations of
+    /// the specified type.
+    IPv6ResrvRange getIPv6Reservations(const IPv6Resrv::Type& type) const;
+
+    /// @brief Sets new hostname.
+    ///
+    /// @param hostname New hostname.
+    void setHostname(const std::string& hostname) {
+        hostname_ = hostname;
+    }
+
+    /// @brief Returns reserved hostname.
+    const std::string& getHostname() const {
+        return (hostname_);
+    }
+
+    /// @brief Adds new client class for DHCPv4.
+    ///
+    /// @param class_name Class name.
+    void addClientClass4(const std::string& class_name) {
+        addClientClassInternal(dhcp4_client_classes_, class_name);
+    }
+
+    /// @brief Returns classes which DHCPv4 client is associated with.
+    const ClientClasses& getClientClasses4() const {
+        return (dhcp4_client_classes_);
+    }
+
+    /// @brief Adds new client class for DHCPv6.
+    ///
+    /// @param class_name Class name.
+    void addClientClass6(const std::string& class_name) {
+        addClientClassInternal(dhcp6_client_classes_, class_name);
+    }
+
+    /// @brief Returns classes which DHCPv6 client is associated with.
+    const ClientClasses& getClientClasses6() const {
+        return (dhcp6_client_classes_);
+    }
+
+private:
+
+    /// @brief Adds new client class for DHCPv4 or DHCPv6.
+    ///
+    /// This method is called internally by the @c addClientClass4 and
+    /// @c addClientClass6 functions. It adds the class of the specified name
+    /// to the supplied class set. The class names are trimmed before they are
+    /// added. Empty class names are ignored.
+    ///
+    /// @param [out] classes Set of classes to which the new class should be
+    /// inserted.
+    /// @param class_name Class name.
+    void addClientClassInternal(ClientClasses& classes,
+                                const std::string& class_name);
+
+    /// @brief Pointer to the hardware address associated with the reservations
+    /// for the host.
+    HWAddrPtr hw_address_;
+    /// @brief Pointer to the DUID associated with the reservations for the
+    /// host.
+    DuidPtr duid_;
+    /// @brief Subnet identifier for the DHCPv4 client.
+    SubnetID ipv4_subnet_id_;
+    /// @brief Subnet identifier for the DHCPv6 client.
+    SubnetID ipv6_subnet_id_;
+    /// @brief Reserved IPv4 address.
+    asiolink::IOAddress ipv4_reservation_;
+    /// @brief Collection of IPv6 reservations for the host.
+    IPv6ResrvCollection ipv6_reservations_;
+    /// @brief Name reserved for the host.
+    std::string hostname_;
+    /// @brief Collection of classes associated with a DHCPv4 client.
+    ClientClasses dhcp4_client_classes_;
+    /// @brief Collection of classes associated with a DHCPv6 client.
+    ClientClasses dhcp6_client_classes_;
+};
+
+}
+}
+
+#endif // HOST_H

+ 32 - 0
src/lib/dhcpsrv/subnet_id.h

@@ -0,0 +1,32 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef SUBNET_ID_H
+#define SUBNET_ID_H
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Defines unique IPv4 or IPv6 subnet identifier.
+///
+/// Each subnet for which the DHCP service has been configured is identifed
+/// by the unique value called subnet id. Right now it is represented as
+/// a simple unsiged integer. In the future it may be extended to more complex
+/// type.
+typedef uint32_t SubnetID;
+
+}
+}
+
+#endif // SUBNET_ID_H

+ 1 - 0
src/lib/dhcpsrv/tests/Makefile.am

@@ -65,6 +65,7 @@ libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc
 libdhcpsrv_unittests_SOURCES += d2_udp_unittest.cc
 libdhcpsrv_unittests_SOURCES += daemon_unittest.cc
 libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc
+libdhcpsrv_unittests_SOURCES += host_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_file_io.cc lease_file_io.h
 libdhcpsrv_unittests_SOURCES += lease_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc

+ 453 - 0
src/lib/dhcpsrv/tests/host_unittest.cc

@@ -0,0 +1,453 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <dhcpsrv/host.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
+namespace {
+
+// This test verifies that it is possible to create IPv6 address
+// reservation.
+TEST(IPv6ResrvTest, constructorAddress) {
+    IPv6Resrv resrv(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(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 invalid prefix is rejected.
+TEST(IPv6ResrvTest, constructorInvalidPrefix) {
+    // IPv4 address is invalid for IPv6 reservation.
+    EXPECT_THROW(IPv6Resrv(IOAddress("10.0.0.1"), 128), isc::BadValue);
+    // Multicast address is invalid for IPv6 reservation.
+    EXPECT_THROW(IPv6Resrv(IOAddress("ff02:1::2"), 128), isc::BadValue);
+}
+
+// This test verifies that invalid prefix length is rejected.
+TEST(IPv6ResrvTest, constructiorInvalidPrefixLength) {
+    ASSERT_NO_THROW(IPv6Resrv(IOAddress("2001:db8:1::"), 128));
+    EXPECT_THROW(IPv6Resrv(IOAddress("2001:db8:1::"), 129), isc::BadValue);
+    EXPECT_THROW(IPv6Resrv(IOAddress("2001:db8:1::"), 244), 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(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(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(IOAddress("10.0.0.1"), 128), isc::BadValue);
+    // IPv6 multicast address is invalid for IPv6 reservation.
+    EXPECT_THROW(resrv.set(IOAddress("ff02::1:2"), 128), isc::BadValue);
+    // Prefix length greater than 128 is invalid.
+    EXPECT_THROW(resrv.set(IOAddress("2001:db8:1::"), 129), isc::BadValue);
+}
+
+// This test checks that the equality operators work fine.
+TEST(IPv6ResrvTest, equal) {
+    EXPECT_TRUE(IPv6Resrv(IOAddress("2001:db8::"), 64) ==
+                IPv6Resrv(IOAddress("2001:db8::"), 64));
+
+    EXPECT_FALSE(IPv6Resrv(IOAddress("2001:db8::"), 64) !=
+                IPv6Resrv(IOAddress("2001:db8::"), 64));
+
+
+    EXPECT_TRUE(IPv6Resrv(IOAddress("2001:db8::1")) ==
+                IPv6Resrv(IOAddress("2001:db8::1")));
+    EXPECT_FALSE(IPv6Resrv(IOAddress("2001:db8::1")) !=
+                 IPv6Resrv(IOAddress("2001:db8::1")));
+
+
+    EXPECT_FALSE(IPv6Resrv(IOAddress("2001:db8::1")) ==
+                 IPv6Resrv(IOAddress("2001:db8::2")));
+    EXPECT_TRUE(IPv6Resrv(IOAddress("2001:db8::1")) !=
+                 IPv6Resrv(IOAddress("2001:db8::2")));
+
+    EXPECT_FALSE(IPv6Resrv(IOAddress("2001:db8::"), 64) ==
+                 IPv6Resrv(IOAddress("2001:db8::"), 48));
+    EXPECT_TRUE(IPv6Resrv(IOAddress("2001:db8::"), 64) !=
+                 IPv6Resrv(IOAddress("2001:db8::"), 48));
+
+}
+
+// This test verfies that it is possible to create a Host object
+// using hardware address in the textual format.
+TEST(HostTest, createFromHWAddrString) {
+    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"),
+                                        "somehost.example.org")));
+    // 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());
+
+    // 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("010203040506", "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(HostTest, createFromDUIDString) {
+    boost::scoped_ptr<Host> 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());
+
+    // 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(HostTest, createFromHWAddrBinary) {
+    boost::scoped_ptr<Host> 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")));
+    // 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());
+}
+
+// This test verifies that it is possible to create a Host object using
+// DUID in the binary format.
+TEST(HostTest, createFromDuidBinary) {
+    boost::scoped_ptr<Host> 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());
+}
+
+// Test that it is possible to replace an identifier for a particular
+// Host instance (HW address -> DUID and vice versa) with a new
+// indentifier in the textual format.
+TEST(HostTest, setIdentifierString) {
+    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"),
+                                        "me.example.com")));
+    // Initially, there should be a HW address, but not a DUID set.
+    ASSERT_TRUE(host->getHWAddress());
+    ASSERT_FALSE(host->getDuid());
+
+    // Now, use a DUID as identifier.
+    ASSERT_NO_THROW(host->setIdentifier("aabbccddee", "duid"));
+
+    // Verify that the DUID is correct.
+    DuidPtr duid = host->getDuid();
+    ASSERT_TRUE(duid);
+    EXPECT_EQ("aa:bb:cc:dd:ee", duid->toText());
+    // HW address should be not set.
+    EXPECT_FALSE(host->getHWAddress());
+
+    // Now, let's do another way around.
+
+    ASSERT_NO_THROW(host->setIdentifier("09:08:07:06:05:04", "hw-address"));
+
+    // Verify that HW address is correct.
+    HWAddrPtr hw_addr = host->getHWAddress();
+    ASSERT_TRUE(hw_addr);
+    EXPECT_EQ("hwtype=1 09:08:07:06:05:04", hw_addr->toText());
+    // DUID should be not set.
+    EXPECT_FALSE(host->getDuid());
+}
+
+// Test that it is possible to replace an identifier for a particular
+// Host instance (HW address -> DUID and vice versa) with the new
+// identifier in the binary format.
+TEST(HostTest, setIdentifierBinary) {
+    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"),
+                                        "me.example.com")));
+    // Initially, there should be a HW address, but not a DUID set.
+    ASSERT_TRUE(host->getHWAddress());
+    ASSERT_FALSE(host->getDuid());
+
+    // Now, use a DUID as identifier.
+    const uint8_t duid_data[] = {
+        0xaa, 0xbb, 0xcc, 0xdd, 0xee
+    };
+    ASSERT_NO_THROW(host->setIdentifier(duid_data, sizeof(duid_data),
+                                        Host::IDENT_DUID));
+
+    // Verify that the DUID is correct.
+    DuidPtr duid = host->getDuid();
+    ASSERT_TRUE(duid);
+    EXPECT_EQ("aa:bb:cc:dd:ee", duid->toText());
+    // HW address should be not set.
+    EXPECT_FALSE(host->getHWAddress());
+
+    // Now, let's do another way around.
+
+    const uint8_t hwaddr_data[] = {
+        9, 8, 7, 6, 5, 4
+    };
+    ASSERT_NO_THROW(host->setIdentifier(hwaddr_data, sizeof(hwaddr_data),
+                                        Host::IDENT_HWADDR));
+
+    // Verify that HW address is correct.
+    HWAddrPtr hw_addr = host->getHWAddress();
+    ASSERT_TRUE(hw_addr);
+    EXPECT_EQ("hwtype=1 09:08:07:06:05:04", hw_addr->toText());
+    // DUID should be not set.
+    EXPECT_FALSE(host->getDuid());
+}
+
+/// @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.
+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);
+}
+
+// This test verifies that the IPv6 reservations of a different type can
+// be added for the host.
+TEST(HostTest, addReservations) {
+    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"))));
+
+    // Add 4 reservations: 2 for NAs, 2 for PDs.
+    ASSERT_NO_THROW(
+        host->addReservation(IPv6Resrv(IOAddress("2001:db8:1::cafe")));
+        host->addReservation(IPv6Resrv(IOAddress("2001:db8:1:1::"), 64));
+        host->addReservation(IPv6Resrv(IOAddress("2001:db8:1:2::"), 64));
+        host->addReservation(IPv6Resrv(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(IOAddress("2001:db8:1::cafe")),
+                                  addresses));
+    EXPECT_TRUE(reservationExists(IPv6Resrv(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(IOAddress("2001:db8:1:1::"), 64),
+                                  prefixes));
+    EXPECT_TRUE(reservationExists(IPv6Resrv(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(HostTest, setValues) {
+    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"),
+                                        "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());
+
+    host->setIPv4SubnetID(SubnetID(123));
+    host->setIPv6SubnetID(SubnetID(234));
+    host->setIPv4Reservation(IOAddress("10.0.0.1"));
+    host->setHostname("other-host.example.org");
+
+    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());
+}
+
+// Test that Host constructors initialize client classes from string.
+TEST(HostTest, clientClassesFromConstructor) {
+    boost::scoped_ptr<Host> 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(HostTest, addClientClasses) {
+    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"))));
+
+    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"));
+}
+
+} // end of anonymous namespace