name: add isKeyword and appendKeyword convenience methods

Also, deprecate Component::fromImplicitSha256Digest() and
Component::fromParametersSha256Digest() that do not provide
any advantages over the Component constructors.

Change-Id: I4890bb1ecab3dfcd96854f5f3aa4927a2328e5cf
diff --git a/ndn-cxx/impl/name-component-types.hpp b/ndn-cxx/impl/name-component-types.hpp
index d6aab5c..d14718e 100644
--- a/ndn-cxx/impl/name-component-types.hpp
+++ b/ndn-cxx/impl/name-component-types.hpp
@@ -31,7 +31,7 @@
 
 namespace ndn {
 namespace name {
-namespace detail {
+namespace {
 
 /** \brief Declare rules for a NameComponent type.
  */
@@ -181,12 +181,6 @@
   {
   }
 
-  bool
-  match(const Component& comp) const
-  {
-    return comp.type() == m_type && comp.value_size() == util::Sha256::DIGEST_SIZE;
-  }
-
   void
   check(const Component& comp) const final
   {
@@ -196,18 +190,6 @@
     }
   }
 
-  Component
-  create(ConstBufferPtr value) const
-  {
-    return Block(m_type, std::move(value));
-  }
-
-  Component
-  create(span<const uint8_t> value) const
-  {
-    return makeBinaryBlock(m_type, value);
-  }
-
   std::tuple<bool, Component>
   getSuccessor(const Component& comp) const final
   {
@@ -259,22 +241,6 @@
   const std::string m_uriPrefix;
 };
 
-inline const Sha256ComponentType&
-getComponentType1()
-{
-  static const Sha256ComponentType ct1(tlv::ImplicitSha256DigestComponent,
-                                       "ImplicitSha256DigestComponent", "sha256digest");
-  return ct1;
-}
-
-inline const Sha256ComponentType&
-getComponentType2()
-{
-  static const Sha256ComponentType ct2(tlv::ParametersSha256DigestComponent,
-                                       "ParametersSha256DigestComponent", "params-sha256");
-  return ct2;
-}
-
 /** \brief Rules for a component type holding a NonNegativeInteger value, written as
  *         a decimal number in URI representation.
  */
@@ -335,14 +301,16 @@
   const std::string m_uriPrefix;
 };
 
-/** \brief Rules regarding NameComponent types.
+/**
+ * \brief Encapsulates the rules for different NameComponent types.
  */
 class ComponentTypeTable : noncopyable
 {
 public:
   ComponentTypeTable();
 
-  /** \brief Retrieve ComponentType by TLV-TYPE.
+  /**
+   * \brief Retrieve a ComponentType by its TLV-TYPE.
    */
   const ComponentType&
   get(uint32_t type) const
@@ -353,7 +321,8 @@
     return *m_table[type];
   }
 
-  /** \brief Retrieve ComponentType by alternate URI prefix.
+  /**
+   * \brief Retrieve a ComponentType by its alternate URI prefix.
    */
   const ComponentType*
   findByUriPrefix(const std::string& prefix) const
@@ -386,12 +355,19 @@
 {
   m_table.fill(nullptr);
 
-  set(tlv::ImplicitSha256DigestComponent, getComponentType1());
-  set(tlv::ParametersSha256DigestComponent, getComponentType2());
+  static const Sha256ComponentType ct1(tlv::ImplicitSha256DigestComponent,
+                                       "ImplicitSha256DigestComponent", "sha256digest");
+  set(tlv::ImplicitSha256DigestComponent, ct1);
+  static const Sha256ComponentType ct2(tlv::ParametersSha256DigestComponent,
+                                       "ParametersSha256DigestComponent", "params-sha256");
+  set(tlv::ParametersSha256DigestComponent, ct2);
 
   static const GenericNameComponentType ct8;
   set(tlv::GenericNameComponent, ct8);
 
+  static const ComponentType ct32;
+  set(tlv::KeywordNameComponent, ct32);
+
   static const DecimalComponentType ct50(tlv::SegmentNameComponent, "SegmentNameComponent", "seg");
   set(tlv::SegmentNameComponent, ct50);
   static const DecimalComponentType ct52(tlv::ByteOffsetNameComponent, "ByteOffsetNameComponent", "off");
@@ -404,7 +380,8 @@
   set(tlv::SequenceNumNameComponent, ct58);
 }
 
-/** \brief Get the global ComponentTypeTable.
+/**
+ * \brief Get the global ComponentTypeTable.
  */
 inline const ComponentTypeTable&
 getComponentTypeTable()
@@ -413,7 +390,7 @@
   return ctt;
 }
 
-} // namespace detail
+} // namespace
 } // namespace name
 } // namespace ndn
 
