#include <dhcp/option_custom.h>
#include <util/encode/hex.h>
+using namespace isc::asiolink;
+
namespace isc {
namespace dhcp {
}
void
-OptionCustom::addArrayDataField(const asiolink::IOAddress& address) {
+OptionCustom::addArrayDataField(const IOAddress& address) {
checkArrayType();
if ((address.isV4() && definition_.getType() != OPT_IPV4_ADDRESS_TYPE) ||
buffers_.push_back(buf);
}
+void
+OptionCustom::addArrayDataField(const PrefixLen& prefix_len,
+ const asiolink::IOAddress& prefix) {
+ checkArrayType();
+
+ if (definition_.getType() != OPT_IPV6_PREFIX_TYPE) {
+ isc_throw(BadDataTypeCast, "IPv6 prefix can be specified only for"
+ " an option comprising an array of IPv6 prefix values");
+ }
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writePrefix(prefix_len, prefix, buf);
+ buffers_.push_back(buf);
+}
+
+void
+OptionCustom::addArrayDataField(const PSIDLen& psid_len, const PSID& psid) {
+ checkArrayType();
+
+ if (definition_.getType() != OPT_PSID_TYPE) {
+ isc_throw(BadDataTypeCast, "PSID value can be specified onlu for"
+ " an option comprising an array of PSID length / value"
+ " tuples");
+ }
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writePsid(psid_len, psid, buf);
+ buffers_.push_back(buf);
+}
+
void
OptionCustom::checkIndex(const uint32_t index) const {
if (index >= buffers_.size()) {
// 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 '.'.
- if (data_size == 0 &&
- *field == OPT_FQDN_TYPE) {
- OptionDataTypeUtil::writeFqdn(".", buf);
+ // 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
// so we have to allocate exactly one buffer.
OptionBuffer buf;
size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
- if (data_size == 0 &&
- data_type == OPT_FQDN_TYPE) {
- OptionDataTypeUtil::writeFqdn(".", buf);
+ 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.
// 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) ) {
+ } 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
// 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 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");
}
- } 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_buf.end()) < data_size) {
- isc_throw(OutOfRange, "option buffer truncated");
- }
}
+
+ // 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_buf.end()) < data_size) {
+ isc_throw(OutOfRange, "option buffer truncated");
+ }
+
// Store the created buffer.
buffers.push_back(OptionBuffer(data, data + data_size));
// Proceed to the next data field.
// 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;
}
// We don't perform other checks for data types that can't be
// used together with array indicator such as strings, empty field
// 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 {
data_size = std::distance(data, data_buf.end());
}
}
- if (data_size > 0) {
+ if ((data_size > 0) && (std::distance(data, data_buf.end()) >= data_size)) {
buffers.push_back(OptionBuffer(data, data + data_size));
data += data_size;
} else {
}
-asiolink::IOAddress
+IOAddress
OptionCustom::readAddress(const uint32_t index) const {
checkIndex(index);
}
void
-OptionCustom::writeAddress(const asiolink::IOAddress& address,
+OptionCustom::writeAddress(const IOAddress& address,
const uint32_t index) {
- using namespace isc::asiolink;
-
checkIndex(index);
if ((address.isV4() && buffers_[index].size() != V4ADDRESS_LEN) ||
std::swap(buffers_[index], buf);
}
+PrefixTuple
+OptionCustom::readPrefix(const uint32_t index) const {
+ checkIndex(index);
+ return (OptionDataTypeUtil::readPrefix(buffers_[index]));
+}
+
+void
+OptionCustom::writePrefix(const PrefixLen& prefix_len,
+ const IOAddress& prefix,
+ const uint32_t index) {
+ checkIndex(index);
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writePrefix(prefix_len, prefix, buf);
+ // If there are no errors while writing PSID to a buffer, we can
+ // replace the current buffer with a new buffer.
+ std::swap(buffers_[index], buf);
+}
+
+
+PSIDTuple
+OptionCustom::readPsid(const uint32_t index) const {
+ checkIndex(index);
+ return (OptionDataTypeUtil::readPsid(buffers_[index]));
+}
+
+void
+OptionCustom::writePsid(const PSIDLen& psid_len, const PSID& psid,
+ const uint32_t index) {
+ checkIndex(index);
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writePsid(psid_len, psid, buf);
+ // If there are no errors while writing PSID to a buffer, we can
+ // replace the current buffer with a new buffer.
+ std::swap(buffers_[index], buf);
+}
+
+
std::string
OptionCustom::readString(const uint32_t index) const {
checkIndex(index);
#ifndef OPTION_CUSTOM_H
#define OPTION_CUSTOM_H
+#include <asiolink/io_address.h>
#include <dhcp/option.h>
#include <dhcp/option_definition.h>
#include <util/io_utilities.h>
buffers_.push_back(buf);
}
+ /// @brief Create new buffer and store variable length prefix in it.
+ ///
+ /// @param prefix_len Prefix length.
+ /// @param prefix Prefix.
+ void addArrayDataField(const PrefixLen& prefix_len,
+ const asiolink::IOAddress& prefix);
+
+ /// @brief Create new buffer and store PSID length / value in it.
+ ///
+ /// @param psid_len PSID length.
+ /// @param psid PSID.
+ void addArrayDataField(const PSIDLen& psid_len, const PSID& psid);
+
/// @brief Return a number of the data fields.
///
/// @return number of data fields held by the option.
std::swap(buffers_[index], buf);
}
+ /// @brief Read a buffer as variable length prefix.
+ ///
+ /// @param index buffer index.
+ ///
+ /// @return Prefix length / value tuple.
+ /// @throw isc::OutOfRange of index is out of range.
+ PrefixTuple readPrefix(const uint32_t index = 0) const;
+
+ /// @brief Write prefix length and value into a buffer.
+ ///
+ /// @param prefix_len Prefix length.
+ /// @param prefix Prefix value.
+ /// @param index Buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ void writePrefix(const PrefixLen& prefix_len,
+ const asiolink::IOAddress& prefix,
+ const uint32_t index = 0);
+
+ /// @brief Read a buffer as a PSID length / value tuple.
+ ///
+ /// @param index buffer index.
+ ///
+ /// @return PSID length / value tuple.
+ /// @throw isc::OutOfRange of index is out of range.
+ PSIDTuple readPsid(const uint32_t index = 0) const;
+
+ /// @brief Write PSID length / value into a buffer.
+ ///
+ /// @param psid_len PSID length value.
+ /// @param psid PSID value in the range of 0 .. 2^(PSID length).
+ /// @param index buffer index.
+ ///
+ /// @throw isc::dhcp::BadDataTypeCast if PSID length or value is
+ /// invalid.
+ /// @throw isc::OutOfRange if index is out of range.
+ void writePsid(const PSIDLen& psid_len, const PSID& psid,
+ const uint32_t index = 0);
+
/// @brief Read a buffer as string value.
///
/// @param index buffer index.
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
#include <dns/labelsequence.h>
#include <dns/name.h>
#include <util/encode/hex.h>
+#include <algorithm>
+
+using namespace isc::asiolink;
namespace isc {
namespace dhcp {
data_types_["uint32"] = OPT_UINT32_TYPE;
data_types_["ipv4-address"] = OPT_IPV4_ADDRESS_TYPE;
data_types_["ipv6-address"] = OPT_IPV6_ADDRESS_TYPE;
+ data_types_["ipv6-prefix"] = OPT_IPV6_PREFIX_TYPE;
+ data_types_["psid"] = OPT_PSID_TYPE;
data_types_["string"] = OPT_STRING_TYPE;
data_types_["fqdn"] = OPT_FQDN_TYPE;
data_types_["record"] = OPT_RECORD_TYPE;
data_type_names_[OPT_UINT32_TYPE] = "uint32";
data_type_names_[OPT_IPV4_ADDRESS_TYPE] = "ipv4-address";
data_type_names_[OPT_IPV6_ADDRESS_TYPE] = "ipv6-address";
+ data_type_names_[OPT_IPV6_PREFIX_TYPE] = "ipv6-prefix";
+ data_type_names_[OPT_PSID_TYPE] = "psid";
data_type_names_[OPT_STRING_TYPE] = "string";
data_type_names_[OPT_FQDN_TYPE] = "fqdn";
data_type_names_[OPT_RECORD_TYPE] = "record";
case OPT_IPV6_ADDRESS_TYPE:
return (asiolink::V6ADDRESS_LEN);
+ case OPT_PSID_TYPE:
+ return (3);
+
default:
;
}
}
}
+PrefixTuple
+OptionDataTypeUtil::readPrefix(const std::vector<uint8_t>& buf) {
+ // Prefix typically consists of the prefix length and the
+ // actual value. If prefix length is 0, the buffer length should
+ // be at least 1 byte to hold this length value.
+ if (buf.empty()) {
+ isc_throw(BadDataTypeCast, "unable to read prefix length from "
+ "a truncated buffer");
+ }
+
+ // Surround everything with try-catch to unify exceptions being
+ // thrown by various functions and constructors.
+ try {
+ // Try to create PrefixLen object from the prefix length held
+ // in the buffer. This may cause an exception if the length is
+ // invalid (greater than 128).
+ PrefixLen prefix_len(buf.at(0));
+
+ // Convert prefix length to bytes, because we operate on bytes,
+ // rather than bits.
+ uint8_t prefix_len_bytes = (prefix_len.asUint8() / 8);
+ // Check if we need to zero pad any bits. This is the case when
+ // the prefix length is not divisible by 8 (bits per byte). The
+ // calculations below may require some explanations. We first
+ // perform prefix_len % 8 to get the number of useful bits beyond
+ // the current prefix_len_bytes value. By substracting it from 8
+ // we get the number of zero padded bits, but with the special
+ // case of 8 when the result of substraction is 0. The value of
+ // 8 really means no padding so we make a modulo division once
+ // again to turn 8s to 0s.
+ const uint8_t zero_padded_bits =
+ static_cast<uint8_t>((8 - (prefix_len.asUint8() % 8)) % 8);
+ // If there are zero padded bits, it means that we need an extra
+ // byte to be retrieved from the buffer.
+ if (zero_padded_bits > 0) {
+ ++prefix_len_bytes;
+ }
+
+ // Make sure that the buffer is long enough. We substract 1 to
+ // also account for the fact that the buffer includes a prefix
+ // length besides a prefix.
+ if ((buf.size() - 1) < prefix_len_bytes) {
+ isc_throw(BadDataTypeCast, "unable to read a prefix having length of "
+ << prefix_len.asUnsigned() << " from a truncated buffer");
+ }
+
+ // It is possible for a prefix to be zero if the prefix length
+ // is zero.
+ IOAddress prefix(IOAddress::IPV6_ZERO_ADDRESS());
+
+ // If there is anything more than prefix length is this buffer
+ // we need to read it.
+ if (buf.size() > 1) {
+ // Buffer has to be copied, because we will modify its
+ // contents by setting certain bits to 0, if necessary.
+ std::vector<uint8_t> prefix_buf(buf.begin() + 1, buf.end());
+ // All further conversions require that the buffer length is
+ // 16 bytes.
+ if (prefix_buf.size() < V6ADDRESS_LEN) {
+ prefix_buf.resize(V6ADDRESS_LEN);
+ if (prefix_len_bytes < prefix_buf.size()) {
+ // Zero all bits in the buffer beyond prefix length
+ // position.
+ std::fill(prefix_buf.begin() + prefix_len_bytes,
+ prefix_buf.end(), 0);
+
+ if (zero_padded_bits) {
+ // There is a byte that require zero padding. We
+ // achieve that by shifting the value of that byte
+ // back and forth by the number of zeroed bits.
+ prefix_buf.at(prefix_len_bytes - 1) =
+ (prefix_buf.at(prefix_len_bytes - 1)
+ >> zero_padded_bits)
+ << zero_padded_bits;
+ }
+ }
+ }
+ // Convert the buffer to the IOAddress object.
+ prefix = IOAddress::fromBytes(AF_INET6, &prefix_buf[0]);
+ }
+
+ return (std::make_pair(prefix_len, prefix));
+
+ } catch (const BadDataTypeCast& ex) {
+ // Pass through the BadDataTypeCast exceptions.
+ throw;
+
+ } catch (const std::exception& ex) {
+ // If an exception of a different type has been thrown, insert
+ // a text that indicates that the failure occurred during reading
+ // the prefix and modify exception type to BadDataTypeCast.
+ isc_throw(BadDataTypeCast, "unable to read a prefix from a buffer: "
+ << ex.what());
+ }
+}
+
+void
+OptionDataTypeUtil::writePrefix(const PrefixLen& prefix_len,
+ const IOAddress& prefix,
+ std::vector<uint8_t>& buf) {
+ // Prefix must be an IPv6 prefix.
+ if (!prefix.isV6()) {
+ isc_throw(BadDataTypeCast, "illegal prefix value "
+ << prefix);
+ }
+
+ // We don't need to validate the prefix_len value, because it is
+ // already validated by the PrefixLen class.
+ buf.push_back(prefix_len.asUint8());
+
+ // Convert the prefix length to a number of bytes.
+ uint8_t prefix_len_bytes = prefix_len.asUint8() / 8;
+ // Check if there are any bits that require zero padding. See the
+ // commentary in readPrefix to see how this is calculated.
+ const uint8_t zero_padded_bits =
+ static_cast<uint8_t>((8 - (prefix_len.asUint8() % 8)) % 8);
+ // If zero padding is needed it means that we need to extend the
+ // buffer to hold the "partially occupied" byte.
+ if (zero_padded_bits > 0) {
+ ++prefix_len_bytes;
+ }
+
+ // Convert the prefix to byte representation and append it to
+ // our output buffer.
+ std::vector<uint8_t> prefix_bytes = prefix.toBytes();
+ buf.insert(buf.end(), prefix_bytes.begin(),
+ prefix_bytes.begin() + prefix_len_bytes);
+ // If the last byte requires zero padding we achieve that by shifting
+ // bits back and forth by the number of insignificant bits.
+ if (zero_padded_bits) {
+ *buf.rbegin() = (*buf.rbegin() >> zero_padded_bits) << zero_padded_bits;
+ }
+}
+
+PSIDTuple
+OptionDataTypeUtil::readPsid(const std::vector<uint8_t>& buf) {
+ if (buf.size() < 3) {
+ isc_throw(BadDataTypeCast, "unable to read PSID from the buffer."
+ << " Invalid buffer size " << buf.size()
+ << ". Expected 3 bytes (PSID length and PSID value)");
+ }
+
+ // Read PSID length.
+ uint8_t psid_len = buf[0];
+
+ // PSID length must not be greater than 16 bits.
+ if (psid_len > sizeof(uint16_t) * 8) {
+ isc_throw(BadDataTypeCast, "invalid PSID length value "
+ << static_cast<unsigned>(psid_len)
+ << ", this value is expected to be in range of 0 to 16");
+ }
+
+ // Read two bytes of PSID value.
+ uint16_t psid = isc::util::readUint16(&buf[1], 2);
+
+ // We need to check that the PSID value does not exceed the maximum value
+ // for a specified PSID length. That means that all bits placed further than
+ // psid_len from the left must be set to 0. So, we create a bit mask
+ // by shifting a value of 0xFFFF to the left and right by psid_len. This
+ // leaves us with psid_len leftmost bits unset and the rest set. Next, we
+ // apply the mask on the PSID value from the buffer and make sure the result
+ // is 0. Otherwise, it means that there are some bits set in the PSID which
+ // aren't supposed to be set.
+ if ((psid_len > 0) &&
+ ((psid & static_cast<uint16_t>(static_cast<uint16_t>(0xFFFF << psid_len)
+ >> psid_len)) != 0)) {
+ isc_throw(BadDataTypeCast, "invalid PSID value " << psid
+ << " for a specified PSID length "
+ << static_cast<unsigned>(psid_len));
+ }
+
+ // All is good, so we can convert the PSID value read from the buffer to
+ // the port set number.
+ psid = psid >> (sizeof(psid) * 8 - psid_len);
+ return (std::make_pair(PSIDLen(psid_len), PSID(psid)));
+}
+
+void
+OptionDataTypeUtil::writePsid(const PSIDLen& psid_len, const PSID& psid,
+ std::vector<uint8_t>& buf) {
+ if (psid_len.asUint8() > sizeof(psid) * 8) {
+ isc_throw(BadDataTypeCast, "invalid PSID length value "
+ << psid_len.asUnsigned()
+ << ", this value is expected to be in range of 0 to 16");
+ }
+
+ if (psid_len.asUint8() > 0 &&
+ (psid.asUint16() > (0xFFFF >> (sizeof(uint16_t) * 8 - psid_len.asUint8())))) {
+ isc_throw(BadDataTypeCast, "invalid PSID value " << psid.asUint16()
+ << " for a specified PSID length "
+ << psid_len.asUnsigned());
+ }
+
+ buf.resize(buf.size() + 3);
+ buf.at(buf.size() - 3) = psid_len.asUint8();
+ isc::util::writeUint16(static_cast<uint16_t>
+ (psid.asUint16() << (sizeof(uint16_t) * 8 - psid_len.asUint8())),
+ &buf[buf.size() - 2], 2);
+}
+
+
std::string
OptionDataTypeUtil::readString(const std::vector<uint8_t>& buf) {
std::string value;
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
#include <util/io_utilities.h>
#include <stdint.h>
+#include <utility>
namespace isc {
namespace dhcp {
OPT_ANY_ADDRESS_TYPE,
OPT_IPV4_ADDRESS_TYPE,
OPT_IPV6_ADDRESS_TYPE,
+ OPT_IPV6_PREFIX_TYPE,
+ OPT_PSID_TYPE,
OPT_STRING_TYPE,
OPT_FQDN_TYPE,
OPT_RECORD_TYPE,
OPT_UNKNOWN_TYPE
};
+/// @brief Parameters being used to make up an option definition.
+struct OptionDefParams {
+ const char* name; // option name
+ uint16_t code; // option code
+ OptionDataType type; // data type
+ bool array; // is array
+ const OptionDataType* records; // record fields
+ size_t records_size; // number of fields in a record
+ const char* encapsulates; // option space encapsulated by the
+ // particular option.
+};
+
+/// @brief Encapsulation of option definition parameters and the structure size.
+struct OptionDefParamsEncapsulation {
+ const struct OptionDefParams* optionDefParams; // parameters structure
+ const int size; // structure size
+ const char* space; // option space
+};
+
/// @brief Trait class for data types supported in DHCP option definitions.
///
/// This is useful to check whether the type specified as template parameter
static const OptionDataType type = OPT_STRING_TYPE;
};
+/// @brief Encapsulates PSID length.
+class PSIDLen {
+public:
+
+ /// @brief Default constructor.
+ PSIDLen() : psid_len_(0) { }
+
+ /// @brief Constructor.
+ ///
+ /// It checks that the specified value is not greater than
+ /// 16, which is a maximum value for the PSID length.
+ ///
+ /// @param psid_len PSID length.
+ /// @throw isc::OutOfRange If specified PSID length is greater than 16.
+ explicit PSIDLen(const uint8_t psid_len)
+ : psid_len_(psid_len) {
+ if (psid_len_ > sizeof(uint16_t) * 8) {
+ isc_throw(isc::OutOfRange, "invalid value "
+ << asUnsigned() << " of PSID length");
+ }
+ }
+
+ /// @brief Returns PSID length as uint8_t value.
+ uint8_t asUint8() const {
+ return (psid_len_);
+ }
+
+ /// @brief Returns PSID length as unsigned int.
+ ///
+ /// This is useful to convert the value to a numeric type which
+ /// can be logged directly. Note that the uint8_t value has to
+ /// be cast to an integer value to be logged as a number. This
+ /// is because the uint8_t is often implemented as char, in which
+ /// case directly loggingan uint8_t value prints a character rather
+ /// than a number.
+ unsigned int asUnsigned() const {
+ return (static_cast<unsigned>(psid_len_));
+ }
+
+private:
+
+ /// @brief PSID length.
+ uint8_t psid_len_;
+};
+
+/// @brief Encapsulates PSID value.
+class PSID {
+public:
+
+ /// @brief Default constructor.
+ PSID() : psid_(0) { }
+
+ /// @brief Constructor.
+ ///
+ /// This constructor doesn't perform any checks on the input data.
+ ///
+ /// @param psid PSID value.
+ explicit PSID(const uint16_t psid)
+ : psid_(psid) {
+ }
+
+ /// @brief Returns PSID value as a number.
+ uint16_t asUint16() const {
+ return (psid_);
+ }
+
+private:
+
+ /// @brief PSID value.
+ uint16_t psid_;
+
+};
+
+/// @brief Defines a pair of PSID length / value.
+typedef std::pair<PSIDLen, PSID> PSIDTuple;
+
+/// @brief Encapsulates prefix length.
+class PrefixLen {
+public:
+
+ /// @brief Default constructor.
+ PrefixLen() : prefix_len_(0) { }
+
+ /// @brief Constructor.
+ ///
+ /// This constructor checks if the specified prefix length is
+ /// in the range of 0 to 128.
+ ///
+ /// @param prefix_len Prefix length value.
+ /// @throw isc::OutOfRange If specified prefix length is greater than 128.
+ explicit PrefixLen(const uint8_t prefix_len)
+ : prefix_len_(prefix_len) {
+ }
+
+ /// @brief Returns prefix length as uint8_t value.
+ uint8_t asUint8() const {
+ return (prefix_len_);
+ }
+
+ /// @brief Returns prefix length as unsigned int.
+ ///
+ /// This is useful to convert the value to a numeric type which
+ /// can be logged directly. See @ref PSIDLen::asUnsigned for the
+ /// use cases of this accessor.
+ unsigned int asUnsigned() const {
+ return (static_cast<unsigned>(prefix_len_));
+ }
+
+private:
+
+ /// @brief Prefix length.
+ uint8_t prefix_len_;
+};
+
+/// @brief Defines a pair of prefix length / value.
+typedef std::pair<PrefixLen, asiolink::IOAddress> PrefixTuple;
+
/// @brief Utility class for option data types.
///
/// This class provides a set of utility functions to operate on
///
/// @param buf input buffer.
/// @param family address family: AF_INET or AF_INET6.
- ///
+ ///
/// @throw isc::dhcp::BadDataTypeCast when the data being read
/// is truncated.
/// @return address being read.
/// @throw isc::dhcp::BadDataTypeCast if provided name is malformed.
static unsigned int getLabelCount(const std::string& text_name);
+ /// @brief Read prefix from a buffer.
+ ///
+ /// This method reads prefix length and a prefix value from a buffer.
+ /// The prefix value has variable length and this length is determined
+ /// from the first byte of the buffer. If the length is not divisible
+ /// by 8, the prefix is padded with zeros to the next byte boundary.
+ ///
+ /// @param buf input buffer holding a prefix length / prefix tuple.
+ ///
+ /// @return Prefix length and value.
+ static PrefixTuple readPrefix(const std::vector<uint8_t>& buf);
+
+ /// @brief Append prefix into a buffer.
+ ///
+ /// This method writes prefix length (1 byte) followed by a variable
+ /// length prefix.
+ ///
+ /// @param prefix_len Prefix length in bits (0 to 128).
+ /// @param prefix Prefix value.
+ /// @param [out] Output buffer.
+ static void writePrefix(const PrefixLen& prefix_len,
+ const asiolink::IOAddress& prefix,
+ std::vector<uint8_t>& buf);
+
+ /// @brief Read PSID length / value tuple from a buffer.
+ ///
+ /// This method reads three bytes from a buffer. The first byte
+ /// holds a PSID length value. The remaining two bytes contain a
+ /// zero padded PSID value.
+ ///
+ /// @return PSID length / value tuple.
+ /// @throw isc::dhcp::BadDataTypeCast if PSID length or value held
+ /// in the buffer is incorrect or the buffer is truncated.
+ static PSIDTuple readPsid(const std::vector<uint8_t>& buf);
+
+ /// @brief Append PSID length/value into a buffer.
+ ///
+ /// This method appends 1 byte of PSID length and 2 bytes of PSID
+ /// value into a buffer. The PSID value contains a PSID length
+ /// number of significant bits, followed by 16 - PSID length
+ /// zero bits.
+ ///
+ /// @param psid_len PSID length in the range of 0 to 16 holding the
+ /// number of significant bits within the PSID value.
+ /// @param psid PSID value, where the lowest value is 0, and the
+ /// highest value is 2^(PSID length).
+ /// @param [out] buf output buffer.
+ ///
+ /// @throw isc::dhcp::BadDataTypeCast if specified psid_len or
+ /// psid value is incorrect.
+ static void writePsid(const PSIDLen& psid_len, const PSID& psid,
+ std::vector<uint8_t>& buf);
+
/// @brief Read string value from a buffer.
///
/// @param buf input buffer.
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <dhcp/option_opaque_data_tuples.h>
-#include <dhcp/option_space.h>
#include <dhcp/option_string.h>
#include <dhcp/option_vendor.h>
#include <dhcp/option_vendor_class.h>
#include <util/strutil.h>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/predicate.hpp>
+#include <boost/dynamic_bitset.hpp>
using namespace std;
using namespace isc::util;
OptionDataTypeUtil::writeAddress(address, buf);
return;
}
+ case OPT_IPV6_PREFIX_TYPE:
+ {
+ std::string txt = value;
+
+ // first let's remove any whitespaces
+ boost::erase_all(txt, " "); // space
+ boost::erase_all(txt, "\t"); // tabulation
+
+ // Is this prefix/len notation?
+ size_t pos = txt.find("/");
+
+ if (pos == string::npos) {
+ isc_throw(BadDataTypeCast, "provided address/prefix "
+ << value
+ << " is not valid.");
+ }
+
+ std::string txt_address = txt.substr(0, pos);
+ isc::asiolink::IOAddress address = isc::asiolink::IOAddress(txt_address);
+ if (!address.isV6()) {
+ isc_throw(BadDataTypeCast, "provided address "
+ << txt_address
+ << " is not a valid IPv4 or IPv6 address.");
+ }
+
+ std::string txt_prefix = txt.substr(pos + 1);
+ uint8_t len = 0;
+ try {
+ // start with the first character after /
+ len = lexicalCastWithRangeCheck<uint8_t>(txt_prefix);
+ } catch (...) {
+ isc_throw(BadDataTypeCast, "provided prefix "
+ << txt_prefix
+ << " is not valid.");
+ }
+
+
+ // Write a prefix.
+ OptionDataTypeUtil::writePrefix(PrefixLen(len), address, buf);
+
+ return;
+ }
+ case OPT_PSID_TYPE:
+ {
+ std::string txt = value;
+
+ // first let's remove any whitespaces
+ boost::erase_all(txt, " "); // space
+ boost::erase_all(txt, "\t"); // tabulation
+
+ // Is this prefix/len notation?
+ size_t pos = txt.find("/");
+
+ if (pos == string::npos) {
+ isc_throw(BadDataTypeCast, "provided PSID value "
+ << value << " is not valid");
+ }
+
+ const std::string txt_psid = txt.substr(0, pos);
+ const std::string txt_psid_len = txt.substr(pos + 1);
+
+ uint16_t psid = 0;
+ uint8_t psid_len = 0;
+
+ try {
+ psid = lexicalCastWithRangeCheck<uint16_t>(txt_psid);
+ } catch (...) {
+ isc_throw(BadDataTypeCast, "provided PSID "
+ << txt_psid << " is not valid");
+ }
+
+ try {
+ psid_len = lexicalCastWithRangeCheck<uint8_t>(txt_psid_len);
+ } catch (...) {
+ isc_throw(BadDataTypeCast, "provided PSID length "
+ << txt_psid_len << " is not valid");
+ }
+
+ OptionDataTypeUtil::writePsid(PSIDLen(psid_len), PSID(psid), buf);
+ return;
+ }
case OPT_STRING_TYPE:
OptionDataTypeUtil::writeString(value, buf);
return;
/// - "uint16"
/// - "uint32"
/// - "ipv4-address" (IPv4 Address)
-/// - "ipv6-address" (IPV6 Address)
+/// - "ipv6-address" (IPv6 Address)
+/// - "ipv6-prefix" (IPv6 variable length prefix)
+/// - "psid" (PSID length / value)
/// - "string"
/// - "fqdn" (fully qualified name)
/// - "record" (set of data fields of different types)
namespace {
+/// @brief Default (zero) prefix tuple.
+const PrefixTuple
+ZERO_PREFIX_TUPLE(std::make_pair(PrefixLen(0),
+ IOAddress(IOAddress::IPV6_ZERO_ADDRESS())));
+
/// @brief OptionCustomTest test class.
class OptionCustomTest : public ::testing::Test {
public:
);
}
+// The purpose of this test is to verify that the option definition comprising
+// single variable length prefix can be used to create an instance of custom
+// option.
+TEST_F(OptionCustomTest, prefixData) {
+ OptionDefinition opt_def("option-foo", 1000, "ipv6-prefix",
+ "option-foo-space");
+
+ // Initialize input buffer.
+ OptionBuffer buf;
+ writeInt<uint8_t>(32, buf);
+ writeInt<uint32_t>(0x30000001, buf);
+
+ // Append suboption.
+ appendV6Suboption(buf);
+
+ // Create custom option using input buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Custom option should comprise exactly one buffer that represents
+ // a prefix.
+ PrefixTuple prefix(ZERO_PREFIX_TUPLE);
+ // Read prefix from buffer #0.
+ ASSERT_NO_THROW(prefix = option->readPrefix(0));
+
+ // The prefix comprises a prefix length and prefix value.
+ EXPECT_EQ(32, prefix.first.asUnsigned());
+ EXPECT_EQ("3000:1::", prefix.second.toText());
+
+ // Parsed option should have one suboption.
+ EXPECT_TRUE(hasV6Suboption(option.get()));
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// single PSID can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, psidData) {
+ OptionDefinition opt_def("option-foo", 1000, "psid",
+ "option-foo-space");
+
+ // Initialize input buffer.
+ OptionBuffer buf;
+ writeInt<uint8_t>(4, buf);
+ writeInt<uint16_t>(0x8000, buf);
+
+ // Append suboption.
+ appendV6Suboption(buf);
+
+ // Create custom option using input buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Custom option should comprise exactly one buffer that represents
+ // a PSID length / PSID value tuple.
+ PSIDTuple psid;
+ // Read PSID length / PSID value from buffer #0.
+ ASSERT_NO_THROW(psid = option->readPsid(0));
+
+ // The PSID comprises a PSID length and PSID value.
+ EXPECT_EQ(4, psid.first.asUnsigned());
+ EXPECT_EQ(0x08, psid.second.asUint16());
+
+ // Parsed option should have one suboption.
+ EXPECT_TRUE(hasV6Suboption(option.get()));
+}
// 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.
EXPECT_EQ("example.com.", domain1);
}
+// The purpose of this test is to verify that the option definition comprising
+// an array of IPv6 prefixes can be used to create an instance of OptionCustom.
+TEST_F(OptionCustomTest, prefixDataArray) {
+ OptionDefinition opt_def("option-foo", 1000, "ipv6-prefix", true);
+
+ // The following buffer comprises three prefixes with different
+ // prefix lengths.
+ const char data[] = {
+ 32, 0x30, 0x01, 0x00, 0x01, // 3001:1::/32
+ 16, 0x30, 0x00, // 3000::/16
+ 48, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01 // 2001:db8:1::/48
+ };
+
+ // Initialize input buffer
+ OptionBuffer buf(data,
+ data + static_cast<size_t>(sizeof(data) / sizeof(char)));
+
+ // Create custom option using input buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields with 3 prefixes.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ PrefixTuple prefix0(ZERO_PREFIX_TUPLE);
+ PrefixTuple prefix1(ZERO_PREFIX_TUPLE);
+ PrefixTuple prefix2(ZERO_PREFIX_TUPLE);
+
+ ASSERT_NO_THROW(prefix0 = option->readPrefix(0));
+ ASSERT_NO_THROW(prefix1 = option->readPrefix(1));
+ ASSERT_NO_THROW(prefix2 = option->readPrefix(2));
+
+ EXPECT_EQ(32, prefix0.first.asUnsigned());
+ EXPECT_EQ("3001:1::", prefix0.second.toText());
+
+ EXPECT_EQ(16, prefix1.first.asUnsigned());
+ EXPECT_EQ("3000::", prefix1.second.toText());
+
+ EXPECT_EQ(48, prefix2.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", prefix2.second.toText());
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of PSIDs can be used to create an instance of OptionCustom.
+TEST_F(OptionCustomTest, psidDataArray) {
+ OptionDefinition opt_def("option-foo", 1000, "psid", true);
+
+ // The following buffer comprises three PSIDs.
+ const char data[] = {
+ 4, 0x80, 0x00, // PSID len = 4, PSID = '1000 000000000000b'
+ 6, 0xD4, 0x00, // PSID len = 6, PSID = '110101 0000000000b'
+ 1, 0x80, 0x00 // PSID len = 1, PSID = '1 000000000000000b'
+ };
+ // Initialize input buffer.
+ OptionBuffer buf(data,
+ data + static_cast<size_t>(sizeof(data) / sizeof(char)));
+
+ // Create custom option using input buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields with 3 PSIDs.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ PSIDTuple psid0;
+ PSIDTuple psid1;
+ PSIDTuple psid2;
+
+ ASSERT_NO_THROW(psid0 = option->readPsid(0));
+ ASSERT_NO_THROW(psid1 = option->readPsid(1));
+ ASSERT_NO_THROW(psid2 = option->readPsid(2));
+
+ // PSID value is equal to '1000b' (8).
+ EXPECT_EQ(4, psid0.first.asUnsigned());
+ EXPECT_EQ(0x08, psid0.second.asUint16());
+
+ // PSID value is equal to '110101b' (0x35)
+ EXPECT_EQ(6, psid1.first.asUnsigned());
+ EXPECT_EQ(0x35, psid1.second.asUint16());
+
+ // PSID value is equal to '1b' (1).
+ EXPECT_EQ(1, psid2.first.asUnsigned());
+ EXPECT_EQ(0x01, psid2.second.asUint16());
+}
+
// The purpose of this test is to verify that the opton definition comprising
// a record of fixed-size fields can be used to create an option with a
// suboption.
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("string"));
const char fqdn_data[] = {
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 field 5 to string value.
writeString("ABCD", buf);
ASSERT_TRUE(option);
// We should have 6 data fields.
- ASSERT_EQ(6, option->getDataFieldsNum());
+ ASSERT_EQ(7, option->getDataFieldsNum());
// Verify value in the field 0.
uint16_t value0 = 0;
EXPECT_EQ("2001:db8:1::1", value4.toText());
// Verify value in the field 5.
- std::string value5;
- ASSERT_NO_THROW(value5 = option->readString(5));
- EXPECT_EQ("ABCD", value5);
+ 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.
+ std::string value6;
+ ASSERT_NO_THROW(value6 = option->readString(6));
+ EXPECT_EQ("ABCD", value6);
}
// The purpose of this test is to verify that truncated buffer
EXPECT_EQ("2001:db8:1::1", address.toText());
}
+// The purpose of this test is to verify that an option comprising
+// a prefix can be created and that the prefix can be overriden by
+// a new value.
+TEST_F(OptionCustomTest, setPrefixData) {
+ OptionDefinition opt_def("option-foo", 1000, "ipv6-prefix");
+
+ // 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);
+
+ // Make sure the default prefix is set.
+ PrefixTuple prefix(ZERO_PREFIX_TUPLE);
+ ASSERT_NO_THROW(prefix = option->readPrefix());
+ EXPECT_EQ(0, prefix.first.asUnsigned());
+ EXPECT_EQ("::", prefix.second.toText());
+
+ // Write prefix.
+ ASSERT_NO_THROW(option->writePrefix(PrefixLen(48), IOAddress("2001:db8:1::")));
+
+ // Read prefix back and make sure it is the one we just set.
+ ASSERT_NO_THROW(prefix = option->readPrefix());
+ EXPECT_EQ(48, prefix.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", prefix.second.toText());
+}
+
+// The purpose of this test is to verify that an option comprising
+// a single PSID can be created and that the PSID can be overriden
+// by a new value.
+TEST_F(OptionCustomTest, setPsidData) {
+ OptionDefinition opt_def("option-foo", 1000, "psid");
+
+ // 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);
+
+ // Make sure the default PSID is set.
+ PSIDTuple psid;
+ ASSERT_NO_THROW(psid = option->readPsid());
+ EXPECT_EQ(0, psid.first.asUnsigned());
+ EXPECT_EQ(0, psid.second.asUint16());
+
+ // Write PSID.
+ ASSERT_NO_THROW(option->writePsid(PSIDLen(4), PSID(8)));
+
+ // Read PSID back and make sure it is the one we just set.
+ ASSERT_NO_THROW(psid = option->readPsid());
+ EXPECT_EQ(4, psid.first.asUnsigned());
+ EXPECT_EQ(8, psid.second.asUint16());
+}
+
// 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
);
}
+/// The purpose of this test is to verify that an option comprising an
+/// array of PSIDs can be created with no PSIDs and that PSIDs can be
+/// later added after the option has been created.
+TEST_F(OptionCustomTest, setPSIDPrefixArray) {
+ OptionDefinition opt_def("option-foo", 1000, "psid", true);
+
+ // 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);
+
+ // Initially, the array does not contain any data fields.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 new PSIDs
+ ASSERT_NO_THROW(option->addArrayDataField(PSIDLen(4), PSID(1)));
+ ASSERT_NO_THROW(option->addArrayDataField(PSIDLen(0), PSID(123)));
+ ASSERT_NO_THROW(option->addArrayDataField(PSIDLen(1), PSID(1)));
+
+ // Verify the stored values.
+ ASSERT_NO_THROW({
+ PSIDTuple psid0 = option->readPsid(0);
+ EXPECT_EQ(4, psid0.first.asUnsigned());
+ EXPECT_EQ(1, psid0.second.asUint16());
+ });
+
+ ASSERT_NO_THROW({
+ PSIDTuple psid1 = option->readPsid(1);
+ EXPECT_EQ(0, psid1.first.asUnsigned());
+ EXPECT_EQ(0, psid1.second.asUint16());
+ });
+
+ ASSERT_NO_THROW({
+ PSIDTuple psid2 = option->readPsid(2);
+ EXPECT_EQ(1, psid2.first.asUnsigned());
+ EXPECT_EQ(1, psid2.second.asUint16());
+ });
+}
+
+/// The purpose of this test is to verify that an option comprising an
+/// array of IPv6 prefixes can be created with no prefixes and that
+/// prefixes can be later added after the option has been created.
+TEST_F(OptionCustomTest, setIPv6PrefixDataArray) {
+ OptionDefinition opt_def("option-foo", 1000, "ipv6-prefix", true);
+
+ // 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);
+
+ // Initially, the array does not contain any data fields.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 new IPv6 prefixes into the array.
+ ASSERT_NO_THROW(option->addArrayDataField(PrefixLen(64),
+ IOAddress("2001:db8:1::")));
+ ASSERT_NO_THROW(option->addArrayDataField(PrefixLen(32),
+ IOAddress("3001:1::")));
+ ASSERT_NO_THROW(option->addArrayDataField(PrefixLen(16),
+ IOAddress("3000::")));
+
+ // We should have now 3 addresses added.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Verify the stored values.
+ ASSERT_NO_THROW({
+ PrefixTuple prefix0 = option->readPrefix(0);
+ EXPECT_EQ(64, prefix0.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", prefix0.second.toText());
+ });
+
+ ASSERT_NO_THROW({
+ PrefixTuple prefix1 = option->readPrefix(1);
+ EXPECT_EQ(32, prefix1.first.asUnsigned());
+ EXPECT_EQ("3001:1::", prefix1.second.toText());
+ });
+
+ ASSERT_NO_THROW({
+ PrefixTuple prefix2 = option->readPrefix(2);
+ EXPECT_EQ(16, prefix2.first.asUnsigned());
+ EXPECT_EQ("3000::", prefix2.second.toText());
+ });
+}
+
TEST_F(OptionCustomTest, setRecordData) {
OptionDefinition opt_def("OPTION_FOO", 1000, "record");
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("string"));
// Create an option and let the data field be initialized
// The number of elements should be equal to number of elements
// in the record.
- ASSERT_EQ(6, option->getDataFieldsNum());
+ ASSERT_EQ(8, option->getDataFieldsNum());
// Check that the default values have been correctly set.
uint16_t value0;
IOAddress value4("2001:db8:1::1");
ASSERT_NO_THROW(value4 = option->readAddress(4));
EXPECT_EQ("::", value4.toText());
- std::string value5 = "xyz";
- ASSERT_NO_THROW(value5 = option->readString(5));
- EXPECT_TRUE(value5.empty());
+ 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 = "xyz";
+ ASSERT_NO_THROW(value7 = option->readString(7));
+ EXPECT_TRUE(value7.empty());
// Override each value with a new value.
ASSERT_NO_THROW(option->writeInteger<uint16_t>(1234, 0));
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->writeString("hello world", 5));
+ 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->writeString("hello world", 7));
// Check that the new values have been correctly set.
ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
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->readString(5));
- EXPECT_EQ(value5, "hello world");
+ 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->readString(7));
+ EXPECT_EQ(value7, "hello world");
}
// The purpose of this test is to verify that pack function for
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
#include <config.h>
#include <dhcp/option_data_types.h>
#include <gtest/gtest.h>
+#include <utility>
using namespace isc;
+using namespace isc::asiolink;
using namespace isc::dhcp;
namespace {
+/// @brief Default (zero) prefix tuple.
+const PrefixTuple
+ZERO_PREFIX_TUPLE(std::make_pair(PrefixLen(0),
+ IOAddress(IOAddress::IPV6_ZERO_ADDRESS())));
+
/// @brief Test class for option data type utilities.
class OptionDataTypesTest : public ::testing::Test {
public:
);
}
+// The purpose of this test is to verify that the variable length prefix
+// can be read from a buffer correctly.
+TEST_F(OptionDataTypesTest, readPrefix) {
+ std::vector<uint8_t> buf;
+
+ // Prefix 2001:db8::/64
+ writeInt<uint8_t>(64, buf);
+ writeInt<uint32_t>(0x20010db8, buf);
+ writeInt<uint32_t>(0, buf);
+
+ PrefixTuple prefix(ZERO_PREFIX_TUPLE);
+ ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf));
+ EXPECT_EQ(64, prefix.first.asUnsigned());
+ EXPECT_EQ("2001:db8::", prefix.second.toText());
+
+ buf.clear();
+
+ // Prefix 2001:db8::/63
+ writeInt<uint8_t>(63, buf);
+ writeInt<uint32_t>(0x20010db8, buf);
+ writeInt<uint32_t>(0, buf);
+
+ ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf));
+ EXPECT_EQ(63, prefix.first.asUnsigned());
+ EXPECT_EQ("2001:db8::", prefix.second.toText());
+
+ buf.clear();
+
+ // Prefix 2001:db8:c0000. Note that the last four bytes are filled with
+ // 0xFF (all bits set). When the prefix is read those non-significant
+ // bits (beyond prefix length) should be ignored (read as 0). Only first
+ // two bits of 0xFFFFFFFF should be read, thus 0xC000, rather than 0xFFFF.
+ writeInt<uint8_t>(34, buf);
+ writeInt<uint32_t>(0x20010db8, buf);
+ writeInt<uint32_t>(0xFFFFFFFF, buf);
+
+ ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf));
+ EXPECT_EQ(34, prefix.first.asUnsigned());
+ EXPECT_EQ("2001:db8:c000::", prefix.second.toText());
+
+ buf.clear();
+
+ // Prefix having a length of 0.
+ writeInt<uint8_t>(0, buf);
+ writeInt<uint16_t>(0x2001, buf);
+
+ ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf));
+ EXPECT_EQ(0, prefix.first.asUnsigned());
+ EXPECT_EQ("::", prefix.second.toText());
+
+ buf.clear();
+
+ // Prefix having a maximum length of 128.
+ writeInt<uint8_t>(128, buf);
+ buf.insert(buf.end(), 16, 0x11);
+
+ ASSERT_NO_THROW(prefix = OptionDataTypeUtil::readPrefix(buf));
+ EXPECT_EQ(128, prefix.first.asUnsigned());
+ EXPECT_EQ("1111:1111:1111:1111:1111:1111:1111:1111",
+ prefix.second.toText());
+
+ buf.clear();
+
+ // Prefix length is greater than 128. This should result in an
+ // error.
+ writeInt<uint8_t>(129, buf);
+ writeInt<uint16_t>(0x3000, buf);
+ buf.resize(17);
+
+ EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPrefix(buf)),
+ BadDataTypeCast);
+
+ buf.clear();
+
+ // Buffer truncated. Prefix length of 10 requires at least 2 bytes,
+ // but there is only one byte.
+ writeInt<uint8_t>(10, buf);
+ writeInt<uint8_t>(1, buf);
+
+ EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPrefix(buf)),
+ BadDataTypeCast);
+}
+
+// The purpose of this test is to verify that the variable length prefix
+// is written to a buffer correctly.
+TEST_F(OptionDataTypesTest, writePrefix) {
+ // Initialize a buffer and store some value in it. We'll want to make
+ // sure that the prefix being written will not override this value, but
+ // will rather be appended.
+ std::vector<uint8_t> buf(1, 1);
+
+ // Prefix 2001:db8:FFFF::/34 is equal to 2001:db8:C000::/34 because
+ // there are only 34 significant bits. All other bits must be zeroed.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(34),
+ IOAddress("2001:db8:FFFF::"),
+ buf));
+ ASSERT_EQ(7, buf.size());
+
+ EXPECT_EQ(1, static_cast<unsigned>(buf[0]));
+ EXPECT_EQ(34, static_cast<unsigned>(buf[1]));
+ EXPECT_EQ(0x20, static_cast<unsigned>(buf[2]));
+ EXPECT_EQ(0x01, static_cast<unsigned>(buf[3]));
+ EXPECT_EQ(0x0D, static_cast<unsigned>(buf[4]));
+ EXPECT_EQ(0xB8, static_cast<unsigned>(buf[5]));
+ EXPECT_EQ(0xC0, static_cast<unsigned>(buf[6]));
+
+ buf.clear();
+
+ // Prefix length is 0. The entire prefix should be ignored.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(0),
+ IOAddress("2001:db8:FFFF::"),
+ buf));
+ ASSERT_EQ(1, buf.size());
+ EXPECT_EQ(0, static_cast<unsigned>(buf[0]));
+
+ buf.clear();
+
+ // Prefix having a maximum length of 128.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(128),
+ IOAddress("2001:db8::FF"),
+ buf));
+
+ // We should now have a 17 bytes long buffer. 1 byte goes for a prefix
+ // length field, the remaining ones hold the prefix.
+ ASSERT_EQ(17, buf.size());
+ // Because the prefix is 16 bytes long, we can simply use the
+ // IOAddress convenience function to read it back and compare
+ // it with the textual representation. This is simpler than
+ // comparing each byte separately.
+ IOAddress prefix_read = IOAddress::fromBytes(AF_INET6, &buf[1]);
+ EXPECT_EQ("2001:db8::ff", prefix_read.toText());
+
+ buf.clear();
+
+ // It is illegal to use IPv4 address as prefix.
+ EXPECT_THROW(OptionDataTypeUtil::writePrefix(PrefixLen(4),
+ IOAddress("10.0.0.1"), buf),
+ BadDataTypeCast);
+}
+
+// The purpose of this test is to verify that the
+// PSID-len/PSID tuple can be read from a buffer.
+TEST_F(OptionDataTypesTest, readPsid) {
+ std::vector<uint8_t> buf;
+
+ // PSID length is 6 (bits)
+ writeInt<uint8_t>(6, buf);
+ // 0xA400 is represented as 1010010000000000b, which is equivalent
+ // of portset 0x29 (101001b).
+ writeInt<uint16_t>(0xA400, buf);
+
+ PSIDTuple psid;
+ ASSERT_NO_THROW(psid = OptionDataTypeUtil::readPsid(buf));
+ EXPECT_EQ(6, psid.first.asUnsigned());
+ EXPECT_EQ(0x29, psid.second.asUint16());
+
+ buf.clear();
+
+ // PSID length is 0, in which case PSID should be ignored.
+ writeInt<uint8_t>(0, buf);
+ // Let's put some junk into the PSID field to make sure it will
+ // be ignored.
+ writeInt<uint16_t>(0x1234, buf);
+ ASSERT_NO_THROW(psid = OptionDataTypeUtil::readPsid(buf));
+ EXPECT_EQ(0, psid.first.asUnsigned());
+ EXPECT_EQ(0, psid.second.asUint16());
+
+ buf.clear();
+
+ // PSID length greater than 16 is not allowed.
+ writeInt<uint8_t>(17, buf);
+ writeInt<uint16_t>(0, buf);
+ EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)),
+ BadDataTypeCast);
+
+ buf.clear();
+
+ // PSID length is 3 bits, but the PSID value is 11 (1011b), so it
+ // is encoded on 4 bits, rather than 3.
+ writeInt<uint8_t>(3, buf);
+ writeInt<uint16_t>(0xB000, buf);
+ EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)),
+ BadDataTypeCast);
+
+ buf.clear();
+
+ // Buffer is truncated - 2 bytes instead of 3.
+ writeInt<uint8_t>(4, buf);
+ writeInt<uint8_t>(0xF0, buf);
+ EXPECT_THROW(static_cast<void>(OptionDataTypeUtil::readPsid(buf)),
+ BadDataTypeCast);
+}
+
+// The purpose of this test is to verify that the PSID-len/PSID
+// tuple is written to a buffer correctly.
+TEST_F(OptionDataTypesTest, writePsid) {
+ // Let's create a buffer with some data in it. We want to make
+ // sure that the existing data remain untouched when we write
+ // PSID to the buffer.
+ std::vector<uint8_t> buf(1, 1);
+ // PSID length is 4 (bits), PSID value is 8.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(4), PSID(8), buf));
+ ASSERT_EQ(4, buf.size());
+ // The byte which existed in the buffer should still hold the
+ // same value.
+ EXPECT_EQ(1, static_cast<unsigned>(buf[0]));
+ // PSID length should be written as specified in the function call.
+ EXPECT_EQ(4, static_cast<unsigned>(buf[1]));
+ // The PSID structure is as follows:
+ // UUUUPPPPPPPPPPPP, where "U" are useful bits on which we code
+ // the PSID. "P" are zero padded bits. The PSID value 8 is coded
+ // on four useful bits as '1000b'. That means that the PSID value
+ // encoded in the PSID field is: '1000000000000000b', which is
+ // 0x8000. The next two EXPECT_EQ statements verify that.
+ EXPECT_EQ(0x80, static_cast<unsigned>(buf[2]));
+ EXPECT_EQ(0x00, static_cast<unsigned>(buf[3]));
+
+ // Clear the buffer to make sure we don't append to the
+ // existing data.
+ buf.clear();
+
+ // The PSID length of 0 causes the PSID value (of 6) to be ignored.
+ // As a result, the buffer should hold only zeros.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(0), PSID(6), buf));
+ ASSERT_EQ(3, buf.size());
+ EXPECT_EQ(0, static_cast<unsigned>(buf[0]));
+ EXPECT_EQ(0, static_cast<unsigned>(buf[1]));
+ EXPECT_EQ(0, static_cast<unsigned>(buf[2]));
+
+ buf.clear();
+
+ // Another test case, to verify that we can use the maximum length
+ // of PSID (16 bits).
+ ASSERT_NO_THROW(OptionDataTypeUtil::writePsid(PSIDLen(16), PSID(5), buf));
+ ASSERT_EQ(3, buf.size());
+ // PSID length should be written with no change.
+ EXPECT_EQ(16, static_cast<unsigned>(buf[0]));
+ // Check PSID value.
+ EXPECT_EQ(0x00, static_cast<unsigned>(buf[1]));
+ EXPECT_EQ(0x05, static_cast<unsigned>(buf[2]));
+
+ // PSID length of 17 exceeds the maximum allowed value of 16.
+ EXPECT_THROW(OptionDataTypeUtil::writePsid(PSIDLen(17), PSID(1), buf),
+ OutOfRange);
+
+ // PSID length is 1, which allows for coding up to two (2^1)
+ // port sets. These are namely port set 0 and port set 1. The
+ // value of 2 is out of that range.
+ EXPECT_THROW(OptionDataTypeUtil::writePsid(PSIDLen(1), PSID(2), buf),
+ BadDataTypeCast);
+}
+
// The purpose of this test is to verify that the string
// can be read from a buffer correctly.
TEST_F(OptionDataTypesTest, readString) {
EXPECT_FALSE(opt_def_invalid.haveClientFqdnFormat());
}
+// This test verifies that a definition of an option with a single IPv6
+// prefix can be created and used to create an instance of the option.
+TEST_F(OptionDefinitionTest, prefix) {
+ OptionDefinition opt_def("option-prefix", 1000, "ipv6-prefix");
+
+ // Create a buffer holding a prefix.
+ OptionBuffer buf;
+ buf.push_back(32);
+ buf.push_back(0x30);
+ buf.push_back(0x00);
+ buf.resize(5);
+
+ OptionPtr option_v6;
+
+ // Create an instance of this option from the definition.
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, 1000, buf);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast_v6 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+ ASSERT_EQ(1, option_cast_v6->getDataFieldsNum());
+ PrefixTuple prefix = option_cast_v6->readPrefix();
+ EXPECT_EQ(32, prefix.first.asUnsigned());
+ EXPECT_EQ("3000::", prefix.second.toText());
+}
+
+// This test verifies that a definition of an option with a single IPv6
+// prefix can be created and that the instance of this option can be
+// created by specifying the prefix in the textual format.
+TEST_F(OptionDefinitionTest, prefixTokenized) {
+ OptionDefinition opt_def("option-prefix", 1000, "ipv6-prefix");
+
+ OptionPtr option_v6;
+ // Specify a single prefix.
+ std::vector<std::string> values(1, "2001:db8:1::/64");
+
+ // Create an instance of the option using the definition.
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, 1000, values);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast_v6 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+ ASSERT_EQ(1, option_cast_v6->getDataFieldsNum());
+ PrefixTuple prefix = option_cast_v6->readPrefix();
+ EXPECT_EQ(64, prefix.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", prefix.second.toText());
+}
+
+// This test verifies that a definition of an option with an array
+// of IPv6 prefixes can be created and that the instance of this
+// option can be created by specifying multiple prefixes in the
+// textual format.
+TEST_F(OptionDefinitionTest, prefixArrayTokenized) {
+ OptionDefinition opt_def("option-prefix", 1000, "ipv6-prefix", true);
+
+ OptionPtr option_v6;
+
+ // Specify 3 prefixes
+ std::vector<std::string> values;
+ values.push_back("2001:db8:1:: /64");
+ values.push_back("3000::/ 32");
+ values.push_back("3001:1:: / 48");
+
+ // Create an instance of an option using the definition.
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, 1000, values);
+ );
+
+ // Make sure that the option class returned is correct.
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ OptionCustomPtr option_cast_v6 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+
+ // There should be 3 prefixes in this option.
+ ASSERT_EQ(3, option_cast_v6->getDataFieldsNum());
+
+ ASSERT_NO_THROW({
+ PrefixTuple prefix0 = option_cast_v6->readPrefix(0);
+ EXPECT_EQ(64, prefix0.first.asUnsigned());
+ EXPECT_EQ("2001:db8:1::", prefix0.second.toText());
+ });
+
+ ASSERT_NO_THROW({
+ PrefixTuple prefix1 = option_cast_v6->readPrefix(1);
+ EXPECT_EQ(32, prefix1.first.asUnsigned());
+ EXPECT_EQ("3000::", prefix1.second.toText());
+ });
+
+ ASSERT_NO_THROW({
+ PrefixTuple prefix2 = option_cast_v6->readPrefix(2);
+ EXPECT_EQ(48, prefix2.first.asUnsigned());
+ EXPECT_EQ("3001:1::", prefix2.second.toText());
+ });
+}
+
+// This test verifies that a definition of an option with a single PSID
+// value can be created and used to create an instance of the option.
+TEST_F(OptionDefinitionTest, psid) {
+ OptionDefinition opt_def("option-psid", 1000, "psid");
+
+ OptionPtr option_v6;
+
+ // Create a buffer holding PSID.
+ OptionBuffer buf;
+ buf.push_back(6);
+ buf.push_back(0x4);
+ buf.push_back(0x0);
+
+ // Create an instance of this option from the definition.
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, 1000, buf);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast_v6 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+ ASSERT_EQ(1, option_cast_v6->getDataFieldsNum());
+ PSIDTuple psid = option_cast_v6->readPsid();
+ EXPECT_EQ(6, psid.first.asUnsigned());
+ EXPECT_EQ(1, psid.second.asUint16());
+}
+
+// This test verifies that a definition of an option with a single PSID
+// value can be created and that the instance of this option can be
+// created by specifying PSID length and value in the textual format.
+TEST_F(OptionDefinitionTest, psidTokenized) {
+ OptionDefinition opt_def("option-psid", 1000, "psid");
+
+ OptionPtr option_v6;
+ // Specify a single PSID with a length of 6 and value of 3.
+ std::vector<std::string> values(1, "3 / 6");
+
+ // Create an instance of the option using the definition.
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, 1000, values);
+ );
+
+ // Make sure that the returned option class is correct.
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ // Validate the value.
+ OptionCustomPtr option_cast_v6 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+ ASSERT_EQ(1, option_cast_v6->getDataFieldsNum());
+ PSIDTuple psid = option_cast_v6->readPsid();
+ EXPECT_EQ(6, psid.first.asUnsigned());
+ EXPECT_EQ(3, psid.second.asUint16());
+}
+
+// This test verifies that a definition of an option with an array
+// of PSIDs can be created and that the instance of this option can be
+// created by specifying multiple PSIDs in the textual format.
+TEST_F(OptionDefinitionTest, psidArrayTokenized) {
+ OptionDefinition opt_def("option-psid", 1000, "psid", true);
+
+ OptionPtr option_v6;
+
+ // Specify 3 PSIDs.
+ std::vector<std::string> values;
+ values.push_back("3 / 6");
+ values.push_back("0/1");
+ values.push_back("7 / 3");
+
+ // Create an instance of an option using the definition.
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, 1000, values);
+ );
+
+ // Make sure that the option class returned is correct.
+ const Option* optptr = option_v6.get();
+ ASSERT_TRUE(optptr);
+ ASSERT_TRUE(typeid(*optptr) == typeid(OptionCustom));
+
+ OptionCustomPtr option_cast_v6 =
+ boost::dynamic_pointer_cast<OptionCustom>(option_v6);
+
+ // There should be 3 PSIDs in this option.
+ ASSERT_EQ(3, option_cast_v6->getDataFieldsNum());
+
+ // Check their values.
+ PSIDTuple psid0;
+ PSIDTuple psid1;
+ PSIDTuple psid2;
+
+ psid0 = option_cast_v6->readPsid(0);
+ EXPECT_EQ(6, psid0.first.asUnsigned());
+ EXPECT_EQ(3, psid0.second.asUint16());
+
+ psid1 = option_cast_v6->readPsid(1);
+ EXPECT_EQ(1, psid1.first.asUnsigned());
+ EXPECT_EQ(0, psid1.second.asUint16());
+
+ psid2 = option_cast_v6->readPsid(2);
+ EXPECT_EQ(3, psid2.first.asUnsigned());
+ EXPECT_EQ(7, psid2.second.asUint16());
+}
+
} // anonymous namespace