From: Marcin Siodelski Date: Fri, 30 Nov 2012 16:32:12 +0000 (+0100) Subject: [2491] Added first set of functions to set data field values. X-Git-Tag: bind10-1.0.0-beta-release~19^2~15^2~23 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1bbe13152cea9e759bb0423750f244be58eff578;p=thirdparty%2Fkea.git [2491] Added first set of functions to set data field values. --- diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc index 5c6bf6e77d..ba845190f4 100644 --- a/src/lib/dhcp/option_custom.cc +++ b/src/lib/dhcp/option_custom.cc @@ -20,12 +20,19 @@ 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 +void +OptionCustom::checkDataType(const uint32_t index) const { + // Check that the requested return type is a supported integer. + if (!OptionDataTypeTraits::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::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 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 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); @@ -315,12 +421,23 @@ OptionCustom::readString(const uint32_t index) const { return (OptionDataTypeUtil::readString(buffers_[index])); } +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) { diff --git a/src/lib/dhcp/option_custom.h b/src/lib/dhcp/option_custom.h index 1962fe5c8d..64d98869af 100644 --- a/src/lib/dhcp/option_custom.h +++ b/src/lib/dhcp/option_custom.h @@ -38,6 +38,17 @@ namespace dhcp { class OptionCustom : public Option { 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 @@ -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 - 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::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::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(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::len); @@ -161,13 +167,43 @@ public: return (OptionDataTypeUtil::readInt(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 + 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(index); + // Get some temporary buffer. + OptionBuffer buf; + // Try to write to the buffer. + OptionDataTypeUtil::writeInt(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 + 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. diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc index 9395ea5641..da5903f35c 100644 --- a/src/lib/dhcp/tests/option_custom_unittest.cc +++ b/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 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 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()); + EXPECT_EQ(0, value); + + // Try to set the data field value to something different + // than 0. + ASSERT_NO_THROW(option->writeInteger(1234)); + + // Verify that it has been set. + ASSERT_NO_THROW(value = option->readInteger()); + 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 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 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 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) {