interest: add support for ParametersSha256DigestComponent

Add Name::set(), Name::appendParametersSha256Digest(),
Name::appendParametersSha256DigestPlaceholder(), Name::erase()

Refs: #4658
Change-Id: Ic688030504804158ef8627a5f751ebc8b2587611
diff --git a/ndn-cxx/interest.cpp b/ndn-cxx/interest.cpp
index b14ac15..a984242 100644
--- a/ndn-cxx/interest.cpp
+++ b/ndn-cxx/interest.cpp
@@ -21,6 +21,10 @@
 
 #include "ndn-cxx/interest.hpp"
 #include "ndn-cxx/data.hpp"
+#include "ndn-cxx/encoding/buffer-stream.hpp"
+#include "ndn-cxx/security/transform/digest-filter.hpp"
+#include "ndn-cxx/security/transform/step-source.hpp"
+#include "ndn-cxx/security/transform/stream-sink.hpp"
 #include "ndn-cxx/util/random.hpp"
 
 #include <boost/scope_exit.hpp>
@@ -46,15 +50,12 @@
 bool Interest::s_errorIfCanBePrefixUnset = true;
 #endif // NDN_CXX_HAVE_TESTS
 boost::logic::tribool Interest::s_defaultCanBePrefix = boost::logic::indeterminate;
+bool Interest::s_autoCheckParametersDigest = true;
 
 Interest::Interest(const Name& name, time::milliseconds lifetime)
