interest: implement HopLimit encoding/decoding

Refs: #4806
Change-Id: I42ad15221575001d66747aa9f7eafcf3f00acf47
diff --git a/ndn-cxx/interest.cpp b/ndn-cxx/interest.cpp
index a984242..a25147b 100644
--- a/ndn-cxx/interest.cpp
+++ b/ndn-cxx/interest.cpp
@@ -98,7 +98,7 @@
 #endif // NDN_CXX_HAVE_TESTS
   }
 
-  if (hasApplicationParameters()) {
+  if (getHopLimit() || hasApplicationParameters()) {
     return encode03(encoder);
   }
   else {
@@ -133,7 +133,7 @@
   }
 
   // Nonce
-  uint32_t nonce = getNonce(); // if nonce was unset, getNonce generates a random nonce
+  uint32_t nonce = getNonce(); // if nonce was unset, this generates a fresh nonce
   totalLength += encoder.prependByteArrayBlock(tlv::Nonce, reinterpret_cast<uint8_t*>(&nonce), sizeof(nonce));
 
   // Selectors
@@ -183,7 +183,11 @@
     totalLength += encoder.prependBlock(b);
   });
 
-  // HopLimit: not yet supported
+  // HopLimit
+  if (getHopLimit()) {
+    uint8_t hopLimit = *getHopLimit();
+    totalLength += encoder.prependByteArrayBlock(tlv::HopLimit, &hopLimit, sizeof(hopLimit));
+  }
 
   // InterestLifetime
   if (getInterestLifetime() != DEFAULT_INTEREST_LIFETIME) {
@@ -192,7 +196,7 @@
   }
 
   // Nonce
-  uint32_t nonce = getNonce(); // if nonce was unset, getNonce generates a random nonce
+  uint32_t nonce = getNonce(); // if nonce was unset, this generates a fresh nonce
   totalLength += encoder.prependByteArrayBlock(tlv::Nonce, reinterpret_cast<uint8_t*>(&nonce), sizeof(nonce));
 
   // ForwardingHint
@@ -229,10 +233,10 @@
   EncodingEstimator estimator;
   size_t estimatedSize = wireEncode(estimator);
 
-  EncodingBuffer buffer(estimatedSize, 0);
-  wireEncode(buffer);
+  EncodingBuffer encoder(estimatedSize, 0);
+  wireEncode(encoder);
 
-  const_cast<Interest*>(this)->wireDecode(buffer.block());
+  const_cast<Interest*>(this)->wireDecode(encoder.block());
   return m_wire;
 }
 
@@ -248,9 +252,7 @@
 
   if (!decode02()) {
     decode03();
-    if (!hasNonce()) {
-      setNonce(getNonce());
-    }
+    getNonce(); // force generation of nonce
   }
 
   m_isCanBePrefixSet = true; // don't trigger warning from decoded packet
@@ -351,9 +353,10 @@
   m_name = std::move(tempName);
 
   m_selectors = Selectors().setMaxSuffixComponents(1); // CanBePrefix=0
+  m_forwardingHint = {};
   m_nonce.reset();
   m_interestLifetime = DEFAULT_INTEREST_LIFETIME;
-  m_forwardingHint = {};
+  m_hopLimit.reset();
   m_parameters.clear();
 
   int lastElement = 1; // last recognized element index, in spec order
@@ -417,7 +420,7 @@
         if (element->value_size() != 1) {
           NDN_THROW(Error("HopLimit element is malformed"));
         }
-        // TLV-VALUE is ignored
+        m_hopLimit = *element->value();
         lastElement = 7;
         break;
       }
@@ -536,10 +539,20 @@
   if (digestIndex == -2) {
     NDN_THROW(std::invalid_argument("Name cannot have more than one ParametersSha256DigestComponent"));
   }
-  m_name = name;
-  if (hasApplicationParameters()) {
-    addOrReplaceParametersDigestComponent();
+  if (name != m_name) {
+    m_name = name;
+    if (hasApplicationParameters()) {
+      addOrReplaceParametersDigestComponent();
+    }
+    m_wire.reset();
   }
+  return *this;
+}
+
+Interest&
+Interest::setForwardingHint(const DelegationList& value)
+{
+  m_forwardingHint = value;
   m_wire.reset();
   return *this;
 }
