]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[github24] Support for options comprising PSIDs and IPv6 prefixes.
authorMarcin Siodelski <marcin@isc.org>
Mon, 26 Sep 2016 14:29:05 +0000 (16:29 +0200)
committerMarcin Siodelski <marcin@isc.org>
Mon, 26 Sep 2016 14:29:05 +0000 (16:29 +0200)
src/lib/dhcp/option_custom.cc
src/lib/dhcp/option_custom.h
src/lib/dhcp/option_data_types.cc
src/lib/dhcp/option_data_types.h
src/lib/dhcp/option_definition.cc
src/lib/dhcp/option_definition.h
src/lib/dhcp/tests/option_custom_unittest.cc
src/lib/dhcp/tests/option_data_types_unittest.cc
src/lib/dhcp/tests/option_definition_unittest.cc

index d966bb9b3875372558029839c12e8c2c039da8c4..00ee2fba776106686cb7329a721f2aa68c848451 100644 (file)
@@ -10,6 +10,8 @@
 #include <dhcp/option_custom.h>
 #include <util/encode/hex.h>
 
+using namespace isc::asiolink;
+
 namespace isc {
 namespace dhcp {
 
@@ -46,7 +48,7 @@ OptionCustom::clone() const {
 }
 
 void
-OptionCustom::addArrayDataField(const asiolink::IOAddress& address) {
+OptionCustom::addArrayDataField(const IOAddress& address) {
     checkArrayType();
 
     if ((address.isV4() && definition_.getType() != OPT_IPV4_ADDRESS_TYPE) ||
@@ -71,6 +73,36 @@ OptionCustom::addArrayDataField(const bool value) {
     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()) {
@@ -110,10 +142,17 @@ OptionCustom::createBuffers() {
             // 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
@@ -135,9 +174,15 @@ OptionCustom::createBuffers() {
         // 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.
@@ -191,7 +236,7 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
                     // 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
@@ -199,19 +244,28 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
                     // 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.
@@ -253,6 +307,13 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
                     // 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
@@ -284,11 +345,17 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
                     // 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 {
@@ -383,7 +450,7 @@ OptionCustom::pack(isc::util::OutputBuffer& buf) const {
 }
 
 
-asiolink::IOAddress
+IOAddress
 OptionCustom::readAddress(const uint32_t index) const {
     checkIndex(index);
 
@@ -402,10 +469,8 @@ OptionCustom::readAddress(const uint32_t index) const {
 }
 
 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) ||
@@ -471,6 +536,45 @@ OptionCustom::writeFqdn(const std::string& fqdn, const uint32_t index) {
     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);
index ae8009f9c14d7cc24945f7554b10da346db2e4fd..122da7c9b9f829880c3b0c72609e8679b808ea9f 100644 (file)
@@ -7,6 +7,7 @@
 #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>
@@ -112,6 +113,19 @@ public:
         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.
@@ -228,6 +242,45 @@ public:
         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.
index 8a8fdf622d5b22e44522231ea2c39cbe17dd0df1..55a0145c67d251d5f87088a1d07bc181d03f0501 100644 (file)
@@ -1,4 +1,4 @@
-// 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
@@ -8,6 +8,9 @@
 #include <dns/labelsequence.h>
 #include <dns/name.h>
 #include <util/encode/hex.h>
+#include <algorithm>
+
+using namespace isc::asiolink;
 
 namespace isc {
 namespace dhcp {
@@ -24,6 +27,8 @@ OptionDataTypeUtil::OptionDataTypeUtil() {
     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;
@@ -39,6 +44,8 @@ OptionDataTypeUtil::OptionDataTypeUtil() {
     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";
@@ -86,6 +93,9 @@ OptionDataTypeUtil::getDataTypeLen(const OptionDataType data_type) {
     case OPT_IPV6_ADDRESS_TYPE:
         return (asiolink::V6ADDRESS_LEN);
 
+    case OPT_PSID_TYPE:
+        return (3);
+
     default:
         ;
     }
@@ -237,6 +247,207 @@ OptionDataTypeUtil::getLabelCount(const std::string& text_name) {
     }
 }
 
+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;
index e7a5dc8ebd25309ab29de6cff0032be4b3aec6d3..3aa56080e3c12e67a344ac7de86d5c1d64ffe22e 100644 (file)
@@ -1,4 +1,4 @@
-// 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
@@ -13,6 +13,7 @@
 #include <util/io_utilities.h>
 
 #include <stdint.h>
+#include <utility>
 
 namespace isc {
 namespace dhcp {
@@ -53,12 +54,33 @@ enum OptionDataType {
     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
@@ -173,6 +195,123 @@ struct OptionDataTypeTraits<std::string> {
     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
@@ -221,7 +360,7 @@ public:
     ///
     /// @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.
@@ -378,6 +517,59 @@ public:
     /// @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.
index 22744b5f2836ef8be56674357f46ea9fec1f7c8e..30f1a4370ceb3bb159806114cadba0e961c315ed 100644 (file)
@@ -20,7 +20,6 @@
 #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>
@@ -28,6 +27,7 @@
 #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;
@@ -565,6 +565,87 @@ OptionDefinition::writeToBuffer(const std::string& value,
             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;
index a3b94e8ba7b81d86cd2ab19748d7c54993ed8f05..f495a0244e985378750b1bcc59da67419068e08e 100644 (file)
@@ -117,7 +117,9 @@ class OptionIntArray;
 /// - "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)
index bafb4e485bf651ccf961fd65913156b26890cc8a..7ee02edd3826265e78d85154b6e659caa9bd5f41 100644 (file)
@@ -18,6 +18,11 @@ 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 OptionCustomTest test class.
 class OptionCustomTest : public ::testing::Test {
 public:
@@ -476,6 +481,82 @@ TEST_F(OptionCustomTest, ipv6AddressData) {
     );
 }
 
+// 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.
@@ -768,6 +849,97 @@ TEST_F(OptionCustomTest, fqdnDataArray) {
     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.
@@ -821,6 +993,7 @@ TEST_F(OptionCustomTest, recordData) {
     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[] = {
@@ -841,6 +1014,9 @@ TEST_F(OptionCustomTest, recordData) {
     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);
 
@@ -851,7 +1027,7 @@ TEST_F(OptionCustomTest, recordData) {
     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;
@@ -879,9 +1055,15 @@ TEST_F(OptionCustomTest, recordData) {
     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
@@ -1073,6 +1255,64 @@ TEST_F(OptionCustomTest, setIpv6AddressData) {
     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
@@ -1280,6 +1520,96 @@ TEST_F(OptionCustomTest, setIpv6AddressDataArray) {
     );
 }
 
+/// 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");
 
@@ -1288,6 +1618,8 @@ TEST_F(OptionCustomTest, setRecordData) {
     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
@@ -1300,7 +1632,7 @@ TEST_F(OptionCustomTest, setRecordData) {
 
     // 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;
@@ -1318,9 +1650,17 @@ TEST_F(OptionCustomTest, setRecordData) {
     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));
@@ -1328,7 +1668,10 @@ TEST_F(OptionCustomTest, setRecordData) {
     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));
@@ -1341,8 +1684,14 @@ TEST_F(OptionCustomTest, setRecordData) {
     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
index fae109272c93df4e204c71faea25cbe9db35dd9a..fae60eb82a23b4692b8b7043e71929773bd48aa5 100644 (file)
@@ -1,4 +1,4 @@
-// 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
@@ -7,12 +7,19 @@
 #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:
@@ -456,6 +463,258 @@ TEST_F(OptionDataTypesTest, writeFqdn) {
     );
 }
 
+// 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) {
index ea52813c8a21313c2bc4e2a2c62f5d1fe01abd4e..fbb67a9a3e9950285efda9556ee10df8c60c140d 100644 (file)
@@ -1289,4 +1289,225 @@ TEST_F(OptionDefinitionTest, haveClientFqdnFormat) {
     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