Commit 1bbe1315 authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

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

parent 8a21830a
......@@ -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<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);
......@@ -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) {
......
......@@ -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<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.
......
......@@ -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) {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment