face: Implementation of encode/decode of LocalControlHeader

LocalControlHeader can only be used on faces that are derived from
LocalFace.  UnixStreamFace is directly inherited from LocalFace,
TCP face has two specializations: generic TcpFace (strictly not local),
and LocalTcpFace.

refs #1213

Change-Id: I8a158c3bc4bb929eedd15757cfddecc0d1049f9f
diff --git a/daemon/common.hpp b/daemon/common.hpp
index 76bbab5..af4a9fd 100644
--- a/daemon/common.hpp
+++ b/daemon/common.hpp
@@ -49,6 +49,7 @@
 using boost::enable_shared_from_this;
 using boost::make_shared;
 using boost::static_pointer_cast;
+using boost::dynamic_pointer_cast;
 using boost::function;
 using boost::bind;
 
diff --git a/daemon/core/logger.hpp b/daemon/core/logger.hpp
index 6732384..f8c6e8e 100644
--- a/daemon/core/logger.hpp
+++ b/daemon/core/logger.hpp
@@ -68,6 +68,10 @@
   template<> \
   nfd::Logger cls<specialization>::g_logger = nfd::Logger(name);
 
+#define NFD_LOG_INCLASS_2TEMPLATE_SPECIALIZATION_DEFINE(cls, s1, s2, name) \
+  template<> \
+  nfd::Logger cls<s1, s2>::g_logger = nfd::Logger(name);
+
 #define NFD_LOG_TRACE(expression) \
     if(g_logger.isEnabled(nfd::LOG_TRACE)) \
        std::cerr<<"TRACE: "<<"["<<g_logger<<"] " << expression << "\n"
diff --git a/daemon/face/face.cpp b/daemon/face/face.cpp
index 5ca8f29..cc4fba8 100644
--- a/daemon/face/face.cpp
+++ b/daemon/face/face.cpp
@@ -13,7 +13,6 @@
 
 Face::Face()
   : m_id(INVALID_FACEID)
-  , m_localControlHeaderFeatures(LOCAL_CONTROL_HEADER_FEATURE_MAX)
 {
 }
 
@@ -35,6 +34,20 @@
 }
 
 bool
+Face::isLocal() const
+{
+  return m_isLocal;
+}
+
+// this method is protected and can be used only in derived class
+// to set localhost scope
+void
+Face::setLocal(bool isLocal)
+{
+  m_isLocal = isLocal;
+}
+
+bool
 Face::isUp() const
 {
   return true;
@@ -58,21 +71,27 @@
   return false;
 }
 
-void
-Face::setLocalControlHeaderFeature(LocalControlHeaderFeature feature, bool enabled)
+bool
+Face::decodeAndDispatchInput(const Block& element)
 {
-  BOOST_ASSERT(feature > LOCAL_CONTROL_HEADER_FEATURE_ANY &&
-               feature < m_localControlHeaderFeatures.size());
-  m_localControlHeaderFeatures[feature] = enabled;
-  NFD_LOG_DEBUG("face" << this->getId() << " setLocalControlHeaderFeature " <<
-                (enabled ? "enable" : "disable") << " feature " << feature);
-
-  BOOST_STATIC_ASSERT(LOCAL_CONTROL_HEADER_FEATURE_ANY == 0);
-  m_localControlHeaderFeatures[LOCAL_CONTROL_HEADER_FEATURE_ANY] =
-    std::find(m_localControlHeaderFeatures.begin() + 1,
-              m_localControlHeaderFeatures.end(), true) <
-              m_localControlHeaderFeatures.end();
-  // 'find(..) < .end()' instead of 'find(..) != .end()' due to LLVM Bug 16816
+  /// \todo Ensure lazy field decoding process
+  
+  if (element.type() == tlv::Interest)
+    {
+      shared_ptr<Interest> i = make_shared<Interest>();
+      i->wireDecode(element);
+      this->onReceiveInterest(*i);
+    }
+  else if (element.type() == tlv::Data)
+    {
+      shared_ptr<Data> d = make_shared<Data>();
+      d->wireDecode(element);
+      this->onReceiveData(*d);
+    }
+  else
+    return false;
+  
+  return true;
 }
 
 } //namespace nfd
diff --git a/daemon/face/face.hpp b/daemon/face/face.hpp
index 924b4fa..f3c5d40 100644
--- a/daemon/face/face.hpp
+++ b/daemon/face/face.hpp
@@ -22,21 +22,6 @@
 const std::size_t MAX_NDN_PACKET_SIZE = 8800;
 
 
-/* \brief indicates a feature in LocalControlHeader
- */
-enum LocalControlHeaderFeature
-{
-  /// any feature
-  LOCAL_CONTROL_HEADER_FEATURE_ANY,
-  /// in-faceid
-  LOCAL_CONTROL_HEADER_FEATURE_IN_FACEID,
-  /// out-faceid
-  LOCAL_CONTROL_HEADER_FEATURE_NEXTHOP_FACEID,
-  /// upper bound of enum
-  LOCAL_CONTROL_HEADER_FEATURE_MAX
-};
-
-
 /** \brief represents a face
  */
 class Face : noncopyable, public enable_shared_from_this<Face>
@@ -75,22 +60,14 @@
   virtual void
   sendData(const Data& data) = 0;
 
-  /**
-   * \brief Close the face
+  /** \brief Close the face
    *
-   * This terminates all communication on the face and cause
-   * onFail() method event to be invoked
+   *  This terminates all communication on the face and cause
+   *  onFail() method event to be invoked
    */
   virtual void
   close() = 0;
 
-  /** \brief Get whether face is connected to a local app
-   *
-   *  In this base class this property is always false.
-   */
-  virtual bool
-  isLocal() const = 0;
-  
   /** \brief Get whether underlying communication is up
    *
    *  In this base class this property is always true.
@@ -98,6 +75,14 @@
   virtual bool
   isUp() const;
 
+  /** \brief Get whether face is connected to a local app
+   *
+   *  False by default and can become true if a derived class, implementing
+   *  one of the local face types, explicitly calls Face::setLocal(true)
+   */
+  bool
+  isLocal() const;
+  
   /** \brief Set the description
    *
    *  This is typically invoked by mgmt on set description command
@@ -116,22 +101,13 @@
   virtual bool
   isMultiAccess() const;
 
-  /** \brief get whether a LocalControlHeader feature is enabled
-   *
-   *  \param feature The feature. Cannot be LOCAL_CONTROL_HEADER_FEATURE_MAX
-   *  LOCAL_CONTROL_HEADER_FEATURE_ANY returns true if any feature is enabled.
-   */
-  bool
-  isLocalControlHeaderEnabled(LocalControlHeaderFeature feature =
-                              LOCAL_CONTROL_HEADER_FEATURE_ANY) const;
-
-  /** \brief enable or disable a LocalControlHeader feature
-   *
-   *  \param feature The feature. Cannot be LOCAL_CONTROL_HEADER_FEATURE_ANY
-   *                                     or LOCAL_CONTROL_HEADER_FEATURE_MAX
-   */
+protected:
   void