-  : m_name(name)
-  , m_isCanBePrefixSet(false)
-  , m_interestLifetime(lifetime)
 {
-  if (lifetime < 0_ms) {
-    NDN_THROW(std::invalid_argument("InterestLifetime must be >= 0"));
-  }
+  setName(name);
+  setInterestLifetime(lifetime);
 
   if (!boost::logic::indeterminate(s_defaultCanBePrefix)) {
     setCanBePrefix(bool(s_defaultCanBePrefix));
@@ -62,7 +63,6 @@
 }
 
 Interest::Interest(const Block& wire)
-  : m_isCanBePrefixSet(true)
 {
   wireDecode(wire);
 }
@@ -110,8 +110,6 @@
 size_t
 Interest::encode02(EncodingImpl<TAG>& encoder) const
 {
-  size_t totalLength = 0;
-
   // Encode as NDN Packet Format v0.2
   // Interest ::= INTEREST-TYPE TLV-LENGTH
   //                Name
@@ -119,8 +117,9 @@
   //                Nonce
   //                InterestLifetime?
   //                ForwardingHint?
+  // (elements are encoded in reverse order)
 
-  // (reverse encoding)
+  size_t totalLength = 0;
 
   // ForwardingHint
   if (getForwardingHint().size() > 0) {
@@ -154,25 +153,35 @@
 size_t
 Interest::encode03(EncodingImpl<TAG>& encoder) const
 {
+  // Encode as NDN Packet Format v0.3
+  // Interest = INTEREST-TYPE TLV-LENGTH
+  //              Name
+  //              [CanBePrefix]
+  //              [MustBeFresh]
+  //              [ForwardingHint]
+  //              [Nonce]
+  //              [InterestLifetime]
+  //              [HopLimit]
+  //              [ApplicationParameters [InterestSignature]]
+  // (elements are encoded in reverse order)
+
+  // sanity check of ApplicationParameters and ParametersSha256DigestComponent
+  ssize_t digestIndex = findParametersDigestComponent(getName());
+  BOOST_ASSERT(digestIndex != -2); // guaranteed by the checks in setName() and wireDecode()
+  if (digestIndex == -1) {
+    if (hasApplicationParameters())
+      NDN_THROW(Error("Interest with parameters must have a ParametersSha256DigestComponent"));
+  }
+  else if (!hasApplicationParameters()) {
+    NDN_THROW(Error("Interest without parameters must not have a ParametersSha256DigestComponent"));
+  }
+
   size_t totalLength = 0;
 
-  // Encode as NDN Packet Format v0.3
-  // Interest ::= INTEREST-TYPE TLV-LENGTH
-  //                Name
-  //                CanBePrefix?
-  //                MustBeFresh?
-  //                ForwardingHint?
-  //                Nonce?
-  //                InterestLifetime?
-  //                HopLimit?
-  //                ApplicationParameters?
-
-  // (reverse encoding)
-
-  // ApplicationParameters
-  if (hasApplicationParameters()) {
-    totalLength += encoder.prependBlock(getApplicationParameters());
-  }
+  // ApplicationParameters and following elements (in reverse order)
+  std::for_each(m_parameters.rbegin(), m_parameters.rend(), [&] (const Block& b) {
+    totalLength += encoder.prependBlock(b);
+  });
 
   // HopLimit: not yet supported
 
@@ -230,13 +239,13 @@
 void
 Interest::wireDecode(const Block& wire)
 {
+  if (wire.type() != tlv::Interest) {
+    NDN_THROW(Error("Interest", wire.type()));
+  }
+
   m_wire = wire;
   m_wire.parse();
 
-  if (m_wire.type() != tlv::Interest) {
-    NDN_THROW(Error("Interest", m_wire.type()));
-  }
-
   if (!decode02()) {
     decode03();
     if (!hasNonce()) {
@@ -254,7 +263,14 @@
 
   // Name
   if (element != m_wire.elements_end() && element->type() == tlv::Name) {
-    m_name.wireDecode(*element);
+    // decode into a temporary object until we determine that the name is valid, in order
+    // to maintain class invariants and thus provide a basic form of exception safety
+    Name tempName(*element);
+    ssize_t digestIndex = findParametersDigestComponent(tempName);
+    if (digestIndex == -2) {
+      NDN_THROW(Error("Name has more than one ParametersSha256DigestComponent"));
+    }
+    m_name = std::move(tempName);
     ++element;
   }
   else {
@@ -308,32 +324,39 @@
 void
 Interest::decode03()
 {
-  // Interest ::= INTEREST-TYPE TLV-LENGTH
-  //                Name
-  //                CanBePrefix?
-  //                MustBeFresh?
-  //                ForwardingHint?
-  //                Nonce?
-  //                InterestLifetime?
-  //                HopLimit?
-  //                ApplicationParameters?
+  // Interest = INTEREST-TYPE TLV-LENGTH
+  //              Name
+  //              [CanBePrefix]
+  //              [MustBeFresh]
+  //              [ForwardingHint]
+  //              [Nonce]
+  //              [InterestLifetime]
+  //              [HopLimit]
+  //              [ApplicationParameters [InterestSignature]]
 
   auto element = m_wire.elements_begin();
   if (element == m_wire.elements_end() || element->type() != tlv::Name) {
     NDN_THROW(Error("Name element is missing or out of order"));
   }
-  m_name.wireDecode(*element);
-  if (m_name.empty()) {
+  // decode into a temporary object until we determine that the name is valid, in order
+  // to maintain class invariants and thus provide a basic form of exception safety
+  Name tempName(*element);
+  if (tempName.empty()) {
     NDN_THROW(Error("Name has zero name components"));
   }
-  int lastElement = 1; // last recognized element index, in spec order
+  ssize_t digestIndex = findParametersDigestComponent(tempName);
+  if (digestIndex == -2) {
+    NDN_THROW(Error("Name has more than one ParametersSha256DigestComponent"));
+  }
+  m_name = std::move(tempName);
 
   m_selectors = Selectors().setMaxSuffixComponents(1); // CanBePrefix=0
   m_nonce.reset();
   m_interestLifetime = DEFAULT_INTEREST_LIFETIME;
   m_forwardingHint = {};
-  m_parameters = {};
+  m_parameters.clear();
 
+  int lastElement = 1; // last recognized element index, in spec order
   for (++element; element != m_wire.elements_end(); ++element) {
     switch (element->type()) {
       case tlv::CanBePrefix: {
@@ -402,18 +425,29 @@
         if (lastElement >= 8) {
           break; // ApplicationParameters is non-critical, ignore out-of-order appearance
         }
-        m_parameters = *element;
+        BOOST_ASSERT(!hasApplicationParameters());
+        m_parameters.push_back(*element);
         lastElement = 8;
         break;
       }
-      default: {
+      default: { // unrecognized element
+        // if the TLV-TYPE is critical, abort decoding
         if (tlv::isCriticalType(element->type())) {
           NDN_THROW(Error("Unrecognized element of critical type " + to_string(element->type())));
         }
+        // if we already encountered ApplicationParameters, store this element as parameter
+        if (hasApplicationParameters()) {
+          m_parameters.push_back(*element);
+        }
+        // otherwise, ignore it
         break;
       }
     }
   }
+
+  if (s_autoCheckParametersDigest && !isParametersDigestValid()) {
+    NDN_THROW(Error("ParametersSha256DigestComponent does not match the SHA-256 of Interest parameters"));
+  }
 }
 
 std::string
@@ -493,7 +527,22 @@
          getMustBeFresh() == other.getMustBeFresh();
 }
 
-// ---- field accessors ----
+// ---- field accessors and modifiers ----
+
+Interest&
+Interest::setName(const Name& name)
+{
+  ssize_t digestIndex = findParametersDigestComponent(name);
+  if (digestIndex == -2) {
+    NDN_THROW(std::invalid_argument("Name cannot have more than one ParametersSha256DigestComponent"));
+  }
+  m_name = name;
+  if (hasApplicationParameters()) {
+    addOrReplaceParametersDigestComponent();
+  }
+  m_wire.reset();
+  return *this;
+}
 
 uint32_t
 Interest::getNonce() const
@@ -545,40 +594,56 @@
   return *this;
 }
 
+void
+Interest::setApplicationParametersInternal(Block parameters)
+{
+  parameters.encode(); // ensure we have wire encoding needed by computeParametersDigest()
+  if (m_parameters.empty()) {
+    m_parameters.push_back(std::move(parameters));
+  }
+  else {
+    BOOST_ASSERT(m_parameters[0].type() == tlv::ApplicationParameters);
+    m_parameters[0] = std::move(parameters);
+  }
+}
+
 Interest&
 Interest::setApplicationParameters(const Block& parameters)
 {
-  if (parameters.empty()) {
-    m_parameters = Block(tlv::ApplicationParameters);
+  if (!parameters.isValid()) {
+    setApplicationParametersInternal(Block(tlv::ApplicationParameters));
   }
   else if (parameters.type() == tlv::ApplicationParameters) {
-    m_parameters = parameters;
+    setApplicationParametersInternal(parameters);
   }
   else {
-    m_parameters = Block(tlv::ApplicationParameters, parameters);
+    setApplicationParametersInternal(Block(tlv::ApplicationParameters, parameters));
   }
+  addOrReplaceParametersDigestComponent();
   m_wire.reset();
   return *this;
 }
 
 Interest&
-Interest::setApplicationParameters(const uint8_t* buffer, size_t bufferSize)
+Interest::setApplicationParameters(const uint8_t* value, size_t length)
 {
-  if (buffer == nullptr && bufferSize != 0) {
+  if (value == nullptr && length != 0) {
     NDN_THROW(std::invalid_argument("ApplicationParameters buffer cannot be nullptr"));
   }
-  m_parameters = makeBinaryBlock(tlv::ApplicationParameters, buffer, bufferSize);
+  setApplicationParametersInternal(makeBinaryBlock(tlv::ApplicationParameters, value, length));
+  addOrReplaceParametersDigestComponent();
   m_wire.reset();
   return *this;
 }
 
 Interest&
-Interest::setApplicationParameters(ConstBufferPtr buffer)
+Interest::setApplicationParameters(ConstBufferPtr value)
 {
-  if (buffer == nullptr) {
+  if (value == nullptr) {
     NDN_THROW(std::invalid_argument("ApplicationParameters buffer cannot be nullptr"));
   }
-  m_parameters = Block(tlv::ApplicationParameters, std::move(buffer));
+  setApplicationParametersInternal(Block(tlv::ApplicationParameters, std::move(value)));
+  addOrReplaceParametersDigestComponent();
   m_wire.reset();
   return *this;
 }
@@ -586,11 +651,89 @@
 Interest&
 Interest::unsetApplicationParameters()
 {
-  m_parameters = {};
+  m_parameters.clear();
+  ssize_t digestIndex = findParametersDigestComponent(getName());
+  if (digestIndex >= 0) {
+    m_name.erase(digestIndex);
+  }
   m_wire.reset();
   return *this;
 }
 
+// ---- ParametersSha256DigestComponent support ----
+
+bool
+Interest::isParametersDigestValid() const
+{
+  ssize_t digestIndex = findParametersDigestComponent(getName());
+  if (digestIndex == -1) {
+    return !hasApplicationParameters();
+  }
+  // cannot be -2 because of the checks in setName() and wireDecode()
+  BOOST_ASSERT(digestIndex >= 0);
+
+  if (!hasApplicationParameters()) {
+    return false;
+  }
+
+  const auto& digestComponent = getName()[digestIndex];
+  auto digest = computeParametersDigest();
+
+  return std::equal(digestComponent.value_begin(), digestComponent.value_end(),
+                    digest->begin(), digest->end());
+}
+
+shared_ptr<Buffer>
+Interest::computeParametersDigest() const
+{
+  using namespace security::transform;
+
+  StepSource in;
+  OBufferStream out;
+  in >> digestFilter(DigestAlgorithm::SHA256) >> streamSink(out);
+
+  std::for_each(m_parameters.begin(), m_parameters.end(), [&] (const Block& b) {
+    in.write(b.wire(), b.size());
+  });
+  in.end();
+
+  return out.buf();
+}
+
+void
+Interest::addOrReplaceParametersDigestComponent()
+{
+  BOOST_ASSERT(hasApplicationParameters());
+
+  ssize_t digestIndex = findParametersDigestComponent(getName());
+  auto digestComponent = name::Component::fromParametersSha256Digest(computeParametersDigest());
+
+  if (digestIndex == -1) {
+    // no existing digest components, append one
+    m_name.append(std::move(digestComponent));
+  }
+  else {
+    // cannot be -2 because of the checks in setName() and wireDecode()
+    BOOST_ASSERT(digestIndex >= 0);
+    // replace the existing digest component
+    m_name.set(digestIndex, std::move(digestComponent));
+  }
+}
+
+ssize_t
+Interest::findParametersDigestComponent(const Name& name)
+{
+  ssize_t pos = -1;
+  for (ssize_t i = 0; i < static_cast<ssize_t>(name.size()); i++) {
+    if (name[i].isParametersSha256Digest()) {
+      if (pos != -1)
+        return -2;
+      pos = i;
+    }
+  }
+  return pos;
+}
+
 // ---- operators ----
 
 bool
diff --git a/ndn-cxx/interest.hpp b/ndn-cxx/interest.hpp
index 378954d..9c91c2e 100644
--- a/ndn-cxx/interest.hpp
+++ b/ndn-cxx/interest.hpp
@@ -51,7 +51,8 @@
   };
 
   /** @brief Construct an Interest with given @p name and @p lifetime.
-   *  @throw std::invalid_argument @p lifetime is negative
+   *
+   *  @throw std::invalid_argument @p name is invalid or @p lifetime is negative
    *  @warning In certain contexts that use `Interest::shared_from_this()`, Interest must be created
    *           using `make_shared`. Otherwise, `shared_from_this()` will trigger undefined behavior.
    */
@@ -59,6 +60,7 @@
   Interest(const Name& name = Name(), time::milliseconds lifetime = DEFAULT_INTEREST_LIFETIME);
 
   /** @brief Construct an Interest by decoding from @p wire.
+   *
    *  @warning In certain contexts that use `Interest::shared_from_this()`, Interest must be created
    *           using `make_shared`. Otherwise, `shared_from_this()` will trigger undefined behavior.
    */
@@ -132,13 +134,11 @@
     return m_name;
   }
 
+  /** @brief Set the Interest's name.
+   *  @throw std::invalid_argument @p name is invalid
+   */
   Interest&
-  setName(const Name& name)
-  {
-    m_name = name;
-    m_wire.reset();
-    return *this;
-  }
+  setName(const Name& name);
 
   /** @brief Declare the default CanBePrefix setting of the application.
    *
@@ -246,7 +246,7 @@
   /** @brief Check if the Nonce element is present.
    */
   bool
-  hasNonce() const
+  hasNonce() const noexcept
   {
     return m_nonce.has_value();
   }
@@ -277,60 +277,97 @@
     return m_interestLifetime;
   }
 
-  /** @brief Set Interest's lifetime
+  /** @brief Set the Interest's lifetime.
    *  @throw std::invalid_argument @p lifetime is negative
    */
   Interest&
   setInterestLifetime(time::milliseconds lifetime);
 
   bool
-  hasApplicationParameters() const
+  hasApplicationParameters() const noexcept
   {
     return !m_parameters.empty();
   }
 
-  const Block&
+  Block
   getApplicationParameters() const
   {
-    return m_parameters;
+    if (m_parameters.empty())
+      return {};
+    else
+      return m_parameters.front();
   }
 
-  /** @brief Set ApplicationParameters from a Block
-   *
-   *  If the block is default-constructed, this will set a zero-length
-   *  ApplicationParameters element.
-   *  Else, if the block's TLV-TYPE is ApplicationParameters, it will be
-   *  used directly as this Interest's ApplicationParameters element.
-   *  Else, the block will be nested into an ApplicationParameters element.
+  /** @brief Set ApplicationParameters from a Block.
    *  @return a reference to this Interest
+   *
+   *  If the block is default-constructed, this will set a zero-length ApplicationParameters
+   *  element. Else, if the block's TLV-TYPE is ApplicationParameters, it will be used directly
+   *  as this Interest's ApplicationParameters element. Else, the block will be nested into an
+   *  ApplicationParameters element.
+   *
+   *  This function will also recompute the value of the ParametersSha256DigestComponent in the
+   *  Interest's name. If the name does not contain a ParametersSha256DigestComponent, one will
+   *  be appended to it.
    */
   Interest&
   setApplicationParameters(const Block& parameters);
 
-  /** @brief Copy ApplicationParameters from raw buffer
-   *
-   *  @param buffer pointer to the first octet of parameters
-   *  @param bufferSize size of the raw buffer
+  /** @brief Set ApplicationParameters by copying from a raw buffer.
+   *  @param value points to a buffer from which the TLV-VALUE of the parameters will be copied;
+   *               may be nullptr if @p length is zero
+   *  @param length size of the buffer
    *  @return a reference to this Interest
+   *
+   *  This function will also recompute the value of the ParametersSha256DigestComponent in the
+   *  Interest's name. If the name does not contain a ParametersSha256DigestComponent, one will
+   *  be appended to it.
    */
   Interest&
-  setApplicationParameters(const uint8_t* buffer, size_t bufferSize);
+  setApplicationParameters(const uint8_t* value, size_t length);
 
-  /** @brief Set ApplicationParameters from a wire buffer
-   *
-   *  @param buffer buffer containing the parameters, must not be nullptr
+  /** @brief Set ApplicationParameters from a shared buffer.
+   *  @param value buffer containing the TLV-VALUE of the parameters; must not be nullptr
    *  @return a reference to this Interest
+   *
+   *  This function will also recompute the value of the ParametersSha256DigestComponent in the
+   *  Interest's name. If the name does not contain a ParametersSha256DigestComponent, one will
+   *  be appended to it.
    */
   Interest&
-  setApplicationParameters(ConstBufferPtr buffer);
+  setApplicationParameters(ConstBufferPtr value);
 
-  /** @brief Remove the ApplicationParameters element from this Interest
-   *
+  /** @brief Remove the ApplicationParameters element from this Interest.
    *  @post hasApplicationParameters() == false
+   *
+   *  This function will also remove any ParametersSha256DigestComponents from the Interest's name.
    */
   Interest&
   unsetApplicationParameters();
 
+public: // ParametersSha256DigestComponent support
+  static bool
+  getAutoCheckParametersDigest()
+  {
+    return s_autoCheckParametersDigest;
+  }
+
+  static void
+  setAutoCheckParametersDigest(bool b)
+  {
+    s_autoCheckParametersDigest = b;
+  }
+
+  /** @brief Check if the ParametersSha256DigestComponent in the name is valid.
+   *
+   *  Returns true if there is a single ParametersSha256DigestComponent in the name and the digest
+   *  value is correct, or if there is no ParametersSha256DigestComponent in the name and the
+   *  Interest does not contain any parameters.
+   *  Returns false otherwise.
+   */
+  bool
+  isParametersDigestValid() const;
+
 public: // Selectors (deprecated)
   /** @brief Check if Interest has any selector present.
    */
@@ -453,7 +490,7 @@
   /** @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.
+   *  @throw tlv::Error decoding error.
    */
   bool
   decode02();
@@ -464,23 +501,55 @@
   void
   decode03();
 
+  void
+  setApplicationParametersInternal(Block parameters);
+
+  shared_ptr<Buffer>
+  computeParametersDigest() const;
+
+  /** @brief Append a ParametersSha256DigestComponent to the Interest's name
+   *         or update the digest value in the existing component.
+   *
+   *  @pre The name is assumed to be valid, i.e., it must not contain more than one
+   *       ParametersSha256DigestComponent.
+   *  @pre hasApplicationParameters() == true
+   */
+  void
+  addOrReplaceParametersDigestComponent();
+
+  /** @brief Return the index of the ParametersSha256DigestComponent in @p name.
+   *
+   *  @retval pos The name contains exactly one ParametersSha256DigestComponent at index `pos`.
+   *  @retval -1  The name contains zero ParametersSha256DigestComponents.
+   *  @retval -2  The name contains more than one ParametersSha256DigestComponents.
+   */
+  static ssize_t
+  findParametersDigestComponent(const Name& name);
+
 #ifdef NDN_CXX_HAVE_TESTS
 public:
-  /** @brief If true, not setting CanBePrefix results in an error in wireEncode().
-   */
+  /// If true, not setting CanBePrefix results in an error in wireEncode().
   static bool s_errorIfCanBePrefixUnset;
 #endif // NDN_CXX_HAVE_TESTS
 
 private:
   static boost::logic::tribool s_defaultCanBePrefix;
+  static bool s_autoCheckParametersDigest;
 
   Name m_name;
   Selectors m_selectors; // NDN Packet Format v0.2 only
-  mutable bool m_isCanBePrefixSet;
+  mutable bool m_isCanBePrefixSet = false;
   mutable optional<uint32_t> m_nonce;
   time::milliseconds m_interestLifetime;
   DelegationList m_forwardingHint;
-  Block m_parameters; // NDN Packet Format v0.3 only
+
+  // Stores the "Interest parameters", i.e., all maybe-unrecognized non-critical TLV
+  // elements that appear at the end of the Interest, starting from ApplicationParameters.
+  // If the Interest does not contain any ApplicationParameters TLV, this vector will
+  // be empty. Conversely, if this vector is not empty, the first element will always
+  // be an ApplicationParameters block. All blocks in this vector are covered by the
+  // digest in the ParametersSha256DigestComponent.
+  std::vector<Block> m_parameters; // NDN Packet Format v0.3 only
 
   mutable Block m_wire;
 
diff --git a/ndn-cxx/name-component.hpp b/ndn-cxx/name-component.hpp
index 079b9fe..ef06d20 100644
--- a/ndn-cxx/name-component.hpp
+++ b/ndn-cxx/name-component.hpp
@@ -588,10 +588,11 @@
 
 private:
   /**
-   * @brief Throw @c Error if this NameComponent is invalid.
+   * @brief Throw Error if this Component is invalid.
    *
-   * A NameComponent is invalid if its TLV-TYPE is outside the 1-65535 range.
-   * Additionally, if this is an ImplicitSha256DigestComponent, the TLV-LENGTH must be 32.
+   * A name component is invalid if its TLV-TYPE is outside the [1, 65535] range.
+   * Additionally, if it is an ImplicitSha256DigestComponent or a ParametersSha256DigestComponent,
+   * its TLV-LENGTH must be 32.
    */
   void
   ensureValid() const;
diff --git a/ndn-cxx/name.cpp b/ndn-cxx/name.cpp
index 5ea12cf..01513b7 100644
--- a/ndn-cxx/name.cpp
+++ b/ndn-cxx/name.cpp
@@ -179,7 +179,7 @@
 Name::at(ssize_t i) const
 {
   if (i < 0) {
-    i = size() + i;
+    i += static_cast<ssize_t>(size());
   }
 
   if (i < 0 || static_cast<size_t>(i) >= size()) {
@@ -194,13 +194,13 @@
 {
   PartialName result;
 
-  ssize_t iStart = iStartComponent < 0 ? this->size() + iStartComponent : iStartComponent;
-  size_t iEnd = this->size();
+  if (iStartComponent < 0)
+    iStartComponent += static_cast<ssize_t>(size());
+  size_t iStart = iStartComponent < 0 ? 0 : static_cast<size_t>(iStartComponent);
 
-  iStart = std::max(iStart, static_cast<ssize_t>(0));
-
+  size_t iEnd = size();
   if (nComponents != npos)
-    iEnd = std::min(this->size(), iStart + nComponents);
+    iEnd = std::min(size(), iStart + nComponents);
 
   for (size_t i = iStart; i < iEnd; ++i)
     result.append(at(i));
@@ -211,6 +211,30 @@
 // ---- modifiers ----
 
 Name&
+Name::set(ssize_t i, const Component& component)
+{
+  if (i < 0) {
+    i += static_cast<ssize_t>(size());
+  }
+
+  const_cast<Block::element_container&>(m_wire.elements())[i] = component;
+  m_wire.resetWire();
+  return *this;
+}
+
+Name&
+Name::set(ssize_t i, Component&& component)
+{
+  if (i < 0) {
+    i += static_cast<ssize_t>(size());
+  }
+
+  const_cast<Block::element_container&>(m_wire.elements())[i] = std::move(component);
+  m_wire.resetWire();
+  return *this;
+}
+
+Name&
 Name::appendVersion(optional<uint64_t> version)
 {
   return append(Component::fromVersion(version.value_or(time::toUnixTimestamp(time::system_clock::now()).count())));
@@ -235,13 +259,45 @@
   return *this;
 }
 
+static constexpr uint8_t SHA256_OF_EMPTY_STRING[] = {
+  0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14,
+  0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
+  0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c,
+  0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55,
+};
+
+Name&
+Name::appendParametersSha256DigestPlaceholder()
+{
+  static const Component placeholder(tlv::ParametersSha256DigestComponent,
+                                     SHA256_OF_EMPTY_STRING, sizeof(SHA256_OF_EMPTY_STRING));
+  return append(placeholder);
+}
+
+void
+Name::erase(ssize_t i)
+{
+  if (i < 0) {
+    i += static_cast<ssize_t>(size());
+  }
+
+  m_wire.erase(m_wire.elements_begin() + i);
+}
+
+void
+Name::clear()
+{
+  m_wire = Block(tlv::Name);
+}
+
 // ---- algorithms ----
 
 Name
 Name::getSuccessor() const
 {
   if (empty()) {
-    return Name("/sha256digest=0000000000000000000000000000000000000000000000000000000000000000");
+    static const Name n("/sha256digest=0000000000000000000000000000000000000000000000000000000000000000");
+    return n;
   }
 
   return getPrefix(-1).append(get(-1).getSuccessor());
diff --git a/ndn-cxx/name.hpp b/ndn-cxx/name.hpp
index cbb33bd..27859fb 100644
--- a/ndn-cxx/name.hpp
+++ b/ndn-cxx/name.hpp
@@ -133,7 +133,7 @@
   deepCopy() const;
 
 public: // access
-  /** @brief Check if name is empty
+  /** @brief Checks if the name is empty, i.e. has no components.
    */
   bool
   empty() const
@@ -141,7 +141,7 @@
     return m_wire.elements().empty();
   }
 
-  /** @brief Get number of components
+  /** @brief Returns the number of components.
    */
   size_t
   size() const
@@ -149,20 +149,21 @@
     return m_wire.elements_size();
   }
 
-  /** @brief Get the component at the given index
-   *  @param i zero-based index; if negative, it starts at the end of this name
-   *  @warning Indexing out of bounds triggers undefined behavior.
+  /** @brief Returns an immutable reference to the component at the specified index.
+   *  @param i zero-based index of the component to return;
+   *           if negative, it is interpreted as offset from the end of the name
+   *  @warning No bounds checking is performed, using an out-of-range index is undefined behavior.
    */
   const Component&
   get(ssize_t i) const
   {
     if (i < 0) {
-      i += size();
+      i += static_cast<ssize_t>(size());
     }
     return reinterpret_cast<const Component&>(m_wire.elements()[i]);
   }
 
-  /** @brief Equivalent to get(i)
+  /** @brief Equivalent to `get(i)`.
    */
   const Component&
   operator[](ssize_t i) const
@@ -170,34 +171,35 @@
     return get(i);
   }
 
-  /** @brief Get the component at the given index
-   *  @param i zero-based index; if negative, size()+i is used instead
-   *  @throws Name::Error index is out of bounds
+  /** @brief Returns an immutable reference to the component at the specified index,
+   *         with bounds checking.
+   *  @param i zero-based index of the component to return;
+   *           if negative, it is interpreted as offset from the end of the name
+   *  @throws Error The index is out of bounds.
    */
   const Component&
   at(ssize_t i) const;
 
-  /** @brief Extract some components as a sub-name (PartialName)
+  /** @brief Extracts some components as a sub-name (PartialName).
    *  @param iStartComponent zero-based index of the first component;
    *                         if negative, size()+iStartComponent is used instead
-   *  @param nComponents Number of components starting at iStartComponent.
-   *                     Use @p npos to get the PartialName until the end of this Name.
+   *  @param nComponents number of desired components, starting at @p iStartComponent;
+   *                     use @c npos to return all components until the end of the name
    *  @return a new PartialName containing the extracted components
    *
-   *  If iStartComponent is positive and indexes out of bounds, returns an empty PartialName.
-   *  If iStartComponent is negative and indexes out of bounds, returns components starting from the
-   *  beginning of the Name. If nComponents is out of bounds, returns the components until the end
-   *  of this Name.
+   *  If @p iStartComponent is positive and indexes out of bounds, returns an empty PartialName.
+   *  If @p iStartComponent is negative and indexes out of bounds, the sub-name will start from
+   *  the beginning of the name instead. If @p nComponents is out of bounds, returns all components
+   *  until the end of the name.
    */
   PartialName
   getSubName(ssize_t iStartComponent, size_t nComponents = npos) const;
 
-  /** @brief Extract a prefix of the name
-   *  @param nComponents Number of components; if negative, size()+nComponents is used instead
-   *  @return a new Name containing the prefix
-   *                    the prefix up to name.size() - N. For example getPrefix(-1)
-   *                    returns the name without the final component.
-   * @return A new partial name
+  /** @brief Returns a prefix of the name.
+   *  @param nComponents number of components; if negative, size()+nComponents is used instead
+   *
+   *  Returns a new PartialName containing a prefix of this name up to `size() - nComponents`.
+   *  For example, `getPrefix(-1)` returns the name without the final component.
    */
   PartialName
   getPrefix(ssize_t nComponents) const
@@ -242,6 +244,26 @@
   }
 
 public: // modifiers
+  /** @brief Replace the component at the specified index.
+   *  @param i zero-based index of the component to replace;
+   *           if negative, it is interpreted as offset from the end of the name
+   *  @param component the new component to use as a replacement
+   *  @return a reference to this name, to allow chaining.
+   *  @warning No bounds checking is performed, using an out-of-range index is undefined behavior.
+   */
+  Name&
+  set(ssize_t i, const Component& component);
+
+  /** @brief Replace the component at the specified index.
+   *  @param i zero-based index of the component to replace;
+   *           if negative, it is interpreted as offset from the end of the name
+   *  @param component the new component to use as a replacement
+   *  @return a reference to this name, to allow chaining.
+   *  @warning No bounds checking is performed, using an out-of-range index is undefined behavior.
+   */
+  Name&
+  set(ssize_t i, Component&& component);
+
   /** @brief Append a component.
    *  @return a reference to this name, to allow chaining.
    */
@@ -252,6 +274,16 @@
     return *this;
   }
 
+  /** @brief Append a component.
+   *  @return a reference to this name, to allow chaining.
+   */
+  Name&
+  append(Component&& component)
+  {
+    m_wire.push_back(std::move(component));
+    return *this;
+  }
+
   /** @brief Append a NameComponent of TLV-TYPE @p type, copying @p count bytes at @p value as
    *         TLV-VALUE.
    *  @return a reference to this name, to allow chaining.
@@ -312,22 +344,29 @@
   }
 
   /** @brief Append a GenericNameComponent from a TLV element.
-   *  @param value a TLV element. If its type is @c tlv::GenericNameComponent, it is used as is.
-   *               Otherwise, it is encapsulated into a GenericNameComponent.
+   *  @param value a TLV element. If its TLV-TYPE is tlv::GenericNameComponent, it is
+   *               appended as is. Otherwise, it is nested into a GenericNameComponent.
    *  @return a reference to this name, to allow chaining.
    */
   Name&
-  append(const Block& value)
+  append(Block value)
   {
     if (value.type() == tlv::GenericNameComponent) {
-      m_wire.push_back(value);
+      m_wire.push_back(std::move(value));
     }
     else {
-      m_wire.push_back(Block(tlv::GenericNameComponent, value));
+      m_wire.push_back(Block(tlv::GenericNameComponent, std::move(value)));
     }
     return *this;
   }
 
+  /** @brief Append a PartialName.
+   *  @param name the components to append
+   *  @return a reference to this name, to allow chaining
+   */
+  Name&
+  append(const PartialName& name);
+
   /** @brief Append a component with a nonNegativeInteger
    *  @sa number the number
    *  @return a reference to this name, to allow chaining
@@ -408,7 +447,7 @@
     return append(Component::fromSequenceNumber(seqNo));
   }
 
-  /** @brief Append an ImplicitSha256Digest component
+  /** @brief Append an ImplicitSha256Digest component.
    *  @return a reference to this name, to allow chaining
    */
   Name&
@@ -417,7 +456,7 @@
     return append(Component::fromImplicitSha256Digest(std::move(digest)));
   }
 
-  /** @brief Append an ImplicitSha256Digest component
+  /** @brief Append an ImplicitSha256Digest component.
    *  @return a reference to this name, to allow chaining
    */
   Name&
@@ -426,12 +465,29 @@
     return append(Component::fromImplicitSha256Digest(digest, digestSize));
   }
 
-  /** @brief Append a PartialName
-   *  @param name the components to append
+  /** @brief Append a ParametersSha256Digest component.
    *  @return a reference to this name, to allow chaining
    */
   Name&
-  append(const PartialName& name);
+  appendParametersSha256Digest(ConstBufferPtr digest)
+  {
+    return append(Component::fromParametersSha256Digest(std::move(digest)));
+  }
+
+  /** @brief Append a ParametersSha256Digest component.
+   *  @return a reference to this name, to allow chaining
+   */
+  Name&
+  appendParametersSha256Digest(const uint8_t* digest, size_t digestSize)
+  {
+    return append(Component::fromParametersSha256Digest(digest, digestSize));
+  }
+
+  /** @brief Append a placeholder for a ParametersSha256Digest component.
+   *  @return a reference to this name, to allow chaining
+   */
+  Name&
+  appendParametersSha256DigestPlaceholder();
 
   /** @brief Append a component
    *  @note This makes push_back an alias of append, giving Name a similar API as STL vector.
@@ -443,14 +499,19 @@
     append(component);
   }
 
-  /** @brief Remove all components
-   *  @post empty() == true
+  /** @brief Erase the component at the specified index.
+   *  @param i zero-based index of the component to replace;
+   *           if negative, it is interpreted as offset from the end of the name
+   *  @warning No bounds checking is performed, using an out-of-range index is undefined behavior.
    */
   void
-  clear()
-  {
-    m_wire = Block(tlv::Name);
-  }
+  erase(ssize_t i);
+
+  /** @brief Remove all components.
+   *  @post `empty() == true`
+   */
+  void
+  clear();
 
 public: // algorithms
   /** @brief Get the successor of a name
@@ -529,7 +590,7 @@
   /** @brief compares [pos1, pos1+count1) components in this Name
    *         to [pos2, pos2+count2) components in @p other
    *
-   *  This is equivalent to this->getSubName(pos1, count1).compare(other.getSubName(pos2, count2));
+   *  Equivalent to `getSubName(pos1, count1).compare(other.getSubName(pos2, count2))`.
    */
   int
   compare(size_t pos1, size_t count1,
@@ -576,7 +637,7 @@
   }
 
 public:
-  /** @brief indicates "until the end" in getSubName and compare
+  /** @brief Indicates "until the end" in getSubName() and compare().
    */
   static const size_t npos;
 
diff --git a/tests/make-interest-data.hpp b/tests/make-interest-data.hpp
index b9a9aec..10381fd 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-2018 Regents of the University of California.
+ * Copyright (c) 2013-2019 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -68,27 +68,17 @@
 lp::Nack
 makeNack(const Interest& interest, lp::NackReason reason);
 
-/** \brief replace a name component
- *  \param[inout] name name
- *  \param index name component index
- *  \param a arguments to name::Component constructor
+/** \brief replace a name component in a packet
+ *  \param[inout] pkt the packet
+ *  \param index the index of the name component to replace
+ *  \param args arguments to name::Component constructor
  */
-template<typename...A>
+template<typename Packet, typename ...Args>
 void
-setNameComponent(Name& name, ssize_t index, const A& ...a)
-{
-  Name name2 = name.getPrefix(index);
-  name2.append(name::Component(a...));
-  name2.append(name.getSubName(name2.size()));
-  name = name2;
-}
-
-template<typename PKT, typename...A>
-void
-setNameComponent(PKT& pkt, ssize_t index, const A& ...a)
+setNameComponent(Packet& pkt, ssize_t index, Args&& ...args)
 {
   Name name = pkt.getName();
-  setNameComponent(name, index, a...);
+  name.set(index, name::Component(std::forward<Args>(args)...));
   pkt.setName(name);
 }
 
diff --git a/tests/unit/interest.t.cpp b/tests/unit/interest.t.cpp
index 9c4b55d..e536755 100644
--- a/tests/unit/interest.t.cpp
+++ b/tests/unit/interest.t.cpp
@@ -32,21 +32,38 @@
 
 BOOST_AUTO_TEST_SUITE(TestInterest)
 
-// ---- constructor, encode, decode ----
+class DisableAutoCheckParametersDigest
+{
+public:
+  DisableAutoCheckParametersDigest()
+    : m_saved(Interest::getAutoCheckParametersDigest())
+  {
+    Interest::setAutoCheckParametersDigest(false);
+  }
+
+  ~DisableAutoCheckParametersDigest()
+  {
+    Interest::setAutoCheckParametersDigest(m_saved);
+  }
+
+private:
+  bool m_saved;
+};
 
 BOOST_AUTO_TEST_CASE(DefaultConstructor)
 {
   Interest i;
-  BOOST_CHECK(!i.hasWire());
+  BOOST_CHECK_EQUAL(i.hasWire(), false);
   BOOST_CHECK_EQUAL(i.getName(), "/");
   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.getForwardingHint().empty(), true);
+  BOOST_CHECK_EQUAL(i.hasNonce(), false);
   BOOST_CHECK_EQUAL(i.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
-  BOOST_CHECK(!i.hasSelectors());
-  BOOST_CHECK(!i.hasApplicationParameters());
-  BOOST_CHECK(i.getApplicationParameters().empty());
+  BOOST_CHECK_EQUAL(i.hasSelectors(), false);
+  BOOST_CHECK_EQUAL(i.hasApplicationParameters(), false);
+  BOOST_CHECK_EQUAL(i.getApplicationParameters().isValid(), false);
+  BOOST_CHECK_EQUAL(i.isParametersDigestValid(), true);
 }
 
 BOOST_AUTO_TEST_CASE(DecodeNotInterest)
@@ -54,7 +71,9 @@
   BOOST_CHECK_THROW(Interest("4202CAFE"_block), tlv::Error);
 }
 
-BOOST_AUTO_TEST_CASE(EncodeDecode02Basic)
+BOOST_AUTO_TEST_SUITE(EncodeDecode02)
+
+BOOST_AUTO_TEST_CASE(Basic)
 {
   const uint8_t WIRE[] = {
     0x05, 0x1c, // Interest
@@ -77,11 +96,13 @@
   BOOST_CHECK(i2.getSelectors().empty());
   BOOST_CHECK_EQUAL(i2.getNonce(), 1);
   BOOST_CHECK_EQUAL(i2.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
+  BOOST_CHECK_EQUAL(i2.hasApplicationParameters(), false);
+  BOOST_CHECK_EQUAL(i2.isParametersDigestValid(), true);
 
   BOOST_CHECK_EQUAL(i1, i2);
 }
 
-BOOST_AUTO_TEST_CASE(EncodeDecode02Full)
+BOOST_AUTO_TEST_CASE(Full)
 {
   const uint8_t WIRE[] = {
     0x05, 0x31, // Interest
@@ -117,51 +138,146 @@
   BOOST_CHECK_EQUAL(i2.getNonce(), 1);
   BOOST_CHECK_EQUAL(i2.getInterestLifetime(), 1000_ms);
   BOOST_CHECK_EQUAL(i2.getForwardingHint(), DelegationList({{1, "/A"}}));
+  BOOST_CHECK_EQUAL(i2.hasApplicationParameters(), false);
+  BOOST_CHECK_EQUAL(i2.isParametersDigestValid(), true);
 
   BOOST_CHECK_EQUAL(i1, i2);
 }
 
-BOOST_AUTO_TEST_CASE(EncodeDecode03Basic)
+BOOST_AUTO_TEST_CASE(ParametersSha256DigestComponent)
 {
   const uint8_t WIRE[] = {
-    0x05, 0x22, // Interest
-          0x07, 0x14, // Name
+    0x05, 0x60, // Interest
+          0x07, 0x58, // Name
                 0x08, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, // GenericNameComponent
                 0x08, 0x03, 0x6e, 0x64, 0x6e, // GenericNameComponent
                 0x08, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, // GenericNameComponent
+                0x02, 0x20, // ParametersSha256DigestComponent
+                      0xff, 0x91, 0x00, 0xe0, 0x4e, 0xaa, 0xdc, 0xf3, 0x06, 0x74, 0xd9, 0x80,
+                      0x26, 0xa0, 0x51, 0xba, 0x25, 0xf5, 0x6b, 0x69, 0xbf, 0xa0, 0x26, 0xdc,
+                      0xcc, 0xd7, 0x2c, 0x6e, 0xa0, 0xf7, 0x31, 0x5a,
+                0x02, 0x20, // ParametersSha256DigestComponent
+                      0xff, 0x91, 0x00, 0xe0, 0x4e, 0xaa, 0xdc, 0xf3, 0x06, 0x74, 0xd9, 0x80,
+                      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,
+  };
+
+  Interest i1("/I");
+  BOOST_CHECK_THROW(i1.wireDecode(Block(WIRE, sizeof(WIRE))), tlv::Error);
+
+  // i1 is still in a valid state
+  BOOST_CHECK_EQUAL(i1.getName(), "/I");
+  BOOST_CHECK_EQUAL(i1.isParametersDigestValid(), true);
+
+  Interest i2("/I/params-sha256=f16db273f40436a852063f864d5072b01ead53151f5a688ea1560492bebedd05");
+  i2.setCanBePrefix(true);
+  i2.setNonce(2);
+  BOOST_CHECK_EQUAL(i2.isParametersDigestValid(), false);
+  // encoding in v0.2 format does not validate the ParametersSha256DigestComponent
+  Block wire2 = i2.wireEncode();
+  BOOST_CHECK_EQUAL(wire2, "052D 0725(080149 "
+                           "0220F16DB273F40436A852063F864D5072B01EAD53151F5A688EA1560492BEBEDD05) "
+                           "0A0402000000"_block);
+
+  // decoding from v0.2 format does not validate the ParametersSha256DigestComponent
+  Interest i3(wire2);
+  BOOST_CHECK_EQUAL(i3.getName(), i2.getName());
+  BOOST_CHECK_EQUAL(i3.isParametersDigestValid(), false);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // EncodeDecode02
+
+BOOST_AUTO_TEST_SUITE(Encode03)
+
+// Enable after #4567
+//BOOST_AUTO_TEST_CASE(Basic)
+//{
+//  const uint8_t WIRE[] = {
+//    0x05, 0x1c, // Interest
+//          0x07, 0x14, // Name
+//                0x08, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, // GenericNameComponent
+//                0x08, 0x03, 0x6e, 0x64, 0x6e, // GenericNameComponent
+//                0x08, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, // GenericNameComponent
+//          0x0a, 0x04, // Nonce
+//                0x01, 0x00, 0x00, 0x00,
+//  };
+
+//  Interest i1;
+//  i1.setName("/local/ndn/prefix");
+//  i1.setCanBePrefix(false);
+//  i1.setNonce(1);
+//  BOOST_CHECK_EQUAL(i1.isParametersDigestValid(), true);
+
+//  Block wire1 = i1.wireEncode();
+//  BOOST_CHECK_EQUAL_COLLECTIONS(wire1.begin(), wire1.end(), WIRE, WIRE + sizeof(WIRE));
+
+//  Interest i2(wire1);
+//  BOOST_CHECK_EQUAL(i2.getName(), "/local/ndn/prefix");
+//  BOOST_CHECK_EQUAL(i2.getCanBePrefix(), false);
+//  BOOST_CHECK_EQUAL(i2.getMustBeFresh(), false);
+//  BOOST_CHECK_EQUAL(i2.getForwardingHint().empty(), true);
+//  BOOST_CHECK_EQUAL(i2.getNonce(), 1);
+//  BOOST_CHECK_EQUAL(i2.getInterestLifetime(), DEFAULT_INTEREST_LIFETIME);
+//  BOOST_CHECK_EQUAL(i2.hasApplicationParameters(), false);
+//  BOOST_CHECK_EQUAL(i2.getApplicationParameters().isValid(), false);
+//  BOOST_CHECK_EQUAL(i2.getPublisherPublicKeyLocator().empty(), true);
+//}
+
+BOOST_AUTO_TEST_CASE(WithParameters)
+{
+  const uint8_t WIRE[] = {
+    0x05, 0x44, // Interest
+          0x07, 0x36, // Name
+                0x08, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, // GenericNameComponent
+                0x08, 0x03, 0x6e, 0x64, 0x6e, // GenericNameComponent
+                0x08, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, // GenericNameComponent
+                0x02, 0x20, // ParametersSha256DigestComponent
+                      0xff, 0x91, 0x00, 0xe0, 0x4e, 0xaa, 0xdc, 0xf3, 0x06, 0x74, 0xd9, 0x80,
+                      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,
           0x24, 0x04, // ApplicationParameters
-                0xc0, 0xc1, 0xc2, 0xc3};
+                0xc0, 0xc1, 0xc2, 0xc3
+  };
 
   Interest i1;
   i1.setName("/local/ndn/prefix");
   i1.setCanBePrefix(false);
   i1.setNonce(1);
   i1.setApplicationParameters("2404C0C1C2C3"_block);
+  BOOST_CHECK_EQUAL(i1.isParametersDigestValid(), true);
+
   Block wire1 = i1.wireEncode();
   BOOST_CHECK_EQUAL_COLLECTIONS(wire1.begin(), wire1.end(), WIRE, WIRE + sizeof(WIRE));
 
   Interest i2(wire1);
-  BOOST_CHECK_EQUAL(i2.getName(), "/local/ndn/prefix");
+  BOOST_CHECK_EQUAL(i2.getName(),
+                    "/local/ndn/prefix/params-sha256=ff9100e04eaadcf30674d98026a051ba25f56b69bfa026dcccd72c6ea0f7315a");
   BOOST_CHECK_EQUAL(i2.getCanBePrefix(), false);
   BOOST_CHECK_EQUAL(i2.getMustBeFresh(), false);
-  BOOST_CHECK(i2.getForwardingHint().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.hasApplicationParameters());
+  BOOST_CHECK_EQUAL(i2.hasApplicationParameters(), true);
   BOOST_CHECK_EQUAL(i2.getApplicationParameters(), "2404C0C1C2C3"_block);
-  BOOST_CHECK(i2.getPublisherPublicKeyLocator().empty());
+  BOOST_CHECK_EQUAL(i2.getPublisherPublicKeyLocator().empty(), true);
 }
 
-BOOST_AUTO_TEST_CASE(EncodeDecode03Full)
+BOOST_AUTO_TEST_CASE(Full)
 {
   const uint8_t WIRE[] = {
-    0x05, 0x37, // Interest
-          0x07, 0x14, // Name
+    0x05, 0x59, // Interest
+          0x07, 0x36, // Name
                 0x08, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, // GenericNameComponent
                 0x08, 0x03, 0x6e, 0x64, 0x6e, // GenericNameComponent
                 0x08, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, // GenericNameComponent
+                0x02, 0x20, // ParametersSha256DigestComponent
+                      0xff, 0x91, 0x00, 0xe0, 0x4e, 0xaa, 0xdc, 0xf3, 0x06, 0x74, 0xd9, 0x80,
+                      0x26, 0xa0, 0x51, 0xba, 0x25, 0xf5, 0x6b, 0x69, 0xbf, 0xa0, 0x26, 0xdc,
+                      0xcc, 0xd7, 0x2c, 0x6e, 0xa0, 0xf7, 0x31, 0x5a,
           0x21, 0x00, // CanBePrefix
           0x12, 0x00, // MustBeFresh
           0x1e, 0x0b, // ForwardingHint
@@ -175,7 +291,9 @@
           0x0c, 0x02, // Interest Lifetime
                 0x76, 0xa1,
           0x24, 0x04, // ApplicationParameters
-                0xc0, 0xc1, 0xc2, 0xc3};
+                0xc0, 0xc1, 0xc2, 0xc3
+  };
+
   Interest i1;
   i1.setName("/local/ndn/prefix");
   i1.setMustBeFresh(true);
@@ -186,22 +304,51 @@
   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
+  BOOST_CHECK_EQUAL(i1.isParametersDigestValid(), true);
+
   Block wire1 = i1.wireEncode();
   BOOST_CHECK_EQUAL_COLLECTIONS(wire1.begin(), wire1.end(), WIRE, WIRE + sizeof(WIRE));
 
   Interest i2(wire1);
-  BOOST_CHECK_EQUAL(i2.getName(), "/local/ndn/prefix");
+  BOOST_CHECK_EQUAL(i2.getName(),
+                    "/local/ndn/prefix/params-sha256=ff9100e04eaadcf30674d98026a051ba25f56b69bfa026dcccd72c6ea0f7315a");
   BOOST_CHECK_EQUAL(i2.getCanBePrefix(), true);
   BOOST_CHECK_EQUAL(i2.getMustBeFresh(), true);
   BOOST_CHECK_EQUAL(i2.getForwardingHint(), DelegationList({{15893, "/H"}}));
-  BOOST_CHECK(i2.hasNonce());
+  BOOST_CHECK_EQUAL(i2.hasNonce(), true);
   BOOST_CHECK_EQUAL(i2.getNonce(), 0x4c1ecb4a);
   BOOST_CHECK_EQUAL(i2.getInterestLifetime(), 30369_ms);
   BOOST_CHECK_EQUAL(i2.getApplicationParameters(), "2404C0C1C2C3"_block);
   BOOST_CHECK_EQUAL(i2.getMinSuffixComponents(), -1); // Default because minSuffixComponents was not encoded
-  BOOST_CHECK(i2.getExclude().empty()); // Exclude was not encoded
+  BOOST_CHECK_EQUAL(i2.getExclude().empty(), true); // Exclude was not encoded
 }
 
+// Enable after #4567
+//BOOST_AUTO_TEST_CASE(MissingApplicationParameters)
+//{
+//  Interest i;
+//  i.setName(Name("/A").appendParametersSha256DigestPlaceholder());
+//  i.setCanBePrefix(false);
+//  BOOST_CHECK_EQUAL(i.isParametersDigestValid(), false);
+//  BOOST_CHECK_THROW(i.wireEncode(), tlv::Error);
+//}
+
+BOOST_AUTO_TEST_CASE(MissingParametersSha256DigestComponent)
+{
+  // there's no way to create an Interest that fails this check via programmatic construction,
+  // so we have to decode an invalid Interest and force reencoding
+
+  DisableAutoCheckParametersDigest disabler;
+  Interest i("050F 0703(080149) 0A04F000F000 2402CAFE"_block);
+  BOOST_CHECK_EQUAL(i.isParametersDigestValid(), false);
+  BOOST_CHECK_NO_THROW(i.wireEncode()); // this succeeds because it uses the cached wire encoding
+
+  i.setNonce(42); // trigger reencoding
+  BOOST_CHECK_THROW(i.wireEncode(), tlv::Error); // now the check fails while attempting to reencode
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Encode03
+
 class Decode03Fixture
 {
 protected:
@@ -222,38 +369,54 @@
 
 BOOST_FIXTURE_TEST_SUITE(Decode03, Decode03Fixture)
 
-BOOST_AUTO_TEST_CASE(Minimal)
+BOOST_AUTO_TEST_CASE(NameOnly)
 {
-  i.wireDecode("0505 0703080149"_block);
+  i.wireDecode("0505 0703(080149)"_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.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.getPublisherPublicKeyLocator().empty());
-  BOOST_CHECK(!i.hasApplicationParameters());
+  BOOST_CHECK_EQUAL(i.getPublisherPublicKeyLocator().empty(), true);
+  BOOST_CHECK_EQUAL(i.hasApplicationParameters(), false);
+  BOOST_CHECK_EQUAL(i.getApplicationParameters().isValid(), false);
 
   BOOST_CHECK(!i.hasWire()); // nonce generation resets wire encoding
 
   // modify then re-encode as v0.2 format
   i.setNonce(0x54657c95);
-  BOOST_CHECK_EQUAL(i.wireEncode(), "0510 0703080149 09030E0101 0A04957C6554"_block);
+  BOOST_CHECK_EQUAL(i.wireEncode(), "0510 0703(080149) 09030E0101 0A04957C6554"_block);
 }
 
-BOOST_AUTO_TEST_CASE(Full)
+BOOST_AUTO_TEST_CASE(NameCanBePrefix)
 {
-  i.wireDecode("0531 0703080149 FC00 2100 FC00 1200 "
-               "FC00 1E0B(1F09 1E023E15 0703080148) FC00 0A044ACB1E4C "
-               "FC00 0C0276A1 FC00 2201D6 FC00"_block);
+  i.wireDecode("0507 0703(080149) 2100"_block);
+  BOOST_CHECK_EQUAL(i.getName(), "/I");
+  BOOST_CHECK_EQUAL(i.getCanBePrefix(), true);
+  BOOST_CHECK_EQUAL(i.getMustBeFresh(), false);
+  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_EQUAL(i.hasApplicationParameters(), false);
+  BOOST_CHECK_EQUAL(i.getApplicationParameters().isValid(), false);
+}
+
+BOOST_AUTO_TEST_CASE(FullWithoutParameters)
+{
+  i.wireDecode("0531 0703(080149) "
+               "FC00 2100 FC00 1200 FC00 1E0B(1F09 1E023E15 0703080148) "
+               "FC00 0A044ACB1E4C FC00 0C0276A1 FC00 2201D6 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.hasNonce(), true);
   BOOST_CHECK_EQUAL(i.getNonce(), 0x4c1ecb4a);
   BOOST_CHECK_EQUAL(i.getInterestLifetime(), 30369_ms);
   // HopLimit=214 is not stored
+  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);
@@ -261,7 +424,46 @@
   // modify then re-encode as v0.2 format
   i.setName("/J");
   BOOST_CHECK_EQUAL(i.wireEncode(),
-    "0520 070308014A 09021200 0A044ACB1E4C 0C0276A1 1E0B(1F09 1E023E15 0703080148)"_block);
+                    "0520 0703(08014A) 09021200 0A044ACB1E4C 0C0276A1 1E0B(1F09 1E023E15 0703080148)"_block);
+}
+
+BOOST_AUTO_TEST_CASE(FullWithParameters)
+{
+  i.wireDecode("055B 0725(080149 0220F16DB273F40436A852063F864D5072B01EAD53151F5A688EA1560492BEBEDD05) "
+               "FC00 2100 FC00 1200 FC00 1E0B(1F09 1E023E15 0703080148) "
+               "FC00 0A044ACB1E4C FC00 0C0276A1 FC00 2201D6 FC00 2404C0C1C2C3 FC00"_block);
+  BOOST_CHECK_EQUAL(i.getName(),
+                    "/I/params-sha256=f16db273f40436a852063f864d5072b01ead53151f5a688ea1560492bebedd05");
+  BOOST_CHECK_EQUAL(i.getCanBePrefix(), true);
+  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.getInterestLifetime(), 30369_ms);
+  // HopLimit=214 is not stored
+  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)
+  i.setName("/J");
+  BOOST_CHECK_EQUAL(i.isParametersDigestValid(), true);
+  BOOST_CHECK_EQUAL(i.wireEncode(),
+                    "054A 0725(08014A 0220F16DB273F40436A852063F864D5072B01EAD53151F5A688EA1560492BEBEDD05) "
+                    "2100 1200 1E0B(1F09 1E023E15 0703080148) "
+                    "0A044ACB1E4C 0C0276A1 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) "
+                    "2100 1200 1E0B(1F09 1E023E15 0703080148) "
+                    "0A044ACB1E4C 0C0276A1 2402CAFE FC00"_block);
 }
 
 BOOST_AUTO_TEST_CASE(CriticalElementOutOfOrder)
@@ -294,28 +496,41 @@
 
 BOOST_AUTO_TEST_CASE(NonCriticalElementOutOfOrder)
 {
-  // HopLimit
-  i.wireDecode("0514 0703080149 2201D6 2200 2404C0C1C2C3 22020101"_block);
-  BOOST_CHECK_EQUAL(i.getName(), "/I");
+  // duplicate HopLimit
+  i.wireDecode("0536 0725(080149 0220FF9100E04EAADCF30674D98026A051BA25F56B69BFA026DCCCD72C6EA0F7315A)"
+               "2201D6 2200 2404C0C1C2C3 22020101"_block);
+  BOOST_CHECK_EQUAL(i.getName(),
+                    "/I/params-sha256=ff9100e04eaadcf30674d98026a051ba25f56b69bfa026dcccd72c6ea0f7315a");
   // HopLimit=214 is not stored
+  BOOST_CHECK_EQUAL(i.hasApplicationParameters(), true);
   BOOST_CHECK_EQUAL(i.getApplicationParameters(), "2404C0C1C2C3"_block);
 
-  // ApplicationParameters
-  i.wireDecode("051F 0703080149 2100 1200 0A044ACB1E4C 0C0276A1 2201D6 2404C0C1C2C3 2401EE"_block);
-  BOOST_CHECK_EQUAL(i.getName(), "/I");
+  // duplicate ApplicationParameters
+  i.wireDecode("0541 0725(080149 0220FF9100E04EAADCF30674D98026A051BA25F56B69BFA026DCCCD72C6EA0F7315A)"
+               "2100 1200 0A044ACB1E4C 0C0276A1 2201D6 2404C0C1C2C3 2401EE"_block);
+  BOOST_CHECK_EQUAL(i.getName(),
+                    "/I/params-sha256=ff9100e04eaadcf30674d98026a051ba25f56b69bfa026dcccd72c6ea0f7315a");
   BOOST_CHECK_EQUAL(i.hasApplicationParameters(), true);
   BOOST_CHECK_EQUAL(i.getApplicationParameters(), "2404C0C1C2C3"_block);
 }
 
-BOOST_AUTO_TEST_CASE(NameMissing)
+BOOST_AUTO_TEST_CASE(MissingName)
 {
   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_AUTO_TEST_CASE(BadName)
 {
+  // empty
   BOOST_CHECK_THROW(i.wireDecode("0502 0700"_block), tlv::Error);
+
+  // more than one ParametersSha256DigestComponent
+  BOOST_CHECK_THROW(i.wireDecode("054C 074A(080149"
+                                 "02200000000000000000000000000000000000000000000000000000000000000000"
+                                 "080132"
+                                 "02200000000000000000000000000000000000000000000000000000000000000000)"_block),
+                    tlv::Error);
 }
 
 BOOST_AUTO_TEST_CASE(BadCanBePrefix)
@@ -341,6 +556,29 @@
   BOOST_CHECK_THROW(i.wireDecode("0509 0703080149 22021356"_block), tlv::Error);
 }
 
+BOOST_AUTO_TEST_CASE(BadParametersDigest)
+{
+  // ApplicationParameters without ParametersSha256DigestComponent
+  Block b1("0509 0703(080149) 2402CAFE"_block);
+  // ParametersSha256DigestComponent without ApplicationParameters
+  Block b2("0527 0725(080149 0220E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855)"_block);
+  // digest mismatch
+  Block b3("052B 0725(080149 02200000000000000000000000000000000000000000000000000000000000000000) "
+           "2402CAFE"_block);
+
+  BOOST_CHECK_THROW(i.wireDecode(b1), tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode(b2), tlv::Error);
+  BOOST_CHECK_THROW(i.wireDecode(b3), tlv::Error);
+
+  DisableAutoCheckParametersDigest disabler;
+  BOOST_CHECK_NO_THROW(i.wireDecode(b1));
+  BOOST_CHECK_EQUAL(i.isParametersDigestValid(), false);
+  BOOST_CHECK_NO_THROW(i.wireDecode(b2));
+  BOOST_CHECK_EQUAL(i.isParametersDigestValid(), false);
+  BOOST_CHECK_NO_THROW(i.wireDecode(b3));
+  BOOST_CHECK_EQUAL(i.isParametersDigestValid(), false);
+}
+
 BOOST_AUTO_TEST_CASE(UnrecognizedNonCriticalElementBeforeName)
 {
   BOOST_CHECK_THROW(i.wireDecode("0507 FC00 0703080149"_block), tlv::Error);
@@ -353,8 +591,6 @@
 
 BOOST_AUTO_TEST_SUITE_END() // Decode03
 
-// ---- matching ----
-
 BOOST_AUTO_TEST_CASE(MatchesData)
 {
   auto interest = makeInterest("/A");
@@ -381,8 +617,8 @@
   interest = makeInterest(data->getFullName());
   BOOST_CHECK_EQUAL(interest->matchesData(*data), true);
 
-  setNameComponent(*interest, -1, Name("/sha256digest=000000000000000000000000"
-                                       "0000000000000000000000000000000000000000").at(0));
+  setNameComponent(*interest, -1, name::Component::fromEscapedString(
+                     "sha256digest=0000000000000000000000000000000000000000000000000000000000000000"));
   BOOST_CHECK_EQUAL(interest->matchesData(*data), false); // violates implicit digest
 }
 
@@ -418,9 +654,22 @@
   BOOST_CHECK_EQUAL(interest.matchesInterest(other), true);
 }
 
-// ---- field accessors ----
+BOOST_AUTO_TEST_CASE(SetName)
+{
+  Interest i;
+  BOOST_CHECK_EQUAL(i.getName(), "/");
+  i.setName("/A/B");
+  BOOST_CHECK_EQUAL(i.getName(), "/A/B");
+  i.setName("/I/params-sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
+  BOOST_CHECK_EQUAL(i.getName(),
+                    "/I/params-sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
+  BOOST_CHECK_THROW(i.setName("/I"
+                              "/params-sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+                              "/params-sha256=0000000000000000000000000000000000000000000000000000000000000000"),
+                    std::invalid_argument);
+}
 
-BOOST_AUTO_TEST_CASE(CanBePrefix)
+BOOST_AUTO_TEST_CASE(SetCanBePrefix)
 {
   Interest i;
   BOOST_CHECK_EQUAL(i.getCanBePrefix(), true);
@@ -432,7 +681,7 @@
   BOOST_CHECK_EQUAL(i.getSelectors().getMaxSuffixComponents(), -1);
 }
 
-BOOST_AUTO_TEST_CASE(MustBeFresh)
+BOOST_AUTO_TEST_CASE(SetMustBeFresh)
 {
   Interest i;
   BOOST_CHECK_EQUAL(i.getMustBeFresh(), false);
@@ -562,7 +811,46 @@
   BOOST_CHECK_THROW(i.setApplicationParameters(nullptr), std::invalid_argument);
 }
 
-// ---- operators ----
+BOOST_AUTO_TEST_CASE(ParametersSha256DigestComponent)
+{
+  Interest i("/I");
+  BOOST_CHECK_EQUAL(i.isParametersDigestValid(), true);
+
+  i.setApplicationParameters("2404C0C1C2C3"_block); // auto-appends ParametersSha256DigestComponent
+  BOOST_CHECK_EQUAL(i.getName(),
+                    "/I/params-sha256=ff9100e04eaadcf30674d98026a051ba25f56b69bfa026dcccd72c6ea0f7315a");
+  BOOST_CHECK_EQUAL(i.isParametersDigestValid(), true);
+
+  i.setApplicationParameters(nullptr, 0); // updates ParametersSha256DigestComponent
+  BOOST_CHECK_EQUAL(i.getName(),
+                    "/I/params-sha256=33b67cb5385ceddad93d0ee960679041613bed34b8b4a5e6362fe7539ba2d3ce");
+  BOOST_CHECK_EQUAL(i.hasApplicationParameters(), true);
+  BOOST_CHECK_EQUAL(i.isParametersDigestValid(), true);
+
+  i.unsetApplicationParameters(); // removes ParametersSha256DigestComponent
+  BOOST_CHECK_EQUAL(i.getName(), "/I");
+  BOOST_CHECK_EQUAL(i.isParametersDigestValid(), true);
+
+  i.setName(Name("/P").appendParametersSha256DigestPlaceholder().append("Q"));
+  BOOST_CHECK_EQUAL(i.hasApplicationParameters(), false);
+  BOOST_CHECK_EQUAL(i.isParametersDigestValid(), false);
+
+  i.unsetApplicationParameters(); // removes ParametersSha256DigestComponent
+  BOOST_CHECK_EQUAL(i.getName(), "/P/Q");
+  BOOST_CHECK_EQUAL(i.isParametersDigestValid(), true);
+
+  i.setName(Name("/P").appendParametersSha256DigestPlaceholder().append("Q"));
+  i.setApplicationParameters("2404C0C1C2C3"_block); // updates ParametersSha256DigestComponent
+  BOOST_CHECK_EQUAL(i.getName(),
+                    "/P/params-sha256=ff9100e04eaadcf30674d98026a051ba25f56b69bfa026dcccd72c6ea0f7315a/Q");
+  BOOST_CHECK_EQUAL(i.isParametersDigestValid(), true);
+
+  i.setName("/A/B/C"); // auto-appends ParametersSha256DigestComponent
+  BOOST_CHECK_EQUAL(i.getName(),
+                    "/A/B/C/params-sha256=ff9100e04eaadcf30674d98026a051ba25f56b69bfa026dcccd72c6ea0f7315a");
+  BOOST_CHECK_EQUAL(i.hasApplicationParameters(), true);
+  BOOST_CHECK_EQUAL(i.isParametersDigestValid(), true);
+}
 
 BOOST_AUTO_TEST_CASE(Equality)
 {
diff --git a/tests/unit/name.t.cpp b/tests/unit/name.t.cpp
index a560678..362f443 100644
--- a/tests/unit/name.t.cpp
+++ b/tests/unit/name.t.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-2019 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -197,6 +197,29 @@
 
 // ---- modifiers ----
 
+BOOST_AUTO_TEST_CASE(SetComponent)
+{
+  Name name("/A/B");
+  BOOST_CHECK_EQUAL(name.wireEncode(), "0706 080141 080142"_block);
+  BOOST_CHECK_EQUAL(name.hasWire(), true);
+
+  // pass by const lvalue ref
+  const Component c("C");
+  name.set(0, c);
+  BOOST_CHECK_EQUAL(name.hasWire(), false);
+  BOOST_CHECK_EQUAL(name.wireEncode(), "0706 080143 080142"_block);
+
+  // pass by rvalue ref
+  Component d("D");
+  name.set(1, std::move(d));
+  BOOST_CHECK_EQUAL(name.hasWire(), false);
+  BOOST_CHECK_EQUAL(name.wireEncode(), "0706 080143 080144"_block);
+
+  // negative index
+  name.set(-1, Component("E"));
+  BOOST_CHECK_EQUAL(name.wireEncode(), "0706 080143 080145"_block);
+}
+
 BOOST_AUTO_TEST_CASE(AppendComponent)
 {
   Name name;
@@ -239,6 +262,10 @@
   name.append(PartialName("/6=C/D"))
       .append(PartialName("/E"));
   BOOST_CHECK_EQUAL(name.wireEncode(), "070F 080141 080142 060143 080144 080145"_block);
+
+  name = "/A/B";
+  name.append(name);
+  BOOST_CHECK_EQUAL(name.wireEncode(), "070C 080141 080142 080141 080142"_block);
 }
 
 BOOST_AUTO_TEST_CASE(AppendNumber)
@@ -254,6 +281,26 @@
   }
 }
 
+BOOST_AUTO_TEST_CASE(AppendParametersSha256Digest)
+{
+  auto digest = make_shared<Buffer>(32);
+
+  Name name("/P");
+  name.appendParametersSha256Digest(digest);
+  BOOST_CHECK_EQUAL(name.wireEncode(),
+                    "0725 080150 02200000000000000000000000000000000000000000000000000000000000000000"_block);
+
+  name = "/P";
+  name.appendParametersSha256Digest(digest->data(), digest->size());
+  BOOST_CHECK_EQUAL(name.wireEncode(),
+                    "0725 080150 02200000000000000000000000000000000000000000000000000000000000000000"_block);
+
+  name = "/P";
+  name.appendParametersSha256DigestPlaceholder();
+  BOOST_CHECK_EQUAL(name.wireEncode(),
+                    "0725 080150 0220E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"_block);
+}
+
 BOOST_AUTO_TEST_CASE(Markers)
 {
   // TestNameComponent/NamingConvention provides additional coverage for these methods,
@@ -282,6 +329,32 @@
   BOOST_CHECK_EQUAL(number, 11676);
 }
 
+BOOST_AUTO_TEST_CASE(EraseComponent)
+{
+  Name name("/A/B/C");
+  BOOST_CHECK_EQUAL(name.wireEncode(), "0709 080141 080142 080143"_block);
+  BOOST_CHECK_EQUAL(name.hasWire(), true);
+
+  name.erase(1);
+  BOOST_CHECK_EQUAL(name.size(), 2);
+  BOOST_CHECK_EQUAL(name.hasWire(), false);
+  BOOST_CHECK_EQUAL(name.wireEncode(), "0706 080141 080143"_block);
+
+  name.erase(-1);
+  BOOST_CHECK_EQUAL(name.size(), 1);
+  BOOST_CHECK_EQUAL(name.hasWire(), false);
+  BOOST_CHECK_EQUAL(name.wireEncode(), "0703 080141"_block);
+}
+
+BOOST_AUTO_TEST_CASE(Clear)
+{
+  Name name("/A/B/C");
+  BOOST_CHECK_EQUAL(name.empty(), false);
+  name.clear();
+  BOOST_CHECK_EQUAL(name.empty(), true);
+  BOOST_CHECK(name.begin() == name.end());
+}
+
 // ---- algorithms ----
 
 BOOST_AUTO_TEST_CASE(GetSuccessor)