interest: Cache Link object after the first call to getLink

Change-Id: I2625cdb665365cb3c1a69ec7d917d09ad621988d
Refs: #3158
diff --git a/src/interest.cpp b/src/interest.cpp
index d4ce90b..35be5ce 100644
--- a/src/interest.cpp
+++ b/src/interest.cpp
@@ -313,10 +313,9 @@
 
   // Selectors
   Block::element_const_iterator val = m_wire.find(tlv::Selectors);
-  if (val != m_wire.elements_end())
-    {
-      m_selectors.wireDecode(*val);
-    }
+  if (val != m_wire.elements_end()) {
+    m_selectors.wireDecode(*val);
+  }
   else
     m_selectors = Selectors();
 
@@ -325,27 +324,28 @@
 
   // InterestLifetime
   val = m_wire.find(tlv::InterestLifetime);
-  if (val != m_wire.elements_end())
-    {
-      m_interestLifetime = time::milliseconds(readNonNegativeInteger(*val));
-    }
-  else
-    {
-      m_interestLifetime = DEFAULT_INTEREST_LIFETIME;
-    }
+  if (val != m_wire.elements_end()) {
+    m_interestLifetime = time::milliseconds(readNonNegativeInteger(*val));
+  }
+  else {
+    m_interestLifetime = DEFAULT_INTEREST_LIFETIME;
+  }
 
   // Link object
+  m_linkCached.reset();
   val = m_wire.find(tlv::Data);
-  if (val != m_wire.elements_end())
-    {
-      m_link = (*val);
-    }
+  if (val != m_wire.elements_end()) {
+    m_link = (*val);
+  }
+  else {
+    m_link = Block();
+  }
 
   // SelectedDelegation
   val = m_wire.find(tlv::SelectedDelegation);
   if (val != m_wire.elements_end()) {
     if (!this->hasLink()) {
-      BOOST_THROW_EXCEPTION(Error("Interest contains selectedDelegation, but no LINK object"));
+      BOOST_THROW_EXCEPTION(Error("Interest contains SelectedDelegation, but no LINK object"));
     }
     uint64_t selectedDelegation = readNonNegativeInteger(*val);
     if (selectedDelegation < uint64_t(Link::countDelegationsFromWire(m_link))) {
@@ -355,23 +355,26 @@
       BOOST_THROW_EXCEPTION(Error("Invalid selected delegation index when decoding Interest"));
     }
   }
+  else {
+    m_selectedDelegationIndex = INVALID_SELECTED_DELEGATION_INDEX;
+  }
 }
 
 bool
 Interest::hasLink() const
 {
-  if (m_link.hasWire())
-    return true;
-  return false;
+  return m_link.hasWire();
 }
 
-Link
+const Link&
 Interest::getLink() const
 {
-  if (hasLink())
-    {
-      return Link(m_link);
+  if (hasLink()) {
+    if (!m_linkCached) {
+      m_linkCached = make_shared<Link>(m_link);
     }
+    return *m_linkCached;
+  }
   BOOST_THROW_EXCEPTION(Error("There is no encapsulated link object"));
 }
 
@@ -383,6 +386,7 @@
     BOOST_THROW_EXCEPTION(Error("The given link does not have a wire format"));
   }
   m_wire.reset();
+  m_linkCached.reset();
   this->unsetSelectedDelegation();
 }
 
@@ -391,17 +395,14 @@
 {
   m_link.reset();
   m_wire.reset();
+  m_linkCached.reset();
   this->unsetSelectedDelegation();
 }
 
 bool
 Interest::hasSelectedDelegation() const
 {
-  if (m_selectedDelegationIndex != INVALID_SELECTED_DELEGATION_INDEX)
-    {
-      return true;
-    }
-  return false;
+  return m_selectedDelegationIndex != INVALID_SELECTED_DELEGATION_INDEX;
 }
 
 Name
diff --git a/src/interest.hpp b/src/interest.hpp
index 80a1b18..686f0a0 100644
--- a/src/interest.hpp
+++ b/src/interest.hpp
@@ -134,8 +134,9 @@
    * @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
+   * @throws tlv::Error if the incorporated link object is malformed
    */
-  Link
+  const Link&
   getLink() const;
 
   /**
@@ -147,7 +148,8 @@
   setLink(const Block& link);
 
   /**
-   *@brief Reset the wire format of the given interest and the contained link
+   * @brief Delete the link object for this interest
+   * @post !hasLink()
    */
   void
   unsetLink();
