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
diff --git a/tests/daemon/face/dummy-receive-link-service.hpp b/tests/daemon/face/dummy-receive-link-service.hpp
new file mode 100644
index 0000000..91dabd2
--- /dev/null
+++ b/tests/daemon/face/dummy-receive-link-service.hpp
@@ -0,0 +1,75 @@
+/* -*- 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_TESTS_DAEMON_FACE_DUMMY_RECEIVE_LINK_SERVICE_HPP
+#define NFD_TESTS_DAEMON_FACE_DUMMY_RECEIVE_LINK_SERVICE_HPP
+
+#include "common.hpp"
+
+#include "face/link-service.hpp"
+
+namespace nfd {
+namespace face {
+namespace tests {
+
+/** \brief a dummy LinkService that logs all received packets, for Transport testing
+ *  \warning This LinkService does not allow sending.
+ */
+class DummyReceiveLinkService : public LinkService
+{
+private:
+  virtual void
+  doSendInterest(const Interest& interest) DECL_OVERRIDE
+  {
+    BOOST_ASSERT(false);
+  }
+
+  virtual void
+  doSendData(const Data& data) DECL_OVERRIDE
+  {
+    BOOST_ASSERT(false);
+  }
+
+  virtual void
+  doSendNack(const lp::Nack& nack) DECL_OVERRIDE
+  {
+    BOOST_ASSERT(false);
+  }
+
+  virtual void
+  doReceivePacket(Transport::Packet&& packet) DECL_OVERRIDE
+  {
+    receivedPackets.push_back(packet);
+  }
+
+public:
+  std::vector<Transport::Packet> receivedPackets;
+};
+
+} // namespace tests
+} // namespace face
+} // namespace nfd
+
+#endif // NFD_TESTS_DAEMON_FACE_DUMMY_RECEIVE_LINK_SERVICE_HPP
diff --git a/tests/daemon/face/websocket-transport.t.cpp b/tests/daemon/face/websocket-transport.t.cpp
new file mode 100644
index 0000000..f4115bc
--- /dev/null
+++ b/tests/daemon/face/websocket-transport.t.cpp
@@ -0,0 +1,323 @@
+/* -*- 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 "face/websocket-transport.hpp"
+#include "face/lp-face.hpp"
+#include "dummy-receive-link-service.hpp"
+#include "transport-properties.hpp"
+
+#include "tests/test-common.hpp"
+#include "tests/limited-io.hpp"
+
+namespace nfd {
+namespace face {
+namespace tests {
+
+using namespace nfd::tests;
+namespace ip = boost::asio::ip;
+
+BOOST_AUTO_TEST_SUITE(Face)
+
+/** \brief a fixture that accepts a single WebSocket connection from a client
+ */
+class SingleWebSocketFixture : public BaseFixture
+{
+public:
+  SingleWebSocketFixture()
+    : transport(nullptr)
+    , serverReceivedPackets(nullptr)
+    , clientShouldPong(true)
+  {
+  }
+
+  /** \brief initialize server and start listening
+   */
+  void
+  serverListen(const ip::tcp::endpoint& ep,
+               const time::milliseconds& pongTimeout = time::milliseconds(1000))
+  {
+    server.clear_access_channels(websocketpp::log::alevel::all);
+    server.clear_error_channels(websocketpp::log::elevel::all);
+
+    server.init_asio(&g_io);
+    server.set_open_handler(bind(&SingleWebSocketFixture::serverHandleOpen, this, _1));
+    server.set_close_handler(bind(&SingleWebSocketFixture::serverHandleClose, this));
+    server.set_message_handler(bind(&SingleWebSocketFixture::serverHandleMessage, this, _2));
+    server.set_pong_handler(bind(&SingleWebSocketFixture::serverHandlePong, this));
+    server.set_pong_timeout_handler(bind(&SingleWebSocketFixture::serverHandlePongTimeout, this));
+    server.set_pong_timeout(pongTimeout.count());
+
+    server.set_reuse_addr(true);
+
+    server.listen(ep);
+    server.start_accept();
+  }
+
+  /** \brief initialize client and connect to server
+   */
+  void
+  clientConnect(const std::string& uri)
+  {
+    client.clear_access_channels(websocketpp::log::alevel::all);
+    client.clear_error_channels(websocketpp::log::elevel::all);
+
+    client.init_asio(&g_io);
+    client.set_open_handler(bind(&SingleWebSocketFixture::clientHandleOpen, this, _1));
+    client.set_message_handler(bind(&SingleWebSocketFixture::clientHandleMessage, this, _2));
+    client.set_ping_handler(bind(&SingleWebSocketFixture::clientHandlePing, this));
+
+    websocketpp::lib::error_code ec;
+    websocket::Client::connection_ptr con = client.get_connection("ws://127.0.0.1:20070", ec);
+    BOOST_REQUIRE(!ec);
+
+    client.connect(con);
+  }
+
+  void
+  makeFace(const time::milliseconds& pingInterval = time::milliseconds(10000))
+  {
+    face = make_unique<LpFace>(
+             make_unique<DummyReceiveLinkService>(),
+             make_unique<WebSocketTransport>(serverHdl, ref(server), pingInterval));
+    transport = static_cast<WebSocketTransport*>(face->getTransport());
+    serverReceivedPackets = &static_cast<DummyReceiveLinkService*>(face->getLinkService())->receivedPackets;
+  }
+
+  /** \brief initialize both server and client, and have each other connected, create Transport
+   */
+  void
+  endToEndInitialize(const ip::tcp::endpoint& ep,
+                     const time::milliseconds& pingInterval = time::milliseconds(10000),
+                     const time::milliseconds& pongTimeout = time::milliseconds(1000))
+  {
+    this->serverListen(ep, pongTimeout);
+    std::string uri = "ws://" + ep.address().to_string() + ":" + to_string(ep.port());
+    this->clientConnect(uri);
+    BOOST_REQUIRE_EQUAL(limitedIo.run(2, // serverHandleOpen, clientHandleOpen
+                        time::seconds(1)), LimitedIo::EXCEED_OPS);
+    this->makeFace(pingInterval);
+  }
+
+private:
+  void
+  serverHandleOpen(websocketpp::connection_hdl hdl)
+  {
+    serverHdl = hdl;
+    limitedIo.afterOp();
+  }
+
+  void
+  serverHandleClose()
+  {
+    if (transport == nullptr) {
+      return;
+    }
+
+    transport->close();
+    limitedIo.afterOp();
+  }
+
+  void
+  serverHandleMessage(websocket::Server::message_ptr msg)
+  {
+    if (transport == nullptr) {
+      return;
+    }
+
+    transport->receiveMessage(msg->get_payload());
+    limitedIo.afterOp();
+  }
+
+  void
+  serverHandlePong()
+  {
+    if (transport == nullptr) {
+      return;
+    }
+
+    transport->handlePong();
+    limitedIo.afterOp();
+  }
+
+  void
+  serverHandlePongTimeout()
+  {
+    if (transport == nullptr) {
+      return;
+    }
+
+    transport->handlePongTimeout();
+    limitedIo.afterOp();
+  }
+
+  void
+  clientHandleOpen(websocketpp::connection_hdl hdl)
+  {
+    clientHdl = hdl;
+    limitedIo.afterOp();
+  }
+
+  void
+  clientHandleMessage(websocket::Client::message_ptr msg)
+  {
+    clientReceivedMessages.push_back(msg->get_payload());
+    limitedIo.afterOp();
+  }
+
+  bool
+  clientHandlePing()
+  {
+    limitedIo.afterOp();
+    return clientShouldPong;
+  }
+
+public:
+  LimitedIo limitedIo;
+
+  websocket::Server server;
+  websocketpp::connection_hdl serverHdl;
+  unique_ptr<LpFace> face;
+  WebSocketTransport* transport;
+  std::vector<Transport::Packet>* serverReceivedPackets;
+
+  websocket::Client client;
+  websocketpp::connection_hdl clientHdl;
+  bool clientShouldPong;
+  std::vector<std::string> clientReceivedMessages;
+};
+
+BOOST_FIXTURE_TEST_SUITE(TestWebSocketTransport, SingleWebSocketFixture)
+
+BOOST_AUTO_TEST_CASE(StaticProperties)
+{
+  ip::tcp::endpoint ep(ip::address_v4::loopback(), 20070);
+  this->endToEndInitialize(ep);
+  checkStaticPropertiesInitialized(*transport);
+
+  BOOST_CHECK_EQUAL(transport->getLocalUri(), FaceUri("ws://127.0.0.1:20070"));
+  BOOST_CHECK_EQUAL(transport->getRemoteUri().getScheme(), "wsclient");
+  BOOST_CHECK_EQUAL(transport->getRemoteUri().getHost(), "127.0.0.1");
+  BOOST_CHECK_EQUAL(transport->getRemoteUri().getPath(), "");
+  BOOST_CHECK_EQUAL(transport->getScope(), ndn::nfd::FACE_SCOPE_LOCAL);
+  BOOST_CHECK_EQUAL(transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
+  BOOST_CHECK_EQUAL(transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
+  BOOST_CHECK_EQUAL(transport->getMtu(), MTU_UNLIMITED);
+}
+
+BOOST_AUTO_TEST_CASE(PingPong)
+{
+  ip::tcp::endpoint ep(ip::address_v4::loopback(), 20070);
+  this->endToEndInitialize(ep, time::milliseconds(500), time::milliseconds(300));
+
+  BOOST_CHECK_EQUAL(limitedIo.run(2, // clientHandlePing, serverHandlePong
+                    time::milliseconds(1500)), LimitedIo::EXCEED_OPS);
+  BOOST_CHECK_EQUAL(transport->getState(), TransportState::UP);
+
+  this->clientShouldPong = false;
+  BOOST_CHECK_EQUAL(limitedIo.run(2, // clientHandlePing, serverHandlePongTimeout
+                    time::milliseconds(2000)), LimitedIo::EXCEED_OPS);
+  BOOST_CHECK_MESSAGE(transport->getState() == TransportState::FAILED ||
+                      transport->getState() == TransportState::CLOSED,
+                      "expect FAILED or CLOSED state, actual state=" << transport->getState());
+}
+
+BOOST_AUTO_TEST_CASE(Send)
+{
+  ip::tcp::endpoint ep(ip::address_v4::loopback(), 20070);
+  this->endToEndInitialize(ep);
+
+  Block pkt1 = ndn::encoding::makeStringBlock(300, "hello");
+  transport->send(Transport::Packet(Block(pkt1)));
+  BOOST_CHECK_EQUAL(limitedIo.run(1, // clientHandleMessage
+                    time::milliseconds(1000)), LimitedIo::EXCEED_OPS);
+
+  Block pkt2 = ndn::encoding::makeStringBlock(301, "world!");
+  transport->send(Transport::Packet(Block(pkt2)));
+  BOOST_CHECK_EQUAL(limitedIo.run(1, // clientHandleMessage
+                    time::milliseconds(1000)), LimitedIo::EXCEED_OPS);
+
+  BOOST_REQUIRE_EQUAL(clientReceivedMessages.size(), 2);
+  BOOST_CHECK_EQUAL_COLLECTIONS(
+    reinterpret_cast<const uint8_t*>(clientReceivedMessages[0].data()),
+    reinterpret_cast<const uint8_t*>(clientReceivedMessages[0].data()) + clientReceivedMessages[0].size(),
+    pkt1.begin(), pkt1.end());
+  BOOST_CHECK_EQUAL_COLLECTIONS(
+    reinterpret_cast<const uint8_t*>(clientReceivedMessages[1].data()),
+    reinterpret_cast<const uint8_t*>(clientReceivedMessages[1].data()) + clientReceivedMessages[1].size(),
+    pkt2.begin(), pkt2.end());
+}
+
+BOOST_AUTO_TEST_CASE(Receive)
+{
+  ip::tcp::endpoint ep(ip::address_v4::loopback(), 20070);
+  this->endToEndInitialize(ep);
+
+  Block pkt1 = ndn::encoding::makeStringBlock(300, "hello");
+  client.send(clientHdl, pkt1.wire(), pkt1.size(), websocketpp::frame::opcode::binary);
+  BOOST_CHECK_EQUAL(limitedIo.run(1, // serverHandleMessage
+                    time::milliseconds(1000)), LimitedIo::EXCEED_OPS);
+
+  Block pkt2 = ndn::encoding::makeStringBlock(301, "world!");
+  client.send(clientHdl, pkt2.wire(), pkt2.size(), websocketpp::frame::opcode::binary);
+  BOOST_CHECK_EQUAL(limitedIo.run(1, // serverHandleMessage
+                    time::milliseconds(1000)), LimitedIo::EXCEED_OPS);
+
+  BOOST_REQUIRE_EQUAL(serverReceivedPackets->size(), 2);
+  BOOST_CHECK(serverReceivedPackets->at(0).packet == pkt1);
+  BOOST_CHECK(serverReceivedPackets->at(1).packet == pkt2);
+  BOOST_CHECK_EQUAL(serverReceivedPackets->at(0).remoteEndpoint, serverReceivedPackets->at(1).remoteEndpoint);
+}
+
+BOOST_AUTO_TEST_CASE(ReceiveMalformed)
+{
+  ip::tcp::endpoint ep(ip::address_v4::loopback(), 20070);
+  this->endToEndInitialize(ep);
+
+  Block pkt1 = ndn::encoding::makeStringBlock(300, "hello");
+  client.send(clientHdl, pkt1.wire(), pkt1.size() - 1, // truncated
+              websocketpp::frame::opcode::binary);
+  BOOST_CHECK_EQUAL(limitedIo.run(1, // serverHandleMessage
+                    time::milliseconds(1000)), LimitedIo::EXCEED_OPS);
+
+  // bad packet is dropped
+  BOOST_CHECK_EQUAL(transport->getState(), TransportState::UP);
+  BOOST_CHECK_EQUAL(serverReceivedPackets->size(), 0);
+
+  Block pkt2 = ndn::encoding::makeStringBlock(301, "world!");
+  client.send(clientHdl, pkt2.wire(), pkt2.size(), websocketpp::frame::opcode::binary);
+  BOOST_CHECK_EQUAL(limitedIo.run(1, // serverHandleMessage
+                    time::milliseconds(1000)), LimitedIo::EXCEED_OPS);
+
+  // next valid packet is still received normally
+  BOOST_REQUIRE_EQUAL(serverReceivedPackets->size(), 1);
+  BOOST_CHECK(serverReceivedPackets->at(0).packet == pkt2);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestWebSocketTransport
+BOOST_AUTO_TEST_SUITE_END() // Face
+
+} // namespace tests
+} // namespace face
+} // namespace nfd
diff --git a/tests/daemon/face/websocket.t.cpp b/tests/daemon/face/websocket.t.cpp
index a1d8a52..ee98bf3 100644
--- a/tests/daemon/face/websocket.t.cpp
+++ b/tests/daemon/face/websocket.t.cpp
@@ -24,21 +24,19 @@
  */
 
 #include "face/websocket-channel.hpp"
