lp: IncomingFaceId, NextHopFaceId, CachePolicy tags

LocalControlHeader is deprecated.

This commit also adjusts includes in some lp/ headers.

refs #3296

Change-Id: Icdc7b469d70739fe5c65da51817c92ff9136c923
diff --git a/src/common.hpp b/src/common.hpp
index 8212a7e..a51f58d 100644
--- a/src/common.hpp
+++ b/src/common.hpp
@@ -63,6 +63,7 @@
 #  error "ndn-cxx applications must be compiled using the C++11 standard"
 #endif
 
+#include <algorithm>
 #include <cstddef>
 #include <cstdint>
 #include <cstring>
diff --git a/src/data.cpp b/src/data.cpp
index f96fb8f..724335e 100644
--- a/src/data.cpp
+++ b/src/data.cpp
@@ -289,26 +289,6 @@
   return *this;
 }
 
-//
-
-Data&
-Data::setIncomingFaceId(uint64_t incomingFaceId)
-{
-  getLocalControlHeader().setIncomingFaceId(incomingFaceId);
-  // ! do not reset Data's wire !
-
-  return *this;
-}
-
-Data&
-Data::setCachingPolicy(nfd::LocalControlHeader::CachingPolicy cachingPolicy)
-{
-  getLocalControlHeader().setCachingPolicy(cachingPolicy);
-  // ! do not reset Data's wire !
-
-  return *this;
-}
-
 void
 Data::onChanged()
 {
@@ -349,4 +329,53 @@
   return os;
 }
 
+#ifdef NDN_LP_KEEP_LOCAL_CONTROL_HEADER
+
+// Permit deprecated usage for gcc only.
+// clang allows deprecated usage in deprecated functions, so it doesn't need this directive.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
+lp::LocalControlHeaderFacade
+Data::getLocalControlHeader()
+{
+  return lp::LocalControlHeaderFacade(*this);
+}
+
+const lp::LocalControlHeaderFacade
+Data::getLocalControlHeader() const
+{
+  return lp::LocalControlHeaderFacade(const_cast<Data&>(*this));
+}
+
+uint64_t
+Data::getIncomingFaceId() const
+{
+  return getLocalControlHeader().getIncomingFaceId();
+}
+
+Data&
+Data::setIncomingFaceId(uint64_t incomingFaceId)
+{
+  getLocalControlHeader().setIncomingFaceId(incomingFaceId);
+  return *this;
+}
+
+lp::LocalControlHeaderFacade::CachingPolicy
+Data::getCachingPolicy() const
+{
+  return getLocalControlHeader().getCachingPolicy();
+}
+
+Data&
+Data::setCachingPolicy(lp::LocalControlHeaderFacade::CachingPolicy cachingPolicy)
+{
+  getLocalControlHeader().setCachingPolicy(cachingPolicy);
+  return *this;
+}
+
+#pragma GCC diagnostic pop
+
+#endif // NDN_LP_KEEP_LOCAL_CONTROL_HEADER
+
 } // namespace ndn
diff --git a/src/data.hpp b/src/data.hpp
index b5e1c02..17c04a5 100644
--- a/src/data.hpp
+++ b/src/data.hpp
@@ -29,7 +29,7 @@
 #include "signature.hpp"
 #include "meta-info.hpp"
 #include "key-locator.hpp"
-#include "management/nfd-local-control-header.hpp"
+#include "lp/tags.hpp"
 #include "tag-host.hpp"
 
 namespace ndn {
@@ -285,23 +285,43 @@
 
   ///////////////////////////////////////////////////////////////
 
-  nfd::LocalControlHeader&
-  getLocalControlHeader();
+#ifdef NDN_LP_KEEP_LOCAL_CONTROL_HEADER
+  /** @deprecated use getTag and setTag with lp::IncomingFaceIdTag, lp::CachePolicyTag
+   */
+  DEPRECATED(
+  lp::LocalControlHeaderFacade
+  getLocalControlHeader());
 
-  const nfd::LocalControlHeader&
-  getLocalControlHeader() const;
+  /** @deprecated use getTag with lp::IncomingFaceIdTag, lp::CachePolicyTag
+   */
+  DEPRECATED(
+  const lp::LocalControlHeaderFacade
+  getLocalControlHeader() const);
 
+  /** @deprecated use getTag<lp::IncomingFaceIdTag>
+   */
+  DEPRECATED(
   uint64_t
-  getIncomingFaceId() const;
+  getIncomingFaceId() const);
 
+  /** @deprecated use setTag<lp::IncomingFaceIdTag>
+   */
+  DEPRECATED(
   Data&
-  setIncomingFaceId(uint64_t incomingFaceId);
+  setIncomingFaceId(uint64_t incomingFaceId));
 
-  nfd::LocalControlHeader::CachingPolicy
-  getCachingPolicy() const;
+  /** @deprecated use getTag<lp::CachePolicyTag>
+   */
+  DEPRECATED(
+  lp::LocalControlHeaderFacade::CachingPolicy
+  getCachingPolicy() const);
 
+  /** @deprecated use setTag<lp::CachePolicyTag>
+   */
+  DEPRECATED(
   Data&
-  setCachingPolicy(nfd::LocalControlHeader::CachingPolicy cachingPolicy);
+  setCachingPolicy(lp::LocalControlHeaderFacade::CachingPolicy cachingPolicy));
+#endif // NDN_LP_KEEP_LOCAL_CONTROL_HEADER
 
 public: // EqualityComparable concept
   bool
@@ -325,9 +345,6 @@
 
   mutable Block m_wire;
   mutable Name m_fullName;
-
-  nfd::LocalControlHeader m_localControlHeader;
-  friend class nfd::LocalControlHeader;
 };
 
 std::ostream&
@@ -375,30 +392,6 @@
   return m_signature;
 }
 
-inline nfd::LocalControlHeader&
-Data::getLocalControlHeader()
-{
-  return m_localControlHeader;
-}
-
-inline const nfd::LocalControlHeader&
-Data::getLocalControlHeader() const
-{
-  return m_localControlHeader;
-}
-
-inline uint64_t
-Data::getIncomingFaceId() const
-{
-  return getLocalControlHeader().getIncomingFaceId();
-}
-
-inline nfd::LocalControlHeader::CachingPolicy
-Data::getCachingPolicy() const
-{
-  return getLocalControlHeader().getCachingPolicy();
-}
-
 } // namespace ndn
 
 #endif
diff --git a/src/detail/face-impl.hpp b/src/detail/face-impl.hpp
index 808355f..ef49992 100644
--- a/src/detail/face-impl.hpp
+++ b/src/detail/face-impl.hpp
@@ -39,9 +39,9 @@
 
 #include "../management/nfd-controller.hpp"
 #include "../management/nfd-command-options.hpp"
-#include "../management/nfd-local-control-header.hpp"
 
 #include "../lp/packet.hpp"