@@ -442,6 +444,7 @@
   time::milliseconds m_interestLifetime;
 
   mutable Block m_link;
+  mutable shared_ptr<Link> m_linkCached;
   size_t m_selectedDelegationIndex;
   mutable Block m_wire;
 
diff --git a/src/link.cpp b/src/link.cpp
index d4de993..587f480 100644
--- a/src/link.cpp
+++ b/src/link.cpp
@@ -161,11 +161,11 @@
       preference = static_cast<uint32_t>(readNonNegativeInteger(*val));
     }
     catch (tlv::Error&) {
-      BOOST_THROW_EXCEPTION(Error("Missing preference field in Link Encoding"));
+      BOOST_THROW_EXCEPTION(Error("Missing Preference field in Link Encoding"));
     }
     ++val;
     if (val == delegation.elements_end()) {
-      BOOST_THROW_EXCEPTION(Error("Missing name field in Link Encoding"));
+      BOOST_THROW_EXCEPTION(Error("Missing Name field in Link Encoding"));
     }
     Name name(*val);
     m_delegations.insert({preference, name});
diff --git a/tests/unit-tests/interest.t.cpp b/tests/unit-tests/interest.t.cpp
index 106fbf0..66c25dc 100644
--- a/tests/unit-tests/interest.t.cpp
+++ b/tests/unit-tests/interest.t.cpp
@@ -169,6 +169,57 @@
           0x00
 };
 
+const uint8_t LINK[] = {
+  0x06, 0xda, // Data
+      0x07, 0x14, // Name
+          0x08, 0x05,
+              0x6c, 0x6f, 0x63, 0x61, 0x6c,
+          0x08, 0x03,
+              0x6e, 0x64, 0x6e,
+          0x08, 0x06,
+              0x70, 0x72, 0x65, 0x66, 0x69, 0x78,
+      0x14, 0x07, // MetaInfo
+          0x18, 0x01, // ContentType
+              0x01,
+          0x19, 0x02, // FreshnessPeriod
+              0x27, 0x10,
+      0x15, 0x1a, // Content
+          0x1f, 0x0c, // LinkDelegation
+              0x1e, 0x01, // LinkPreference
+                  0x0a,
+              0x07, 0x07, // Name
+                  0x08, 0x05,
+                      0x6c, 0x6f, 0x63, 0x61, 0x6c,
+          0x1f, 0x0a, // LinkDelegation
+              0x1e, 0x01, // LinkPreference
+                  0x14,
+              0x07, 0x05, // Name
+                  0x08, 0x03,
+                      0x6e, 0x64, 0x6e,
+       0x16, 0x1b, // SignatureInfo
+           0x1b, 0x01, // SignatureType
+               0x01,
+       0x1c, 0x16, // KeyLocator
+           0x07, 0x14, // Name
+               0x08, 0x04,
+                   0x74, 0x65, 0x73, 0x74,
+               0x08, 0x03,
+                   0x6b, 0x65, 0x79,
+               0x08, 0x07,
+                   0x6c, 0x6f, 0x63, 0x61, 0x74, 0x6f, 0x72,
+       0x17, 0x80, // SignatureValue
+           0x2f, 0xd6, 0xf1, 0x6e, 0x80, 0x6f, 0x10, 0xbe, 0xb1, 0x6f, 0x3e, 0x31, 0xec,
+           0xe3, 0xb9, 0xea, 0x83, 0x30, 0x40, 0x03, 0xfc, 0xa0, 0x13, 0xd9, 0xb3, 0xc6,
+           0x25, 0x16, 0x2d, 0xa6, 0x58, 0x41, 0x69, 0x62, 0x56, 0xd8, 0xb3, 0x6a, 0x38,
+           0x76, 0x56, 0xea, 0x61, 0xb2, 0x32, 0x70, 0x1c, 0xb6, 0x4d, 0x10, 0x1d, 0xdc,
+           0x92, 0x8e, 0x52, 0xa5, 0x8a, 0x1d, 0xd9, 0x96, 0x5e, 0xc0, 0x62, 0x0b, 0xcf,
+           0x3a, 0x9d, 0x7f, 0xca, 0xbe, 0xa1, 0x41, 0x71, 0x85, 0x7a, 0x8b, 0x5d, 0xa9,
+           0x64, 0xd6, 0x66, 0xb4, 0xe9, 0x8d, 0x0c, 0x28, 0x43, 0xee, 0xa6, 0x64, 0xe8,
+           0x55, 0xf6, 0x1c, 0x19, 0x0b, 0xef, 0x99, 0x25, 0x1e, 0xdc, 0x78, 0xb3, 0xa7,
+           0xaa, 0x0d, 0x14, 0x58, 0x30, 0xe5, 0x37, 0x6a, 0x6d, 0xdb, 0x56, 0xac, 0xa3,
+           0xfc, 0x90, 0x7a, 0xb8, 0x66, 0x9c, 0x0e, 0xf6, 0xb7, 0x64, 0xd1
+};
+
 const uint8_t InterestWithLinkMissingContentType[] = {
   0x05,  0xf8, // Interest
       0x07,  0x14, // Name
@@ -565,17 +616,11 @@
   BOOST_CHECK_EQUAL(a == b, true);
   BOOST_CHECK_EQUAL(a != b, false);
 
-  // Link object
-  Link link("test", {{10, "/test1"}, {20, "/test2"}, {100, "/test3"}});
-  KeyChain keyChain;
-  keyChain.sign(link);
-  Block wire = link.wireEncode();
-
-  a.setLink(wire);
+  a.setLink(Block(LINK, sizeof(LINK)));
   BOOST_CHECK_EQUAL(a == b, false);
   BOOST_CHECK_EQUAL(a != b, true);
 
-  b.setLink(wire);
+  b.setLink(Block(LINK, sizeof(LINK)));
   BOOST_CHECK_EQUAL(a == b, true);
   BOOST_CHECK_EQUAL(a != b, false);
 
@@ -583,11 +628,11 @@
   BOOST_CHECK_EQUAL(a.hasSelectedDelegation(), false);
   BOOST_CHECK_EQUAL(b.hasSelectedDelegation(), false);
 
-  a.setSelectedDelegation(Name("test2"));
+  a.setSelectedDelegation(Name("/local"));
   BOOST_CHECK_EQUAL(a == b, false);
   BOOST_CHECK_EQUAL(a != b, true);
 
-  b.setSelectedDelegation(Name("test2"));
+  b.setSelectedDelegation(Name("/local"));
   BOOST_CHECK_EQUAL(a == b, true);
   BOOST_CHECK_EQUAL(a != b, false);
 }