-  setLocalControlHeaderFeature(LocalControlHeaderFeature feature, bool enabled);
+  setLocal(bool isLocal);
+
+  // this is a non-virtual method
+  bool
+  decodeAndDispatchInput(const Block& element);
 
 private:
   void
@@ -140,19 +116,12 @@
 private:
   FaceId m_id;
   std::string m_description;
-  std::vector<bool> m_localControlHeaderFeatures;
-
+  bool m_isLocal; // for scoping purposes
+  
   // allow setting FaceId
   friend class Forwarder;
 };
 
-inline bool
-Face::isLocalControlHeaderEnabled(LocalControlHeaderFeature feature) const
-{
-  BOOST_ASSERT(feature < m_localControlHeaderFeatures.size());
-  return m_localControlHeaderFeatures[feature];
-}
-
 } // namespace nfd
 
 #endif // NFD_FACE_FACE_HPP
diff --git a/daemon/face/local-face.hpp b/daemon/face/local-face.hpp
new file mode 100644
index 0000000..6175b5d
--- /dev/null
+++ b/daemon/face/local-face.hpp
@@ -0,0 +1,179 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#ifndef NFD_FACE_LOCAL_FACE_HPP
+#define NFD_FACE_LOCAL_FACE_HPP
+
+#include "face.hpp"
+
+namespace nfd {
+
+/* \brief indicates a feature in LocalControlHeader
+ */
+enum LocalControlHeaderFeature
+{
+  /// any feature
+  LOCAL_CONTROL_HEADER_FEATURE_ANY,
+  /// in-faceid
+  LOCAL_CONTROL_HEADER_FEATURE_IN_FACEID,
+  /// out-faceid
+  LOCAL_CONTROL_HEADER_FEATURE_NEXTHOP_FACEID,
+  /// upper bound of enum
+  LOCAL_CONTROL_HEADER_FEATURE_MAX
+};
+
+
+/** \brief represents a face
+ */
+class LocalFace : public Face
+{
+public:
+  LocalFace();
+
+  /** \brief get whether a LocalControlHeader feature is enabled
+   *
+   *  \param feature The feature. Cannot be LOCAL_CONTROL_HEADER_FEATURE_MAX
+   *  LOCAL_CONTROL_HEADER_FEATURE_ANY returns true if any feature is enabled.
+   */
+  bool
+  isLocalControlHeaderEnabled(LocalControlHeaderFeature feature =
+                              LOCAL_CONTROL_HEADER_FEATURE_ANY) const;
+
+  /** \brief enable or disable a LocalControlHeader feature
+   *
+   *  \param feature The feature. Cannot be LOCAL_CONTROL_HEADER_FEATURE_ANY
+   *                                     or LOCAL_CONTROL_HEADER_FEATURE_MAX
+   */
+  void
+  setLocalControlHeaderFeature(LocalControlHeaderFeature feature, bool enabled = true);
+
+protected:
+  // statically overridden from Face
+
+  /** \brief Decode block into Interest/Data, considering potential LocalControlHeader
+   *
+   *  If LocalControlHeader is present, the encoded data is filtered out, based
+   *  on enabled features on the face.
+   */
+  bool
+  decodeAndDispatchInput(const Block& element);
+
+  // LocalFace-specific methods
+
+  /** \brief Check if LocalControlHeader needs to be included, taking into account
+   *         both set parameters in supplied LocalControlHeader and features
+   *         enabled on the local face.
+   */
+  bool
+  isEmptyFilteredLocalControlHeader(const ndn::nfd::LocalControlHeader& header) const;
+
+  /** \brief Create LocalControlHeader, considering enabled features
+   */
+  template<class Packet>
+  Block
+  filterAndEncodeLocalControlHeader(const Packet& packet);
+
+private:
+  std::vector<bool> m_localControlHeaderFeatures;
+};
+
+inline
+LocalFace::LocalFace()
+  : m_localControlHeaderFeatures(LOCAL_CONTROL_HEADER_FEATURE_MAX)
+{
+  setLocal(true);
+}
+
+inline bool
+LocalFace::isLocalControlHeaderEnabled(LocalControlHeaderFeature feature) const
+{
+  BOOST_ASSERT(feature < m_localControlHeaderFeatures.size());
+  return m_localControlHeaderFeatures[feature];
+}
+
+inline void
+LocalFace::setLocalControlHeaderFeature(LocalControlHeaderFeature feature, bool enabled/* = true*/)
+{
+  BOOST_ASSERT(feature > LOCAL_CONTROL_HEADER_FEATURE_ANY &&
+               feature < m_localControlHeaderFeatures.size());
+  m_localControlHeaderFeatures[feature] = enabled;
+
+  BOOST_STATIC_ASSERT(LOCAL_CONTROL_HEADER_FEATURE_ANY == 0);
+  m_localControlHeaderFeatures[LOCAL_CONTROL_HEADER_FEATURE_ANY] =
+    std::find(m_localControlHeaderFeatures.begin() + 1,
+              m_localControlHeaderFeatures.end(), true) <
+              m_localControlHeaderFeatures.end();
+  // 'find(..) < .end()' instead of 'find(..) != .end()' due to LLVM Bug 16816
+}
+
+inline bool
+LocalFace::decodeAndDispatchInput(const Block& element)
+{
+  const Block& payload = ndn::nfd::LocalControlHeader::getPayload(element);
+
+  // If received LocalControlHeader, but it is not enabled on the face
+  if ((&payload != &element) && !this->isLocalControlHeaderEnabled())
+    return false;
+
+  if (payload.type() == tlv::Interest)
+    {
+      shared_ptr<Interest> i = make_shared<Interest>();
+      i->wireDecode(payload);
+      if (&payload != &element)
+        {
+          i->getLocalControlHeader().wireDecode(element,
+            false,
+            this->isLocalControlHeaderEnabled(LOCAL_CONTROL_HEADER_FEATURE_NEXTHOP_FACEID));
+        }
+
+      this->onReceiveInterest(*i);
+    }
+  else if (payload.type() == tlv::Data)
+    {
+      shared_ptr<Data> d = make_shared<Data>();
+      d->wireDecode(payload);
+
+      /// \todo Uncomment and correct the following when we have more
+      ///       options in LocalControlHeader that apply for incoming
+      ///       Data packets (if ever)
+      // if (&payload != &element)
+      //   {
+      //
+      //     d->getLocalControlHeader().wireDecode(element,
+      //       false,
+      //       false);
+      //   }
+
+      this->onReceiveData(*d);
+    }
+  else
+    return false;
+
+  return true;
+}
+
+inline bool
+LocalFace::isEmptyFilteredLocalControlHeader(const ndn::nfd::LocalControlHeader& header) const
+{
+  if (!this->isLocalControlHeaderEnabled())
+    return true;
+
+  return header.empty(this->isLocalControlHeaderEnabled(LOCAL_CONTROL_HEADER_FEATURE_IN_FACEID),
+                      false);
+}
+
+template<class Packet>
+inline Block
+LocalFace::filterAndEncodeLocalControlHeader(const Packet& packet)
+{
+  return packet.getLocalControlHeader().wireEncode(packet,
+           this->isLocalControlHeaderEnabled(LOCAL_CONTROL_HEADER_FEATURE_IN_FACEID),
+           false);
+}
+
+} // namespace nfd
+
+#endif // NFD_FACE_LOCAL_FACE_HPP
diff --git a/daemon/face/stream-face.hpp b/daemon/face/stream-face.hpp
index ac08421..9baef1d 100644
--- a/daemon/face/stream-face.hpp
+++ b/daemon/face/stream-face.hpp
@@ -8,14 +8,18 @@
 #define NFD_FACE_STREAM_FACE_HPP
 
 #include "face.hpp"