@@ -547,8 +560,9 @@
 uint32_t
 Interest::getNonce() const
 {
-  if (!m_nonce) {
+  if (!hasNonce()) {
     m_nonce = random::generateWord32();
+    m_wire.reset();
   }
   return *m_nonce;
 }
@@ -556,8 +570,10 @@
 Interest&
 Interest::setNonce(uint32_t nonce)
 {
-  m_nonce = nonce;
-  m_wire.reset();
+  if (nonce != m_nonce) {
+    m_nonce = nonce;
+    m_wire.reset();
+  }
   return *this;
 }
 
@@ -567,12 +583,11 @@
   if (!hasNonce())
     return;
 
-  uint32_t oldNonce = getNonce();
-  uint32_t newNonce = oldNonce;
-  while (newNonce == oldNonce)
-    newNonce = random::generateWord32();
+  uint32_t oldNonce = *m_nonce;
+  while (m_nonce == oldNonce)
+    m_nonce = random::generateWord32();
 
-  setNonce(newNonce);
+  m_wire.reset();
 }
 
 Interest&
@@ -581,16 +596,20 @@
   if (lifetime < 0_ms) {
     NDN_THROW(std::invalid_argument("InterestLifetime must be >= 0"));
   }
-  m_interestLifetime = lifetime;
-  m_wire.reset();
+  if (lifetime != m_interestLifetime) {
+    m_interestLifetime = lifetime;
+    m_wire.reset();
+  }
   return *this;
 }
 
 Interest&
-Interest::setForwardingHint(const DelegationList& value)
+Interest::setHopLimit(optional<uint8_t> hopLimit)
 {
-  m_forwardingHint = value;
-  m_wire.reset();
+  if (hopLimit != m_hopLimit) {
+    m_hopLimit = hopLimit;
+    m_wire.reset();
+  }
   return *this;
 }
 
diff --git a/ndn-cxx/interest.hpp b/ndn-cxx/interest.hpp
index 9c91c2e..432f68c 100644
--- a/ndn-cxx/interest.hpp
+++ b/ndn-cxx/interest.hpp
@@ -283,6 +283,19 @@
   Interest&
   setInterestLifetime(time::milliseconds lifetime);
 
