clients to obtain the addresses of multiple NTP servers.
</para>
<!-- @todo: describe record types -->
+ <!-- @todo: describe array in record types -->
<para>
The <xref linkend="dhcp4-custom-options"/> describes the configuration
<row><entry>ipv6-address</entry><entry>IPv6 address in the usual colon notation (e.g. 2001:db8::1)</entry></row>
<row><entry>ipv6-prefix</entry><entry>IPv6 prefix and prefix length specified using CIDR notation, e.g. 2001:db8:1::/64. This data type is used to represent an 8-bit field conveying a prefix length and the variable length prefix value</entry></row>
<row><entry>psid</entry><entry>PSID and PSID length separated by a slash, e.g. 3/4 specifies PSID=3 and PSID length=4. In the wire format it is represented by an 8-bit field carrying PSID length (in this case equal to 4) and the 16-bits long PSID value field (in this case equal to "0011000000000000b" using binary notation). Allowed values for a PSID length are 0 to 16. See <ulink url="http://tools.ietf.org/html/rfc7597">RFC 7597</ulink> for the details about the PSID wire representation</entry></row>
- <row><entry>record</entry><entry>Structured data that may comprise any types (except "record" and "empty")</entry></row>
+ <row><entry>record</entry><entry>Structured data that may comprise any types (except "record" and "empty"). The array flag applies to the last field.</entry></row>
<row><entry>string</entry><entry>Any text</entry></row>
<row><entry>tuple</entry><entry>A length encoded as a 8 (16 for DHCPv6) bit unsigned integer followed by a string of this length</entry></row>
<row><entry>uint8</entry><entry>8 bit unsigned integer with allowed values 0 to 255</entry></row>
the types set in the <command>record-types</command> field of the option
definition.
</para>
+ <para>
+ When the <command>array</command> is set to <command>true</command>
+ with a <command>type</command> is set to "record", the last field
+ is an array, i.e., it can contain more than one value as in:
+<screen>
+"Dhcp4": {
+ "option-def": [
+ {
+ <userinput>"name": "bar",
+ "code": 223,
+ "space": "dhcp4",
+ "type": "record",
+ "array": true,
+ "record-types": "ipv4-address, uint16",
+ "encapsulate": ""</userinput>
+ }, ...
+ ],
+ ...
+}
+</screen>
+ The new option content is one IPv4 address followed by one or more 16
+ bit unsigned integers.
+ </para>
<note>
<para>In the general case, boolean values are specified as <command>true</command> or
<command>false</command>, without quotes. Some specific boolean parameters may
</para>
<!-- @todo: describe record types -->
+<!-- @todo: describe array in record types -->
<para>
The <xref linkend="dhcp6-custom-options"/> describes the configuration
the "record-types" field of the option definition.
</para>
+ <para>
+ When the <command>array</command> is set to <command>true</command>
+ with a <command>type</command> is set to "record", the last field
+ is an array, i.e., it can contain more than one value as in:
+<screen>
+"Dhcp6": {
+ "option-def": [
+ {
+ <userinput>"name": "bar",
+ "code": 101,
+ "space": "dhcp6",
+ "type": "record",
+ "array": true,
+ "record-types": "ipv6-address, uint16",
+ "encapsulate": ""</userinput>
+ }, ...
+ ],
+ ...
+}
+</screen>
+ The new option content is one IPv6 address followed by one or more 16
+ bit unsigned integers.
+ </para>
+
<note>
<para>In the general case, boolean values are specified as <command>true</command> or
<command>false</command>, without quotes. Some specific boolean parameters may
}
}
+void
+OptionCustom::createBuffer(OptionBuffer& buffer,
+ const OptionDataType data_type) const {
+ // For data types that have a fixed size we can use the
+ // utility function to get the buffer's size.
+ size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
+
+ // For variable data sizes the utility function returns zero.
+ // It is ok for string values because the default string
+ // is 'empty'. However for FQDN the empty value is not valid
+ // so we initialize it to '.'. For prefix there is a prefix
+ // length fixed field.
+ if (data_size == 0) {
+ if (data_type == OPT_FQDN_TYPE) {
+ OptionDataTypeUtil::writeFqdn(".", buffer);
+
+ } else if (data_type == OPT_IPV6_PREFIX_TYPE) {
+ OptionDataTypeUtil::writePrefix(PrefixLen(0),
+ IOAddress::IPV6_ZERO_ADDRESS(),
+ buffer);
+ }
+ } else {
+ // At this point we can resize the buffer. Note that
+ // for string values we are setting the empty buffer
+ // here.
+ buffer.resize(data_size);
+ }
+}
+
void
OptionCustom::createBuffers() {
definition_.validate();
for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
field != fields.end(); ++field) {
OptionBuffer buf;
-
- // For data types that have a fixed size we can use the
- // utility function to get the buffer's size.
- size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field);
-
- // For variable data sizes the utility function returns zero.
- // It is ok for string values because the default string
- // is 'empty'. However for FQDN the empty value is not valid
- // so we initialize it to '.'. For prefix there is a prefix
- // length fixed field.
- if (data_size == 0) {
- if (*field == OPT_FQDN_TYPE) {
- OptionDataTypeUtil::writeFqdn(".", buf);
-
- } else if (*field == OPT_IPV6_PREFIX_TYPE) {
- OptionDataTypeUtil::writePrefix(PrefixLen(0),
- IOAddress::IPV6_ZERO_ADDRESS(),
- buf);
- }
- } else {
- // At this point we can resize the buffer. Note that
- // for string values we are setting the empty buffer
- // here.
- buf.resize(data_size);
- }
+ createBuffer(buf, *field);
// We have the buffer with default value prepared so we
// add it to the set of buffers.
buffers.push_back(buf);
// For non-arrays we have a single value being held by the option
// so we have to allocate exactly one buffer.
OptionBuffer buf;
- size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
- if (data_size == 0) {
- if (data_type == OPT_FQDN_TYPE) {
- OptionDataTypeUtil::writeFqdn(".", buf);
-
- } else if (data_type == OPT_IPV6_PREFIX_TYPE) {
- OptionDataTypeUtil::writePrefix(PrefixLen(0),
- IOAddress::IPV6_ZERO_ADDRESS(),
- buf);
- }
- } else {
- // Note that if our option holds a string value then
- // we are making empty buffer here.
- buf.resize(data_size);
- }
+ createBuffer(buf, data_type);
// Add a buffer that we have created and leave.
buffers.push_back(buf);
}
std::swap(buffers, buffers_);
}
+size_t
+OptionCustom::bufferLength(const OptionDataType data_type, bool in_array,
+ OptionBuffer::const_iterator begin,
+ OptionBuffer::const_iterator end) const {
+ // For fixed-size data type such as boolean, integer, even
+ // IP address we can use the utility function to get the required
+ // buffer size.
+ size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
+
+ // 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) {
+ // 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 (data_type == OPT_FQDN_TYPE) {
+ std::string fqdn =
+ OptionDataTypeUtil::readFqdn(OptionBuffer(begin, end));
+ // The size of the buffer holding an FQDN is always
+ // 1 byte larger than the size of the string
+ // representation of this FQDN.
+ data_size = fqdn.size() + 1;
+ } else if (!definition_.getArrayType() &&
+ ((data_type == OPT_BINARY_TYPE) ||
+ (data_type == OPT_STRING_TYPE))) {
+ // 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(begin, end);
+ } else if (data_type == OPT_IPV6_PREFIX_TYPE) {
+ // The size of the IPV6 prefix type is determined as
+ // one byte (which is the size of the prefix in bits)
+ // followed by the prefix bits (right-padded with
+ // zeros to the nearest octet boundary)
+ if ((begin == end) && !in_array)
+ return 0;
+ PrefixTuple prefix =
+ OptionDataTypeUtil::readPrefix(OptionBuffer(begin, end));
+ // Data size comprises 1 byte holding a prefix length and the
+ // prefix length (in bytes) rounded to the nearest byte boundary.
+ data_size = sizeof(uint8_t) + (prefix.first.asUint8() + 7) / 8;
+ } else if (data_type == OPT_TUPLE_TYPE) {
+ OpaqueDataTuple::LengthFieldType lft =
+ getUniverse() == Option::V4 ?
+ OpaqueDataTuple::LENGTH_1_BYTE :
+ OpaqueDataTuple::LENGTH_2_BYTES;
+ std::string value =
+ OptionDataTypeUtil::readTuple(OptionBuffer(begin, end), lft);
+ data_size = value.size();
+ // The size of the buffer holding a tuple is always
+ // 1 or 2 byte larger than the size of the string
+ data_size += getUniverse() == Option::V4 ? 1 : 2;
+ } else {
+ // If we reached the end of buffer we assume that this option is
+ // truncated because there is no remaining data to initialize
+ // an option field.
+ isc_throw(OutOfRange, "option buffer truncated");
+ }
+ }
+
+ return data_size;
+}
+
void
OptionCustom::createBuffers(const OptionBuffer& data_buf) {
// Check that the option definition is correct as we are going
// Go over all data fields within a record.
for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
field != fields.end(); ++field) {
- // For fixed-size data type such as boolean, integer, even
- // IP address we can use the utility function to get the required
- // buffer size.
- 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) {
- // 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) {
- std::string fqdn =
- OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
- // The size of the buffer holding an FQDN is always
- // 1 byte larger than the size of the string
- // representation of this FQDN.
- data_size = fqdn.size() + 1;
- } else if ((*field == OPT_BINARY_TYPE) || (*field == OPT_STRING_TYPE)) {
- // 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_buf.end());
- } else if (*field == OPT_IPV6_PREFIX_TYPE) {
- // The size of the IPV6 prefix type is determined as
- // one byte (which is the size of the prefix in bits)
- // followed by the prefix bits (right-padded with
- // zeros to the nearest octet boundary).
- if (std::distance(data, data_buf.end()) > 0) {
- data_size = static_cast<size_t>(sizeof(uint8_t) + (*data + 7) / 8);
- }
- } else if (*field == OPT_TUPLE_TYPE) {
- OpaqueDataTuple::LengthFieldType lft =
- getUniverse() == Option::V4 ?
- OpaqueDataTuple::LENGTH_1_BYTE :
- OpaqueDataTuple::LENGTH_2_BYTES;
- std::string value =
- OptionDataTypeUtil::readTuple(OptionBuffer(data, data_buf.end()),
- lft);
- data_size = value.size();
- // The size of the buffer holding a tuple is always
- // 1 or 2 byte larger than the size of the string
- data_size += getUniverse() == Option::V4 ? 1 : 2;
- } else {
- // If we reached the end of buffer we assume that this option is
- // truncated because there is no remaining data to initialize
- // an option field.
- isc_throw(OutOfRange, "option buffer truncated");
- }
- }
+ size_t data_size = bufferLength(*field, false,
+ data, data_buf.end());
// Our data field requires that there is a certain chunk of
// data left in the buffer. If not, option is truncated.
data += data_size;
}
+ // Get extra buffers when the last field is an array.
+ if (definition_.getArrayType()) {
+ while (data != data_buf.end()) {
+ // Code copied from the standard array case
+ size_t data_size = bufferLength(fields.back(), true,
+ data, data_buf.end());
+ assert(data_size > 0);
+ if (std::distance(data, data_buf.end()) < data_size) {
+ break;
+ }
+ buffers.push_back(OptionBuffer(data, data + data_size));
+ data += data_size;
+ }
+ }
+
// Unpack suboptions if any.
- if (data != data_buf.end() && !getEncapsulatedSpace().empty()) {
+ else if (data != data_buf.end() && !getEncapsulatedSpace().empty()) {
unpackOptions(OptionBuffer(data, data_buf.end()));
}
// we have to handle multiple buffers.
if (definition_.getArrayType()) {
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) {
- std::string fqdn =
- OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
- // The size of the buffer holding an FQDN is always
- // 1 byte larger than the size of the string
- // representation of this FQDN.
- data_size = fqdn.size() + 1;
-
- } else if (data_type == OPT_IPV6_PREFIX_TYPE) {
- PrefixTuple prefix =
- OptionDataTypeUtil::readPrefix(OptionBuffer(data, data_buf.end()));
- // Data size comprises 1 byte holding a prefix length and the
- // prefix length (in bytes) rounded to the nearest byte boundary.
- data_size = sizeof(uint8_t) + (prefix.first.asUint8() + 7) / 8;
- } else if (data_type == OPT_TUPLE_TYPE) {
- OpaqueDataTuple::LengthFieldType lft =
- getUniverse() == Option::V4 ?
- OpaqueDataTuple::LENGTH_1_BYTE :
- OpaqueDataTuple::LENGTH_2_BYTES;
- std::string value =
- OptionDataTypeUtil::readTuple(OptionBuffer(data, data_buf.end()),
- lft);
- data_size = value.size();
- // The size of the buffer holding a tuple is always
- // 1 or 2 byte larger than the size of the string
- data_size += getUniverse() == Option::V4 ? 1 : 2;
-
- }
+ data_size = bufferLength(data_type, true, data, data_buf.end());
// 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
// 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) {
- // For FQDN we get the size by actually reading the FQDN.
- if (data_type == OPT_FQDN_TYPE) {
- std::string fqdn =
- OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
- // The size of the buffer holding an FQDN is always
- // 1 bytes larger than the size of the string
- // representation of this FQDN.
- data_size = fqdn.size() + 1;
-
- } else if (data_type == OPT_IPV6_PREFIX_TYPE) {
- if (!data_buf.empty()) {
- data_size = static_cast<size_t>
- (sizeof(uint8_t) + (data_buf[0] + 7) / 8);
- }
- } else if (data_type == OPT_TUPLE_TYPE) {
- OpaqueDataTuple::LengthFieldType lft =
- getUniverse() == Option::V4 ?
- OpaqueDataTuple::LENGTH_1_BYTE :
- OpaqueDataTuple::LENGTH_2_BYTES;
- std::string value =
- OptionDataTypeUtil::readTuple(OptionBuffer(data, data_buf.end()),
- lft);
- data_size = value.size();
- // The size of the buffer holding a tuple is always
- // 1 or 2 byte larger than the size of the string
- data_size += getUniverse() == Option::V4 ? 1 : 2;
-
- } else {
- data_size = std::distance(data, data_buf.end());
- }
- }
+ data_size = bufferLength(data_type, false, data, data_buf.end());
if ((data_size > 0) && (std::distance(data, data_buf.end()) >= data_size)) {
buffers.push_back(OptionBuffer(data, data + data_size));
data += data_size;
output << " " << dataFieldToText(*field, std::distance(fields.begin(),
field));
}
+
+ // If the last record field is an array iterate on extra buffers
+ if (definition_.getArrayType()) {
+ for (unsigned int i = fields.size(); i < getDataFieldsNum(); ++i) {
+ output << " " << dataFieldToText(fields.back(), i);
+ }
+ }
} else {
// For non-record types we iterate over all buffers
// and print the data type set globally for an option
void addArrayDataField(const T value) {
checkArrayType();
OptionDataType data_type = definition_.getType();
+ // Handle record last field.
+ if (data_type == OPT_RECORD_TYPE) {
+ data_type = definition_.getRecordFields().back();
+ }
if (OptionDataTypeTraits<T>::type != data_type) {
isc_throw(isc::dhcp::InvalidDataType,
"specified data type " << data_type << " does not"
/// @throw isc::OutOfRange if index is out of range.
void checkIndex(const uint32_t index) const;
+ /// @brief Create a non initialized buffer.
+ ///
+ /// @param buffer buffer to update.
+ /// @param data_type data type of buffer.
+ void createBuffer(OptionBuffer& buffer,
+ const OptionDataType data_type) const;
+
/// @brief Create a collection of non initialized buffers.
void createBuffers();
+ /// @brief Return length of a buffer.
+ ///
+ /// @param data_type data type of buffer.
+ /// @param in_array true is called from the array case
+ /// @param begin iterator to first byte of input data.
+ /// @param end iterator to end of input data.
+ ///
+ /// @return size of data to copy to the buffer.
+ size_t bufferLength(const OptionDataType data_type, bool in_array,
+ OptionBuffer::const_iterator begin,
+ OptionBuffer::const_iterator end) const;
+
/// @brief Create collection of buffers representing data field values.
///
/// @param data_buf a buffer to be parsed.
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 (definition_.getArrayType()) {
+ // If the array flag is set the last record field is an array.
+ if (index < record_fields.size()) {
+ // Get the data type to be returned.
+ data_type = record_fields[index];
+ } else {
+ // Get the data type to be returned from the last record field.
+ data_type = record_fields.back();
+ }
+ } else {
+ // 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) {
for (size_t i = 0; i < records.size(); ++i) {
writeToBuffer(u, util::str::trim(values[i]), records[i], buf);
}
+ if (array_type_ && (values.size() > records.size())) {
+ for (size_t i = records.size(); i < values.size(); ++i) {
+ writeToBuffer(u, util::str::trim(values[i]),
+ records.back(), buf);
+ }
+ }
}
return (optionFactory(u, type, buf.begin(), buf.end()));
}
// Option definition must be of a known type.
err_str << "option type " << type_ << " not supported.";
- } else if (array_type_) {
- if (type_ == OPT_STRING_TYPE) {
- // Array of strings is not allowed because there is no way
- // to determine the size of a particular string and thus there
- // it no way to tell when other data fields begin.
- err_str << "array of strings is not a valid option definition.";
- } else if (type_ == OPT_BINARY_TYPE) {
- err_str << "array of binary values is not"
- << " a valid option definition.";
-
- } else if (type_ == OPT_EMPTY_TYPE) {
- err_str << "array of empty value is not"
- << " a valid option definition.";
-
- }
-
} else if (type_ == OPT_RECORD_TYPE) {
// At least two data fields should be added to the record. Otherwise
// non-record option definition could be used.
it < fields.end() - 1) {
err_str << "binary data field can't be laid before data"
<< " fields of other types.";
+ break;
}
/// Empty type is not allowed within a record.
if (*it == OPT_EMPTY_TYPE) {
break;
}
}
+ // If the array flag is set the last field is an array.
+ if (err_str.str().empty() && array_type_) {
+ const OptionDataType& last_type = fields.back();
+ if (last_type == OPT_STRING_TYPE) {
+ err_str << "array of strings is not"
+ << "a valid option definition.";
+ } else if (last_type == OPT_BINARY_TYPE) {
+ err_str << "array of binary values is not"
+ << " a valid option definition.";
+ }
+ // Empty type was already checked.
+ }
}
+ } else if (array_type_) {
+ if (type_ == OPT_STRING_TYPE) {
+ // Array of strings is not allowed because there is no way
+ // to determine the size of a particular string and thus there
+ // it no way to tell when other data fields begin.
+ err_str << "array of strings is not a valid option definition.";
+ } else if (type_ == OPT_BINARY_TYPE) {
+ err_str << "array of binary values is not"
+ << " a valid option definition.";
+
+ } else if (type_ == OPT_EMPTY_TYPE) {
+ err_str << "array of empty value is not"
+ << " a valid option definition.";
+
+ }
}
// Non-empty error string means that we have hit the error. We throw
bool
OptionDefinition::haveIAx6Format(OptionDataType first_type) const {
return (haveType(OPT_RECORD_TYPE) &&
+ !getArrayType() &&
record_fields_.size() == 3 &&
record_fields_[0] == first_type &&
record_fields_[1] == OPT_UINT32_TYPE &&
bool
OptionDefinition::haveIAPrefix6Format() const {
return (haveType(OPT_RECORD_TYPE) &&
+ !getArrayType() &&
record_fields_.size() == 4 &&
record_fields_[0] == OPT_UINT32_TYPE &&
record_fields_[1] == OPT_UINT32_TYPE &&
bool
OptionDefinition::haveFqdn4Format() const {
return (haveType(OPT_RECORD_TYPE) &&
+ !getArrayType() &&
record_fields_.size() == 4 &&
record_fields_[0] == OPT_UINT8_TYPE &&
record_fields_[1] == OPT_UINT8_TYPE &&
bool
OptionDefinition::haveClientFqdnFormat() const {
return (haveType(OPT_RECORD_TYPE) &&
+ !getArrayType() &&
(record_fields_.size() == 2) &&
(record_fields_[0] == OPT_UINT8_TYPE) &&
(record_fields_[1] == OPT_FQDN_TYPE));
EXPECT_EQ("ABCD", value6);
}
+// The purpose of this test is to verify that the option definition comprising
+// a record of various data fields with an array for the last can be used
+// to create an instance of custom option.
+TEST_F(OptionCustomTest, recordArrayData) {
+ // Create the definition of an option which comprises
+ // a record of fields of different types.
+ OptionDefinition opt_def("OPTION_FOO", 1000, "record", true);
+ 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("psid"));
+ ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
+
+ 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 to 8712.
+ writeInt<uint16_t>(8712, buf);
+ // Initialize field 1 to 'true'
+ buf.push_back(static_cast<unsigned short>(1));
+ // Initialize field 2 to 'mydomain.example.com'.
+ 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 4 to IPv6 address.
+ writeAddress(IOAddress("2001:db8:1::1"), buf);
+ // Initialize PSID len and PSID value.
+ writeInt<uint8_t>(6, buf);
+ writeInt<uint16_t>(0xD400, buf);
+ // Initialize last field 6 to a pair of int 12345678 and 87654321.
+ writeInt<uint32_t>(12345678, buf);
+ writeInt<uint32_t>(87654321, buf);
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 7+1 data fields.
+ ASSERT_EQ(8, option->getDataFieldsNum());
+
+ // Verify value in the field 0.
+ uint16_t value0 = 0;
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(8712, value0);
+
+ // Verify value in the field 1.
+ bool value1 = false;
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_TRUE(value1);
+
+ // Verify value in the field 2.
+ std::string value2 = "";
+ ASSERT_NO_THROW(value2 = option->readFqdn(2));
+ EXPECT_EQ("mydomain.example.com.", value2);
+
+ // Verify value in the field 3.
+ IOAddress value3("127.0.0.1");
+ ASSERT_NO_THROW(value3 = option->readAddress(3));
+ EXPECT_EQ("192.168.0.1", value3.toText());
+
+ // Verify value in the field 4.
+ IOAddress value4("::1");
+ ASSERT_NO_THROW(value4 = option->readAddress(4));
+ EXPECT_EQ("2001:db8:1::1", value4.toText());
+
+ // Verify value in the field 5.
+ PSIDTuple value5;
+ ASSERT_NO_THROW(value5 = option->readPsid(5));
+ EXPECT_EQ(6, value5.first.asUnsigned());
+ EXPECT_EQ(0x35, value5.second.asUint16());
+
+ // Verify value in the field 6.
+ uint32_t value6;
+ ASSERT_NO_THROW(value6 = option->readInteger<uint32_t>(6));
+ EXPECT_EQ(12345678, value6);
+
+ // Verify value in the extra field 7.
+ uint32_t value7;
+ ASSERT_NO_THROW(value7 = option->readInteger<uint32_t>(7));
+ EXPECT_EQ(87654321, value7);
+}
+
// The purpose of this test is to verify that truncated buffer
// can't be used to create an option being a record of value of
// different types.
EXPECT_EQ(value8, "hello world");
}
+TEST_F(OptionCustomTest, setRecordArrayData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "record", true);
+
+ 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("psid"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv6-prefix"));
+ ASSERT_NO_THROW(opt_def.addRecordField("tuple"));
+ ASSERT_NO_THROW(opt_def.addRecordField("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 number of elements should be equal to number of elements
+ // in the record.
+ ASSERT_EQ(9, option->getDataFieldsNum());
+
+ // Check that the default values have been correctly set.
+ uint16_t value0;
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(0, value0);
+ bool value1 = true;
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_FALSE(value1);
+ std::string value2;
+ ASSERT_NO_THROW(value2 = option->readFqdn(2));
+ EXPECT_EQ(".", value2);
+ IOAddress value3("127.0.0.1");
+ ASSERT_NO_THROW(value3 = option->readAddress(3));
+ EXPECT_EQ("0.0.0.0", value3.toText());
+ IOAddress value4("2001:db8:1::1");
+ ASSERT_NO_THROW(value4 = option->readAddress(4));
+ EXPECT_EQ("::", value4.toText());
+ PSIDTuple value5;
+ ASSERT_NO_THROW(value5 = option->readPsid(5));
+ EXPECT_EQ(0, value5.first.asUnsigned());
+ EXPECT_EQ(0, value5.second.asUint16());
+ PrefixTuple value6(ZERO_PREFIX_TUPLE);
+ ASSERT_NO_THROW(value6 = option->readPrefix(6));
+ EXPECT_EQ(0, value6.first.asUnsigned());
+ EXPECT_EQ("::", value6.second.toText());
+ std::string value7 = "abc";
+ // Tuple has no default value
+ EXPECT_THROW(option->readTuple(7), BadDataTypeCast);
+ uint32_t value8;
+ ASSERT_NO_THROW(value8 = option->readInteger<uint32_t>(8));
+ EXPECT_EQ(0, value8);
+
+ // Override each value with a new value.
+ ASSERT_NO_THROW(option->writeInteger<uint16_t>(1234, 0));
+ ASSERT_NO_THROW(option->writeBoolean(true, 1));
+ ASSERT_NO_THROW(option->writeFqdn("example.com", 2));
+ ASSERT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1"), 3));
+ ASSERT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::100"), 4));
+ ASSERT_NO_THROW(option->writePsid(PSIDLen(4), PSID(8), 5));
+ ASSERT_NO_THROW(option->writePrefix(PrefixLen(48),
+ IOAddress("2001:db8:1::"), 6));
+ ASSERT_NO_THROW(option->writeTuple("foobar", 7));
+ ASSERT_NO_THROW(option->writeInteger<uint32_t>(12345678, 8));
+ ASSERT_NO_THROW(option->addArrayDataField<uint32_t>(87654321));
+
+ // Check that the new values have been correctly set.
+ ASSERT_EQ(10, option->getDataFieldsNum());
+
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(1234, value0);
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_TRUE(value1);
+ ASSERT_NO_THROW(value2 = option->readFqdn(2));
+ EXPECT_EQ("example.com.", value2);
+ ASSERT_NO_THROW(value3 = option->readAddress(3));
+ EXPECT_EQ("192.168.0.1", value3.toText());
+ ASSERT_NO_THROW(value4 = option->readAddress(4));
+ EXPECT_EQ("2001:db8:1::100", value4.toText());
+ ASSERT_NO_THROW(value5 = option->readPsid(5));
+ EXPECT_EQ(4, value5.first.asUnsigned());
+ EXPECT_EQ(8, value5.second.asUint16());
+ ASSERT_NO_THROW(value6 = option->readPrefix(6));
+ EXPECT_EQ(48, value6.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", value6.second.toText());
+ ASSERT_NO_THROW(value7 = option->readTuple(7));
+ EXPECT_EQ(value7, "foobar");
+ ASSERT_NO_THROW(value8 = option->readInteger<uint32_t>(8));
+ EXPECT_EQ(12345678, value8);
+ uint32_t value9;
+ ASSERT_NO_THROW(value9 = option->readInteger<uint32_t>(9));
+ EXPECT_EQ(87654321, value9);
+}
+
// The purpose of this test is to verify that pack function for
// DHCPv4 custom option works correctly.
TEST_F(OptionCustomTest, pack4) {
}
}
+// The purpose of this test is to verify that unpack function works
+// correctly for a custom option with record and trailing array.
+TEST_F(OptionCustomTest, unpackRecordArray) {
+ OptionDefinition opt_def("OPTION_FOO", 231, "record", true);
+
+ ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
+
+ // Initialize reference data.
+ OptionBuffer buf;
+ writeInt<uint16_t>(8712, buf);
+
+ std::vector<IOAddress> addresses;
+ addresses.push_back(IOAddress("192.168.0.1"));
+ addresses.push_back(IOAddress("127.0.0.1"));
+ addresses.push_back(IOAddress("10.10.1.2"));
+
+ // Store the collection of IPv4 addresses into the buffer.
+ for (size_t i = 0; i < addresses.size(); ++i) {
+ writeAddress(addresses[i], buf);
+ }
+
+ // Use the input buffer to create custom option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 4 data fields.
+ ASSERT_EQ(4, option->getDataFieldsNum());
+
+ // We expect a 16 bit integer
+ uint16_t value0;
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(8712, value0);
+
+ // ... and 3 IPv4 addresses being stored in the option.
+ for (int i = 0; i < 3; ++i) {
+ IOAddress address("10.10.10.10");
+ ASSERT_NO_THROW(address = option->readAddress(i + 1));
+ EXPECT_EQ(addresses[i], address);
+ }
+
+ std::string text = option->toText();
+ EXPECT_EQ("type=231, len=014: 8712 (uint16) 192.168.0.1 (ipv4-address) "
+ "127.0.0.1 (ipv4-address) 10.10.1.2 (ipv4-address)", text);
+}
+
// The purpose of this test is to verify that new data can be set for
// a custom option.
TEST_F(OptionCustomTest, initialize) {
"record");
opt_def16.addRecordField("uint8");
opt_def16.addRecordField("string");
+ EXPECT_NO_THROW(opt_def16.validate());
+
+ // ... at least if it is not an array.
+ OptionDefinition opt_def17("OPTION_STATUS_CODE", D6O_STATUS_CODE,
+ "record", true);
+ opt_def17.addRecordField("uint8");
+ opt_def17.addRecordField("string");
+ EXPECT_THROW(opt_def17.validate(), MalformedOptionDefinition);
// Check invalid encapsulated option space name.
- OptionDefinition opt_def17("OPTION_VENDOR_OPTS", D6O_VENDOR_OPTS,
+ OptionDefinition opt_def18("OPTION_VENDOR_OPTS", D6O_VENDOR_OPTS,
"uint32", "invalid%space%name");
- EXPECT_THROW(opt_def17.validate(), MalformedOptionDefinition);
+ EXPECT_THROW(opt_def18.validate(), MalformedOptionDefinition);
}