+#include "local-face.hpp"
 
 namespace nfd {
 
-template <class T>
-class StreamFace : public Face
+// forward declaration
+template<class T, class U, class V> struct StreamFaceSenderImpl;
+
+template<class Protocol, class FaceBase = Face>
+class StreamFace : public FaceBase
 {
 public:
-  typedef T protocol;
+  typedef Protocol protocol;
 
   /**
    * \brief Create instance of StreamFace
@@ -37,6 +41,12 @@
 
 protected:
   void
+  processErrorCode(const boost::system::error_code& error);
+
+  void
+  handleSend(const boost::system::error_code& error,
+             const Block& header, const Block& payload);
+  void
   handleSend(const boost::system::error_code& error,
              const Block& wire);
 
@@ -56,159 +66,201 @@
 private:
   uint8_t m_inputBuffer[MAX_NDN_PACKET_SIZE];
   std::size_t m_inputBufferSize;
-  
+
+  friend struct StreamFaceSenderImpl<Protocol, FaceBase, Interest>;
+  friend struct StreamFaceSenderImpl<Protocol, FaceBase, Data>;
+
   NFD_LOG_INCLASS_DECLARE();
 };
 
 // All inherited classes must use
 // NFD_LOG_INCLASS_TEMPLATE_SPECIALIZATION_DEFINE(StreamFace, <specialization-parameter>, "Name");
 
-template <class T>
+
+/** \brief Class allowing validation of the StreamFace use
+ *
+ *  For example, partial specialization based on boost::asio::ip::tcp should check
+ *  that local endpoint is loopback
+ *
+ *  @throws Face::Error if validation failed
+ */
+template<class Protocol, class U>
+struct StreamFaceValidator
+{
+  static void
+  validateSocket(typename Protocol::socket& socket)
+  {
+  }
+};
+
+
+template<class T, class U>
 inline
-StreamFace<T>::StreamFace(const shared_ptr<typename StreamFace::protocol::socket>& socket)
+StreamFace<T, U>::StreamFace(const shared_ptr<typename StreamFace::protocol::socket>& socket)
   : m_socket(socket)
   , m_inputBufferSize(0)
 {
+  StreamFaceValidator<T, U>::validateSocket(*socket);
   m_socket->async_receive(boost::asio::buffer(m_inputBuffer, MAX_NDN_PACKET_SIZE), 0,
-                          bind(&StreamFace<T>::handleReceive, this, _1, _2));
+                          bind(&StreamFace<T, U>::handleReceive, this, _1, _2));
 }
 
-template <class T>
+template<class T, class U>
 inline
-StreamFace<T>::~StreamFace()
+StreamFace<T, U>::~StreamFace()
 {
 }
 
-
-template <class T>
-inline void
-StreamFace<T>::sendInterest(const Interest& interest)
+template<class Protocol, class FaceBase, class Packet>
+struct StreamFaceSenderImpl
 {
-  m_socket->async_send(boost::asio::buffer(interest.wireEncode().wire(),
-                                           interest.wireEncode().size()),
-                       bind(&StreamFace<T>::handleSend, this, _1, interest.wireEncode()));
-  
-  // anything else should be done here?
+  static void
+  send(StreamFace<Protocol, FaceBase>& face, const Packet& packet)
+  {
+    face.m_socket->async_send(boost::asio::buffer(packet.wireEncode().wire(),
+                                                  packet.wireEncode().size()),
+                              bind(&StreamFace<Protocol, FaceBase>::handleSend,
+                                   &face, _1, packet.wireEncode()));
+  }
+};
+
+// partial specialization (only classes can be partially specialized)
+template<class Protocol, class Packet>
+struct StreamFaceSenderImpl<Protocol, LocalFace, Packet>
+{
+  static void
+  send(StreamFace<Protocol, LocalFace>& face, const Packet& packet)
+  {
+    using namespace boost::asio;
+
+    if (face.isEmptyFilteredLocalControlHeader(packet.getLocalControlHeader()))
+      {
+        const Block& payload = packet.wireEncode();
+        face.m_socket->async_send(buffer(payload.wire(), payload.size()),
+                                  bind(&StreamFace<Protocol, LocalFace>::handleSend,
+                                       &face, _1, packet.wireEncode()));
+      }
+    else
+      {
+        Block header = face.filterAndEncodeLocalControlHeader(packet);
+        const Block& payload = packet.wireEncode();
+
+        std::vector<const_buffer> buffers;
+        buffers.reserve(2);
+        buffers.push_back(buffer(header.wire(),  header.size()));
+        buffers.push_back(buffer(payload.wire(), payload.size()));
+
+        face.m_socket->async_send(buffers,
+                                  bind(&StreamFace<Protocol, LocalFace>::handleSend,
+                                       &face, _1, header, payload));
+      }
+  }
+};
+
+
+template<class T, class U>
+inline void
+StreamFace<T, U>::sendInterest(const Interest& interest)
+{
+  StreamFaceSenderImpl<T, U, Interest>::send(*this, interest);
 }
 
-template <class T>
+template<class T, class U>
 inline void
-StreamFace<T>::sendData(const Data& data)
+StreamFace<T, U>::sendData(const Data& data)
 {
-  m_socket->async_send(boost::asio::buffer(data.wireEncode().wire(),
-                                           data.wireEncode().size()),
-                       bind(&StreamFace<T>::handleSend, this, _1, data.wireEncode()));
-  
-  // anything else should be done here?
+  StreamFaceSenderImpl<T, U, Data>::send(*this, data);
 }
 
-template <class T>
+template<class T, class U>
 inline void
-StreamFace<T>::close()
+StreamFace<T, U>::close()
 {
   if (!m_socket->is_open())
     return;
-  
+
   NFD_LOG_INFO("[id:" << this->getId()
                << ",endpoint:" << m_socket->local_endpoint()
                << "] Close connection");
 
   closeSocket();
-  onFail("Close connection");
+  this->onFail("Close connection");
 }
 
-template <class T>
+template<class T, class U>
 inline void
-StreamFace<T>::handleSend(const boost::system::error_code& error,
-                          const Block& wire)
+StreamFace<T, U>::processErrorCode(const boost::system::error_code& error)
 {
-  if (error) {
-    if (error == boost::system::errc::operation_canceled) // when socket is closed by someone
-      return;
-
-    if (!m_socket->is_open())
-      {
-        onFail("Connection closed");
-        return;
-      }
-
-    if (error == boost::asio::error::eof)
-      {
-        NFD_LOG_INFO("[id:" << this->getId()
-                     << ",endpoint:" << m_socket->local_endpoint()
-                     << "] Connection closed");
-      }
-    else
-      {
-        NFD_LOG_WARN("[id:" << this->getId()
-                     << ",endpoint:" << m_socket->local_endpoint()
-                     << "] Send operation failed, closing socket: "
-                     << error.category().message(error.value()));
-      }
-
-    closeSocket();
-
-    if (error == boost::asio::error::eof)
-      {
-        onFail("Connection closed");
-      }
-    else
-      {
-        onFail("Send operation failed, closing socket: " +
-               error.category().message(error.value()));
-      }
+  if (error == boost::system::errc::operation_canceled) // when socket is closed by someone
     return;
-  }
+
+  if (!m_socket->is_open())
+    {
+      this->onFail("Connection closed");
+      return;
+    }
+
+  if (error == boost::asio::error::eof)
+    {
+      NFD_LOG_INFO("[id:" << this->getId()
+                   << ",endpoint:" << m_socket->local_endpoint()
+                   << "] Connection closed");
+    }
+  else
+    {
+      NFD_LOG_WARN("[id:" << this->getId()
+                   << ",endpoint:" << m_socket->local_endpoint()
+                   << "] Send or receive operation failed, closing socket: "
+                   << error.category().message(error.value()));
+    }
+
+  closeSocket();
+
+  if (error == boost::asio::error::eof)
+    {
+      this->onFail("Connection closed");
+    }
+  else
+    {
+      this->onFail("Send or receive operation failed, closing socket: " +
+                   error.category().message(error.value()));
+    }
+}
+
+
+template<class T, class U>
+inline void
+StreamFace<T, U>::handleSend(const boost::system::error_code& error,
+                             const Block& wire)
+{
+  if (error)
+    return processErrorCode(error);
 
   NFD_LOG_TRACE("[id:" << this->getId()
                 << ",endpoint:" << m_socket->local_endpoint()
                 << "] Successfully sent: " << wire.size() << " bytes");
-  // do nothing (needed to retain validity of wire memory block
 }
 
-template <class T>
+template<class T, class U>
 inline void
-StreamFace<T>::handleReceive(const boost::system::error_code& error,
+StreamFace<T, U>::handleSend(const boost::system::error_code& error,
+                             const Block& header, const Block& payload)
+{
+  if (error)
+    return processErrorCode(error);
+
+  NFD_LOG_TRACE("[id:" << this->getId()
+                << ",endpoint:" << m_socket->local_endpoint()
+                << "] Successfully sent: " << (header.size()+payload.size()) << " bytes");
+}
+
+template<class T, class U>
+inline void
+StreamFace<T, U>::handleReceive(const boost::system::error_code& error,
                              std::size_t bytes_recvd)
 {
-  if (error || bytes_recvd == 0) {
-    if (error == boost::system::errc::operation_canceled) // when socket is closed by someone
-      return;
-
-    // this should be unnecessary, but just in case
-    if (!m_socket->is_open())
-      {
-        onFail("Connection closed");
-        return;
-      }
-
-    if (error == boost::asio::error::eof)
-      {
-        NFD_LOG_INFO("[id:" << this->getId()
-                     << ",endpoint:" << m_socket->local_endpoint()
-                     << "] Connection closed");
-      }
-    else
-      {
-        NFD_LOG_WARN("[id:" << this->getId()
-                     << ",endpoint:" << m_socket->local_endpoint()
-                     << "] Receive operation failed: "
-                     << error.category().message(error.value()));
-      }
-
-    closeSocket();
-
-    if (error == boost::asio::error::eof)
-      {
-        onFail("Connection closed");
-      }
-    else
-      {
-        onFail("Receive operation failed, closing socket: " +
-               error.category().message(error.value()));
-      }
-    return;
-  }
+  if (error)
+    return processErrorCode(error);
 
   NFD_LOG_TRACE("[id:" << this->getId()
                 << ",endpoint:" << m_socket->local_endpoint()
@@ -226,27 +278,8 @@
         offset += element.size();
 
         BOOST_ASSERT(offset <= m_inputBufferSize);
-        
-        /// @todo Ensure lazy field decoding process
-        if (element.type() == tlv::Interest)
-          {
-            shared_ptr<Interest> i = make_shared<Interest>();
-            i->wireDecode(element);
-            onReceiveInterest(*i);
-          }
-        else if (element.type() == tlv::Data)
-          {
-            shared_ptr<Data> d = make_shared<Data>();
-            d->wireDecode(element);
-            onReceiveData(*d);
-          }
-        // @todo Add local header support
-        // else if (element.type() == tlv::LocalHeader)
-        //   {
-        //     shared_ptr<Interest> i = make_shared<Interest>();
-        //     i->wireDecode(element);
-        //   }
-        else
+
+        if (!this->decodeAndDispatchInput(element))
           {
             NFD_LOG_WARN("[id:" << this->getId()
                          << ",endpoint:" << m_socket->local_endpoint()
@@ -265,7 +298,7 @@
                      << "closing down the face");
 
         closeSocket();
-        onFail("Received input is invalid or too large to process, closing down the face");
+        this->onFail("Received input is invalid or too large to process, closing down the face");
         return;
       }
   }
@@ -286,18 +319,18 @@
 
   m_socket->async_receive(boost::asio::buffer(m_inputBuffer + m_inputBufferSize,
                                               MAX_NDN_PACKET_SIZE - m_inputBufferSize), 0,
-                          bind(&StreamFace<T>::handleReceive, this, _1, _2));
+                          bind(&StreamFace<T, U>::handleReceive, this, _1, _2));
 }
 
-template <class T>
+template<class T, class U>
 inline void
-StreamFace<T>::keepFaceAliveUntilAllHandlersExecuted(const shared_ptr<Face>& face)
+StreamFace<T, U>::keepFaceAliveUntilAllHandlersExecuted(const shared_ptr<Face>& face)
 {
 }
 
-template <class T>
+template<class T, class U>
 inline void
-StreamFace<T>::closeSocket()
+StreamFace<T, U>::closeSocket()
 {
   boost::asio::io_service& io = m_socket->get_io_service();
 
@@ -309,7 +342,7 @@
 
   // ensure that the Face object is alive at least until all pending
   // handlers are dispatched
-  io.post(bind(&StreamFace<T>::keepFaceAliveUntilAllHandlersExecuted,
+  io.post(bind(&StreamFace<T, U>::keepFaceAliveUntilAllHandlersExecuted,
                this, this->shared_from_this()));
 }
 
diff --git a/daemon/face/tcp-channel.cpp b/daemon/face/tcp-channel.cpp
index 087a24c..34b734a 100644
--- a/daemon/face/tcp-channel.cpp
+++ b/daemon/face/tcp-channel.cpp
@@ -123,7 +123,12 @@
 {
   tcp::Endpoint remoteEndpoint = socket->remote_endpoint();
 
-  shared_ptr<TcpFace> face = make_shared<TcpFace>(boost::cref(socket));
+  shared_ptr<Face> face;
+  if (socket->local_endpoint().address().is_loopback())
+    face = make_shared<TcpLocalFace>(boost::cref(socket));
+  else
+    face = make_shared<TcpFace>(boost::cref(socket));
+  
   face->onFail += bind(&TcpChannel::afterFaceFailed, this, remoteEndpoint);
 
   onFaceCreated(face);
diff --git a/daemon/face/tcp-channel.hpp b/daemon/face/tcp-channel.hpp
index 4cca5c5..db6521f 100644
--- a/daemon/face/tcp-channel.hpp
+++ b/daemon/face/tcp-channel.hpp
@@ -32,7 +32,7 @@
    *        (as a response to incoming connection or after connection
    *        is established)
    */
-  typedef function<void(const shared_ptr<TcpFace>& newFace)> FaceCreatedCallback;
+  typedef function<void(const shared_ptr<Face>& newFace)> FaceCreatedCallback;
 
   /**
    * \brief Prototype for the callback that is called when face is failed to
@@ -135,7 +135,7 @@
   boost::asio::io_service& m_ioService;
   tcp::Endpoint m_localEndpoint;
 
-  typedef std::map< tcp::Endpoint, shared_ptr<TcpFace> > ChannelFaceMap;
+  typedef std::map< tcp::Endpoint, shared_ptr<Face> > ChannelFaceMap;
   ChannelFaceMap m_channelFaces;
 
   bool isListening;
diff --git a/daemon/face/tcp-face.cpp b/daemon/face/tcp-face.cpp
index 874de98..4f8cbab 100644
--- a/daemon/face/tcp-face.cpp
+++ b/daemon/face/tcp-face.cpp
@@ -13,15 +13,19 @@
 
 NFD_LOG_INCLASS_TEMPLATE_SPECIALIZATION_DEFINE(StreamFace, TcpFace::protocol, "TcpFace");
 
+NFD_LOG_INCLASS_2TEMPLATE_SPECIALIZATION_DEFINE(StreamFace,
+                                                TcpLocalFace::protocol, LocalFace, "TcpLocalFace");
+
 TcpFace::TcpFace(const shared_ptr<TcpFace::protocol::socket>& socket)
   : StreamFace<protocol>(socket)
 {
 }
 
-bool
-TcpFace::isLocal() const
+//
+
+TcpLocalFace::TcpLocalFace(const shared_ptr<TcpLocalFace::protocol::socket>& socket)
+  : StreamFace<protocol, LocalFace>(socket)
 {
-  return false;
 }
 
 
diff --git a/daemon/face/tcp-face.hpp b/daemon/face/tcp-face.hpp
index c41f601..dfd6028 100644
--- a/daemon/face/tcp-face.hpp
+++ b/daemon/face/tcp-face.hpp
@@ -23,11 +23,45 @@
 
   explicit
   TcpFace(const shared_ptr<protocol::socket>& socket);
-
-  virtual bool
-  isLocal() const;
 };
 
+//
+
+/** \brief Class validating use of TcpLocalFace
+ */
+template<>
+struct StreamFaceValidator<boost::asio::ip::tcp, LocalFace>
+{
+  /** Check that local endpoint is loopback
+   *
+   *  @throws Face::Error if validation failed
+   */
+  static void
+  validateSocket(boost::asio::ip::tcp::socket& socket)
+  {
+    if (!socket.local_endpoint().address().is_loopback() ||
+        !socket.remote_endpoint().address().is_loopback())
+      {
+        throw Face::Error("TcpLocalFace can be created only on loopback interface");
+      }
+  }
+};
+
+/**
+ * \brief Implementation of Face abstraction that uses TCP
+ *        as underlying transport mechanism and is used for
+ *        local communication (can enable LocalControlHeader)
+ */
+class TcpLocalFace : public StreamFace<boost::asio::ip::tcp, LocalFace>
+{
+public:
+  typedef boost::asio::ip::tcp protocol;
+
+  explicit
+  TcpLocalFace(const shared_ptr<protocol::socket>& socket);
+};
+
+
 } // namespace nfd
 
 #endif // NFD_FACE_TCP_FACE_HPP
diff --git a/daemon/face/unix-stream-face.cpp b/daemon/face/unix-stream-face.cpp
index de7c4f9..27ba693 100644
--- a/daemon/face/unix-stream-face.cpp
+++ b/daemon/face/unix-stream-face.cpp
@@ -11,17 +11,13 @@
 // The whole purpose of this file is to specialize the logger,
 // otherwise, everything could be put into the header file.
 
-NFD_LOG_INCLASS_TEMPLATE_SPECIALIZATION_DEFINE(StreamFace, UnixStreamFace::protocol, "UnixStreamFace");
+NFD_LOG_INCLASS_2TEMPLATE_SPECIALIZATION_DEFINE(StreamFace,
+                                                UnixStreamFace::protocol, LocalFace,
+                                                "UnixStreamFace");
 
 UnixStreamFace::UnixStreamFace(const shared_ptr<UnixStreamFace::protocol::socket>& socket)
-  : StreamFace<protocol>(socket)
+  : StreamFace<protocol, LocalFace>(socket)
 {
 }
 
-bool
-UnixStreamFace::isLocal() const
-{
-  return true;
-}
-
 } // namespace nfd
diff --git a/daemon/face/unix-stream-face.hpp b/daemon/face/unix-stream-face.hpp
index cc97c21..8492f54 100644
--- a/daemon/face/unix-stream-face.hpp
+++ b/daemon/face/unix-stream-face.hpp
@@ -20,20 +20,13 @@
  * \brief Implementation of Face abstraction that uses stream-oriented
  *        Unix domain sockets as underlying transport mechanism
  */
-class UnixStreamFace : public StreamFace<boost::asio::local::stream_protocol>
+class UnixStreamFace : public StreamFace<boost::asio::local::stream_protocol, LocalFace>
 {
 public:
   typedef boost::asio::local::stream_protocol protocol;
 
   explicit
   UnixStreamFace(const shared_ptr<protocol::socket>& socket);
-
-  /** \brief Get whether face is connected to a local app
-   *
-   *  Always true for a UnixStreamFace.
-   */
-  virtual bool
-  isLocal() const;
 };
 
 } // namespace nfd
diff --git a/daemon/mgmt/internal-face.cpp b/daemon/mgmt/internal-face.cpp
index 1f1a6cb..707ee30 100644
--- a/daemon/mgmt/internal-face.cpp
+++ b/daemon/mgmt/internal-face.cpp
@@ -12,7 +12,7 @@
 
 InternalFace::InternalFace()
 {
-
+  setLocal(true);
 }
 
 void
@@ -98,12 +98,6 @@
   throw Error("Internal face cannot be closed");
 }
 
