Browse Source

[3360] Implemented the CSV lease file parser for DHCPv4.

Marcin Siodelski 11 years ago
parent
commit
21a15e9e50

+ 33 - 24
src/lib/dhcp/duid.cc

@@ -49,24 +49,8 @@ DUID::DUID(const uint8_t* data, size_t len) {
     duid_ = std::vector<uint8_t>(data, data + len);
     duid_ = std::vector<uint8_t>(data, data + len);
 }
 }
 
 
-const std::vector<uint8_t>& DUID::getDuid() const {
-    return (duid_);
-}
-
-DUID::DUIDType DUID::getType() const {
-    if (duid_.size() < 2) {
-        return (DUID_UNKNOWN);
-    }
-    uint16_t type = (duid_[0] << 8) + duid_[1];
-    if (type < DUID_MAX) {
-        return (static_cast<DUID::DUIDType>(type));
-    } else {
-        return (DUID_UNKNOWN);
-    }
-}
-
-DUID
-DUID::fromText(const std::string& text) {
+std::vector<uint8_t>
+DUID::decode(const std::string& text) {
     /// @todo optimize stream operations here.
     /// @todo optimize stream operations here.
     std::vector<std::string> split_text;
     std::vector<std::string> split_text;
     boost::split(split_text, text, boost::is_any_of(":"),
     boost::split(split_text, text, boost::is_any_of(":"),
@@ -78,22 +62,41 @@ DUID::fromText(const std::string& text) {
             s << "0";
             s << "0";
 
 
         } else if (split_text[i].size() > 2) {
         } else if (split_text[i].size() > 2) {
-            isc_throw(isc::BadValue, "invalid DUID '" << text << "'");
+            isc_throw(isc::BadValue, "invalid identifier '" << text << "'");
         }
         }
         s << split_text[i];
         s << split_text[i];
     }
     }
-    if (s.str().empty()) {
-        isc_throw(isc::BadValue, "empty DUID is not allowed");
-    }
 
 
     std::vector<uint8_t> binary;
     std::vector<uint8_t> binary;
     try {
     try {
         util::encode::decodeHex(s.str(), binary);
         util::encode::decodeHex(s.str(), binary);
     } catch (const Exception& ex) {
     } catch (const Exception& ex) {
-        isc_throw(isc::BadValue, "failed to create DUID from text '"
+        isc_throw(isc::BadValue, "failed to create identifier from text '"
                   << text << "': " << ex.what());
                   << text << "': " << ex.what());
     }
     }
-    return DUID(&binary[0], binary.size());
+    return (binary);
+}
+
+const std::vector<uint8_t>& DUID::getDuid() const {
+    return (duid_);
+}
+
+DUID::DUIDType DUID::getType() const {
+    if (duid_.size() < 2) {
+        return (DUID_UNKNOWN);
+    }
+    uint16_t type = (duid_[0] << 8) + duid_[1];
+    if (type < DUID_MAX) {
+        return (static_cast<DUID::DUIDType>(type));
+    } else {
+        return (DUID_UNKNOWN);
+    }
+}
+
+DUID
+DUID::fromText(const std::string& text) {
+    std::vector<uint8_t> binary = decode(text);
+    return DUID(binary);
 }
 }
 
 
 std::string DUID::toText() const {
 std::string DUID::toText() const {
@@ -152,6 +155,12 @@ std::string ClientId::toText() const {
     return (DUID::toText());
     return (DUID::toText());
 }
 }
 
 
+ClientIdPtr
+ClientId::fromText(const std::string& text) {
+    std::vector<uint8_t> binary = decode(text);
+    return (ClientIdPtr(new ClientId(binary)));
+}
+
 // Compares two client-ids
 // Compares two client-ids
 bool ClientId::operator==(const ClientId& other) const {
 bool ClientId::operator==(const ClientId& other) const {
     return (this->duid_ == other.duid_);
     return (this->duid_ == other.duid_);

+ 35 - 11
src/lib/dhcp/duid.h

@@ -72,13 +72,7 @@ class DUID {
     /// @brief Create DUID from the textual format.
     /// @brief Create DUID from the textual format.
     ///
     ///
     /// This static function parses a DUID specified in the textual format.
     /// This static function parses a DUID specified in the textual format.
-    /// The format being parsed should match the DUID representation returned
-    /// by the @c DUID::toText method, i.e. the pairs of hexadecimal digits
-    /// representing bytes of DUID must be separated by colons. Usually the
-    /// single byte is represented by two hexadecimal digits. However, this
-    /// function allows one digit per byte. In this case, a zero is prepended
-    /// before the conversion. For example, a DUID 0:1:2::4:5 equals to
-    /// 00:01:02:00:04:05.
+    /// Internally it uses @c DUID::decode to parse the DUID.
     ///
     ///
     /// @param text DUID in the hexadecimal format with digits representing
     /// @param text DUID in the hexadecimal format with digits representing
     /// individual bytes separated by colons.
     /// individual bytes separated by colons.
@@ -96,6 +90,23 @@ class DUID {
     bool operator!=(const DUID& other) const;
     bool operator!=(const DUID& other) const;
 
 
  protected:
  protected:
+
+    /// @brief Decodes the textual format of the DUID.
+    ///
+    /// The format being parsed should match the DUID representation returned
+    /// by the @c DUID::toText method, i.e. the pairs of hexadecimal digits
+    /// representing bytes of DUID must be separated by colons. Usually the
+    /// single byte is represented by two hexadecimal digits. However, this
+    /// function allows one digit per byte. In this case, a zero is prepended
+    /// before the conversion. For example, a DUID 0:1:2::4:5 equals to
+    /// 00:01:02:00:04:05.
+    ///
+    /// @param text DUID in the hexadecimal format with digits representing
+    /// individual bytes separated by colons.
+    ///
+    /// @throw isc::BadValue if parsing the DUID failed.
+    static std::vector<uint8_t> decode(const std::string& text);
+
     /// The actual content of the DUID
     /// The actual content of the DUID
     std::vector<uint8_t> duid_;
     std::vector<uint8_t> duid_;
 };
 };
@@ -103,7 +114,10 @@ class DUID {
 /// @brief Shared pointer to a DUID
 /// @brief Shared pointer to a DUID
 typedef boost::shared_ptr<DUID> DuidPtr;
 typedef boost::shared_ptr<DUID> DuidPtr;
 
 
-
+/// @brief Forward declaration to the @c ClientId class.
+class ClientId;
+/// @brief Shared pointer to a Client ID.
+typedef boost::shared_ptr<ClientId> ClientIdPtr;
 
 
 /// @brief Holds Client identifier or client IPv4 address
 /// @brief Holds Client identifier or client IPv4 address
 ///
 ///
@@ -147,6 +161,19 @@ public:
     /// @brief Returns textual representation of a DUID (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;
     std::string toText() const;
 
 
+    /// @brief Create client identifier from the textual format.
+    ///
+    /// This static function creates the instance of the @c ClientId from the
+    /// textual format. Internally it calls @c DUID::fromText. The format of
+    /// the input must match the format of the DUID in @c DUID::fromText.
+    ///
+    /// @param text Client identifier in the textual format.
+    ///
+    /// @return Pointer to the instance of the @c ClientId.
+    /// @throw isc::BadValue if parsing the client identifier failed.
+    /// @throw isc::OutOfRange if the client identifier is truncated.
+    static ClientIdPtr fromText(const std::string& text);
+
     /// @brief Compares two client-ids for equality
     /// @brief Compares two client-ids for equality
     bool operator==(const ClientId& other) const;
     bool operator==(const ClientId& other) const;
 
 
@@ -154,9 +181,6 @@ public:
     bool operator!=(const ClientId& other) const;
     bool operator!=(const ClientId& other) const;
 };
 };
 
 
-/// @brief Shared pointer to a Client ID.
-typedef boost::shared_ptr<ClientId> ClientIdPtr;
-
 }; // end of isc::dhcp namespace
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
 }; // end of isc namespace
 
 

