interest: refactor nonce handling

Nonce is a sequence of 4 bytes, not a 32-bit unsigned integer.
This also fixes various endianness bugs in unit tests.

Refs: #3294
Change-Id: I057b7b771fecb0bc997b0b11a03e6660c9b7a826
diff --git a/.travis.yml b/.travis.yml
index a240836..f991bb9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,14 +10,10 @@
   - s390x
 
 env:
-  global:
-    - JOB_NAME=limited-build
-    - WAF_JOBS=2
-  jobs:
-    - COMPILER=g++-7
-    - COMPILER=g++-9
-    - COMPILER=clang++-6.0
-    - COMPILER=clang++-9
+  - COMPILER=g++-7
+  - COMPILER=g++-9
+  - COMPILER=clang++-6.0
+  - COMPILER=clang++-9
 
 jobs:
   include:
@@ -76,13 +72,15 @@
 before_script:
   - |
     : Setting environment variables
+    if [[ -n ${COMPILER} ]]; then
+      export CXX=${COMPILER}
+    fi
     case ${TRAVIS_OS_NAME} in
       linux)  export NODE_LABELS="Linux Ubuntu Ubuntu-18.04" ;;
       osx)    export NODE_LABELS="OSX OSX-$(sw_vers -productVersion | cut -d . -f -2)" ;;
     esac
-    if [[ -n ${COMPILER} ]]; then
-      export CXX=${COMPILER}
-    fi
+    export JOB_NAME=limited-build
+    export WAF_JOBS=2
   - |
     : Enabling workarounds
     case "${TRAVIS_CPU_ARCH},${COMPILER}" in
diff --git a/ndn-cxx/impl/common-pch.hpp b/ndn-cxx/impl/common-pch.hpp
index 7f1ab5a..877c6e7 100644
--- a/ndn-cxx/impl/common-pch.hpp
+++ b/ndn-cxx/impl/common-pch.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2018 Regents of the University of California.
+ * Copyright (c) 2013-2020 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -28,18 +28,21 @@
 #include "ndn-cxx/detail/common.hpp"
 
 // STL headers to precompile
+#include <array>
+#include <cstring>
 #include <fstream>
+#include <iterator>
 #include <list>
 #include <map>
 #include <set>
 #include <sstream>
 #include <unordered_map>
-#include <unordered_set>
 #include <vector>
 
 // Boost headers to precompile
 #include <boost/algorithm/string.hpp>
 #include <boost/chrono.hpp>
+#include <boost/endian/conversion.hpp>
 #include <boost/filesystem.hpp>
 #include <boost/iostreams/categories.hpp>
 #include <boost/iostreams/stream.hpp>
diff --git a/ndn-cxx/interest.cpp b/ndn-cxx/interest.cpp
index 00a3249..5ebc21e 100644
--- a/ndn-cxx/interest.cpp
+++ b/ndn-cxx/interest.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2019 Regents of the University of California.
+ * Copyright (c) 2013-2020 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -127,8 +127,7 @@
 
   // HopLimit
   if (getHopLimit()) {
-    uint8_t hopLimit = *getHopLimit();
-    totalLength += encoder.prependByteArrayBlock(tlv::HopLimit, &hopLimit, sizeof(hopLimit));
+    totalLength += encoder.prependByteArrayBlock(tlv::HopLimit, &*m_hopLimit, 1);
   }
 
   // InterestLifetime
@@ -138,8 +137,9 @@
   }
 
   // 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));
+  getNonce(); // if nonce was unset, this generates a fresh nonce
+  BOOST_ASSERT(hasNonce());
+  totalLength += encoder.prependByteArrayBlock(tlv::Nonce, m_nonce->data(), m_nonce->size());
 
   // ForwardingHint
   if (!getForwardingHint().empty()) {
@@ -262,12 +262,11 @@
         if (lastElement >= 5) {
           NDN_THROW(Error("Nonce element is out of order"));
         }
-        uint32_t nonce = 0;
-        if (element->value_size() != sizeof(nonce)) {
+        if (element->value_size() != Nonce().size()) {
           NDN_THROW(Error("Nonce element is malformed"));
         }
-        std::memcpy(&nonce, element->value(), sizeof(nonce));
-        m_nonce = nonce;
+        m_nonce.emplace();
+        std::memcpy(m_nonce->data(), element->value(), m_nonce->size());
         lastElement = 5;
         break;
       }
@@ -396,18 +395,27 @@
   return *this;
 }
 
