Browse Source

[2491] Added first set of functions to set data field values.

Marcin Siodelski 12 years ago
parent
commit
1bbe13152c
3 changed files with 359 additions and 49 deletions
  1. 131 14
      src/lib/dhcp/option_custom.cc
  2. 89 35
      src/lib/dhcp/option_custom.h
  3. 139 0
      src/lib/dhcp/tests/option_custom_unittest.cc

+ 131 - 14
src/lib/dhcp/option_custom.cc

@@ -21,11 +21,18 @@ namespace isc {
 namespace dhcp {
 
 OptionCustom::OptionCustom(const OptionDefinition& def,
+                           Universe u)
+    : Option(u, def.getCode(), OptionBuffer()),
+      definition_(def) {
+    createBuffers();
+}
+
+OptionCustom::OptionCustom(const OptionDefinition& def,
                              Universe u,
                              const OptionBuffer& data)
     : Option(u, def.getCode(), data.begin(), data.end()),
       definition_(def) {
-    createBuffers();
+    createBuffers(data_);
 }
 
 OptionCustom::OptionCustom(const OptionDefinition& def,
@@ -34,7 +41,7 @@ OptionCustom::OptionCustom(const OptionDefinition& def,
                              OptionBufferConstIter last)
     : Option(u, def.getCode(), first, last),
       definition_(def) {
-    createBuffers();
+    createBuffers(data_);
 }
 
 void
@@ -45,14 +52,83 @@ OptionCustom::checkIndex(const uint32_t index) const {
     }
 }
 
+template<typename T>
+void
+OptionCustom::checkDataType(const uint32_t index) const {
+    // Check that the requested return type is a supported integer.
+    if (!OptionDataTypeTraits<T>::integer_type) {
+        isc_throw(isc::dhcp::InvalidDataType, "specified data type"
+                  " is not a supported integer type.");
+    }
+
+    // Get the option definition type.
+    OptionDataType data_type = definition_.getType();
+    if (data_type == OPT_RECORD_TYPE) {
+        const OptionDefinition::RecordFieldsCollection& record_fields =
+            definition_.getRecordFields();
+        // When we initialized buffers we have already checked that
+        // the number of these buffers is equal to number of option
+        // fields in the record so the condition below should be met.
+        assert(index < record_fields.size());
+        // Get the data type to be returned.
+        data_type = record_fields[index];
+    }
+
+    if (OptionDataTypeTraits<T>::type != data_type) {
+        isc_throw(isc::dhcp::InvalidDataType,
+                  "specified data type " << data_type << " does not"
+                  "match data type in an option definition for field"
+                  " index " << index);
+    }
+}
+
 void
 OptionCustom::createBuffers() {
+    definition_.validate();
+
+    std::vector<OptionBuffer> buffers;
+
+    OptionDataType data_type = definition_.getType();
+    if (data_type == OPT_RECORD_TYPE) {
+        const OptionDefinition::RecordFieldsCollection fields =
+            definition_.getRecordFields();
+
+        for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
+             field != fields.end(); ++field) {
+            OptionBuffer buf;
+
+            size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field);
+
+            if (data_size == 0 &&
+                *field == OPT_FQDN_TYPE) {
+                    OptionDataTypeUtil::writeFqdn(".", buf);
+            } else {
+                buf.resize(data_size);
+            }
+            buffers.push_back(buf);
+        }
+    } else if (!definition_.getArrayType() &&
+               data_type != OPT_EMPTY_TYPE) {
+        OptionBuffer buf;
+        size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
+        if (data_size == 0 &&
+            data_type == OPT_FQDN_TYPE) {
+            OptionDataTypeUtil::writeFqdn(".", buf);
+        }
+        buf.resize(data_size);
+        buffers.push_back(buf);
+    }
+    std::swap(buffers, buffers_);
+}
+
+void
+OptionCustom::createBuffers(const OptionBuffer& data_buf) {
     // Check that the option definition is correct as we are going
     // to use it to split the data_ buffer into set of sub buffers.
     definition_.validate();
 
     std::vector<OptionBuffer> buffers;
-    OptionBuffer::iterator data = data_.begin();
+    OptionBuffer::const_iterator data = data_buf.begin();
 
     OptionDataType data_type = definition_.getType();
     if (data_type == OPT_RECORD_TYPE) {
@@ -78,7 +154,7 @@ OptionCustom::createBuffers() {
                 // to obtain the length of the data is to read the FQDN. The
                 // utility function will return the size of the buffer on success.
                 if (*field == OPT_FQDN_TYPE) {
-                    OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_.end()),
+                    OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()),
                                                  data_size);
                 } else {
                     // In other case we are dealing with string or binary value
@@ -87,7 +163,7 @@ OptionCustom::createBuffers() {
                     // size data can be laid at the end of the option only and
                     // that the validate() function in OptionDefinition object
                     // should have checked wheter it is a case for this option.
-                    data_size = std::distance(data, data_.end());
+                    data_size = std::distance(data, data_buf.end());
                 }
                 if (data_size == 0) {
                     // If we reached the end of buffer we assume that this option is
@@ -100,7 +176,7 @@ OptionCustom::createBuffers() {
             } else {
                 // Our data field requires that there is a certain chunk of
                 // data left in the buffer. If not, option is truncated.
-                if (std::distance(data, data_.end()) < data_size) {
+                if (std::distance(data, data_buf.end()) < data_size) {
                     isc_throw(OutOfRange, "option buffer truncated");
                 }
             }
@@ -121,19 +197,19 @@ OptionCustom::createBuffers() {
         // Note that data_size returned by getDataTypeLen may be zero
         // if variable length data is being held by the option but
         // this will not cause this check to throw exception.
-        if (std::distance(data, data_.end()) < data_size) {
+        if (std::distance(data, data_buf.end()) < data_size) {
             isc_throw(OutOfRange, "option buffer truncated");
         }
         // For an array of values we are taking different path because
         // we have to handle multiple buffers.
         if (definition_.getArrayType()) {
-            while (data != data_.end()) {
+            while (data != data_buf.end()) {
                 // FQDN is a special case because it is of a variable length.
                 // The actual length for a particular FQDN is encoded within
                 // a buffer so we have to actually read the FQDN from a buffer
                 // to get it.
                 if (data_type == OPT_FQDN_TYPE) {
-                    OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_.end()),
+                    OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()),
                                                  data_size);
                 }
                 // We don't perform other checks for data types that can't be
@@ -147,7 +223,7 @@ OptionCustom::createBuffers() {
                 // data_size. Note that it is ok to truncate the data if and only
                 // if the data buffer is long enough to keep at least one value.
                 // This has been checked above already.
-                if (std::distance(data, data_.end()) < data_size) {
+                if (std::distance(data, data_buf.end()) < data_size) {
                     break;
                 }
                 buffers.push_back(OptionBuffer(data, data + data_size));
@@ -160,10 +236,10 @@ OptionCustom::createBuffers() {
             if (data_size == 0) {
                 // For FQDN we get the size by actually reading the FQDN.
                 if (data_type == OPT_FQDN_TYPE) {
-                    OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_.end()),
+                    OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()),
                                                  data_size);
                 } else {
-                    data_size = std::distance(data, data_.end());
+                    data_size = std::distance(data, data_buf.end());
                 }
             }
             if (data_size > 0) {
@@ -286,6 +362,28 @@ OptionCustom::readAddress(const uint32_t index) const {
     }
 }
 
+void
+OptionCustom::writeAddress(const asiolink::IOAddress& address,
+                           const uint32_t index) {
+    using namespace isc::asiolink;
+
+    checkIndex(index);
+
+    if ((address.getFamily() == AF_INET &&
+         buffers_[index].size() != V4ADDRESS_LEN) ||
+        (address.getFamily() == AF_INET6 &&
+         buffers_[index].size() != V6ADDRESS_LEN)) {
+        isc_throw(BadDataTypeCast, "invalid address specified "
+                  << address.toText() << ". Expected a valid IPv"
+                  << (buffers_[index].size() == V4ADDRESS_LEN ? "4" : "6")
+                  << " address.");
+    }
+
+    OptionBuffer buf;
+    OptionDataTypeUtil::writeAddress(address, buf);
+    std::swap(buf, buffers_[index]);
+}
+
 const OptionBuffer&
 OptionCustom::readBinary(const uint32_t index) const {
     checkIndex(index);
@@ -298,6 +396,14 @@ OptionCustom::readBoolean(const uint32_t index) const {
     return (OptionDataTypeUtil::readBool(buffers_[index]));
 }
 
+void
+OptionCustom::writeBoolean(const bool value, const uint32_t index) {
+    checkIndex(index);
+
+    buffers_[index].clear();
+    OptionDataTypeUtil::writeBool(value, buffers_[index]);
+}
+
 std::string
 OptionCustom::readFqdn(const uint32_t index) const {
     checkIndex(index);
@@ -316,11 +422,22 @@ OptionCustom::readString(const uint32_t index) const {
 }
 
 void
+OptionCustom::writeString(const std::string& text, const uint32_t index) {
+    checkIndex(index);
+
+    if (!text.empty()) {
+        buffers_[index].clear();
+        OptionDataTypeUtil::writeString(text, buffers_[index]);
+    }
+}
+
+
+void
 OptionCustom::unpack(OptionBufferConstIter begin,
                      OptionBufferConstIter end) {
     data_ = OptionBuffer(begin, end);
     // Chop the buffer stored in data_ into set of sub buffers.
-    createBuffers();
+    createBuffers(data_);
 }
 
 uint16_t
@@ -352,7 +469,7 @@ void OptionCustom::setData(const OptionBufferConstIter first,
 
     // Chop the data_ buffer into set of buffers that represent
     // option fields data.
-    createBuffers();
+    createBuffers(data_);
 }
 
 std::string OptionCustom::toText(int indent) {

+ 89 - 35
src/lib/dhcp/option_custom.h

@@ -40,6 +40,17 @@ public:
 
     /// @brief Constructor, used for options to be sent.
     ///
+    /// This constructor creates an instance of an option with default
+    /// data set for all data fields. The option buffers are allocated
+    /// according to data size being stored in particular data fields.
+    /// For variable size data empty buffers are created.
+    ///
+    /// @param def option definition.
+    /// @param u specifies universe (V4 or V6)
+    OptionCustom(const OptionDefinition& def, Universe u);
+
+    /// @brief Constructor, used for options to be sent.
+    ///
     /// This constructor creates an instance of an option from the whole
     /// supplied buffer. This constructor is mainly used to create an
     /// instances of options to be stored in outgoing DHCP packets.
@@ -87,7 +98,17 @@ public:
     ///
     /// @return IP address read from a buffer.
     /// @throw isc::OutOfRange if index is out of range.
-    asiolink::IOAddress readAddress(const uint32_t index) const;
+    asiolink::IOAddress readAddress(const uint32_t index = 0) const;
+
+    /// @brief Write an IP address into a buffer.
+    ///
+    /// @param address IP address being written.
+    /// @param index buffer index.
+    ///
+    /// @throw isc::OutOfRange if index is out of range.
+    /// @throw isc::dhcp::BadDataTypeCast if IP address is invalid.
+    void writeAddress(const asiolink::IOAddress& address,
+                      const uint32_t index = 0);
 
     /// @brief Read a buffer as binary data.
     ///
@@ -95,7 +116,7 @@ public:
     ///
     /// @throw isc::OutOfRange if index is out of range.
     /// @return read buffer holding binary data.
-    const OptionBuffer& readBinary(const uint32_t index) const;
+    const OptionBuffer& readBinary(const uint32_t index = 0) const;
 
     /// @brief Read a buffer as boolean value.
     ///
@@ -103,7 +124,15 @@ public:
     ///
     /// @throw isc::OutOfRange if index is out of range.
     /// @return read boolean value.
-    bool readBoolean(const uint32_t index) const;
+    bool readBoolean(const uint32_t index = 0) const;
+
+    /// @brief Write a boolean value into a buffer.
+    ///
+    /// @param value boolean value to be written.
+    /// @param index buffer index.
+    ///
+    /// @throw isc::OutOfRange if index is out of range.
+    void writeBoolean(const bool value, const uint32_t index = 0);
 
     /// @brief Read a buffer as FQDN.
     ///
@@ -114,7 +143,7 @@ public:
     /// @throw isc::dhcp::BadDataTypeCast if a buffer being read
     /// does not hold a valid FQDN.
     /// @return string representation if FQDN.
-    std::string readFqdn(const uint32_t index) const;
+    std::string readFqdn(const uint32_t index = 0) const;
 
     /// @brief Read a buffer as integer value.
     ///
@@ -122,38 +151,15 @@ public:
     /// @tparam integer type of a value being returned.
     ///
     /// @throw isc::OutOfRange if index is out of range.
+    /// @throw isc::dhcp::InvalidDataType if T is invalid.
     /// @return read integer value.
     template<typename T>
-    T readInteger(const uint32_t index) const {
+    T readInteger(const uint32_t index = 0) const {
+        // Check thet tha index is not out of range.
         checkIndex(index);
-
-        // Check that the requested return type is a supported integer.
-        if (!OptionDataTypeTraits<T>::integer_type) {
-            isc_throw(isc::dhcp::InvalidDataType, "specified data type to be returned"
-                      " by readInteger is not supported integer type");
-        }
-
-        // Get the option definition type.
-        OptionDataType data_type = definition_.getType();
-        if (data_type == OPT_RECORD_TYPE) {
-            const OptionDefinition::RecordFieldsCollection& record_fields =
-                definition_.getRecordFields();
-            // When we initialized buffers we have already checked that
-            // the number of these buffers is equal to number of option
-            // fields in the record so the condition below should be met.
-            assert(index < record_fields.size());
-            // Get the data type to be returned.
-            data_type = record_fields[index];
-        }
-
-        // Requested data type must match the data type in a record.
-        if (OptionDataTypeTraits<T>::type != data_type) {
-            isc_throw(isc::dhcp::InvalidDataType,
-                      "unable to read option field with index " << index
-                      << " as integer value. The field's data type"
-                      << data_type << " does not match the integer type"
-                      << "returned by the readInteger function.");
-        }
+        // Check that T points to a valid integer type and this type
+        // is consistent with an option definition.
+        checkDataType<T>(index);
         // When we created the buffer we have checked that it has a
         // valid size so this condition here should be always fulfiled.
         assert(buffers_[index].size() == OptionDataTypeTraits<T>::len);
@@ -161,13 +167,43 @@ public:
         return (OptionDataTypeUtil::readInt<T>(buffers_[index]));
     }
 
+    /// @brief Write an integer value into a buffer.
+    ///
+    /// @param value integer value to be written.
+    /// @param index buffer index.
+    /// @tparam T integer type of a value being written.
+    ///
+    /// @throw isc::OutOfRange if index is out of range.
+    /// @throw isc::dhcp::InvalidDataType if T is invalid.
+    template<typename T>
+    void writeInteger(const T value, const uint32_t index = 0) {
+        // Check that the index is not out of range.
+        checkIndex(index);
+        // Check that T points to a valid integer type and this type
+        // is consistent with an option definition.
+        checkDataType<T>(index);
+        // Get some temporary buffer.
+        OptionBuffer buf;
+        // Try to write to the buffer.
+        OptionDataTypeUtil::writeInt<T>(value, buf);
+        // If successful, replace the old buffer with new one.
+        std::swap(buffers_[index], buf);
+    }
+
     /// @brief Read a buffer as string value.
     ///
     /// @param index buffer index.
     ///
     /// @return string value read from buffer.
     /// @throw isc::OutOfRange if index is out of range.
-    std::string readString(const uint32_t index) const;
+    std::string readString(const uint32_t index = 0) const;
+
+    /// @brief Write a string value into a buffer.
+    ///
+    /// @param text the string value to be written.
+    /// @param buffer index.
+    void writeString(const std::string& text,
+                     const uint32_t index = 0);
 
     /// @brief Parses received buffer.
     ///
@@ -219,9 +255,27 @@ private:
     /// @throw isc::OutOfRange if index is out of range.
     void checkIndex(const uint32_t index) const;
 
-    /// @brief Create collection of buffers representing data field values.
+    /// @brief Verify that the integer type is consistent with option
+    /// field type.
+    ///
+    /// This convenience function checks that the data type specified as T
+    /// is consistent with a type of a data field identified by index.
+    ///
+    /// @param index data field index.
+    /// @tparam data type to be validated.
+    ///
+    /// @throw isc::dhcp::InvalidDataType if the type is invalid.
+    template<typename T>
+    void checkDataType(const uint32_t index) const;
+
+    /// @brief Create a collection of non initialized buffers.
     void createBuffers();
 
+    /// @brief Create collection of buffers representing data field values.
+    ///
+    /// @param data_buf a buffer to be parsed.
+    void createBuffers(const OptionBuffer& data_buf);
+
     /// @brief Return a text representation of a data field.
     ///
     /// @param data_type data type of a field.

+ 139 - 0
src/lib/dhcp/tests/option_custom_unittest.cc

@@ -106,6 +106,17 @@ TEST_F(OptionCustomTest, constructor) {
 
     EXPECT_EQ(Option::V4, option->getUniverse());
     EXPECT_EQ(232, option->getType());
+
+    // Try to create an option using 'empty data' constructor
+    OptionDefinition opt_def3("OPTION_FOO", 1000, "uint32");
+
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def3, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    EXPECT_EQ(Option::V6, option->getUniverse());
+    EXPECT_EQ(1000, option->getType());
 }
 
 // The purpose of this test is to verify that 'empty' option definition can
@@ -777,6 +788,134 @@ TEST_F(OptionCustomTest, recordDataTruncated) {
     );
 }
 
+// The purpose of this test is to verify that an option comprising
+// single boolean data field can be created and that its default
+// value can be overriden by a new value.
+TEST_F(OptionCustomTest, setBooleanData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "boolean");
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+    // Check that the default boolean value is false.
+    bool value = false;
+    ASSERT_NO_THROW(value = option->readBoolean());
+    EXPECT_FALSE(value);
+    // Check that we can override the default value.
+    ASSERT_NO_THROW(option->writeBoolean(true));
+    // Finally, check that it has been actually overriden.
+    ASSERT_NO_THROW(value = option->readBoolean());
+    EXPECT_TRUE(value);
+}
+
+/// The purpose of this test is to verify that the data field value
+/// can be overriden by a new value.
+TEST_F(OptionCustomTest, setUint32Data) {
+    // Create a definition of an option that holds single
+    // uint32 value.
+    OptionDefinition opt_def("OPTION_FOO", 1000, "uint32");
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    // The default value for integer data fields is 0.
+    uint32_t value = 0;
+    ASSERT_NO_THROW(option->readInteger<uint32_t>());
+    EXPECT_EQ(0, value);
+
+    // Try to set the data field value to something different
+    // than 0.
+    ASSERT_NO_THROW(option->writeInteger<uint32_t>(1234));
+
+    // Verify that it has been set.
+    ASSERT_NO_THROW(value = option->readInteger<uint32_t>());
+    EXPECT_EQ(1234, value);
+}
+
+// The purpose of this test is to verify that an opton comprising
+// single IPv4 address can be created and that this address can
+// be overriden by a new value.
+TEST_F(OptionCustomTest, setIpv4AddressData) {
+    OptionDefinition opt_def("OPTION_FOO", 232, "ipv4-address");
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4));
+    );
+    ASSERT_TRUE(option);
+
+    asiolink::IOAddress address("127.0.0.1");
+    ASSERT_NO_THROW(address = option->readAddress());
+    EXPECT_EQ("0.0.0.0", address.toText());
+
+    EXPECT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1")));
+
+    EXPECT_NO_THROW(address = option->readAddress());
+    EXPECT_EQ("192.168.0.1", address.toText());
+}
+
+// The purpose of this test is to verify that an opton comprising
+// single IPv6 address can be created and that this address can
+// be overriden by a new value.
+TEST_F(OptionCustomTest, setIpv6AddressData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address");
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    asiolink::IOAddress address("::1");
+    ASSERT_NO_THROW(address = option->readAddress());
+    EXPECT_EQ("::", address.toText());
+
+    EXPECT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::1")));
+
+    EXPECT_NO_THROW(address = option->readAddress());
+    EXPECT_EQ("2001:db8:1::1", address.toText());
+}
+
+// The purpose of this test is to verify that an option comprising
+// single string value can be created and that this value
+// is initialized to the default value. Also, this test checks that
+// this value can be overwritten by a new value.
+TEST_F(OptionCustomTest, setStringData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "string");
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    // Get the default value of the option.
+    std::string value;
+    ASSERT_NO_THROW(value = option->readString());
+    // By default the string data field is empty.
+    EXPECT_TRUE(value.empty());
+    // Write some text to this field.
+    EXPECT_NO_THROW(option->writeString("hello world"));
+    // Check that it has been actually written.
+    EXPECT_NO_THROW(value = option->readString());
+    EXPECT_EQ("hello world", value);
+}
+
 // The purpose of this test is to verify that pack function for
 // DHCPv4 custom option works correctly.
 TEST_F(OptionCustomTest, pack4) {