Browse Source

[2491] Added a function to write/read FQDN to/from buffer.

Marcin Siodelski 12 years ago
parent
commit
22a33dfddc

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

@@ -37,6 +37,7 @@ libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
 libb10_dhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
 libb10_dhcp___la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
 libb10_dhcp___la_LIBADD   = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+libb10_dhcp___la_LIBADD  += $(top_builddir)/src/lib/dns/libb10-dns++.la
 libb10_dhcp___la_LIBADD  += $(top_builddir)/src/lib/util/libb10-util.la
 libb10_dhcp___la_LDFLAGS  = -no-undefined -version-info 2:0:0
 

+ 44 - 0
src/lib/dhcp/option_data_types.cc

@@ -13,6 +13,8 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <dhcp/option_data_types.h>
+#include <dns/labelsequence.h>
+#include <dns/name.h>
 #include <util/encode/hex.h>
 
 namespace isc {
@@ -207,6 +209,48 @@ OptionDataTypeUtil::writeBool(const bool value,
 }
 
 std::string
+OptionDataTypeUtil::readFqdn(const std::vector<uint8_t>& buf) {
+    // If buffer is empty emit an error.
+    if (buf.empty()) {
+        isc_throw(BadDataTypeCast, "unable to read FQDN from a buffer."
+                  << " The buffer is empty");
+    }
+    // Copy the data from a buffer to InputBuffer so as we can use
+    // isc::dns::Name object to get the FQDN. This is not the most
+    // efficient way to do it but currently there is no construtor
+    // in Name that would use std::vector directly.
+    isc::util::InputBuffer in_buf(static_cast<const void*>(&buf[0]), buf.size());
+    try {
+        // Try to create an object from the buffer. If exception is thrown
+        // it means that the buffer doesn't hold a valid domain name (invalid
+        // syntax).
+        isc::dns::Name name(in_buf);
+        return (name.toText());
+    } catch (const isc::Exception& ex) {
+        // Unable to convert the data in the buffer into FQDN.
+        isc_throw(BadDataTypeCast, ex.what());
+    }
+}
+
+void
+OptionDataTypeUtil::writeFqdn(const std::string& fqdn,
+                              std::vector<uint8_t>& buf) {
+    try {
+        isc::dns::Name name(fqdn);
+        isc::dns::LabelSequence labels(name);
+        if (labels.getDataLength() > 0) {
+            buf.resize(buf.size() + labels.getDataLength());
+            size_t read_len = 0;
+            const uint8_t* data = labels.getData(&read_len);
+            memcpy(static_cast<void*>(&buf[buf.size() - labels.getDataLength()]),
+                   data, read_len);
+        }
+    } catch (const isc::Exception& ex) {
+        isc_throw(BadDataTypeCast, ex.what());
+    }
+}
+
+std::string
 OptionDataTypeUtil::readString(const std::vector<uint8_t>& buf) {
     std::string value;
     if (buf.size() > 0) {

+ 27 - 0
src/lib/dhcp/option_data_types.h

@@ -332,6 +332,33 @@ public:
         }
     }
 
+    /// @brief Read FQDN from a buffer as a string value.
+    ///
+    /// The format of an FQDN within a buffer complies with RFC1035,
+    /// section 3.1.
+    ///
+    /// @param buf input buffer holding a FQDN.
+    ///
+    /// @throw BadDataTypeCast if a FQDN stored within a buffer is
+    /// invalid (e.g. empty, contains invalid characters, truncated).
+    /// @return fully qualified domain name in a text form.
+    static std::string readFqdn(const std::vector<uint8_t>& buf);
+
+    /// @brief Append FQDN into a buffer.
+    ///
+    /// This method appends the Fully Qualified Domain Name (FQDN)
+    /// represented as string value into a buffer. The format of
+    /// the FQDN being stored into a buffer complies with RFC1035,
+    /// section 3.1.
+    ///
+    /// @param fqdn fully qualified domain name to be written.
+    /// @param [out] buf output buffer.
+    ///
+    /// @throw isc::dhcp::BadDataTypeCast if provided FQDN
+    /// is invalid.
+    static void writeFqdn(const std::string& fqdn,
+                          std::vector<uint8_t>& buf);
+
     /// @brief Read string value from a buffer.
     ///
     /// @param buf input buffer.

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

@@ -35,6 +35,7 @@ libdhcp___unittests_SOURCES += option6_ia_unittest.cc
 libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc
 libdhcp___unittests_SOURCES += option6_int_array_unittest.cc
 libdhcp___unittests_SOURCES += option6_int_unittest.cc
+libdhcp___unittests_SOURCES += option_data_types_unittest.cc
 libdhcp___unittests_SOURCES += option_definition_unittest.cc
 libdhcp___unittests_SOURCES += option_custom_unittest.cc
 libdhcp___unittests_SOURCES += option_unittest.cc

+ 144 - 0
src/lib/dhcp/tests/option_data_types_unittest.cc

@@ -0,0 +1,144 @@
+// 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 <config.h>
+#include <dhcp/option_data_types.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Test class for option data type utilities.
+class OptionDataTypesTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    OptionDataTypesTest() { }
+
+};
+
+// The purpose of this test is to verify that FQDN is read from
+// a buffer and returned as a text. The representation of the FQDN
+// in the buffer complies with RFC1035, section 3.1.
+// This test also checks that if invalid (truncated) FQDN is stored
+// in a buffer the appropriate exception is returned when trying to
+// read it as a string.
+TEST_F(OptionDataTypesTest, readFqdn) {
+    // The binary representation of the "mydomain.example.com".
+    // Values: 8, 7, 3 and 0 specify the lengths of subsequent
+    // labels within the FQDN.
+    const char data[] = {
+        8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0
+    };
+
+    // Make a vector out of the data.
+    std::vector<uint8_t> buf(data, data + sizeof(data));
+
+    // Read the buffer as FQDN and verify its correctness.
+    std::string fqdn;
+    EXPECT_NO_THROW(fqdn = OptionDataTypeUtil::readFqdn(buf));
+    EXPECT_EQ("mydomain.example.com.", fqdn);
+
+    // By resizing the buffer we simulate truncation. The first
+    // length field (8) indicate that the first label's size is
+    // 8 but the actual buffer size is 5. Expect that conversion
+    // fails.
+    buf.resize(5);
+    EXPECT_THROW(
+        OptionDataTypeUtil::readFqdn(buf),
+        isc::dhcp::BadDataTypeCast
+    );
+
+    // Another special case: provide an empty buffer.
+    buf.clear();
+    EXPECT_THROW(
+        OptionDataTypeUtil::readFqdn(buf),
+        isc::dhcp::BadDataTypeCast
+    );
+}
+
+// The purpose of this test is to verify that FQDN's syntax is validated
+// and that FQDN is correctly written to a buffer in a format described
+// in RFC1035 section 3.1.
+TEST_F(OptionDataTypesTest, writeFqdn) {
+    // Create empty buffer. The FQDN will be written to it.
+    OptionBuffer buf;
+    // Write a domain name into the buffer in the format described
+    // in RFC1035 section 3.1. This function should not throw
+    // exception because domain name is well formed.
+    EXPECT_NO_THROW(
+        OptionDataTypeUtil::writeFqdn("mydomain.example.com", buf)
+    );
+    // The length of the data is 22 (8 bytes for "mydomain" label,
+    // 7 bytes for "example" label, 3 bytes for "com" label and
+    // finally 4 bytes positions between labels where length
+    // information is stored.
+    ASSERT_EQ(22, buf.size());
+
+    // Verify that length fields between labels hold valid values.
+    EXPECT_EQ(8, buf[0]);  // length of "mydomain"
+    EXPECT_EQ(7, buf[9]);  // length of "example"
+    EXPECT_EQ(3, buf[17]); // length of "com"
+    EXPECT_EQ(0, buf[21]); // zero byte at the end.
+
+    // Verify that labels are valid.
+    std::string label0(buf.begin() + 1, buf.begin() + 9);
+    EXPECT_EQ("mydomain", label0);
+
+    std::string label1(buf.begin() + 10, buf.begin() + 17);
+    EXPECT_EQ("example", label1);
+
+    std::string label2(buf.begin() + 18, buf.begin() + 21);
+    EXPECT_EQ("com", label2);
+
+    // The tested function is supposed to append data to a buffer
+    // so let's check that it is a case by appending another domain.
+    OptionDataTypeUtil::writeFqdn("hello.net", buf);
+
+    // The buffer length should be now longer.
+    ASSERT_EQ(33, buf.size());
+
+    // Check the length fields for new labels being appended.
+    EXPECT_EQ(5, buf[22]);
+    EXPECT_EQ(3, buf[28]);
+
+    // And check that labels are ok.
+    std::string label3(buf.begin() + 23, buf.begin() + 28);
+    EXPECT_EQ("hello", label3);
+
+    std::string label4(buf.begin() + 29, buf.begin() + 32);
+    EXPECT_EQ("net", label4);
+
+    // Check that invalid (empty) FQDN is rejected and expected
+    // exception type is thrown.
+    buf.clear();
+    EXPECT_THROW(
+        OptionDataTypeUtil::writeFqdn("", buf),
+        isc::dhcp::BadDataTypeCast
+    );
+
+    // Check another invalid domain name (with repeated dot).
+    buf.clear();
+    EXPECT_THROW(
+        OptionDataTypeUtil::writeFqdn("example..com", buf),
+        isc::dhcp::BadDataTypeCast
+    );
+}
+
+} // anonymous namespace