Browse Source

Merge branch 'master' into trac2504

Mukund Sivaraman 12 years ago
parent
commit
835553eb30
52 changed files with 3347 additions and 889 deletions
  1. 16 0
      ChangeLog
  2. 1 1
      configure.ac
  3. 2 2
      doc/Doxyfile
  4. 2 2
      doc/guide/bind10-guide.xml
  5. 1 1
      src/bin/auth/tests/auth_srv_unittest.cc
  6. 2 1
      src/bin/zonemgr/zonemgr.py.in
  7. 1 1
      src/bin/zonemgr/zonemgr_messages.mes
  8. 15 0
      src/lib/asiolink/io_address.cc
  9. 21 2
      src/lib/asiolink/io_address.h
  10. 41 0
      src/lib/asiolink/tests/io_address_unittest.cc
  11. 1 1
      src/lib/dhcp/Makefile.am
  12. 12 12
      src/lib/dhcp/duid.cc
  13. 30 19
      src/lib/dhcp/duid.h
  14. 1 1
      src/lib/dhcpsrv/Makefile.am
  15. 13 0
      src/lib/dhcpsrv/database_backends.dox
  16. 8 3
      src/lib/dhcpsrv/dhcpdb_create.mysql
  17. 42 11
      src/lib/dhcpsrv/lease_mgr.cc
  18. 151 76
      src/lib/dhcpsrv/lease_mgr.h
  19. 0 0
      src/lib/dhcpsrv/libdhcpsrv.dox
  20. 2 3
      src/lib/dhcpsrv/memfile_lease_mgr.h
  21. 896 316
      src/lib/dhcpsrv/mysql_lease_mgr.cc
  22. 232 25
      src/lib/dhcpsrv/mysql_lease_mgr.h
  23. 1 1
      src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
  24. 356 26
      src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
  25. 1 1
      src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
  26. 851 253
      src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
  27. 6 4
      src/lib/dhcpsrv/tests/schema_copy.h
  28. 4 0
      src/lib/dns/Makefile.am
  29. 2 1
      src/lib/dns/gen-rdatacode.py.in
  30. 1 1
      src/lib/dns/python/tests/rdata_python_test.py
  31. 98 0
      src/lib/dns/rdata/generic/detail/char_string.cc
  32. 63 0
      src/lib/dns/rdata/generic/detail/char_string.h
  33. 74 49
      src/lib/dns/rdata/generic/detail/txt_like.h
  34. 20 6
      src/lib/dns/rdata/generic/spf_99.cc
  35. 3 1
      src/lib/dns/rdata/generic/spf_99.h
  36. 16 2
      src/lib/dns/rdata/generic/txt_16.cc
  37. 3 1
      src/lib/dns/rdata/generic/txt_16.h
  38. 1 0
      src/lib/dns/tests/Makefile.am
  39. 147 0
      src/lib/dns/tests/rdata_char_string_unittest.cc
  40. 173 39
      src/lib/dns/tests/rdata_txt_like_unittest.cc
  41. 3 3
      src/lib/dns/tests/testdata/rdata_txt_fromWire1
  42. 1 1
      src/lib/util/python/gen_wiredata.py.in
  43. 1 1
      tests/tools/perfdhcp/Makefile.am
  44. 0 10
      tests/tools/perfdhcp/templates/Makefile.am
  45. 5 1
      tests/tools/perfdhcp/tests/Makefile.am
  46. 23 11
      tests/tools/perfdhcp/tests/test_control_unittest.cc
  47. 0 0
      tests/tools/perfdhcp/tests/testdata/.gitignore
  48. 4 0
      tests/tools/perfdhcp/tests/testdata/Makefile.am
  49. 0 0
      tests/tools/perfdhcp/tests/testdata/discover-example.hex
  50. 0 0
      tests/tools/perfdhcp/tests/testdata/request4-example.hex
  51. 0 0
      tests/tools/perfdhcp/tests/testdata/request6-example.hex
  52. 0 0
      tests/tools/perfdhcp/tests/testdata/solicit-example.hex

+ 16 - 0
ChangeLog