+ 37 - 3
src/lib/dhcp/hwaddr.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -15,6 +15,10 @@
 #include <dhcp/hwaddr.h>
 #include <dhcp/hwaddr.h>
 #include <dhcp/dhcp4.h>
 #include <dhcp/dhcp4.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
+#include <util/encode/hex.h>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/constants.hpp>
+#include <boost/algorithm/string/split.hpp>
 #include <iomanip>
 #include <iomanip>
 #include <sstream>
 #include <sstream>
 #include <vector>
 #include <vector>
@@ -42,9 +46,11 @@ HWAddr::HWAddr(const std::vector<uint8_t>& hwaddr, uint8_t htype)
     }
     }
 }
 }
 
 
-std::string HWAddr::toText() const {
+std::string HWAddr::toText(bool include_htype) const {
     std::stringstream tmp;
     std::stringstream tmp;
-    tmp << "hwtype=" << static_cast<int>(htype_) << " ";
+    if (include_htype) {
+        tmp << "hwtype=" << static_cast<int>(htype_) << " ";
+    }
     tmp << std::hex;
     tmp << std::hex;
     bool delim = false;
     bool delim = false;
     for (std::vector<uint8_t>::const_iterator it = hwaddr_.begin();
     for (std::vector<uint8_t>::const_iterator it = hwaddr_.begin();
@@ -58,6 +64,34 @@ std::string HWAddr::toText() const {
     return (tmp.str());
     return (tmp.str());
 }
 }
 
 
+HWAddr
+HWAddr::fromText(const std::string& text) {
+    /// @todo optimize stream operations here.
+    std::vector<std::string> split_text;
+    boost::split(split_text, text, boost::is_any_of(":"),
+                 boost::algorithm::token_compress_on);
+
+    std::ostringstream s;
+    for (size_t i = 0; i < split_text.size(); ++i) {
+        if (split_text[i].size() == 1) {
+            s << "0";
+
+        } else if (split_text[i].size() > 2) {
+            isc_throw(isc::BadValue, "invalid hwaddr '" << text << "'");
+        }
+        s << split_text[i];
+    }
+
+    std::vector<uint8_t> binary;
+    try {
+        util::encode::decodeHex(s.str(), binary);
+    } catch (const Exception& ex) {
+        isc_throw(isc::BadValue, "failed to create hwaddr from text '"
+                  << text << "': " << ex.what());
+    }
+    return (HWAddr(binary, HTYPE_ETHER));
+}
+
 bool HWAddr::operator==(const HWAddr& other) const {
 bool HWAddr::operator==(const HWAddr& other) const {
     return ((this->htype_  == other.htype_) &&
     return ((this->htype_  == other.htype_) &&
             (this->hwaddr_ == other.hwaddr_));
             (this->hwaddr_ == other.hwaddr_));

+ 29 - 3
src/lib/dhcp/hwaddr.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -54,8 +54,34 @@ public:
     // Hardware type
     // Hardware type
     uint8_t htype_;
     uint8_t htype_;
 
 
-    /// @brief Returns textual representation of a client-id (e.g. 00:01:02:03)
-    std::string toText() const;
+    /// @brief Returns textual representation of a hardware address
+    /// (e.g. 00:01:02:03:04:05)
+    ///
+    /// @param include_htype Boolean value which controls whether the hardware
+    /// type is included in the returned string (true), or not (false).
+    ///
+    /// @return Hardware address in the textual format.
+    std::string toText(bool include_htype = true) const;
+
+    /// @brief Creates instance of the hardware address from textual format.
+    ///
+    /// This function parses HW address specified as text and creates the
+    /// corresponding @c HWAddr instance. The hexadecimal digits representing
+    /// individual bytes of the hardware address should be separated with
+    /// colons. Typically, two digits per byte are used. However, this function
+    /// allows for 1 digit per HW address byte. In this case, the digit is
+    /// prepended with '0' during conversion to binary value.
+    ///
+    /// This function can be used to perform a reverse operation to the
+    /// @c HWAddr::toText(false).
+    ///
+    /// The instance created by this function sets HTYPE_ETHER as a hardware
+    /// type.
+    ///
+    /// @param text HW address in the textual format.
+    ///
+    /// @return Instance of the HW address created from text.
+    static HWAddr fromText(const std::string& text);
 
 
     /// @brief Compares two hardware addresses for equality
     /// @brief Compares two hardware addresses for equality
     bool operator==(const HWAddr& other) const;
     bool operator==(const HWAddr& other) const;

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

@@ -280,4 +280,36 @@ TEST(ClientIdTest, toText) {
     EXPECT_EQ("00:01:02:03:04:ff:fe", clientid.toText());
     EXPECT_EQ("00:01:02:03:04:ff:fe", clientid.toText());
 }
 }
 
 
+// This test checks that the ClientId instance can be created from the textual
+// format and that error is reported if the textual format is invalid.
+TEST(ClientIdTest, fromText) {
+    ClientIdPtr cid;
+    // ClientId with only decimal digits.
+    ASSERT_NO_THROW(
+        cid = ClientId::fromText("00:01:02:03:04:05:06")
+    );
+    EXPECT_EQ("00:01:02:03:04:05:06", cid->toText());
+    // ClientId with some hexadecimal digits (upper case and lower case).
+    ASSERT_NO_THROW(
+        cid = ClientId::fromText("00:aa:bb:CD:ee:EF:ab")
+    );
+    EXPECT_EQ("00:aa:bb:cd:ee:ef:ab", cid->toText());
+    // ClientId with one digit for a particular byte.
+    ASSERT_NO_THROW(
+        cid = ClientId::fromText("00:a:bb:D:ee:EF:ab")
+    );
+    EXPECT_EQ("00:0a:bb:0d:ee:ef:ab", cid->toText());
+    // Repeated colon sign in the ClientId should be ignored.
+    ASSERT_NO_THROW(
+        cid = ClientId::fromText("00::bb:D:ee:EF:ab")
+    );
+    EXPECT_EQ("00:bb:0d:ee:ef:ab", cid->toText());
+    // ClientId with excessive number of digits for one of the bytes.
+    EXPECT_THROW(
+       cid = ClientId::fromText("00:01:021:03:04:05:06"),
+       isc::BadValue
+    );
+}
+
+
 } // end of anonymous namespace
 } // end of anonymous namespace

+ 26 - 1
src/lib/dhcp/tests/hwaddr_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012, 2014 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -108,6 +108,9 @@ TEST(HWAddrTest, toText) {
 
 
     EXPECT_EQ("hwtype=15 00:01:02:03:04:05", hw->toText());
     EXPECT_EQ("hwtype=15 00:01:02:03:04:05", hw->toText());
 
 
+    // In some cases we don't want htype value to be included. Check that
+    // it can be forced.
+    EXPECT_EQ("00:01:02:03:04:05", hw->toText(false));
 }
 }
 
 
 TEST(HWAddrTest, stringConversion) {
 TEST(HWAddrTest, stringConversion) {
@@ -131,5 +134,27 @@ TEST(HWAddrTest, stringConversion) {
     EXPECT_EQ(std::string("hwtype=1 c3:07:a2:e8:42"), result);
     EXPECT_EQ(std::string("hwtype=1 c3:07:a2:e8:42"), result);
 }
 }
 
 
+// Checks that the HW address can be created from the textual format.
+TEST(HWAddrTest, fromText) {
+    scoped_ptr<HWAddr> hwaddr;
+    // Create HWAddr from text.
+    ASSERT_NO_THROW(
+        hwaddr.reset(new HWAddr(HWAddr::fromText("00:01:A:bc:d:67")));
+    );
+    EXPECT_EQ("00:01:0a:bc:0d:67", hwaddr->toText(false));
+
+    // HWAddr class should allow empty address.
+    ASSERT_NO_THROW(
+        hwaddr.reset(new HWAddr(HWAddr::fromText("")));
+    );
+    EXPECT_TRUE(hwaddr->toText(false).empty());
+
+    // There should be no more than two digits per byte of the HW addr.
+    EXPECT_THROW(
+       hwaddr.reset(new HWAddr(HWAddr::fromText("00:01:00A:bc:0d:67"))),
+       isc::BadValue
+    );
+
+}
 
 
 } // end of anonymous namespace
 } // end of anonymous namespace

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