-bool
-InternalFace::isLocal() const
-{
-  return true;
-}
-
 void
 InternalFace::setInterestFilter(const Name& filter,
                                 OnInterest onInterest)
diff --git a/daemon/mgmt/internal-face.hpp b/daemon/mgmt/internal-face.hpp
index 0e17b3b..8d5805c 100644
--- a/daemon/mgmt/internal-face.hpp
+++ b/daemon/mgmt/internal-face.hpp
@@ -36,13 +36,6 @@
   virtual void
   close();
 
-  /** \brief Get whether face is connected to a local app
-   *
-   *  Always true for a InternalFace.
-   */
-  virtual bool
-  isLocal() const;
-
   // Methods implementing AppFace interface. Do not invoke from forwarder.
 
   virtual void
diff --git a/daemon/mgmt/local-control-header-manager.cpp b/daemon/mgmt/local-control-header-manager.cpp
index 6a0f056..5717f54 100644
--- a/daemon/mgmt/local-control-header-manager.cpp
+++ b/daemon/mgmt/local-control-header-manager.cpp
@@ -5,6 +5,7 @@
  */
 
 #include "local-control-header-manager.hpp"
+#include "face/local-face.hpp"
 
 namespace nfd {
 
@@ -58,7 +59,15 @@
       return;
     }
 
