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,
OptionBufferConstIter last)
: Option(u, def.getCode(), first, last),
definition_(def) {
- createBuffers();
+ createBuffers(data_);
}
void
}
}
+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) {
// 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
// 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
} 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");
}
}
// 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
// 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));
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) {
}
}
+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);
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);
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
// Chop the data_ buffer into set of buffers that represent
// option fields data.
- createBuffers();
+ createBuffers(data_);
}
std::string OptionCustom::toText(int indent) {
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
///
/// @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.
///
///
/// @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.
///
///
/// @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.
///
/// @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.
///
/// @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);
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.
///
/// @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.
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
);
}
+// 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) {