Introduce Delegation and DelegationList

DelegationList will become the payload structure of ForwardingHint
and LinkContent in a later commit.

refs #4055

Change-Id: If29a39c47b779aafdd4aebb104092403f28e6653
diff --git a/src/delegation-list.cpp b/src/delegation-list.cpp
new file mode 100644
index 0000000..a0dd7c8
--- /dev/null
+++ b/src/delegation-list.cpp
@@ -0,0 +1,240 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "delegation-list.hpp"
+
+namespace ndn {
+
+BOOST_CONCEPT_ASSERT((boost::EqualityComparable<DelegationList>));
+BOOST_CONCEPT_ASSERT((WireEncodableWithEncodingBuffer<DelegationList>));
+BOOST_CONCEPT_ASSERT((WireDecodable<DelegationList>));
+
+DelegationList::Error::Error(const std::string& what)
+  : tlv::Error(what)
+{
+}
+
+DelegationList::Error::Error(const std::string& what, const std::exception& innerException)
+  : Error(what + std::string(": ") + innerException.what())
+{
+}
+
+DelegationList::DelegationList()
+  : m_isSorted(true)
+{
+}
+
+DelegationList::DelegationList(const Block& block, bool wantSort)
+{
+  this->wireDecode(block, wantSort);
+}
+
+bool
+DelegationList::isValidTlvType(uint32_t type)
+{
+  switch (type) {
+    case tlv::Content:
+    case tlv::ForwardingHint:
+      return true;
+    default:
+      return false;
+  }
+}
+
+template<encoding::Tag TAG>
+size_t
+DelegationList::wireEncode(EncodingImpl<TAG>& encoder, uint32_t type) const
+{
+  if (!isValidTlvType(type)) {
+    BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid TLV-TYPE " + to_string(type) +
+                                                " when encoding DelegationList"));
+  }
+
+  if (this->size() == 0) {
+    BOOST_THROW_EXCEPTION(Error("Empty DelegationList"));
+  }
+
+  // LinkContent ::= (type) TLV-LENGTH
+  //                    Delegation+
+
+  // Delegation ::= LINK-DELEGATION-TYPE TLV-LENGTH
+  //                  Preference
+  //                  Name
+
+  // Preference ::= LINK-PREFERENCE-TYPE TLV-LENGTH
+  //       nonNegativeInteger
+
+  size_t totalLen = 0;
+  for (auto i = m_dels.rbegin(); i != m_dels.rend(); ++i) {
+    size_t delLen = 0;
+    delLen += i->name.wireEncode(encoder);
+    delLen += prependNonNegativeIntegerBlock(encoder, tlv::LinkPreference, i->preference);
+    delLen += encoder.prependVarNumber(delLen);
+    delLen += encoder.prependVarNumber(tlv::LinkDelegation);
+    totalLen += delLen;
+  }
+  totalLen += encoder.prependVarNumber(totalLen);
+  totalLen += encoder.prependVarNumber(type);
+  return totalLen;
+}
+
+template size_t
+DelegationList::wireEncode<encoding::EncoderTag>(EncodingImpl<encoding::EncoderTag>&, uint32_t) const;
+
+template size_t
+DelegationList::wireEncode<encoding::EstimatorTag>(EncodingImpl<encoding::EstimatorTag>&, uint32_t) const;
+
+void
+DelegationList::wireDecode(const Block& block, bool wantSort)
+{
+  if (!isValidTlvType(block.type())) {
+    BOOST_THROW_EXCEPTION(Error("Unexpected TLV-TYPE " + to_string(block.type()) +
+                                " when decoding DelegationList"));
+  }
+
+  m_isSorted = wantSort;
+  m_dels.clear();
+
+  block.parse();
+  for (const auto& del : block.elements()) {
+    if (del.type() != tlv::LinkDelegation) {
+      BOOST_THROW_EXCEPTION(Error("Unexpected TLV-TYPE " + to_string(del.type()) +
+                                  " when decoding Delegation"));
+    }
+    del.parse();
+
+    auto val = del.elements_begin();
+    if (val == del.elements_end() || val->type() != tlv::LinkPreference) {
+      BOOST_THROW_EXCEPTION(Error("Missing Preference field in Delegation"));
+    }
+    uint64_t preference = 0;
+    try {
+      preference = readNonNegativeInteger(*val);
+    }
+    catch (const tlv::Error& inner) {
+      BOOST_THROW_EXCEPTION(Error("Invalid Preference field in Delegation", inner));
+    }
+
+    ++val;
+    if (val == del.elements_end() || val->type() != tlv::Name) {
+      BOOST_THROW_EXCEPTION(Error("Missing Name field in Delegation"));
+    }
+    Name name;
+    try {
+      name.wireDecode(*val);
+    }
+    catch (const tlv::Error& inner) {
+      BOOST_THROW_EXCEPTION(Error("Invalid Name field in Delegation", inner));
+    }
+
+    this->insertImpl(preference, name);
+  }
+
+  if (this->size() == 0) {
+    BOOST_THROW_EXCEPTION(Error("Empty DelegationList"));
+  }
+}
+
+void
+DelegationList::sort()
+{
+  if (m_isSorted) {
+    return;
+  }
+
+  std::vector<Delegation> dels;
+  dels.swap(m_dels);
+
+  m_isSorted = true;
+  for (const Delegation& del : dels) {
+    this->insertImpl(del.preference, del.name);
+  }
+}
+
+bool
+DelegationList::insert(uint64_t preference, const Name& name,
+                       InsertConflictResolution onConflict)
+{
+  switch (onConflict) {
+    case INS_REPLACE:
+      this->eraseImpl(nullopt, name);
+      this->insertImpl(preference, name);
+      return true;
+    case INS_APPEND:
+      this->insertImpl(preference, name);
+      return true;
+    case INS_SKIP:
+      if (!std::any_of(m_dels.begin(), m_dels.end(),
+                       [name] (const Delegation& del) { return del.name == name; })) {
+        this->insertImpl(preference, name);
+        return true;
+      }
+      return false;
+  }
+  BOOST_ASSERT_MSG(false, "Unknown onConflict");
+  return false;
+}
+
+void
+DelegationList::insertImpl(uint64_t preference, const Name& name)
+{
+  if (!m_isSorted) {
+    m_dels.push_back({preference, name});
+    return;
+  }
+
+  Delegation del{preference, name};
+  auto pos = std::upper_bound(m_dels.begin(), m_dels.end(), del);
+  m_dels.insert(pos, del);
+}
+
+size_t
+DelegationList::eraseImpl(optional<uint64_t> preference, const Name& name)
+{
+  size_t nErased = 0;
+  for (auto i = m_dels.begin(); i != m_dels.end();) {
+    if ((!preference || i->preference == *preference) &&
+        i->name == name) {
+      ++nErased;
+      i = m_dels.erase(i);
+    }
+    else {
+      ++i;
+    }
+  }
+  return nErased;
+}
+
+bool
+operator==(const DelegationList& lhs, const DelegationList& rhs)
+{
+  return lhs.m_dels == rhs.m_dels;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const DelegationList& dl)
+{
+  os << '[';
+  std::copy(dl.begin(), dl.end(), make_ostream_joiner(os, ','));
+  return os << ']';
+}
+
+} // namespace ndn
diff --git a/src/delegation-list.hpp b/src/delegation-list.hpp
new file mode 100644
index 0000000..9f74399
--- /dev/null
+++ b/src/delegation-list.hpp
@@ -0,0 +1,239 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_DELEGATION_LIST_HPP
+#define NDN_DELEGATION_LIST_HPP
+
+#include "delegation.hpp"
+
+namespace ndn {
+
+/** \brief represents a list of Delegations
+ *  \sa https://named-data.net/doc/ndn-tlv/link.html
+ *
+ *  Delegations are stored in an std::vector, under the assumption that there is usually only a
+ *  small number of Delegations, so that copying is acceptable when they are modified.
+ */
+class DelegationList
+{
+public:
+  class Error : public tlv::Error
+  {
+  public:
+    explicit
+    Error(const std::string& what);
+
+    Error(const std::string& what, const std::exception& innerException);
+  };
+
+  /** \brief construct an empty DelegationList
+   */
+  DelegationList();
+
+  /** \brief decode a DelegationList
+   *  \sa wireDecode
+   */
+  explicit
+  DelegationList(const Block& block, bool wantSort = true);
+
+  /** \brief encode into wire format
+   *  \param encoder either an EncodingBuffer or an EncodingEstimator
+   *  \param type TLV-TYPE code, either Content (for \p Link) or ForwardingHint
+   *  \throw std::invalid_argument \p type is invalid
+   *  \throw Error there is no Delegation
+   */
+  template<encoding::Tag TAG>
+  size_t
+  wireEncode(EncodingImpl<TAG>& encoder, uint32_t type = tlv::ForwardingHint) const;
+
+  /** \brief decode a DelegationList
+   *  \param block either a Content block (from \p Link) or a ForwardingHint block
+   *  \param wantSort if true, delegations are sorted
+   *  \throw Error the block cannot be parsed as a list of Delegations
+   */
+  void
+  wireDecode(const Block& block, bool wantSort = true);
+
+  bool
+  isSorted() const
+  {
+    return m_isSorted;
+  }
+
+  using const_iterator = std::vector<Delegation>::const_iterator;
+
+  const_iterator
+  begin() const
+  {
+    return m_dels.begin();
+  }
+
+  const_iterator
+  end() const
+  {
+    return m_dels.end();
+  }
+
+  size_t
+  size() const
+  {
+    return m_dels.size();
+  }
+
+  /** \brief get the i-th delegation
+   *  \pre i < size()
+   */
+  const Delegation&
+  operator[](size_t i) const
+  {
+    BOOST_ASSERT(i < size());
+    return m_dels[i];
+  }
+
+  /** \brief get the i-th delegation
+   *  \throw std::out_of_range i >= size()
+   */
+  const Delegation&
+  at(size_t i) const
+  {
+    return m_dels.at(i);
+  }
+
+public: // modifiers
+  /** \brief sort the delegation list
+   *  \post isSorted() == true
+   *  \post Delegations are sorted in increasing preference order.
+   *
+   *  A DelegationList can be constructed as sorted or unsorted. In most cases, it is recommended
+   *  to use a sorted DelegationList. An unsorted DelegationList is useful for extracting the i-th
+   *  delegation from a received ForwardingHint or Link object.
+   *
+   *  This method turns an unsorted DelegationList into a sorted DelegationList.
+   *  If access to unsorted DelegationList is not needed, it is more efficient to sort the
+   *  DelegationList in wireDecode.
+   */
+  void
+  sort();
+
+  /** \brief what to do when inserting a duplicate name
+   */
+  enum InsertConflictResolution {
+    /** \brief existing delegation(s) with the same name are replaced with the new delegation
+     */
+    INS_REPLACE,
+
+    /** \brief multiple delegations with the same name are kept in the DelegationList
+     *  \note This is NOT RECOMMENDED by Link specification.
+     */
+    INS_APPEND,
+
+    /** \brief new delegation is not inserted if an existing delegation has the same name
+     */
+    INS_SKIP
+  };
+
+  /** \brief insert Delegation
+   *  \return whether inserted
+   */
+  bool
+  insert(uint64_t preference, const Name& name,
+         InsertConflictResolution onConflict = INS_REPLACE);
+
+  /** \brief insert Delegation
+   *  \return whether inserted
+   */
+  bool
+  insert(const Delegation& del, InsertConflictResolution onConflict = INS_REPLACE)
+  {
+    return this->insert(del.preference, del.name, onConflict);
+  }
+
+  /** \brief delete Delegation(s) with specified preference and name
+   *  \return count of erased Delegation(s)
+   */
+  size_t
+  erase(uint64_t preference, const Name& name)
+  {
+    return this->eraseImpl(preference, name);
+  }
+
+  /** \brief delete Delegation(s) with matching preference and name
+   *  \return count of erased Delegation(s)
+   */
+  size_t
+  erase(const Delegation& del)
+  {
+    return this->eraseImpl(del.preference, del.name);
+  }
+
+  /** \brief erase Delegation(s) with specified name
+   *  \return count of erased Delegation(s)
+   */
+  size_t
+  erase(const Name& name)
+  {
+    return this->eraseImpl(nullopt, name);
+  }
+
+private:
+  static bool
+  isValidTlvType(uint32_t type);
+
+  void
+  insertImpl(uint64_t preference, const Name& name);
+
+  size_t
+  eraseImpl(optional<uint64_t> preference, const Name& name);
+
+private:
+  bool m_isSorted;
+
+  /** \brief delegation container; its contents are sorted when \p m_isSorted is true
+   *  \note This container is a member field rather than a base class, in order to ensure contents
+   *        are sorted when \p m_isSorted is true.
+   *  \note A vector is chosen instead of a std::set, so that the container can be unsorted when
+   *        \p m_isSorted is false. This container is expected to have less than seven items, and
+   *        therefore the overhead of moving items during insertion and deletion is small.
+   */
+  std::vector<Delegation> m_dels;
+
+  friend bool operator==(const DelegationList&, const DelegationList&);
+};
+
+/** \brief compare whether two DelegationLists are equal
+ *  \note Order matters! If two DelegationLists contain the same Delegations but at least one is
+ *        unsorted, they may compare unequal if the Delegations appear in different order.
+ */
+bool
+operator==(const DelegationList& lhs, const DelegationList& rhs);
+
+inline bool
+operator!=(const DelegationList& lhs, const DelegationList& rhs)
+{
+  return !(lhs == rhs);
+}
+
+std::ostream&
+operator<<(std::ostream& os, const DelegationList& dl);
+
+} // namespace ndn
+
+#endif // NDN_DELEGATION_LIST_HPP
diff --git a/src/delegation.cpp b/src/delegation.cpp
new file mode 100644
index 0000000..178b1d0
--- /dev/null
+++ b/src/delegation.cpp
@@ -0,0 +1,55 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "delegation.hpp"
+
+namespace ndn {
+
+BOOST_CONCEPT_ASSERT((boost::EqualityComparable<Delegation>));
+
+bool
+operator==(const Delegation& lhs, const Delegation& rhs)
+{
+  return lhs.preference == rhs.preference &&
+         lhs.name == rhs.name;
+}
+
+bool
+operator<(const Delegation& lhs, const Delegation& rhs)
+{
+  return std::tie(lhs.preference, lhs.name) <
+         std::tie(rhs.preference, rhs.name);
+}
+
+bool
+operator<=(const Delegation& lhs, const Delegation& rhs)
+{
+  return std::tie(lhs.preference, lhs.name) <=
+         std::tie(rhs.preference, rhs.name);
+}
+
+std::ostream&
+operator<<(std::ostream& os, const Delegation& del)
+{
+  return os << del.name << '(' << del.preference << ')';
+}
+
+} // namespace ndn
diff --git a/src/delegation.hpp b/src/delegation.hpp
new file mode 100644
index 0000000..4e73ebe
--- /dev/null
+++ b/src/delegation.hpp
@@ -0,0 +1,70 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_DELEGATION_HPP
+#define NDN_DELEGATION_HPP
+
+#include "name.hpp"
+
+namespace ndn {
+
+/** \brief represents a delegation
+ *  \sa https://named-data.net/doc/ndn-tlv/link.html
+ */
+struct Delegation
+{
+  uint64_t preference;
+  Name name;
+};
+
+bool
+operator==(const Delegation& lhs, const Delegation& rhs);
+
+inline bool
+operator!=(const Delegation& lhs, const Delegation& rhs)
+{
+  return !(lhs == rhs);
+}
+
+bool
+operator<(const Delegation& lhs, const Delegation& rhs);
+
+bool
+operator<=(const Delegation& lhs, const Delegation& rhs);
+
+inline bool
+operator>(const Delegation& lhs, const Delegation& rhs)
+{
+  return !(lhs <= rhs);
+}
+
+inline bool
+operator>=(const Delegation& lhs, const Delegation& rhs)
+{
+  return !(lhs < rhs);
+}
+
+std::ostream&
+operator<<(std::ostream& os, const Delegation& del);
+
+} // namespace ndn
+
+#endif // NDN_DELEGATION_HPP
diff --git a/src/encoding/tlv.hpp b/src/encoding/tlv.hpp
index b15ec14..bcb562a 100644
--- a/src/encoding/tlv.hpp
+++ b/src/encoding/tlv.hpp
@@ -65,6 +65,7 @@
   Nonce         = 10,
   // <Unassigned> = 11,
   InterestLifetime          = 12,
