interest: recognize Interest in Packet Format v0.3

Interest::wireDecode accepts both v0.2 and v0.3 formats,
but Interest::wireEncode only encodes into v0.2 format.

refs #4527

Change-Id: I784b6f1d5c0d93db33155efddc0180359d8cab74
diff --git a/src/encoding/tlv.hpp b/src/encoding/tlv.hpp
index 8fbe92d..10961c5 100644
--- a/src/encoding/tlv.hpp
+++ b/src/encoding/tlv.hpp
@@ -156,7 +156,7 @@
  * @brief Determine whether a TLV-TYPE is "critical" for evolvability purpose.
  * @sa https://named-data.net/doc/NDN-packet-spec/0.3/tlv.html#considerations-for-evolvability-of-tlv-based-encoding
  */
-inline bool
+constexpr bool
 isCriticalType(uint32_t type)
 {
   return type <= 31 || (type & 0x01);
diff --git a/src/interest.cpp b/src/interest.cpp
index 40e78e4..f08eeb2 100644
--- a/src/interest.cpp
+++ b/src/interest.cpp
@@ -121,49 +121,193 @@
   m_wire = wire;
   m_wire.parse();
 
-  if (m_wire.type() != tlv::Interest)
-    BOOST_THROW_EXCEPTION(Error("Unexpected TLV number when decoding Interest"));
+  if (m_wire.type() != tlv::Interest) {
+    BOOST_THROW_EXCEPTION(Error("expecting Interest element, got " + to_string(m_wire.type())));
+  }
+
+  if (!decode02()) {
+    decode03();
+    if (!hasNonce()) {
+      setNonce(getNonce());
+    }
+  }
+}
+
+bool
+Interest::decode02()
+{
+  auto ele = m_wire.elements_begin();
 
   // Name
-  m_name.wireDecode(m_wire.get(tlv::Name));
-
-  // Selectors
-  Block::element_const_iterator val = m_wire.find(tlv::Selectors);
-  if (val != m_wire.elements_end()) {
-    m_selectors.wireDecode(*val);
+  if (ele != m_wire.elements_end() && ele->type() == tlv::Name) {
+    m_name.wireDecode(*ele);
+    ++ele;
   }
-  else
+  else {
+    return false;
+  }
+
+  // Selectors?
+  if (ele != m_wire.elements_end() && ele->type() == tlv::Selectors) {
+    m_selectors.wireDecode(*ele);
+    ++ele;
+  }
+  else {
     m_selectors = Selectors();
+  }
 
   // Nonce
-  val = m_wire.find(tlv::Nonce);
-  if (val == m_wire.elements_end()) {
-    BOOST_THROW_EXCEPTION(Error("Nonce element is missing"));
+  if (ele != m_wire.elements_end() && ele->type() == tlv::Nonce) {
+    uint32_t nonce = 0;
+    if (ele->value_size() != sizeof(nonce)) {
+      BOOST_THROW_EXCEPTION(Error("Nonce element is malformed"));
+    }
+    std::memcpy(&nonce, ele->value(), sizeof(nonce));
+    m_nonce = nonce;
+    ++ele;
   }
-  uint32_t nonce = 0;
-  if (val->value_size() != sizeof(nonce)) {
-    BOOST_THROW_EXCEPTION(Error("Nonce element is malformed"));
+  else {
+    return false;
   }
-  std::memcpy(&nonce, val->value(), sizeof(nonce));
-  m_nonce = nonce;
 
-  // InterestLifetime
-  val = m_wire.find(tlv::InterestLifetime);
-  if (val != m_wire.elements_end()) {
-    m_interestLifetime = time::milliseconds(readNonNegativeInteger(*val));
+  // InterestLifetime?
+  if (ele != m_wire.elements_end() && ele->type() == tlv::InterestLifetime) {
+    m_interestLifetime = time::milliseconds(readNonNegativeInteger(*ele));
+    ++ele;
   }
   else {
     m_interestLifetime = DEFAULT_INTEREST_LIFETIME;
   }
 
-  // ForwardingHint
-  val = m_wire.find(tlv::ForwardingHint);
-  if (val != m_wire.elements_end()) {
-    m_forwardingHint.wireDecode(*val, false);
+  // ForwardingHint?
+  if (ele != m_wire.elements_end() && ele->type() == tlv::ForwardingHint) {
+    m_forwardingHint.wireDecode(*ele, false);
+    ++ele;
   }
   else {
     m_forwardingHint = DelegationList();
   }
+
+  return ele == m_wire.elements_end();
+}
+
+void
+Interest::decode03()
+{
+  // Interest ::= INTEREST-TYPE TLV-LENGTH
+  //                Name
+  //                CanBePrefix?
+  //                MustBeFresh?
+  //                ForwardingHint?
+  //                Nonce?
+  //                InterestLifetime?
+  //                HopLimit?
+  //                Parameters?
+
+  bool hasName = false;
+  m_selectors = Selectors().setMaxSuffixComponents(1); // CanBePrefix=0
+  m_nonce.reset();
+  m_interestLifetime = DEFAULT_INTEREST_LIFETIME;
+  m_forwardingHint = DelegationList();
+
+  int lastEle = 0; // last recognized element index, in spec order
+  for (const Block& ele : m_wire.elements()) {
+    switch (ele.type()) {
+      case tlv::Name: {
+        if (lastEle >= 1) {
+          BOOST_THROW_EXCEPTION(Error("Name element is out of order"));
+        }
+        hasName = true;
+        m_name.wireDecode(ele);
+        if (m_name.empty()) {
+          BOOST_THROW_EXCEPTION(Error("Name has zero name components"));
+        }
+        lastEle = 1;
+        break;
+      }
+      case tlv::CanBePrefix: {
+        if (lastEle >= 2) {
+          BOOST_THROW_EXCEPTION(Error("CanBePrefix element is out of order"));
+        }
+        if (ele.value_size() != 0) {
+          BOOST_THROW_EXCEPTION(Error("CanBePrefix element has non-zero TLV-LENGTH"));
+        }
+        m_selectors.setMaxSuffixComponents(-1);
+        lastEle = 2;
+        break;
+      }
+      case tlv::MustBeFresh: {
+        if (lastEle >= 3) {
+          BOOST_THROW_EXCEPTION(Error("MustBeFresh element is out of order"));
+        }
+        if (ele.value_size() != 0) {
+          BOOST_THROW_EXCEPTION(Error("MustBeFresh element has non-zero TLV-LENGTH"));
+        }
+        m_selectors.setMustBeFresh(true);
+        lastEle = 3;
+        break;
+      }
+      case tlv::ForwardingHint: {
+        if (lastEle >= 4) {
+          BOOST_THROW_EXCEPTION(Error("ForwardingHint element is out of order"));
+        }
+        m_forwardingHint.wireDecode(ele);
+        lastEle = 4;
+        break;
+      }
+      case tlv::Nonce: {
+        if (lastEle >= 5) {
+          BOOST_THROW_EXCEPTION(Error("Nonce element is out of order"));
+        }
+        uint32_t nonce = 0;
+        if (ele.value_size() != sizeof(nonce)) {
+          BOOST_THROW_EXCEPTION(Error("Nonce element is malformed"));
+        }
+        std::memcpy(&nonce, ele.value(), sizeof(nonce));
+        m_nonce = nonce;
+        lastEle = 5;
+        break;
+      }
+      case tlv::InterestLifetime: {
+        if (lastEle >= 6) {
+          BOOST_THROW_EXCEPTION(Error("InterestLifetime element is out of order"));
+        }
+        m_interestLifetime = time::milliseconds(readNonNegativeInteger(ele));
+        lastEle = 6;
+        break;
+      }
+      case tlv::HopLimit: {
+        if (lastEle >= 7) {
+          break; // HopLimit is non-critical, ignore out-of-order appearance
+        }
+        if (ele.value_size() != 1) {
+          BOOST_THROW_EXCEPTION(Error("HopLimit element is malformed"));
+        }
+        // TLV-VALUE is ignored
+        lastEle = 7;
+        break;
+      }
+      case tlv::Parameters: {
+        if (lastEle >= 8) {
+          BOOST_THROW_EXCEPTION(Error("Parameters element is out of order"));
+        }
+        // TLV-VALUE is ignored
+        lastEle = 8;
+        break;
+      }
+      default: {
+        if (tlv::isCriticalType(ele.type())) {
+          BOOST_THROW_EXCEPTION(Error("unrecognized element of critical type " +
+                                      to_string(ele.type())));
+        }
+        break;
+      }
+    }
+  }
+
+  if (!hasName) {
+    BOOST_THROW_EXCEPTION(Error("Name element is missing"));
+  }
 }
 
 std::string
diff --git a/src/interest.hpp b/src/interest.hpp
index d547daa..5fc1c42 100644
--- a/src/interest.hpp
+++ b/src/interest.hpp
@@ -73,12 +73,16 @@
   size_t
   wireEncode(EncodingImpl<TAG>& encoder) const;
 
-  /** @brief Encode to a @c Block in NDN Packet Format v0.2.
+  /** @brief Encode to a @c Block.
+   *
+   *  Normally, this function encodes to NDN Packet Format v0.2. However, if this instance has
+   *  cached wire encoding (@c hasWire() is true), the cached encoding is returned and it might
+   *  be in v0.3 format.
    */
   const Block&
   wireEncode() const;
 
-  /** @brief Decode from @p wire in NDN Packet Format v0.2.
+  /** @brief Decode from @p wire in NDN Packet Format v0.2 or v0.3.
    */
   void
   wireDecode(const Block& wire);
@@ -372,6 +376,21 @@
   }
 
 private:
+  /** @brief Decode @c m_wire as NDN Packet Format v0.2.
+   *  @retval true decoding successful.
+   *  @retval false decoding failed due to structural error.
+   *  @throw tlv::Error decoding error within a sub-element.
+   */
+  bool
+  decode02();
+
+  /** @brief Decode @c m_wire as NDN Packet Format v0.3.
+   *  @throw tlv::Error decoding error.
+   */
+  void
+  decode03();
+
+private:
   Name m_name;
   Selectors m_selectors;
   mutable optional<uint32_t> m_nonce;
diff --git a/tests/unit-tests/interest.t.cpp b/tests/unit-tests/interest.t.cpp
index 4d970d5..2860551 100644
--- a/tests/unit-tests/interest.t.cpp
+++ b/tests/unit-tests/interest.t.cpp
@@ -24,6 +24,7 @@
 #include "security/digest-sha256.hpp"
 #include "security/signature-sha256-with-rsa.hpp"
 
+#include "block-literal.hpp"
 #include "boost-test.hpp"
 #include "identity-management-fixture.hpp"
 
@@ -37,13 +38,17 @@
 BOOST_AUTO_TEST_CASE(DefaultConstructor)
 {
   Interest i;
+  BOOST_CHECK(!i.hasWire());
   BOOST_CHECK_EQUAL(i.getName(), "/");
-  BOOST_CHECK(i.getSelectors().empty());
-  BOOST_CHECK_EQUAL(i.hasNonce(), false);
+  BOOST_CHECK_EQUAL(i.getCanBePrefix(), true);
+  BOOST_CHECK_EQUAL(i.getMustBeFresh(), false);
+  BOOST_CHECK(i.getForwardingHint().empty());
+  BOOST_CHECK(!i.hasNonce());
   BOOST_CHECK_EQUAL(i.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
+  BOOST_CHECK(!i.hasSelectors());
 }
 
-BOOST_AUTO_TEST_CASE(EncodeDecodeBasic)
+BOOST_AUTO_TEST_CASE(EncodeDecode02Basic)
 {
   const uint8_t WIRE[] = {
     0x05, 0x1c, // Interest
@@ -69,7 +74,7 @@
   BOOST_CHECK_EQUAL(i1, i2);
 }
 
-BOOST_AUTO_TEST_CASE(EncodeDecodeFull)
+BOOST_AUTO_TEST_CASE(EncodeDecode02Full)
 {
   const uint8_t WIRE[] = {
     0x05, 0x31, // Interest
@@ -108,58 +113,144 @@
   BOOST_CHECK_EQUAL(i1, i2);
 }
 
-BOOST_AUTO_TEST_CASE(WireDecodeReset) // checks wireDecode resets all fields
+class Decode03Fixture
 {
-  Interest i1;
-  i1.setName("/test");
-  i1.setMinSuffixComponents(100);
-  i1.setNonce(10);
-  i1.setInterestLifetime(10_s);
+protected:
+  Decode03Fixture()
+  {
+    // initialize all elements to non-empty, to verify wireDecode clears them
+    i.setName("/A");
+    i.setForwardingHint({{10309, "/F"}});
+    i.setNonce(0x03d645a8);
+    i.setInterestLifetime(18554_ms);
+    i.setPublisherPublicKeyLocator(Name("/K"));
+  }
 
-  Interest i2(i1.wireEncode());
-  BOOST_CHECK_EQUAL(i2.getName().toUri(), "/test");
-  BOOST_CHECK_EQUAL(i2.getInterestLifetime(), 10_s);
-  BOOST_CHECK_EQUAL(i2.getMinSuffixComponents(), 100);
-  BOOST_CHECK_EQUAL(i2.getNonce(), 10);
-
-  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_AUTO_TEST_CASE(DecodeNoName)
-{
-  Block b(tlv::Interest);
-  b.push_back(makeBinaryBlock(tlv::Nonce, "FISH", 4));
-  b.encode();
-
+protected:
   Interest i;
-  BOOST_CHECK_THROW(i.wireDecode(b), tlv::Error);
-}
+};
 
-BOOST_AUTO_TEST_CASE(DecodeNoNonce)
+BOOST_FIXTURE_TEST_SUITE(Decode03, Decode03Fixture)
+
+BOOST_AUTO_TEST_CASE(Minimal)
 {
-  Block b(tlv::Interest);
-  b.push_back(Name("/YvzNKtPWh").wireEncode());
-  b.encode();
+  i.wireDecode("0505 0703080149"_block);
+  BOOST_CHECK_EQUAL(i.getName(), "/I");
+  BOOST_CHECK_EQUAL(i.getCanBePrefix(), false);
+  BOOST_CHECK_EQUAL(i.getMustBeFresh(), false);
+  BOOST_CHECK(i.getForwardingHint().empty());
+  BOOST_CHECK(i.hasNonce()); // a random nonce is generated
+  BOOST_CHECK_EQUAL(i.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
+  BOOST_CHECK(i.getPublisherPublicKeyLocator().empty());
 
-  Interest i;
-  BOOST_CHECK_THROW(i.wireDecode(b), tlv::Error);
+  BOOST_CHECK(!i.hasWire()); // nonce generation resets wire encoding
+
+  // modify then re-encode as v0.2 format
+  i.setNonce(0x54657c95);
+  BOOST_CHECK(i.wireEncode() == "0510 0703080149 09030E0101 0A04957C6554"_block);
 }
 
-BOOST_AUTO_TEST_CASE(DecodeBadNonce)
+BOOST_AUTO_TEST_CASE(Full)
 {
-  Block b(tlv::Interest);
-  b.push_back(Name("/BJzEHVxDJ").wireEncode());
-  b.push_back(makeBinaryBlock(tlv::Nonce, "SKY", 3));
-  b.encode();
+  i.wireDecode("053B FC00 0703080149 FC00 2100 FC00 1200 "
+               "FC00 1E0B(1F09 1E023E15 0703080148) FC00 0A044ACB1E4C "
+               "FC00 0C0276A1 FC00 2201D6 FC00 2304C0C1C2C3 FC00"_block);
+  BOOST_CHECK_EQUAL(i.getName(), "/I");
+  BOOST_CHECK_EQUAL(i.getCanBePrefix(), true);
+  BOOST_CHECK_EQUAL(i.getMustBeFresh(), true);
+  BOOST_CHECK_EQUAL(i.getForwardingHint(), DelegationList({{15893, "/H"}}));
+  BOOST_CHECK(i.hasNonce());
+  BOOST_CHECK_EQUAL(i.getNonce(), 0x4c1ecb4a);
+  BOOST_CHECK_EQUAL(i.getInterestLifetime(), 30369_ms);
+  // HopLimit=214 is not stored
+  // Parameters="C0C1C2C3" is not stored
 
-  Interest i;
-  BOOST_CHECK_THROW(i.wireDecode(b), tlv::Error);
+  // encode without modification: retain original wire encoding
+  BOOST_CHECK_EQUAL(i.wireEncode().value_size(), 59);
+
+  // modify then re-encode as v0.2 format
+  i.setName("/J");
+  BOOST_CHECK(i.wireEncode() ==
+              "0520 070308014A 09021200 0A044ACB1E4C 0C0276A1 "
+              "1E0B(1F09 1E023E15 0703080148)"_block);
 }
 
+BOOST_AUTO_TEST_CASE(CriticalElementOutOfOrder)
+{
+  BOOST_CHECK_THROW(i.wireDecode(
+    "0529 2100 0703080149 1200 1E0B(1F09 1E023E15 0703080148) "
+    "0A044ACB1E4C 0C0276A1 2201D6 2304C0C1C2C3"_block),
+    tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode(
+    "0529 0703080149 1200 2100 1E0B(1F09 1E023E15 0703080148) "
+    "0A044ACB1E4C 0C0276A1 2201D6 2304C0C1C2C3"_block),
+    tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode(
+    "0529 0703080149 2100 1E0B(1F09 1E023E15 0703080148) 1200 "
+    "0A044ACB1E4C 0C0276A1 2201D6 2304C0C1C2C3"_block),
+    tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode(
+    "0529 0703080149 2100 1200 0A044ACB1E4C "
+    "1E0B(1F09 1E023E15 0703080148) 0C0276A1 2201D6 2304C0C1C2C3"_block),
+    tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode(
+    "0529 0703080149 2100 1200 1E0B(1F09 1E023E15 0703080148) "
+    "0C0276A1 0A044ACB1E4C 2201D6 2304C0C1C2C3"_block),
+    tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode(
+    "0529 0703080149 2100 1200 1E0B(1F09 1E023E15 0703080148) "
+    "0A044ACB1E4C 2201D6 0C0276A1 2304C0C1C2C3"_block),
+    tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode(
+    "052F 0703080149 2100 1200 1E0B(1F09 1E023E15 0703080148) "
+    "0A044ACB1E4C 0C0276A1 2201D6 2304C0C1C2C3 2304C0C1C2C3"_block),
+    tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(HopLimitOutOfOrder)
+{
+  // HopLimit is non-critical, its out-of-order appearances are ignored
+  i.wireDecode("0514 0703080149 2201D6 2200 2304C0C1C2C3 22020101"_block);
+  BOOST_CHECK_EQUAL(i.getName(), "/I");
+  // HopLimit=214 is not stored
+  // Parameters="C0C1C2C3" is not stored
+}
+
+BOOST_AUTO_TEST_CASE(NameMissing)
+{
+  BOOST_CHECK_THROW(i.wireDecode("0500"_block), tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode("0502 1200"_block), tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(NameEmpty)
+{
+  BOOST_CHECK_THROW(i.wireDecode("0502 0700"_block), tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(BadCanBePrefix)
+{
+  BOOST_CHECK_THROW(i.wireDecode("0508 0703080149 210102"_block), tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(BadMustBeFresh)
+{
+  BOOST_CHECK_THROW(i.wireDecode("0508 0703080149 120102"_block), tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(BadNonce)
+{
+  BOOST_CHECK_THROW(i.wireDecode("0507 0703080149 0A00"_block), tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode("050A 0703080149 0A0304C263"_block), tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode("050C 0703080149 0A05EFA420B262"_block), tlv::Error);
+}
+
+BOOST_AUTO_TEST_CASE(UnrecognizedCriticalElement)
+{
+  BOOST_CHECK_THROW(i.wireDecode("0507 0703080149 FB00"_block), tlv::Error);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Decode03
+
 // ---- matching ----
 
 BOOST_AUTO_TEST_CASE(MatchesData)