@@ -1,3 +1,19 @@
+518.	[func]		stephen
+	Extend DHCP MySQL backend to handle IPv4 addresses.
+	(Trac #2404, git ce7db48d3ff5d5aad12b1da5e67ae60073cb2607)
+
+517.	[func]		stephen
+	Added IOAddress::toBytes() to get byte representation of address.
+	Also added convenience methods for V4/V6 address determination.
+	(Trac #2396, git c23f87e8ac3ea781b38d688f8f7b58539f85e35a)
+
+516.	[bug]		marcin
+	Fixed 'make distcheck' failure when running perfdhcp unit tests.
+	The unit tests used to read files from the folder specified
+	with the path relative to current folder, thus when the test was
+	run from a different folder the files could not be found.
+	(Trac #2479, git 4e8325e1b309f1d388a3055ec1e1df98c377f383)
+
 515.	[bug]		jinmei
 	The in-memory data source now accepts an RRSIG provided without
 	a covered RRset in loading.  A subsequent query for its owner name

+ 1 - 1
configure.ac

@@ -1308,7 +1308,7 @@ AC_CONFIG_FILES([Makefile
                  tests/tools/badpacket/tests/Makefile
                  tests/tools/perfdhcp/Makefile
                  tests/tools/perfdhcp/tests/Makefile
-                 tests/tools/perfdhcp/templates/Makefile
+                 tests/tools/perfdhcp/tests/testdata/Makefile
                  dns++.pc
                ])
 AC_OUTPUT([doc/version.ent

+ 2 - 2
doc/Doxyfile

@@ -580,8 +580,8 @@ INPUT                  = ../src/lib/exceptions ../src/lib/cc \
     ../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
     ../src/bin/sockcreator/ ../src/lib/util/ ../src/lib/util/io/ \
     ../src/lib/util/threads/ ../src/lib/resolve ../src/lib/acl \
-    ../src/lib/statistics ../src/bin/dhcp6 ../src/lib/dhcp ../src/bin/dhcp4 \
-    ../tests/tools/perfdhcp devel
+    ../src/lib/statistics ../src/bin/dhcp6 ../src/lib/dhcp ../src/lib/dhcpsrv \
+    ../src/bin/dhcp4 ../tests/tools/perfdhcp devel
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is

+ 2 - 2
doc/guide/bind10-guide.xml

@@ -472,7 +472,7 @@ var/
       <title>Packages</title>
 
       <para>
-        Some operating systems or softare package vendors may
+        Some operating systems or software package vendors may
         provide ready-to-use, pre-built software packages for
         the BIND 10 suite.
         Installing a pre-built package means you do not need to
@@ -2157,7 +2157,7 @@ AND_MATCH := "ALL": [ RULE_RAW, RULE_RAW, ... ]
 	you indicate that the system is not usable without the
 	component and if such component fails, the system shuts
 	down no matter when the failure happened.  This is the
-	behaviour of the core components (the ones you can't turn
+	behavior of the core components (the ones you can't turn
 	off), but you can declare any other components as core as
 	well if you wish (but you can turn these off, they just
 	can't fail).

+ 1 - 1
src/bin/auth/tests/auth_srv_unittest.cc

@@ -225,7 +225,7 @@ createBuiltinVersionResponse(const qid_t qid, vector<uint8_t>& data) {
     message.setHeaderFlag(Message::HEADERFLAG_AA);
     RRsetPtr rrset_version = RRsetPtr(new RRset(version_name, RRClass::CH(),
                                                 RRType::TXT(), RRTTL(0)));
-    rrset_version->addRdata(generic::TXT(PACKAGE_STRING));
+    rrset_version->addRdata(generic::TXT("\"" PACKAGE_STRING "\""));
     message.addRRset(Message::SECTION_ANSWER, rrset_version);
 
     RRsetPtr rrset_version_ns = RRsetPtr(new RRset(apex_name, RRClass::CH(),

+ 2 - 1
src/bin/zonemgr/zonemgr.py.in

@@ -193,7 +193,8 @@ class ZonemgrRefresh:
     def zone_handle_notify(self, zone_name_class, master):
         """Handle zone notify"""
         if (self._zone_not_exist(zone_name_class)):
-            logger.error(ZONEMGR_UNKNOWN_ZONE_NOTIFIED, zone_name_class[0], zone_name_class[1])
+            logger.error(ZONEMGR_UNKNOWN_ZONE_NOTIFIED, zone_name_class[0],
+                         zone_name_class[1], master)
             raise ZonemgrException("[b10-zonemgr] Notified zone (%s, %s) "
                                    "doesn't belong to zonemgr" % zone_name_class)
         self._set_zone_notifier_master(zone_name_class, master)

+ 1 - 1
src/bin/zonemgr/zonemgr_messages.mes

@@ -138,7 +138,7 @@ zone, or, if this error appears without the administrator giving transfer
 commands, it can indicate an error in the program, as it should not have
 initiated transfers of unknown zones on its own.
 
-% ZONEMGR_UNKNOWN_ZONE_NOTIFIED notified zone %1 (class %2) is not known to the zone manager
+% ZONEMGR_UNKNOWN_ZONE_NOTIFIED notified zone %1/%2 from %3 is not known to the zone manager
 A NOTIFY was received but the zone that was the subject of the operation
 is not being managed by the zone manager.  This may indicate an error
 in the program (as the operation should not have been initiated if this

+ 15 - 0
src/lib/asiolink/io_address.cc

@@ -76,6 +76,21 @@ IOAddress::fromBytes(short family, const uint8_t* data) {
     return IOAddress(string(addr_str));
 }
 
+std::vector<uint8_t>
+IOAddress::toBytes() const {
+    if (asio_address_.is_v4()) {
+        const asio::ip::address_v4::bytes_type bytes4 =
+            asio_address_.to_v4().to_bytes();
+        return (std::vector<uint8_t>(bytes4.begin(), bytes4.end()));
+    }
+
+    // Not V4 address, so must be a V6 address (else we could never construct
+    // this object).
+    const asio::ip::address_v6::bytes_type bytes6 =
+        asio_address_.to_v6().to_bytes();
+    return (std::vector<uint8_t>(bytes6.begin(), bytes6.end()));
+}
+
 short
 IOAddress::getFamily() const {
     if (asio_address_.is_v4()) {

+ 21 - 2
src/lib/asiolink/io_address.h

@@ -24,6 +24,7 @@
 
 #include <functional>
 #include <string>
+#include <vector>
 
 #include <exceptions/exceptions.h>
 
@@ -103,6 +104,19 @@ public:
     /// \return AF_INET for IPv4 or AF_INET6 for IPv6.
     short getFamily() const;
 
+    /// \brief Convenience function to check for an IPv4 address
+    ///
+    /// \return true if the address is a V4 address
+    bool isV4() const {
+        return (asio_address_.is_v4());
+    }
+
+    /// \brief Convenience function to check for an IPv6 address
+    ///
+    /// \return true if the address is a V6 address
+    bool isV6() const {
+        return (asio_address_.is_v6());
+    }
 
     /// \brief Creates an address from over wire data.
     ///
@@ -110,8 +124,13 @@ public:
     /// \param data pointer to first char of data
     ///
     /// \return Created IOAddress object
-    static IOAddress
-    fromBytes(short family, const uint8_t* data);
+    static IOAddress fromBytes(short family, const uint8_t* data);
+
+    /// \brief Return address as set of bytes
+    ///
+    /// \return Contents of the address as a set of bytes in network-byte
+    ///         order.
+    std::vector<uint8_t> toBytes() const;
 
     /// \brief Compare addresses for equality
     ///

+ 41 - 0
src/lib/asiolink/tests/io_address_unittest.cc

@@ -18,7 +18,9 @@
 #include <asiolink/io_error.h>
 #include <asiolink/io_address.h>
 
+#include <algorithm>
 #include <cstring>
+#include <vector>
 
 using namespace isc::asiolink;
 
@@ -84,6 +86,45 @@ TEST(IOAddressTest, fromBytes) {
     EXPECT_EQ(addr.toText(), IOAddress("192.0.2.3").toText());
 }
 
+TEST(IOAddressTest, toBytesV4) {
+    // Address and network byte-order representation of the address.
+    const char* V4STRING = "192.0.2.1";
+    uint8_t V4[] = {0xc0, 0x00, 0x02, 0x01};
+
+    std::vector<uint8_t> actual = IOAddress(V4STRING).toBytes();
+    ASSERT_EQ(sizeof(V4), actual.size());
+    EXPECT_TRUE(std::equal(actual.begin(), actual.end(), V4));
+}
+
+TEST(IOAddressTest, toBytesV6) {
+    // Address and network byte-order representation of the address.
+    const char* V6STRING = "2001:db8:1::dead:beef";
+    uint8_t V6[] = {
+        0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef 
+    };
+
+    std::vector<uint8_t> actual = IOAddress(V6STRING).toBytes();
+    ASSERT_EQ(sizeof(V6), actual.size());
+    EXPECT_TRUE(std::equal(actual.begin(), actual.end(), V6));
+}
+
+TEST(IOAddressTest, isV4) {
+    const IOAddress address4("192.0.2.1");
+    const IOAddress address6("2001:db8:1::dead:beef");
+
+    EXPECT_TRUE(address4.isV4());
+    EXPECT_FALSE(address6.isV4());
+}
+
+TEST(IOAddressTest, isV6) {
+    const IOAddress address4("192.0.2.1");
+    const IOAddress address6("2001:db8:1::dead:beef");
+
+    EXPECT_FALSE(address4.isV6());
+    EXPECT_TRUE(address6.isV6());
+}
+
 TEST(IOAddressTest, uint32) {
     IOAddress addr1("192.0.2.5");
 

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

@@ -40,7 +40,7 @@ libb10_dhcp___la_LIBADD   = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 libb10_dhcp___la_LIBADD  += $(top_builddir)/src/lib/util/libb10-util.la
 libb10_dhcp___la_LDFLAGS  = -no-undefined -version-info 2:0:0
 
-EXTRA_DIST  = README
+EXTRA_DIST  = README libdhcp++.dox
 
 if USE_CLANGPP
 # Disable unused parameter warning caused by some of the

+ 12 - 12
src/lib/dhcp/duid.cc

@@ -33,7 +33,7 @@ DUID::DUID(const std::vector<uint8_t>& duid) {
     }
 }
 
-DUID::DUID(const uint8_t * data, size_t len) {
+DUID::DUID(const uint8_t* data, size_t len) {
     if (len > MAX_DUID_LEN) {
         isc_throw(OutOfRange, "DUID too large");
     }
@@ -72,36 +72,36 @@ std::string DUID::toText() const {
     return (tmp.str());
 }
 
-bool DUID::operator == (const DUID& other) const {
+bool DUID::operator==(const DUID& other) const {
     return (this->duid_ == other.duid_);
 }
 
-bool DUID::operator != (const DUID& other) const {
+bool DUID::operator!=(const DUID& other) const {
     return (this->duid_ != other.duid_);
 }
 
-/// constructor based on vector<uint8_t>
+// Constructor based on vector<uint8_t>
 ClientId::ClientId(const std::vector<uint8_t>& clientid)
-    :DUID(clientid) {
+    : DUID(clientid) {
 }
 
-/// constructor based on C-style data
+// Constructor based on C-style data
 ClientId::ClientId(const uint8_t *clientid, size_t len)
-    :DUID(clientid, len) {
+    : DUID(clientid, len) {
 }
 
-/// @brief returns a copy of client-id data
+// Returns a copy of client-id data
 const std::vector<uint8_t> ClientId::getClientId() const {
     return (duid_);
 }
 
-// compares two client-ids
-bool ClientId::operator == (const ClientId& other) const {
+// Compares two client-ids
+bool ClientId::operator==(const ClientId& other) const {
     return (this->duid_ == other.duid_);
 }
 
-// compares two client-ids
-bool ClientId::operator != (const ClientId& other) const {
+// Compares two client-ids
+bool ClientId::operator!=(const ClientId& other) const {
     return (this->duid_ != other.duid_);
 }
 

+ 30 - 19
src/lib/dhcp/duid.h

@@ -45,13 +45,13 @@ class DUID {
         DUID_MAX          ///< not a real type, just maximum defined value + 1
     } DUIDType;
 
-    /// @brief creates a DUID
+    /// @brief Constructor from vector
     DUID(const std::vector<uint8_t>& duid);
 
-    /// @brief creates a DUID
-    DUID(const uint8_t *duid, size_t len);
+    /// @brief Constructor from array and array size
+    DUID(const uint8_t* duid, size_t len);
 
-    /// @brief returns a const reference to the actual DUID value
+    /// @brief Returns a const reference to the actual DUID value
     ///
     /// Note: For safety reasons, this method returns a copy of data as
     /// otherwise the reference would be only valid as long as the object that
@@ -60,47 +60,58 @@ class DUID {
     /// (e.g. storeSelf()) that will avoid data copying.
     const std::vector<uint8_t> getDuid() const;
 
-    /// @brief returns DUID type
+    /// @brief Returns the DUID type
     DUIDType getType() const;
 
-    /// returns textual prepresentation (e.g. 00:01:02:03:ff)
+    /// @brief Returns textual representation of a DUID (e.g. 00:01:02:03:ff)
     std::string toText() const;
 
-    /// compares two DUIDs
+    /// @brief Compares two DUIDs for equality
     bool operator==(const DUID& other) const;
 
-    /// compares two DUIDs
+    /// @brief Compares two DUIDs for inequality
     bool operator!=(const DUID& other) const;
 
  protected:
-    /// the actual content of the DUID
+    /// The actual content of the DUID
     std::vector<uint8_t> duid_;
 };
 
+/// @brief Shared pointer to a DUID
 typedef boost::shared_ptr<DUID> DuidPtr;
 
+
+
 /// @brief Holds Client identifier or client IPv4 address
 ///
 /// This class is intended to be a generic IPv4 client identifier. It can hold
 /// a client-id
 class ClientId : DUID {
- public:
+public:
+    /// @brief Maximum size of a client ID
+    ///
+    /// This is the same as the maximum size of the underlying DUID.
+    ///
+    /// @note RFC 2131 does not specify an upper length of a client ID, the
+    ///       value chosen here just being that of the underlying DUID.  For
+    ///       some backend database, there may be a possible (minor)
+    ///       performance enhancement if this were smaller.
+    static const size_t MAX_CLIENT_ID_LEN = DUID::MAX_DUID_LEN;
 
-    /// constructor based on vector<uint8_t>
+    /// @brief Constructor based on vector<uint8_t>
     ClientId(const std::vector<uint8_t>& clientid);
 
-    /// constructor based on C-style data
-    ClientId(const uint8_t *clientid, size_t len);
+    /// @brief Constructor based on array and array size
+    ClientId(const uint8_t* clientid, size_t len);
 
-    /// @brief returns reference to the client-id data
-    ///
+    /// @brief Returns reference to the client-id data
     const std::vector<uint8_t> getClientId() const;
 
-    // compares two client-ids
-    bool operator == (const ClientId& other) const;
+    /// @brief Compares two client-ids for equality
+    bool operator==(const ClientId& other) const;
 
-    // compares two client-ids
-    bool operator != (const ClientId& other) const;
+    /// @brief Compares two client-ids for inequality
+    bool operator!=(const ClientId& other) const;
 };
 
 }; // end of isc::dhcp namespace

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

@@ -48,5 +48,5 @@ libb10_dhcpsrv_la_CXXFLAGS += -Wno-unused-parameter
 endif
 
 # Distribute MySQL schema creation script and backend documentation
-EXTRA_DIST = dhcpdb_create.mysql database_backends.dox
+EXTRA_DIST = dhcpdb_create.mysql database_backends.dox libdhcpsrv.dox
 dist_pkgdata_DATA = dhcpdb_create.mysql

+ 13 - 0
src/lib/dhcpsrv/database_backends.dox

@@ -8,6 +8,17 @@
   the abstract isc::dhcp::LeaseMgr class.  This provides methods to
   create, retrieve, modify and delete leases in the database.
 
+  There are currently two available Lease Managers, MySQL and Memfile:
+
+  - The MySQL lease manager uses the freely available MySQL as its backend
+  database.  This is not included in BIND 10 DHCP by default:
+  the --with-dhcp-mysql switch must be supplied to "configure" for support
+  to be compiled into the software.
+  - Memfile is an in-memory lease database, with (currently) nothing being
+  written to persistent storage.  The long-term plans for the backend do
+  include the ability to store this on disk, but it is currently a
+  low-priority item.
+
   @section dhcpdb-instantiation Instantiation of Lease Managers
 
   A lease manager is instantiated through the LeaseMgrFactory class.  This
@@ -32,6 +43,8 @@
 
   - <b>type</b> - specifies the type of database backend.  The following values
   for the type keyword are supported:
+     - <B>memfile</b> - In-memory database.  Nothing is written to any
+       external storage, so this should not be used in production.
      - <b>mysql</b> - Use MySQL as the database
 
   The following sections list the database-specific keywords:

+ 8 - 3
src/lib/dhcpsrv/dhcpdb_create.mysql

@@ -34,7 +34,7 @@ CREATE TABLE lease4 (
     address INT UNSIGNED PRIMARY KEY NOT NULL,  # IPv4 address
     hwaddr VARBINARY(20),                       # Hardware address
     client_id VARBINARY(128),                   # Client ID
-    lease_time INT UNSIGNED,                    # Length of the lease (seconds)
+    valid_lifetime INT UNSIGNED,                # Length of the lease (seconds)
     expire TIMESTAMP,                           # Expiration time of the lease
     subnet_id INT UNSIGNED                      # Subnet identification
     ) ENGINE = INNODB;
@@ -43,7 +43,7 @@ CREATE TABLE lease4 (
 # N.B. The use of a VARCHAR for the address is temporary for development:
 # it will eventually be replaced by BINARY(16).
 CREATE TABLE lease6 (
-    address VARCHAR(40) PRIMARY KEY NOT NULL,   # IPv6 address
+    address VARCHAR(39) PRIMARY KEY NOT NULL,   # IPv6 address
     duid VARBINARY(128),                        # DUID
     valid_lifetime INT UNSIGNED,                # Length of the lease (seconds)
     expire TIMESTAMP,                           # Expiration time of the lease
@@ -72,12 +72,17 @@ COMMIT;
 # This table is only modified during schema upgrades.  For historical reasons
 # (related to the names of the columns in the BIND 10 DNS database file), the
 # first column is called "version" and not "major".
+#
+# NOTE: this MUST be kept in step with src/lib/dhcpsrv/tests/schema_copy.h,
+#       which defines the schema for the unit tests.  If you are updating
+#       the version number, the schema has changed: please ensure that
+#       schema_copy.h has been updated as well.
 CREATE TABLE schema_version (
     version INT PRIMARY KEY NOT NULL,       # Major version number
     minor INT                               # Minor version number
     );
 START TRANSACTION;
-INSERT INTO schema_version VALUES (0, 1);
+INSERT INTO schema_version VALUES (1, 0);
 COMMIT;
 
 # Notes:

+ 42 - 11
src/lib/dhcpsrv/lease_mgr.cc

@@ -29,15 +29,16 @@
 
 using namespace std;
 
-using namespace isc::dhcp;
+namespace isc {
+namespace dhcp {
 
-Lease6::Lease6(LeaseType type, const isc::asiolink::IOAddress& addr, DuidPtr duid,
-               uint32_t iaid, uint32_t preferred, uint32_t valid, uint32_t t1,
-               uint32_t t2, SubnetID subnet_id, uint8_t prefixlen)
-    :type_(type), addr_(addr), prefixlen_(prefixlen), iaid_(iaid), duid_(duid),
-     preferred_lft_(preferred), valid_lft_(valid), t1_(t1), t2_(t2),
-     subnet_id_(subnet_id), fixed_(false), fqdn_fwd_(false),
-     fqdn_rev_(false) {
+Lease6::Lease6(LeaseType type, const isc::asiolink::IOAddress& addr,
+               DuidPtr duid, uint32_t iaid, uint32_t preferred, uint32_t valid,
+               uint32_t t1, uint32_t t2, SubnetID subnet_id, uint8_t prefixlen)
+    : addr_(addr), type_(type), prefixlen_(prefixlen), iaid_(iaid), duid_(duid),
+      preferred_lft_(preferred), valid_lft_(valid), t1_(t1), t2_(t2),
+      subnet_id_(subnet_id), fixed_(false), fqdn_fwd_(false),
+      fqdn_rev_(false) {
     if (!duid) {
         isc_throw(InvalidOperation, "DUID must be specified for a lease");
     }
@@ -83,16 +84,46 @@ Lease6::toText() {
 }
 
 bool
+Lease4::operator==(const Lease4& other) const {
+    return (
+        addr_ == other.addr_ &&
+        ext_ == other.ext_ &&
+        hwaddr_ == other.hwaddr_ &&
+        *client_id_ == *other.client_id_ &&
+        t1_ == other.t1_ &&
+        t2_ == other.t2_ &&
+        valid_lft_ == other.valid_lft_ &&
+        cltt_ == other.cltt_ &&
+        subnet_id_ == other.subnet_id_ &&
+        fixed_ == other.fixed_ &&
+        hostname_ == other.hostname_ &&
+        fqdn_fwd_ == other.fqdn_fwd_ &&
+        fqdn_rev_ == other.fqdn_rev_ &&
+        comments_ == other.comments_
+    );
+}
+
+bool
 Lease6::operator==(const Lease6& other) const {
     return (
-        type_ == other.type_ &&
         addr_ == other.addr_ &&
+        type_ == other.type_ &&
         prefixlen_ == other.prefixlen_ &&
         iaid_ == other.iaid_ &&
         *duid_ == *other.duid_ &&
         preferred_lft_ == other.preferred_lft_ &&
         valid_lft_ == other.valid_lft_ &&
+        t1_ == other.t1_ &&
+        t2_ == other.t2_ &&
         cltt_ == other.cltt_ &&
-        subnet_id_ == other.subnet_id_
-        );
+        subnet_id_ == other.subnet_id_ &&
+        fixed_ == other.fixed_ &&
+        hostname_ == other.hostname_ &&
+        fqdn_fwd_ == other.fqdn_fwd_ &&
+        fqdn_rev_ == other.fqdn_rev_ &&
+        comments_ == other.comments_
+    );
 }
+
+} // namespace isc::dhcp
+} // namespace isc

+ 151 - 76
src/lib/dhcpsrv/lease_mgr.h

@@ -87,6 +87,13 @@ public:
         isc::Exception(file, line, what) {}
 };
 
+/// @brief Multiple lease records found where one expected
+class MultipleRecords : public Exception {
+public:
+    MultipleRecords(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
 /// @brief Attempt to update lease that was not there
 class NoSuchLease : public Exception {
 public:
@@ -94,6 +101,13 @@ public:
         isc::Exception(file, line, what) {}
 };
 
+/// @brief Data is truncated
+class DataTruncated : public Exception {
+public:
+    DataTruncated(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
 /// @brief Structure that holds a lease for IPv4 address
 ///
 /// For performance reasons it is a simple structure, not a class. If we chose
@@ -101,191 +115,253 @@ public:
 /// would be required. As this is a critical part of the code that will be used
 /// extensively, direct access is warranted.
 struct Lease4 {
+    /// @brief Maximum size of a hardware address
+    static const size_t HWADDR_MAX = 20;
+
+    /// @brief Constructor
+    ///
+    /// @param addr IPv4 address as unsigned 32-bit integer in network byte
+    ///        order.
+    /// @param hwaddr Hardware address buffer
+    /// @param hwaddr_len Length of hardware address buffer
+    /// @param clientid Client identification buffer
+    /// @param clientid_len Length of client identification buffer
+    /// @param valid_lft Lifetime of the lease
+    /// @param cltt Client last transmission time
+    /// @param subnet_id Subnet identification
+    Lease4(uint32_t addr, const uint8_t* hwaddr, size_t hwaddr_len,
+           const uint8_t* clientid, size_t clientid_len, uint32_t valid_lft,
+           time_t cltt, uint32_t subnet_id)
+        : addr_(addr), ext_(0), hwaddr_(hwaddr, hwaddr + hwaddr_len),
+          client_id_(new ClientId(clientid, clientid_len)), t1_(0), t2_(0),
+          valid_lft_(valid_lft), cltt_(cltt), subnet_id_(subnet_id),
+          fixed_(false), hostname_(), fqdn_fwd_(false), fqdn_rev_(false),
+          comments_()
+    {}
+
+    /// @brief Default Constructor
+    ///
+    /// Initialize fields that don't have a default constructor.
+    Lease4() : addr_(0) {}
+
+
     /// IPv4 address
     isc::asiolink::IOAddress addr_;
 
     /// @brief Address extension
     ///
-    /// It is envisaged that in some cases IPv4 address will be accompanied with some
-    /// additional data. One example of such use are Address + Port solutions (or
-    /// Port-restricted Addresses), where several clients may get the same address, but
-    /// different port ranges. This feature is not expected to be widely used.
-    /// Under normal circumstances, the value should be 0.
+    /// It is envisaged that in some cases IPv4 address will be accompanied
+    /// with some additional data. One example of such use are Address + Port
+    /// solutions (or Port-restricted Addresses), where several clients may get
+    /// the same address, but different port ranges. This feature is not
+    /// expected to be widely used.  Under normal circumstances, the value
+    /// should be 0.
     uint32_t ext_;
 
-    /// @brief hardware address
+    /// @brief Hardware address
     std::vector<uint8_t> hwaddr_;
 
-    /// @brief client identifier
+    /// @brief Client identifier
+    ///
+    /// @todo Should this be a pointer to a client ID or the ID itself?
+    ///       Compare with the DUID in the Lease6 structure.
     boost::shared_ptr<ClientId> client_id_;
 
-    /// @brief renewal timer
+    /// @brief Renewal timer
     ///
-    /// Specifies renewal time. Although technically it is a property of IA container,
-    /// not the address itself, since our data model does not define separate IA
-    /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
-    /// for the same IA, each must have consistent T1 and T2 values. Specified in
-    /// seconds since cltt.
+    /// Specifies renewal time. Although technically it is a property of the
+    /// IA container and not the address itself, since our data model does not
+    /// define a separate IA entity, we are keeping it in the lease. In the
+    /// case of multiple addresses/prefixes for the same IA, each must have
+    /// consistent T1 and T2 values. This is specified in seconds since cltt.
     uint32_t t1_;
 
-    /// @brief rebinding timer
+    /// @brief Rebinding timer
     ///
-    /// Specifies rebinding time. Although technically it is a property of IA container,
-    /// not the address itself, since our data model does not define separate IA
-    /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
-    /// for the same IA, each must have consistent T1 and T2 values. Specified in
-    /// seconds since cltt.
+    /// Specifies rebinding time. Although technically it is a property of the
+    /// IA container and not the address itself, since our data model does not
+    /// define a separate IA entity, we are keeping it in the lease. In the
+    /// case of multiple addresses/prefixes for the same IA, each must have
+    /// consistent T1 and T2 values. This is specified in seconds since cltt.
     uint32_t t2_;
 
-    /// @brief valid lifetime
+    /// @brief Valid lifetime
     ///
-    /// Expressed as number of seconds since cltt
+    /// Expressed as number of seconds since cltt.
     uint32_t valid_lft_;
 
-    /// @brief client last transmission time
+    /// @brief Client last transmission time
     ///
-    /// Specifies a timestamp, when last transmission from a client was received.
+    /// Specifies a timestamp giving the time when the last transmission from a
+    /// client was received.
     time_t cltt_;
 
     /// @brief Subnet identifier
     ///
-    /// Specifies subnet-id of the subnet that the lease belongs to
+    /// Specifies the identification of the subnet to which the lease belongs.
     SubnetID subnet_id_;
 
-    /// @brief Is this a fixed lease?
+    /// @brief Fixed lease?
     ///
     /// Fixed leases are kept after they are released/expired.
     bool fixed_;
 
-    /// @brief client hostname
+    /// @brief Client hostname
     ///
     /// This field may be empty
     std::string hostname_;
 
-    /// @brief did we update AAAA record for this lease?
+    /// @brief Forward zone updated?
+    ///
+    /// Set true if the DNS AAAA record for this lease has been updated.
     bool fqdn_fwd_;
 
-    /// @brief did we update PTR record for this lease?
+    /// @brief Reverse zone updated?
+    ///
+    /// Set true if the DNS PTR record for this lease has been updated.
     bool fqdn_rev_;
 
-    /// @brief Lease comments.
+    /// @brief Lease comments
     ///
     /// Currently not used. It may be used for keeping comments made by the
     /// system administrator.
     std::string comments_;
 
-    /// @todo: Add DHCPv4 failover related fields here
+    /// @brief Compare two leases for equality
+    ///
+    /// @param other lease6 object with which to compare
+    bool operator==(const Lease4& other) const;
 
-    /// @brief Constructor
+    /// @brief Compare two leases for inequality
     ///
-    /// Initialize fields that don't have a default constructor.
-    /// @todo Remove this
-    Lease4() : addr_(0) {}
+    /// @param other lease6 object with which to compare
+    bool operator!=(const Lease4& other) const {
+        return (!operator==(other));
+    }
+
+    /// @todo: Add DHCPv4 failover related fields here
 };
 
 /// @brief Pointer to a Lease4 structure.
 typedef boost::shared_ptr<Lease4> Lease4Ptr;
 
 /// @brief A collection of IPv4 leases.
-typedef std::vector< boost::shared_ptr<Lease4Ptr> > Lease4Collection;
+typedef std::vector<Lease4Ptr> Lease4Collection;
+
+
 
 /// @brief Structure that holds a lease for IPv6 address and/or prefix
 ///
-/// For performance reasons it is a simple structure, not a class. Had we chose to
-/// make it a class, all fields would have to be made private and getters/setters
+/// For performance reasons it is a simple structure, not a class. If we chose
+/// make it a class, all fields would have to made private and getters/setters
 /// would be required. As this is a critical part of the code that will be used
-/// extensively, direct access rather than through getters/setters is warranted.
+/// extensively, direct access is warranted.
 struct Lease6 {
+
+    /// @brief Type of lease contents
     typedef enum {
         LEASE_IA_NA, /// the lease contains non-temporary IPv6 address
         LEASE_IA_TA, /// the lease contains temporary IPv6 address
         LEASE_IA_PD  /// the lease contains IPv6 prefix (for prefix delegation)
     } LeaseType;
 
+    /// @brief Constructor
     Lease6(LeaseType type, const isc::asiolink::IOAddress& addr, DuidPtr duid,
            uint32_t iaid, uint32_t preferred, uint32_t valid, uint32_t t1,
            uint32_t t2, SubnetID subnet_id, uint8_t prefixlen_ = 0);
 
-    /// @brief specifies lease type (normal addr, temporary addr, prefix)
-    LeaseType type_;
-
-    /// IPv6 address
+    /// @brief IPv6 address
+    ///
+    /// IPv6 address or, in the case of a prefix delegation, the prefix.
     isc::asiolink::IOAddress addr_;
 
-    /// IPv6 prefix length (used only for PD)
+    /// @brief Lease type
+    ///
+    /// One of normal address, temporary address, or prefix.
+    LeaseType type_;
+
+    /// @brief IPv6 prefix length
+    ///
+    /// This is used only for prefix delegations and is ignored otherwise.
     uint8_t prefixlen_;
 
-    /// @brief IAID
+    /// @brief Identity Association Identifier (IAID)
     ///
-    /// Identity Association IDentifier. DHCPv6 stores all addresses and prefixes
-    /// in IA containers (IA_NA, IA_TA, IA_PD). Most containers may appear more
-    /// than once in a message. To differentiate between them, IAID field is present
+    /// DHCPv6 stores all addresses and prefixes in IA containers (IA_NA,
+    /// IA_TA, IA_PD). All containers may appear more than once in a message.
+    /// To differentiate between them, the IAID field is present
     uint32_t iaid_;
 
-    /// @brief client identifier
-    boost::shared_ptr<DUID> duid_;
+    /// @brief Client identifier
+    DuidPtr duid_;
 
     /// @brief preferred lifetime
     ///
-    /// This parameter specifies preferred lifetime since the lease was assigned/renewed
-    /// (cltt), expressed in seconds.
+    /// This parameter specifies the preferred lifetime since the lease was
+    /// assigned or renewed (cltt), expressed in seconds.
     uint32_t preferred_lft_;
 
     /// @brief valid lifetime
     ///
-    /// This parameter specified valid lifetime since the lease was assigned/renewed
-    /// (cltt), expressed in seconds.
+    /// This parameter specifies the valid lifetime since the lease waa
+    /// assigned/renewed (cltt), expressed in seconds.
     uint32_t valid_lft_;
 
     /// @brief T1 timer
     ///
-    /// Specifies renewal time. Although technically it is a property of IA container,
-    /// not the address itself, since our data model does not define separate IA
-    /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
-    /// for the same IA, each must have consistent T1 and T2 values. Specified in
-    /// seconds since cltt.
-    /// This value will also be useful for failover to calculate the next expected
-    /// client transmission time.
+    /// Specifies renewal time. Although technically it is a property of the
+    /// IA container and not the address itself, since our data model does not
+    /// define a separate IA entity, we are keeping it in the lease. In the
+    /// case of multiple addresses/prefixes for the same IA, each must have
+    /// consistent T1 and T2 values. This is specified in seconds since cltt.
+    /// The value will also be useful for failover to calculate the next
+    /// expected client transmission time.
     uint32_t t1_;
 
     /// @brief T2 timer
     ///
-    /// Specifies rebinding time. Although technically it is a property of IA container,
-    /// not the address itself, since our data model does not define separate IA
-    /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
-    /// for the same IA, each must have consistent T1 and T2 values. Specified in
-    /// seconds since cltt.
+    /// Specifies rebinding time. Although technically it is a property of the
+    /// IA container and not the address itself, since our data model does not
+    /// define a separate IA entity, we are keeping it in the lease. In the
+    /// case of multiple addresses/prefixes for the same IA, each must have
+    /// consistent T1 and T2 values. This is specified in seconds since cltt.
     uint32_t t2_;
 
-    /// @brief client last transmission time
+    /// @brief Client last transmission time
     ///
-    /// Specifies a timestamp, when last transmission from a client was received.
+    /// Specifies a timestamp giving the time when the last transmission from a
+    /// client was received.
     time_t cltt_;
 
     /// @brief Subnet identifier
     ///
-    /// Specifies subnet-id of the subnet that the lease belongs to
+    /// Specifies the identification of the subnet to which the lease belongs.
     SubnetID subnet_id_;
 
-    /// @brief Is this a fixed lease?
+    /// @brief Fixed lease?
     ///
     /// Fixed leases are kept after they are released/expired.
     bool fixed_;
 
-    /// @brief client hostname
+    /// @brief Client hostname
     ///
     /// This field may be empty
     std::string hostname_;
 
-    /// @brief did we update AAAA record for this lease?
+    /// @brief Forward zone updated?
+    ///
+    /// Set true if the DNS AAAA record for this lease has been updated.
     bool fqdn_fwd_;
 
-    /// @brief did we update PTR record for this lease?
+    /// @brief Reverse zone updated?
+    ///
+    /// Set true if the DNS PTR record for this lease has been updated.
     bool fqdn_rev_;
 
     /// @brief Lease comments
     ///
-    /// This field is currently not used.
+    /// Currently not used. It may be used for keeping comments made by the
+    /// system administrator.
     std::string comments_;
 
     /// @todo: Add DHCPv6 failover related fields here
@@ -311,13 +387,12 @@ struct Lease6 {
     bool operator!=(const Lease6& other) const {
         return (!operator==(other));
     }
-
 };
 
 /// @brief Pointer to a Lease6 structure.
 typedef boost::shared_ptr<Lease6> Lease6Ptr;
 
-/// @brief Const pointer to a Lease6 structure.
+/// @brief Pointer to a const Lease6 structure.
 typedef boost::shared_ptr<const Lease6> ConstLease6Ptr;
 
 /// @brief A collection of IPv6 leases.
@@ -335,7 +410,7 @@ typedef std::vector<Lease6Ptr> Lease6Collection;
 /// see the documentation of those classes for details.
 class LeaseMgr {
 public:
-    /// Client Hardware address
+    /// Client hardware address
     typedef std::vector<uint8_t> HWAddr;
 
     /// Database configuration parameter map

src/lib/dhcp/libdhcsrv.dox → src/lib/dhcpsrv/libdhcpsrv.dox


+ 2 - 3
src/lib/dhcpsrv/memfile_lease_mgr.h

@@ -199,12 +199,11 @@ public:
 
     /// @brief Returns backend name.
     ///
-    /// As there is no variation, in this case we return the type of the
-    /// backend.
+    /// For now, memfile can only store data in memory.
     ///
     /// @return Name of the backend.
     virtual std::string getName() const {
-        return ("memfile");
+        return ("memory");
     }
 
     /// @brief Returns description of the backend.

File diff suppressed because it is too large
+ 896 - 316
src/lib/dhcpsrv/mysql_lease_mgr.cc


+ 232 - 25
src/lib/dhcpsrv/mysql_lease_mgr.h

@@ -27,18 +27,22 @@ namespace dhcp {
 
 // Define the current database schema values
 
-const uint32_t CURRENT_VERSION_VERSION = 0;
-const uint32_t CURRENT_VERSION_MINOR = 1;
+const uint32_t CURRENT_VERSION_VERSION = 1;
+const uint32_t CURRENT_VERSION_MINOR = 0;
 
 
-// Forward declaration of the Lease6 exchange object.  This class is defined
+// Forward declaration of the Lease exchange objects.  These classes are defined
 // in the .cc file.
+class MySqlLease4Exchange;
 class MySqlLease6Exchange;
 
 
 /// @brief MySQL Lease Manager
 ///
-/// This is a concrete API for the backend for the MySQL database.
+/// This class provides the \ref isc::dhcp::LeaseMgr interface to the MySQL
+/// database.  Use of this backend presupposes that a MySQL database is
+/// available and that the Kea schema has been created within it.
+
 class MySqlLeaseMgr : public LeaseMgr {
 public:
     /// @brief Constructor
@@ -68,7 +72,7 @@ public:
     /// @brief Destructor (closes database)
     virtual ~MySqlLeaseMgr();
 
-    /// @brief Adds an IPv4 lease.
+    /// @brief Adds an IPv4 lease
     ///
     /// @param lease lease to be added
     ///
@@ -79,7 +83,7 @@ public:
     ///        failed.
     virtual bool addLease(const Lease4Ptr& lease);
 
-    /// @brief Adds an IPv6 lease.
+    /// @brief Adds an IPv6 lease
     ///
     /// @param lease lease to be added
     ///
@@ -106,7 +110,7 @@ public:
     /// @brief Returns an IPv4 lease for specified IPv4 address
     ///
     /// This method return a lease that is associated with a given address.
-    /// For other query types (by hardware addr, by DUID) there can be
+    /// For other query types (by hardware addr, by Client ID) there can be
     /// several leases in different subnets (e.g. for mobile clients that
     /// got address in different subnets). However, for a single address
     /// there can be only one lease, so this method returns a pointer to
@@ -115,6 +119,12 @@ public:
     /// @param addr address of the searched lease
     ///
     /// @return smart pointer to the lease (or NULL if a lease is not found)
+    ///
+    /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+    ///        fit into the space allocated for the result.  This indicates a
+    ///        programming error.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
     virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress& addr) const;
 
 
@@ -128,6 +138,12 @@ public:
     /// @param hwaddr hardware address of the client
     ///
     /// @return lease collection
+    ///
+    /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+    ///        fit into the space allocated for the result.  This indicates a
+    ///        programming error.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
     virtual Lease4Collection getLease4(const HWAddr& hwaddr) const;
 
     /// @brief Returns existing IPv4 leases for specified hardware address
@@ -140,6 +156,12 @@ public:
     /// @param subnet_id identifier of the subnet that lease must belong to
     ///
     /// @return a pointer to the lease (or NULL if a lease is not found)
+    ///
+    /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+    ///        fit into the space allocated for the result.  This indicates a
+    ///        programming error.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
     virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
                                 SubnetID subnet_id) const;
 
@@ -153,6 +175,12 @@ public:
     /// @param clientid client identifier
     ///
     /// @return lease collection
+    ///
+    /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+    ///        fit into the space allocated for the result.  This indicates a
+    ///        programming error.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
     virtual Lease4Collection getLease4(const ClientId& clientid) const;
 
     /// @brief Returns existing IPv4 lease for specified client-id
@@ -164,6 +192,12 @@ public:
     /// @param subnet_id identifier of the subnet that lease must belong to
     ///
     /// @return a pointer to the lease (or NULL if a lease is not found)
+    ///
+    /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+    ///        fit into the space allocated for the result.  This indicates a
+    ///        programming error.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
     virtual Lease4Ptr getLease4(const ClientId& clientid,
                                 SubnetID subnet_id) const;
 
@@ -179,6 +213,9 @@ public:
     ///
     /// @throw isc::BadValue record retrieved from database had an invalid
     ///        lease type field.
+    /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+    ///        fit into the space allocated for the result.  This indicates a
+    ///        programming error.
     /// @throw isc::dhcp::DbOperationError An operation on the open database has
     ///        failed.
     virtual Lease6Ptr getLease6(const isc::asiolink::IOAddress& addr) const;
@@ -194,6 +231,14 @@ public:
     /// @param iaid IA identifier
     ///
     /// @return smart pointer to the lease (or NULL if a lease is not found)
+    ///
+    /// @throw isc::BadValue record retrieved from database had an invalid
+    ///        lease type field.
+    /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+    ///        fit into the space allocated for the result.  This indicates a
+    ///        programming error.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
     virtual Lease6Collection getLease6(const DUID& duid,
                                        uint32_t iaid) const;
 
@@ -204,18 +249,35 @@ public:
     /// @param subnet_id subnet id of the subnet the lease belongs to
     ///
     /// @return smart pointer to the lease (or NULL if a lease is not found)
+    ///
+    /// @throw isc::BadValue record retrieved from database had an invalid
+    ///        lease type field.
+    /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+    ///        fit into the space allocated for the result.  This indicates a
+    ///        programming error.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
     virtual Lease6Ptr getLease6(const DUID& duid, uint32_t iaid,
                                 SubnetID subnet_id) const;
 
     /// @brief Updates IPv4 lease.
     ///
+    /// Updates the record of the lease in the database (as identified by the
+    /// address) with the data in the passed lease object.
+    ///
     /// @param lease4 The lease to be updated.
     ///
-    /// If no such lease is present, an exception will be thrown.
+    /// @throw isc::dhcp::NoSuchLease Attempt to update a lease that did not
+    ///        exist.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
     virtual void updateLease4(const Lease4Ptr& lease4);
 
     /// @brief Updates IPv6 lease.
     ///
+    /// Updates the record of the lease in the database (as identified by the
+    /// address) with the data in the passed lease object.
+    ///
     /// @param lease6 The lease to be updated.
     ///
     /// @throw isc::dhcp::NoSuchLease Attempt to update a lease that did not
@@ -226,13 +288,24 @@ public:
 
     /// @brief Deletes an IPv4 lease.
     ///
+    /// @todo Merge with deleteLease6: it is possible to determine whether
+    ///       an address is V4 or V6 from the IOAddress argument, so there
+    ///       is no need for separate V4 or V6 methods.
+    ///
     /// @param addr IPv4 address of the lease to be deleted.
     ///
     /// @return true if deletion was successful, false if no such lease exists
+    ///
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
     virtual bool deleteLease4(const isc::asiolink::IOAddress& addr);
 
     /// @brief Deletes an IPv6 lease.
     ///
+    /// @todo Merge with deleteLease4: it is possible to determine whether
+    ///       an address is V4 or V6 from the IOAddress argument, so there
+    ///       is no need for separate V4 or V6 methods.
+    ///
     /// @param addr IPv6 address of the lease to be deleted.
     ///
     /// @return true if deletion was successful, false if no such lease exists
@@ -343,12 +416,20 @@ public:
     ///
     /// The contents of the enum are indexes into the list of SQL statements
     enum StatementIndex {
+        DELETE_LEASE4,              // Delete from lease4 by address
         DELETE_LEASE6,              // Delete from lease6 by address
+        GET_LEASE4_ADDR,            // Get lease4 by address
+        GET_LEASE4_CLIENTID,        // Get lease4 by client ID
+        GET_LEASE4_CLIENTID_SUBID,  // Get lease4 by client ID & subnet ID
+        GET_LEASE4_HWADDR,          // Get lease4 by HW address
+        GET_LEASE4_HWADDR_SUBID,    // Get lease4 by HW address & subnet ID
         GET_LEASE6_ADDR,            // Get lease6 by address
         GET_LEASE6_DUID_IAID,       // Get lease6 by DUID and IAID
-        GET_LEASE6_DUID_IAID_SUBID, // Get lease6 by DUID, IAID and Subnet ID
+        GET_LEASE6_DUID_IAID_SUBID, // Get lease6 by DUID, IAID and subnet ID
         GET_VERSION,                // Obtain version number
+        INSERT_LEASE4,              // Add entry to lease4 table
         INSERT_LEASE6,              // Add entry to lease6 table
+        UPDATE_LEASE4,              // Update a Lease4 entry
         UPDATE_LEASE6,              // Update a Lease6 entry
         NUM_STATEMENTS              // Number of statements
     };
@@ -389,25 +470,150 @@ private:
     /// @throw DbOpenError Error opening the database
     void openDatabase();
 
-    /// @brief Binds Parameters and Executes
+    /// @brief Add Lease Common Code
+    ///
+    /// This method performs the common actions for both flavours (V4 and V6)
+    /// of the addLease method.  It binds the contents of the lease object to
+    /// the prepared statement and adds it to the database.
     ///
-    /// This method abstracts a lot of common processing from the getXxxx()
-    /// methods.  It binds the parameters passed to it to the appropriate
-    /// prepared statement, and binds the variables in the exchange6 object to
-    /// the output parameters of the statement.  It then executes the prepared
-    /// statement.
+    /// @param stindex Index of statemnent being executed
+    /// @param bind MYSQL_BIND array that has been created for the type
+    ///        of lease in question.
     ///
-    /// The data can be retrieved using mysql_stmt_fetch and the getLeaseData()
-    /// method on the exchange6 object.
+    /// @return true if the lease was added, false if it was not added because
+    ///         a lease with that address already exists in the database.
+    ///
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
+    bool addLeaseCommon(StatementIndex stindex, std::vector<MYSQL_BIND>& bind);
+
+    /// @brief Get Lease Collection Common Code
+    ///
+    /// This method performs the common actions for obtaining multiple leases
+    /// from the database.
+    ///
+    /// @param stindex Index of statement being executed
+    /// @param bind MYSQL_BIND array for input parameters
+    /// @param exchange Exchange object to use
+    /// @param lease LeaseCollection object returned.  Note that any leases in
+    ///        the collection when this method is called are not erased: the
+    ///        new data is appended to the end.
+    /// @param single If true, only a single data item is to be retrieved.
+    ///        If more than one is present, a MultipleRecords exception will
+    ///        be thrown.
+    ///
+    /// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
+    /// @throw isc::dhcp::MultipleRecords Multiple records were retrieved
+    ///        from the database where only one was expected.
+    template <typename Exchange, typename LeaseCollection>
+    void getLeaseCollection(StatementIndex stindex, MYSQL_BIND* bind,
+                            Exchange& exchange, LeaseCollection& result,
+                            bool single = false) const;
+
+    /// @brief Get Lease Collection
+    ///
+    /// Gets a collection of Lease4 objects.  This is just an interface to
+    /// the get lease collection common code.
+    ///
+    /// @param stindex Index of statement being executed
+    /// @param bind MYSQL_BIND array for input parameters
+    /// @param lease LeaseCollection object returned.  Note that any leases in
+    ///        the collection when this method is called are not erased: the
+    ///        new data is appended to the end.
+    ///
+    /// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
+    /// @throw isc::dhcp::MultipleRecords Multiple records were retrieved
+    ///        from the database where only one was expected.
+    void getLeaseCollection(StatementIndex stindex, MYSQL_BIND* bind,
+                            Lease4Collection& result) const {
+        getLeaseCollection(stindex, bind, exchange4_, result);
+    }
+
+    /// @brief Get Lease Collection
+    ///
+    /// Gets a collection of Lease6 objects.  This is just an interface to
+    /// the get lease collection common code.
+    ///
+    /// @param stindex Index of statement being executed
+    /// @param bind MYSQL_BIND array for input parameters
+    /// @param lease LeaseCollection object returned.  Note that any existing
+    ///        data in the collection is erased first.
+    ///
+    /// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
+    /// @throw isc::dhcp::MultipleRecords Multiple records were retrieved
+    ///        from the database where only one was expected.
+    void getLeaseCollection(StatementIndex stindex, MYSQL_BIND* bind,
+                            Lease6Collection& result) const {
+        getLeaseCollection(stindex, bind, exchange6_, result);
+    }
+
+    /// @brief Get Lease4 Common Code
+    ///
+    /// This method performs the common actions for the various getLease4()
+    /// methods.  It acts as an interface to the getLeaseCollection() method,
+    /// but retrieveing only a single lease.
+    ///
+    /// @param stindex Index of statement being executed
+    /// @param bind MYSQL_BIND array for input parameters
+    /// @param lease Lease4 object returned
+    void getLease(StatementIndex stindex, MYSQL_BIND* bind,
+                  Lease4Ptr& result) const;
+
+    /// @brief Get Lease6 Common Code
+    ///
+    /// This method performs the common actions for the various getLease46)
+    /// methods.  It acts as an interface to the getLeaseCollection() method,
+    /// but retrieveing only a single lease.
+    ///
+    /// @param stindex Index of statement being executed
+    /// @param bind MYSQL_BIND array for input parameters
+    /// @param lease Lease6 object returned
+    void getLease(StatementIndex stindex, MYSQL_BIND* bind,
+                   Lease6Ptr& result) const;
+
+    /// @brief Update lease common code
+    ///
+    /// Holds the common code for updating a lease.  It binds the parameters
+    /// to the prepared statement, executes it, then checks how many rows
+    /// were affected.
+    ///
+    /// @param stindex Index of prepared statement to be executed
+    /// @param bind Array of MYSQL_BIND objects representing the parameters.
+    ///        (Note that the number is determined by the number of parameters
+    ///        in the statement.)
+    /// @param lease Pointer to the lease object whose record is being updated.
+    ///
+    /// @throw NoSuchLease Could not update a lease because no lease matches
+    ///        the address given.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
+    template <typename LeasePtr>
+    void updateLeaseCommon(StatementIndex stindex, MYSQL_BIND* bind,
+                           const LeasePtr& lease);
+
+    /// @brief Delete lease common code
+    ///
+    /// Holds the common code for deleting a lease.  It binds the parameters
+    /// to the prepared statement, executes the statement and checks to
+    /// see how many rows were deleted.
     ///
     /// @param stindex Index of prepared statement to be executed
-    /// @param inbind Array of MYSQL_BIND objects representing the parameters.
+    /// @param bind Array of MYSQL_BIND objects representing the parameters.
     ///        (Note that the number is determined by the number of parameters
     ///        in the statement.)
     ///
+    /// @return true if one or more rows were deleted, false if none were
+    ///         deleted.
+    ///
     /// @throw isc::dhcp::DbOperationError An operation on the open database has
     ///        failed.
-    void bind6AndExecute(StatementIndex stindex, MYSQL_BIND* inbind) const;
+    bool deleteLease(StatementIndex stindex, MYSQL_BIND* bind);
 
     /// @brief Check Error and Throw Exception
     ///
@@ -433,14 +639,15 @@ private:
 
     // Members
 
-    /// Used for transfer of data to/from the database. This is a pointed-to
-    /// object as its contents may change in "const" calls, while the rest
-    /// of this object does not.  (At alternative would be to declare it as
-    /// "mutable".)
-    boost::scoped_ptr<MySqlLease6Exchange> exchange6_;
+    /// The exchange objects are used for transfer of data to/from the database.
+    /// They are pointed-to objects as the contents may change in "const" calls,
+    /// while the rest of this object does not.  (At alternative would be to
+    /// declare them as "mutable".)
+    boost::scoped_ptr<MySqlLease4Exchange> exchange4_; ///< Exchange object
+    boost::scoped_ptr<MySqlLease6Exchange> exchange6_; ///< Exchange object
     MYSQL*              mysql_;                 ///< MySQL context object
-    std::vector<std::string> text_statements_;  ///< Raw text of statements
     std::vector<MYSQL_STMT*> statements_;       ///< Prepared statements
+    std::vector<std::string> text_statements_;  ///< Raw text of statements
 };
 
 }; // end of isc::dhcp namespace

+ 1 - 1
src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012 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

+ 356 - 26
src/lib/dhcpsrv/tests/lease_mgr_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012 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
@@ -22,6 +22,8 @@
 #include <iostream>
 #include <sstream>
 
+#include <time.h>
+
 using namespace std;
 using namespace isc;
 using namespace isc::asiolink;
@@ -236,16 +238,12 @@ public:
 };
 
 namespace {
-// empty class for now, but may be extended once Addr6 becomes bigger
-class LeaseMgrTest : public ::testing::Test {
-public:
-    LeaseMgrTest() {
-    }
-};
 
-// This test checks if the LeaseMgr can be instantiated and that it
-// parses parameters string properly.
-TEST_F(LeaseMgrTest, getParameter) {
+/// @brief getParameter test
+///
+/// This test checks if the LeaseMgr can be instantiated and that it
+/// parses parameters string properly.
+TEST(LeaseMgr, getParameter) {
 
     LeaseMgr::ParameterMap pmap;
     pmap[std::string("param1")] = std::string("value1");
@@ -261,36 +259,368 @@ TEST_F(LeaseMgrTest, getParameter) {
 // are purely virtual, so we would only call ConcreteLeaseMgr methods.
 // Those methods are just stubs that do not return anything.
 
+/// @brief Lease4 Constructor Test
+///
+/// Lease4 is also defined in lease_mgr.h, so is tested in this file as well.
+// This test checks if the Lease4 structure can be instantiated correctly
+TEST(Lease4, Lease4Constructor) {
+
+    // Random values for the tests
+    const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
+    std::vector<uint8_t> hwaddr(HWADDR, HWADDR + sizeof(HWADDR));
+
+    const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
+    std::vector<uint8_t> clientid_vec(CLIENTID, CLIENTID + sizeof(CLIENTID));
+    ClientId clientid(clientid_vec);
+
+    // ...and a time
+    const time_t current_time = time(NULL);
+
+    // Other random constants. 
+    const uint32_t SUBNET_ID = 42;
+    const uint32_t VALID_LIFETIME = 500;
+
+    // We want to check that various addresses work, so let's iterate over
+    // these.
+    const uint32_t ADDRESS[] = {
+        0x00000000, 0x01020304, 0x7fffffff, 0x80000000, 0x80000001, 0xffffffff
+    };
+
+    for (int i = 0; i < sizeof(ADDRESS) / sizeof(ADDRESS[0]); ++i) {
+
+        // Create the lease
+        Lease4 lease(ADDRESS[i], HWADDR, sizeof(HWADDR),
+                     CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time,
+                     SUBNET_ID);
+
+        EXPECT_EQ(ADDRESS[i], static_cast<uint32_t>(lease.addr_));
+        EXPECT_EQ(0, lease.ext_);
+        EXPECT_TRUE(hwaddr == lease.hwaddr_);
+        EXPECT_TRUE(clientid == *lease.client_id_);
+        EXPECT_EQ(0, lease.t1_);
+        EXPECT_EQ(0, lease.t2_);
+        EXPECT_EQ(VALID_LIFETIME, lease.valid_lft_);
+        EXPECT_EQ(current_time, lease.cltt_);
+        EXPECT_EQ(SUBNET_ID, lease.subnet_id_);
+        EXPECT_FALSE(lease.fixed_);
+        EXPECT_TRUE(lease.hostname_.empty());
+        EXPECT_FALSE(lease.fqdn_fwd_);
+        EXPECT_FALSE(lease.fqdn_rev_);
+        EXPECT_TRUE(lease.comments_.empty());
+    }
+}
+
+/// @brief Lease4 Equality Test
+///
+/// Checks that the operator==() correctly compares two leases for equality.
+/// As operator!=() is also defined for this class, every check on operator==()
+/// is followed by the reverse check on operator!=().
+TEST(Lease4, OperatorEquals) {
+
+    // Random values for the tests
+    const uint32_t ADDRESS = 0x01020304;
+    const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
+    std::vector<uint8_t> hwaddr(HWADDR, HWADDR + sizeof(HWADDR));
+    const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
+    std::vector<uint8_t> clientid_vec(CLIENTID, CLIENTID + sizeof(CLIENTID));
+    ClientId clientid(clientid_vec);
+    const time_t current_time = time(NULL);
+    const uint32_t SUBNET_ID = 42;
+    const uint32_t VALID_LIFETIME = 500;
+
+    // Check when the leases are equal.
+    Lease4 lease1(ADDRESS, HWADDR, sizeof(HWADDR),
+                  CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time,
+                  SUBNET_ID);
+    Lease4 lease2(ADDRESS, HWADDR, sizeof(HWADDR),
+                  CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time,
+                  SUBNET_ID);
+    EXPECT_TRUE(lease1 == lease2);
+    EXPECT_FALSE(lease1 != lease2);
+
+    // Now vary individual fields in a lease and check that the leases compare
+    // not equal in every case.
+    lease1.addr_ = IOAddress(ADDRESS + 1);
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.addr_ = lease2.addr_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.ext_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.ext_ = lease2.ext_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.hwaddr_[0];
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.hwaddr_ = lease2.hwaddr_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++clientid_vec[0];
+    lease1.client_id_.reset(new ClientId(clientid_vec));
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    --clientid_vec[0];
+    lease1.client_id_.reset(new ClientId(clientid_vec));
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.t1_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.t1_ = lease2.t1_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.t2_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.t2_ = lease2.t2_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.valid_lft_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.valid_lft_ = lease2.valid_lft_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.cltt_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.cltt_ = lease2.cltt_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.subnet_id_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.subnet_id_ = lease2.subnet_id_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.fixed_ = !lease1.fixed_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.fixed_ = lease2.fixed_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.hostname_ += string("Something random");
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.hostname_ = lease2.hostname_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.fqdn_fwd_ = !lease1.fqdn_fwd_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.fqdn_fwd_ = lease2.fqdn_fwd_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.fqdn_rev_ = !lease1.fqdn_rev_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.fqdn_rev_ = lease2.fqdn_rev_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.comments_ += string("Something random");
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.comments_ = lease2.comments_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+}
+
+
+
 // Lease6 is also defined in lease_mgr.h, so is tested in this file as well.
 // This test checks if the Lease6 structure can be instantiated correctly
 TEST(Lease6, Lease6Constructor) {
 
-    IOAddress addr("2001:db8:1::456");
+    // check a variety of addresses with different bits set.
+    const char* ADDRESS[] = {
+        "::", "::1", "2001:db8:1::456",
+        "7fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+        "8000::", "8000::1",
+        "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
+    };
 
+    // Other values
     uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
     DuidPtr duid(new DUID(llt, sizeof(llt)));
-
     uint32_t iaid = 7; // just a number
-
     SubnetID subnet_id = 8; // just another number
 
-    Lease6Ptr x(new Lease6(Lease6::LEASE_IA_NA, addr,
-                           duid, iaid, 100, 200, 50, 80,
-                           subnet_id));
-
-    EXPECT_TRUE(x->addr_ == addr);
-    EXPECT_TRUE(*x->duid_ == *duid);
-    EXPECT_TRUE(x->iaid_ == iaid);
-    EXPECT_TRUE(x->subnet_id_ == subnet_id);
-    EXPECT_TRUE(x->type_ == Lease6::LEASE_IA_NA);
-    EXPECT_TRUE(x->preferred_lft_ == 100);
-    EXPECT_TRUE(x->valid_lft_ == 200);
-    EXPECT_TRUE(x->t1_ == 50);
-    EXPECT_TRUE(x->t2_ == 80);
+    for (int i = 0; i < sizeof(ADDRESS) / sizeof(ADDRESS[0]); ++i) {
+        IOAddress addr(ADDRESS[i]);
+        Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr,
+                               duid, iaid, 100, 200, 50, 80,
+                               subnet_id));
+
+        EXPECT_TRUE(lease->addr_ == addr);
+        EXPECT_TRUE(*lease->duid_ == *duid);
+        EXPECT_TRUE(lease->iaid_ == iaid);
+        EXPECT_TRUE(lease->subnet_id_ == subnet_id);
+        EXPECT_TRUE(lease->type_ == Lease6::LEASE_IA_NA);
+        EXPECT_TRUE(lease->preferred_lft_ == 100);
+        EXPECT_TRUE(lease->valid_lft_ == 200);
+        EXPECT_TRUE(lease->t1_ == 50);
+        EXPECT_TRUE(lease->t2_ == 80);
+    }
 
     // Lease6 must be instantiated with a DUID, not with NULL pointer
+    IOAddress addr(ADDRESS[0]);
     EXPECT_THROW(new Lease6(Lease6::LEASE_IA_NA, addr,
                             DuidPtr(), iaid, 100, 200, 50, 80,
                             subnet_id), InvalidOperation);
 }
+
+/// @brief Lease6 Equality Test
+///
+/// Checks that the operator==() correctly compares two leases for equality.
+/// As operator!=() is also defined for this class, every check on operator==()
+/// is followed by the reverse check on operator!=().
+TEST(Lease6, OperatorEquals) {
+
+    // check a variety of addressemas with different bits set.
+    const IOAddress addr("2001:db8:1::456");
+    uint8_t duid_array[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+    DuidPtr duid(new DUID(duid_array, sizeof(duid_array)));
+    uint32_t iaid = 7; // just a number
+    SubnetID subnet_id = 8; // just another number
+
+    // Check for equality.
+    Lease6 lease1(Lease6::LEASE_IA_NA, addr, duid, iaid, 100, 200, 50, 80,
+                               subnet_id);
+    Lease6 lease2(Lease6::LEASE_IA_NA, addr, duid, iaid, 100, 200, 50, 80,
+                               subnet_id);
+    EXPECT_TRUE(lease1 == lease2);
+    EXPECT_FALSE(lease1 != lease2);
+
+    // Go through and alter all the fields one by one
+
+    lease1.addr_ = IOAddress("::1");
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.addr_ = lease2.addr_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.type_ = Lease6::LEASE_IA_PD;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.type_ = lease2.type_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.prefixlen_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.prefixlen_ = lease2.prefixlen_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.iaid_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.iaid_ = lease2.iaid_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++duid_array[0];
+    lease1.duid_.reset(new DUID(duid_array, sizeof(duid_array)));
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    --duid_array[0];
+    lease1.duid_.reset(new DUID(duid_array, sizeof(duid_array)));
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.preferred_lft_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.preferred_lft_ = lease2.preferred_lft_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.valid_lft_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.valid_lft_ = lease2.valid_lft_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.t1_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.t1_ = lease2.t1_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.t2_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.t2_ = lease2.t2_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.cltt_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.cltt_ = lease2.cltt_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.subnet_id_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.subnet_id_ = lease2.subnet_id_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.fixed_ = !lease1.fixed_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.fixed_ = lease2.fixed_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.hostname_ += string("Something random");
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.hostname_ = lease2.hostname_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.fqdn_fwd_ = !lease1.fqdn_fwd_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.fqdn_fwd_ = lease2.fqdn_fwd_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.fqdn_rev_ = !lease1.fqdn_rev_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.fqdn_rev_ = lease2.fqdn_rev_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.comments_ += string("Something random");
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.comments_ = lease2.comments_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+}
 }; // end of anonymous namespace

+ 1 - 1
src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc

@@ -53,7 +53,7 @@ TEST_F(MemfileLeaseMgrTest, getTypeAndName) {
     boost::scoped_ptr<Memfile_LeaseMgr> lease_mgr(new Memfile_LeaseMgr(pmap));
 
     EXPECT_EQ(std::string("memfile"), lease_mgr->getType());
-    EXPECT_EQ(std::string("memfile"), lease_mgr->getName());
+    EXPECT_EQ(std::string("memory"), lease_mgr->getName());
 }
 
 // Checks that adding/getting/deleting a Lease6 object works.

File diff suppressed because it is too large
+ 851 - 253
src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc


+ 6 - 4
src/lib/dhcpsrv/tests/schema_copy.h

@@ -25,7 +25,9 @@ namespace {
 // by semicolons, and the strings must end with a comma.  The final line
 // statement must be NULL (not in quotes)
 
-// THIS MUST BE KEPT UP TO DATE AND UPDATED IF THE SCHEMA CHANGES
+// NOTE: This file mirrors the schema in src/lib/dhcpsrv/dhcpdb_create.mysql.
+//       If this file is altered, please ensure that any change is compatible
+//       with the schema in dhcpdb_create.mysql.
 
 // Deletion of existing tables.
 
@@ -44,13 +46,13 @@ const char* create_statement[] = {
         "address INT UNSIGNED PRIMARY KEY NOT NULL,"
         "hwaddr VARBINARY(20),"
         "client_id VARBINARY(128),"
-        "lease_time INT UNSIGNED,"
+        "valid_lifetime INT UNSIGNED,"
         "expire TIMESTAMP,"
         "subnet_id INT UNSIGNED"
         ") ENGINE = INNODB",
 
     "CREATE TABLE lease6 ("
-        "address VARCHAR(40) PRIMARY KEY NOT NULL,"
+        "address VARCHAR(39) PRIMARY KEY NOT NULL,"
         "duid VARBINARY(128),"
         "valid_lifetime INT UNSIGNED,"
         "expire TIMESTAMP,"
@@ -75,7 +77,7 @@ const char* create_statement[] = {
         "minor INT"
         ")",
 
-    "INSERT INTO schema_version VALUES (0, 1)",
+    "INSERT INTO schema_version VALUES (1, 0)",
 
     NULL
 };

+ 4 - 0
src/lib/dns/Makefile.am

@@ -21,6 +21,8 @@ EXTRA_DIST += rdata/ch_3/a_1.cc
 EXTRA_DIST += rdata/ch_3/a_1.h
 EXTRA_DIST += rdata/generic/cname_5.cc
 EXTRA_DIST += rdata/generic/cname_5.h
+EXTRA_DIST += rdata/generic/detail/char_string.cc
+EXTRA_DIST += rdata/generic/detail/char_string.h
 EXTRA_DIST += rdata/generic/detail/nsec_bitmap.cc
 EXTRA_DIST += rdata/generic/detail/nsec_bitmap.h
 EXTRA_DIST += rdata/generic/detail/nsec3param_common.cc
@@ -123,6 +125,8 @@ libb10_dns___la_SOURCES += tsigrecord.h tsigrecord.cc
 libb10_dns___la_SOURCES += character_string.h character_string.cc
 libb10_dns___la_SOURCES += master_loader_callbacks.h
 libb10_dns___la_SOURCES += master_loader.h
+libb10_dns___la_SOURCES += rdata/generic/detail/char_string.h
+libb10_dns___la_SOURCES += rdata/generic/detail/char_string.cc
 libb10_dns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h
 libb10_dns___la_SOURCES += rdata/generic/detail/nsec_bitmap.cc
 libb10_dns___la_SOURCES += rdata/generic/detail/nsec3param_common.cc

+ 2 - 1
src/lib/dns/gen-rdatacode.py.in

@@ -32,7 +32,8 @@ import sys
 #
 # Example:
 #     new_rdata_factory_users = [('a', 'in'), ('a', 'ch'), ('soa', 'generic')]
-new_rdata_factory_users = [('aaaa', 'in')]
+new_rdata_factory_users = [('aaaa', 'in'), ('txt', 'generic'),
+                           ('spf', 'generic')]
 
 re_typecode = re.compile('([\da-z]+)_(\d+)')
 classcode2txt = {}

+ 1 - 1
src/lib/dns/python/tests/rdata_python_test.py

@@ -38,7 +38,7 @@ class RdataTest(unittest.TestCase):
         self.assertRaises(InvalidRdataText, Rdata, RRType("A"), RRClass("IN"),
                           "Invalid Rdata Text")
         self.assertRaises(CharStringTooLong, Rdata, RRType("TXT"),
-                          RRClass("IN"), ' ' * 256)
+                          RRClass("IN"), 'x' * 256)
         self.assertRaises(InvalidRdataLength, Rdata, RRType("TXT"),
                           RRClass("IN"), bytes(65536))
         self.assertRaises(DNSMessageFORMERR, Rdata, RRType("TXT"),

+ 98 - 0
src/lib/dns/rdata/generic/detail/char_string.cc

@@ -0,0 +1,98 @@
+// Copyright (C) 2012  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 <exceptions/exceptions.h>
+
+#include <dns/rdata.h>
+#include <dns/master_lexer.h>
+#include <dns/rdata/generic/detail/char_string.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <cassert>
+#include <cctype>
+#include <cstring>
+#include <vector>
+
+#include <stdint.h>
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+
+namespace {
+// Convert a DDD form to the corresponding integer
+int
+decimalToNumber(const char* s, const char* s_end) {
+    if (s_end - s < 3) {
+        isc_throw(InvalidRdataText, "Escaped digits too short");
+    }
+
+    const std::string num_str(s, s + 3);
+    try {
+        const int i = boost::lexical_cast<int>(num_str);
+        if (i > 255) {
+            isc_throw(InvalidRdataText, "Escaped digits too large: "
+                      << num_str);
+        }
+        return (i);
+    } catch (const boost::bad_lexical_cast&) {
+        isc_throw(InvalidRdataText,
+                  "Invalid form for escaped digits: " << num_str);
+    }
+}
+}
+
+void
+strToCharString(const MasterToken::StringRegion& str_region,
+                CharString& result)
+{
+    // make a space for the 1-byte length field; filled in at the end
+    result.push_back(0);
+
+    bool escape = false;
+    const char* s = str_region.beg;
+    const char* const s_end = str_region.beg + str_region.len;
+
+    for (size_t n = str_region.len; n != 0; --n, ++s) {
+        int c = (*s & 0xff);
+        if (escape && std::isdigit(c) != 0) {
+            c = decimalToNumber(s, s_end);
+            assert(n >= 3);
+            n -= 2;
+            s += 2;
+        } else if (!escape && c == '\\') {
+            escape = true;
+            continue;
+        }
+        escape = false;
+        result.push_back(c);
+    }
+    if (escape) {               // terminated by non-escaped '\'
+        isc_throw(InvalidRdataText, "character-string ends with '\\'");
+    }
+    if (result.size() > MAX_CHARSTRING_LEN + 1) { // '+ 1' due to the len field
+        isc_throw(CharStringTooLong, "character-string is too long: " <<
+                  (result.size() - 1) << "(+1) characters");
+    }
+    result[0] = result.size() - 1;
+}
+
+} // end of detail
+} // end of generic
+} // end of rdata
+} // end of dns
+} // end of isc

+ 63 - 0
src/lib/dns/rdata/generic/detail/char_string.h

@@ -0,0 +1,63 @@
+// Copyright (C) 2012  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 DNS_RDATA_CHARSTRING_H
+#define DNS_RDATA_CHARSTRING_H 1
+
+#include <dns/master_lexer.h>
+
+#include <vector>
+#include <stdint.h>
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+
+/// \brief Type for DNS character string.
+///
+/// A character string can contain any unsigned 8-bit value, so this cannot
+/// be the bare char basis.
+typedef std::vector<uint8_t> CharString;
+
+/// \brief Convert a DNS character-string into corresponding binary data.
+///
+/// This helper function takes a string object that is expected to be a
+/// textual representation of a valid DNS character-string, and dumps
+/// the corresponding binary sequence in the given placeholder (passed
+/// via the \c result parameter).  It handles escape notations of
+/// character-strings with a backslash ('\'), and checks the length
+/// restriction.
+///
+/// \throw CharStringTooLong The resulting binary data are too large for a
+/// valid character-string.
+/// \throw InvalidRdataText Other syntax errors.
+///
+/// \brief str_region A string that represents a character-string.
+/// \brief result A placeholder vector where the resulting data are to be
+/// stored.  Expected to be empty, but it's not checked.
+void strToCharString(const MasterToken::StringRegion& str_region,
+                     CharString& result);
+
+} // namespace detail
+} // namespace generic
+} // namespace rdata
+} // namespace dns
+} // namespace isc
+#endif  // DNS_RDATA_CHARSTRING_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 74 - 49
src/lib/dns/rdata/generic/detail/txt_like.h

@@ -15,13 +15,20 @@
 #ifndef TXT_LIKE_H
 #define TXT_LIKE_H 1
 
+#include <dns/master_lexer.h>
+#include <dns/rdata/generic/detail/char_string.h>
+
 #include <stdint.h>
 
 #include <string>
+#include <sstream>
 #include <vector>
 
-using namespace std;
-using namespace isc::util;
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
 
 /// \brief \c rdata::TXTLikeImpl class represents the TXT-like RDATA for TXT
 /// and SPF types.
@@ -41,7 +48,7 @@ public:
     ///
     /// \c InvalidRdataLength is thrown if rdata_len exceeds the maximum.
     /// \c DNSMessageFORMERR is thrown if the RR is misformed.
-    TXTLikeImpl(InputBuffer& buffer, size_t rdata_len) {
+    TXTLikeImpl(util::InputBuffer& buffer, size_t rdata_len) {
         if (rdata_len > MAX_RDLENGTH) {
             isc_throw(InvalidRdataLength, "RDLENGTH too large: " << rdata_len);
         }
@@ -59,7 +66,7 @@ public:
                           " RDATA: character string length is too large: " <<
                           static_cast<int>(len));
             }
-            vector<uint8_t> data(len + 1);
+            std::vector<uint8_t> data(len + 1);
             data[0] = len;
             buffer.readData(&data[0] + 1, len);
             string_list_.push_back(data);
@@ -70,46 +77,61 @@ public:
 
     /// \brief Constructor from string.
     ///
-    /// <b>Exceptions</b>
-    ///
-    /// \c CharStringTooLong is thrown if the parameter string length exceeds
-    /// maximum.
-    /// \c InvalidRdataText is thrown if the method cannot process the
-    /// parameter data.
+    /// \throw CharStringTooLong the parameter string length exceeds maximum.
+    /// \throw InvalidRdataText the method cannot process the parameter data
     explicit TXTLikeImpl(const std::string& txtstr) {
-        // TBD: this is a simple, incomplete implementation that only supports
-        // a single character-string.
+        std::istringstream ss(txtstr);
+        MasterLexer lexer;
+        lexer.pushSource(ss);
+
+        try {
+            buildFromTextHelper(lexer);
+            if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+                isc_throw(InvalidRdataText, "Failed to construct " <<
+                          RRType(typeCode) << " RDATA from '" << txtstr <<
+                          "': extra new line");
+            }
+        } catch (const MasterLexer::LexerError& ex) {
+            isc_throw(InvalidRdataText, "Failed to construct " <<
+                      RRType(typeCode) << " RDATA from '" << txtstr << "': "
+                      << ex.what());
+        }
+    }
 
-        size_t length = txtstr.size();
-        size_t pos_begin = 0;
+    /// \brief Constructor using the master lexer.
+    ///
+    /// \throw CharStringTooLong the parameter string length exceeds maximum.
+    /// \throw InvalidRdataText the method cannot process the parameter data
+    ///
+    /// \param lexer A \c MasterLexer object parsing a master file for this
+    /// RDATA.
+    TXTLikeImpl(MasterLexer& lexer) {
+        buildFromTextHelper(lexer);
+    }
 
-        if (length > 1 && txtstr[0] == '"' && txtstr[length - 1] == '"') {
-            pos_begin = 1;
-            length -= 2;
+private:
+    void buildFromTextHelper(MasterLexer& lexer) {
+        while (true) {
+            const MasterToken& token = lexer.getNextToken(
+                MasterToken::QSTRING, true);
+            if (token.getType() != MasterToken::STRING &&
+                token.getType() != MasterToken::QSTRING) {
+                break;
+            }
+            string_list_.push_back(std::vector<uint8_t>());
+            strToCharString(token.getStringRegion(), string_list_.back());
         }
 
-        if (length > MAX_CHARSTRING_LEN) {
-            isc_throw(CharStringTooLong, RRType(typeCode) <<
-                      " RDATA construction from text:"
-                      " string length is too long: " << length);
-        }
+        // Let upper layer handle eol/eof.
+        lexer.ungetToken();
 
-        // TBD: right now, we don't support escaped characters
-        if (txtstr.find('\\') != string::npos) {
-            isc_throw(InvalidRdataText, RRType(typeCode) <<
-                      " RDATA from text:"
-                      " escaped character is currently not supported: " <<
-                      txtstr);
+        if (string_list_.empty()) {
+            isc_throw(InvalidRdataText, "Failed to construct" <<
+                      RRType(typeCode) << " RDATA: empty input");
         }
-
-        vector<uint8_t> data;
-        data.reserve(length + 1);
-        data.push_back(length);
-        data.insert(data.end(), txtstr.begin() + pos_begin,
-                    txtstr.begin() + pos_begin + length);
-        string_list_.push_back(data);
     }
 
+public:
     /// \brief The copy constructor.
     ///
     /// Trivial for now, we could've used the default one.
@@ -122,9 +144,9 @@ public:
     ///
     /// \param buffer An output buffer to store the wire data.
     void
-    toWire(OutputBuffer& buffer) const {
-        for (vector<vector<uint8_t> >::const_iterator it =
-                                                          string_list_.begin();
+    toWire(util::OutputBuffer& buffer) const {
+        for (std::vector<std::vector<uint8_t> >::const_iterator it =
+                 string_list_.begin();
              it != string_list_.end();
              ++it)
         {
@@ -139,8 +161,8 @@ public:
     /// to.
     void
     toWire(AbstractMessageRenderer& renderer) const {
-        for (vector<vector<uint8_t> >::const_iterator it =
-                                                          string_list_.begin();
+        for (std::vector<std::vector<uint8_t> >::const_iterator it =
+                 string_list_.begin();
              it != string_list_.end();
              ++it)
         {
@@ -151,14 +173,14 @@ public:
     /// \brief Convert the TXT-like data to a string.
     ///
     /// \return A \c string object that represents the TXT-like data.
-    string
+    std::string
     toText() const {
-        string s;
+        std::string s;
 
         // XXX: this implementation is not entirely correct.  for example, it
         // should escape double-quotes if they appear in the character string.
-        for (vector<vector<uint8_t> >::const_iterator it =
-                                                          string_list_.begin();
+        for (std::vector<std::vector<uint8_t> >::const_iterator it =
+                 string_list_.begin();
              it != string_list_.end();
              ++it)
         {
@@ -189,7 +211,7 @@ public:
         OutputBuffer this_buffer(0);
         toWire(this_buffer);
         uint8_t const* const this_data = (uint8_t const*)this_buffer.getData();
-        size_t this_len = this_buffer.getLength();
+        const size_t this_len = this_buffer.getLength();
 
         OutputBuffer other_buffer(0);
         other.toWire(other_buffer);
@@ -214,11 +236,14 @@ private:
     std::vector<std::vector<uint8_t> > string_list_;
 };
 
-// END_RDATA_NAMESPACE
-// END_ISC_NAMESPACE
+}      // namespace detail
+}      // namespace generic
+}      // namespace rdata
+}      // namespace dns
+}      // namespace isc
 
 #endif //  TXT_LIKE_H
 
-// Local Variables: 
+// Local Variables:
 // mode: c++
-// End: 
+// End:

+ 20 - 6
src/lib/dns/rdata/generic/spf_99.cc

@@ -24,18 +24,17 @@
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
 
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class. The semantics of the class is provided by
+/// a copy of instantiated TXTLikeImpl class common to both TXT and SPF.
+#include <dns/rdata/generic/detail/txt_like.h>
+
 using namespace std;
 using namespace isc::util;
 
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
-/// This class implements the basic interfaces inherited from the abstract
-/// \c rdata::Rdata class. The semantics of the class is provided by
-/// a copy of instantiated TXTLikeImpl class common to both TXT and SPF.
-
-#include <dns/rdata/generic/detail/txt_like.h>
-
 /// \brief The assignment operator
 ///
 /// It internally allocates a resource, and if it fails a corresponding
@@ -67,6 +66,21 @@ SPF::SPF(InputBuffer& buffer, size_t rdata_len) :
     impl_(new SPFImpl(buffer, rdata_len))
 {}
 
+/// \brief Constructor using the master lexer.
+///
+/// This implementation only uses the \c lexer parameters; others are
+/// ignored.
+///
+/// \throw CharStringTooLong the parameter string length exceeds maximum.
+/// \throw InvalidRdataText the method cannot process the parameter data
+///
+/// \param lexer A \c MasterLexer object parsing a master file for this
+/// RDATA.
+SPF::SPF(MasterLexer& lexer, const Name*, MasterLoader::Options,
+         MasterLoaderCallbacks&) :
+    impl_(new SPFImpl(lexer))
+{}
+
 /// \brief Constructor from string.
 ///
 /// It internally allocates a resource, and if it fails a corresponding

+ 3 - 1
src/lib/dns/rdata/generic/spf_99.h

@@ -28,7 +28,9 @@
 
 // BEGIN_RDATA_NAMESPACE
 
+namespace detail {
 template<class Type, uint16_t typeCode> class TXTLikeImpl;
+}
 
 /// \brief \c rdata::SPF class represents the SPF RDATA as defined %in
 /// RFC4408.
@@ -65,7 +67,7 @@ public:
     const std::vector<std::vector<uint8_t> >& getString() const;
 
 private:
-    typedef TXTLikeImpl<SPF, 99> SPFImpl;
+    typedef isc::dns::rdata::generic::detail::TXTLikeImpl<SPF, 99> SPFImpl;
     SPFImpl* impl_;
 };
 

+ 16 - 2
src/lib/dns/rdata/generic/txt_16.cc

@@ -23,6 +23,7 @@
 #include <dns/messagerenderer.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/txt_like.h>
 
 using namespace std;
 using namespace isc::util;
@@ -30,8 +31,6 @@ using namespace isc::util;
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
-#include <dns/rdata/generic/detail/txt_like.h>
-
 TXT&
 TXT::operator=(const TXT& source) {
     if (impl_ == source.impl_) {
@@ -53,6 +52,21 @@ TXT::TXT(InputBuffer& buffer, size_t rdata_len) :
     impl_(new TXTImpl(buffer, rdata_len))
 {}
 
+/// \brief Constructor using the master lexer.
+///
+/// This implementation only uses the \c lexer parameters; others are
+/// ignored.
+///
+/// \throw CharStringTooLong the parameter string length exceeds maximum.
+/// \throw InvalidRdataText the method cannot process the parameter data
+///
+/// \param lexer A \c MasterLexer object parsing a master file for this
+/// RDATA.
+TXT::TXT(MasterLexer& lexer, const Name*, MasterLoader::Options,
+         MasterLoaderCallbacks&) :
+    impl_(new TXTImpl(lexer))
+{}
+
 TXT::TXT(const std::string& txtstr) :
     impl_(new TXTImpl(txtstr))
 {}

+ 3 - 1
src/lib/dns/rdata/generic/txt_16.h

@@ -28,7 +28,9 @@
 
 // BEGIN_RDATA_NAMESPACE
 
+namespace detail {
 template<class Type, uint16_t typeCode> class TXTLikeImpl;
+}
 
 class TXT : public Rdata {
 public:
@@ -39,7 +41,7 @@ public:
     ~TXT();
 
 private:
-    typedef TXTLikeImpl<TXT, 16> TXTImpl;
+    typedef isc::dns::rdata::generic::detail::TXTLikeImpl<TXT, 16> TXTImpl;
     TXTImpl* impl_;
 };
 

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

@@ -36,6 +36,7 @@ run_unittests_SOURCES += opcode_unittest.cc
 run_unittests_SOURCES += rcode_unittest.cc
 run_unittests_SOURCES += rdata_unittest.h rdata_unittest.cc
 run_unittests_SOURCES += rdatafields_unittest.cc
+run_unittests_SOURCES += rdata_char_string_unittest.cc
 run_unittests_SOURCES += rdata_in_a_unittest.cc rdata_in_aaaa_unittest.cc
 run_unittests_SOURCES += rdata_ns_unittest.cc rdata_soa_unittest.cc
 run_unittests_SOURCES += rdata_txt_like_unittest.cc

+ 147 - 0
src/lib/dns/tests/rdata_char_string_unittest.cc

@@ -0,0 +1,147 @@
+// Copyright (C) 2012  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 <util/unittests/wiredata.h>
+
+#include <dns/rdata.h>
+#include <dns/rdata/generic/detail/char_string.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+#include <vector>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using isc::dns::rdata::generic::detail::CharString;
+using isc::dns::rdata::generic::detail::strToCharString;
+using isc::util::unittests::matchWireData;
+
+namespace {
+const uint8_t test_charstr[] = {
+    sizeof("Test String") - 1,
+    'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
+};
+
+class CharStringTest : public ::testing::Test {
+protected:
+    CharStringTest() :
+        // char-string representation for test data using two types of escape
+        // ('r' = 114)
+        test_str("Test\\ St\\114ing")
+    {
+        str_region.beg = &test_str[0];
+        str_region.len = test_str.size();
+    }
+    CharString chstr;           // place holder
+    const std::string test_str;
+    MasterToken::StringRegion str_region;
+};
+
+MasterToken::StringRegion
+createStringRegion(const std::string& str) {
+    MasterToken::StringRegion region;
+    region.beg = &str[0]; // note std ensures this works even if str is empty
+    region.len = str.size();
+    return (region);
+}
+
+TEST_F(CharStringTest, normalConversion) {
+    uint8_t tmp[3];             // placeholder for expected sequence
+
+    strToCharString(str_region, chstr);
+    matchWireData(test_charstr, sizeof(test_charstr), &chstr[0], chstr.size());
+
+    // Empty string
+    chstr.clear();
+    strToCharString(createStringRegion(""), chstr);
+    tmp[0] = 0;
+    matchWireData(tmp, 1, &chstr[0], chstr.size());
+
+    // Possible largest char string
+    chstr.clear();
+    std::string long_str(255, 'x');
+    strToCharString(createStringRegion(long_str), chstr);
+    std::vector<uint8_t> expected;
+    expected.push_back(255);    // len of char string
+    expected.insert(expected.end(), long_str.begin(), long_str.end());
+    matchWireData(&expected[0], expected.size(), &chstr[0], chstr.size());
+
+    // Same data as the previous case, but the original string is longer than
+    // the max; this shouldn't be rejected
+    chstr.clear();
+    long_str.at(254) = '\\';    // replace the last 'x' with '\'
+    long_str.append("120");     // 'x' = 120
+    strToCharString(createStringRegion(long_str), chstr);
+    matchWireData(&expected[0], expected.size(), &chstr[0], chstr.size());
+
+    // Escaped '\'
+    chstr.clear();
+    tmp[0] = 1;
+    tmp[1] = '\\';
+    strToCharString(createStringRegion("\\\\"), chstr);
+    matchWireData(tmp, 2, &chstr[0], chstr.size());
+
+    // Boundary values for \DDD
+    chstr.clear();
+    tmp[0] = 1;
+    tmp[1] = 0;
+    strToCharString(createStringRegion("\\000"), chstr);
+    matchWireData(tmp, 2, &chstr[0], chstr.size());
+
+    chstr.clear();
+    strToCharString(createStringRegion("\\255"), chstr);
+    tmp[0] = 1;
+    tmp[1] = 255;
+    matchWireData(tmp, 2, &chstr[0], chstr.size());
+
+    // Another digit follows DDD; it shouldn't cause confusion
+    chstr.clear();
+    strToCharString(createStringRegion("\\2550"), chstr);
+    tmp[0] = 2;                 // string len is now 2
+    tmp[2] = '0';
+    matchWireData(tmp, 3, &chstr[0], chstr.size());
+}
+
+TEST_F(CharStringTest, badConversion) {
+    // string cannot exceed 255 bytes
+    EXPECT_THROW(strToCharString(createStringRegion(std::string(256, 'a')),
+                                 chstr),
+                 CharStringTooLong);
+
+    // input string ending with (non escaped) '\'
+    chstr.clear();
+    EXPECT_THROW(strToCharString(createStringRegion("foo\\"), chstr),
+                 InvalidRdataText);
+}
+
+TEST_F(CharStringTest, badDDD) {
+    // Check various type of bad form of \DDD
+
+    // Not a number
+    EXPECT_THROW(strToCharString(createStringRegion("\\1a2"), chstr),
+                 InvalidRdataText);
+    EXPECT_THROW(strToCharString(createStringRegion("\\12a"), chstr),
+                 InvalidRdataText);
+
+    // Not in the range of uint8_t
+    EXPECT_THROW(strToCharString(createStringRegion("\\256"), chstr),
+                 InvalidRdataText);
+
+    // Short buffer
+    EXPECT_THROW(strToCharString(createStringRegion("\\42"), chstr),
+                 InvalidRdataText);
+}
+
+} // unnamed namespace

+ 173 - 39
src/lib/dns/tests/rdata_txt_like_unittest.cc

@@ -17,17 +17,25 @@
 #include <util/buffer.h>
 #include <dns/exceptions.h>
 #include <dns/rdataclass.h>
-#include <gtest/gtest.h>
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
 
+#include <gtest/gtest.h>
+
+#include <boost/bind.hpp>
+
+#include <string>
+#include <sstream>
+#include <vector>
+
 using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
 
+namespace {
 
 template<class T>
 class RRTYPE : public RRType {
@@ -38,36 +46,42 @@ public:
 template<> RRTYPE<generic::TXT>::RRTYPE() : RRType(RRType::TXT()) {}
 template<> RRTYPE<generic::SPF>::RRTYPE() : RRType(RRType::SPF()) {}
 
-namespace {
 const uint8_t wiredata_txt_like[] = {
-    sizeof("Test String") - 1,
-    'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
+    sizeof("Test-String") - 1,
+    'T', 'e', 's', 't', '-', 'S', 't', 'r', 'i', 'n', 'g'
 };
 
 const uint8_t wiredata_nulltxt[] = { 0 };
-vector<uint8_t> wiredata_longesttxt(256, 'a');
+
+// For lexer-based constructor
+void
+dummyCallback(const string&, size_t, const string&) {
+}
 
 template<class TXT_LIKE>
 class Rdata_TXT_LIKE_Test : public RdataTest {
 protected:
-    Rdata_TXT_LIKE_Test() {
+    Rdata_TXT_LIKE_Test() :
+        callback(boost::bind(&dummyCallback, _1, _2, _3)),
+        loader_cb(callback, callback),
+        wiredata_longesttxt(256, 'a'),
+        rdata_txt_like("Test-String"),
+        rdata_txt_like_empty("\"\""),
+        rdata_txt_like_quoted("\"Test-String\"")
+    {
         wiredata_longesttxt[0] = 255; // adjust length
     }
 
-    static const TXT_LIKE rdata_txt_like;
-    static const TXT_LIKE rdata_txt_like_empty;
-    static const TXT_LIKE rdata_txt_like_quoted;
-};
-
-template<class TXT_LIKE>
-const TXT_LIKE Rdata_TXT_LIKE_Test<TXT_LIKE>::rdata_txt_like("Test String");
-
-template<class TXT_LIKE>
-const TXT_LIKE Rdata_TXT_LIKE_Test<TXT_LIKE>::rdata_txt_like_empty("");
+private:
+    const MasterLoaderCallbacks::IssueCallback callback;
 
-template<class TXT_LIKE>
-const TXT_LIKE Rdata_TXT_LIKE_Test<TXT_LIKE>::rdata_txt_like_quoted
-                                                          ("\"Test String\"");
+protected:
+    MasterLoaderCallbacks loader_cb;
+    vector<uint8_t> wiredata_longesttxt;
+    const TXT_LIKE rdata_txt_like;
+    const TXT_LIKE rdata_txt_like_empty;
+    const TXT_LIKE rdata_txt_like_quoted;
+};
 
 // The list of types we want to test.
 typedef testing::Types<generic::TXT, generic::SPF> Implementations;
@@ -75,37 +89,155 @@ typedef testing::Types<generic::TXT, generic::SPF> Implementations;
 TYPED_TEST_CASE(Rdata_TXT_LIKE_Test, Implementations);
 
 TYPED_TEST(Rdata_TXT_LIKE_Test, createFromText) {
-    // normal case is covered in toWireBuffer.
+    // Below we check the behavior for the "from text" constructors, both
+    // from std::string and with MasterLexer.  The underlying implementation
+    // is the same, so both should work exactly same, but we confirm both
+    // cases.
+
+    const std::string multi_line = "(\n \"Test-String\" )";
+    const std::string escaped_txt = "Test\\045Strin\\g";
+
+    // test input for the lexer version
+    std::stringstream ss;
+    ss << "Test-String\n";
+    ss << "\"Test-String\"\n";   // explicitly surrounded by '"'s
+    ss << multi_line << "\n";   // multi-line text with ()
+    ss << escaped_txt << "\n";   // using the two types of escape with '\'
+    ss << "\"\"\n";              // empty string (note: still valid char-str)
+    ss << string(255, 'a') << "\n"; // Longest possible character-string.
+    ss << string(256, 'a') << "\n"; // char-string too long
+    ss << "\"Test-String\\\"\n";    // unbalanced quote
+    ss << "\"Test-String\\\"\"\n";
+    this->lexer.pushSource(ss);
+
+    // commonly used Rdata to compare below, created from wire
+    ConstRdataPtr const rdata =
+        this->rdataFactoryFromFile(RRTYPE<TypeParam>(),
+                                   RRClass("IN"), "rdata_txt_fromWire1");
+
+    // normal case is covered in toWireBuffer.  First check the std::string
+    // case, then with MasterLexer.  For the latter, we need to read and skip
+    // '\n'.  These apply to most of the other cases below.
+    EXPECT_EQ(0, this->rdata_txt_like.compare(*rdata));
+    EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+                           this->loader_cb).compare(*rdata));
+    EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
 
     // surrounding double-quotes shouldn't change the result.
-    EXPECT_EQ(0, this->rdata_txt_like.compare(this->rdata_txt_like_quoted));
+    EXPECT_EQ(0, this->rdata_txt_like_quoted.compare(*rdata));
+    EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+                           this->loader_cb).compare(*rdata));
+    EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
+
+    // multi-line input with ()
+    EXPECT_EQ(0, TypeParam(multi_line).compare(*rdata));
+    EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+                           this->loader_cb).compare(*rdata));
+    EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
+
+    // for the same data using escape
+    EXPECT_EQ(0, TypeParam(escaped_txt).compare(*rdata));
+    EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+                           this->loader_cb).compare(*rdata));
+    EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
 
     // Null character-string.
     this->obuffer.clear();
-    TypeParam(string("")).toWire(this->obuffer);
+    TypeParam(string("\"\"")).toWire(this->obuffer);
     EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        this->obuffer.getData(),
-                        this->obuffer.getLength(),
+                        this->obuffer.getData(), this->obuffer.getLength(),
                         wiredata_nulltxt, sizeof(wiredata_nulltxt));
+    this->obuffer.clear();
+    TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS, this->loader_cb).
+        toWire(this->obuffer);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        this->obuffer.getData(), this->obuffer.getLength(),
+                        wiredata_nulltxt, sizeof(wiredata_nulltxt));
+    EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
 
     // Longest possible character-string.
     this->obuffer.clear();
     TypeParam(string(255, 'a')).toWire(this->obuffer);
     EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        this->obuffer.getData(),
-                        this->obuffer.getLength(),
-                        &wiredata_longesttxt[0], wiredata_longesttxt.size());
+                        this->obuffer.getData(), this->obuffer.getLength(),
+                        &this->wiredata_longesttxt[0],
+                        this->wiredata_longesttxt.size());
+    this->obuffer.clear();
+    TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS, this->loader_cb).
+        toWire(this->obuffer);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        this->obuffer.getData(), this->obuffer.getLength(),
+                        &this->wiredata_longesttxt[0],
+                        this->wiredata_longesttxt.size());
+    EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
 
     // Too long text for a valid character-string.
     EXPECT_THROW(TypeParam(string(256, 'a')), CharStringTooLong);
+    EXPECT_THROW(TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+                           this->loader_cb), CharStringTooLong);
+    EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
 
     // The escape character makes the double quote a part of character-string,
     // so this is invalid input and should be rejected.
-    EXPECT_THROW(TypeParam("\"Test String\\\""), InvalidRdataText);
+    EXPECT_THROW(TypeParam("\"Test-String\\\""), InvalidRdataText);
+    EXPECT_THROW(TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+                           this->loader_cb), MasterLexer::LexerError);
+    EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, createMultiStringsFromText) {
+    // Tests for "from text" variants construction with various forms of
+    // multi character-strings.
+
+    std::vector<std::string > texts;
+    texts.push_back("\"Test-String\" \"Test-String\""); // most common form
+    texts.push_back("\"Test-String\"\"Test-String\"");  // no space between'em
+    texts.push_back("\"Test-String\" Test-String");  // no '"' for one
+    texts.push_back("\"Test-String\"Test-String"); // and no space either
+    texts.push_back("Test-String \"Test-String\""); // no '"' for the other
+    // This one currently doesn't work
+    //texts.push_back("Test-String\"Test-String\""); // and no space either
+
+    std::stringstream ss;
+    for (std::vector<std::string >::const_iterator it = texts.begin();
+         it != texts.end(); ++it) {
+        ss << *it << "\n";
+    }
+    this->lexer.pushSource(ss);
+
+    // The corresponding Rdata built from wire to compare in the checks below.
+    ConstRdataPtr const rdata =
+        this->rdataFactoryFromFile(RRTYPE<TypeParam>(),
+                                   RRClass("IN"), "rdata_txt_fromWire3.wire");
+
+    // Confirm we can construct the Rdata from the test text, both from
+    // std::string and with lexer, and that matches the from-wire data.
+    for (std::vector<std::string >::const_iterator it = texts.begin();
+         it != texts.end(); ++it) {
+        SCOPED_TRACE(*it);
+        EXPECT_EQ(0, TypeParam(*it).compare(*rdata));
+
+        EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+                               this->loader_cb).compare(*rdata));
+        EXPECT_EQ(MasterToken::END_OF_LINE,
+                  this->lexer.getNextToken().getType());
+    }
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, createFromTextExtra) {
+    // This is for the std::string version only: the input must end with EOF;
+    // an extra new-line will result in an exception.
+    EXPECT_THROW(TypeParam("\"Test-String\"\n"), InvalidRdataText);
+    // Same if there's a space before '\n'
+    EXPECT_THROW(TypeParam("\"Test-String\" \n"), InvalidRdataText);
+}
 
-    // Terminating double-quote is provided, so this is valid, but in this
-    // version of implementation we reject escaped characters.
-    EXPECT_THROW(TypeParam("\"Test String\\\"\""), InvalidRdataText);
+TYPED_TEST(Rdata_TXT_LIKE_Test, fromTextEmpty) {
+    // If the input text doesn't contain any character-string, it should be
+    // rejected
+    EXPECT_THROW(TypeParam(""), InvalidRdataText);
+    EXPECT_THROW(TypeParam(" "), InvalidRdataText); // even with a space
+    EXPECT_THROW(TypeParam("(\n)"), InvalidRdataText); // or multi-line with ()
 }
 
 void
@@ -129,13 +261,15 @@ makeLargest(vector<uint8_t>& data) {
 
 TYPED_TEST(Rdata_TXT_LIKE_Test, createFromWire) {
     EXPECT_EQ(0, this->rdata_txt_like.compare(
-                  *this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
-                                        "rdata_txt_fromWire1")));
+                  *this->rdataFactoryFromFile(RRTYPE<TypeParam>(),
+                                              RRClass("IN"),
+                                              "rdata_txt_fromWire1")));
 
     // Empty character string
     EXPECT_EQ(0, this->rdata_txt_like_empty.compare(
-                  *this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
-                                        "rdata_txt_fromWire2.wire")));
+                  *this->rdataFactoryFromFile(RRTYPE<TypeParam>(),
+                                              RRClass("IN"),
+                                              "rdata_txt_fromWire2.wire")));
 
     // Multiple character strings
     this->obuffer.clear();
@@ -188,7 +322,7 @@ TYPED_TEST(Rdata_TXT_LIKE_Test, createFromWire) {
 TYPED_TEST(Rdata_TXT_LIKE_Test, createFromLexer) {
     EXPECT_EQ(0, this->rdata_txt_like.compare(
         *test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(),
-                                     "Test String")));
+                                     "Test-String")));
 }
 
 TYPED_TEST(Rdata_TXT_LIKE_Test, toWireBuffer) {
@@ -208,7 +342,7 @@ TYPED_TEST(Rdata_TXT_LIKE_Test, toWireRenderer) {
 }
 
 TYPED_TEST(Rdata_TXT_LIKE_Test, toText) {
-    EXPECT_EQ("\"Test String\"", this->rdata_txt_like.toText());
+    EXPECT_EQ("\"Test-String\"", this->rdata_txt_like.toText());
 }
 
 TYPED_TEST(Rdata_TXT_LIKE_Test, assignment) {
@@ -238,8 +372,8 @@ TYPED_TEST(Rdata_TXT_LIKE_Test, compare) {
 
     EXPECT_EQ(TypeParam(txt1).compare(TypeParam(txt1)), 0);
 
-    EXPECT_LT(TypeParam("").compare(TypeParam(txt1)), 0);
-    EXPECT_GT(TypeParam(txt1).compare(TypeParam("")), 0);
+    EXPECT_LT(TypeParam("\"\"").compare(TypeParam(txt1)), 0);
+    EXPECT_GT(TypeParam(txt1).compare(TypeParam("\"\"")), 0);
 
     EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt2)), 0);
     EXPECT_GT(TypeParam(txt2).compare(TypeParam(txt1)), 0);

+ 3 - 3
src/lib/dns/tests/testdata/rdata_txt_fromWire1

@@ -1,9 +1,9 @@
 #
 # various kinds of TXT RDATA stored in an input buffer
 #
-# Valid RDATA for "Test String"
+# Valid RDATA for "Test-String"
 #
 # RDLENGHT=12 bytes
  00 0c
-#    T  e  s  t     S  t  r  i  n  g
- 0b 54 65 73 74 20 53 74 72 69 6e 67
+#    T  e  s  t  -  S  t  r  i  n  g
+ 0b 54 65 73 74 2d 53 74 72 69 6e 67

+ 1 - 1
src/lib/util/python/gen_wiredata.py.in

@@ -770,7 +770,7 @@ class TXT(RR):
 
     nstring = 1
     stringlen = None
-    string = 'Test String'
+    string = 'Test-String'
 
     def dump(self, f):
         stringlen_list = []

+ 1 - 1
tests/tools/perfdhcp/Makefile.am

@@ -1,4 +1,4 @@
-SUBDIRS = . tests templates
+SUBDIRS = . tests
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log

+ 0 - 10
tests/tools/perfdhcp/templates/Makefile.am

@@ -1,10 +0,0 @@
-SUBDIRS = .
-
-# The test[1-5].hex are created by the TestControl.PacketTemplates
-# unit tests and have to be removed.
-CLEANFILES = test1.hex test2.hex test3.hex test4.hex test5.hex
-
-perfdhcpdir = $(pkgdatadir)
-
-EXTRA_DIST = discover-example.hex request4-example.hex
-EXTRA_DIST += solicit-example.hex request6-example.hex

+ 5 - 1
tests/tools/perfdhcp/tests/Makefile.am

@@ -1,6 +1,7 @@
-SUBDIRS = .
+SUBDIRS = . testdata
 
 AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_srcdir)/testdata\"
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
@@ -9,6 +10,9 @@ AM_LDFLAGS = -static
 endif
 
 CLEANFILES = *.gcno *.gcda
+# The test[1-5].hex are created by the TestControl.PacketTemplates
+# unit tests and have to be removed.
+CLEANFILES += test1.hex test2.hex test3.hex test4.hex test5.hex
 
 TESTS_ENVIRONMENT = \
         $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)

