diff --git a/tests/daemon/face/websocket-channel.t.cpp b/tests/daemon/face/websocket-channel.t.cpp index 9e102392..afa36297 100644 --- a/tests/daemon/face/websocket-channel.t.cpp +++ b/tests/daemon/face/websocket-channel.t.cpp @@ -1,6 +1,6 @@ /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /** - * Copyright (c) 2014-2015, Regents of the University of California, + * Copyright (c) 2014-2016, Regents of the University of California, * Arizona Board of Regents, * Colorado State University, * University Pierre & Marie Curie, Sorbonne University, @@ -24,18 +24,293 @@ */ #include "face/websocket-channel.hpp" +#include "face/websocket-transport.hpp" +#include "tests/limited-io.hpp" #include "tests/test-common.hpp" namespace nfd { namespace tests { BOOST_AUTO_TEST_SUITE(Face) -BOOST_FIXTURE_TEST_SUITE(TestWebSocketChannel, BaseFixture) -// TODO add a test case to accept multiple incoming connections on a channel +using nfd::Face; +namespace ip = boost::asio::ip; -// TODO add a test case to check a failed face is removed from the channel +class WebSocketChannelFixture : public BaseFixture +{ +protected: + WebSocketChannelFixture() + : clientShouldPong(true) + , m_nextPort(20070) + { + } + + unique_ptr + makeChannel(const ip::address& addr, uint16_t port = 0) + { + if (port == 0) + port = m_nextPort++; + + return make_unique(websocket::Endpoint(addr, port)); + } + + void + listen(const ip::address& addr, + const time::milliseconds& pingInterval = time::seconds(10), + const time::milliseconds& pongTimeout = time::seconds(1)) + { + listenerEp = websocket::Endpoint(addr, 20030); + listenerChannel = makeChannel(addr, 20030); + listenerChannel->setPingInterval(pingInterval); + listenerChannel->setPongTimeout(pongTimeout); + listenerChannel->listen(bind(&WebSocketChannelFixture::listenerOnFaceCreated, this, _1)); + } + + void + clientConnect(websocket::Client& client) + { + 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(&WebSocketChannelFixture::clientHandleOpen, this, _1)); + client.set_message_handler(bind(&WebSocketChannelFixture::clientHandleMessage, this, _1, _2)); + client.set_ping_handler(bind(&WebSocketChannelFixture::clientHandlePing, this, _1, _2)); + + std::string uri = "ws://" + listenerEp.address().to_string() + ":" + to_string(listenerEp.port()); + websocketpp::lib::error_code ec; + websocket::Client::connection_ptr con = client.get_connection(uri, ec); + BOOST_REQUIRE_EQUAL(ec, websocketpp::lib::error_code()); + + client.connect(con); + } + + void + initialize(const ip::address& addr, + const time::milliseconds& pingInterval = time::seconds(10), + const time::milliseconds& pongTimeout = time::seconds(1)) + { + listen(addr, pingInterval, pongTimeout); + clientConnect(client); + BOOST_REQUIRE_EQUAL(limitedIo.run(2, // listenerOnFaceCreated, clientHandleOpen + time::seconds(1)), LimitedIo::EXCEED_OPS); + BOOST_REQUIRE_EQUAL(listenerChannel->size(), 1); + } + + void + clientSendInterest(const Interest& interest) + { + const Block& payload = interest.wireEncode(); + client.send(clientHandle, payload.wire(), payload.size(), websocketpp::frame::opcode::binary); + } + +private: + void + listenerOnFaceCreated(const shared_ptr& newFace) + { + BOOST_REQUIRE(newFace != nullptr); + newFace->afterReceiveInterest.connect(bind(&WebSocketChannelFixture::faceAfterReceiveInterest, this, _1)); + connectFaceClosedSignal(*newFace, [this] { limitedIo.afterOp(); }); + listenerFaces.push_back(newFace); + limitedIo.afterOp(); + } + + void + faceAfterReceiveInterest(const Interest& interest) + { + faceReceivedInterests.push_back(interest); + limitedIo.afterOp(); + } + + void + clientHandleOpen(websocketpp::connection_hdl hdl) + { + clientHandle = hdl; + limitedIo.afterOp(); + } + + void + clientHandleMessage(websocketpp::connection_hdl, websocket::Client::message_ptr msg) + { + clientReceivedMessages.push_back(msg->get_payload()); + limitedIo.afterOp(); + } + + bool + clientHandlePing(websocketpp::connection_hdl, std::string) + { + auto now = time::steady_clock::now(); + if (m_prevPingRecvTime != time::steady_clock::TimePoint()) { + measuredPingInterval = now - m_prevPingRecvTime; + } + m_prevPingRecvTime = now; + + limitedIo.afterOp(); + return clientShouldPong; + } + +protected: + LimitedIo limitedIo; + + websocket::Endpoint listenerEp; + unique_ptr listenerChannel; + std::vector> listenerFaces; + std::vector faceReceivedInterests; + + websocket::Client client; + websocketpp::connection_hdl clientHandle; + std::vector clientReceivedMessages; + time::steady_clock::Duration measuredPingInterval; + bool clientShouldPong; // set clientShouldPong false to disable the pong response, + // which will cause timeout in listenerChannel + +private: + uint16_t m_nextPort; + time::steady_clock::TimePoint m_prevPingRecvTime; +}; + +BOOST_FIXTURE_TEST_SUITE(TestWebSocketChannel, WebSocketChannelFixture) + +BOOST_AUTO_TEST_CASE(Uri) +{ + websocket::Endpoint ep(ip::address_v4::loopback(), 20070); + auto channel = makeChannel(ep.address(), ep.port()); + BOOST_CHECK_EQUAL(channel->getUri(), FaceUri("ws://127.0.0.1:20070")); +} + +BOOST_AUTO_TEST_CASE(Listen) +{ + auto channel = makeChannel(ip::address_v4()); + BOOST_CHECK_EQUAL(channel->isListening(), false); + + channel->listen(nullptr); + BOOST_CHECK_EQUAL(channel->isListening(), true); + + // listen() is idempotent + BOOST_CHECK_NO_THROW(channel->listen(nullptr)); + BOOST_CHECK_EQUAL(channel->isListening(), true); +} + +BOOST_AUTO_TEST_CASE(MultipleAccepts) +{ + listen(ip::address_v4::loopback()); + + BOOST_CHECK_EQUAL(listenerChannel->isListening(), true); + BOOST_CHECK_EQUAL(listenerChannel->size(), 0); + + websocket::Client client1; + clientConnect(client1); + BOOST_CHECK_EQUAL(limitedIo.run(2, // listenerOnFaceCreated, clientHandleOpen + time::seconds(1)), LimitedIo::EXCEED_OPS); + BOOST_CHECK_EQUAL(listenerChannel->size(), 1); + + websocket::Client client2; + websocket::Client client3; + clientConnect(client2); + clientConnect(client3); + BOOST_CHECK_EQUAL(limitedIo.run(4, // 2 listenerOnFaceCreated, 2 clientHandleOpen + time::seconds(1)), LimitedIo::EXCEED_OPS); + BOOST_CHECK_EQUAL(listenerChannel->size(), 3); + + // check face persistency + for (const auto& face : listenerFaces) { + BOOST_CHECK_EQUAL(face->getPersistency(), ndn::nfd::FACE_PERSISTENCY_ON_DEMAND); + } +} + +BOOST_AUTO_TEST_CASE(Send) +{ + initialize(ip::address_v4::loopback()); + auto transport = listenerFaces.front()->getTransport(); + + Block pkt1 = ndn::encoding::makeStringBlock(300, "hello"); + transport->send(face::Transport::Packet(Block(pkt1))); + BOOST_CHECK_EQUAL(limitedIo.run(1, // clientHandleMessage + time::seconds(1)), LimitedIo::EXCEED_OPS); + + Block pkt2 = ndn::encoding::makeStringBlock(301, "world!"); + transport->send(face::Transport::Packet(Block(pkt2))); + BOOST_CHECK_EQUAL(limitedIo.run(1, // clientHandleMessage + time::seconds(1)), LimitedIo::EXCEED_OPS); + + BOOST_REQUIRE_EQUAL(clientReceivedMessages.size(), 2); + BOOST_CHECK_EQUAL_COLLECTIONS( + reinterpret_cast(clientReceivedMessages[0].data()), + reinterpret_cast(clientReceivedMessages[0].data()) + clientReceivedMessages[0].size(), + pkt1.begin(), pkt1.end()); + BOOST_CHECK_EQUAL_COLLECTIONS( + reinterpret_cast(clientReceivedMessages[1].data()), + reinterpret_cast(clientReceivedMessages[1].data()) + clientReceivedMessages[1].size(), + pkt2.begin(), pkt2.end()); +} + +BOOST_AUTO_TEST_CASE(Receive) +{ + initialize(ip::address_v4::loopback()); + + // use network-layer packets here, otherwise GenericLinkService + // won't recognize the packet type and will discard it + auto interest1 = makeInterest("ndn:/TpnzGvW9R"); + auto interest2 = makeInterest("ndn:/QWiIMfj5sL"); + + clientSendInterest(*interest1); + BOOST_CHECK_EQUAL(limitedIo.run(1, // faceAfterReceiveInterest + time::seconds(1)), LimitedIo::EXCEED_OPS); + + clientSendInterest(*interest2); + BOOST_CHECK_EQUAL(limitedIo.run(1, // faceAfterReceiveInterest + time::seconds(1)), LimitedIo::EXCEED_OPS); + + BOOST_REQUIRE_EQUAL(faceReceivedInterests.size(), 2); + BOOST_CHECK_EQUAL(faceReceivedInterests[0].getName(), interest1->getName()); + BOOST_CHECK_EQUAL(faceReceivedInterests[1].getName(), interest2->getName()); +} + +BOOST_AUTO_TEST_CASE(FaceClosure) +{ + initialize(ip::address_v4::loopback()); + + listenerFaces.front()->close(); + BOOST_CHECK_EQUAL(listenerChannel->size(), 0); +} + +BOOST_AUTO_TEST_CASE(RemoteClose) +{ + initialize(ip::address_v4::loopback()); + + client.close(clientHandle, websocketpp::close::status::going_away, ""); + BOOST_CHECK_EQUAL(limitedIo.run(1, // faceClosedSignal + time::seconds(1)), LimitedIo::EXCEED_OPS); + BOOST_CHECK_EQUAL(listenerChannel->size(), 0); +} + +BOOST_AUTO_TEST_CASE(SetPingInterval) +{ + auto pingInterval = time::milliseconds(300); + initialize(ip::address_v4::loopback(), pingInterval, time::milliseconds(1000)); + + BOOST_CHECK_EQUAL(limitedIo.run(2, // clientHandlePing + time::seconds(1)), LimitedIo::EXCEED_OPS); + BOOST_CHECK_LE(measuredPingInterval, pingInterval * 1.1); + BOOST_CHECK_GE(measuredPingInterval, pingInterval * 0.9); +} + +BOOST_AUTO_TEST_CASE(SetPongTimeOut) +{ + initialize(ip::address_v4::loopback(), time::milliseconds(500), time::milliseconds(300)); + + clientShouldPong = false; + BOOST_CHECK_EQUAL(limitedIo.run(2, // clientHandlePing, faceClosedSignal + time::seconds(2)), LimitedIo::EXCEED_OPS); + BOOST_CHECK_EQUAL(listenerChannel->size(), 0); + + auto transport = static_cast(listenerFaces.front()->getTransport()); + BOOST_CHECK(transport->getState() == face::TransportState::FAILED || + transport->getState() == face::TransportState::CLOSED); + BOOST_CHECK_EQUAL(transport->getCounters().nOutPings, 1); + BOOST_CHECK_EQUAL(transport->getCounters().nInPongs, 0); +} BOOST_AUTO_TEST_SUITE_END() // TestWebSocketChannel BOOST_AUTO_TEST_SUITE_END() // Face