diff --git a/ndn-cxx/interest.cpp b/ndn-cxx/interest.cpp
index c69d3c0..c72d04a 100644
--- a/ndn-cxx/interest.cpp
+++ b/ndn-cxx/interest.cpp
@@ -713,7 +713,7 @@
   BOOST_ASSERT(hasApplicationParameters());
 
   ssize_t digestIndex = findParametersDigestComponent(getName());
-  auto digestComponent = name::Component::fromParametersSha256Digest(computeParametersDigest());
+  name::Component digestComponent(tlv::ParametersSha256DigestComponent, computeParametersDigest());
 
   if (digestIndex == -1) {
     // no existing digest components, append one
diff --git a/ndn-cxx/name-component.cpp b/ndn-cxx/name-component.cpp
index e6eb609..406785c 100644
--- a/ndn-cxx/name-component.cpp
+++ b/ndn-cxx/name-component.cpp
@@ -122,7 +122,7 @@
   if (type() < tlv::NameComponentMin || type() > tlv::NameComponentMax) {
     NDN_THROW(Error("TLV-TYPE " + to_string(type()) + " is not a valid NameComponent"));
   }
-  detail::getComponentTypeTable().get(type()).check(*this);
+  getComponentTypeTable().get(type()).check(*this);
 }
 
 Component::Component(uint32_t type)
@@ -191,7 +191,7 @@
                                 input.data() + valuePos, input.size() - valuePos);
   }
 
-  auto ct = detail::getComponentTypeTable().findByUriPrefix(typePrefix);
+  auto ct = getComponentTypeTable().findByUriPrefix(typePrefix);
   if (ct == nullptr) {
     NDN_THROW(Error("Unknown TLV-TYPE '" + typePrefix + "' in NameComponent URI"));
   }
@@ -202,10 +202,10 @@
 Component::toUri(std::ostream& os, UriFormat format) const
 {
   if (wantAltUri(format)) {
-    detail::getComponentTypeTable().get(type()).writeUri(os, *this);
+    getComponentTypeTable().get(type()).writeUri(os, *this);
   }
   else {
-    detail::ComponentType().writeUri(os, *this);
+    ComponentType().writeUri(os, *this);
   }
 }
 
@@ -234,13 +234,6 @@
 }
 
 bool
-Component::isVersion() const
-{
-  return (canDecodeMarkerConvention() && type() == tlv::GenericNameComponent && isNumberWithMarker(VERSION_MARKER)) ||
-         (canDecodeTypedConvention() && type() == tlv::VersionNameComponent && isNumber());
-}
-
-bool
 Component::isSegment() const
 {
   return (canDecodeMarkerConvention() && type() == tlv::GenericNameComponent && isNumberWithMarker(SEGMENT_MARKER)) ||
@@ -255,6 +248,13 @@
 }
 
 bool
+Component::isVersion() const
+{
+  return (canDecodeMarkerConvention() && type() == tlv::GenericNameComponent && isNumberWithMarker(VERSION_MARKER)) ||
+         (canDecodeTypedConvention() && type() == tlv::VersionNameComponent && isNumber());
+}
+
+bool
 Component::isTimestamp() const
 {
   return (canDecodeMarkerConvention() && type() == tlv::GenericNameComponent && isNumberWithMarker(TIMESTAMP_MARKER)) ||
@@ -291,18 +291,6 @@
 }
 
 uint64_t