@@ -668,7 +713,7 @@
 
 BOOST_AUTO_TEST_CASE(LinkObject)
 {
-  Link link1("test", {{10, "/test1"}, {20, "/test2"}, {100, "/test3"}});
+  Link link1("test", {{100, "/test3"}, {20, "/test2"}, {10, "/test1"}});
   KeyChain keyChain;
   keyChain.sign(link1);
   Block wire = link1.wireEncode();
@@ -695,6 +740,9 @@
   BOOST_CHECK_EQUAL(std::get<0>(*i), 100);
   BOOST_CHECK_EQUAL(std::get<1>(*i), Name("test3"));
 
+  a.setLink(Block(LINK, sizeof(LINK)));
+  BOOST_CHECK_EQUAL(a.getLink().getDelegations().size(), 2);
+
   a.unsetLink();
   BOOST_CHECK_EQUAL(a.hasLink(), false);
 }
@@ -804,7 +852,8 @@
 
   ndn::Interest i;
   BOOST_REQUIRE_NO_THROW(i.wireDecode(interestBlock));
-  BOOST_REQUIRE_THROW(i.getLink(), Block::Error);
+  BOOST_CHECK_THROW(i.getLink(), tlv::Error);
+  BOOST_CHECK_THROW(i.getLink(), tlv::Error);
 }
 
 BOOST_AUTO_TEST_CASE(LinkObjectWrongContentType)
@@ -814,7 +863,7 @@
 
   ndn::Interest i;
   BOOST_REQUIRE_NO_THROW(i.wireDecode(interestBlock));
-  BOOST_REQUIRE_THROW(i.getLink(), Link::Error);
+  BOOST_CHECK_THROW(i.getLink(), Link::Error);
 }
 
 BOOST_AUTO_TEST_CASE(InterestContainingSelectedDelegationButNoLink)
@@ -823,7 +872,7 @@
                       sizeof(InterestWithSelectedDelegationButNoLink));
 
   ndn::Interest i;