@@ -39,6 +39,7 @@ libb10_dhcpsrv_la_SOURCES  =
 libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
 libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
 libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
 libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
 libb10_dhcpsrv_la_SOURCES += callout_handle_store.h
 libb10_dhcpsrv_la_SOURCES += callout_handle_store.h
+libb10_dhcpsrv_la_SOURCES += csv_lease_file4.cc csv_lease_file4.h
 libb10_dhcpsrv_la_SOURCES += csv_lease_file6.cc csv_lease_file6.h
 libb10_dhcpsrv_la_SOURCES += csv_lease_file6.cc csv_lease_file6.h
 libb10_dhcpsrv_la_SOURCES += d2_client_cfg.cc d2_client_cfg.h
 libb10_dhcpsrv_la_SOURCES += d2_client_cfg.cc d2_client_cfg.h
 libb10_dhcpsrv_la_SOURCES += d2_client_mgr.cc d2_client_mgr.h
 libb10_dhcpsrv_la_SOURCES += d2_client_mgr.cc d2_client_mgr.h

+ 178 - 0
src/lib/dhcpsrv/csv_lease_file4.cc

@@ -0,0 +1,178 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcpsrv/csv_lease_file4.h>
+
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+CSVLeaseFile4::CSVLeaseFile4(const std::string& filename)
+    : CSVFile(filename) {
+    initColumns();
+}
+
+void
+CSVLeaseFile4::append(const Lease4& lease) const {
+    CSVRow row(getColumnCount());
+    row.writeAt(getColumnIndex("address"), lease.addr_.toText());
+    HWAddr hwaddr(lease.hwaddr_, HTYPE_ETHER);
+    row.writeAt(getColumnIndex("hwaddr"), hwaddr.toText(false));
+    // Client id may be unset (NULL).
+    if (lease.client_id_) {
+        row.writeAt(getColumnIndex("client_id"), lease.client_id_->toText());
+    }
+    row.writeAt(getColumnIndex("valid_lifetime"), lease.valid_lft_);
+    row.writeAt(getColumnIndex("expire"), lease.cltt_ + lease.valid_lft_);
+    row.writeAt(getColumnIndex("subnet_id"), lease.subnet_id_);
+    row.writeAt(getColumnIndex("fqdn_fwd"), lease.fqdn_fwd_);
+    row.writeAt(getColumnIndex("fqdn_rev"), lease.fqdn_rev_);
+    row.writeAt(getColumnIndex("hostname"), lease.hostname_);
+    CSVFile::append(row);
+}
+
+bool
+CSVLeaseFile4::next(Lease4Ptr& lease) {
+    // We will return NULL pointer if the lease is not read.
+    lease.reset();
+    // Get the row of CSV values.
+    CSVRow row;
+    CSVFile::next(row);
+    // The empty row signals EOF.
+    if (row == CSVFile::EMPTY_ROW()) {
+        return (true);
+    }
+
+    // Try to create a lease from the values read. This may easily result in
+    // exception. We don't want this function to throw exceptions, so we catch
+    // them all and rather return the false value.
+    try {
+        // Get client id. It is possible that the client id is empty and the
+        // returned pointer is NULL. This is ok, but if the client id is NULL,
+        // we need to be careful to not use the NULL pointer.
+        ClientIdPtr client_id = readClientId(row);
+        std::vector<uint8_t> client_id_vec;
+        if (client_id) {
+            client_id_vec = client_id->getClientId();
+        }
+        size_t client_id_len = client_id_vec.empty() ? 0 : client_id_vec.size();
+
+        // Get the HW address. It should never be empty and the readHWAddr checks
+        // that.
+        HWAddr hwaddr = readHWAddr(row);
+        lease.reset(new Lease4(readAddress(row),
+                               &hwaddr.hwaddr_[0], hwaddr.hwaddr_.size(),
+                               client_id_vec.empty() ? NULL : &client_id_vec[0],
+                               client_id_len,
+                               readValid(row),
+                               0, 0, // t1, t2 = 0
+                               readCltt(row),
+                               readSubnetID(row),
+                               readFqdnFwd(row),
+                               readFqdnRev(row),
+                               readHostname(row)));
+
+    } catch (std::exception& ex) {
+        // The lease might have been created, so let's set it back to NULL to
+        // signal that lease hasn't been parsed.
+        lease.reset();
+        setReadMsg(ex.what());
+        return (false);
+    }
+    return (true);
+}
+
+void
+CSVLeaseFile4::initColumns() {
+    addColumn("address");
+    addColumn("hwaddr");
+    addColumn("client_id");
+    addColumn("valid_lifetime");
+    addColumn("expire");
+    addColumn("subnet_id");
+    addColumn("fqdn_fwd");
+    addColumn("fqdn_rev");
+    addColumn("hostname");
+}
+
+IOAddress
+CSVLeaseFile4::readAddress(const CSVRow& row) {
+    IOAddress address(row.readAt(getColumnIndex("address")));
+    return (address);
+}
+
+HWAddr
+CSVLeaseFile4::readHWAddr(const CSVRow& row) {
+    HWAddr hwaddr = HWAddr::fromText(row.readAt(getColumnIndex("hwaddr")));
+    if (hwaddr.hwaddr_.empty()) {
+        isc_throw(isc::BadValue, "hardware address in the lease file"
+                  " must not be empty");
+    }
+    return (hwaddr);
+}
+
+ClientIdPtr
+CSVLeaseFile4::readClientId(const CSVRow& row) {
+    std::string client_id = row.readAt(getColumnIndex("client_id"));
+    // NULL client ids are allowed in DHCPv4.
+    if (client_id.empty()) {
+        return (ClientIdPtr());
+    }
+    ClientIdPtr cid = ClientId::fromText(client_id);
+    return (cid);
+}
+
+uint32_t
+CSVLeaseFile4::readValid(const CSVRow& row) {
+    uint32_t valid =
+        row.readAndConvertAt<uint32_t>(getColumnIndex("valid_lifetime"));
+    return (valid);
+}
+
+time_t
+CSVLeaseFile4::readCltt(const CSVRow& row) {
+    uint32_t cltt = row.readAndConvertAt<uint32_t>(getColumnIndex("expire"))
+        - readValid(row);
+    return (cltt);
+}
+
+SubnetID
+CSVLeaseFile4::readSubnetID(const CSVRow& row) {
+    SubnetID subnet_id =
+        row.readAndConvertAt<SubnetID>(getColumnIndex("subnet_id"));
+    return (subnet_id);
+}
+
+bool
+CSVLeaseFile4::readFqdnFwd(const CSVRow& row) {
+    bool fqdn_fwd = row.readAndConvertAt<bool>(getColumnIndex("fqdn_fwd"));
+    return (fqdn_fwd);
+}
+
+bool
+CSVLeaseFile4::readFqdnRev(const CSVRow& row) {
+    bool fqdn_rev = row.readAndConvertAt<bool>(getColumnIndex("fqdn_rev"));
+    return (fqdn_rev);
+}
+
+std::string
+CSVLeaseFile4::readHostname(const CSVRow& row) {
+    std::string hostname = row.readAt(getColumnIndex("hostname"));
+    return (hostname);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc

+ 153 - 0
src/lib/dhcpsrv/csv_lease_file4.h

@@ -0,0 +1,153 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef CSV_LEASE_FILE4_H
+#define CSV_LEASE_FILE4_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/subnet.h>
+#include <util/csv_file.h>
+#include <stdint.h>
+#include <string>
+#include <time.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Provides methods to access CSV file with DHCPv4 leases.
+///
+/// This class contains methods customized to read DHCPv4 leases from the CSV
+/// file. It expects that the CSV file being parsed, contains the set of columns
+/// with well known names (initialized in the class constructor).
+///
+/// @todo This class doesn't validate the lease values read from the file.
+/// The @c Lease4 is a structure that should be itself responsible for this
+/// validation (see http://bind10.isc.org/ticket/2405). However, when #2405
+/// is implemented, the @c next function may need to be updated to use the
+/// validation capablity of @c Lease4.
+class CSVLeaseFile4 : public isc::util::CSVFile {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Initializes columns of the lease file.
+    ///
+    /// @param filename Name of the lease file.
+    CSVLeaseFile4(const std::string& filename);
+
+    /// @brief Appends the lease record to the CSV file.
+    ///
+    /// This function doesn't throw exceptions itself. In theory, exceptions
+    /// are possible when the index of the indexes of the values being written
+    /// to the file are invalid. However, this would have been a programming
+    /// error.
+    ///
+    /// @param lease Structure representing a DHCPv4 lease.
+    void append(const Lease4& lease) const;
+
+    /// @brief Reads next lease from the CSV file.
+    ///
+    /// If this function hits an error during lease read, it sets the error
+    /// message using @c CSVFile::setReadMsg and returns false. The error
+    /// string may be read using @c CSVFile::getReadMsg.
+    ///
+    /// This function is exception safe.
+    ///
+    /// @param [out] lease Pointer to the lease read from CSV file or
+    /// NULL pointer if lease hasn't been read.
+    ///
+    /// @return Boolean value indicating that the new lease has been
+    /// read from the CSV file (if true), or that the error has occurred
+    /// (false).
+    ///
+    /// @todo Make sure that the values read from the file are correct.
+    /// The appropriate @c Lease4 validation mechanism should be used once
+    /// ticket http://bind10.isc.org/ticket/2405 is implemented.
+    bool next(Lease4Ptr& lease);
+
+private:
+
+    /// @brief Initializes columns of the CSV file holding leases.
+    ///
+    /// This function initializes the following columns:
+    /// - address
+    /// - hwaddr
+    /// - client_id
+    /// - valid_lifetime
+    /// - expire
+    /// - subnet_id
+    /// - fqdn_fwd
+    /// - fqdn_rev
+    /// - hostname
+    void initColumns();
+
+    ///
+    /// @name Methods which read specific lease fields from the CSV row.
+    ///
+    //@{
+    ///
+    /// @brief Reads lease address from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    asiolink::IOAddress readAddress(const util::CSVRow& row);
+
+    /// @brief Reads HW address from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    HWAddr readHWAddr(const util::CSVRow& row);
+
+    /// @brief Reads client identifier from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    ClientIdPtr readClientId(const util::CSVRow& row);
+
+    /// @brief Reads valid lifetime from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    uint32_t readValid(const util::CSVRow& row);
+
+    /// @brief Reads cltt value from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    time_t readCltt(const util::CSVRow& row);
+
+    /// @brief Reads subnet id from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    SubnetID readSubnetID(const util::CSVRow& row);
+
+    /// @brief Reads the FQDN forward flag from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    bool readFqdnFwd(const util::CSVRow& row);
+
+    /// @brief Reads the FQDN reverse flag from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    bool readFqdnRev(const util::CSVRow& row);
+
+    /// @brief Reads hostname from the CSV file row.
+    ///
+    /// @param row CSV file holding lease values.
+    std::string readHostname(const util::CSVRow& row);
+    //@}
+
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // CSV_LEASE_FILE4_H

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

@@ -56,6 +56,7 @@ libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc
 libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
 libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
 libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc
 libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += csv_lease_file4_unittest.cc
 libdhcpsrv_unittests_SOURCES += csv_lease_file6_unittest.cc
 libdhcpsrv_unittests_SOURCES += csv_lease_file6_unittest.cc
 libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc
 libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc
 libdhcpsrv_unittests_SOURCES += d2_udp_unittest.cc
 libdhcpsrv_unittests_SOURCES += d2_udp_unittest.cc

+ 166 - 0
src/lib/dhcpsrv/tests/csv_lease_file4_unittest.cc

@@ -0,0 +1,166 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcpsrv/csv_lease_file4.h>
+#include <dhcpsrv/lease.h>
+#include <dhcpsrv/tests/lease_file_io.h>
+#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::util;
+
+namespace {
+
+// HWADDR values used by unit tests.
+const uint8_t HWADDR0[] = { 0, 1, 2, 3, 4, 5 };
+const uint8_t HWADDR1[] = { 0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf };
+
+const uint8_t CLIENTID0[] = { 1, 2, 3, 4 };
+const uint8_t CLIENTID1[] = { 0xa, 0xb, 0xc, 0xd };
+
+/// @brief Test fixture class for @c CSVLeaseFile4 validation.
+class CSVLeaseFile4Test : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Initializes IO for lease file used by unit tests.
+    CSVLeaseFile4Test();
+
+    /// @brief Prepends the absolute path to the file specified
+    /// as an argument.
+    ///
+    /// @param filename Name of the file.
+    /// @return Absolute path to the test file.
+    static std::string absolutePath(const std::string& filename);
+
+    /// @brief Name of the test lease file.
+    std::string filename_;
+
+    /// @brief Object providing access to lease file IO.
+    LeaseFileIO io_;
+
+};
+
+CSVLeaseFile4Test::CSVLeaseFile4Test()
+    : filename_(absolutePath("leases4.csv")), io_(filename_) {
+}
+
+std::string
+CSVLeaseFile4Test::absolutePath(const std::string& filename) {
+    std::ostringstream s;
+    s << TEST_DATA_BUILDDIR << "/" << filename;
+    return (s.str());
+}
+
+// This test checks the capability to read and parse leases from the file.
+TEST_F(CSVLeaseFile4Test, parse) {
+    // Open the lease file.
+    boost::scoped_ptr<CSVLeaseFile4>
+        lf(new CSVLeaseFile4(absolutePath("leases4_0.csv")));
+    ASSERT_NO_THROW(lf->open());
+
+    Lease4Ptr lease;
+    // Reading first read should be successful.
+    EXPECT_TRUE(lf->next(lease));
+    ASSERT_TRUE(lease);
+
+    // Verify that the lease attributes are correct.
+    EXPECT_EQ("192.0.2.1", lease->addr_.toText());
+    HWAddr hwaddr1(lease->hwaddr_, HTYPE_ETHER);
+    EXPECT_EQ("06:07:08:09:0a:bc", hwaddr1.toText(false));
+    EXPECT_FALSE(lease->client_id_);
+    EXPECT_EQ(200, lease->valid_lft_);
+    EXPECT_EQ(0, lease->cltt_);
+    EXPECT_EQ(8, lease->subnet_id_);
+    EXPECT_TRUE(lease->fqdn_fwd_);
+    EXPECT_TRUE(lease->fqdn_rev_);
+    EXPECT_EQ("host.example.com", lease->hostname_);
+
+    // Second lease is malformed - HW address is empty.
+    EXPECT_FALSE(lf->next(lease));
+
+    // Even though parsing previous lease failed, reading the next lease should be
+    // successful.
+    EXPECT_TRUE(lf->next(lease));
+    ASSERT_TRUE(lease);
+    // Verify that the third lease is correct.
+    EXPECT_EQ("192.0.3.15", lease->addr_.toText());
+    HWAddr hwaddr3(lease->hwaddr_, HTYPE_ETHER);
+    EXPECT_EQ("dd:de:ba:0d:1b:2e:3e:4f", hwaddr3.toText(false));
+    ASSERT_TRUE(lease->client_id_);
+    EXPECT_EQ("0a:00:01:04", lease->client_id_->toText());
+    EXPECT_EQ(100, lease->valid_lft_);
+    EXPECT_EQ(0, lease->cltt_);
+    EXPECT_EQ(7, lease->subnet_id_);
+    EXPECT_FALSE(lease->fqdn_fwd_);
+    EXPECT_FALSE(lease->fqdn_rev_);
+    EXPECT_TRUE(lease->hostname_.empty());
+
+    // There are no more leases. Reading should cause no error, but the returned
+    // lease pointer should be NULL.
+    EXPECT_TRUE(lf->next(lease));
+    EXPECT_FALSE(lease);
+
+    // We should be able to do it again.
+    EXPECT_TRUE(lf->next(lease));
+    EXPECT_FALSE(lease);
+
+}
+
+// This test checks creation of the lease file and writing leases.
+TEST_F(CSVLeaseFile4Test, recreate) {
+    boost::scoped_ptr<CSVLeaseFile4> lf(new CSVLeaseFile4(filename_));
+    ASSERT_NO_THROW(lf->recreate());
+    ASSERT_TRUE(io_.exists());
+    // Create first lease, with NULL client id.
+    Lease4Ptr lease(new Lease4(IOAddress("192.0.3.2"),
+                               HWADDR0, sizeof(HWADDR0),
+                               NULL, 0,
+                               200, 50, 80, 0, 8, true, true,
+                               "host.example.com"));
+    ASSERT_NO_THROW(lf->append(*lease));
+    // Create second lease, with non-NULL client id.
+    lease.reset(new Lease4(IOAddress("192.0.3.10"),
+                           HWADDR1, sizeof(HWADDR1),
+                           CLIENTID0, sizeof(CLIENTID0),
+                           100, 60, 90, 0, 7));
+    ASSERT_NO_THROW(lf->append(*lease));
+    // Close the lease file.
+    lf->close();
+    // Check that the contents of the csv file are correct.
+    EXPECT_EQ("address,hwaddr,client_id,valid_lifetime,expire,subnet_id,"
+              "fqdn_fwd,fqdn_rev,hostname\n"
+              "192.0.3.2,00:01:02:03:04:05,,200,200,8,1,1,host.example.com\n"
+              "192.0.3.10,0d:0e:0a:0d:0b:0e:0e:0f,01:02:03:04,100,100,7,0,"
+              "0,\n",
+              io_.readFile());
+}
+
+/// @todo Currently we don't check invalid lease attributes, such as invalid
+/// lease type, invalid preferred lifetime vs valid lifetime etc. The Lease6
+/// should be extended with the function that validates lease attributes. Once
+/// this is implemented we should provide more tests for malformed leases
+/// in the CSV file. See http://bind10.isc.org/ticket/2405.
+
+} // end of anonymous namespace

+ 4 - 0
src/lib/dhcpsrv/tests/testdata/leases4_0.csv

@@ -0,0 +1,4 @@
+address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname
+192.0.2.1,06:07:08:09:0a:bc,,200,200,8,1,1,host.example.com
+192.0.2.1,,a:11:01:04,200,200,8,1,1,host.example.com
+192.0.3.15,dd:de:ba:0d:1b:2e:3e:4f,0a:00:01:04,100,100,7,0,0,