]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[github24] Completed implementation of the Prefix Exclude option.
authorMarcin Siodelski <marcin@isc.org>
Wed, 27 Jul 2016 21:31:29 +0000 (23:31 +0200)
committerMarcin Siodelski <marcin@isc.org>
Wed, 27 Jul 2016 21:31:29 +0000 (23:31 +0200)
src/lib/dhcp/option6_pdexclude.cc
src/lib/dhcp/option6_pdexclude.h
src/lib/dhcp/tests/option6_pdexclude_unittest.cc

index 6e90e8e4c03da605b67b27f39bab7a487257f00b..734205d1401119994a65854fd26e35e849387d5e 100644 (file)
@@ -9,15 +9,14 @@
 #include <config.h>
 
 #include <asiolink/io_address.h>
+#include <exceptions/exceptions.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/option6_pdexclude.h>
 #include <exceptions/exceptions.h>
+#include <util/io_utilities.h>
 
 #include <boost/dynamic_bitset.hpp>
-
-#include <sstream>
-
-#include <arpa/inet.h>
+#include <iostream>
 #include <stdint.h>
 
 using namespace std;
@@ -29,66 +28,201 @@ using namespace isc::util;
 namespace isc {
 namespace dhcp {
 
-Option6PDExclude::Option6PDExclude(
-        const isc::asiolink::IOAddress& delegated_address,
-        uint8_t delegated_prefix_length,
-        const isc::asiolink::IOAddress& excluded_address,
-        uint8_t excluded_prefix_length) :
-    Option(V6, D6O_PD_EXCLUDE), delegated_address_(delegated_address),
-    delegated_prefix_length_(delegated_prefix_length),
-    excluded_address_(excluded_address),
-    excluded_prefix_length_(excluded_prefix_length) {
+Option6PDExclude::Option6PDExclude(const isc::asiolink::IOAddress& delegated_prefix,
+                                   const uint8_t delegated_prefix_length,
+                                   const isc::asiolink::IOAddress& excluded_prefix,
+                                   const uint8_t excluded_prefix_length)
+    : Option(V6, D6O_PD_EXCLUDE),
+      delegated_prefix_(delegated_prefix),
+      delegated_prefix_length_(delegated_prefix_length),
+      excluded_prefix_(excluded_prefix),
+      excluded_prefix_length_(excluded_prefix_length) {
+
+    // Expecting v6 prefixes of sane length.
+    if (!delegated_prefix_.isV6() || !excluded_prefix_.isV6() ||
+        (delegated_prefix_length_ > 128) || (excluded_prefix_length_ > 128)) {
+        isc_throw(BadValue, "invalid delegated or excluded prefix values specified: "
+                  << delegated_prefix_ << "/"
+                  << static_cast<int>(delegated_prefix_length_) << ", "
+                  << excluded_prefix_ << "/"
+                  << static_cast<int>(excluded_prefix_length_));
+    }
+
+    // Excluded prefix must be longer than the delegated prefix.
+    if (excluded_prefix_length_ <= delegated_prefix_length_) {
+        isc_throw(BadValue, "length of the excluded prefix "
+                  << excluded_prefix_ << "/"
+                  << static_cast<int>(excluded_prefix_length_)
+                  << " must be greater than the length of the"
+                  " delegated prefix " << delegated_prefix_ << "/"
+                  << static_cast<int>(delegated_prefix_length_));
+    }
+
+    // Both prefixes must share common part with a length equal to the
+    // delegated prefix length.
+    std::vector<uint8_t> delegated_prefix_bytes = delegated_prefix_.toBytes();
+    boost::dynamic_bitset<uint8_t> delegated_prefix_bits(delegated_prefix_bytes.rbegin(),
+                                                         delegated_prefix_bytes.rend());
+
+    std::vector<uint8_t> excluded_prefix_bytes = excluded_prefix_.toBytes();
+    boost::dynamic_bitset<uint8_t> excluded_prefix_bits(excluded_prefix_bytes.rbegin(),
+                                                        excluded_prefix_bytes.rend());
+
+
+    // See RFC6603, section 4.2: assert(p1>>s == p2>>s)
+    const uint8_t delta = 128 - delegated_prefix_length;
+
+    if ((delegated_prefix_bits >> delta) != (excluded_prefix_bits >> delta)) {
+        isc_throw(BadValue, "excluded prefix "
+                  << excluded_prefix_ << "/"
+                  << static_cast<int>(excluded_prefix_length_)
+                  << " must have the same common prefix part of "
+                  << static_cast<int>(delegated_prefix_length)
+                  << " as the delegated prefix "
+                  << delegated_prefix_ << "/"
+                  << static_cast<int>(delegated_prefix_length_));
+    }
+
+}
+
+Option6PDExclude::Option6PDExclude(const isc::asiolink::IOAddress& delegated_prefix,
+                                   const uint8_t delegated_prefix_length,
+                                   OptionBufferConstIter begin,
+                                   OptionBufferConstIter end)
+    : Option(V6, D6O_PD_EXCLUDE),
+      delegated_prefix_(delegated_prefix),
+      delegated_prefix_length_(delegated_prefix_length),
+      excluded_prefix_(IOAddress::IPV6_ZERO_ADDRESS()),
+      excluded_prefix_length_(0) {
+    unpack(begin, end);
 }
 
-void Option6PDExclude::pack(isc::util::OutputBuffer& buf) const {
+OptionPtr
+Option6PDExclude::clone() const {
+    return (cloneInternal<Option6PDExclude>());
+}
+
+void
+Option6PDExclude::pack(isc::util::OutputBuffer& buf) const {
     // Header = option code and length.
     packHeader(buf);
 
-    buf.writeData(&excluded_prefix_length_, sizeof(excluded_prefix_length_));
-
-    std::vector<uint8_t> excluded_address_bytes = excluded_address_.toBytes();
-    boost::dynamic_bitset<uint8_t> bits(excluded_address_bytes.rbegin(), excluded_address_bytes.rend());
-    bits = bits << delegated_prefix_length_;
+    // Excluded prefix length is always 1 byte long field.
+    buf.writeUint8(excluded_prefix_length_);
 
-    const uint8_t subtractedPrefixesOctetLength = getSubtractedPrefixesOctetLength();
-    for (uint8_t i = 0U; i < subtractedPrefixesOctetLength; i++) {
-        const boost::dynamic_bitset<uint8_t> tmp = bits >> 120;
+    // Retrieve entire prefix and convert it to bit representation.
+    std::vector<uint8_t> excluded_prefix_bytes = excluded_prefix_.toBytes();
+    boost::dynamic_bitset<uint8_t> bits(excluded_prefix_bytes.rbegin(),
+                                        excluded_prefix_bytes.rend());
 
-        uint8_t val = static_cast<uint8_t>(tmp.to_ulong());
+    // Shifting prefix by delegated prefix length leaves us with only a
+    // subnet id part of the excluded prefix.
+    bits = bits << delegated_prefix_length_;
 
-        //Zero padded bits follow when excluded_prefix_length_ is not divided exactly by 8
-        if (i == subtractedPrefixesOctetLength - 1U) {
-            uint8_t subtractedPrefixesBitLength = excluded_prefix_length_ -
-                    delegated_prefix_length_;
-            uint8_t zeroPaddingBitLength = (8 - (subtractedPrefixesBitLength % 8)) % 8;
-            val <<= zeroPaddingBitLength;
+    // Calculate subnet id length.
+    const uint8_t subnet_id_length = getSubnetIDLength();
+    for (uint8_t i = 0; i < subnet_id_length; ++i) {
+        // Retrieve bit representation of the current byte.
+        const boost::dynamic_bitset<uint8_t> first_byte = bits >> 120;
+        // Convert it to a numeric value.
+        uint8_t val = static_cast<uint8_t>(first_byte.to_ulong());
+
+        // Zero padded bits follow when excluded_prefix_length_ is not divisible by 8.
+        if (i == subnet_id_length - 1) {
+            uint8_t length_delta = excluded_prefix_length_ - delegated_prefix_length_;
+            uint8_t mask = 0xFF;
+            mask <<= (8 - (length_delta % 8));
+            val &= mask;
         }
-        bits = bits << 8;
-        buf.writeData(&val, sizeof(val));
+        // Store calculated value in a buffer.
+        buf.writeUint8(val);
+
+        // Go to the next byte.
+        bits <<= 8;
     }
 }
 
-void Option6PDExclude::unpack(OptionBufferConstIter begin,
-        OptionBufferConstIter end) {
-    delegated_prefix_length_ = 0;
-    excluded_prefix_length_ = *begin;
-    begin += sizeof(uint8_t);
-    delegated_address_ = IOAddress::IPV6_ZERO_ADDRESS();
-    excluded_address_ = IOAddress::IPV6_ZERO_ADDRESS();
+void
+Option6PDExclude::unpack(OptionBufferConstIter begin,
+                         OptionBufferConstIter end) {
+
+    // At this point we don't know the excluded prefix length, but the
+    // minimum requirement is that reminder of this option includes the
+    // excluded prefix length and at least 1 byte of the IPv6 subnet id.
+    if (std::distance(begin, end) < 2) {
+        isc_throw(BadValue, "truncated Prefix Exclude option");
+    }
+
+    // We can safely read the excluded prefix length and move forward.
+    excluded_prefix_length_ = *begin++;
+
+    // We parsed the excluded prefix length so we can now determine the
+    // size of the IPv6 subnet id. The reminder of the option should
+    // include data of that size. If the option size is lower than the
+    // subnet id length we report an error.
+    const unsigned int subnet_id_length = getSubnetIDLength();
+    if (subnet_id_length > std::distance(begin, end)) {
+        isc_throw(BadValue, "truncated Prefix Exclude option, expected "
+                  "IPv6 subnet id length is " << subnet_id_length);
+    }
+
+    // Get binary representation of the delegated prefix.
+    std::vector<uint8_t> delegated_prefix_bytes = delegated_prefix_.toBytes();
+    //  We need to calculate how many bytes include the useful data and assign
+    // zeros to remaining bytes (beyond the prefix length).
+    const uint8_t bytes_length = (delegated_prefix_length_ / 8) + 
+        static_cast<uint8_t>(delegated_prefix_length_ % 8 != 0);
+    std::fill(delegated_prefix_bytes.begin() + bytes_length,
+              delegated_prefix_bytes.end(), 0);
+
+    // Convert the delegated prefix to bit format.
+    boost::dynamic_bitset<uint8_t> bits(delegated_prefix_bytes.rbegin(),
+                                        delegated_prefix_bytes.rend());
+
+    // Convert subnet id to bit format.
+    std::vector<uint8_t> subnet_id_bytes(begin, end);
+    boost::dynamic_bitset<uint8_t> subnet_id_bits(subnet_id_bytes.rbegin(),
+                                                  subnet_id_bytes.rend());
+
+    // Subnet id parsed, proceed to the end of the option.
     begin = end;
+
+    // Concatenate the delegated prefix with subnet id. The resulting prefix
+    // is an excluded prefix in bit format.
+    for (int i = subnet_id_bits.size() - 1; i >= 0; --i) {
+        bits.set(128 - delegated_prefix_length_ - subnet_id_bits.size() + i,
+                 subnet_id_bits.test(i));
+    }
+
+    // Convert the prefix to binary format.
+    std::vector<uint8_t> bytes(V6ADDRESS_LEN);
+    boost::to_block_range(bits, bytes.rbegin());
+
+    // And create a prefix object from bytes.
+    excluded_prefix_ = IOAddress::fromBytes(AF_INET6, &bytes[0]);
+}
+
+uint16_t
+Option6PDExclude::len() const {
+    return (getHeaderLen() + sizeof(excluded_prefix_length_)
+            + getSubnetIDLength());
 }
 
-uint16_t Option6PDExclude::len() const {
-    return getHeaderLen() + sizeof(excluded_prefix_length_)
-            + getSubtractedPrefixesOctetLength();
+std::string
+Option6PDExclude::toText(int indent) const {
+    std::ostringstream s;
+    s << headerToText(indent) << ": ";
+    s << excluded_prefix_ << "/"
+      << static_cast<int>(excluded_prefix_length_);
+    return (s.str());
 }
 
-uint8_t Option6PDExclude::getSubtractedPrefixesOctetLength() const {
-    // Promote what is less than 8 bits to 1 octet.
-    uint8_t subtractedPrefixesBitLength = excluded_prefix_length_
-            - delegated_prefix_length_ - 1;
-    uint8_t subtractedPrefixesOctetLength = (subtractedPrefixesBitLength / 8) + 1;
-    return subtractedPrefixesOctetLength;
+uint8_t
+Option6PDExclude::getSubnetIDLength() const {
+    uint8_t subnet_id_length_bits = excluded_prefix_length_ -
+        delegated_prefix_length_ - 1;
+    uint8_t subnet_id_length = (subnet_id_length_bits / 8) + 1;
+    return (subnet_id_length);
 }
 
 } // end of namespace isc::dhcp
index b83a9450e7ae52ecc1a8513d67d4bd84f86a31ce..20d27d980bad569c2f1a2f3f2dfafecfae15f4db 100644 (file)
@@ -9,26 +9,52 @@
 #ifndef OPTION6_PDEXCLUDE_H
 #define OPTION6_PDEXCLUDE_H
 
-#include <dhcp/option.h>
-
 #include <asiolink/io_address.h>
+#include <dhcp/option.h>
 #include <boost/shared_ptr.hpp>
+#include <stdint.h>
 
 namespace isc {
 namespace dhcp {
 
-/// @brief DHCPv6 Option class for handling list of IPv6 addresses.
+/// @brief DHCPv6 option class representing Prefix Exclude Option (RFC 6603).
 ///
-/// This class handles a list of IPv6 addresses. An example of such option
-/// is dns-servers option. It can also be used to handle single address.
+/// This class represents DHCPv6 Prefix Exclude option (67). This option is
+/// carried in the IA Prefix option and it conveys a single prefix which is
+/// used by the delegating router to communicate with a requesting router on
+/// the requesting router's uplink. This prefix is not used on the
+/// requesting router's downlinks (is excluded from other delegated prefixes).
 class Option6PDExclude: public Option {
-
 public:
 
-    Option6PDExclude(const isc::asiolink::IOAddress& delegated_address,
-            uint8_t delegated_prefix_length,
-            const isc::asiolink::IOAddress& excluded_address,
-            uint8_t excluded_prefix_length);
+    /// @brief Constructor.
+    ///
+    /// @param delegated_prefix Delagated prefix.
+    /// @param delegated_prefix_length Delegated prefix length.
+    /// @param excluded_prefix Excluded prefix.
+    /// @param excluded_prefix_length Excluded prefix length.
+    ///
+    /// @throw BadValue if prefixes are invalid, if excluded prefix length
+    /// is not greater than delegated prefix length or if common parts of
+    /// prefixes does not match.
+    Option6PDExclude(const isc::asiolink::IOAddress& delegated_prefix,
+                     const uint8_t delegated_prefix_length,
+                     const isc::asiolink::IOAddress& excluded_prefix,
+                     const uint8_t excluded_prefix_length);
+
+    /// @brief Constructor, creates option instance from part of the buffer.
+    ///
+    /// This constructor is mostly used to parse Prefix Exclude options in the
+    /// received messages.
+    ///
+    /// @param begin Lower bound of the buffer to create option from.
+    /// @param end Upper bound of the buffer to create option from.
+    Option6PDExclude(const isc::asiolink::IOAddress& delegated_prefix,
+                     const uint8_t delegated_prefix_length,
+                     OptionBufferConstIter begin, OptionBufferConstIter end);
+
+    /// @brief Copies this option and returns a pointer to the copy.
+    virtual OptionPtr clone() const;
 
     /// @brief Writes option in wire-format to a buffer.
     ///
@@ -36,9 +62,12 @@ public:
     /// byte after stored option (that is useful for writing options one after
     /// another).
     ///
-    /// @param buf pointer to a buffer
+    /// The format of the option includes excluded prefix length specified as
+    /// a number of bits. It also includes IPv6 subnet ID field which is
+    /// computed from the delegated and excluded prefixes, according to the
+    /// section 4.2 of RFC 6603.
     ///
-    /// @throw BadValue Universe of the option is neither V4 nor V6.
+    /// @param [out] buf Pointer to a buffer.
     virtual void pack(isc::util::OutputBuffer& buf) const;
 
     /// @brief Parses received buffer.
@@ -53,56 +82,52 @@ public:
     /// @return length of the option
     virtual uint16_t len() const;
 
-    /// @brief Returns the address of the delegated address space.
+    /// @brief Returns Prefix Exclude option in textual format.
     ///
-    /// @return address of delegated address space
-    isc::asiolink::IOAddress getDelegatedAddress() const {
-        return delegated_address_;
+    /// @param ident Number of spaces to be inserted before the text.
+    virtual std::string toText(int indent = 0) const;
+
+    /// @brief Returns delegated prefix.
+    isc::asiolink::IOAddress getDelegatedPrefix() const {
+        return (delegated_prefix_);
     }
 
-    /// @brief Returns the prefix length of the delegated address space.
-    ///
-    /// @return prefix length of delegated address space
+    /// @brief Returns delegated prefix length.
     uint8_t getDelegatedPrefixLength() const {
-        return delegated_prefix_length_;
+        return (delegated_prefix_length_);
     }
 
-    /// @brief Returns the address of the excluded address space.
-    ///
-    /// @return address of excluded address space
-    isc::asiolink::IOAddress getExcludedAddress() const {
-        return excluded_address_;
+    /// @brief Returns excluded prefix.
+    isc::asiolink::IOAddress getExcludedPrefix() const {
+        return (excluded_prefix_);
     }
 
-    /// @brief Returns the prefix length of the excluded address space.
-    ///
-    /// @return prefix length of excluded address space
+    /// @brief Returns excluded prefix length.
     uint8_t getExcludedPrefixLength() const {
-        return excluded_prefix_length_;
+        return (excluded_prefix_length_);
     }
 
-protected:
-    /// @brief Returns the prefix length of the excluded prefix.
+private:
+
+    /// @brief Returns IPv6 subnet ID length in octets.
     ///
-    /// @return prefix length of excluded prefix
-    uint8_t getSubtractedPrefixesOctetLength() const;
+    /// The IPv6 subnet ID length is between 1 and 16 octets.
+    uint8_t getSubnetIDLength() const;
 
-    /// @brief The address and prefix length identifying the delegated IPV6
-    /// prefix.
-    /// {
-    isc::asiolink::IOAddress delegated_address_;
+    /// @brief Holds delegated prefix.
+    isc::asiolink::IOAddress delegated_prefix_;
+
+    /// @brief Holds delegated prefix length,
     uint8_t delegated_prefix_length_;
-    /// }
 
-    /// @brief The address and prefix length identifying the excluded IPV6
-    /// prefix.
-    /// {
-    isc::asiolink::IOAddress excluded_address_;
+    /// @brief Holds excluded prefix.
+    isc::asiolink::IOAddress excluded_prefix_;
+
+    /// @brief Holds excluded prefix length.
     uint8_t excluded_prefix_length_;
-    /// }
 };
 
-/// @brief Pointer to the @c Option6PDExclude object.
+/// @brief Pointer to the @ref Option6PDExclude object.
 typedef boost::shared_ptr<Option6PDExclude> Option6PDExcludePtr;
 
 } // isc::dhcp namespace
index 5203cbed10f5103c26b53cf33d7eefa2cdeeb7a6..b7dfa62dfa46e68a78f73edb825453047d9c0bc4 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // Author: Andrei Pavel <andrei.pavel@qualitance.com>
 //
@@ -8,9 +8,11 @@
 
 #include <config.h>
 
-#include <dhcp/option6_pdexclude.h>
 #include <asiolink/io_address.h>
+#include <exceptions/exceptions.h>
+#include <dhcp/option6_pdexclude.h>
 #include <dhcpsrv/pool.h>
+#include <util/buffer.h>
 #include <gtest/gtest.h>
 
 using namespace isc;
@@ -19,59 +21,155 @@ using namespace asiolink;
 
 namespace {
 
-const IOAddress empty("::");
-const IOAddress beef("2001:db8:dead:beef::"); // /48 prefix length
-const IOAddress cafe("2001:db8:dead:cafe::"); // /48 prefix length
-const IOAddress beef01("2001:db8:dead:beef::01"); // /56 prefix length
+// Prefix constants used in unit tests.
+const IOAddress v4("192.0.2.0");
+const IOAddress bee0("2001:db8:dead:bee0::");
+const IOAddress beef("2001:db8:dead:beef::");
+const IOAddress cafe("2001:db8:dead:cafe::");
+const IOAddress beef01("2001:db8:dead:beef::01");
 
-// Description
+// This test verifies that the constructor sets parameters appropriately.
 TEST(Option6PDExcludeTest, constructor) {
     Option6PDExclude option = Option6PDExclude(beef, 56, beef01, 60);
 
-    EXPECT_EQ(option.getDelegatedAddress(), beef);
-    EXPECT_EQ(option.getDelegatedPrefixLength(), 56);
-    EXPECT_EQ(option.getExcludedAddress(), beef01);
-    EXPECT_EQ(option.getExcludedPrefixLength(), 60);
-    EXPECT_EQ(option.len(), Option::OPTION6_HDR_LEN +
-        /* excluded_prefix_length_ AKA prefix-len is 1B */ 1 +
-        /* [delegated_prefix_length_ - excluded_prefix_length_](bytes) */ 1);
+    EXPECT_EQ(beef, option.getDelegatedPrefix());
+    EXPECT_EQ(56, option.getDelegatedPrefixLength());
+    EXPECT_EQ(beef01, option.getExcludedPrefix());
+    EXPECT_EQ(60, option.getExcludedPrefixLength());
+    // Total length is a sum of option header length, excluded prefix
+    // length (always 1 byte) and delegated prefix length - excluded prefix
+    // length rounded to bytes.
+    EXPECT_EQ(Option::OPTION6_HDR_LEN + 1 + 1, option.len());
+
+    // v4 prefix is not accepted.
+    EXPECT_THROW(Option6PDExclude(v4, 56, beef01, 64), BadValue);
+    EXPECT_THROW(Option6PDExclude(beef, 56, v4, 64), BadValue);
+    // Length greater than 128 is not accepted.
+    EXPECT_THROW(Option6PDExclude(beef, 128, beef01, 129), BadValue);
+    // Excluded prefix length must be greater than delegated prefix length.
+    EXPECT_THROW(Option6PDExclude(beef, 56, beef01, 56), BadValue);
+    // Both prefixes shifted by 56 must be equal (see RFC6603, section 4.2).
+    EXPECT_THROW(Option6PDExclude(cafe, 56, beef01, 64), BadValue);
 }
 
-TEST(Option6PDExcludeTest, packing_and_unpacking) {
-    EXPECT_NO_THROW(isc_throw(Exception, "Not implemented yet."));
-
-    /*
-    OptionBuffer data(option.getData());
-
+// This test verifies that on-wire format of the Prefix Exclude option is
+// created properly.
+TEST(Option6PDExcludeTest, pack) {
+    // Expected wire format of the option.
+    const uint8_t expected_data[] = {
+        0x00, 0x43, // option code 67
+        0x00, 0x02, // option length 2
+        0x3F, 0x70  // excluded prefix length 59 + subnet id
+    };
+    std::vector<uint8_t> expected_vec(expected_data,
+                                      expected_data + sizeof(expected_data));
+    // Generate wire format of the option.
     util::OutputBuffer buf(128);
-    option.pack(buf);
+    Option6PDExcludePtr option;
+    ASSERT_NO_THROW(option.reset(new Option6PDExclude(IOAddress("2001:db8:dead:bee0::"),
+                                                      59,
+                                                      IOAddress("2001:db8:dead:beef::"),
+                                                      63)));
+    ASSERT_NO_THROW(option->pack(buf));
+
+    // Check that size matches.
+    ASSERT_EQ(expected_vec.size(), buf.getLength());
+
+    // Check that the generated wire format is correct.
+    const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
+    std::vector<uint8_t> vec(data, data + buf.getLength());
+    ASSERT_TRUE(std::equal(vec.begin(), vec.end(), expected_vec.begin()));
+}
 
-    Option6PDExclude unpackedOption(empty, 0, empty, 0);
+// This test verifies parsing option wire format with subnet id of
+// 1 byte.
+TEST(Option6PDExcludeTest, unpack1ByteSubnetId) {
+    const uint8_t data[] = {
+        0x00, 0x43, // option code 67
+        0x00, 0x02, // option length 2
+        0x40, 0x78  // excluded prefix length 60 + subnet id
+    };
+    std::vector<uint8_t> vec(data, data + sizeof(data));
+
+    // Parse option.
+    Option6PDExcludePtr option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6PDExclude(IOAddress("2001:db8:dead:bee0::1"),
+                                          59, vec.begin() + 4, vec.end()))
+    );
+
+    // Make sure that the option has been parsed correctly.
+    EXPECT_EQ("2001:db8:dead:bee0::1", option->getDelegatedPrefix().toText());
+    EXPECT_EQ(59, static_cast<int>(option->getDelegatedPrefixLength()));
+    EXPECT_EQ("2001:db8:dead:beef::", option->getExcludedPrefix().toText());
+    EXPECT_EQ(64, static_cast<int>(option->getExcludedPrefixLength()));
+}
+
+// This test verifies parsing option wire format with subnet id of
+// 1 bytes.
+TEST(Option6PDExcludeTest, unpack2ByteSubnetId) {
+    const uint8_t data[] = {
+        0x00, 0x43,       // option code 67
+        0x00, 0x02,       // option length
+        0x40, 0xbe, 0xef  // excluded prefix length 60 + subnet id
+    };
+    std::vector<uint8_t> vec(data, data + sizeof(data));
+
+    // Parse option.
+    Option6PDExcludePtr option;
+    ASSERT_NO_THROW(
+        option.reset(new Option6PDExclude(IOAddress("2001:db8:dead::"),
+                                          48, vec.begin() + 4, vec.end()))
+    );
+
+    // Make sure that the option has been parsed correctly.
+    EXPECT_EQ("2001:db8:dead::", option->getDelegatedPrefix().toText());
+    EXPECT_EQ(48, static_cast<int>(option->getDelegatedPrefixLength()));
+    EXPECT_EQ("2001:db8:dead:beef::", option->getExcludedPrefix().toText());
+    EXPECT_EQ(64, static_cast<int>(option->getExcludedPrefixLength()));
+}
 
-    unpackedOption.unpack(data.begin(), data.end());
+// This test verifies that errors are reported when option buffer contains
+// invalid option data.
+TEST(Option6PDExcludeTest, unpackErrors) {
+    const uint8_t data[] = {
+        0x00, 0x43,
+        0x00, 0x02,
+        0x40, 0x78
+    };
+    std::vector<uint8_t> vec(data, data + sizeof(data));
+
+    // Option has no IPv6 subnet id.
+    EXPECT_THROW(Option6PDExclude(IOAddress("2001:db8:dead:bee0::"),
+                                  59, vec.begin() + 4, vec.end() - 1),
+                 BadValue);
+
+    // Option has IPv6 subnet id of 1 byte, but it should have 2 bytes.
+    EXPECT_THROW(Option6PDExclude(IOAddress("2001:db8:dead::"), 48,
+                                  vec.begin() + 4, vec.end()),
+                 BadValue);
+}
 
-    EXPECT_EQ(option.getDelegatedAddress(),
-            unpackedOption.getDelegatedAddress());
-    EXPECT_EQ(option.getDelegatedPrefixLength(),
-            unpackedOption.getDelegatedPrefixLength());
-    EXPECT_EQ(option.getExcludedAddress(), unpackedOption.getExcludedAddress());
-    EXPECT_EQ(option.getExcludedPrefixLength(),
-            unpackedOption.getExcludedPrefixLength());
-    //*/
+// This test verifies conversion of the Prefix Exclude option to the
+// textual format.
+TEST(Option6PDExcludeTest, toText) {
+    Option6PDExclude option(bee0, 59, beef, 64);
+    EXPECT_EQ("type=00067, len=00002: 2001:db8:dead:beef::/64",
+              option.toText());
 }
 
-TEST(Option6PDExcludeTest, pool) {
-    EXPECT_NO_THROW(isc_throw(Exception, "Not implemented yet."));
-
-    /*
-    Pool6Ptr pool6Ptr = Pool6Ptr(new Pool6(Lease::TYPE_PD, beef, cafe));
-    ASSERT_TRUE(pool6Ptr);
-    ASSERT_GT(pool6Ptr->getPrefixExcludedLength(), 0);
-     OptionPtr opt(
-     new Option6PDExclude((*l)->addr_, (*l)->prefixlen_,
-     pool->getPrefixExcluded(),
-     pool->getPrefixExcludedLength()));
-    //*/
+// This test verifies calculation of the Prefix Exclude option length.
+TEST(Option6PDExcludeTest, len) {
+   Option6PDExcludePtr option;
+   // The IPv6 subnet id is 2 bytes long. Hence the total length is
+   // 2 bytes (option code) +  2 bytes (option length) + 1 byte
+   // (excluded prefix length) + 2 bytes (IPv6 subnet id) = 7 bytes.
+   ASSERT_NO_THROW(option.reset(new Option6PDExclude(bee0, 48, beef, 64)));
+   EXPECT_EQ(7, option->len());
+
+   // IPv6 subnet id is 1 byte long. The total length is 6.
+   ASSERT_NO_THROW(option.reset(new Option6PDExclude(bee0, 59, beef, 64)));
+   EXPECT_EQ(6, option->len());
 }
 
 } // anonymous namespace