-  BOOST_REQUIRE_THROW(i.wireDecode(interestBlock), Interest::Error);
+  BOOST_CHECK_THROW(i.wireDecode(interestBlock), Interest::Error);
 }
 
 BOOST_AUTO_TEST_CASE(SelectedDelegationIsNotNonNegativeInteger)
@@ -832,7 +881,7 @@
                       sizeof(InterestWithLinkNotNonIntegerSelectedDelegation));
 
   ndn::Interest i;
-  BOOST_REQUIRE_THROW(i.wireDecode(interestBlock), tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode(interestBlock), tlv::Error);
 }
 
 BOOST_AUTO_TEST_CASE(SelectedDelegationEqualToDelegationCount)
@@ -848,7 +897,7 @@
   a.setNonce(100);
   a.setInterestLifetime(time::seconds(10));
   a.setLink(wire);
-  BOOST_REQUIRE_THROW(a.setSelectedDelegation(3), Interest::Error);
+  BOOST_CHECK_THROW(a.setSelectedDelegation(3), Interest::Error);
 }
 
 BOOST_AUTO_TEST_CASE(SelectedDelegationGreaterThanDelegationCount)
@@ -864,7 +913,7 @@
   a.setNonce(100);
   a.setInterestLifetime(time::seconds(10));
   a.setLink(wire);
-  BOOST_REQUIRE_THROW(a.setSelectedDelegation(4), Interest::Error);
+  BOOST_CHECK_THROW(a.setSelectedDelegation(4), Interest::Error);
 }
 
 BOOST_AUTO_TEST_CASE(Decode)
@@ -885,6 +934,8 @@
   BOOST_CHECK_EQUAL(i.getMustBeFresh(), false);
   BOOST_CHECK_EQUAL(i.getExclude().toUri(), "alex,xxxx,*,yyyy");
   BOOST_CHECK_EQUAL(i.getNonce(), 1U);
+  BOOST_CHECK_EQUAL(i.hasLink(), false);
+  BOOST_CHECK_EQUAL(i.hasSelectedDelegation(), false);
 }
 
 BOOST_AUTO_TEST_CASE(DecodeFromStream)
@@ -905,6 +956,8 @@
   BOOST_CHECK_EQUAL(i.getMustBeFresh(), false);
   BOOST_CHECK_EQUAL(i.getExclude().toUri(), "alex,xxxx,*,yyyy");
   BOOST_CHECK_EQUAL(i.getNonce(), 1U);
+  BOOST_CHECK_EQUAL(i.hasLink(), false);
+  BOOST_CHECK_EQUAL(i.hasSelectedDelegation(), false);
 }
 
 BOOST_AUTO_TEST_CASE(Encode)
@@ -955,6 +1008,35 @@
   BOOST_CHECK_NE(i.getNonce(), 2);
 }
 
+BOOST_AUTO_TEST_CASE(DecodeEncode) // this test case to ensure that wireDecode resets all the fields
+{
+  Interest i1;
+  i1.setName("/test");
+  i1.setMinSuffixComponents(100);
+  i1.setNonce(10);
+  i1.setInterestLifetime(time::seconds(10));
+  i1.setLink(Block(LINK, sizeof(LINK)));
+  i1.setSelectedDelegation(0);
+
+  Interest i2(i1.wireEncode());
+
+  BOOST_CHECK_EQUAL(i2.getName().toUri(), "/test");
+  BOOST_CHECK_EQUAL(i2.getInterestLifetime(), time::seconds(10));
+  BOOST_CHECK_EQUAL(i2.getMinSuffixComponents(), 100);
+  BOOST_CHECK_EQUAL(i2.getNonce(), 10);
+  BOOST_CHECK_EQUAL(i2.hasLink(), true);
+  BOOST_CHECK_EQUAL(i2.hasSelectedDelegation(), true);
+
+  i2.wireDecode(Interest().wireEncode());
+
+  BOOST_CHECK_EQUAL(i2.getName().toUri(), "/");
+  BOOST_CHECK_EQUAL(i2.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
+  BOOST_CHECK_EQUAL(i2.getMinSuffixComponents(), -1);
+  BOOST_WARN_NE(i2.getNonce(), 10);
+  BOOST_CHECK_EQUAL(i2.hasLink(), false);
+  BOOST_CHECK_EQUAL(i2.hasSelectedDelegation(), false);
+}
+
 BOOST_AUTO_TEST_CASE(EncodeWithLocalHeader)
 {
   ndn::Interest interest(ndn::Name("/local/ndn/prefix"));