-  shared_ptr<Face> face = m_getFace(request.getIncomingFaceId());
+  shared_ptr<LocalFace> face =
+    dynamic_pointer_cast<LocalFace>(m_getFace(request.getIncomingFaceId()));
+
+  if (!static_cast<bool>(face))
+    {
+      NFD_LOG_INFO("command result: request to enable control header on non-local face");
+      sendResponse(command, 400, "Command not supported on the requested face");
+      return;
+    }
 
   const Name::Component& module = command.get(COMMAND_PREFIX.size());
   const Name::Component& verb = command.get(COMMAND_PREFIX.size() + 1);
diff --git a/daemon/mgmt/local-control-header-manager.hpp b/daemon/mgmt/local-control-header-manager.hpp
index ab3c08a..8c03e4e 100644
--- a/daemon/mgmt/local-control-header-manager.hpp
+++ b/daemon/mgmt/local-control-header-manager.hpp
@@ -23,7 +23,6 @@
   void
   onLocalControlHeaderRequest(const Interest& request);
 
-
 private:
   function<shared_ptr<Face>(FaceId)> m_getFace;
 
diff --git a/tests/face/dummy-face.hpp b/tests/face/dummy-face.hpp
index 7de128d..92100b6 100644
--- a/tests/face/dummy-face.hpp
+++ b/tests/face/dummy-face.hpp
@@ -8,6 +8,7 @@
 #define NFD_TEST_FACE_DUMMY_FACE_HPP
 
 #include "face/face.hpp"