+  optional<uint8_t>
+  getHopLimit() const
+  {
+    return m_hopLimit;
+  }
+
+  /** @brief Set the Interest's hop limit.
+   *
+   *  Use `setHopLimit(nullopt)` to remove any hop limit from the Interest.
+   */
+  Interest&
+  setHopLimit(optional<uint8_t> hopLimit);
+
   bool
   hasApplicationParameters() const noexcept
   {
@@ -539,9 +552,10 @@
   Name m_name;
   Selectors m_selectors; // NDN Packet Format v0.2 only
   mutable bool m_isCanBePrefixSet = false;
+  DelegationList m_forwardingHint;
   mutable optional<uint32_t> m_nonce;
   time::milliseconds m_interestLifetime;
-  DelegationList m_forwardingHint;
+  optional<uint8_t> m_hopLimit;
 
   // Stores the "Interest parameters", i.e., all maybe-unrecognized non-critical TLV
   // elements that appear at the end of the Interest, starting from ApplicationParameters.
diff --git a/tests/unit/interest.t.cpp b/tests/unit/interest.t.cpp
index e536755..e7354e9 100644
--- a/tests/unit/interest.t.cpp
+++ b/tests/unit/interest.t.cpp
@@ -55,12 +55,13 @@
   Interest i;
   BOOST_CHECK_EQUAL(i.hasWire(), false);
   BOOST_CHECK_EQUAL(i.getName(), "/");
+  BOOST_CHECK_EQUAL(i.hasSelectors(), false);
   BOOST_CHECK_EQUAL(i.getCanBePrefix(), true);
   BOOST_CHECK_EQUAL(i.getMustBeFresh(), false);
   BOOST_CHECK_EQUAL(i.getForwardingHint().empty(), true);
   BOOST_CHECK_EQUAL(i.hasNonce(), false);
   BOOST_CHECK_EQUAL(i.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
-  BOOST_CHECK_EQUAL(i.hasSelectors(), false);
+  BOOST_CHECK(i.getHopLimit() == nullopt);
   BOOST_CHECK_EQUAL(i.hasApplicationParameters(), false);
   BOOST_CHECK_EQUAL(i.getApplicationParameters().isValid(), false);
   BOOST_CHECK_EQUAL(i.isParametersDigestValid(), true);
@@ -94,8 +95,10 @@
   Interest i2(wire1);
   BOOST_CHECK_EQUAL(i2.getName(), "/local/ndn/prefix");
   BOOST_CHECK(i2.getSelectors().empty());
+  BOOST_CHECK_EQUAL(i2.getForwardingHint().empty(), true);
   BOOST_CHECK_EQUAL(i2.getNonce(), 1);
   BOOST_CHECK_EQUAL(i2.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
+  BOOST_CHECK(i2.getHopLimit() == nullopt);
   BOOST_CHECK_EQUAL(i2.hasApplicationParameters(), false);
   BOOST_CHECK_EQUAL(i2.isParametersDigestValid(), true);
 
@@ -135,9 +138,10 @@
   Interest i2(wire1);
   BOOST_CHECK_EQUAL(i2.getName(), "/local/ndn/prefix");
   BOOST_CHECK_EQUAL(i2.getMinSuffixComponents(), 1);
+  BOOST_CHECK_EQUAL(i2.getForwardingHint(), DelegationList({{1, "/A"}}));
   BOOST_CHECK_EQUAL(i2.getNonce(), 1);
   BOOST_CHECK_EQUAL(i2.getInterestLifetime(), 1000_ms);
-  BOOST_CHECK_EQUAL(i2.getForwardingHint(), DelegationList({{1, "/A"}}));
+  BOOST_CHECK(i2.getHopLimit() == nullopt);
   BOOST_CHECK_EQUAL(i2.hasApplicationParameters(), false);
   BOOST_CHECK_EQUAL(i2.isParametersDigestValid(), true);
 
@@ -220,6 +224,7 @@
 //  BOOST_CHECK_EQUAL(i2.getForwardingHint().empty(), true);
 //  BOOST_CHECK_EQUAL(i2.getNonce(), 1);
 //  BOOST_CHECK_EQUAL(i2.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
+//  BOOST_CHECK(i2.getHopLimit() == nullopt);
 //  BOOST_CHECK_EQUAL(i2.hasApplicationParameters(), false);
 //  BOOST_CHECK_EQUAL(i2.getApplicationParameters().isValid(), false);
 //  BOOST_CHECK_EQUAL(i2.getPublisherPublicKeyLocator().empty(), true);
@@ -261,6 +266,7 @@
   BOOST_CHECK_EQUAL(i2.getForwardingHint().empty(), true);
   BOOST_CHECK_EQUAL(i2.getNonce(), 1);
   BOOST_CHECK_EQUAL(i2.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
+  BOOST_CHECK(i2.getHopLimit() == nullopt);
   BOOST_CHECK_EQUAL(i2.hasApplicationParameters(), true);
   BOOST_CHECK_EQUAL(i2.getApplicationParameters(), "2404C0C1C2C3"_block);
   BOOST_CHECK_EQUAL(i2.getPublisherPublicKeyLocator().empty(), true);
@@ -269,7 +275,7 @@
 BOOST_AUTO_TEST_CASE(Full)
 {
   const uint8_t WIRE[] = {
-    0x05, 0x59, // Interest
+    0x05, 0x5c, // Interest
           0x07, 0x36, // Name
                 0x08, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, // GenericNameComponent
                 0x08, 0x03, 0x6e, 0x64, 0x6e, // GenericNameComponent
@@ -288,8 +294,10 @@
                             0x08, 0x01, 0x48,
           0x0a, 0x04, // Nonce
                 0x4a, 0xcb, 0x1e, 0x4c,
-          0x0c, 0x02, // Interest Lifetime
+          0x0c, 0x02, // InterestLifetime
                 0x76, 0xa1,
+          0x22, 0x01, // HopLimit
+                0xdc,
           0x24, 0x04, // ApplicationParameters
                 0xc0, 0xc1, 0xc2, 0xc3
   };
@@ -301,6 +309,7 @@
   i1.setForwardingHint(DelegationList({{15893, "/H"}}));
   i1.setNonce(0x4c1ecb4a);
   i1.setInterestLifetime(30369_ms);
+  i1.setHopLimit(220);
   i1.setApplicationParameters("2404C0C1C2C3"_block);
   i1.setMinSuffixComponents(1); // v0.2-only elements will not be encoded
   i1.setExclude(Exclude().excludeAfter(name::Component("J"))); // v0.2-only elements will not be encoded
@@ -318,6 +327,7 @@
   BOOST_CHECK_EQUAL(i2.hasNonce(), true);
   BOOST_CHECK_EQUAL(i2.getNonce(), 0x4c1ecb4a);
   BOOST_CHECK_EQUAL(i2.getInterestLifetime(), 30369_ms);
+  BOOST_CHECK_EQUAL(*i2.getHopLimit(), 220);
   BOOST_CHECK_EQUAL(i2.getApplicationParameters(), "2404C0C1C2C3"_block);
   BOOST_CHECK_EQUAL(i2.getMinSuffixComponents(), -1); // Default because minSuffixComponents was not encoded
   BOOST_CHECK_EQUAL(i2.getExclude().empty(), true); // Exclude was not encoded
@@ -359,6 +369,7 @@
     i.setForwardingHint({{10309, "/F"}});
     i.setNonce(0x03d645a8);
     i.setInterestLifetime(18554_ms);
+    i.setHopLimit(64);
     i.setPublisherPublicKeyLocator(Name("/K"));
     i.setApplicationParameters("2404A0A1A2A3"_block);
   }
@@ -378,6 +389,7 @@
   BOOST_CHECK_EQUAL(i.getForwardingHint().empty(), true);
   BOOST_CHECK_EQUAL(i.hasNonce(), true); // a random nonce is generated
   BOOST_CHECK_EQUAL(i.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
+  BOOST_CHECK(i.getHopLimit() == nullopt);
   BOOST_CHECK_EQUAL(i.getPublisherPublicKeyLocator().empty(), true);
   BOOST_CHECK_EQUAL(i.hasApplicationParameters(), false);
   BOOST_CHECK_EQUAL(i.getApplicationParameters().isValid(), false);
@@ -398,6 +410,7 @@
   BOOST_CHECK_EQUAL(i.getForwardingHint().empty(), true);
   BOOST_CHECK_EQUAL(i.hasNonce(), true); // a random nonce is generated
   BOOST_CHECK_EQUAL(i.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
+  BOOST_CHECK(i.getHopLimit() == nullopt);
   BOOST_CHECK_EQUAL(i.hasApplicationParameters(), false);
   BOOST_CHECK_EQUAL(i.getApplicationParameters().isValid(), false);
 }
@@ -414,17 +427,19 @@
   BOOST_CHECK_EQUAL(i.hasNonce(), true);
   BOOST_CHECK_EQUAL(i.getNonce(), 0x4c1ecb4a);
   BOOST_CHECK_EQUAL(i.getInterestLifetime(), 30369_ms);
-  // HopLimit=214 is not stored
+  BOOST_CHECK_EQUAL(*i.getHopLimit(), 214);
   BOOST_CHECK_EQUAL(i.hasApplicationParameters(), false);
   BOOST_CHECK_EQUAL(i.getApplicationParameters().isValid(), false);
 
   // encode without modification: retain original wire encoding
   BOOST_CHECK_EQUAL(i.wireEncode().value_size(), 49);
 
-  // modify then re-encode as v0.2 format
+  // modify then re-encode as v0.3 format: unrecognized elements are discarded
   i.setName("/J");
   BOOST_CHECK_EQUAL(i.wireEncode(),
-                    "0520 0703(08014A) 09021200 0A044ACB1E4C 0C0276A1 1E0B(1F09 1E023E15 0703080148)"_block);
+                    "0523 0703(08014A) "
+                    "2100 1200 1E0B(1F09 1E023E15 0703080148) "
+                    "0A044ACB1E4C 0C0276A1 2201D6"_block);
 }
 
 BOOST_AUTO_TEST_CASE(FullWithParameters)
@@ -440,30 +455,29 @@
   BOOST_CHECK_EQUAL(i.hasNonce(), true);
   BOOST_CHECK_EQUAL(i.getNonce(), 0x4c1ecb4a);
   BOOST_CHECK_EQUAL(i.getInterestLifetime(), 30369_ms);
-  // HopLimit=214 is not stored
+  BOOST_CHECK_EQUAL(*i.getHopLimit(), 214);
   BOOST_CHECK_EQUAL(i.hasApplicationParameters(), true);
   BOOST_CHECK_EQUAL(i.getApplicationParameters(), "2404C0C1C2C3"_block);
 
   // encode without modification: retain original wire encoding
   BOOST_CHECK_EQUAL(i.wireEncode().value_size(), 91);
 
-  // modify then re-encode as v0.3 format:
-  //  - unrecognized elements after ApplicationParameters are preserved, the rest are discarded
-  //  - HopLimit is dropped (encoding not implemented)
+  // modify then re-encode as v0.3 format: unrecognized elements
+  // after ApplicationParameters are preserved, the rest are discarded
   i.setName("/J");
   BOOST_CHECK_EQUAL(i.isParametersDigestValid(), true);
   BOOST_CHECK_EQUAL(i.wireEncode(),
-                    "054A 0725(08014A 0220F16DB273F40436A852063F864D5072B01EAD53151F5A688EA1560492BEBEDD05) "
+                    "054D 0725(08014A 0220F16DB273F40436A852063F864D5072B01EAD53151F5A688EA1560492BEBEDD05) "
                     "2100 1200 1E0B(1F09 1E023E15 0703080148) "
-                    "0A044ACB1E4C 0C0276A1 2404C0C1C2C3 FC00"_block);
+                    "0A044ACB1E4C 0C0276A1 2201D6 2404C0C1C2C3 FC00"_block);
 
   // modify ApplicationParameters: unrecognized elements are preserved
   i.setApplicationParameters("2402CAFE"_block);
   BOOST_CHECK_EQUAL(i.isParametersDigestValid(), true);
   BOOST_CHECK_EQUAL(i.wireEncode(),
-                    "0548 0725(08014A 02205FDA67967EE302FC457E41B7D3D51BA6A9379574D193FD88F64954BF16C2927A) "
+                    "054B 0725(08014A 02205FDA67967EE302FC457E41B7D3D51BA6A9379574D193FD88F64954BF16C2927A) "
                     "2100 1200 1E0B(1F09 1E023E15 0703080148) "
-                    "0A044ACB1E4C 0C0276A1 2402CAFE FC00"_block);
+                    "0A044ACB1E4C 0C0276A1 2201D6 2402CAFE FC00"_block);
 }
 
 BOOST_AUTO_TEST_CASE(CriticalElementOutOfOrder)
