blob: f4115bcd4de8230c6a9f1a7940fb7bf912e02967 [file] [log] [blame]
Yukai Tu2d6d5632015-10-26 11:06:02 -07001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/**
3 * Copyright (c) 2014-2015, Regents of the University of California,
4 * Arizona Board of Regents,
5 * Colorado State University,
6 * University Pierre & Marie Curie, Sorbonne University,
7 * Washington University in St. Louis,
8 * Beijing Institute of Technology,
9 * The University of Memphis.
10 *
11 * This file is part of NFD (Named Data Networking Forwarding Daemon).
12 * See AUTHORS.md for complete list of NFD authors and contributors.
13 *
14 * NFD is free software: you can redistribute it and/or modify it under the terms
15 * of the GNU General Public License as published by the Free Software Foundation,
16 * either version 3 of the License, or (at your option) any later version.
17 *
18 * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
19 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
20 * PURPOSE. See the GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License along with
23 * NFD, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
24 */
25
26#include "face/websocket-transport.hpp"
27#include "face/lp-face.hpp"
28#include "dummy-receive-link-service.hpp"
29#include "transport-properties.hpp"
30
31#include "tests/test-common.hpp"
32#include "tests/limited-io.hpp"
33
34namespace nfd {
35namespace face {
36namespace tests {
37
38using namespace nfd::tests;
39namespace ip = boost::asio::ip;
40
41BOOST_AUTO_TEST_SUITE(Face)
42
43/** \brief a fixture that accepts a single WebSocket connection from a client
44 */
45class SingleWebSocketFixture : public BaseFixture
46{
47public:
48 SingleWebSocketFixture()
49 : transport(nullptr)
50 , serverReceivedPackets(nullptr)
51 , clientShouldPong(true)
52 {
53 }
54
55 /** \brief initialize server and start listening
56 */
57 void
58 serverListen(const ip::tcp::endpoint& ep,
59 const time::milliseconds& pongTimeout = time::milliseconds(1000))
60 {
61 server.clear_access_channels(websocketpp::log::alevel::all);
62 server.clear_error_channels(websocketpp::log::elevel::all);
63
64 server.init_asio(&g_io);
65 server.set_open_handler(bind(&SingleWebSocketFixture::serverHandleOpen, this, _1));
66 server.set_close_handler(bind(&SingleWebSocketFixture::serverHandleClose, this));
67 server.set_message_handler(bind(&SingleWebSocketFixture::serverHandleMessage, this, _2));
68 server.set_pong_handler(bind(&SingleWebSocketFixture::serverHandlePong, this));
69 server.set_pong_timeout_handler(bind(&SingleWebSocketFixture::serverHandlePongTimeout, this));
70 server.set_pong_timeout(pongTimeout.count());
71
72 server.set_reuse_addr(true);
73
74 server.listen(ep);
75 server.start_accept();
76 }
77
78 /** \brief initialize client and connect to server
79 */
80 void
81 clientConnect(const std::string& uri)
82 {
83 client.clear_access_channels(websocketpp::log::alevel::all);
84 client.clear_error_channels(websocketpp::log::elevel::all);
85
86 client.init_asio(&g_io);
87 client.set_open_handler(bind(&SingleWebSocketFixture::clientHandleOpen, this, _1));
88 client.set_message_handler(bind(&SingleWebSocketFixture::clientHandleMessage, this, _2));
89 client.set_ping_handler(bind(&SingleWebSocketFixture::clientHandlePing, this));
90
91 websocketpp::lib::error_code ec;
92 websocket::Client::connection_ptr con = client.get_connection("ws://127.0.0.1:20070", ec);
93 BOOST_REQUIRE(!ec);
94
95 client.connect(con);
96 }
97
98 void
99 makeFace(const time::milliseconds& pingInterval = time::milliseconds(10000))
100 {
101 face = make_unique<LpFace>(
102 make_unique<DummyReceiveLinkService>(),
103 make_unique<WebSocketTransport>(serverHdl, ref(server), pingInterval));
104 transport = static_cast<WebSocketTransport*>(face->getTransport());
105 serverReceivedPackets = &static_cast<DummyReceiveLinkService*>(face->getLinkService())->receivedPackets;
106 }
107
108 /** \brief initialize both server and client, and have each other connected, create Transport
109 */
110 void
111 endToEndInitialize(const ip::tcp::endpoint& ep,
112 const time::milliseconds& pingInterval = time::milliseconds(10000),
113 const time::milliseconds& pongTimeout = time::milliseconds(1000))
114 {
115 this->serverListen(ep, pongTimeout);
116 std::string uri = "ws://" + ep.address().to_string() + ":" + to_string(ep.port());
117 this->clientConnect(uri);
118 BOOST_REQUIRE_EQUAL(limitedIo.run(2, // serverHandleOpen, clientHandleOpen
119 time::seconds(1)), LimitedIo::EXCEED_OPS);
120 this->makeFace(pingInterval);
121 }
122
123private:
124 void
125 serverHandleOpen(websocketpp::connection_hdl hdl)
126 {
127 serverHdl = hdl;
128 limitedIo.afterOp();
129 }
130
131 void
132 serverHandleClose()
133 {
134 if (transport == nullptr) {
135 return;
136 }
137
138 transport->close();
139 limitedIo.afterOp();
140 }
141
142 void
143 serverHandleMessage(websocket::Server::message_ptr msg)
144 {
145 if (transport == nullptr) {
146 return;
147 }
148
149 transport->receiveMessage(msg->get_payload());
150 limitedIo.afterOp();
151 }
152
153 void
154 serverHandlePong()
155 {
156 if (transport == nullptr) {
157 return;
158 }
159
160 transport->handlePong();
161 limitedIo.afterOp();
162 }
163
164 void
165 serverHandlePongTimeout()
166 {
167 if (transport == nullptr) {
168 return;
169 }
170
171 transport->handlePongTimeout();
172 limitedIo.afterOp();
173 }
174
175 void
176 clientHandleOpen(websocketpp::connection_hdl hdl)
177 {
178 clientHdl = hdl;
179 limitedIo.afterOp();
180 }
181
182 void
183 clientHandleMessage(websocket::Client::message_ptr msg)
184 {
185 clientReceivedMessages.push_back(msg->get_payload());
186 limitedIo.afterOp();
187 }
188
189 bool
190 clientHandlePing()
191 {
192 limitedIo.afterOp();
193 return clientShouldPong;
194 }
195
196public:
197 LimitedIo limitedIo;
198
199 websocket::Server server;
200 websocketpp::connection_hdl serverHdl;
201 unique_ptr<LpFace> face;
202 WebSocketTransport* transport;
203 std::vector<Transport::Packet>* serverReceivedPackets;
204
205 websocket::Client client;
206 websocketpp::connection_hdl clientHdl;
207 bool clientShouldPong;
208 std::vector<std::string> clientReceivedMessages;
209};
210
211BOOST_FIXTURE_TEST_SUITE(TestWebSocketTransport, SingleWebSocketFixture)
212
213BOOST_AUTO_TEST_CASE(StaticProperties)
214{
215 ip::tcp::endpoint ep(ip::address_v4::loopback(), 20070);
216 this->endToEndInitialize(ep);
217 checkStaticPropertiesInitialized(*transport);
218
219 BOOST_CHECK_EQUAL(transport->getLocalUri(), FaceUri("ws://127.0.0.1:20070"));
220 BOOST_CHECK_EQUAL(transport->getRemoteUri().getScheme(), "wsclient");
221 BOOST_CHECK_EQUAL(transport->getRemoteUri().getHost(), "127.0.0.1");
222 BOOST_CHECK_EQUAL(transport->getRemoteUri().getPath(), "");
223 BOOST_CHECK_EQUAL(transport->getScope(), ndn::nfd::FACE_SCOPE_LOCAL);
224 BOOST_CHECK_EQUAL(transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
225 BOOST_CHECK_EQUAL(transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
226 BOOST_CHECK_EQUAL(transport->getMtu(), MTU_UNLIMITED);
227}
228
229BOOST_AUTO_TEST_CASE(PingPong)
230{
231 ip::tcp::endpoint ep(ip::address_v4::loopback(), 20070);
232 this->endToEndInitialize(ep, time::milliseconds(500), time::milliseconds(300));
233
234 BOOST_CHECK_EQUAL(limitedIo.run(2, // clientHandlePing, serverHandlePong
235 time::milliseconds(1500)), LimitedIo::EXCEED_OPS);
236 BOOST_CHECK_EQUAL(transport->getState(), TransportState::UP);
237
238 this->clientShouldPong = false;
239 BOOST_CHECK_EQUAL(limitedIo.run(2, // clientHandlePing, serverHandlePongTimeout
240 time::milliseconds(2000)), LimitedIo::EXCEED_OPS);
241 BOOST_CHECK_MESSAGE(transport->getState() == TransportState::FAILED ||
242 transport->getState() == TransportState::CLOSED,
243 "expect FAILED or CLOSED state, actual state=" << transport->getState());
244}
245
246BOOST_AUTO_TEST_CASE(Send)
247{
248 ip::tcp::endpoint ep(ip::address_v4::loopback(), 20070);
249 this->endToEndInitialize(ep);
250
251 Block pkt1 = ndn::encoding::makeStringBlock(300, "hello");
252 transport->send(Transport::Packet(Block(pkt1)));
253 BOOST_CHECK_EQUAL(limitedIo.run(1, // clientHandleMessage
254 time::milliseconds(1000)), LimitedIo::EXCEED_OPS);
255
256 Block pkt2 = ndn::encoding::makeStringBlock(301, "world!");
257 transport->send(Transport::Packet(Block(pkt2)));
258 BOOST_CHECK_EQUAL(limitedIo.run(1, // clientHandleMessage
259 time::milliseconds(1000)), LimitedIo::EXCEED_OPS);
260
261 BOOST_REQUIRE_EQUAL(clientReceivedMessages.size(), 2);
262 BOOST_CHECK_EQUAL_COLLECTIONS(
263 reinterpret_cast<const uint8_t*>(clientReceivedMessages[0].data()),
264 reinterpret_cast<const uint8_t*>(clientReceivedMessages[0].data()) + clientReceivedMessages[0].size(),
265 pkt1.begin(), pkt1.end());
266 BOOST_CHECK_EQUAL_COLLECTIONS(
267 reinterpret_cast<const uint8_t*>(clientReceivedMessages[1].data()),
268 reinterpret_cast<const uint8_t*>(clientReceivedMessages[1].data()) + clientReceivedMessages[1].size(),
269 pkt2.begin(), pkt2.end());
270}
271
272BOOST_AUTO_TEST_CASE(Receive)
273{
274 ip::tcp::endpoint ep(ip::address_v4::loopback(), 20070);
275 this->endToEndInitialize(ep);
276
277 Block pkt1 = ndn::encoding::makeStringBlock(300, "hello");
278 client.send(clientHdl, pkt1.wire(), pkt1.size(), websocketpp::frame::opcode::binary);
279 BOOST_CHECK_EQUAL(limitedIo.run(1, // serverHandleMessage
280 time::milliseconds(1000)), LimitedIo::EXCEED_OPS);
281
282 Block pkt2 = ndn::encoding::makeStringBlock(301, "world!");
283 client.send(clientHdl, pkt2.wire(), pkt2.size(), websocketpp::frame::opcode::binary);
284 BOOST_CHECK_EQUAL(limitedIo.run(1, // serverHandleMessage
285 time::milliseconds(1000)), LimitedIo::EXCEED_OPS);
286
287 BOOST_REQUIRE_EQUAL(serverReceivedPackets->size(), 2);
288 BOOST_CHECK(serverReceivedPackets->at(0).packet == pkt1);
289 BOOST_CHECK(serverReceivedPackets->at(1).packet == pkt2);
290 BOOST_CHECK_EQUAL(serverReceivedPackets->at(0).remoteEndpoint, serverReceivedPackets->at(1).remoteEndpoint);
291}
292
293BOOST_AUTO_TEST_CASE(ReceiveMalformed)
294{
295 ip::tcp::endpoint ep(ip::address_v4::loopback(), 20070);
296 this->endToEndInitialize(ep);
297
298 Block pkt1 = ndn::encoding::makeStringBlock(300, "hello");
299 client.send(clientHdl, pkt1.wire(), pkt1.size() - 1, // truncated
300 websocketpp::frame::opcode::binary);
301 BOOST_CHECK_EQUAL(limitedIo.run(1, // serverHandleMessage
302 time::milliseconds(1000)), LimitedIo::EXCEED_OPS);
303
304 // bad packet is dropped
305 BOOST_CHECK_EQUAL(transport->getState(), TransportState::UP);
306 BOOST_CHECK_EQUAL(serverReceivedPackets->size(), 0);
307
308 Block pkt2 = ndn::encoding::makeStringBlock(301, "world!");
309 client.send(clientHdl, pkt2.wire(), pkt2.size(), websocketpp::frame::opcode::binary);
310 BOOST_CHECK_EQUAL(limitedIo.run(1, // serverHandleMessage
311 time::milliseconds(1000)), LimitedIo::EXCEED_OPS);
312
313 // next valid packet is still received normally
314 BOOST_REQUIRE_EQUAL(serverReceivedPackets->size(), 1);
315 BOOST_CHECK(serverReceivedPackets->at(0).packet == pkt2);
316}
317
318BOOST_AUTO_TEST_SUITE_END() // TestWebSocketTransport
319BOOST_AUTO_TEST_SUITE_END() // Face
320
321} // namespace tests
322} // namespace face
323} // namespace nfd