Commit 84e441c7 authored by Marcin Siodelski's avatar Marcin Siodelski

[2491] Support for custom option to hold FQDN.

parent 22a33dfd
......@@ -68,17 +68,27 @@ OptionCustom::createBuffers() {
// For fixed-size data type such as boolean, integer, even
// IP address we can use the utility function to get the required
// buffer size.
int data_size = OptionDataTypeUtil::getDataTypeLen(*field);
// For variable size types (such as string) the function above
// will return 0 so we need to do a runtime check. Since variable
// length data fields may be laid only at the end of an option we
// consume the rest of this option. Note that validate() function
// in OptionDefinition object should have checked whether the
// data fields layout is correct (that the variable string fields
// are laid at the end).
size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field);
// For variable size types (e.g. string) the function above will
// return 0 so we need to do a runtime check of the length.
if (data_size == 0) {
data_size = std::distance(data, data_.end());
// FQDN is a special data type as it stores variable length data
// but the data length is encoded in the buffer. The easiest way
// 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()),
data_size);
} else {
// In other case we are dealing with string or binary value
// which size can't be determined. Thus we consume the
// remaining part of the buffer for it. Note that variable
// 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());
}
if (data_size == 0) {
// If we reached the end of buffer we assume that this option is
// truncated because there is no remaining data to initialize
......@@ -105,7 +115,7 @@ OptionCustom::createBuffers() {
// data fields of the same type. The type of those fields
// is held in the data_type variable so let's use it to determine
// a size of buffers.
int data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
// The check below will fail if the input buffer is too short
// for the data size being held by this option.
// Note that data_size returned by getDataTypeLen may be zero
......@@ -117,27 +127,44 @@ OptionCustom::createBuffers() {
// For an array of values we are taking different path because
// we have to handle multiple buffers.
if (definition_.getArrayType()) {
// We don't perform other checks for data types that can't be
// used together with array indicator such as strings, empty field
// etc. This is because OptionDefinition::validate function should
// have checked this already. Thus data_size must be greater than
// zero.
assert(data_size > 0);
// Get equal chunks of data and store as collection of buffers.
// Truncate any remaining part which length is not divisible by
// 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.
do {
while (data != data_.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()),
data_size);
}
// We don't perform other checks for data types that can't be
// used together with array indicator such as strings, empty field
// etc. This is because OptionDefinition::validate function should
// have checked this already. Thus data_size must be greater than
// zero.
assert(data_size > 0);
// Get chunks of data and store as a collection of buffers.
// Truncate any remaining part which length is not divisible by
// 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) {
break;
}
buffers.push_back(OptionBuffer(data, data + data_size));
data += data_size;
} while (std::distance(data, data_.end()) >= data_size);
}
} else {
// For non-arrays the data_size can be zero because
// getDataTypeLen returns zero for variable size data types
// such as strings. Simply take whole buffer.
if (data_size == 0) {
data_size = std::distance(data, data_.end());
// For FQDN we get the size by actually reading the FQDN.
if (data_type == OPT_FQDN_TYPE) {
OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_.end()),
data_size);
} else {
data_size = std::distance(data, data_.end());
}
}
if (data_size > 0) {
buffers.push_back(OptionBuffer(data, data + data_size));
......@@ -185,6 +212,9 @@ OptionCustom::dataFieldToText(const OptionDataType data_type,
case OPT_IPV6_ADDRESS_TYPE:
text << readAddress(index).toText();
break;
case OPT_FQDN_TYPE:
text << readFqdn(index);
break;
case OPT_STRING_TYPE:
text << readString(index);
break;
......@@ -268,6 +298,17 @@ OptionCustom::readBoolean(const uint32_t index) const {
return (OptionDataTypeUtil::readBool(buffers_[index]));
}
std::string
OptionCustom::readFqdn(const uint32_t index) const {
checkIndex(index);
try {
size_t len = 0;
return (OptionDataTypeUtil::readFqdn(buffers_[index], len));
} catch (const Exception& ex) {
isc_throw(BadDataTypeCast, ex.what());
}
}
std::string
OptionCustom::readString(const uint32_t index) const {
checkIndex(index);
......
......@@ -105,6 +105,17 @@ public:
/// @return read boolean value.
bool readBoolean(const uint32_t index) const;
/// @brief Read a buffer as FQDN.
///
/// @param index buffer index.
/// @param [out] len number of bytes read from a buffer.
///
/// @throw isc::OutOfRange if buffer index is out of range.
/// @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;
/// @brief Read a buffer as integer value.
///
/// @param index buffer index.
......
......@@ -209,7 +209,10 @@ OptionDataTypeUtil::writeBool(const bool value,
}
std::string
OptionDataTypeUtil::readFqdn(const std::vector<uint8_t>& buf) {
OptionDataTypeUtil::readFqdn(const std::vector<uint8_t>& buf,
size_t& len) {
len = 0;
// If buffer is empty emit an error.
if (buf.empty()) {
isc_throw(BadDataTypeCast, "unable to read FQDN from a buffer."
......@@ -225,6 +228,7 @@ OptionDataTypeUtil::readFqdn(const std::vector<uint8_t>& buf) {
// it means that the buffer doesn't hold a valid domain name (invalid
// syntax).
isc::dns::Name name(in_buf);
len = name.getLength();
return (name.toText());
} catch (const isc::Exception& ex) {
// Unable to convert the data in the buffer into FQDN.
......
......@@ -338,11 +338,13 @@ public:
/// section 3.1.
///
/// @param buf input buffer holding a FQDN.
/// @param [out] len number of bytes read from a buffer.
///
/// @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);
static std::string readFqdn(const std::vector<uint8_t>& buf,
size_t& len);
/// @brief Append FQDN into a buffer.
///
......
......@@ -203,6 +203,39 @@ TEST_F(OptionCustomTest, booleanData) {
);
}
// The purpose of this test is to verify that the data from a buffer
// can be read as FQDN.
TEST_F(OptionCustomTest, fqdnData) {
OptionDefinition opt_def("OPTION_FOO", 1000, "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,
};
std::vector<uint8_t> buf(data, data + sizeof(data));
boost::scoped_ptr<OptionCustom> option;
ASSERT_NO_THROW(
option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
);
ASSERT_TRUE(option);
ASSERT_EQ(1, option->getDataFieldsNum());
std::string domain0 = option->readFqdn(0);
EXPECT_EQ("mydomain.example.com.", domain0);
// Check that the option with truncated data can't be created.
EXPECT_THROW(
option.reset(new OptionCustom(opt_def, Option::V6,
buf.begin(), buf.begin() + 4)),
isc::dhcp::BadDataTypeCast
);
}
// The purpose of this test is to verify that the option definition comprising
// 16-bit signed integer value can be used to create an instance of custom option.
TEST_F(OptionCustomTest, int16Data) {
......@@ -338,6 +371,7 @@ TEST_F(OptionCustomTest, ipv6AddressData) {
);
}
// The purpose of this test is to verify that the option definition comprising
// string value can be used to create an instance of custom option.
TEST_F(OptionCustomTest, stringData) {
......@@ -575,6 +609,45 @@ TEST_F(OptionCustomTest, ipv6AddressDataArray) {
);
}
// The purpose of this test is to verify that the option comprising
// an array of FQDN values can be created from a buffer which holds
// multiple FQDN values encoded as described in the RFC1035, section
// 3.1
TEST_F(OptionCustomTest, fqdnDataArray) {
OptionDefinition opt_def("OPTION_FOO", 1000, "fqdn", true);
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,
7, 101, 120, 97, 109, 112, 108, 101, // "example"
3, 99, 111, 109, // "com"
0
};
// Create a buffer that holds two FQDNs.
std::vector<uint8_t> buf(data, data + sizeof(data));
// Create an option from using a buffer.
boost::scoped_ptr<OptionCustom> option;
ASSERT_NO_THROW(
option.reset(new OptionCustom(opt_def, Option::V6, buf));
);
ASSERT_TRUE(option);
// We expect that two FQDN values have been extracted
// from a buffer.
ASSERT_EQ(2, option->getDataFieldsNum());
// Validate both values.
std::string domain0 = option->readFqdn(0);
EXPECT_EQ("mydomain.example.com.", domain0);
std::string domain1 = option->readFqdn(1);
EXPECT_EQ("example.com.", domain1);
}
// The purpose of this test is to verify that the option definition comprising
// a record of various data fields can be used to create an instance of
// custom option.
......@@ -584,30 +657,46 @@ TEST_F(OptionCustomTest, recordData) {
OptionDefinition opt_def("OPTION_FOO", 1000, "record");
ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
ASSERT_NO_THROW(opt_def.addRecordField("fqdn"));
ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
ASSERT_NO_THROW(opt_def.addRecordField("string"));
const char fqdn_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,
};
OptionBuffer buf;
// Initialize field 0.
writeInt<uint16_t>(8712, buf);
// Initialize field 1 to 'true'
buf.push_back(static_cast<unsigned short>(1));
// Initialize field 2 to IPv4 address.
// Initialize field 2.
buf.insert(buf.end(), fqdn_data, fqdn_data + sizeof(fqdn_data));
// Initialize field 3 to IPv4 address.
writeAddress(IOAddress("192.168.0.1"), buf);
// Initialize field 3 to IPv6 address.
// Initialize field 4 to IPv6 address.
writeAddress(IOAddress("2001:db8:1::1"), buf);
// Initialize field 4 to string value.
// Initialize field 5 to string value.
writeString("ABCD", buf);
boost::scoped_ptr<OptionCustom> option;
try {
option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
} catch (const Exception& ex) {
std::cout << ex.what() << std::endl;
}
ASSERT_NO_THROW(
option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
);
ASSERT_TRUE(option);
// We should have 5 data fields.
ASSERT_EQ(5, option->getDataFieldsNum());
ASSERT_EQ(6, option->getDataFieldsNum());
// Verify value in the field 0.
uint16_t value0 = 0;
......@@ -620,19 +709,24 @@ TEST_F(OptionCustomTest, recordData) {
EXPECT_TRUE(value1);
// Verify value in the field 2.
IOAddress value2("127.0.0.1");
ASSERT_NO_THROW(value2 = option->readAddress(2));
EXPECT_EQ("192.168.0.1", value2.toText());
std::string value2 = "";
ASSERT_NO_THROW(value2 = option->readFqdn(2));
EXPECT_EQ("mydomain.example.com.", value2);
// Verify value in the field 3.
IOAddress value3("::1");
IOAddress value3("127.0.0.1");
ASSERT_NO_THROW(value3 = option->readAddress(3));
EXPECT_EQ("2001:db8:1::1", value3.toText());
EXPECT_EQ("192.168.0.1", value3.toText());
// Verify value in the field 4.
std::string value4;
ASSERT_NO_THROW(value4 = option->readString(4));
EXPECT_EQ("ABCD", value4);
IOAddress value4("::1");
ASSERT_NO_THROW(value4 = option->readAddress(4));
EXPECT_EQ("2001:db8:1::1", value4.toText());
// Verify value in the field 4.
std::string value5;
ASSERT_NO_THROW(value5 = option->readString(5));
EXPECT_EQ("ABCD", value5);
}
// The purpose of this test is to verify that truncated buffer
......@@ -901,6 +995,4 @@ TEST_F(OptionCustomTest, invalidIndex) {
EXPECT_THROW(option->readInteger<uint32_t>(11), isc::OutOfRange);
}
} // anonymous namespace
......@@ -52,8 +52,10 @@ TEST_F(OptionDataTypesTest, readFqdn) {
// Read the buffer as FQDN and verify its correctness.
std::string fqdn;
EXPECT_NO_THROW(fqdn = OptionDataTypeUtil::readFqdn(buf));
size_t len = 0;
EXPECT_NO_THROW(fqdn = OptionDataTypeUtil::readFqdn(buf, len));
EXPECT_EQ("mydomain.example.com.", fqdn);
EXPECT_EQ(len, buf.size());
// By resizing the buffer we simulate truncation. The first
// length field (8) indicate that the first label's size is
......@@ -61,14 +63,14 @@ TEST_F(OptionDataTypesTest, readFqdn) {
// fails.
buf.resize(5);
EXPECT_THROW(
OptionDataTypeUtil::readFqdn(buf),
OptionDataTypeUtil::readFqdn(buf, len),
isc::dhcp::BadDataTypeCast
);
// Another special case: provide an empty buffer.
buf.clear();
EXPECT_THROW(
OptionDataTypeUtil::readFqdn(buf),
OptionDataTypeUtil::readFqdn(buf, len),
isc::dhcp::BadDataTypeCast
);
}
......
Markdown is supported
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