+#include "../lp/tags.hpp"
 
 namespace ndn {
 
@@ -149,9 +149,9 @@
 
     lp::Packet packet;
 
-    nfd::LocalControlHeader localControlHeader = interest->getLocalControlHeader();
-    if (localControlHeader.hasNextHopFaceId()) {
-      packet.add<lp::NextHopFaceIdField>(localControlHeader.getNextHopFaceId());
+    shared_ptr<lp::NextHopFaceIdTag> nextHopFaceIdTag = interest->getTag<lp::NextHopFaceIdTag>();
+    if (nextHopFaceIdTag != nullptr) {
+      packet.add<lp::NextHopFaceIdField>(*nextHopFaceIdTag);
     }
 
     packet.add<lp::FragmentField>(std::make_pair(interest->wireEncode().begin(),
@@ -179,18 +179,9 @@
 
     lp::Packet packet;
 
-    nfd::LocalControlHeader localControlHeader = data->getLocalControlHeader();
-    if (localControlHeader.hasCachingPolicy()) {
-      switch (localControlHeader.getCachingPolicy()) {
-        case nfd::LocalControlHeader::CachingPolicy::NO_CACHE: {
-          lp::CachePolicy cachePolicy;
-          cachePolicy.setPolicy(lp::CachePolicyType::NO_CACHE);
-          packet.add<lp::CachePolicyField>(cachePolicy);
-          break;
-        }
-        default:
-          break;
-      }
+    shared_ptr<lp::CachePolicyTag> cachePolicyTag = data->getTag<lp::CachePolicyTag>();
+    if (cachePolicyTag != nullptr) {
+      packet.add<lp::CachePolicyField>(*cachePolicyTag);
     }
 
     packet.add<lp::FragmentField>(std::make_pair(data->wireEncode().begin(),
diff --git a/src/face.cpp b/src/face.cpp
index 9d6b0dd..0212fd7 100644
--- a/src/face.cpp
+++ b/src/face.cpp
@@ -473,8 +473,7 @@
 extractLpLocalFields(NETPKT& netPacket, const lp::Packet& lpPacket)
 {
   if (lpPacket.has<lp::IncomingFaceIdField>()) {
-    netPacket.getLocalControlHeader().
-      setIncomingFaceId(lpPacket.get<lp::IncomingFaceIdField>());
+    netPacket.setTag(make_shared<lp::IncomingFaceIdTag>(lpPacket.get<lp::IncomingFaceIdField>()));
   }
 }
 
diff --git a/src/face.hpp b/src/face.hpp
index e07f541..9a01d17 100644
--- a/src/face.hpp
+++ b/src/face.hpp
@@ -28,8 +28,9 @@
 #include "interest.hpp"
 #include "interest-filter.hpp"
 #include "data.hpp"
-#include "security/signing-info.hpp"
+#include "encoding/nfd-constants.hpp"
 #include "lp/nack.hpp"
+#include "security/signing-info.hpp"
 
 #define NDN_FACE_KEEP_DEPRECATED_REGISTRATION_SIGNING
 
diff --git a/src/interest.cpp b/src/interest.cpp
index 35be5ce..1ad619c 100644
--- a/src/interest.cpp
+++ b/src/interest.cpp
@@ -485,4 +485,53 @@
   return os;
 }
 
+#ifdef NDN_LP_KEEP_LOCAL_CONTROL_HEADER
+
+// Permit deprecated usage for gcc only.
+// clang allows deprecated usage in deprecated functions, so it doesn't need this directive.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
+lp::LocalControlHeaderFacade
+Interest::getLocalControlHeader()
+{
+  return lp::LocalControlHeaderFacade(*this);
+}
+
+const lp::LocalControlHeaderFacade
+Interest::getLocalControlHeader() const
+{
+  return lp::LocalControlHeaderFacade(const_cast<Interest&>(*this));
+}
+
+uint64_t
+Interest::getIncomingFaceId() const
+{
+  return getLocalControlHeader().getIncomingFaceId();
+}
+
+Interest&
+Interest::setIncomingFaceId(uint64_t incomingFaceId)
+{
+  getLocalControlHeader().setIncomingFaceId(incomingFaceId);
+  return *this;
+}
+
+uint64_t
+Interest::getNextHopFaceId() const
+{
+  return getLocalControlHeader().getNextHopFaceId();
+}
+
+Interest&
+Interest::setNextHopFaceId(uint64_t nextHopFaceId)
+{
+  getLocalControlHeader().setNextHopFaceId(nextHopFaceId);
+  return *this;
+}
+
+#pragma GCC diagnostic pop
+
+#endif // NDN_LP_KEEP_LOCAL_CONTROL_HEADER
+
 } // namespace ndn
diff --git a/src/interest.hpp b/src/interest.hpp
index b87cd50..c61d328 100644
--- a/src/interest.hpp
+++ b/src/interest.hpp
@@ -27,7 +27,7 @@
 #include "name.hpp"
 #include "selectors.hpp"
 #include "util/time.hpp"
-#include "management/nfd-local-control-header.hpp"
+#include "lp/tags.hpp"
 #include "tag-host.hpp"
 #include "link.hpp"
 
@@ -275,46 +275,44 @@
   void
   refreshNonce();
 
+#ifdef NDN_LP_KEEP_LOCAL_CONTROL_HEADER
 public: // local control header
-  nfd::LocalControlHeader&
-  getLocalControlHeader()
-  {
-    return m_localControlHeader;
-  }
+  /** @deprecated use getTag and setTag with lp::IncomingFaceIdTag, lp::NextHopFaceIdTag
+   */
+  DEPRECATED(
+  lp::LocalControlHeaderFacade
+  getLocalControlHeader());
 
-  const nfd::LocalControlHeader&
-  getLocalControlHeader() const
-  {
-    return m_localControlHeader;
-  }
+  /** @deprecated use getTag with lp::IncomingFaceIdTag, lp::NextHopFaceIdTag
+   */
+  DEPRECATED(
+  const lp::LocalControlHeaderFacade
+  getLocalControlHeader() const);
 
+  /** @deprecated use getTag<lp::IncomingFaceIdTag>
+   */
+  DEPRECATED(
   uint64_t
-  getIncomingFaceId() const
-  {
-    return getLocalControlHeader().getIncomingFaceId();
-  }
+  getIncomingFaceId() const);
 
+  /** @deprecated use setTag<lp::IncomingFaceIdTag>
+   */
+  DEPRECATED(
   Interest&
-  setIncomingFaceId(uint64_t incomingFaceId)
-  {
-    getLocalControlHeader().setIncomingFaceId(incomingFaceId);
-    // ! do not reset Interest's wire !
-    return *this;
-  }
+  setIncomingFaceId(uint64_t incomingFaceId));
 
+  /** @deprecated use getTag<lp::NextHopFaceIdTag>
+   */
+  DEPRECATED(
   uint64_t
-  getNextHopFaceId() const
-  {
-    return getLocalControlHeader().getNextHopFaceId();
-  }
+  getNextHopFaceId() const);
 
+  /** @deprecated use setTag<lp::NextHopFaceIdTag>
+   */
+  DEPRECATED(
   Interest&
-  setNextHopFaceId(uint64_t nextHopFaceId)
-  {
-    getLocalControlHeader().setNextHopFaceId(nextHopFaceId);
-    // ! do not reset Interest's wire !
-    return *this;
-  }
+  setNextHopFaceId(uint64_t nextHopFaceId));
+#endif // NDN_LP_KEEP_LOCAL_CONTROL_HEADER
 
 public: // Selectors
   /**
@@ -447,9 +445,6 @@
   mutable shared_ptr<Link> m_linkCached;
   size_t m_selectedDelegationIndex;
   mutable Block m_wire;
-
-  nfd::LocalControlHeader m_localControlHeader;
-  friend class nfd::LocalControlHeader;
 };
 
 std::ostream&
diff --git a/src/lp/cache-policy.hpp b/src/lp/cache-policy.hpp
index a67a9c3..5c5c5d1 100644
--- a/src/lp/cache-policy.hpp
+++ b/src/lp/cache-policy.hpp
@@ -25,6 +25,7 @@
 #define NDN_CXX_LP_CACHE_POLICY_HPP
 
 #include "../common.hpp"
+#include "../tag.hpp"
 #include "../encoding/encoding-buffer.hpp"
 #include "../encoding/block-helpers.hpp"
 
@@ -109,4 +110,4 @@
 } // namespace lp
 } // namespace ndn
 
-#endif // NDN_CXX_LP_CACHE_POLICY_HPP
\ No newline at end of file
+#endif // NDN_CXX_LP_CACHE_POLICY_HPP
diff --git a/src/lp/detail/field-decl.hpp b/src/lp/detail/field-decl.hpp
index 02a3677..44df431 100644
--- a/src/lp/detail/field-decl.hpp
+++ b/src/lp/detail/field-decl.hpp
@@ -22,14 +22,11 @@
 #ifndef NDN_CXX_LP_DETAIL_FIELD_DECL_HPP
 #define NDN_CXX_LP_DETAIL_FIELD_DECL_HPP
 
-#include "../../common.hpp"
-
 #include "../field.hpp"
-#include "../sequence.hpp"
-#include "../cache-policy.hpp"
-#include "../nack.hpp"
 #include "../tlv.hpp"
 
+#include "../../encoding/block-helpers.hpp"
+#include "../../util/concepts.hpp"
 #include <boost/concept/requires.hpp>
 
 namespace ndn {
diff --git a/src/lp/field.hpp b/src/lp/field.hpp
index 199fb16..712bd86 100644
--- a/src/lp/field.hpp
+++ b/src/lp/field.hpp
@@ -25,6 +25,8 @@
 #include "../common.hpp"
 #include "../encoding/encoding-buffer.hpp"
 
+#include <boost/concept/assert.hpp>
+#include <boost/concept/usage.hpp>
 #include <boost/type_traits.hpp>
 
 namespace ndn {
@@ -79,4 +81,4 @@
 } // namespace lp
 } // namespace ndn
 
-#endif // NDN_CXX_LP_FIELD_HPP
\ No newline at end of file
+#endif // NDN_CXX_LP_FIELD_HPP
diff --git a/src/lp/fields.hpp b/src/lp/fields.hpp
index ad656be..ff9db28 100644
--- a/src/lp/fields.hpp
+++ b/src/lp/fields.hpp
@@ -22,11 +22,11 @@
 #ifndef NDN_CXX_LP_FIELDS_HPP
 #define NDN_CXX_LP_FIELDS_HPP
 
-#include "../common.hpp"
-
-#include "tlv.hpp"
 #include "detail/field-decl.hpp"
-#include "field.hpp"
+
+#include "sequence.hpp"
+#include "cache-policy.hpp"
+#include "nack-header.hpp"
 
 #include <boost/mpl/set.hpp>
 
@@ -94,4 +94,4 @@
 } // namespace lp
 } // namespace ndn
 
-#endif // NDN_CXX_LP_FIELDS_HPP
\ No newline at end of file
+#endif // NDN_CXX_LP_FIELDS_HPP
diff --git a/src/lp/nack.hpp b/src/lp/nack.hpp
index a50221c..3b23cf6 100644
--- a/src/lp/nack.hpp
+++ b/src/lp/nack.hpp
@@ -27,16 +27,15 @@
 #include "../common.hpp"
 #include "../tag-host.hpp"
 #include "../interest.hpp"
-#include "../management/nfd-local-control-header.hpp"
 
 #include "nack-header.hpp"
 
 namespace ndn {
 namespace lp {
 
-/**
- * \brief represents a Network NACK
- * \details This type binds a NackHeader and an Interest, and is intended for use in network layer.
+/** \brief represents a Network Nack
+ *
+ *  This type binds a NackHeader and an Interest, and is intended for use in network layer.
  */
 class Nack : public TagHost
 {
@@ -88,18 +87,6 @@
     return *this;
   }
 
-  nfd::LocalControlHeader&
-  getLocalControlHeader()
-  {
-    return m_interest.getLocalControlHeader();
-  }
-
-  const nfd::LocalControlHeader&
-  getLocalControlHeader() const
-  {
-    return m_interest.getLocalControlHeader();
-  }
-
 public: // NackHeader proxy
   NackReason
   getReason() const
diff --git a/src/lp/tags.cpp b/src/lp/tags.cpp
new file mode 100644
index 0000000..53e0138
--- /dev/null
+++ b/src/lp/tags.cpp
@@ -0,0 +1,131 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2015 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "tags.hpp"
+#include "../encoding/nfd-constants.hpp"
+
+namespace ndn {
+namespace lp {
+
+#ifdef NDN_LP_KEEP_LOCAL_CONTROL_HEADER
+
+using ndn::nfd::INVALID_FACE_ID;
+
+LocalControlHeaderFacade::LocalControlHeaderFacade(TagHost& pkt)
+  : m_pkt(pkt)
+{
+}
+
+bool
+LocalControlHeaderFacade::hasIncomingFaceId() const
+{
+  return m_pkt.getTag<IncomingFaceIdTag>() != nullptr;
+}
+
+uint64_t
+LocalControlHeaderFacade::getIncomingFaceId() const
+{
+  shared_ptr<IncomingFaceIdTag> tag = m_pkt.getTag<IncomingFaceIdTag>();
+  if (tag == nullptr) {
+    return INVALID_FACE_ID;
+  }
+  return *tag;
+}
+
+void
+LocalControlHeaderFacade::setIncomingFaceId(uint64_t incomingFaceId)
+{
+  if (incomingFaceId == INVALID_FACE_ID) {
+    m_pkt.removeTag<IncomingFaceIdTag>();
+    return;
+  }
+
+  auto tag = make_shared<IncomingFaceIdTag>(incomingFaceId);
+  m_pkt.setTag(tag);
+}
+
+bool
+LocalControlHeaderFacade::hasNextHopFaceId() const
+{
+  return m_pkt.getTag<NextHopFaceIdTag>() != nullptr;
+}
+
+uint64_t
+LocalControlHeaderFacade::getNextHopFaceId() const
+{
+  shared_ptr<NextHopFaceIdTag> tag = m_pkt.getTag<NextHopFaceIdTag>();
+  if (tag == nullptr) {
+    return INVALID_FACE_ID;
+  }
+  return *tag;
+}
+
+void
+LocalControlHeaderFacade::setNextHopFaceId(uint64_t nextHopFaceId)
+{
+  if (nextHopFaceId == INVALID_FACE_ID) {
+    m_pkt.removeTag<NextHopFaceIdTag>();
+    return;
+  }
+
+  auto tag = make_shared<NextHopFaceIdTag>(nextHopFaceId);
+  m_pkt.setTag(tag);
+}
+
+bool
+LocalControlHeaderFacade::hasCachingPolicy() const
+{
+  return m_pkt.getTag<CachePolicyTag>() != nullptr;
+}
+
+LocalControlHeaderFacade::CachingPolicy
+LocalControlHeaderFacade::getCachingPolicy() const
+{
+  shared_ptr<CachePolicyTag> tag = m_pkt.getTag<CachePolicyTag>();
+  if (tag == nullptr) {
+    return INVALID_POLICY;
+  }
+  switch (tag->get().getPolicy()) {
+    case CachePolicyType::NO_CACHE:
+      return NO_CACHE;
+    default:
+      return INVALID_POLICY;
+  }
+}
+
+void
+LocalControlHeaderFacade::setCachingPolicy(CachingPolicy cachingPolicy)
+{
+  switch (cachingPolicy) {
+    case NO_CACHE: {
+      m_pkt.setTag(make_shared<CachePolicyTag>(CachePolicy().setPolicy(CachePolicyType::NO_CACHE)));
+      break;
+    }
+    default:
+      m_pkt.removeTag<CachePolicyTag>();
+      break;
+  }
+}
+
+#endif // NDN_LP_KEEP_LOCAL_CONTROL_HEADER
+
+} // namespace lp
+} // namespace ndn
diff --git a/src/lp/tags.hpp b/src/lp/tags.hpp
new file mode 100644
index 0000000..f91b3f1
--- /dev/null
+++ b/src/lp/tags.hpp
@@ -0,0 +1,111 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2015 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#ifndef NDN_CXX_LP_TAGS_HPP
+#define NDN_CXX_LP_TAGS_HPP
+
+#include "cache-policy.hpp"
+#include "../tag-host.hpp"
+
+namespace ndn {
+namespace lp {
+
+/** \class IncomingFaceIdTag
+ *  \brief a packet tag for IncomingFaceId field
+ *
+ *  This tag can be attached to Interest, Data, Nack.
+ */
+typedef SimpleTag<uint64_t, 10> IncomingFaceIdTag;
+
+/** \class NextHopFaceIdTag
+ *  \brief a packet tag for NextHopFaceId field
+ *
+ *  This tag can be attached to Interest.
+ */
+typedef SimpleTag<uint64_t, 11> NextHopFaceIdTag;
+
+/** \class CachePolicyTag
+ *  \brief a packet tag for CachePolicy field
+ *
+ *  This tag can be attached to Data.
+ */
+typedef SimpleTag<CachePolicy, 12> CachePolicyTag;
+
+
+#define NDN_LP_KEEP_LOCAL_CONTROL_HEADER
+
+#ifdef NDN_LP_KEEP_LOCAL_CONTROL_HEADER
+/** \brief expose NDNLPv2 tags as LocalControlHeader API
+ *
+ *  This class has the same public API as ndn::nfd::LocalControlHeader,
+ *  but internally accesses IncomingFaceIdTag, NextHopFaceIdTag, CachePolicyTag
+ *  on the host packet.
+ *
+ *  \deprecated use getTag and setTag with IncomingFaceIdTag, NextHopFaceIdTag, CachePolicyTag
+ */
+class LocalControlHeaderFacade
+{
+public:
+  DEPRECATED(
+  explicit
+  LocalControlHeaderFacade(TagHost& pkt));
+
+  bool
+  hasIncomingFaceId() const;
+
+  uint64_t
+  getIncomingFaceId() const;
+
+  void
+  setIncomingFaceId(uint64_t incomingFaceId);
+
+  bool
+  hasNextHopFaceId() const;
+
+  uint64_t
+  getNextHopFaceId() const;
+
+  void
+  setNextHopFaceId(uint64_t nextHopFaceId);
+
+  bool
+  hasCachingPolicy() const;
+
+  enum CachingPolicy : uint8_t {
+    INVALID_POLICY = 0,
+    NO_CACHE       = 1
+  };
+
+  CachingPolicy
+  getCachingPolicy() const;
+
+  void
+  setCachingPolicy(CachingPolicy cachingPolicy);
+
+private:
+  TagHost& m_pkt;
+};
+#endif // NDN_LP_KEEP_LOCAL_CONTROL_HEADER
+
+} // namespace lp
+} // namespace ndn
+
+#endif // NDN_CXX_LP_TAGS_HPP
diff --git a/src/management/nfd-local-control-header.hpp b/src/management/nfd-local-control-header.hpp
index 327035d..3bc049e 100644
--- a/src/management/nfd-local-control-header.hpp
+++ b/src/management/nfd-local-control-header.hpp
@@ -22,309 +22,16 @@
 #ifndef NDN_MANAGEMENT_NFD_LOCAL_CONTROL_HEADER_HPP
 #define NDN_MANAGEMENT_NFD_LOCAL_CONTROL_HEADER_HPP
 
-#include "../encoding/encoding-buffer.hpp"
-#include "../encoding/tlv-nfd.hpp"
-#include "../encoding/block-helpers.hpp"
+#include "../lp/tags.hpp"
 
 namespace ndn {
 namespace nfd {
 
-/**
- * @ingroup management
- * @brief Class to handle work with LocalControlHeader
- * @sa http://redmine.named-data.net/projects/nfd/wiki/LocalControlHeader
- */
-class LocalControlHeader
-{
-public:
-  class Error : public std::runtime_error
-  {
-  public:
-    explicit
-    Error(const std::string& what)
-      : std::runtime_error(what)
-    {
-    }
-  };
-
-  enum EncodeFlags : uint8_t {
-    ENCODE_NONE             = 0,
-    ENCODE_INCOMING_FACE_ID = (1 << 0),
-    ENCODE_NEXT_HOP         = (1 << 1),
-    ENCODE_CACHING_POLICY   = (1 << 2),
-    ENCODE_ALL              = 0xff
-  };
-
-  enum CachingPolicy : uint8_t {
-    INVALID_POLICY = 0,
-    NO_CACHE       = 1
-  };
-
-  LocalControlHeader()
-    : m_incomingFaceId(INVALID_FACE_ID)
-    , m_nextHopFaceId(INVALID_FACE_ID)
-    , m_cachingPolicy(CachingPolicy::INVALID_POLICY)
-  {
-  }
-
-  /**
-   * @brief Create from wire encoding
-   *
-   * @sa wireDecode
-   */
-  explicit
-  LocalControlHeader(const Block& wire, uint8_t encodeMask = ENCODE_ALL)
-  {
-    wireDecode(wire, encodeMask);
-  }
-
-  /**
-   * @brief Create wire encoding with options LocalControlHeader and the supplied item
-   *
-   * The caller is responsible of checking whether LocalControlHeader contains
-   * any information.
-   *
-   * !It is an error to call this method if none of IncomingFaceId, NextHopFaceId and CachingPolicy
-   * are set, or neither of them are enabled.
-   *
-   * @throws LocalControlHeader::Error when empty LocalControlHeader be produced
-   *
-   * @returns Block, containing LocalControlHeader. Top-level length field of the
-   *          returned LocalControlHeader includes payload length, but the memory
-   *          block is independent of the payload's wire buffer.  It is expected
-   *          that both LocalControlHeader's and payload's wire will be send out
-   *          together within a single send call.
-   *
-   * @see http://redmine.named-data.net/projects/nfd/wiki/LocalControlHeader
-   */
-  template<class U>
-  inline Block
-  wireEncode(const U& payload, uint8_t encodeMask = ENCODE_ALL) const;
-
-  /**
-   * @brief Decode from the wire format and set LocalControlHeader on the supplied item
-   *
-   * The supplied wire MUST contain LocalControlHeader.  Determination whether the optional
-   * LocalControlHeader should be done before calling this method.
-   */
-  inline void
-  wireDecode(const Block& wire, uint8_t encodeMask = ENCODE_ALL);
-
-  inline static const Block&
-  getPayload(const Block& wire);
-
-  ///////////////////////////////////////////////////////////////////////////////
-  ///////////////////////////////////////////////////////////////////////////////
-  ///////////////////////////////////////////////////////////////////////////////
-  // Getters/setters
-
-  bool
-  empty(uint8_t encodeMask) const
-  {
-    bool needIncomingFaceId = encodeMask & ENCODE_INCOMING_FACE_ID;
-    bool needNextHopFaceId = encodeMask & ENCODE_NEXT_HOP;
-    bool needCachingPolicy = encodeMask & ENCODE_CACHING_POLICY;
-
-    return !((needIncomingFaceId && hasIncomingFaceId()) ||
-             (needNextHopFaceId  && hasNextHopFaceId())  ||
-             (needCachingPolicy  && hasCachingPolicy()));
-  }
-
-  //
-
-  bool
-  hasIncomingFaceId() const
-  {
-    return m_incomingFaceId != INVALID_FACE_ID;
-  }
-
-  uint64_t
-  getIncomingFaceId() const
-  {
-    return m_incomingFaceId;
-  }
-
-  void
-  setIncomingFaceId(uint64_t incomingFaceId)
-  {
-    m_incomingFaceId = incomingFaceId;
-  }
-
-  //
-
-  bool
-  hasNextHopFaceId() const
-  {
-    return m_nextHopFaceId != INVALID_FACE_ID;
-  }
-
-  uint64_t
-  getNextHopFaceId() const
-  {
-    return m_nextHopFaceId;
-  }
-
-  void
-  setNextHopFaceId(uint64_t nextHopFaceId)
-  {
-    m_nextHopFaceId = nextHopFaceId;
-  }
-
-  //
-
-  bool
-  hasCachingPolicy() const
-  {
-    return m_cachingPolicy != CachingPolicy::INVALID_POLICY;
-  }
-
-  CachingPolicy
-  getCachingPolicy() const
-  {
-    return m_cachingPolicy;
-  }
-
-  void
-  setCachingPolicy(CachingPolicy cachingPolicy)
-  {
-    m_cachingPolicy = cachingPolicy;
-  }
-
-private:
-  template<encoding::Tag TAG>
-  inline size_t
-  wireEncode(EncodingImpl<TAG>& block, size_t payloadSize, uint8_t encodeMask) const;
-
-private:
-  uint64_t m_incomingFaceId;
-  uint64_t m_nextHopFaceId;
-  CachingPolicy m_cachingPolicy;
-};
-
-
-/**
- * @brief Fast encoding or block size estimation
- */
-template<encoding::Tag TAG>
-inline size_t
-LocalControlHeader::wireEncode(EncodingImpl<TAG>& block, size_t payloadSize,
-                               uint8_t encodeMask) const
-{
-  bool needIncomingFaceId = encodeMask & ENCODE_INCOMING_FACE_ID;
-  bool needNextHopFaceId = encodeMask & ENCODE_NEXT_HOP;
-  bool needCachingPolicy = encodeMask & ENCODE_CACHING_POLICY;
-
-  size_t totalLength = payloadSize;
-
-  if (needIncomingFaceId && hasIncomingFaceId())
-    {
-      totalLength += prependNonNegativeIntegerBlock(block,
-                                                    tlv::nfd::IncomingFaceId, getIncomingFaceId());
-    }
-
-  if (needNextHopFaceId && hasNextHopFaceId())
-    {
-      totalLength += prependNonNegativeIntegerBlock(block,
-                                                    tlv::nfd::NextHopFaceId, getNextHopFaceId());
-    }
-
-  if (needCachingPolicy && hasCachingPolicy())
-    {
-      size_t cachingPolicyLength = 0;
-      cachingPolicyLength += block.prependVarNumber(0);
-      cachingPolicyLength += block.prependVarNumber(tlv::nfd::NoCache);
-      cachingPolicyLength += block.prependVarNumber(cachingPolicyLength);
-      cachingPolicyLength += block.prependVarNumber(tlv::nfd::CachingPolicy);
-
-      totalLength += cachingPolicyLength;
-    }
-
-  totalLength += block.prependVarNumber(totalLength);
-  totalLength += block.prependVarNumber(tlv::nfd::LocalControlHeader);
-  return totalLength;
-}
-
-template<class U>
-inline Block
-LocalControlHeader::wireEncode(const U& payload, uint8_t encodeMask) const
-{
-  /// @todo should this be BOOST_ASSERT instead?  This is kind of unnecessary overhead
-  if (empty(encodeMask))
-    BOOST_THROW_EXCEPTION(Error("Requested wire for LocalControlHeader, but none of the fields are "
-                                "set or enabled"));
-
-  EncodingEstimator estimator;
-  size_t length = wireEncode(estimator, payload.wireEncode().size(), encodeMask);
-
-  EncodingBuffer buffer(length);
-  wireEncode(buffer, payload.wireEncode().size(), encodeMask);
-
-  return buffer.block(false);
-}
-
-inline void
-LocalControlHeader::wireDecode(const Block& wire, uint8_t encodeMask)
-{
-  bool needIncomingFaceId = encodeMask & ENCODE_INCOMING_FACE_ID;
-  bool needNextHopFaceId = encodeMask & ENCODE_NEXT_HOP;
-  bool needCachingPolicy = encodeMask & ENCODE_CACHING_POLICY;
-
-  BOOST_ASSERT(wire.type() == tlv::nfd::LocalControlHeader);
-  wire.parse();
-
-  m_incomingFaceId = INVALID_FACE_ID;
-  m_nextHopFaceId = INVALID_FACE_ID;
-  m_cachingPolicy = CachingPolicy::INVALID_POLICY;
-
-  for (Block::element_const_iterator i = wire.elements_begin();
-       i != wire.elements_end();
-       ++i)
-    {
-      switch (i->type())
-        {
-        case tlv::nfd::IncomingFaceId:
-          if (needIncomingFaceId)
-            m_incomingFaceId = readNonNegativeInteger(*i);
-          break;
-        case tlv::nfd::NextHopFaceId:
-          if (needNextHopFaceId)
-            m_nextHopFaceId = readNonNegativeInteger(*i);
-          break;
-        case tlv::nfd::CachingPolicy:
-          if (needCachingPolicy) {
-            i->parse();
-            Block::element_const_iterator it = i->elements_begin();
-            if (it != i->elements_end() && it->type() == tlv::nfd::NoCache) {
-              m_cachingPolicy = CachingPolicy::NO_CACHE;
-            }
-            else {
-              BOOST_THROW_EXCEPTION(Error("CachingPolicy: Missing required NoCache field"));
-            }
-          }
-          break;
-        default:
-          // ignore all unsupported
-          break;
-        }
-    }
-}
-
-inline const Block&
-LocalControlHeader::getPayload(const Block& wire)
-{
-  if (wire.type() == tlv::nfd::LocalControlHeader)
-    {
-      wire.parse();
-      if (wire.elements_size() < 1)
-        return wire; // don't throw an error, but don't continue processing
-
-      return wire.elements()[wire.elements().size()-1];
-    }
-  else
-    {
-      return wire;
-    }
-}
+#ifdef NDN_LP_KEEP_LOCAL_CONTROL_HEADER
+typedef ndn::lp::LocalControlHeaderFacade LocalControlHeader;
+#else
+#error "LocalControlHeader is deleted"
+#endif // NDN_LP_KEEP_LOCAL_CONTROL_HEADER
 
 } // namespace nfd
 } // namespace ndn
diff --git a/src/tag.hpp b/src/tag.hpp
index 195d821..cd36445 100644
--- a/src/tag.hpp
+++ b/src/tag.hpp
@@ -25,13 +25,13 @@
 namespace ndn {
 
 /**
- * @brief Base class for interest/data tags that can hold any arbitrary information
+ * @brief Base class for packet tags that can hold any arbitrary information
  */
 class Tag
 {
 public:
   virtual
-  ~Tag() = 0;
+  ~Tag();
 
   /**
    * @fn static constexpr int getTypeId()
@@ -48,9 +48,49 @@
 };
 
 inline
-Tag::~Tag()
+Tag::~Tag() = default;
+
+/** @brief provides a tag type for simple types
+ *  @tparam T the value type
+ *  @tparam TypeId the TypeId
+ */
+template<typename T, int TypeId>
+class SimpleTag : public Tag
 {
-}
+public:
+  static constexpr int
+  getTypeId()
+  {
+    return TypeId;
+  }
+
+  /** \brief explicitly convertible from T
+   */
+  explicit
+  SimpleTag(const T& value)
+    : m_value(value)
+  {
+  }
+
+  /** \brief implicitly convertible to T
+   *  \return a copy of the enclosed value
+   */
+  operator T() const
+  {
+    return m_value;
+  }
+
+  /** \return the enclosed value
+   */
+  const T&
+  get() const
+  {
+    return m_value;
+  }
+
+private:
+  T m_value;
+};
 
 } // namespace ndn
 
diff --git a/src/util/dummy-client-face.cpp b/src/util/dummy-client-face.cpp
index 676a7a0..aadb6bc 100644
--- a/src/util/dummy-client-face.cpp
+++ b/src/util/dummy-client-face.cpp
@@ -115,14 +115,13 @@
         shared_ptr<lp::Nack> nack = make_shared<lp::Nack>(std::move(*interest));
         nack->setHeader(lpPacket.get<lp::NackField>());
         if (lpPacket.has<lp::NextHopFaceIdField>()) {
-          nack->getLocalControlHeader().setNextHopFaceId(lpPacket.get<lp::NextHopFaceIdField>());
+          nack->setTag(make_shared<lp::NextHopFaceIdTag>(lpPacket.get<lp::NextHopFaceIdField>()));
         }
         onSendNack(*nack);
       }
       else {
         if (lpPacket.has<lp::NextHopFaceIdField>()) {
-          interest->getLocalControlHeader().
-            setNextHopFaceId(lpPacket.get<lp::NextHopFaceIdField>());
+          interest->setTag(make_shared<lp::NextHopFaceIdTag>(lpPacket.get<lp::NextHopFaceIdField>()));
         }
         onSendInterest(*interest);
       }
@@ -131,9 +130,7 @@
       shared_ptr<Data> data = make_shared<Data>(block);
 
       if (lpPacket.has<lp::CachePolicyField>()) {
-        if (lpPacket.get<lp::CachePolicyField>().getPolicy() == lp::CachePolicyType::NO_CACHE) {
-          data->getLocalControlHeader().setCachingPolicy(nfd::LocalControlHeader::CachingPolicy::NO_CACHE);
-        }
+        data->setTag(make_shared<lp::CachePolicyTag>(lpPacket.get<lp::CachePolicyField>()));
       }
 
       onSendData(*data);
@@ -196,16 +193,17 @@
 {
   lp::Packet lpPacket(packet.wireEncode());
 
-  nfd::LocalControlHeader localControlHeader = packet.getLocalControlHeader();
-
-  if (localControlHeader.hasIncomingFaceId()) {
-    lpPacket.add<lp::IncomingFaceIdField>(localControlHeader.getIncomingFaceId());
+  shared_ptr<lp::IncomingFaceIdTag> incomingFaceIdTag =
+    static_cast<const TagHost&>(packet).getTag<lp::IncomingFaceIdTag>();
+  if (incomingFaceIdTag != nullptr) {
+    lpPacket.add<lp::IncomingFaceIdField>(*incomingFaceIdTag);
   }
 
-  if (localControlHeader.hasNextHopFaceId()) {
-    lpPacket.add<lp::NextHopFaceIdField>(localControlHeader.getNextHopFaceId());
+  shared_ptr<lp::NextHopFaceIdTag> nextHopFaceIdTag =
+    static_cast<const TagHost&>(packet).getTag<lp::NextHopFaceIdTag>();
+  if (nextHopFaceIdTag != nullptr) {
+    lpPacket.add<lp::NextHopFaceIdField>(*nextHopFaceIdTag);
   }
-
   m_transport->receive(lpPacket.wireEncode());
 }
 
@@ -224,10 +222,9 @@
   Block interest = nack.getInterest().wireEncode();
   lpPacket.add<lp::FragmentField>(make_pair(interest.begin(), interest.end()));
 
-  nfd::LocalControlHeader localControlHeader = nack.getLocalControlHeader();
-
-  if (localControlHeader.hasIncomingFaceId()) {
-    lpPacket.add<lp::IncomingFaceIdField>(localControlHeader.getIncomingFaceId());
+  shared_ptr<lp::IncomingFaceIdTag> incomingFaceIdTag = nack.getTag<lp::IncomingFaceIdTag>();
+  if (incomingFaceIdTag != nullptr) {
+    lpPacket.add<lp::IncomingFaceIdField>(*incomingFaceIdTag);
   }
 
   m_transport->receive(lpPacket.wireEncode());
diff --git a/tests/unit-tests/data.t.cpp b/tests/unit-tests/data.t.cpp
index ab9cee6..e243416 100644
--- a/tests/unit-tests/data.t.cpp
+++ b/tests/unit-tests/data.t.cpp
@@ -120,19 +120,6 @@
   0x0a, 0xb6
 };
 
-const uint8_t DataWithLocalControlHeader[] = {
-  0x50, 0x29, 0x53, 0x02, 0x60, 0x00,
-  0x06, 0x23, 0x07, 0x03, 0x08, 0x01, 0x41, 0x14, 0x04, 0x19, 0x02, 0x27, 0x10, 0x15,
-  0x09, 0x73, 0x6F, 0x6D, 0x65, 0x44, 0x61, 0x74, 0x61, 0x00, 0x16, 0x05, 0x1B, 0x01,
-  0x01, 0x1C, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00
-};
-
-const uint8_t DataWithoutLocalControlHeader[] = {
-  0x06, 0x23, 0x07, 0x03, 0x08, 0x01, 0x41, 0x14, 0x04, 0x19, 0x02, 0x27, 0x10, 0x15,
-  0x09, 0x73, 0x6F, 0x6D, 0x65, 0x44, 0x61, 0x74, 0x61, 0x00, 0x16, 0x05, 0x1B, 0x01,
-  0x01, 0x1C, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00
-};
-
 BOOST_AUTO_TEST_CASE(DataEqualityChecks)
 {
   using namespace time;
@@ -213,114 +200,6 @@
   BOOST_CHECK_EQUAL(a != b, false);
 }
 
-BOOST_AUTO_TEST_CASE(EncodeWithLocalHeader)
-{
-  Data data("ndn:/A");
-  data.setFreshnessPeriod(time::seconds(10));
-  static const uint8_t someData[] = "someData";
-  data.setContent(someData, sizeof(someData));
-  data.setSignature(SignatureSha256WithRsa());
-  data.setCachingPolicy(nfd::LocalControlHeader::CachingPolicy::NO_CACHE);
-
-  BOOST_CHECK(!data.hasWire());
-
-  Block headerBlock =
-    data.getLocalControlHeader().wireEncode(data, nfd::LocalControlHeader::ENCODE_CACHING_POLICY);
-
-  BOOST_CHECK(data.hasWire());
-  BOOST_CHECK(headerBlock.hasWire());
-
-  BOOST_CHECK_NE(headerBlock.wire(), data.wireEncode().wire());
-  BOOST_CHECK_NE(headerBlock.size(), data.wireEncode().size());
-  BOOST_CHECK_EQUAL(headerBlock.size(), 6);
-
-  BOOST_CHECK_EQUAL_COLLECTIONS(DataWithLocalControlHeader,
-                                DataWithLocalControlHeader + 6,
-                                headerBlock.begin(), headerBlock.end());
-
-  data.setFreshnessPeriod(time::seconds(1000));
-
-  Block updatedHeaderBlock = data.getLocalControlHeader()
-                                 .wireEncode(data, nfd::LocalControlHeader::ENCODE_CACHING_POLICY);
-  BOOST_CHECK_EQUAL(updatedHeaderBlock.size(), 6);
-
-  // only length should have changed
-  BOOST_CHECK_EQUAL_COLLECTIONS(updatedHeaderBlock.begin() + 2, updatedHeaderBlock.end(),
-                                headerBlock.begin() + 2,        headerBlock.end());
-
-  // adding IncomingFaceId
-  data.setIncomingFaceId(10);
-  updatedHeaderBlock =
-    data.getLocalControlHeader().wireEncode(data, nfd::LocalControlHeader::ENCODE_INCOMING_FACE_ID
-                                                  | nfd::LocalControlHeader::ENCODE_CACHING_POLICY);
-  BOOST_CHECK_EQUAL(updatedHeaderBlock.size(), 9);
-
-  // masking CachingPolicy
-  updatedHeaderBlock = data.getLocalControlHeader()
-                           .wireEncode(data, nfd::LocalControlHeader::ENCODE_INCOMING_FACE_ID);
-  BOOST_CHECK_EQUAL(updatedHeaderBlock.size(), 5);
-
-  // masking everything
-  BOOST_CHECK_THROW(data.getLocalControlHeader()
-                        .wireEncode(data, nfd::LocalControlHeader::ENCODE_NONE),
-                    nfd::LocalControlHeader::Error);
-}
-
-BOOST_AUTO_TEST_CASE(DecodeWithLocalHeader)
-{
-  Block wireBlock(DataWithLocalControlHeader, sizeof(DataWithLocalControlHeader));
-  const Block& payload = nfd::LocalControlHeader::getPayload(wireBlock);
-  BOOST_REQUIRE_NE(&payload, &wireBlock);
-
-  BOOST_CHECK_EQUAL(payload.type(), static_cast<uint32_t>(tlv::Data));
-  BOOST_CHECK_EQUAL(wireBlock.type(), static_cast<uint32_t>(tlv::nfd::LocalControlHeader));
-
-  Data data(payload);
-  BOOST_CHECK(!data.getLocalControlHeader().hasCachingPolicy());
-  BOOST_CHECK(!data.getLocalControlHeader().hasIncomingFaceId());
-
-  data.getLocalControlHeader().wireDecode(wireBlock);
-
-  BOOST_CHECK_EQUAL(data.getLocalControlHeader()
-                        .wireEncode(data, nfd::LocalControlHeader::ENCODE_CACHING_POLICY).size(),
-                    6);
-
-  BOOST_CHECK(data.getLocalControlHeader().hasCachingPolicy());
-  BOOST_CHECK(!data.getLocalControlHeader().hasIncomingFaceId());
-
-  BOOST_CHECK_THROW(data.getLocalControlHeader()
-                        .wireEncode(data, nfd::LocalControlHeader::ENCODE_NONE),
-                    nfd::LocalControlHeader::Error);
-
-  BOOST_CHECK_THROW(data.getLocalControlHeader()
-                        .wireEncode(data, nfd::LocalControlHeader::ENCODE_INCOMING_FACE_ID),
-                    nfd::LocalControlHeader::Error);
-
-  BOOST_CHECK_NO_THROW(data.getLocalControlHeader()
-                           .wireEncode(data, nfd::LocalControlHeader::ENCODE_CACHING_POLICY));
-  BOOST_CHECK_NO_THROW(
-    data.getLocalControlHeader().wireEncode(data, nfd::LocalControlHeader::ENCODE_INCOMING_FACE_ID |
-                                                  nfd::LocalControlHeader::ENCODE_CACHING_POLICY));
-
-  BOOST_CHECK_NE((void*)data.getLocalControlHeader()
-                            .wireEncode(data, nfd::LocalControlHeader::ENCODE_INCOMING_FACE_ID |
-                                              nfd::LocalControlHeader::ENCODE_CACHING_POLICY)
-                            .wire(),
-                 (void*)wireBlock.wire());
-
-  BOOST_CHECK_EQUAL(data.getLocalControlHeader()
-                        .wireEncode(data, nfd::LocalControlHeader::ENCODE_INCOMING_FACE_ID |
-                                          nfd::LocalControlHeader::ENCODE_CACHING_POLICY).size(),
-                    6);
-}
-
-BOOST_AUTO_TEST_CASE(DecodeWithoutLocalHeader)
-{
-  Block wireBlock(DataWithoutLocalControlHeader, sizeof(DataWithoutLocalControlHeader));
-  const Block& payload = nfd::LocalControlHeader::getPayload(wireBlock);
-  BOOST_CHECK_EQUAL(&payload, &wireBlock);
-}
-
 class TestDataFixture
 {
 public:
diff --git a/tests/unit-tests/face.t.cpp b/tests/unit-tests/face.t.cpp
index 438dde5..ccd7df4 100644
--- a/tests/unit-tests/face.t.cpp
+++ b/tests/unit-tests/face.t.cpp
@@ -569,80 +569,6 @@
   BOOST_CHECK_EQUAL(nRegSuccesses, 1);
 }
 
-BOOST_AUTO_TEST_CASE(ExpressInterestWithLocalControlHeader)
-{
-  Interest i("/Hello/World");
-  i.setNextHopFaceId(1000);
-  i.setIncomingFaceId(2000);
-
-  face->expressInterest(i, bind([]{}), bind([]{}));
-  advanceClocks(time::milliseconds(10));
-
-  BOOST_REQUIRE_EQUAL(face->sentInterests.size(), 1);
-  // only NextHopFaceId is allowed to go out
-  BOOST_CHECK(face->sentInterests[0].getLocalControlHeader().hasNextHopFaceId());
-  BOOST_CHECK(!face->sentInterests[0].getLocalControlHeader().hasIncomingFaceId());
-  BOOST_CHECK_EQUAL(face->sentInterests[0].getNextHopFaceId(), 1000);
-}
-
-BOOST_AUTO_TEST_CASE(ReceiveInterestWithLocalControlHeader)
-{
-  face->setInterestFilter("/Hello/World",
-                          [] (const InterestFilter&, const Interest& i) {
-                            BOOST_CHECK(i.getLocalControlHeader().hasIncomingFaceId());
-                            BOOST_CHECK_EQUAL(i.getIncomingFaceId(), 2000);
-                          },
-                          bind([]{}),
-                          bind([] {
-                              BOOST_FAIL("Unexpected setInterestFilter failure");
-                            }));
-  advanceClocks(time::milliseconds(10));
-
-  Interest i("/Hello/World/!");
-  i.setNextHopFaceId(1000);
-  i.setIncomingFaceId(2000);
-
-  face->receive(i);
-  advanceClocks(time::milliseconds(10));
-}
-
-BOOST_AUTO_TEST_CASE(PutDataWithLocalControlHeader)
-{
-  shared_ptr<Data> d = util::makeData("/Bye/World/!");
-  d->setIncomingFaceId(2000);
-  d->getLocalControlHeader().setNextHopFaceId(1000); // setNextHopFaceId is intentionally
-                                                     // not exposed directly
-
-  face->put(*d);
-  advanceClocks(time::milliseconds(10));
-
-  BOOST_REQUIRE_EQUAL(face->sentDatas.size(), 1);
-  BOOST_CHECK(!face->sentDatas[0].getLocalControlHeader().hasNextHopFaceId());
-  BOOST_CHECK(!face->sentDatas[0].getLocalControlHeader().hasIncomingFaceId());
-}
-
-BOOST_AUTO_TEST_CASE(ReceiveDataWithLocalControlHeader)
-{
-  face->expressInterest(Interest("/Hello/World", time::milliseconds(50)),
-                        [&] (const Interest& i, const Data& d) {
-                          BOOST_CHECK(d.getLocalControlHeader().hasIncomingFaceId());
-                          BOOST_CHECK_EQUAL(d.getIncomingFaceId(), 2000);
-                        },
-                        bind([] {
-                            BOOST_FAIL("Unexpected timeout");
-                          }));
-
-  advanceClocks(time::milliseconds(10));
-
-  shared_ptr<Data> d = util::makeData("/Hello/World/!");
-  d->setIncomingFaceId(2000);
-  d->getLocalControlHeader().setNextHopFaceId(1000); // setNextHopFaceId is intentionally
-                                                     // not exposed directly
-  face->receive(*d);
-
-  advanceClocks(time::milliseconds(10), 100);
-}
-
 BOOST_AUTO_TEST_CASE(PutNack)
 {
   lp::Nack nack(Interest("/Hello/World", time::milliseconds(50)));
diff --git a/tests/unit-tests/interest.t.cpp b/tests/unit-tests/interest.t.cpp
index 66c25dc..27cdff3 100644
--- a/tests/unit-tests/interest.t.cpp
+++ b/tests/unit-tests/interest.t.cpp
@@ -543,19 +543,6 @@
           0x01
 };
 
-const uint8_t InterestWithLocalControlHeader[] = {
-  0x50, 0x25, 0x51, 0x01, 0x0a,
-  0x05, 0x20, 0x07, 0x14, 0x08, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x08, 0x03, 0x6e, 0x64,
-  0x6e, 0x08, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x09, 0x02, 0x12, 0x00, 0x0a, 0x04,
-  0x01, 0x00, 0x00, 0x00
-};
-
-const uint8_t InterestWithoutLocalControlHeader[] = {
-  0x05, 0x20, 0x07, 0x14, 0x08, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x08, 0x03, 0x6e, 0x64,
-  0x6e, 0x08, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x09, 0x02, 0x12, 0x00, 0x0a, 0x04,
-  0x01, 0x00, 0x00, 0x00
-};
-
 BOOST_AUTO_TEST_CASE(InterestEqualityChecks)
 {
   // Interest ::= INTEREST-TYPE TLV-LENGTH
@@ -1037,146 +1024,6 @@
   BOOST_CHECK_EQUAL(i2.hasSelectedDelegation(), false);
 }
 
-BOOST_AUTO_TEST_CASE(EncodeWithLocalHeader)
-{
-  ndn::Interest interest(ndn::Name("/local/ndn/prefix"));
-  interest.setMustBeFresh(true);
-  interest.setIncomingFaceId(10);
-  interest.setNonce(1);
-
-  BOOST_CHECK(!interest.hasWire());
-
-  Block headerBlock =
-    interest.getLocalControlHeader()
-            .wireEncode(interest, nfd::LocalControlHeader::ENCODE_INCOMING_FACE_ID |
-                                  nfd::LocalControlHeader::ENCODE_NEXT_HOP);
-
-  BOOST_CHECK(interest.hasWire());
-  BOOST_CHECK(headerBlock.hasWire());
-
-  BOOST_CHECK_NE(headerBlock.wire(), interest.wireEncode().wire());
-  BOOST_CHECK_NE(headerBlock.size(), interest.wireEncode().size());
-  BOOST_CHECK_EQUAL(headerBlock.size(), 5);
-
-  BOOST_CHECK_EQUAL_COLLECTIONS(InterestWithLocalControlHeader,
-                                InterestWithLocalControlHeader + 5,
-                                headerBlock.begin(), headerBlock.end());
-
-  interest.setNonce(1000);
-
-  Block updatedHeaderBlock =
-    interest.getLocalControlHeader()
-            .wireEncode(interest, nfd::LocalControlHeader::ENCODE_INCOMING_FACE_ID |
-                                  nfd::LocalControlHeader::ENCODE_NEXT_HOP);
-  BOOST_CHECK_EQUAL(updatedHeaderBlock.size(), 5);
-
-  // only length should have changed
-  BOOST_CHECK_EQUAL_COLLECTIONS(updatedHeaderBlock.begin() + 2, updatedHeaderBlock.end(),
-                                headerBlock.begin() + 2,        headerBlock.end());
-
-  // updating IncomingFaceId that keeps the length
-  interest.setIncomingFaceId(100);
-  updatedHeaderBlock =
-    interest.getLocalControlHeader()
-            .wireEncode(interest, nfd::LocalControlHeader::ENCODE_INCOMING_FACE_ID |
-                                  nfd::LocalControlHeader::ENCODE_NEXT_HOP);
-  BOOST_CHECK_EQUAL(updatedHeaderBlock.size(), 5);
-  BOOST_CHECK_NE(*(updatedHeaderBlock.begin() + 4), *(headerBlock.begin() + 4));
-
-  // updating IncomingFaceId that increases the length by 2
-  interest.setIncomingFaceId(1000);
-  updatedHeaderBlock =
-    interest.getLocalControlHeader()
-            .wireEncode(interest, nfd::LocalControlHeader::ENCODE_INCOMING_FACE_ID |
-                                  nfd::LocalControlHeader::ENCODE_NEXT_HOP);
-  BOOST_CHECK_EQUAL(updatedHeaderBlock.size(), 6);
-
-  // adding NextHopId
-  interest.setNextHopFaceId(1);
-  updatedHeaderBlock =
-    interest.getLocalControlHeader()
-            .wireEncode(interest, nfd::LocalControlHeader::ENCODE_INCOMING_FACE_ID |
-                                  nfd::LocalControlHeader::ENCODE_NEXT_HOP);
-  BOOST_CHECK_EQUAL(updatedHeaderBlock.size(), 9);
-
-  // masking IncomingFaceId
-  updatedHeaderBlock = interest.getLocalControlHeader()
-                               .wireEncode(interest, nfd::LocalControlHeader::ENCODE_NEXT_HOP);
-  BOOST_CHECK_EQUAL(updatedHeaderBlock.size(), 5);
-
-  // masking NextHopId
-  updatedHeaderBlock =
-    interest.getLocalControlHeader()
-            .wireEncode(interest, nfd::LocalControlHeader::ENCODE_INCOMING_FACE_ID);
-  BOOST_CHECK_EQUAL(updatedHeaderBlock.size(), 6);
-
-  // masking everything
-  BOOST_CHECK_THROW(interest.getLocalControlHeader()
-                            .wireEncode(interest, nfd::LocalControlHeader::ENCODE_NONE),
-                    nfd::LocalControlHeader::Error);
-}
-
-
-BOOST_AUTO_TEST_CASE(DecodeWithLocalHeader)
-{
-  Block wireBlock(InterestWithLocalControlHeader, sizeof(InterestWithLocalControlHeader));
-  const Block& payload = nfd::LocalControlHeader::getPayload(wireBlock);
-  BOOST_REQUIRE_NE(&payload, &wireBlock);
-
-  BOOST_CHECK_EQUAL(payload.type(), static_cast<uint32_t>(tlv::Interest));
-  BOOST_CHECK_EQUAL(wireBlock.type(), static_cast<uint32_t>(tlv::nfd::LocalControlHeader));
-
-  Interest interest(payload);
-  BOOST_CHECK(!interest.getLocalControlHeader().hasIncomingFaceId());
-  BOOST_CHECK(!interest.getLocalControlHeader().hasNextHopFaceId());
-
-  BOOST_REQUIRE_NO_THROW(interest.getLocalControlHeader().wireDecode(wireBlock));
-
-  BOOST_CHECK_EQUAL(
-    interest.getLocalControlHeader()
-            .wireEncode(interest, nfd::LocalControlHeader::ENCODE_INCOMING_FACE_ID |
-                                  nfd::LocalControlHeader::ENCODE_NEXT_HOP).size(),
-    5);
-
-  BOOST_CHECK_EQUAL(interest.getIncomingFaceId(), 10);
-  BOOST_CHECK(!interest.getLocalControlHeader().hasNextHopFaceId());
-
-  BOOST_CHECK_THROW(interest.getLocalControlHeader()
-                            .wireEncode(interest, nfd::LocalControlHeader::ENCODE_NONE),
-                    nfd::LocalControlHeader::Error);
-
-  BOOST_CHECK_THROW(interest.getLocalControlHeader()
-                            .wireEncode(interest, nfd::LocalControlHeader::ENCODE_NEXT_HOP),
-                    nfd::LocalControlHeader::Error);
-
-  BOOST_CHECK_NO_THROW(
-    interest.getLocalControlHeader()
-            .wireEncode(interest, nfd::LocalControlHeader::ENCODE_INCOMING_FACE_ID));
-  BOOST_CHECK_NO_THROW(
-    interest.getLocalControlHeader()
-            .wireEncode(interest, nfd::LocalControlHeader::ENCODE_INCOMING_FACE_ID |
-                                  nfd::LocalControlHeader::ENCODE_NEXT_HOP));
-
-  BOOST_CHECK_NE(
-    (void*)interest.getLocalControlHeader()
-                   .wireEncode(interest, nfd::LocalControlHeader::ENCODE_INCOMING_FACE_ID |
-                                         nfd::LocalControlHeader::ENCODE_NEXT_HOP)
-                   .wire(),
-    (void*)wireBlock.wire());
-
-  BOOST_CHECK_EQUAL(interest.getLocalControlHeader()
-                            .wireEncode(interest, nfd::LocalControlHeader::ENCODE_INCOMING_FACE_ID |
-                                                  nfd::LocalControlHeader::ENCODE_NEXT_HOP).size(),
-                    5);
-}
-
-BOOST_AUTO_TEST_CASE(DecodeWithoutLocalHeader)
-{
-  Block wireBlock(InterestWithoutLocalControlHeader, sizeof(InterestWithoutLocalControlHeader));
-  const Block& payload = nfd::LocalControlHeader::getPayload(wireBlock);
-  BOOST_CHECK_EQUAL(&payload, &wireBlock);
-}
-
 BOOST_AUTO_TEST_CASE(MatchesData)
 {
   Interest interest;
diff --git a/tests/unit-tests/lp/tags.t.cpp b/tests/unit-tests/lp/tags.t.cpp
new file mode 100644
index 0000000..139c7f7
--- /dev/null
+++ b/tests/unit-tests/lp/tags.t.cpp
@@ -0,0 +1,197 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2015 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "lp/tags.hpp"
+#include "encoding/nfd-constants.hpp"
+#include "interest.hpp"
+#include "data.hpp"
+#include "lp/nack.hpp"
+
+#include <boost/mpl/vector.hpp>
+#include "boost-test.hpp"
+
+#ifdef NDN_LP_KEEP_LOCAL_CONTROL_HEADER
+#include "management/nfd-local-control-header.hpp"
+#endif // NDN_LP_KEEP_LOCAL_CONTROL_HEADER
+
+namespace ndn {
+namespace lp {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Lp)
+BOOST_AUTO_TEST_SUITE(TestTags)
+
+#ifdef NDN_LP_KEEP_LOCAL_CONTROL_HEADER
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
+BOOST_AUTO_TEST_SUITE(Facade)
+
+typedef boost::mpl::vector<Interest, Data, Nack> TagHostTypes;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(IncomingFaceId, T, TagHostTypes)
+{
+  T pkt;
+  LocalControlHeaderFacade lch(pkt);
+
+  BOOST_CHECK_EQUAL(lch.hasIncomingFaceId(), false);
+
+  lch.setIncomingFaceId(303);
+  shared_ptr<IncomingFaceIdTag> tag = static_cast<TagHost&>(pkt).getTag<IncomingFaceIdTag>();
+  BOOST_REQUIRE(tag != nullptr);
+  BOOST_CHECK_EQUAL(*tag, 303);
+
+  lch.setIncomingFaceId(ndn::nfd::INVALID_FACE_ID);
+  BOOST_CHECK(static_cast<TagHost&>(pkt).getTag<IncomingFaceIdTag>() == nullptr);
+
+  static_cast<TagHost&>(pkt).setTag(make_shared<IncomingFaceIdTag>(104));
+  BOOST_CHECK_EQUAL(lch.getIncomingFaceId(), 104);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(NextHopFaceId, T, TagHostTypes)
+{
+  T pkt;
+  LocalControlHeaderFacade lch(pkt);
+
+  BOOST_CHECK_EQUAL(lch.hasNextHopFaceId(), false);
+
+  lch.setNextHopFaceId(303);
+  shared_ptr<NextHopFaceIdTag> tag = static_cast<TagHost&>(pkt).getTag<NextHopFaceIdTag>();
+  BOOST_REQUIRE(tag != nullptr);
+  BOOST_CHECK_EQUAL(*tag, 303);
+
+  lch.setNextHopFaceId(ndn::nfd::INVALID_FACE_ID);
+  BOOST_CHECK(static_cast<TagHost&>(pkt).getTag<NextHopFaceIdTag>() == nullptr);
+
+  static_cast<TagHost&>(pkt).setTag(make_shared<NextHopFaceIdTag>(104));
+  BOOST_CHECK_EQUAL(lch.getNextHopFaceId(), 104);
+}
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(CachePolicy, T, TagHostTypes)
+{
+  using lp::CachePolicy;
+
+  T pkt;
+  LocalControlHeaderFacade lch(pkt);
+
+  BOOST_CHECK_EQUAL(lch.hasCachingPolicy(), false);
+
+  lch.setCachingPolicy(LocalControlHeaderFacade::NO_CACHE);
+  shared_ptr<CachePolicyTag> tag = static_cast<TagHost&>(pkt).getTag<CachePolicyTag>();
+  BOOST_REQUIRE(tag != nullptr);
+  BOOST_CHECK_EQUAL(tag->get().getPolicy(), CachePolicyType::NO_CACHE);
+
+  lch.setCachingPolicy(LocalControlHeaderFacade::INVALID_POLICY);
+  BOOST_CHECK(static_cast<TagHost&>(pkt).getTag<CachePolicyTag>() == nullptr);
+
+  auto tag2 = make_shared<CachePolicyTag>(CachePolicy().setPolicy(CachePolicyType::NO_CACHE));
+  static_cast<TagHost&>(pkt).setTag(tag2);
+  BOOST_CHECK_EQUAL(lch.getCachingPolicy(), LocalControlHeaderFacade::NO_CACHE);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // Facade
+
+BOOST_AUTO_TEST_CASE(InterestGetters)
+{
+  Interest interest;
+
+  interest.setTag(make_shared<IncomingFaceIdTag>(319));
+  BOOST_CHECK_EQUAL(interest.getLocalControlHeader().getIncomingFaceId(), 319);
+  BOOST_CHECK_EQUAL(interest.getIncomingFaceId(), 319);
+
+  interest.setTag(make_shared<NextHopFaceIdTag>(213));
+  BOOST_CHECK_EQUAL(interest.getLocalControlHeader().getNextHopFaceId(), 213);
+  BOOST_CHECK_EQUAL(interest.getNextHopFaceId(), 213);
+}
+
+BOOST_AUTO_TEST_CASE(InterestSetters)
+{
+  Interest interest;
+
+  interest.getLocalControlHeader().setIncomingFaceId(268);
+  shared_ptr<IncomingFaceIdTag> incomingFaceIdTag = interest.getTag<IncomingFaceIdTag>();
+  BOOST_REQUIRE(incomingFaceIdTag != nullptr);
+  BOOST_CHECK_EQUAL(*incomingFaceIdTag, 268);
+
+  interest.setIncomingFaceId(153);
+  incomingFaceIdTag = interest.getTag<IncomingFaceIdTag>();
+  BOOST_REQUIRE(incomingFaceIdTag != nullptr);
+  BOOST_CHECK_EQUAL(*incomingFaceIdTag, 153);
+
+  interest.getLocalControlHeader().setNextHopFaceId(307);
+  shared_ptr<NextHopFaceIdTag> nextHopFaceIdTag = interest.getTag<NextHopFaceIdTag>();
+  BOOST_REQUIRE(nextHopFaceIdTag != nullptr);
+  BOOST_CHECK_EQUAL(*nextHopFaceIdTag, 307);
+
+  interest.setNextHopFaceId(260);
+  nextHopFaceIdTag = interest.getTag<NextHopFaceIdTag>();
+  BOOST_REQUIRE(nextHopFaceIdTag != nullptr);
+  BOOST_CHECK_EQUAL(*nextHopFaceIdTag, 260);
+}
+
+BOOST_AUTO_TEST_CASE(DataGetters)
+{
+  Data data;
+
+  data.setTag(make_shared<IncomingFaceIdTag>(16));
+  BOOST_CHECK_EQUAL(data.getLocalControlHeader().getIncomingFaceId(), 16);
+  BOOST_CHECK_EQUAL(data.getIncomingFaceId(), 16);
+
+  data.setTag(make_shared<CachePolicyTag>(CachePolicy().setPolicy(CachePolicyType::NO_CACHE)));
+  BOOST_CHECK_EQUAL(data.getLocalControlHeader().getCachingPolicy(), nfd::LocalControlHeader::NO_CACHE);
+  BOOST_CHECK_EQUAL(data.getCachingPolicy(), nfd::LocalControlHeader::NO_CACHE);
+}
+
+BOOST_AUTO_TEST_CASE(DataSetters)
+{
+  Data data;
+
+  data.getLocalControlHeader().setIncomingFaceId(297);
+  shared_ptr<IncomingFaceIdTag> incomingFaceIdTag = data.getTag<IncomingFaceIdTag>();
+  BOOST_REQUIRE(incomingFaceIdTag != nullptr);
+  BOOST_CHECK_EQUAL(*incomingFaceIdTag, 297);
+
+  data.setIncomingFaceId(233);
+  incomingFaceIdTag = data.getTag<IncomingFaceIdTag>();
+  BOOST_REQUIRE(incomingFaceIdTag != nullptr);
+  BOOST_CHECK_EQUAL(*incomingFaceIdTag, 233);
+
+  data.getLocalControlHeader().setCachingPolicy(nfd::LocalControlHeader::NO_CACHE);
+  shared_ptr<CachePolicyTag> cachePolicyTag = data.getTag<CachePolicyTag>();
+  BOOST_REQUIRE(cachePolicyTag != nullptr);
+  BOOST_CHECK_EQUAL(cachePolicyTag->get().getPolicy(), CachePolicyType::NO_CACHE);
+
+  data.setCachingPolicy(nfd::LocalControlHeader::INVALID_POLICY);
+  cachePolicyTag = data.getTag<CachePolicyTag>();
+  BOOST_CHECK(cachePolicyTag == nullptr);
+}
+
+#pragma GCC diagnostic pop
+
+#endif // NDN_LP_KEEP_LOCAL_CONTROL_HEADER
+
+BOOST_AUTO_TEST_SUITE_END() // TestTags
+BOOST_AUTO_TEST_SUITE_END() // Lp
+
+} // namespace tests
+} // namespace lp
+} // namespace ndn
diff --git a/tests/unit-tests/tag.t.cpp b/tests/unit-tests/tag.t.cpp
new file mode 100644
index 0000000..994449a
--- /dev/null
+++ b/tests/unit-tests/tag.t.cpp
@@ -0,0 +1,45 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2015 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ */
+
+#include "tag.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(TestTag)
+
+BOOST_AUTO_TEST_CASE(SimpleTag)
+{
+  typedef ndn::SimpleTag<int, 3> MyTag;
+
+  BOOST_CHECK_EQUAL(MyTag::getTypeId(), 3);
+  MyTag tag(23361); // explicitly convertible from value type
+  int value = tag; // implicitly convertible to value type
+  BOOST_CHECK_EQUAL(value, 23361);
+  BOOST_CHECK_EQUAL(tag.get(), 23361);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestTag
+
+} // namespace tests
+} // namespace ndn