+#include "face/local-face.hpp"
 
 namespace nfd {
 
@@ -15,15 +16,10 @@
  *  \brief provides a Face that cannot communicate
  *  for unit testing only
  */
-class DummyFace : public Face
+template<class FaceBase>
+class DummyFaceImpl : public FaceBase
 {
 public:
-  explicit
-  DummyFace(bool isLocal = false)
-    : m_isLocal(isLocal)
-  {
-  }
-  
   virtual void
   sendInterest(const Interest& interest)
   {
@@ -39,16 +35,12 @@
   {
   }
 
-  virtual bool
-  isLocal() const
-  {
-    return m_isLocal;
-  }
-
 private:
-  bool m_isLocal;
 };
 
+typedef DummyFaceImpl<Face> DummyFace;
+typedef DummyFaceImpl<LocalFace> DummyLocalFace;
+
 } // namespace nfd
 
 #endif // TEST_FACE_DUMMY_FACE_HPP
diff --git a/tests/face/face.cpp b/tests/face/face.cpp
index 07e3fb4..3fb4ea4 100644
--- a/tests/face/face.cpp
+++ b/tests/face/face.cpp
@@ -5,6 +5,7 @@
  */
 
 #include "face/face.hpp"
+#include "face/local-face.hpp"
 #include "dummy-face.hpp"
 
 #include <boost/test/unit_test.hpp>
@@ -22,7 +23,7 @@
 
 BOOST_AUTO_TEST_CASE(LocalControlHeaderEnabled)
 {
-  DummyFace face;
+  DummyLocalFace face;
   
   BOOST_CHECK_EQUAL(face.isLocalControlHeaderEnabled(), false);
   
diff --git a/tests/face/tcp.cpp b/tests/face/tcp.cpp
index d64d1ce..8014f52 100644
--- a/tests/face/tcp.cpp
+++ b/tests/face/tcp.cpp
@@ -32,7 +32,7 @@
 {
 public:
   void
-  channel1_onFaceCreated(const shared_ptr<TcpFace>& newFace)
+  channel1_onFaceCreated(const shared_ptr<Face>& newFace)
   {
     BOOST_CHECK(!static_cast<bool>(m_face1));
     m_face1 = newFace;
@@ -78,7 +78,7 @@
   }
 
   void
-  channel2_onFaceCreated(const shared_ptr<TcpFace>& newFace)
+  channel2_onFaceCreated(const shared_ptr<Face>& newFace)
   {
     BOOST_CHECK(!static_cast<bool>(m_face2));
     m_face2 = newFace;
@@ -124,7 +124,7 @@
   }
 
   void
-  channel_onFaceCreated(const shared_ptr<TcpFace>& newFace)
+  channel_onFaceCreated(const shared_ptr<Face>& newFace)
   {
     m_faces.push_back(newFace);
     this->afterIo();
@@ -163,14 +163,14 @@
 
   int m_ioRemaining;
 
-  shared_ptr<TcpFace> m_face1;
+  shared_ptr<Face> m_face1;
   std::vector<Interest> m_face1_receivedInterests;
   std::vector<Data> m_face1_receivedDatas;
-  shared_ptr<TcpFace> m_face2;
+  shared_ptr<Face> m_face2;
   std::vector<Interest> m_face2_receivedInterests;
   std::vector<Data> m_face2_receivedDatas;
 
-  std::list< shared_ptr<TcpFace> > m_faces;
+  std::list< shared_ptr<Face> > m_faces;
 };
 
 
@@ -202,7 +202,15 @@
 
   BOOST_REQUIRE(static_cast<bool>(m_face1));
   BOOST_REQUIRE(static_cast<bool>(m_face2));
-    
+
+  BOOST_CHECK_EQUAL(m_face1->isLocal(), true);
+  BOOST_CHECK_EQUAL(m_face2->isLocal(), true);
+
+  BOOST_CHECK_EQUAL(static_cast<bool>(dynamic_pointer_cast<LocalFace>(m_face1)), true);
+  BOOST_CHECK_EQUAL(static_cast<bool>(dynamic_pointer_cast<LocalFace>(m_face2)), true);
+
+  // integrated tests needs to check that TcpFace for non-loopback fails these tests...
+  
   abortEvent =
     scheduler.scheduleEvent(time::seconds(1),
                             bind(&EndToEndFixture::abortTestCase, this,
diff --git a/tests/face/unix-stream.cpp b/tests/face/unix-stream.cpp
index fee5827..9d6a6cf 100644
--- a/tests/face/unix-stream.cpp
+++ b/tests/face/unix-stream.cpp
@@ -61,7 +61,7 @@
 
     this->afterIo();
   }
-  
+
   void
   face1_onReceiveInterest(const Interest& interest)
   {
@@ -69,7 +69,7 @@
 
     this->afterIo();
   }
-  
+
   void
   face1_onReceiveData(const Data& data)
   {
@@ -85,7 +85,7 @@
 
     this->afterIo();
   }
-  
+
   void
   face2_onReceiveData(const Data& data)
   {
@@ -310,6 +310,138 @@
   BOOST_CHECK_EQUAL(m_face2_receivedDatas    [0].getName(), data1.getName());
 }
 
+static inline void
+noOp()
+{
+}
+
+BOOST_FIXTURE_TEST_CASE(UnixStreamFaceLocalControlHeader, EndToEndFixture)
+{
+  UnixStreamChannelFactory factory(m_ioService);
+  Scheduler scheduler(m_ioService); // to limit the amount of time the test may take
+
+  EventId abortEvent =
+    scheduler.scheduleEvent(time::seconds(1),
+                            bind(&EndToEndFixture::abortTestCase, this,
+                                 "UnixStreamChannel error: cannot connect or cannot accept connection"));
+
+  shared_ptr<UnixStreamChannel> channel1 = factory.create("foo");
+  channel1->listen(bind(&EndToEndFixture::channel1_onFaceCreated,   this, _1),
+                   bind(&EndToEndFixture::channel1_onConnectFailed, this, _1));
+
+  shared_ptr<stream_protocol::socket> client =
+      make_shared<stream_protocol::socket>(boost::ref(m_ioService));
+  client->async_connect(stream_protocol::endpoint("foo"),
+                        bind(&EndToEndFixture::client_onConnect, this, _1));
+
+  m_ioRemaining = 2;
+  m_ioService.run();
+  m_ioService.reset();
+  scheduler.cancelEvent(abortEvent);
+
+  BOOST_REQUIRE(static_cast<bool>(m_face1));
+
+  abortEvent =
+    scheduler.scheduleEvent(time::seconds(1),
+                            bind(&EndToEndFixture::abortTestCase, this,
+                                 "UnixStreamChannel error: cannot send or receive Interest/Data packets"));
+
+  m_face2 = make_shared<UnixStreamFace>(client);
+  m_face2->onReceiveInterest +=
+    bind(&EndToEndFixture::face2_onReceiveInterest, this, _1);
+  m_face2->onReceiveData +=
+    bind(&EndToEndFixture::face2_onReceiveData, this, _1);
+
+  Interest interest1("ndn:/TpnzGvW9R");
+  Data     data1    ("ndn:/KfczhUqVix");
+  data1.setContent(0, 0);
+  Interest interest2("ndn:/QWiIMfj5sL");
+  Data     data2    ("ndn:/XNBV796f");
+  data2.setContent(0, 0);
+
+  ndn::SignatureSha256WithRsa fakeSignature;
+  fakeSignature.setValue(ndn::dataBlock(tlv::SignatureValue, reinterpret_cast<const uint8_t*>(0), 0));
+
+  // set fake signature on data1 and data2
+  data1.setSignature(fakeSignature);
+  data2.setSignature(fakeSignature);
+
+  m_face1->setLocalControlHeaderFeature(LOCAL_CONTROL_HEADER_FEATURE_IN_FACEID);
+  m_face1->setLocalControlHeaderFeature(LOCAL_CONTROL_HEADER_FEATURE_NEXTHOP_FACEID);
+
+  BOOST_CHECK(m_face1->isLocalControlHeaderEnabled(LOCAL_CONTROL_HEADER_FEATURE_IN_FACEID));
+  BOOST_CHECK(m_face1->isLocalControlHeaderEnabled(LOCAL_CONTROL_HEADER_FEATURE_NEXTHOP_FACEID));
+
+  m_face2->setLocalControlHeaderFeature(LOCAL_CONTROL_HEADER_FEATURE_IN_FACEID);
+  m_face2->setLocalControlHeaderFeature(LOCAL_CONTROL_HEADER_FEATURE_NEXTHOP_FACEID);
+
+  BOOST_CHECK(m_face2->isLocalControlHeaderEnabled(LOCAL_CONTROL_HEADER_FEATURE_IN_FACEID));
+  BOOST_CHECK(m_face2->isLocalControlHeaderEnabled(LOCAL_CONTROL_HEADER_FEATURE_NEXTHOP_FACEID));
+
+  ////////////////////////////////////////////////////////
+
+  interest1.setIncomingFaceId(11);
+  interest1.setNextHopFaceId(111);
+
+  m_face1->sendInterest(interest1);
+
+  data1.setIncomingFaceId(22);
+  data1.getLocalControlHeader().setNextHopFaceId(222);
+
+  m_face1->sendData    (data1);
+
+  //
+
+  m_ioRemaining = 2;
+  m_ioService.run();
+  m_ioService.reset();
+
+  BOOST_REQUIRE_EQUAL(m_face2_receivedInterests.size(), 1);
+  BOOST_REQUIRE_EQUAL(m_face2_receivedDatas    .size(), 1);
+
+  // sending allows only IncomingFaceId, receiving allows only NextHopFaceId
+  BOOST_CHECK_EQUAL(m_face2_receivedInterests[0].getLocalControlHeader().hasIncomingFaceId(), false);
+  BOOST_CHECK_EQUAL(m_face2_receivedInterests[0].getLocalControlHeader().hasNextHopFaceId(), false);
+
+  BOOST_CHECK_EQUAL(m_face2_receivedDatas[0].getLocalControlHeader().hasIncomingFaceId(), false);
+  BOOST_CHECK_EQUAL(m_face2_receivedDatas[0].getLocalControlHeader().hasNextHopFaceId(), false);
+
+  ////////////////////////////////////////////////////////
+
+  using namespace boost::asio;
+
+  std::vector<const_buffer> interestWithHeader;
+  Block iHeader  = interest1.getLocalControlHeader().wireEncode(interest1, true, true);
+  Block iPayload = interest1.wireEncode();
+  interestWithHeader.push_back(buffer(iHeader.wire(),  iHeader.size()));
+  interestWithHeader.push_back(buffer(iPayload.wire(), iPayload.size()));
+
+  std::vector<const_buffer> dataWithHeader;
+  Block dHeader  = data1.getLocalControlHeader().wireEncode(data1, true, true);
+  Block dPayload = data1.wireEncode();
+  dataWithHeader.push_back(buffer(dHeader.wire(),  dHeader.size()));
+  dataWithHeader.push_back(buffer(dPayload.wire(), dPayload.size()));
+
+  //
+
+  client->async_send(interestWithHeader, bind(&noOp));
+  client->async_send(dataWithHeader, bind(&noOp));
+
+  m_ioRemaining = 2;
+  m_ioService.run();
+  m_ioService.reset();
+
+  BOOST_REQUIRE_EQUAL(m_face1_receivedInterests.size(), 1);
+  BOOST_REQUIRE_EQUAL(m_face1_receivedDatas    .size(), 1);
+
+  BOOST_CHECK_EQUAL(m_face1_receivedInterests[0].getLocalControlHeader().hasIncomingFaceId(), false);
+  BOOST_CHECK_EQUAL(m_face1_receivedInterests[0].getLocalControlHeader().hasNextHopFaceId(), true);
+  BOOST_CHECK_EQUAL(m_face1_receivedInterests[0].getNextHopFaceId(), 111);
+
+  BOOST_CHECK_EQUAL(m_face1_receivedDatas[0].getLocalControlHeader().hasIncomingFaceId(), false);
+  BOOST_CHECK_EQUAL(m_face1_receivedDatas[0].getLocalControlHeader().hasNextHopFaceId(), false);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace nfd
diff --git a/tests/fw/forwarder.cpp b/tests/fw/forwarder.cpp
index ed8f6a7..e066f52 100644
--- a/tests/fw/forwarder.cpp
+++ b/tests/fw/forwarder.cpp
@@ -38,12 +38,6 @@
   {
   }
 
-  virtual bool
-  isLocal() const
-  {
-    return false;
-  }
-
   void
   receiveInterest(const Interest& interest)
   {
@@ -129,25 +123,6 @@
   BOOST_CHECK_EQUAL(face1->m_sentDatas[0].getIncomingFaceId(), face2->getId());
 }
 
-
-class ForwarderTestLocalFace : public DummyFace {
-public:
-  explicit
-  ForwarderTestLocalFace(bool isLocal)
-    : m_isLocal(isLocal)
-  {
-  }
-
-  virtual bool
-  isLocal() const
-  {
-    return m_isLocal;
-  }
-
-private:
-  bool m_isLocal;
-};
-
 class ScopeLocalhostTestForwarder : public Forwarder
 {
 public:
@@ -182,8 +157,8 @@
 {
   boost::asio::io_service io;
   ScopeLocalhostTestForwarder forwarder(io);
-  shared_ptr<ForwarderTestLocalFace> face1 = make_shared<ForwarderTestLocalFace>(true);
-  shared_ptr<ForwarderTestLocalFace> face2 = make_shared<ForwarderTestLocalFace>(false);
+  shared_ptr<DummyLocalFace> face1 = make_shared<DummyLocalFace>();
+  shared_ptr<DummyFace>      face2 = make_shared<DummyFace>();
   forwarder.addFace(face1);
   forwarder.addFace(face2);
   
diff --git a/tests/mgmt/local-control-header-manager.cpp b/tests/mgmt/local-control-header-manager.cpp
index b9c1d32..b7fdf58 100644
--- a/tests/mgmt/local-control-header-manager.cpp
+++ b/tests/mgmt/local-control-header-manager.cpp
@@ -6,6 +6,7 @@
 
 #include "mgmt/local-control-header-manager.hpp"
 #include "face/face.hpp"
+#include "face/local-face.hpp"
 #include "mgmt/internal-face.hpp"
 #include "../face/dummy-face.hpp"
 
@@ -94,7 +95,7 @@
 
 BOOST_FIXTURE_TEST_CASE(InFaceId, LocalControlHeaderManagerFixture)
 {
-  shared_ptr<Face> dummy = make_shared<DummyFace>();
+  shared_ptr<LocalFace> dummy = make_shared<DummyLocalFace>();
   addFace(dummy);
 
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
@@ -136,7 +137,7 @@
 
 BOOST_FIXTURE_TEST_CASE(NextHopFaceId, LocalControlHeaderManagerFixture)
 {
-  shared_ptr<Face> dummy = make_shared<DummyFace>();
+  shared_ptr<LocalFace> dummy = make_shared<DummyLocalFace>();
   addFace(dummy);
 
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
@@ -179,7 +180,7 @@
 
 BOOST_FIXTURE_TEST_CASE(ShortCommand, LocalControlHeaderManagerFixture)
 {
-  shared_ptr<Face> dummy = make_shared<DummyFace>();
+  shared_ptr<LocalFace> dummy = make_shared<DummyLocalFace>();
   addFace(dummy);
 
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
@@ -204,7 +205,7 @@
 
 BOOST_FIXTURE_TEST_CASE(ShortCommandModule, LocalControlHeaderManagerFixture)
 {
-  shared_ptr<Face> dummy = make_shared<DummyFace>();
+  shared_ptr<LocalFace> dummy = make_shared<DummyLocalFace>();
   addFace(dummy);
 
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
@@ -229,7 +230,7 @@
 
 BOOST_FIXTURE_TEST_CASE(UnsupportedModule, LocalControlHeaderManagerFixture)
 {
-  shared_ptr<Face> dummy = make_shared<DummyFace>();
+  shared_ptr<LocalFace> dummy = make_shared<DummyLocalFace>();
   addFace(dummy);
 
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
@@ -254,7 +255,7 @@
 
 BOOST_FIXTURE_TEST_CASE(InFaceIdUnsupportedVerb, LocalControlHeaderManagerFixture)
 {
-  shared_ptr<Face> dummy = make_shared<DummyFace>();
+  shared_ptr<LocalFace> dummy = make_shared<DummyLocalFace>();
   addFace(dummy);
 
   shared_ptr<InternalFace> face(make_shared<InternalFace>());
@@ -279,7 +280,7 @@
 
 BOOST_FIXTURE_TEST_CASE(NextHopFaceIdUnsupportedVerb, LocalControlHeaderManagerFixture)
 {
-  shared_ptr<Face> dummy = make_shared<DummyFace>();
+  shared_ptr<LocalFace> dummy = make_shared<DummyLocalFace>();
   addFace(dummy);
 
   shared_ptr<InternalFace> face(make_shared<InternalFace>());