-Component::toVersion() const
-{
-  if (canDecodeMarkerConvention() && type() == tlv::GenericNameComponent) {
-    return toNumberWithMarker(VERSION_MARKER);
-  }
-  if (canDecodeTypedConvention() && type() == tlv::VersionNameComponent) {
-    return toNumber();
-  }
-  NDN_THROW(Error("Not a Version component"));
-}
-
-uint64_t
 Component::toSegment() const
 {
   if (canDecodeMarkerConvention() && type() == tlv::GenericNameComponent) {
@@ -326,6 +314,18 @@
   NDN_THROW(Error("Not a ByteOffset component"));
 }
 
+uint64_t
+Component::toVersion() const
+{
+  if (canDecodeMarkerConvention() && type() == tlv::GenericNameComponent) {
+    return toNumberWithMarker(VERSION_MARKER);
+  }
+  if (canDecodeTypedConvention() && type() == tlv::VersionNameComponent) {
+    return toNumber();
+  }
+  NDN_THROW(Error("Not a Version component"));
+}
+
 time::system_clock::time_point
 Component::toTimestamp() const
 {
@@ -382,14 +382,6 @@
 }
 
 Component
-Component::fromVersion(uint64_t version)
-{
-  return g_conventionEncoding == Convention::MARKER ?
-         fromNumberWithMarker(VERSION_MARKER, version) :
-         fromNumber(version, tlv::VersionNameComponent);
-}
-
-Component
 Component::fromSegment(uint64_t segmentNo)
 {
   return g_conventionEncoding == Convention::MARKER ?
@@ -406,6 +398,14 @@
 }
 
 Component
+Component::fromVersion(uint64_t version)
+{
+  return g_conventionEncoding == Convention::MARKER ?
+         fromNumberWithMarker(VERSION_MARKER, version) :
+         fromNumber(version, tlv::VersionNameComponent);
+}
+
+Component
 Component::fromTimestamp(const time::system_clock::time_point& timePoint)
 {
   uint64_t value = time::duration_cast<time::microseconds>(timePoint - time::getUnixEpoch()).count();
@@ -425,45 +425,39 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 bool
-Component::isGeneric() const
-{
-  return type() == tlv::GenericNameComponent;
-}
-
-bool
 Component::isImplicitSha256Digest() const
 {
-  return detail::getComponentType1().match(*this);
+  return type() == tlv::ImplicitSha256DigestComponent && value_size() == util::Sha256::DIGEST_SIZE;
 }
 
 Component
 Component::fromImplicitSha256Digest(ConstBufferPtr digest)
 {
-  return detail::getComponentType1().create(std::move(digest));
+  return {tlv::ImplicitSha256DigestComponent, std::move(digest)};
 }
 
 Component
 Component::fromImplicitSha256Digest(span<const uint8_t> digest)
 {
-  return detail::getComponentType1().create(digest);
+  return {tlv::ImplicitSha256DigestComponent, digest};
 }
 
 bool
 Component::isParametersSha256Digest() const
 {
-  return detail::getComponentType2().match(*this);
+  return type() == tlv::ParametersSha256DigestComponent && value_size() == util::Sha256::DIGEST_SIZE;
 }
 
 Component
 Component::fromParametersSha256Digest(ConstBufferPtr digest)
 {
-  return detail::getComponentType2().create(std::move(digest));
+  return {tlv::ParametersSha256DigestComponent, std::move(digest)};
 }
 
 Component
 Component::fromParametersSha256Digest(span<const uint8_t> digest)
 {
-  return detail::getComponentType2().create(digest);
+  return {tlv::ParametersSha256DigestComponent, digest};
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -506,13 +500,13 @@
 {
   bool isOverflow = false;
   Component successor;
-  std::tie(isOverflow, successor) = detail::getComponentTypeTable().get(type()).getSuccessor(*this);
+  std::tie(isOverflow, successor) = getComponentTypeTable().get(type()).getSuccessor(*this);
   if (!isOverflow) {
     return successor;
   }
 
   uint32_t type = this->type() + 1;
-  auto value = detail::getComponentTypeTable().get(type).getMinValue();
+  auto value = getComponentTypeTable().get(type).getMinValue();
   return {type, value};
 }
 
diff --git a/ndn-cxx/name-component.hpp b/ndn-cxx/name-component.hpp
index 47ad17d..acd87db 100644
--- a/ndn-cxx/name-component.hpp
+++ b/ndn-cxx/name-component.hpp
@@ -306,13 +306,6 @@
   isNumberWithMarker(uint8_t marker) const;
 
   /**
-   * @brief Check if the component is a version per NDN naming conventions
-   * @sa https://named-data.net/publications/techreports/ndn-tr-22-3-ndn-memo-naming-conventions/
-   */
-  bool
-  isVersion() const;
-
-  /**
    * @brief Check if the component is a segment number per NDN naming conventions
    * @sa https://named-data.net/publications/techreports/ndn-tr-22-3-ndn-memo-naming-conventions/
    */
@@ -327,6 +320,13 @@
   isByteOffset() const;
 
   /**
+   * @brief Check if the component is a version per NDN naming conventions
+   * @sa https://named-data.net/publications/techreports/ndn-tr-22-3-ndn-memo-naming-conventions/
+   */
+  bool
+  isVersion() const;
+
+  /**
    * @brief Check if the component is a timestamp per NDN naming conventions
    * @sa https://named-data.net/publications/techreports/ndn-tr-22-3-ndn-memo-naming-conventions/
    */
@@ -341,44 +341,30 @@
   isSequenceNumber() const;
 
   /**
-   * @brief Interpret this name component as NonNegativeInteger
-   *
+   * @brief Interpret this name component as a NonNegativeInteger
    * @sa https://named-data.net/doc/NDN-packet-spec/current/tlv.html#non-negative-integer-encoding
-   *
-   * @return The integer number.
+   * @return The decoded non-negative integer.
    */
   uint64_t
   toNumber() const;
 
   /**
-   * @brief Interpret this name component as NameComponentWithMarker
+   * @brief Interpret this name component as a NameComponentWithMarker
    *
    * @sa NDN Naming Conventions revision 1 (obsolete)
    *     https://named-data.net/wp-content/uploads/2014/08/ndn-tr-22-ndn-memo-naming-conventions.pdf
    *
    * @param marker 1-byte octet of the marker
    * @return The integer number.
-   * @throws Error if name component does not have the specified marker.
-   *         tlv::Error if format does not follow NameComponentWithMarker specification.
+   * @throw Error if name component does not have the specified marker.
+   * @throw tlv::Error if format does not follow NameComponentWithMarker specification.
    */
   uint64_t
   toNumberWithMarker(uint8_t marker) const;
 
   /**
-   * @brief Interpret as version component using NDN naming conventions
-   *
-   * @sa https://named-data.net/publications/techreports/ndn-tr-22-3-ndn-memo-naming-conventions/
-   *
-   * @throw tlv::Error not a Version component interpreted by the chosen convention(s).
-   */
-  uint64_t
-  toVersion() const;
-
-  /**
    * @brief Interpret as segment number component using NDN naming conventions
-   *
    * @sa https://named-data.net/publications/techreports/ndn-tr-22-3-ndn-memo-naming-conventions/
-   *
    * @throw tlv::Error not a Segment component interpreted by the chosen convention(s).
    */
   uint64_t
@@ -386,19 +372,23 @@
 
   /**
    * @brief Interpret as byte offset component using NDN naming conventions
-   *
    * @sa https://named-data.net/publications/techreports/ndn-tr-22-3-ndn-memo-naming-conventions/
-   *
    * @throw tlv::Error not a ByteOffset component interpreted by the chosen convention(s).
    */
   uint64_t
   toByteOffset() const;
 
   /**
-   * @brief Interpret as timestamp component using NDN naming conventions
-   *
+   * @brief Interpret as version component using NDN naming conventions
    * @sa https://named-data.net/publications/techreports/ndn-tr-22-3-ndn-memo-naming-conventions/
-   *
+   * @throw tlv::Error not a Version component interpreted by the chosen convention(s).
+   */
+  uint64_t
+  toVersion() const;
+
+  /**
+   * @brief Interpret as timestamp component using NDN naming conventions
+   * @sa https://named-data.net/publications/techreports/ndn-tr-22-3-ndn-memo-naming-conventions/
    * @throw tlv::Error not a Timestamp component interpreted by the chosen convention(s).
    */
   time::system_clock::time_point
@@ -406,9 +396,7 @@
 
   /**
    * @brief Interpret as sequence number component using NDN naming conventions
-   *
    * @sa https://named-data.net/publications/techreports/ndn-tr-22-3-ndn-memo-naming-conventions/
-   *
    * @throw tlv::Error not a SequenceNumber component interpreted by the chosen convention(s).
    */
   uint64_t
@@ -446,16 +434,7 @@
   fromNumberWithMarker(uint8_t marker, uint64_t number);
 
   /**
-   * @brief Create a version component using NDN naming conventions
-   *
-   * @sa https://named-data.net/publications/techreports/ndn-tr-22-3-ndn-memo-naming-conventions/
-   */
-  static Component
-  fromVersion(uint64_t version);
-
-  /**
    * @brief Create a segment number component using NDN naming conventions
-   *
    * @sa https://named-data.net/publications/techreports/ndn-tr-22-3-ndn-memo-naming-conventions/
    */
   static Component
@@ -463,15 +442,20 @@
 
   /**
    * @brief Create a byte offset component using NDN naming conventions
-   *
    * @sa https://named-data.net/publications/techreports/ndn-tr-22-3-ndn-memo-naming-conventions/
    */
   static Component
   fromByteOffset(uint64_t offset);
 
   /**
+   * @brief Create a version component using NDN naming conventions
+   * @sa https://named-data.net/publications/techreports/ndn-tr-22-3-ndn-memo-naming-conventions/
+   */
+  static Component
+  fromVersion(uint64_t version);
+
+  /**
    * @brief Create a timestamp component using NDN naming conventions
-   *
    * @sa https://named-data.net/publications/techreports/ndn-tr-22-3-ndn-memo-naming-conventions/
    */
   static Component
@@ -479,7 +463,6 @@
 
   /**
    * @brief Create a sequence number component using NDN naming conventions
-   *
    * @sa https://named-data.net/publications/techreports/ndn-tr-22-3-ndn-memo-naming-conventions/
    */
   static Component
@@ -487,47 +470,73 @@
 
 public: // commonly used TLV-TYPEs
   /**
-   * @brief Check if the component is GenericNameComponent
+   * @brief Check if the component is a GenericNameComponent
+   * @sa https://redmine.named-data.net/projects/ndn-tlv/wiki/NameComponentType
    */
   bool
-  isGeneric() const;
+  isGeneric() const noexcept
+  {
+    return type() == tlv::GenericNameComponent;
+  }
 
   /**
-   * @brief Check if the component is ImplicitSha256DigestComponent
+   * @brief Check if the component is an ImplicitSha256DigestComponent
+   * @sa https://redmine.named-data.net/projects/ndn-tlv/wiki/NameComponentType
+   * @sa https://named-data.net/doc/NDN-packet-spec/0.3/name.html#implicit-digest-component
    */
   bool
   isImplicitSha256Digest() const;
 
   /**
    * @brief Create ImplicitSha256DigestComponent component
+   * @deprecated Use Component(uint32_t, ConstBufferPtr)
    */
+  [[deprecated("use one of the name::Component constructors")]]
   static Component
   fromImplicitSha256Digest(ConstBufferPtr digest);
 
   /**
    * @brief Create ImplicitSha256DigestComponent component
+   * @deprecated Use Component(uint32_t, span<const uint8_t>)
    */
+  [[deprecated("use one of the name::Component constructors")]]
   static Component
   fromImplicitSha256Digest(span<const uint8_t> digest);
 
   /**
-   * @brief Check if the component is ParametersSha256DigestComponent
+   * @brief Check if the component is a ParametersSha256DigestComponent
+   * @sa https://redmine.named-data.net/projects/ndn-tlv/wiki/NameComponentType
+   * @sa https://named-data.net/doc/NDN-packet-spec/0.3/name.html#parameters-digest-component
    */
   bool
   isParametersSha256Digest() const;
 
   /**
    * @brief Create ParametersSha256DigestComponent component
+   * @deprecated Use Component(uint32_t, ConstBufferPtr)
    */
+  [[deprecated("use one of the name::Component constructors")]]
   static Component
   fromParametersSha256Digest(ConstBufferPtr digest);
 
   /**
    * @brief Create ParametersSha256DigestComponent component
+   * @deprecated Use Component(uint32_t, span<const uint8_t>)
    */
+  [[deprecated("use one of the name::Component constructors")]]
   static Component
   fromParametersSha256Digest(span<const uint8_t> digest);
 
+  /**
+   * @brief Check if the component is a KeywordNameComponent
+   * @sa https://redmine.named-data.net/projects/ndn-tlv/wiki/NameComponentType
+   */
+  bool
+  isKeyword() const noexcept
+  {
+    return type() == tlv::KeywordNameComponent;
+  }
+
 public: // comparison
   NDN_CXX_NODISCARD bool
   empty() const
diff --git a/ndn-cxx/name.cpp b/ndn-cxx/name.cpp
index b8c808b..567bd46 100644
--- a/ndn-cxx/name.cpp
+++ b/ndn-cxx/name.cpp
@@ -241,13 +241,14 @@
 Name&
 Name::append(const PartialName& name)
 {
-  if (&name == this)
+  if (&name == this) {
     // Copying from this name, so need to make a copy first.
     return append(PartialName(name));
+  }
 
-  for (size_t i = 0; i < name.size(); ++i)
-    append(name.at(i));
-
+  for (const auto& c : name) {
+    append(c);
+  }
   return *this;
 }
 
@@ -268,11 +269,12 @@
 void
 Name::erase(ssize_t i)
 {
-  if (i < 0) {
-    i += static_cast<ssize_t>(size());
+  if (i >= 0) {
+    m_wire.erase(std::next(m_wire.elements_begin(), i));
   }
-
-  m_wire.erase(m_wire.elements_begin() + i);
+  else {
+    m_wire.erase(std::prev(m_wire.elements_end(), -i));
+  }
 }
 
 void
diff --git a/ndn-cxx/name.hpp b/ndn-cxx/name.hpp
index 3265f7d..39513e7 100644
--- a/ndn-cxx/name.hpp
+++ b/ndn-cxx/name.hpp
@@ -419,17 +419,6 @@
   }
 
   /**
-   * @brief Append a version component
-   * @param version the version number to append; if nullopt, the current UNIX time
-   *                in milliseconds is used
-   * @return a reference to this name, to allow chaining
-   * @sa NDN Naming Conventions
-   *     https://named-data.net/publications/techreports/ndn-tr-22-3-ndn-memo-naming-conventions/
-   */
-  Name&
-  appendVersion(const optional<uint64_t>& version = nullopt);
-
-  /**
    * @brief Append a segment number (sequential) component
    * @return a reference to this name, to allow chaining
    * @sa NDN Naming Conventions
@@ -454,6 +443,17 @@
   }
 
   /**
+   * @brief Append a version component
+   * @param version the version number to append; if nullopt, the current UNIX time
+   *                in milliseconds is used
+   * @return a reference to this name, to allow chaining
+   * @sa NDN Naming Conventions
+   *     https://named-data.net/publications/techreports/ndn-tr-22-3-ndn-memo-naming-conventions/
+   */
+  Name&
+  appendVersion(const optional<uint64_t>& version = nullopt);
+
+  /**
    * @brief Append a timestamp component
    * @param timestamp the timestamp to append; if nullopt, the current system time is used
    * @return a reference to this name, to allow chaining
@@ -482,7 +482,7 @@
   Name&
   appendImplicitSha256Digest(ConstBufferPtr digest)
   {
-    return append(Component::fromImplicitSha256Digest(std::move(digest)));
+    return append(Component(tlv::ImplicitSha256DigestComponent, std::move(digest)));
   }
 
   /**
@@ -492,7 +492,7 @@
   Name&
   appendImplicitSha256Digest(span<const uint8_t> digestBytes)
   {
-    return append(Component::fromImplicitSha256Digest(digestBytes));
+    return append(Component(tlv::ImplicitSha256DigestComponent, digestBytes));
   }
 
   /**
@@ -502,7 +502,7 @@
   Name&
   appendParametersSha256Digest(ConstBufferPtr digest)
   {
-    return append(Component::fromParametersSha256Digest(std::move(digest)));
+    return append(Component(tlv::ParametersSha256DigestComponent, std::move(digest)));
   }
 
   /**
@@ -512,7 +512,7 @@
   Name&
   appendParametersSha256Digest(span<const uint8_t> digestBytes)
   {
-    return append(Component::fromParametersSha256Digest(digestBytes));
+    return append(Component(tlv::ParametersSha256DigestComponent, digestBytes));
   }
 
   /**
@@ -522,6 +522,27 @@
   Name&
   appendParametersSha256DigestPlaceholder();
 
+  /**
+   * @brief Append a keyword component.
+   * @return a reference to this name, to allow chaining
+   */
+  Name&
+  appendKeyword(span<const uint8_t> keyword)
+  {
+    return append(Component(tlv::KeywordNameComponent, keyword));
+  }
+
+  /**
+   * @brief Append a keyword component.
+   * @return a reference to this name, to allow chaining
+   */
+  Name&
+  appendKeyword(const char* keyword)
+  {
+    return append(Component(tlv::KeywordNameComponent, {reinterpret_cast<const uint8_t*>(keyword),
+                                                        std::char_traits<char>::length(keyword)}));
+  }
+
   /** @brief Append a component
    *  @note This makes push_back an alias of append, giving Name a similar API as STL vector.
    */
diff --git a/tests/unit/name-component.t.cpp b/tests/unit/name-component.t.cpp
index 4dc0370..2743514 100644
--- a/tests/unit/name-component.t.cpp
+++ b/tests/unit/name-component.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2013-2021 Regents of the University of California.
+ * Copyright (c) 2013-2022 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -91,6 +91,7 @@
   BOOST_CHECK_THROW(Component::fromEscapedString(""), Component::Error);
   BOOST_CHECK_THROW(Component::fromEscapedString("."), Component::Error);
   BOOST_CHECK_THROW(Component::fromEscapedString(".."), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString("8="), Component::Error);
 }
 
 static void
@@ -104,7 +105,7 @@
   }
   const std::string hexPctCanonical = "%28%BA%D4%B5%27%5B%D3%92%DB%B6p%C7%5C%F0%B6o%13%F7%94%2B%21%E8%0FU%C0%E8k7GS%A5H";
 
-  Component comp(Block(type, fromHex(hexLower)));
+  Component comp(type, fromHex(hexLower));
 
   BOOST_CHECK_EQUAL(comp.type(), type);
   BOOST_CHECK_EQUAL(comp.toUri(), uriPrefix + hexLower);
@@ -152,7 +153,7 @@
   BOOST_CHECK_EQUAL(comp, Component::fromEscapedString(to_string(type) + "=%2A"));
   BOOST_CHECK_EQUAL(comp, Component::fromNumber(42, type));
 
-  const Component comp2(Block(type, fromHex("010203"))); // TLV-VALUE is *not* a NonNegativeInteger
+  const Component comp2(type, fromHex("010203")); // TLV-VALUE is *not* a NonNegativeInteger
   BOOST_CHECK_EQUAL(comp2.type(), type);
   BOOST_CHECK_EQUAL(comp2.isNumber(), false);
   const auto comp2Uri = to_string(type) + "=%01%02%03";
@@ -196,6 +197,29 @@
   testDecimalComponent(tlv::SequenceNumNameComponent, "seq=");
 }
 