@@ -501,7 +515,7 @@
                "2201D6 2200 2404C0C1C2C3 22020101"_block);
   BOOST_CHECK_EQUAL(i.getName(),
                     "/I/params-sha256=ff9100e04eaadcf30674d98026a051ba25f56b69bfa026dcccd72c6ea0f7315a");
-  // HopLimit=214 is not stored
+  BOOST_CHECK_EQUAL(*i.getHopLimit(), 214);
   BOOST_CHECK_EQUAL(i.hasApplicationParameters(), true);
   BOOST_CHECK_EQUAL(i.getApplicationParameters(), "2404C0C1C2C3"_block);
 
@@ -510,6 +524,7 @@
                "2100 1200 0A044ACB1E4C 0C0276A1 2201D6 2404C0C1C2C3 2401EE"_block);
   BOOST_CHECK_EQUAL(i.getName(),
                     "/I/params-sha256=ff9100e04eaadcf30674d98026a051ba25f56b69bfa026dcccd72c6ea0f7315a");
+  BOOST_CHECK_EQUAL(*i.getHopLimit(), 214);
   BOOST_CHECK_EQUAL(i.hasApplicationParameters(), true);
   BOOST_CHECK_EQUAL(i.getApplicationParameters(), "2404C0C1C2C3"_block);
 }
