face: WebSocketTransport

Change-Id: I28db9cd6f828e4a9fe5895bb7a6152cca5baa9fc
Refs: #3169, #3160
diff --git a/daemon/face/websocket-channel.cpp b/daemon/face/websocket-channel.cpp
index 7997b1e..520bb7d 100644
--- a/daemon/face/websocket-channel.cpp
+++ b/daemon/face/websocket-channel.cpp
@@ -24,10 +24,9 @@
  */
 
 #include "websocket-channel.hpp"
+#include "generic-link-service.hpp"
+#include "websocket-transport.hpp"
 #include "core/global-io.hpp"
-#include "core/scheduler.hpp"
-
-#include <boost/date_time/posix_time/posix_time.hpp>
 
 namespace nfd {
 
@@ -35,23 +34,23 @@
 
 WebSocketChannel::WebSocketChannel(const websocket::Endpoint& localEndpoint)
   : m_localEndpoint(localEndpoint)
-  , m_isListening(false)
   , m_pingInterval(10000)
 {
   setUri(FaceUri(m_localEndpoint, "ws"));
 
-  // Setup WebSocket server
+  // Be quiet
   m_server.clear_access_channels(websocketpp::log::alevel::all);
-  m_server.clear_error_channels(websocketpp::log::alevel::all);
+  m_server.clear_error_channels(websocketpp::log::elevel::all);
 
-  m_server.set_message_handler(bind(&WebSocketChannel::handleMessage, this, _1, _2));
+  // Setup WebSocket server
+  m_server.init_asio(&getGlobalIoService());
   m_server.set_open_handler(bind(&WebSocketChannel::handleOpen, this, _1));
   m_server.set_close_handler(bind(&WebSocketChannel::handleClose, this, _1));
-  m_server.init_asio(&getGlobalIoService());
+  m_server.set_message_handler(bind(&WebSocketChannel::handleMessage, this, _1, _2));
 
   // Detect disconnections using ping-pong messages
-  m_server.set_pong_handler(bind(&WebSocketChannel::handlePong, this, _1, _2));
-  m_server.set_pong_timeout_handler(bind(&WebSocketChannel::handlePongTimeout, this, _1, _2));
+  m_server.set_pong_handler(bind(&WebSocketChannel::handlePong, this, _1));
+  m_server.set_pong_timeout_handler(bind(&WebSocketChannel::handlePongTimeout, this, _1));
 
   // Always set SO_REUSEADDR flag
   m_server.set_reuse_addr(true);
@@ -60,32 +59,40 @@
 void
 WebSocketChannel::setPingInterval(time::milliseconds interval)
 {
+  BOOST_ASSERT(!m_server.is_listening());
+
   m_pingInterval = interval;
 }
 
 void
 WebSocketChannel::setPongTimeout(time::milliseconds timeout)
 {
+  BOOST_ASSERT(!m_server.is_listening());
+
   m_server.set_pong_timeout(static_cast<long>(timeout.count()));
 }
 
 void
-WebSocketChannel::handlePongTimeout(websocketpp::connection_hdl hdl, std::string msg)
+WebSocketChannel::handlePongTimeout(websocketpp::connection_hdl hdl)
 {
   auto it = m_channelFaces.find(hdl);
   if (it != m_channelFaces.end()) {
-    NFD_LOG_TRACE(__func__ << ": " << it->second->getRemoteUri());
-    it->second->close();
-    m_channelFaces.erase(it);
+    static_cast<face::WebSocketTransport*>(it->second->getLpFace()->getTransport())->handlePongTimeout();
+  }
+  else {
+    NFD_LOG_WARN("Pong timeout on unknown transport");
   }
 }
 
 void
-WebSocketChannel::handlePong(websocketpp::connection_hdl hdl, std::string msg)
+WebSocketChannel::handlePong(websocketpp::connection_hdl hdl)
 {
   auto it = m_channelFaces.find(hdl);
   if (it != m_channelFaces.end()) {
-    NFD_LOG_TRACE("Pong from " << it->second->getRemoteUri());
+    static_cast<face::WebSocketTransport*>(it->second->getLpFace()->getTransport())->handlePong();
+  }
+  else {
+    NFD_LOG_WARN("Pong received on unknown transport");
   }
 }
 
@@ -95,62 +102,30 @@
 {
   auto it = m_channelFaces.find(hdl);
   if (it != m_channelFaces.end()) {
-    it->second->handleReceive(msg->get_payload());
+    static_cast<face::WebSocketTransport*>(it->second->getLpFace()->getTransport())->receiveMessage(msg->get_payload());
+  }
+  else {
+    NFD_LOG_WARN("Message received on unknown transport");
   }
 }
 
 void
 WebSocketChannel::handleOpen(websocketpp::connection_hdl hdl)
 {
-  try {
-    std::string remote = "wsclient://" + m_server.get_con_from_hdl(hdl)->get_remote_endpoint();
-    auto face = make_shared<WebSocketFace>(FaceUri(remote), this->getUri(),
-                                           hdl, ref(m_server));
-    m_onFaceCreatedCallback(face);
-    m_channelFaces[hdl] = face;
-    // Schedule ping message
-    scheduler::EventId pingEvent = scheduler::schedule(m_pingInterval,
-                                                       bind(&WebSocketChannel::sendPing,
-                                                            this, hdl));
-    face->setPingEventId(pingEvent);
-  }
-  catch (const FaceUri::Error& e) {
-    NFD_LOG_WARN(e.what());
-    websocketpp::lib::error_code ec;
-    m_server.close(hdl, websocketpp::close::status::normal, "closed by channel", ec);
-    // ignore error on close
-  }
-  catch (const websocketpp::exception& e) {
-    NFD_LOG_WARN("Cannot get remote connection: " << e.what());
-    websocketpp::lib::error_code ec;
-    m_server.close(hdl, websocketpp::close::status::normal, "closed by channel", ec);
-    // ignore error on close
-  }
-}
+  auto linkService = make_unique<face::GenericLinkService>();
+  auto transport = make_unique<face::WebSocketTransport>(hdl, ref(m_server), m_pingInterval);
+  auto lpFace = make_unique<face::LpFace>(std::move(linkService), std::move(transport));
+  auto face = make_shared<face::LpFaceWrapper>(std::move(lpFace));
 
-void
-WebSocketChannel::sendPing(websocketpp::connection_hdl hdl)
-{
-  auto it = m_channelFaces.find(hdl);
-  if (it != m_channelFaces.end()) {
-    NFD_LOG_TRACE("Sending ping to " << it->second->getRemoteUri());
-
-    websocketpp::lib::error_code ec;
-    m_server.ping(hdl, "NFD-WebSocket", ec);
-    if (ec)
-      {
-        NFD_LOG_WARN("Failed to ping " << it->second->getRemoteUri() << ": " << ec.message());
-        it->second->close();
-        m_channelFaces.erase(it);
-        return;
+  face->getLpFace()->afterStateChange.connect(
+    [this, hdl] (face::FaceState oldState, face::FaceState newState) {
+      if (newState == face::FaceState::CLOSED) {
+        m_channelFaces.erase(hdl);
       }
+    });
+  m_channelFaces[hdl] = face;
 
-    // Schedule next ping message
-    scheduler::EventId pingEvent = scheduler::schedule(m_pingInterval,
-                                                       bind(&WebSocketChannel::sendPing,
-                                                            this, hdl));
-    it->second->setPingEventId(pingEvent);
-  }
+  m_onFaceCreatedCallback(face);
 }
 
 void
@@ -158,20 +133,20 @@
 {
   auto it = m_channelFaces.find(hdl);
   if (it != m_channelFaces.end()) {
-    NFD_LOG_TRACE(__func__ << ": " << it->second->getRemoteUri());
-    it->second->close();
-    m_channelFaces.erase(it);
+    it->second->getLpFace()->close();
+  }
+  else {
+    NFD_LOG_WARN("Close on unknown transport");
   }
 }
 
 void
 WebSocketChannel::listen(const FaceCreatedCallback& onFaceCreated)
 {
-  if (isListening()) {
+  if (m_server.is_listening()) {
     NFD_LOG_WARN("[" << m_localEndpoint << "] Already listening");
     return;
   }
-  m_isListening = true;
 
   m_onFaceCreatedCallback = onFaceCreated;
   m_server.listen(m_localEndpoint);
diff --git a/daemon/face/websocket-channel.hpp b/daemon/face/websocket-channel.hpp
index 027085c..47eda8c 100644
--- a/daemon/face/websocket-channel.hpp
+++ b/daemon/face/websocket-channel.hpp
@@ -27,7 +27,8 @@
 #define NFD_DAEMON_FACE_WEBSOCKET_CHANNEL_HPP
 
 #include "channel.hpp"
-#include "websocket-face.hpp"
+#include "lp-face-wrapper.hpp"
+#include "websocketpp.hpp"
 
 namespace nfd {
 
@@ -46,9 +47,7 @@
    *
    * To enable creation of faces upon incoming connections,
    * one needs to explicitly call WebSocketChannel::listen method.
-   * The created socket is bound to the localEndpoint.
-   *
-   * \throw WebSocketChannel::Error if bind on the socket fails
+   * The created channel is bound to the localEndpoint.
    */
   explicit
   WebSocketChannel(const websocket::Endpoint& localEndpoint);
@@ -56,9 +55,8 @@
   /**
    * \brief Enable listening on the local endpoint, accept connections,
    *        and create faces when remote host makes a connection
-   * \param onFaceCreated  Callback to notify successful creation of the face
    *
-   * \throws WebSocketChannel::Error if called multiple times
+   * \param onFaceCreated Callback to notify successful creation of a face
    */
   void
   listen(const FaceCreatedCallback& onFaceCreated);
@@ -73,24 +71,26 @@
   isListening() const;
 
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  /** \pre listen hasn't been invoked
+   */
   void
   setPingInterval(time::milliseconds interval);
 
+  /** \pre listen hasn't been invoked
+   */
   void
   setPongTimeout(time::milliseconds timeout);
 
+  void
+  handlePong(websocketpp::connection_hdl hdl);
+
+  void
+  handlePongTimeout(websocketpp::connection_hdl hdl);
+
 private:
   void
-  sendPing(websocketpp::connection_hdl hdl);
-
-  void
-  handlePong(websocketpp::connection_hdl hdl, std::string msg);
-
-  void
-  handlePongTimeout(websocketpp::connection_hdl hdl, std::string msg);
-
-  void
-  handleMessage(websocketpp::connection_hdl hdl, websocket::Server::message_ptr msg);
+  handleMessage(websocketpp::connection_hdl hdl,
+                websocket::Server::message_ptr msg);
 
   void
   handleOpen(websocketpp::connection_hdl hdl);
@@ -102,26 +102,17 @@
   websocket::Endpoint m_localEndpoint;
   websocket::Server m_server;
 
-  std::map<websocketpp::connection_hdl, shared_ptr<WebSocketFace>,
+  std::map<websocketpp::connection_hdl, shared_ptr<face::LpFaceWrapper>,
            std::owner_less<websocketpp::connection_hdl>> m_channelFaces;
 
-  /**
-   * Callback for face creation
-   */
   FaceCreatedCallback m_onFaceCreatedCallback;
-
-  /**
-   * \brief If true, it means the function listen has already been called
-   */
-  bool m_isListening;
-
   time::milliseconds m_pingInterval;
 };
 
 inline bool
 WebSocketChannel::isListening() const
 {
-  return m_isListening;
+  return m_server.is_listening();
 }
 
 } // namespace nfd
diff --git a/daemon/face/websocket-face.cpp b/daemon/face/websocket-face.cpp
deleted file mode 100644
index 3b94e6e..0000000
--- a/daemon/face/websocket-face.cpp
+++ /dev/null
@@ -1,131 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2015,  Regents of the University of California,
- *                           Arizona Board of Regents,
- *                           Colorado State University,
- *                           University Pierre & Marie Curie, Sorbonne University,
- *                           Washington University in St. Louis,
- *                           Beijing Institute of Technology,
- *                           The University of Memphis.
- *
- * This file is part of NFD (Named Data Networking Forwarding Daemon).
- * See AUTHORS.md for complete list of NFD authors and contributors.
- *
- * NFD is free software: you can redistribute it and/or modify it under the terms
- * of the GNU General Public License as published by the Free Software Foundation,
- * either version 3 of the License, or (at your option) any later version.
- *
- * NFD 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "websocket-face.hpp"
-
-namespace nfd {
-
-NFD_LOG_INIT("WebSocketFace");
-
-WebSocketFace::WebSocketFace(const FaceUri& remoteUri, const FaceUri& localUri,
-                             websocketpp::connection_hdl hdl,
-                             websocket::Server& server)
-  : Face(remoteUri, localUri)
-  , m_handle(hdl)
-  , m_server(server)
-  , m_closed(false)
-{
-  NFD_LOG_FACE_INFO("Creating face");
-  this->setPersistency(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
-}
-
-void
-WebSocketFace::sendInterest(const Interest& interest)
-{
-  if (m_closed)
-    return;
-
-  NFD_LOG_FACE_TRACE(__func__);
-
-  this->emitSignal(onSendInterest, interest);
-
-  const Block& payload = interest.wireEncode();
-  this->getMutableCounters().getNOutBytes() += payload.size();
-
-  websocketpp::lib::error_code ec;
-  m_server.send(m_handle, payload.wire(), payload.size(),
-                websocketpp::frame::opcode::binary, ec);
-  if (ec)
-    NFD_LOG_FACE_WARN("Failed to send Interest: " << ec.message());
-}
-
-void
-WebSocketFace::sendData(const Data& data)
-{
-  if (m_closed)
-    return;
-
-  NFD_LOG_FACE_TRACE(__func__);
-
-  this->emitSignal(onSendData, data);
-
-  const Block& payload = data.wireEncode();
-  this->getMutableCounters().getNOutBytes() += payload.size();
-
-  websocketpp::lib::error_code ec;
-  m_server.send(m_handle, payload.wire(), payload.size(),
-                websocketpp::frame::opcode::binary, ec);
-  if (ec)
-    NFD_LOG_FACE_WARN("Failed to send Data: " << ec.message());
-}
-
-void
-WebSocketFace::close()
-{
-  if (m_closed)
-    return;
-
-  NFD_LOG_FACE_INFO("Closing face");
-
-  m_closed = true;
-  scheduler::cancel(m_pingEventId);
-  websocketpp::lib::error_code ec;
-  m_server.close(m_handle, websocketpp::close::status::normal, "closed by nfd", ec);
-  // ignore error on close
-  fail("Face closed");
-}
-
-void
-WebSocketFace::handleReceive(const std::string& msg)
-{
-  // Copy message into Face internal buffer
-  if (msg.size() > ndn::MAX_NDN_PACKET_SIZE)
-    {
-      NFD_LOG_FACE_WARN("Received WebSocket message is too big (" << msg.size() << " bytes)");
-      return;
-    }
-
-  NFD_LOG_FACE_TRACE("Received: " << msg.size() << " bytes");
-  this->getMutableCounters().getNInBytes() += msg.size();
-
-  // Try to parse message data
-  bool isOk = false;
-  Block element;
-  std::tie(isOk, element) = Block::fromBuffer(reinterpret_cast<const uint8_t*>(msg.c_str()),
-                                              msg.size());
-  if (!isOk)
-    {
-      NFD_LOG_FACE_WARN("Received block is invalid or too large to process");
-      return;
-    }
-
-  if (!this->decodeAndDispatchInput(element))
-    {
-      NFD_LOG_FACE_WARN("Received unrecognized TLV block of type " << element.type());
-      // ignore unknown packet and proceed
-    }
-}
-
-} // namespace nfd
diff --git a/daemon/face/websocket-face.hpp b/daemon/face/websocket-face.hpp
deleted file mode 100644
index 3c7fbf1..0000000
--- a/daemon/face/websocket-face.hpp
+++ /dev/null
@@ -1,86 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2015,  Regents of the University of California,
- *                           Arizona Board of Regents,
- *                           Colorado State University,
- *                           University Pierre & Marie Curie, Sorbonne University,
- *                           Washington University in St. Louis,
- *                           Beijing Institute of Technology,
- *                           The University of Memphis.
- *
- * This file is part of NFD (Named Data Networking Forwarding Daemon).
- * See AUTHORS.md for complete list of NFD authors and contributors.
- *
- * NFD is free software: you can redistribute it and/or modify it under the terms
- * of the GNU General Public License as published by the Free Software Foundation,
- * either version 3 of the License, or (at your option) any later version.
- *
- * NFD 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef NFD_DAEMON_FACE_WEBSOCKET_FACE_HPP
-#define NFD_DAEMON_FACE_WEBSOCKET_FACE_HPP
-
-#include "face.hpp"
-#include "core/scheduler.hpp"
-
-#ifndef HAVE_WEBSOCKET
-#error "Cannot include this file when WebSocket support is not enabled"
-#endif // HAVE_WEBSOCKET
-
-#include "websocketpp.hpp"
-
-namespace nfd {
-
-namespace websocket {
-typedef websocketpp::server<websocketpp::config::asio> Server;
-} // namespace websocket
-
-/**
- * \brief Implementation of Face abstraction that uses WebSocket
- *        as underlying transport mechanism
- */
-class WebSocketFace : public Face
-{
-public:
-  WebSocketFace(const FaceUri& remoteUri, const FaceUri& localUri,
-                websocketpp::connection_hdl hdl, websocket::Server& server);
-
-  // from Face
-  void
-  sendInterest(const Interest& interest) DECL_OVERRIDE;
-
-  void
-  sendData(const Data& data) DECL_OVERRIDE;
-
-  void
-  close() DECL_OVERRIDE;
-
-  void
-  setPingEventId(scheduler::EventId& id)
-  {
-    m_pingEventId = id;
-  }
-
-protected:
-  // friend because it needs to invoke protected handleReceive
-  friend class WebSocketChannel;
-
-  void
-  handleReceive(const std::string& msg);
-
-private:
-  websocketpp::connection_hdl m_handle;
-  websocket::Server& m_server;
-  scheduler::EventId m_pingEventId;
-  bool m_closed;
-};
-
-} // namespace nfd
-
-#endif // NFD_DAEMON_FACE_WEBSOCKET_FACE_HPP
diff --git a/daemon/face/websocket-transport.cpp b/daemon/face/websocket-transport.cpp
new file mode 100644
index 0000000..4fbed81
--- /dev/null
+++ b/daemon/face/websocket-transport.cpp
@@ -0,0 +1,162 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "websocket-transport.hpp"
+
+namespace nfd {
+namespace face {
+
+NFD_LOG_INIT("WebSocketTransport");
+
+WebSocketTransport::WebSocketTransport(websocketpp::connection_hdl hdl,
+                                       websocket::Server& server,
+                                       time::milliseconds pingInterval)
+  : m_handle(hdl)
+  , m_server(server)
+  , m_pingInterval(pingInterval)
+{
+  const auto& sock = m_server.get_con_from_hdl(hdl)->get_socket();
+  this->setLocalUri(FaceUri(sock.local_endpoint(), "ws"));
+  this->setRemoteUri(FaceUri(sock.remote_endpoint(), "wsclient"));
+
+  if (sock.local_endpoint().address().is_loopback() &&
+      sock.remote_endpoint().address().is_loopback())
+    this->setScope(ndn::nfd::FACE_SCOPE_LOCAL);
+  else
+    this->setScope(ndn::nfd::FACE_SCOPE_NON_LOCAL);
+
+  this->setPersistency(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
+  this->setLinkType(ndn::nfd::LINK_TYPE_POINT_TO_POINT);
+  this->setMtu(MTU_UNLIMITED);
+
+  this->schedulePing();
+
+  NFD_LOG_FACE_INFO("Creating transport");
+}
+
+void WebSocketTransport::beforeChangePersistency(ndn::nfd::FacePersistency newPersistency)
+{
+  if (newPersistency != ndn::nfd::FACE_PERSISTENCY_ON_DEMAND) {
+    BOOST_THROW_EXCEPTION(
+      std::invalid_argument("WebSocketTransport supports only FACE_PERSISTENCY_ON_DEMAND"));
+  }
+}
+
+void
+WebSocketTransport::doSend(Transport::Packet&& packet)
+{
+  NFD_LOG_FACE_TRACE(__func__);
+
+  websocketpp::lib::error_code error;
+  m_server.send(m_handle, packet.packet.wire(), packet.packet.size(),
+                websocketpp::frame::opcode::binary, error);
+  if (error)
+    return processErrorCode(error);
+
+  NFD_LOG_FACE_TRACE("Successfully sent: " << packet.packet.size() << " bytes");
+}
+
+void
+WebSocketTransport::receiveMessage(const std::string& msg)
+{
+  NFD_LOG_FACE_TRACE("Received: " << msg.size() << " bytes");
+
+  bool isOk = false;
+  Block element;
+  std::tie(isOk, element) = Block::fromBuffer(reinterpret_cast<const uint8_t*>(msg.c_str()), msg.size());
+  if (!isOk) {
+    NFD_LOG_FACE_WARN("Failed to parse message payload");
+    return;
+  }
+
+  this->receive(Transport::Packet(std::move(element)));
+}
+
+void
+WebSocketTransport::schedulePing()
+{
+  m_pingEventId = scheduler::schedule(m_pingInterval, bind(&WebSocketTransport::sendPing, this));
+}
+
+void
+WebSocketTransport::sendPing()
+{
+  NFD_LOG_FACE_TRACE(__func__);
+
+  websocketpp::lib::error_code error;
+  m_server.ping(m_handle, "NFD-WebSocket", error);
+  if (error)
+    return processErrorCode(error);
+
+  this->schedulePing();
+}
+
+void
+WebSocketTransport::handlePong()
+{
+  NFD_LOG_FACE_TRACE(__func__);
+}
+
+void
+WebSocketTransport::handlePongTimeout()
+{
+  NFD_LOG_FACE_WARN(__func__);
+  this->setState(TransportState::FAILED);
+  doClose();
+}
+
+void
+WebSocketTransport::processErrorCode(const websocketpp::lib::error_code& error)
+{
+  NFD_LOG_FACE_TRACE(__func__);
+
+  if (getState() == TransportState::CLOSING ||
+      getState() == TransportState::FAILED ||
+      getState() == TransportState::CLOSED)
+    // transport is shutting down, ignore any errors
+    return;
+
+  NFD_LOG_FACE_WARN("Send or ping operation failed: " << error.message());
+
+  this->setState(TransportState::FAILED);
+  doClose();
+}
+
+void
+WebSocketTransport::doClose()
+{
+  NFD_LOG_FACE_TRACE(__func__);
+
+  m_pingEventId.cancel();
+
+  // use the non-throwing variant and ignore errors, if any
+  websocketpp::lib::error_code error;
+  m_server.close(m_handle, websocketpp::close::status::normal, "closed by NFD", error);
+
+  this->setState(TransportState::CLOSED);
+}
+
+} // namespace face
+} // namespace nfd
diff --git a/daemon/face/websocket-transport.hpp b/daemon/face/websocket-transport.hpp
new file mode 100644
index 0000000..6bfbdc3
--- /dev/null
+++ b/daemon/face/websocket-transport.hpp
@@ -0,0 +1,88 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_FACE_WEBSOCKET_TRANSPORT_HPP
+#define NFD_DAEMON_FACE_WEBSOCKET_TRANSPORT_HPP
+
+#include "transport.hpp"
+#include "websocketpp.hpp"
+#include "core/scheduler.hpp"
+
+namespace nfd {
+namespace face {
+
+/**
+ * \brief A Transport that communicates on a WebSocket connection
+ */
+class WebSocketTransport : public Transport
+{
+public:
+  WebSocketTransport(websocketpp::connection_hdl hdl,
+                     websocket::Server& server,
+                     time::milliseconds pingInterval);
+
+  /** \brief Translates a message into a Block
+   *         and delivers it to the link service
+   */
+  void
+  receiveMessage(const std::string& msg);
+
+  void
+  handlePong();
+
+  void
+  handlePongTimeout();
+
+protected:
+  virtual void
+  beforeChangePersistency(ndn::nfd::FacePersistency newPersistency) DECL_OVERRIDE;
+
+  virtual void
+  doClose() DECL_OVERRIDE;
+
+private:
+  virtual void
+  doSend(Transport::Packet&& packet) DECL_OVERRIDE;
+
+  void
+  schedulePing();
+
+  void
+  sendPing();
+
+  void
+  processErrorCode(const websocketpp::lib::error_code& error);
+
+private:
+  websocketpp::connection_hdl m_handle;
+  websocket::Server& m_server;
+  time::milliseconds m_pingInterval;
+  scheduler::ScopedEventId m_pingEventId;
+};
+
+} // namespace face
+} // namespace nfd
+
+#endif // NFD_DAEMON_FACE_WEBSOCKET_TRANSPORT_HPP
diff --git a/daemon/face/websocketpp.hpp b/daemon/face/websocketpp.hpp
index f590bd1..35581fe 100644
--- a/daemon/face/websocketpp.hpp
+++ b/daemon/face/websocketpp.hpp
@@ -1,12 +1,12 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2014,  Regents of the University of California,
- *                      Arizona Board of Regents,
- *                      Colorado State University,
- *                      University Pierre & Marie Curie, Sorbonne University,
- *                      Washington University in St. Louis,
- *                      Beijing Institute of Technology,
- *                      The University of Memphis
+ * Copyright (c) 2014-2015,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
  *
  * This file is part of NFD (Named Data Networking Forwarding Daemon).
  * See AUTHORS.md for complete list of NFD authors and contributors.
@@ -27,14 +27,25 @@
 #define NFD_DAEMON_FACE_WEBSOCKETPP_HPP
 
 #ifndef HAVE_WEBSOCKET
-#error "This file must not be included when WebSocket Face support is disabled"
-#endif
+#error "Cannot include this file when WebSocket support is disabled"
+#endif // HAVE_WEBSOCKET
 
 // suppress websocketpp warnings
 #pragma GCC system_header
 #pragma clang system_header
 
+#include <websocketpp/config/asio_no_tls_client.hpp>
+#include <websocketpp/client.hpp>
 #include "websocketpp/config/asio_no_tls.hpp"
 #include "websocketpp/server.hpp"
 
+namespace nfd {
+namespace websocket {
+
+typedef websocketpp::client<websocketpp::config::asio_client> Client;
+typedef websocketpp::server<websocketpp::config::asio> Server;
+
+} // namespace websocket
+} // namespace nfd
+
 #endif // NFD_DAEMON_FACE_WEBSOCKETPP_HPP