+BOOST_AUTO_TEST_CASE(Keyword)
+{
+  Component comp("2007 6E646E2D637878"_block);
+  BOOST_CHECK_EQUAL(comp.type(), tlv::KeywordNameComponent);
+  BOOST_CHECK_EQUAL(comp.isKeyword(), true);
+  BOOST_CHECK_EQUAL(comp.toUri(), "32=ndn-cxx");
+  BOOST_CHECK_EQUAL(comp.toUri(UriFormat::CANONICAL), "32=ndn-cxx");
+  BOOST_CHECK_EQUAL(comp.toUri(UriFormat::ALTERNATE), "32=ndn-cxx");
+  BOOST_CHECK_EQUAL(comp.toUri(UriFormat::ENV_OR_CANONICAL), "32=ndn-cxx");
+  BOOST_CHECK_EQUAL(comp.toUri(UriFormat::ENV_OR_ALTERNATE), "32=ndn-cxx");
+  BOOST_CHECK_EQUAL(Component::fromEscapedString("32=ndn-cxx"), comp);
+
+  comp.wireDecode("2000"_block);
+  BOOST_CHECK_EQUAL(comp.type(), tlv::KeywordNameComponent);
+  BOOST_CHECK_EQUAL(comp.isKeyword(), true);
+  BOOST_CHECK_EQUAL(comp.toUri(), "32=...");
+  BOOST_CHECK_EQUAL(Component::fromEscapedString("32=..."), comp);
+
+  BOOST_CHECK_THROW(Component::fromEscapedString("32="), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString("32=."), Component::Error);
+  BOOST_CHECK_THROW(Component::fromEscapedString("32=.."), Component::Error);
+}
+
 BOOST_AUTO_TEST_CASE(OtherType)
 {
   Component comp("0907 6E646E2D637878"_block);
diff --git a/tests/unit/name.t.cpp b/tests/unit/name.t.cpp
index 165c3fc..9195e15 100644
--- a/tests/unit/name.t.cpp
+++ b/tests/unit/name.t.cpp
@@ -312,29 +312,42 @@
                     "0725 080150 0220E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"_block);
 }
 
