interest: Add Link and SelectedDelegation fields

Change-Id: I1e4bafe8559ef87135de6cb6f209fc94dfa231b0
Refs: #2587
diff --git a/src/interest.cpp b/src/interest.cpp
index 0ae0852..017711a 100644
--- a/src/interest.cpp
+++ b/src/interest.cpp
@@ -36,6 +36,7 @@
 Interest::Interest()
   : m_scope(-1)
   , m_interestLifetime(time::milliseconds::min())
+  , m_selectedDelegationIndex(INVALID_SELECTED_DELEGATION_INDEX)
 {
 }
 
@@ -43,6 +44,7 @@
   : m_name(name)
   , m_scope(-1)
   , m_interestLifetime(time::milliseconds::min())
+  , m_selectedDelegationIndex(INVALID_SELECTED_DELEGATION_INDEX)
 {
 }
 
@@ -50,6 +52,7 @@
   : m_name(name)
   , m_scope(-1)
   , m_interestLifetime(interestLifetime)
+  , m_selectedDelegationIndex(INVALID_SELECTED_DELEGATION_INDEX)
 {
 }
 
@@ -62,6 +65,7 @@
   , m_selectors(selectors)
   , m_scope(scope)
   , m_interestLifetime(interestLifetime)
+  , m_selectedDelegationIndex(INVALID_SELECTED_DELEGATION_INDEX)
 {
   if (nonce > 0) {
     setNonce(nonce);
@@ -239,9 +243,23 @@
   //                Nonce
   //                Scope?
   //                InterestLifetime?
+  //                Link?
+  //                SelectedDelegation?
 
   // (reverse encoding)
 
+  if (hasLink()) {
+    if (hasSelectedDelegation()) {
+      totalLength += prependNonNegativeIntegerBlock(block,
+                                                    tlv::SelectedDelegation,
+                                                    m_selectedDelegationIndex);
+    }
+    totalLength += prependBlock(block, m_link);
+  }
+  else {
+    BOOST_ASSERT(!hasSelectedDelegation());
+  }
+
   // InterestLifetime
   if (getInterestLifetime() >= time::milliseconds::zero() &&
       getInterestLifetime() != DEFAULT_INTEREST_LIFETIME)
@@ -311,6 +329,8 @@
   //                Nonce
   //                Scope?
   //                InterestLifetime?
+  //                Link?
+  //                SelectedDelegation?
 
   if (m_wire.type() != tlv::Interest)
     throw Error("Unexpected TLV number when decoding Interest");
@@ -349,6 +369,114 @@
     {
       m_interestLifetime = DEFAULT_INTEREST_LIFETIME;
     }
+
+  // Link object
+  val = m_wire.find(tlv::Data);
+  if (val != m_wire.elements_end())
+    {
+      m_link = (*val);
+    }
+
+  // SelectedDelegation
+  val = m_wire.find(tlv::SelectedDelegation);
+  if (val != m_wire.elements_end()) {
+    if (!this->hasLink()) {
+      throw Error("Interest contains selectedDelegation, but no LINK object");
+    }
+    uint64_t selectedDelegation = readNonNegativeInteger(*val);
+    if (selectedDelegation < uint64_t(Link::countDelegationsFromWire(m_link))) {
+      m_selectedDelegationIndex = static_cast<size_t>(selectedDelegation);
+    }
+    else {
+      throw Error("Invalid selected delegation index when decoding Interest");
+    }
+  }
+}
+
+bool
+Interest::hasLink() const
+{
+  if (m_link.hasWire())
+    return true;
+  return false;
+}
+
+Link
+Interest::getLink() const
+{
+  if (hasLink())
+    {
+      return Link(m_link);
+    }
+  throw Error("There is no encapsulated link object");
+}
+
+void
+Interest::setLink(const Block& link)
+{
+  m_link = link;
+  if (!link.hasWire()) {
+    throw Error("The given link does not have a wire format");
+  }
+  m_wire.reset();
+  this->unsetSelectedDelegation();
+}
+
+void
+Interest::unsetLink()
+{
+  m_link.reset();
+  m_wire.reset();
+  this->unsetSelectedDelegation();
+}
+
+bool
+Interest::hasSelectedDelegation() const
+{
+  if (m_selectedDelegationIndex != INVALID_SELECTED_DELEGATION_INDEX)
+    {
+      return true;
+    }
+  return false;
+}
+
+Name
+Interest::getSelectedDelegation() const
+{
+  if (!hasSelectedDelegation()) {
+    throw Error("There is no encapsulated selected delegation");
+  }
+  return std::get<1>(Link::getDelegationFromWire(m_link, m_selectedDelegationIndex));
+}
+
+void
+Interest::setSelectedDelegation(const Name& delegationName)
+{
+  size_t delegationIndex = Link::findDelegationFromWire(m_link, delegationName);
+  if (delegationIndex != INVALID_SELECTED_DELEGATION_INDEX) {
+    m_selectedDelegationIndex = delegationIndex;
+  }
+  else {
+    throw std::invalid_argument("Invalid selected delegation name");
+  }
+  m_wire.reset();
+}
+
+void
+Interest::setSelectedDelegation(size_t delegationIndex)
+{
+  if (delegationIndex >= Link(m_link).getDelegations().size()) {
+    throw Error("Invalid selected delegation index");
+  }
+  m_selectedDelegationIndex = delegationIndex;
+  m_wire.reset();
+}
+
+void
+Interest::unsetSelectedDelegation()
+{
+  m_selectedDelegationIndex = INVALID_SELECTED_DELEGATION_INDEX;
+  m_wire.reset();
 }
 
 std::ostream&
diff --git a/src/interest.hpp b/src/interest.hpp
index 48f34f7..1e064fb 100644
--- a/src/interest.hpp
+++ b/src/interest.hpp
@@ -29,6 +29,7 @@
 #include "util/time.hpp"
 #include "management/nfd-local-control-header.hpp"
 #include "tag-host.hpp"
+#include "link.hpp"
 
 namespace ndn {
 
@@ -130,6 +131,76 @@
   std::string
   toUri() const;
 
+public: // Link and forwarding hint
+
+   /**
+   * @brief Check whether the Interest contains a Link object
+   * @return True if there is a link object, otherwise false
+   */
+  bool
+  hasLink() const;
+
+  /**
+   * @brief Get the link object for this interest
+   * @return The link object if there is one contained in this interest
+   * @throws Interest::Error if there is no link object contained in the interest
+   */
+  Link
+  getLink() const;
+
+  /**
+   * @brief Set the link object for this interest
+   * @param link The link object that will be included in this interest (in wire format)
+   * @post !hasSelectedDelegation()
+   */
+  void
+  setLink(const Block& link);
+
+  /**
+   *@brief Reset the wire format of the given interest and the contained link
+   */
+  void
+  unsetLink();
+
+  /**
+   * @brief Check whether the Interest includes a selected delegation
+   * @return True if there is a selected delegation, otherwise false
+   */
+  bool
+  hasSelectedDelegation() const;
+
+  /**
+   * @brief Get the name of the selected delegation
+   * @return The name of the selected delegation
+   * @throw Error SelectedDelegation is not set.
+   */
+  Name
+  getSelectedDelegation() const;
+
+  /**
+   * @brief Set the selected delegation
+   * @param delegationName The name of the selected delegation
+   * @throw Error Link is not set.
+   * @throw std::invalid_argument @p delegationName does not exist in Link.
+   */
+  void
+  setSelectedDelegation(const Name& delegationName);
+
+  /**
+   * @brief Set the selected delegation
+   * @param delegation The index of the selected delegation
+   * @throw Error Link is not set.
+   * @throw std::out_of_range @p delegationIndex is out of bound in Link.
+   */
+  void
+  setSelectedDelegation(size_t delegationIndex);
+
+   /**
+   * @brief Unset the selected delegation
+   */
+  void
+  unsetSelectedDelegation();
+
 public: // matching
   /** @brief Check if Interest, including selectors, matches the given @p name
    *  @param name The name to be matched. If this is a Data name, it shall contain the
@@ -395,6 +466,8 @@
   int m_scope;
   time::milliseconds m_interestLifetime;
 
+  mutable Block m_link;
+  size_t m_selectedDelegationIndex;
   mutable Block m_wire;
 
   nfd::LocalControlHeader m_localControlHeader;
diff --git a/src/link.cpp b/src/link.cpp
index 07ac0d9..ce05b36 100644
--- a/src/link.cpp
+++ b/src/link.cpp
@@ -179,6 +179,53 @@
   decodeContent();
 }
 
+std::tuple<uint32_t, Name>
+Link::getDelegationFromWire(const Block& block, size_t index)
+{
+  block.parse();
+  const Block& contentBlock = block.get(tlv::Content);
+  contentBlock.parse();
+  const Block& delegationBlock = contentBlock.elements().at(index);
+  delegationBlock.parse();
+  if (delegationBlock.type() != tlv::LinkDelegation) {
+    throw Error("Unexpected TLV-TYPE; expecting LinkDelegation");
+  }
+  return std::make_tuple(
+    static_cast<uint32_t>(
+      readNonNegativeInteger(delegationBlock.get(tlv::LinkPreference))),
+    Name(delegationBlock.get(tlv::Name)));
+}
+
+ssize_t
+Link::findDelegationFromWire(const Block& block, const Name& delegationName)
+{
+  block.parse();
+  const Block& contentBlock = block.get(tlv::Content);
+  contentBlock.parse();
+  size_t counter = 0;
+  for (auto&& delegationBlock : contentBlock.elements()) {
+    delegationBlock.parse();
+    if (delegationBlock.type() != tlv::LinkDelegation) {
+      throw Error("Unexpected TLV-TYPE; expecting LinkDelegation");
+    }
+    Name name(delegationBlock.get(tlv::Name));
+    if (name == delegationName) {
+      return counter;
+    }
+    ++counter;
+  }
+  return INVALID_SELECTED_DELEGATION_INDEX;
+}
+
+ssize_t
+Link::countDelegationsFromWire(const Block& block)
+{
+  block.parse();
+  const Block& contentBlock = block.get(tlv::Content);
+  contentBlock.parse();
+  return contentBlock.elements_size();
+}
+
 bool
 Link::removeDelegationNoEncode(const Name& name)
 {
diff --git a/src/link.hpp b/src/link.hpp
index 3ccb7a3..e4e9f38 100644
--- a/src/link.hpp
+++ b/src/link.hpp
@@ -129,12 +129,34 @@
 
   /**
    * @brief Decode from the wire format
+   * @warning This method does not preserve the relative order between delegations.
+   *     To get a delegation by index, use \p getDelegationFromWire method.
    */
   void
   wireDecode(const Block& wire);
 
+  /** @brief gets the delegation at \p index from \p block
+   *  @param block wire format of a Link object
+   *  @param index 0-based index of a delegation in the Link object
+   *  @return delegation preference and name
+   *  @throw std::out_of_range index is out of range
+   */
+  static std::tuple<uint32_t, Name>
+  getDelegationFromWire(const Block& block, size_t index);
+
+  /** @brief finds index of a delegation with \p delegationName from \p block
+   *  @param block wire format of a Link object
+   *  @return 0-based index of the first delegation with \p delegationName ,
+   *          or -1 if no such delegation exists
+   */
+  static ssize_t
+  findDelegationFromWire(const Block& block, const Name& delegationName);
+
+  static ssize_t
+  countDelegationsFromWire(const Block& block);
+
 protected:
-  /** \brief prepend Link object as a Content block to the encoder
+  /** @brief prepend Link object as a Content block to the encoder
    *
    *  The outermost Content element is not part of Link object structure.
    */