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