-BOOST_AUTO_TEST_CASE(Markers)
+BOOST_AUTO_TEST_CASE(AppendTypedComponent)
 {
   // TestNameComponent/NamingConvention provides additional coverage for these methods,
-  // including verifications of the wire format.
+  // including verification of the wire format.
 
   Name name;
   uint64_t number;
 
-  BOOST_REQUIRE_NO_THROW(number = name.appendSegment(30923).at(-1).toSegment());
-  BOOST_CHECK_EQUAL(number, 30923);
+  BOOST_CHECK_NO_THROW(number = name.appendSegment(30923).at(-1).toSegment());
+  BOOST_TEST(number == 30923);
 
-  BOOST_REQUIRE_NO_THROW(number = name.appendVersion().at(-1).toVersion());
+  BOOST_CHECK_NO_THROW(number = name.appendByteOffset(41880).at(-1).toByteOffset());
+  BOOST_TEST(number == 41880);
 
-  BOOST_REQUIRE_NO_THROW(number = name.appendVersion(25912).at(-1).toVersion());
-  BOOST_CHECK_EQUAL(number, 25912);
+  auto before = time::toUnixTimestamp(time::system_clock::now());
+  BOOST_CHECK_NO_THROW(number = name.appendVersion().at(-1).toVersion());
+  auto after = time::toUnixTimestamp(time::system_clock::now());
+  BOOST_TEST(number >= before.count());
+  BOOST_TEST(number <= after.count());
+
+  BOOST_CHECK_NO_THROW(number = name.appendVersion(25912).at(-1).toVersion());
+  BOOST_TEST(number == 25912);
 
   const auto tp = time::system_clock::now();
   time::system_clock::TimePoint tp2;
-  BOOST_REQUIRE_NO_THROW(tp2 = name.appendTimestamp(tp).at(-1).toTimestamp());
-  BOOST_CHECK_LE(time::abs(tp2 - tp), 1_us);
+  BOOST_CHECK_NO_THROW(tp2 = name.appendTimestamp(tp).at(-1).toTimestamp());
+  BOOST_TEST(time::abs(tp2 - tp) <= 1_us);
 
-  BOOST_REQUIRE_NO_THROW(number = name.appendSequenceNumber(11676).at(-1).toSequenceNumber());
-  BOOST_CHECK_EQUAL(number, 11676);
+  BOOST_CHECK_NO_THROW(number = name.appendSequenceNumber(11676).at(-1).toSequenceNumber());
+  BOOST_TEST(number == 11676);
+
+  name.appendKeyword({0xab, 0xcd, 0xef});
+  BOOST_TEST(name.at(-1) == Component::fromEscapedString("32=%AB%CD%EF"));
+
+  name.appendKeyword("test-keyword");
+  BOOST_TEST(name.at(-1) == Component::fromEscapedString("32=test-keyword"));
 }
 
 BOOST_AUTO_TEST_CASE(EraseComponent)
@@ -343,15 +356,20 @@
   BOOST_CHECK_EQUAL(name.wireEncode(), "0709 080141 080142 080143"_block);
   BOOST_CHECK_EQUAL(name.hasWire(), true);
 
-  name.erase(1);
+  name.erase(-2);
   BOOST_CHECK_EQUAL(name.size(), 2);
   BOOST_CHECK_EQUAL(name.hasWire(), false);
   BOOST_CHECK_EQUAL(name.wireEncode(), "0706 080141 080143"_block);
 
-  name.erase(-1);
+  name.erase(1);
   BOOST_CHECK_EQUAL(name.size(), 1);
   BOOST_CHECK_EQUAL(name.hasWire(), false);
   BOOST_CHECK_EQUAL(name.wireEncode(), "0703 080141"_block);
+
+  name.erase(0);
+  BOOST_CHECK_EQUAL(name.size(), 0);
+  BOOST_CHECK_EQUAL(name.hasWire(), false);
+  BOOST_CHECK_EQUAL(name.wireEncode(), "0700"_block);
 }
 
 BOOST_AUTO_TEST_CASE(Clear)