+  ForwardingHint            = 30,
   MinSuffixComponents       = 13,
   MaxSuffixComponents       = 14,
   PublisherPublicKeyLocator = 15,
diff --git a/tests/unit-tests/delegation-list.t.cpp b/tests/unit-tests/delegation-list.t.cpp
new file mode 100644
index 0000000..5961af5
--- /dev/null
+++ b/tests/unit-tests/delegation-list.t.cpp
@@ -0,0 +1,371 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "delegation-list.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestDelegationList)
+
+const uint8_t DEL1A[] = {
+  0x1f, 0x08, // Delegation
+        0x1e, 0x01, 0x01, // Preference=1
+        0x07, 0x03, 0x08, 0x01, 0x41 // Name=/A
+};
+const uint8_t DEL1B[] = {
+  0x1f, 0x08, // Delegation
+        0x1e, 0x01, 0x01, // Preference=1
+        0x07, 0x03, 0x08, 0x01, 0x42 // Name=/B
+};
+const uint8_t DEL2A[] = {
+  0x1f, 0x08, // Delegation
+        0x1e, 0x01, 0x02, // Preference=2
+        0x07, 0x03, 0x08, 0x01, 0x41 // Name=/A
+};
+const uint8_t DEL2B[] = {
+  0x1f, 0x08, // Delegation
+        0x1e, 0x01, 0x02, // Preference=2
+        0x07, 0x03, 0x08, 0x01, 0x42 // Name=/B
+};
+
+Block
+makeDelegationListBlock(uint32_t type, std::initializer_list<const uint8_t*> dels)
+{
+  Block block(type);
+  for (const uint8_t* del : dels) {
+    block.push_back(Block(del, 2 + del[1]));
+  }
+  block.encode();
+  return block;
+}
+
+BOOST_AUTO_TEST_SUITE(Decode)
+
+BOOST_AUTO_TEST_CASE(DecodeUnsorted)
+{
+  DelegationList dl(makeDelegationListBlock(tlv::ForwardingHint, {DEL2A, DEL2B, DEL1A}), false);
+  BOOST_CHECK_EQUAL(dl.size(), 3);
+  BOOST_CHECK_EQUAL(dl.at(0).preference, 2);
+  BOOST_CHECK_EQUAL(dl.at(0).name, "/A");
+  BOOST_CHECK_EQUAL(dl.at(1).preference, 2);
+  BOOST_CHECK_EQUAL(dl.at(1).name, "/B");
+  BOOST_CHECK_EQUAL(dl.at(2).preference, 1);
+  BOOST_CHECK_EQUAL(dl.at(2).name, "/A");
+}
+
+BOOST_AUTO_TEST_CASE(DecodeSorted)
+{
+  DelegationList dl(makeDelegationListBlock(tlv::Content, {DEL2A, DEL2B, DEL1A}));
+  BOOST_CHECK_EQUAL(dl.size(), 3);
+  BOOST_CHECK_EQUAL(dl.at(0).preference, 1);
+  BOOST_CHECK_EQUAL(dl.at(0).name, "/A");
+  BOOST_CHECK_EQUAL(dl.at(1).preference, 2);
+  BOOST_CHECK_EQUAL(dl.at(1).name, "/A");
+  BOOST_CHECK_EQUAL(dl.at(2).preference, 2);
+  BOOST_CHECK_EQUAL(dl.at(2).name, "/B");
+}
+
+BOOST_AUTO_TEST_CASE(DecodeEmpty)
+{
+  DelegationList dl;
+  Block block = makeDelegationListBlock(tlv::ForwardingHint, {});
+  BOOST_CHECK_THROW(dl.wireDecode(block), DelegationList::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeBadType)
+{
+  DelegationList dl;
+  Block block = makeDelegationListBlock(tlv::Selectors, {DEL1A, DEL2B});
+  BOOST_CHECK_THROW(dl.wireDecode(block), DelegationList::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeNotDelegation)
+{
+  const uint8_t BAD_DEL[] = {
+    0x09, 0x00 // Selectors
+  };
+
+  DelegationList dl;
+  Block block = makeDelegationListBlock(tlv::ForwardingHint, {DEL1A, BAD_DEL});
+  BOOST_CHECK_THROW(dl.wireDecode(block), DelegationList::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeMissingPreference)
+{
+  const uint8_t BAD_DEL[] = {
+    0x1f, 0x05, // Delegation
+          0x07, 0x03, 0x08, 0x01, 0x42 // Name=/B
+  };
+
+  DelegationList dl;
+  Block block = makeDelegationListBlock(tlv::ForwardingHint, {DEL1A, BAD_DEL});
+  BOOST_CHECK_THROW(dl.wireDecode(block), DelegationList::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeMissingName)
+{
+  const uint8_t BAD_DEL[] = {
+    0x1f, 0x03, // Delegation
+          0x1e, 0x01, 0x02, // Preference=2
+  };
+
+  DelegationList dl;
+  Block block = makeDelegationListBlock(tlv::ForwardingHint, {DEL1A, BAD_DEL});
+  BOOST_CHECK_THROW(dl.wireDecode(block), DelegationList::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeUnknownField)
+{
+  const uint8_t BAD_DEL[] = {
+    0x1f, 0x0a, // Delegation
+          0x1e, 0x01, 0x02, // Preference=2
+          0x09, 0x00, // Selectors
+          0x07, 0x03, 0x08, 0x01, 0x42 // Name=/B
+  };
+
+  DelegationList dl;
+  Block block = makeDelegationListBlock(tlv::ForwardingHint, {DEL1A, BAD_DEL});
+  BOOST_CHECK_THROW(dl.wireDecode(block), DelegationList::Error);
+}
+
+BOOST_AUTO_TEST_CASE(DecodeWrongOrder)
+{
+  const uint8_t BAD_DEL[] = {
+    0x1f, 0x08, // Delegation
+          0x07, 0x03, 0x08, 0x01, 0x42, // Name=/B
+          0x1e, 0x01, 0x02 // Preference=2
+  };
+
+  DelegationList dl;
+  Block block = makeDelegationListBlock(tlv::ForwardingHint, {DEL1A, BAD_DEL});
+  BOOST_CHECK_THROW(dl.wireDecode(block), DelegationList::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Decode
+
+BOOST_AUTO_TEST_SUITE(InsertEncode)
+
+BOOST_AUTO_TEST_CASE(InsertSimple)
+{
+  DelegationList dl;
+  dl.insert(2, "/A");
+  dl.insert(1, "/B");
+  BOOST_CHECK_EQUAL(dl.size(), 2);
+
+  EncodingBuffer encoder;
+  dl.wireEncode(encoder);
+  BOOST_CHECK(encoder.block() == makeDelegationListBlock(tlv::ForwardingHint, {DEL1B, DEL2A}));
+}
+
+BOOST_AUTO_TEST_CASE(InsertReplace)
+{
+  DelegationList dl;
+  dl.insert(2, "/A");
+  dl.insert(Delegation{1, "/A"}, DelegationList::INS_REPLACE);
+  BOOST_CHECK_EQUAL(dl.size(), 1);
+  BOOST_CHECK_EQUAL(dl.at(0).preference, 1);
+  BOOST_CHECK_EQUAL(dl[0].name, "/A");
+
+  EncodingBuffer encoder;
+  dl.wireEncode(encoder);
+  BOOST_CHECK(encoder.block() == makeDelegationListBlock(tlv::ForwardingHint, {DEL1A}));
+}
+
+BOOST_AUTO_TEST_CASE(InsertAppend)
+{
+  DelegationList dl;
+  dl.insert(2, "/A");
+  dl.insert(Delegation{1, "/A"}, DelegationList::INS_APPEND);
+  BOOST_CHECK_EQUAL(dl.size(), 2);
+  BOOST_CHECK_EQUAL(dl.at(0).preference, 1);
+  BOOST_CHECK_EQUAL(dl.at(1).preference, 2);
+
+  EncodingBuffer encoder;
+  dl.wireEncode(encoder);
+  BOOST_CHECK(encoder.block() == makeDelegationListBlock(tlv::ForwardingHint, {DEL1A, DEL2A}));
+}
+
+BOOST_AUTO_TEST_CASE(InsertSkip)
+{
+  DelegationList dl;
+  dl.insert(2, "/A");
+  dl.insert(Delegation{1, "/A"}, DelegationList::INS_SKIP);
+  BOOST_CHECK_EQUAL(dl.size(), 1);
+  BOOST_CHECK_EQUAL(dl.at(0).preference, 2);
+
+  EncodingBuffer encoder;
+  dl.wireEncode(encoder);
+  BOOST_CHECK(encoder.block() == makeDelegationListBlock(tlv::ForwardingHint, {DEL2A}));
+}
+
+BOOST_AUTO_TEST_CASE(Unsorted)
+{
+  DelegationList dl(makeDelegationListBlock(tlv::ForwardingHint, {DEL2A}), false);
+  dl.insert(1, "/B");
+  BOOST_CHECK_EQUAL(dl.size(), 2);
+  BOOST_CHECK_EQUAL(dl.at(0).preference, 2);
+  BOOST_CHECK_EQUAL(dl.at(0).name, "/A");
+  BOOST_CHECK_EQUAL(dl.at(1).preference, 1);
+  BOOST_CHECK_EQUAL(dl.at(1).name, "/B");
+
+  EncodingBuffer encoder;
+  dl.wireEncode(encoder, tlv::Content);
+  BOOST_CHECK(encoder.block() == makeDelegationListBlock(tlv::Content, {DEL2A, DEL1B}));
+}
+
+BOOST_AUTO_TEST_CASE(EncodeBadType)
+{
+  DelegationList dl(makeDelegationListBlock(tlv::ForwardingHint, {DEL2A}));
+  EncodingBuffer encoder;
+  BOOST_CHECK_THROW(dl.wireEncode(encoder, tlv::Selectors), std::invalid_argument);
+}
+
+BOOST_AUTO_TEST_CASE(EncodeEmpty)
+{
+  DelegationList dl;
+  EncodingBuffer encoder;
+  BOOST_CHECK_THROW(dl.wireEncode(encoder), DelegationList::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // InsertEncode
+
+BOOST_AUTO_TEST_SUITE(Erase)
+
+BOOST_AUTO_TEST_CASE(EraseNoop)
+{
+  DelegationList dl;
+  dl.insert(1, "/A");
+  BOOST_CHECK_EQUAL(dl.erase(2, "/A"), 0);
+  BOOST_CHECK_EQUAL(dl.erase(Delegation{1, "/B"}), 0);
+  BOOST_CHECK_EQUAL(dl.size(), 1);
+  BOOST_CHECK_EQUAL(dl.at(0).preference, 1);
+  BOOST_CHECK_EQUAL(dl.at(0).name, "/A");
+}
+
+BOOST_AUTO_TEST_CASE(EraseOne)
+{
+  DelegationList dl;
+  dl.insert(1, "/A");
+  BOOST_CHECK_EQUAL(dl.erase(1, "/A"), 1);
+  BOOST_CHECK_EQUAL(dl.size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(EraseByName)
+{
+  DelegationList dl;
+  dl.insert(1, "/A");
+  dl.insert(2, "/A", DelegationList::INS_APPEND);
+  BOOST_CHECK_EQUAL(dl.size(), 2);
+  BOOST_CHECK_EQUAL(dl.erase("/A"), 2);
+  BOOST_CHECK_EQUAL(dl.size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Erase
+
+BOOST_AUTO_TEST_SUITE(Sort)
+
+BOOST_AUTO_TEST_CASE(Noop)
+{
+  DelegationList dl(makeDelegationListBlock(tlv::ForwardingHint, {DEL1A}));
+  BOOST_CHECK_EQUAL(dl.isSorted(), true);
+  dl.sort();
+  BOOST_CHECK_EQUAL(dl.isSorted(), true);
+}
+
+BOOST_AUTO_TEST_CASE(Sort)
+{
+  DelegationList dl(makeDelegationListBlock(tlv::ForwardingHint, {DEL2A, DEL2B, DEL1A}), false);
+  BOOST_CHECK_EQUAL(dl.isSorted(), false);
+  dl.sort();
+  BOOST_CHECK_EQUAL(dl.isSorted(), true);
+  BOOST_CHECK_EQUAL(dl.size(), 3);
+  BOOST_CHECK_EQUAL(dl.at(0).preference, 1);
+  BOOST_CHECK_EQUAL(dl.at(0).name, "/A");
+  BOOST_CHECK_EQUAL(dl.at(1).preference, 2);
+  BOOST_CHECK_EQUAL(dl.at(1).name, "/A");
+  BOOST_CHECK_EQUAL(dl.at(2).preference, 2);
+  BOOST_CHECK_EQUAL(dl.at(2).name, "/B");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Sort
+
+BOOST_AUTO_TEST_SUITE(Compare)
+
+BOOST_AUTO_TEST_CASE(Empty)
+{
+  DelegationList dl1, dl2;
+  BOOST_CHECK_EQUAL(dl1, dl2);
+}
+
+BOOST_AUTO_TEST_CASE(SortedEqual)
+{
+  DelegationList dl1(makeDelegationListBlock(tlv::ForwardingHint, {DEL2A, DEL1B})),
+                 dl2(makeDelegationListBlock(tlv::Content, {DEL1B, DEL2A}));
+  BOOST_CHECK_EQUAL(dl1, dl2);
+}
+
+BOOST_AUTO_TEST_CASE(SortedUnequal)
+{
+  DelegationList dl1(makeDelegationListBlock(tlv::ForwardingHint, {DEL2A, DEL1B})),
+                 dl2(makeDelegationListBlock(tlv::Content, {DEL1A, DEL2B}));
+  BOOST_CHECK_NE(dl1, dl2);
+}
+
+BOOST_AUTO_TEST_CASE(UnsortedSameOrder)
+{
+  DelegationList dl1(makeDelegationListBlock(tlv::ForwardingHint, {DEL2A, DEL1B}), false),
+                 dl2(makeDelegationListBlock(tlv::Content, {DEL2A, DEL1B}), false);
+  BOOST_CHECK_EQUAL(dl1, dl2);
+}
+
+BOOST_AUTO_TEST_CASE(UnsortedDifferentOrder)
+{
+  DelegationList dl1(makeDelegationListBlock(tlv::ForwardingHint, {DEL2A, DEL1B}), false),
+                 dl2(makeDelegationListBlock(tlv::Content, {DEL1B, DEL2A}), false);
+  BOOST_CHECK_NE(dl1, dl2);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Compare
+
+BOOST_AUTO_TEST_SUITE(Print)
+
+BOOST_AUTO_TEST_CASE(PrintEmpty)
+{
+  DelegationList dl;
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(dl), "[]");
+}
+
+BOOST_AUTO_TEST_CASE(PrintNormal)
+{
+  DelegationList dl(makeDelegationListBlock(tlv::ForwardingHint, {DEL2A, DEL1B}));
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(dl), "[/B(1),/A(2)]");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Print
+
+BOOST_AUTO_TEST_SUITE_END() // TestDelegationList
+
+} // namespace tests
+} // namespace ndn
diff --git a/tests/unit-tests/delegation.t.cpp b/tests/unit-tests/delegation.t.cpp
new file mode 100644
index 0000000..f15f578
--- /dev/null
+++ b/tests/unit-tests/delegation.t.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "delegation.hpp"
+
+#include "boost-test.hpp"
+#include <boost/lexical_cast.hpp>
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestDelegation)
+
+BOOST_AUTO_TEST_CASE(Compare)
+{
+  BOOST_CHECK_EQUAL((Delegation{1, "/A"}), (Delegation{1, "/A"}));
+  BOOST_CHECK_LE((Delegation{1, "/A"}), (Delegation{1, "/A"}));
+  BOOST_CHECK_GE((Delegation{1, "/A"}), (Delegation{1, "/A"}));
+
+  BOOST_CHECK_NE((Delegation{1, "/A"}), (Delegation{2, "/A"}));
+  BOOST_CHECK_NE((Delegation{1, "/A"}), (Delegation{1, "/B"}));
+
+  BOOST_CHECK_LT((Delegation{1, "/A"}), (Delegation{1, "/B"}));
+  BOOST_CHECK_LE((Delegation{1, "/A"}), (Delegation{1, "/B"}));
+  BOOST_CHECK_LT((Delegation{1, "/B"}), (Delegation{2, "/A"}));
+  BOOST_CHECK_LE((Delegation{1, "/B"}), (Delegation{2, "/A"}));
+  BOOST_CHECK_LT((Delegation{1, "/A"}), (Delegation{2, "/A"}));
+  BOOST_CHECK_LE((Delegation{1, "/A"}), (Delegation{2, "/A"}));
+
+  BOOST_CHECK_GT((Delegation{1, "/B"}), (Delegation{1, "/A"}));
+  BOOST_CHECK_GE((Delegation{1, "/B"}), (Delegation{1, "/A"}));
+  BOOST_CHECK_GT((Delegation{2, "/A"}), (Delegation{1, "/B"}));
+  BOOST_CHECK_GE((Delegation{2, "/A"}), (Delegation{1, "/B"}));
+  BOOST_CHECK_GT((Delegation{2, "/A"}), (Delegation{1, "/A"}));
+  BOOST_CHECK_GE((Delegation{2, "/A"}), (Delegation{1, "/A"}));
+}
+
+BOOST_AUTO_TEST_CASE(Print)
+{
+  BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(Delegation{1, "/B"}), "/B(1)");
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestDelegation
+
+} // namespace tests
+} // namespace ndn