@@ -625,12 +640,14 @@
 BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES(MatchesInterest, 1)
 BOOST_AUTO_TEST_CASE(MatchesInterest)
 {
-  Interest interest("/A");
-  interest.setCanBePrefix(true)
+  Interest interest;
+  interest.setName("/A")
+          .setCanBePrefix(true)
           .setMustBeFresh(true)
           .setForwardingHint({{1, "/H"}})
           .setNonce(2228)
-          .setInterestLifetime(5_s);
+          .setInterestLifetime(5_s)
+          .setHopLimit(90);
 
   Interest other;
   BOOST_CHECK_EQUAL(interest.matchesInterest(other), false);
@@ -652,6 +669,9 @@
 
   other.setInterestLifetime(3_s);
   BOOST_CHECK_EQUAL(interest.matchesInterest(other), true);
+
+  other.setHopLimit(31);
+  BOOST_CHECK_EQUAL(interest.matchesInterest(other), true);
 }
 
 BOOST_AUTO_TEST_CASE(SetName)
@@ -765,8 +785,7 @@
   BOOST_CHECK_THROW(Interest("/A", -1_ms), std::invalid_argument);
   BOOST_CHECK_NO_THROW(Interest("/A", 0_ms));
 
-  Interest i("/local/ndn/prefix");
-  i.setNonce(1);
+  Interest i;
   BOOST_CHECK_EQUAL(i.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
   BOOST_CHECK_THROW(i.setInterestLifetime(-1_ms), std::invalid_argument);
   BOOST_CHECK_EQUAL(i.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
@@ -774,6 +793,19 @@
   BOOST_CHECK_EQUAL(i.getInterestLifetime(), 0_ms);
   i.setInterestLifetime(1_ms);
   BOOST_CHECK_EQUAL(i.getInterestLifetime(), 1_ms);
+
+  i = Interest("/B", 15_s);
+  BOOST_CHECK_EQUAL(i.getInterestLifetime(), 15_s);
+}
+
+BOOST_AUTO_TEST_CASE(SetHopLimit)
+{
+  Interest i;
+  BOOST_CHECK(i.getHopLimit() == nullopt);
+  i.setHopLimit(42);
+  BOOST_CHECK(i.getHopLimit() == 42);
+  i.setHopLimit(nullopt);
+  BOOST_CHECK(i.getHopLimit() == nullopt);
 }
 
 BOOST_AUTO_TEST_CASE(SetApplicationParameters)
@@ -886,6 +918,15 @@
   BOOST_CHECK_EQUAL(a == b, true);
   BOOST_CHECK_EQUAL(a != b, false);
 
+  // compare ForwardingHint
+  a.setForwardingHint({{1, "/H"}});
+  BOOST_CHECK_EQUAL(a == b, false);
+  BOOST_CHECK_EQUAL(a != b, true);
+
+  b.setForwardingHint({{1, "/H"}});
+  BOOST_CHECK_EQUAL(a == b, true);
+  BOOST_CHECK_EQUAL(a != b, false);
+
   // compare Nonce
   a.setNonce(100);
   BOOST_CHECK_EQUAL(a == b, false);
@@ -904,12 +945,12 @@
   BOOST_CHECK_EQUAL(a == b, true);
   BOOST_CHECK_EQUAL(a != b, false);
 
-  // compare ForwardingHint
-  a.setForwardingHint({{1, "/H"}});
+  // compare HopLimit
+  a.setHopLimit(255);
   BOOST_CHECK_EQUAL(a == b, false);
   BOOST_CHECK_EQUAL(a != b, true);
 
-  b.setForwardingHint({{1, "/H"}});
+  b.setHopLimit(255);
   BOOST_CHECK_EQUAL(a == b, true);
   BOOST_CHECK_EQUAL(a != b, false);