-#include "face/websocket-face.hpp"
 #include "face/websocket-factory.hpp"
+#include "face/websocketpp.hpp"
 
 #include "tests/test-common.hpp"
 #include "tests/limited-io.hpp"
 
-#include <websocketpp/config/asio_no_tls_client.hpp>
-#include <websocketpp/client.hpp>
-
-typedef websocketpp::client<websocketpp::config::asio_client> Client;
-
 namespace nfd {
 namespace tests {
 
-BOOST_FIXTURE_TEST_SUITE(FaceWebSocket, BaseFixture)
+BOOST_AUTO_TEST_SUITE(Face)
+BOOST_FIXTURE_TEST_SUITE(TestWebSocket, BaseFixture)
+
+using nfd::Face;
 
 BOOST_AUTO_TEST_CASE(GetChannels)
 {
@@ -212,7 +210,7 @@
   shared_ptr<Face> face1;
   std::vector<Interest> face1_receivedInterests;
   std::vector<Data> face1_receivedDatas;
-  Client client1;
+  websocket::Client client1;
   websocketpp::connection_hdl handle;
   std::vector<Interest> client1_receivedInterests;
   std::vector<Data> client1_receivedDatas;
@@ -243,7 +241,7 @@
   client1.set_ping_handler(bind(&EndToEndFixture::client1_onPing, this, _1, _2));
 
   websocketpp::lib::error_code ec;
-  Client::connection_ptr con = client1.get_connection("ws://127.0.0.1:20070", ec);
+  auto con = client1.get_connection("ws://127.0.0.1:20070", ec);
   client1.connect(con);
 
   BOOST_CHECK_MESSAGE(limitedIo.run(2, time::seconds(10)) == LimitedIo::EXCEED_OPS,
@@ -252,7 +250,7 @@
   BOOST_CHECK_EQUAL(channel1->size(), 1);
 
   BOOST_REQUIRE(static_cast<bool>(face1));
-  BOOST_CHECK_EQUAL(face1->isLocal(), false);
+  BOOST_CHECK_EQUAL(face1->isLocal(), true);
   BOOST_CHECK_EQUAL(face1->getPersistency(), ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
   BOOST_CHECK_EQUAL(face1->isMultiAccess(), false);
   BOOST_CHECK_EQUAL(face1->getLocalUri().toString(), "ws://127.0.0.1:20070");
@@ -262,19 +260,15 @@
   shared_ptr<Interest> interest2 = makeInterest("ndn:/QWiIMfj5sL");
   shared_ptr<Data>     data2     = makeData("ndn:/XNBV796f");
 
-  std::string bigName("ndn:/");
-  bigName.append(9000, 'a');
-  shared_ptr<Interest> bigInterest = makeInterest(bigName);
-
   client1_sendInterest(*interest1);
   client1_sendInterest(*interest1);
   client1_sendInterest(*interest1);
-  client1_sendInterest(*bigInterest);  // This one should be ignored by face1
   face1->sendData     (*data1);
   face1->sendInterest (*interest2);
   client1_sendData    (*data2);
   client1_sendData    (*data2);
   client1_sendData    (*data2);
+
   size_t nBytesSent = data1->wireEncode().size() + interest2->wireEncode().size();
   size_t nBytesReceived = interest1->wireEncode().size() * 3 + data2->wireEncode().size() * 3;
 
@@ -303,7 +297,8 @@
   BOOST_CHECK_EQUAL(channel1->size(), 0);
 }
 
-BOOST_AUTO_TEST_SUITE_END()
+BOOST_AUTO_TEST_SUITE_END() // TestWebSocket
+BOOST_AUTO_TEST_SUITE_END() // Face
 
 } // namespace tests
 } // namespace nfd