+ 23 - 11
tests/tools/perfdhcp/tests/test_control_unittest.cc

@@ -185,6 +185,18 @@ public:
         return ("");
     }
 
+    /// \brief Get full path to a file in testdata directory.
+    ///
+    /// \param filename filename being appended to absolute
+    /// path to testdata directory
+    ///
+    /// \return full path to a file in testdata directory.
+    std::string getFullPath(const std::string& filename) const {
+        std::ostringstream stream;
+        stream << TEST_DATA_DIR << "/" << filename;
+        return (stream.str());
+    }
+
     /// \brief Match requested options in the buffer with given list.
     ///
     /// This method iterates through options provided in the buffer
@@ -896,7 +908,7 @@ TEST_F(TestControlTest, Packet6) {
     }
 }
 
-TEST_F(TestControlTest, DISABLED_Packet4Exchange) {
+TEST_F(TestControlTest, Packet4Exchange) {
     // Get the local loopback interface to open socket on
     // it and test packets exchanges. We don't want to fail
     // the test if interface is not available.
@@ -925,8 +937,8 @@ TEST_F(TestControlTest, DISABLED_Packet4Exchange) {
     // Use templates for this test.
     processCmdLine("perfdhcp -l " + loopback_iface
                    + " -r 100 -R 20 -n 20 -D 10% -L 10547"
-                   + " -T ../templates/discover-example.hex"
-                   + " -T ../templates/request4-example.hex"
+                   + " -T " + getFullPath("discover-example.hex")
+                   + " -T " + getFullPath("request4-example.hex")
                    + " 127.0.0.1");
     // The number iterations is restricted by the percentage of
     // dropped packets (-D 10%). We also have to bump up the number
@@ -939,7 +951,7 @@ TEST_F(TestControlTest, DISABLED_Packet4Exchange) {
     EXPECT_EQ(12, iterations_performed);
 }
 
-TEST_F(TestControlTest, DISABLED_Packet6Exchange) {
+TEST_F(TestControlTest, Packet6Exchange) {
     // Get the local loopback interface to open socket on
     // it and test packets exchanges. We don't want to fail
     // the test if interface is not available.
@@ -967,8 +979,8 @@ TEST_F(TestControlTest, DISABLED_Packet6Exchange) {
     use_templates = true;
     processCmdLine("perfdhcp -l " + loopback_iface
                    + " -6 -r 100 -n 10 -R 20 -D 3 -L 10547"
-                   + " -T ../templates/solicit-example.hex"
-                   + " -T ../templates/request6-example.hex ::1");
+                   + " -T " + getFullPath("solicit-example.hex")
+                   + " -T " + getFullPath("request6-example.hex ::1"));
     // For the first 3 packets we are simulating responses from server.
     // For other packets we don't so packet as 4,5,6 will be dropped and
     // then test should be interrupted and actual number of iterations will
@@ -981,9 +993,9 @@ TEST_F(TestControlTest, DISABLED_Packet6Exchange) {
 
 TEST_F(TestControlTest, PacketTemplates) {
     std::vector<uint8_t> template1(256);
-    std::string file1("../templates/test1.hex");
+    std::string file1("test1.hex");
     std::vector<uint8_t> template2(233);
-    std::string file2("../templates/test2.hex");
+    std::string file2("test2.hex");
     for (int i = 0; i < template1.size(); ++i) {
         template1[i] = static_cast<uint8_t>(random() % 256);
     }
@@ -1011,7 +1023,7 @@ TEST_F(TestControlTest, PacketTemplates) {
     EXPECT_TRUE(std::equal(template2.begin(), template2.end(), buf2.begin()));
 
     // Try to read template file with odd number of digits.
-    std::string file3("../templates/test3.hex");
+    std::string file3("test3.hex");
     // Size of the file is 2 times larger than binary data size and it is always
     // even number. Substracting 1 makes file size odd.
     ASSERT_TRUE(createTemplateFile(file3, template1, template1.size() * 2 - 1));
@@ -1021,7 +1033,7 @@ TEST_F(TestControlTest, PacketTemplates) {
     EXPECT_THROW(tc.initPacketTemplates(), isc::OutOfRange);
 
     // Try to read empty file.
-    std::string file4("../templates/test4.hex");
+    std::string file4("test4.hex");
     ASSERT_TRUE(createTemplateFile(file4, template2, 0));
     ASSERT_NO_THROW(
         processCmdLine("perfdhcp -l 127.0.0.1 -T " + file4 + " all")
@@ -1029,7 +1041,7 @@ TEST_F(TestControlTest, PacketTemplates) {
     EXPECT_THROW(tc.initPacketTemplates(), isc::OutOfRange);
 
     // Try reading file with non hexadecimal characters.
-    std::string file5("../templates/test5.hex");
+    std::string file5("test5.hex");
     ASSERT_TRUE(createTemplateFile(file5, template1, template1.size() * 2, true));
     ASSERT_NO_THROW(
         processCmdLine("perfdhcp -l 127.0.0.1 -T " + file5 + " all")

tests/tools/perfdhcp/templates/.gitignore → tests/tools/perfdhcp/tests/testdata/.gitignore


+ 4 - 0
tests/tools/perfdhcp/tests/testdata/Makefile.am

@@ -0,0 +1,4 @@
+SUBDIRS = .
+
+EXTRA_DIST = discover-example.hex request4-example.hex
+EXTRA_DIST += solicit-example.hex request6-example.hex

tests/tools/perfdhcp/templates/discover-example.hex → tests/tools/perfdhcp/tests/testdata/discover-example.hex


tests/tools/perfdhcp/templates/request4-example.hex → tests/tools/perfdhcp/tests/testdata/request4-example.hex


tests/tools/perfdhcp/templates/request6-example.hex → tests/tools/perfdhcp/tests/testdata/request6-example.hex


tests/tools/perfdhcp/templates/solicit-example.hex → tests/tools/perfdhcp/tests/testdata/solicit-example.hex