-uint32_t
+static auto
+generateNonce()
+{
+  uint32_t r = random::generateWord32();
+  Interest::Nonce n;
+  std::memcpy(n.data(), &r, sizeof(r));
+  return n;
+}
+
+Interest::Nonce
 Interest::getNonce() const
 {
   if (!hasNonce()) {
-    m_nonce = random::generateWord32();
+    m_nonce = generateNonce();
     m_wire.reset();
   }
   return *m_nonce;
 }
 
 Interest&
-Interest::setNonce(uint32_t nonce)
+Interest::setNonce(optional<Interest::Nonce> nonce)
 {
   if (nonce != m_nonce) {
     m_nonce = nonce;
@@ -422,9 +430,9 @@
   if (!hasNonce())
     return;
 
-  uint32_t oldNonce = *m_nonce;
+  auto oldNonce = *m_nonce;
   while (m_nonce == oldNonce)
-    m_nonce = random::generateWord32();
+    m_nonce = generateNonce();
 
   m_wire.reset();
 }
diff --git a/ndn-cxx/interest.hpp b/ndn-cxx/interest.hpp
index c386ce8..f69b855 100644
--- a/ndn-cxx/interest.hpp
+++ b/ndn-cxx/interest.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2019 Regents of the University of California.
+ * Copyright (c) 2013-2020 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -23,10 +23,14 @@
 #define NDN_INTEREST_HPP
 
 #include "ndn-cxx/delegation-list.hpp"
-#include "ndn-cxx/name.hpp"
 #include "ndn-cxx/detail/packet-base.hpp"
+#include "ndn-cxx/name.hpp"
+#include "ndn-cxx/util/string-helper.hpp"
 #include "ndn-cxx/util/time.hpp"
 
+#include <array>
+
+#include <boost/endian/conversion.hpp>
 #include <boost/logic/tribool.hpp>
 
 namespace ndn {
@@ -49,6 +53,52 @@
     using tlv::Error::Error;
   };
 
+  class Nonce final : public std::array<uint8_t, 4>
+  {
+    using Base = std::array<uint8_t, 4>;
+
+  public:
+    Nonce() = default;
+
+    // implicit conversion from uint32_t
+    Nonce(uint32_t n) noexcept
+    {
+      boost::endian::native_to_big_inplace(n);
+      std::memcpy(data(), &n, sizeof(n));
+    }
+
+    Nonce(uint8_t n1, uint8_t n2, uint8_t n3, uint8_t n4) noexcept
+    {
+      data()[0] = n1;
+      data()[1] = n2;
+      data()[2] = n3;
+      data()[3] = n4;
+    }
+
+  private: // non-member operators
+    // NOTE: the following "hidden friend" operators are available via
+    //       argument-dependent lookup only and must be defined inline.
+
+    friend bool
+    operator==(const Nonce& lhs, const Nonce& rhs) noexcept
+    {
+      return static_cast<const Base&>(lhs) == static_cast<const Base&>(rhs);
+    }
+
+    friend bool
+    operator!=(const Nonce& lhs, const Nonce& rhs) noexcept
+    {
+      return static_cast<const Base&>(lhs) != static_cast<const Base&>(rhs);
+    }
+
+    friend std::ostream&
+    operator<<(std::ostream& os, const Nonce& nonce)
+    {
+      printHex(os, nonce.data(), nonce.size(), false);
+      return os;
+    }
+  };
+
   /** @brief Construct an Interest with given @p name and @p lifetime.
    *
    *  @throw std::invalid_argument @p name is invalid or @p lifetime is negative
@@ -228,13 +278,15 @@
    *
    *  If nonce was not present, it is added and assigned a random value.
    */
-  uint32_t
+  Nonce
   getNonce() const;
 
-  /** @brief Set nonce value.
+  /** @brief Set the Interest's nonce.
+   *
+   *  Use `setNonce(nullopt)` to remove any nonce from the Interest.
    */
   Interest&
-  setNonce(uint32_t nonce);
+  setNonce(optional<Nonce> nonce);
 
   /** @brief Change nonce value.
    *
@@ -392,7 +444,7 @@
 
   Name m_name;
   DelegationList m_forwardingHint;
-  mutable optional<uint32_t> m_nonce;
+  mutable optional<Nonce> m_nonce;
   time::milliseconds m_interestLifetime;
   optional<uint8_t> m_hopLimit;
   mutable bool m_isCanBePrefixSet = false;
diff --git a/ndn-cxx/util/string-helper.cpp b/ndn-cxx/util/string-helper.cpp
index 06b08a6..d0afc19 100644
--- a/ndn-cxx/util/string-helper.cpp
+++ b/ndn-cxx/util/string-helper.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2019 Regents of the University of California.
+ * Copyright (c) 2013-2020 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -20,7 +20,6 @@
  */
 
 #include "ndn-cxx/util/string-helper.hpp"
-#include "ndn-cxx/encoding/buffer.hpp"
 #include "ndn-cxx/encoding/buffer-stream.hpp"
 #include "ndn-cxx/security/transform/buffer-source.hpp"
 #include "ndn-cxx/security/transform/hex-decode.hpp"
@@ -31,13 +30,6 @@
 
 namespace ndn {
 
-std::ostream&
-operator<<(std::ostream& os, const AsHex& hex)
-{
-  printHex(os, hex.m_value, os.flags() & std::ostream::uppercase);
-  return os;
-}
-
 void
 printHex(std::ostream& os, uint64_t num, bool wantUpperCase)
 {
@@ -57,12 +49,6 @@
   tr::bufferSource(buffer, length) >> tr::hexEncode(wantUpperCase) >> tr::streamSink(os);
 }
 
-void
-printHex(std::ostream& os, const Buffer& buffer, bool wantUpperCase)
-{
-  return printHex(os, buffer.data(), buffer.size(), wantUpperCase);
-}
-
 std::string
 toHex(const uint8_t* buffer, size_t length, bool wantUpperCase)
 {
@@ -71,12 +57,6 @@
   return result.str();
 }
 
-std::string
-toHex(const Buffer& buffer, bool wantUpperCase)
-{
-  return toHex(buffer.data(), buffer.size(), wantUpperCase);
-}
-
 shared_ptr<Buffer>
 fromHex(const std::string& hexString)
 {
diff --git a/ndn-cxx/util/string-helper.hpp b/ndn-cxx/util/string-helper.hpp
index 97973da..b923317 100644
--- a/ndn-cxx/util/string-helper.hpp
+++ b/ndn-cxx/util/string-helper.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2019 Regents of the University of California.
+ * Copyright (c) 2013-2020 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -22,12 +22,10 @@
 #ifndef NDN_UTIL_STRING_HELPER_HPP
 #define NDN_UTIL_STRING_HELPER_HPP
 
-#include "ndn-cxx/detail/common.hpp"
+#include "ndn-cxx/encoding/buffer.hpp"
 
 namespace ndn {
 
-class Buffer;
-
 class StringHelperError : public std::invalid_argument
 {
 public:
@@ -35,35 +33,6 @@
 };
 
 /**
- * @brief Helper class to convert a number to hexadecimal
- *        format, for use with stream insertion operators
- *
- * Example usage:
- *
- * @code
- * std::cout << AsHex{42};                   // outputs "0x2a"
- * std::cout << std::uppercase << AsHex{42}; // outputs "0x2A"
- * @endcode
- */
-class AsHex
-{
-public:
-  constexpr explicit
-  AsHex(uint64_t val) noexcept
-    : m_value(val)
-  {
-  }
-
-private:
-  uint64_t m_value;
-
-  friend std::ostream& operator<<(std::ostream&, const AsHex&);
-};
-
-std::ostream&
-operator<<(std::ostream& os, const AsHex& hex);
-
-/**
  * @brief Output the hex representation of @p num to the output stream @p os
  *
  * @param os Output stream
@@ -104,8 +73,43 @@
  * @param buffer Buffer of bytes to print in hexadecimal format
  * @param wantUpperCase if true (the default) print uppercase hex chars
  */
-void
-printHex(std::ostream& os, const Buffer& buffer, bool wantUpperCase = true);
+inline void
+printHex(std::ostream& os, const Buffer& buffer, bool wantUpperCase = true)
+{
+  return printHex(os, buffer.data(), buffer.size(), wantUpperCase);
+}
+
+/**
+ * @brief Helper class to convert a number to hexadecimal
+ *        format, for use with stream insertion operators
+ *
+ * Example usage:
+ *
+ * @code
+ * std::cout << AsHex{42};                   // outputs "0x2a"
+ * std::cout << std::uppercase << AsHex{42}; // outputs "0x2A"
+ * @endcode
+ */
+class AsHex
+{
+public:
+  constexpr explicit
+  AsHex(uint64_t val) noexcept
+    : m_value(val)
+  {
+  }
+
+private:
+  friend std::ostream&
+  operator<<(std::ostream& os, const AsHex& hex)
+  {
+    printHex(os, hex.m_value, os.flags() & std::ostream::uppercase);
+    return os;
+  }
+
+private:
+  uint64_t m_value;
+};
 
 /**
  * @brief Return a string containing the hex representation of the bytes in @p buffer
@@ -134,8 +138,11 @@
  * @param buffer Buffer of bytes to convert to hexadecimal format
  * @param wantUpperCase if true (the default) use uppercase hex chars
  */
-NDN_CXX_NODISCARD std::string
-toHex(const Buffer& buffer, bool wantUpperCase = true);
+NDN_CXX_NODISCARD inline std::string
+toHex(const Buffer& buffer, bool wantUpperCase = true)
+{
+  return toHex(buffer.data(), buffer.size(), wantUpperCase);
+}
 
 /**
  * @brief Convert the hex string to buffer
diff --git a/tests/make-interest-data.cpp b/tests/make-interest-data.cpp
index 76b2f44..4031507 100644
--- a/tests/make-interest-data.cpp
+++ b/tests/make-interest-data.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2018 Regents of the University of California.
+ * Copyright (c) 2013-2020 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -27,13 +27,12 @@
 namespace tests {
 
 shared_ptr<Interest>
-makeInterest(const Name& name, bool canBePrefix, time::milliseconds lifetime, uint32_t nonce)
+makeInterest(const Name& name, bool canBePrefix, time::milliseconds lifetime,
+             optional<Interest::Nonce> nonce)
 {
   auto interest = make_shared<Interest>(name, lifetime);
   interest->setCanBePrefix(canBePrefix);
-  if (nonce != 0) {
-    interest->setNonce(nonce);
-  }
+  interest->setNonce(nonce);
   return interest;
 }
 
@@ -55,20 +54,12 @@
 }
 
 lp::Nack
-makeNack(const Interest& interest, lp::NackReason reason)
+makeNack(Interest interest, lp::NackReason reason)
 {
-  lp::Nack nack(interest);
+  lp::Nack nack(std::move(interest));
   nack.setReason(reason);
   return nack;
 }
 
-lp::Nack
-makeNack(const Name& name, uint32_t nonce, lp::NackReason reason)
-{
-  Interest interest(name);
-  interest.setNonce(nonce);
-  return makeNack(interest, reason);
-}
-
 } // namespace tests
 } // namespace ndn
diff --git a/tests/make-interest-data.hpp b/tests/make-interest-data.hpp
index 10381fd..2d12011 100644
--- a/tests/make-interest-data.hpp
+++ b/tests/make-interest-data.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2019 Regents of the University of California.
+ * Copyright (c) 2013-2020 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -24,21 +24,17 @@
 
 #include "ndn-cxx/data.hpp"
 #include "ndn-cxx/interest.hpp"
-#include "ndn-cxx/link.hpp"
 #include "ndn-cxx/lp/nack.hpp"
 
 namespace ndn {
 namespace tests {
 
 /** \brief create an Interest
- *  \param name Interest name
- *  \param canBePrefix CanBePrefix setting
- *  \param lifetime InterestLifetime
- *  \param nonce if non-zero, set Nonce to this value (useful for creating Nack with same Nonce)
  */
 shared_ptr<Interest>
 makeInterest(const Name& name, bool canBePrefix = false,
-             time::milliseconds lifetime = DEFAULT_INTEREST_LIFETIME, uint32_t nonce = 0);
+             time::milliseconds lifetime = DEFAULT_INTEREST_LIFETIME,
+             optional<Interest::Nonce> nonce = nullopt);
 
 /** \brief create a Data with fake signature
  *  \note Data may be modified afterwards without losing the fake signature.
@@ -62,11 +58,9 @@
 }
 
 /** \brief create a Nack
- *  \param interest Interest
- *  \param reason Nack reason
  */
 lp::Nack
-makeNack(const Interest& interest, lp::NackReason reason);
+makeNack(Interest interest, lp::NackReason reason);
 
 /** \brief replace a name component in a packet
  *  \param[inout] pkt the packet
diff --git a/tests/unit/interest.t.cpp b/tests/unit/interest.t.cpp
index 7b8f843..1787b87 100644
--- a/tests/unit/interest.t.cpp
+++ b/tests/unit/interest.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2019 Regents of the University of California.
+ * Copyright (c) 2013-2020 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -75,13 +75,13 @@
                 0x08, 0x03, 0x6e, 0x64, 0x6e, // GenericNameComponent
                 0x08, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, // GenericNameComponent
           0x0a, 0x04, // Nonce
-                0x01, 0x00, 0x00, 0x00,
+                0x01, 0x02, 0x03, 0x04,
   };
 
   Interest i1;
   i1.setName("/local/ndn/prefix");
   i1.setCanBePrefix(false);
-  i1.setNonce(1);
+  i1.setNonce(0x01020304);
   BOOST_CHECK_EQUAL(i1.isParametersDigestValid(), true);
 
   Block wire1 = i1.wireEncode();
@@ -93,7 +93,7 @@
   BOOST_CHECK_EQUAL(i2.getMustBeFresh(), false);
   BOOST_CHECK_EQUAL(i2.getForwardingHint().empty(), true);
   BOOST_CHECK_EQUAL(i2.hasNonce(), true);
-  BOOST_CHECK_EQUAL(i2.getNonce(), 1);
+  BOOST_CHECK_EQUAL(i2.getNonce(), 0x01020304);
   BOOST_CHECK_EQUAL(i2.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
   BOOST_CHECK(i2.getHopLimit() == nullopt);
   BOOST_CHECK_EQUAL(i2.hasApplicationParameters(), false);
@@ -113,7 +113,7 @@
                       0x26, 0xa0, 0x51, 0xba, 0x25, 0xf5, 0x6b, 0x69, 0xbf, 0xa0, 0x26, 0xdc,
                       0xcc, 0xd7, 0x2c, 0x6e, 0xa0, 0xf7, 0x31, 0x5a,
           0x0a, 0x04, // Nonce
-                0x01, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x01,
           0x24, 0x04, // ApplicationParameters
                 0xc0, 0xc1, 0xc2, 0xc3
   };
@@ -121,7 +121,7 @@
   Interest i1;
   i1.setName("/local/ndn/prefix");
   i1.setCanBePrefix(false);
-  i1.setNonce(1);
+  i1.setNonce(0x1);
   i1.setApplicationParameters("2404C0C1C2C3"_block);
   BOOST_CHECK_EQUAL(i1.isParametersDigestValid(), true);
 
@@ -135,7 +135,7 @@
   BOOST_CHECK_EQUAL(i2.getMustBeFresh(), false);
   BOOST_CHECK_EQUAL(i2.getForwardingHint().empty(), true);
   BOOST_CHECK_EQUAL(i2.hasNonce(), true);
-  BOOST_CHECK_EQUAL(i2.getNonce(), 1);
+  BOOST_CHECK_EQUAL(i2.getNonce(), 0x1);
   BOOST_CHECK_EQUAL(i2.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
   BOOST_CHECK(i2.getHopLimit() == nullopt);
   BOOST_CHECK_EQUAL(i2.hasApplicationParameters(), true);
@@ -163,7 +163,7 @@
                       0x07, 0x03,
                             0x08, 0x01, 0x48,
           0x0a, 0x04, // Nonce
-                0x4a, 0xcb, 0x1e, 0x4c,
+                0x4c, 0x1e, 0xcb, 0x4a,
           0x0c, 0x02, // InterestLifetime
                 0x76, 0xa1,
           0x22, 0x01, // HopLimit
@@ -264,7 +264,7 @@
   BOOST_CHECK_EQUAL(i.getApplicationParameters().isValid(), false);
 
   // modify then re-encode
-  i.setNonce(0x54657c95);
+  i.setNonce(0x957c6554);
   BOOST_CHECK_EQUAL(i.hasWire(), false);
   BOOST_CHECK_EQUAL(i.wireEncode(), "050B 0703(080149) 0A04957C6554"_block);
 }
@@ -294,7 +294,7 @@
   BOOST_CHECK_EQUAL(i.getMustBeFresh(), true);
   BOOST_CHECK_EQUAL(i.getForwardingHint(), DelegationList({{15893, "/H"}}));
   BOOST_CHECK_EQUAL(i.hasNonce(), true);
-  BOOST_CHECK_EQUAL(i.getNonce(), 0x4c1ecb4a);
+  BOOST_CHECK_EQUAL(i.getNonce(), 0x4acb1e4c);
   BOOST_CHECK_EQUAL(i.getInterestLifetime(), 30369_ms);
   BOOST_CHECK_EQUAL(*i.getHopLimit(), 214);
   BOOST_CHECK_EQUAL(i.hasApplicationParameters(), false);
@@ -322,7 +322,7 @@
   BOOST_CHECK_EQUAL(i.getMustBeFresh(), true);
   BOOST_CHECK_EQUAL(i.getForwardingHint(), DelegationList({{15893, "/H"}}));
   BOOST_CHECK_EQUAL(i.hasNonce(), true);
-  BOOST_CHECK_EQUAL(i.getNonce(), 0x4c1ecb4a);
+  BOOST_CHECK_EQUAL(i.getNonce(), 0x4acb1e4c);
   BOOST_CHECK_EQUAL(i.getInterestLifetime(), 30369_ms);
   BOOST_CHECK_EQUAL(*i.getHopLimit(), 214);
   BOOST_CHECK_EQUAL(i.hasApplicationParameters(), true);
@@ -503,8 +503,9 @@
   interest = makeInterest(data->getFullName());
   BOOST_CHECK_EQUAL(interest->matchesData(*data), true);
 
-  setNameComponent(*interest, -1, name::Component::fromEscapedString(
-                     "sha256digest=0000000000000000000000000000000000000000000000000000000000000000"));
+  setNameComponent(*interest, -1,
+                   name::Component::fromEscapedString("sha256digest=00000000000000000000000000"
+                                                      "00000000000000000000000000000000000000"));
   BOOST_CHECK_EQUAL(interest->matchesData(*data), false); // violates implicit digest
 }
 
@@ -596,12 +597,12 @@
 BOOST_AUTO_TEST_CASE(GetNonce)
 {
   unique_ptr<Interest> i1, i2;
+  Interest::Nonce nonce1(0), nonce2(0);
 
   // getNonce automatically assigns a random Nonce.
   // It's possible to assign the same Nonce to two Interest, but it's unlikely to get 100 pairs of
-  // same Nonces in a row.
+  // identical Nonces in a row.
   int nIterations = 0;
-  uint32_t nonce1 = 0, nonce2 = 0;
   do {
     i1 = make_unique<Interest>();
     nonce1 = i1->getNonce();
@@ -609,28 +610,37 @@
     nonce2 = i2->getNonce();
   }
   while (nonce1 == nonce2 && ++nIterations < 100);
+
   BOOST_CHECK_NE(nonce1, nonce2);
   BOOST_CHECK(i1->hasNonce());
   BOOST_CHECK(i2->hasNonce());
 
   // Once a Nonce is assigned, it should not change.
   BOOST_CHECK_EQUAL(i1->getNonce(), nonce1);
+  BOOST_CHECK_EQUAL(i2->getNonce(), nonce2);
 }
 
 BOOST_AUTO_TEST_CASE(SetNonce)
 {
   Interest i1("/A");
   i1.setCanBePrefix(false);
+  BOOST_CHECK(!i1.hasNonce());
+
   i1.setNonce(1);
   i1.wireEncode();
+  BOOST_CHECK(i1.hasNonce());
   BOOST_CHECK_EQUAL(i1.getNonce(), 1);
 
   Interest i2(i1);
+  BOOST_CHECK(i2.hasNonce());
   BOOST_CHECK_EQUAL(i2.getNonce(), 1);
 
   i2.setNonce(2);
   BOOST_CHECK_EQUAL(i2.getNonce(), 2);
-  BOOST_CHECK_EQUAL(i1.getNonce(), 1); // should not affect i1 Nonce (Bug #4168)
+  BOOST_CHECK_EQUAL(i1.getNonce(), 1); // should not affect i1's Nonce (Bug #4168)
+
+  i2.setNonce(nullopt);
+  BOOST_CHECK(!i2.hasNonce());
 }
 
 BOOST_AUTO_TEST_CASE(RefreshNonce)
@@ -647,6 +657,31 @@
   BOOST_CHECK_NE(i.getNonce(), 1);
 }
 
+BOOST_AUTO_TEST_CASE(NonceConversions)
+{
+  Interest i;
+  i.setCanBePrefix(false);
+
+  // 4-arg constructor
+  Interest::Nonce n1(1, 2, 3, 4);
+  i.setNonce(n1);
+  BOOST_CHECK_EQUAL(i.getNonce(), 0x01020304);
+
+  // 4-arg constructor + assignment
+  n1 = {0xf, 0xe, 0xd, 0xc};
+  i.setNonce(n1);
+  BOOST_CHECK_EQUAL(i.getNonce(), 0x0f0e0d0c);
+
+  // 1-arg constructor + assignment (implicit conversion)
+  Interest::Nonce n2;
+  n2 = 42;
+  BOOST_CHECK_NE(n1, n2);
+  i.setNonce(n2);
+  n2 = 21; // should not affect i's Nonce
+  BOOST_CHECK_EQUAL(i.getNonce(), 42);
+  BOOST_CHECK_EQUAL(i.toUri(), "/?Nonce=0000002a"); // stored in big-endian
+}
+
 BOOST_AUTO_TEST_CASE(SetInterestLifetime)
 {
   BOOST_CHECK_THROW(Interest("/A", -1_ms), std::invalid_argument);
@@ -766,14 +801,14 @@
   i.setMustBeFresh(true);
   BOOST_CHECK_EQUAL(i.toUri(), "/foo?CanBePrefix&MustBeFresh");
 
-  i.setNonce(1234);
-  BOOST_CHECK_EQUAL(i.toUri(), "/foo?CanBePrefix&MustBeFresh&Nonce=1234");
+  i.setNonce(0xa1b2c3);
+  BOOST_CHECK_EQUAL(i.toUri(), "/foo?CanBePrefix&MustBeFresh&Nonce=00a1b2c3");
 
   i.setInterestLifetime(2_s);
-  BOOST_CHECK_EQUAL(i.toUri(), "/foo?CanBePrefix&MustBeFresh&Nonce=1234&Lifetime=2000");
+  BOOST_CHECK_EQUAL(i.toUri(), "/foo?CanBePrefix&MustBeFresh&Nonce=00a1b2c3&Lifetime=2000");
 
   i.setHopLimit(18);
-  BOOST_CHECK_EQUAL(i.toUri(), "/foo?CanBePrefix&MustBeFresh&Nonce=1234&Lifetime=2000&HopLimit=18");
+  BOOST_CHECK_EQUAL(i.toUri(), "/foo?CanBePrefix&MustBeFresh&Nonce=00a1b2c3&Lifetime=2000&HopLimit=18");
 
   i.setCanBePrefix(false);
   i.setMustBeFresh(false);
@@ -781,7 +816,7 @@
   i.setApplicationParameters("2402CAFE"_block);
   BOOST_CHECK_EQUAL(i.toUri(),
                     "/foo/params-sha256=8621f5e8321f04104640c8d02877d7c5142cad6e203c5effda1783b1a0e476d6"
-                    "?Nonce=1234&Lifetime=2000");
+                    "?Nonce=00a1b2c3&Lifetime=2000");
 }
 
 BOOST_AUTO_TEST_SUITE_END() // TestInterest