blob: ca9bd728d0b20b8683eeeee03b70af6dde4df326 [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"
Eric Newberry8717e872015-11-23 12:41:50 -070029#include "transport-test-common.hpp"
Yukai Tu2d6d5632015-10-26 11:06:02 -070030
Yukai Tu2d6d5632015-10-26 11:06:02 -070031#include "tests/limited-io.hpp"
32
33namespace nfd {
34namespace face {
35namespace tests {
36
37using namespace nfd::tests;
38namespace ip = boost::asio::ip;
39
40BOOST_AUTO_TEST_SUITE(Face)
41
42/** \brief a fixture that accepts a single WebSocket connection from a client
43 */
44class SingleWebSocketFixture : public BaseFixture
45{
46public:
47 SingleWebSocketFixture()
48 : transport(nullptr)
49 , serverReceivedPackets(nullptr)
50 , clientShouldPong(true)
51 {
52 }
53
54 /** \brief initialize server and start listening
55 */
56 void
57 serverListen(const ip::tcp::endpoint& ep,
58 const time::milliseconds& pongTimeout = time::milliseconds(1000))
59 {
60 server.clear_access_channels(websocketpp::log::alevel::all);
61 server.clear_error_channels(websocketpp::log::elevel::all);
62
63 server.init_asio(&g_io);
64 server.set_open_handler(bind(&SingleWebSocketFixture::serverHandleOpen, this, _1));
65 server.set_close_handler(bind(&SingleWebSocketFixture::serverHandleClose, this));
66 server.set_message_handler(bind(&SingleWebSocketFixture::serverHandleMessage, this, _2));
67 server.set_pong_handler(bind(&SingleWebSocketFixture::serverHandlePong, this));
68 server.set_pong_timeout_handler(bind(&SingleWebSocketFixture::serverHandlePongTimeout, this));
69 server.set_pong_timeout(pongTimeout.count());
70
71 server.set_reuse_addr(true);
72
73 server.listen(ep);
74 server.start_accept();
75 }
76
77 /** \brief initialize client and connect to server
78 */
79 void
80 clientConnect(const std::string& uri)
81 {
82 client.clear_access_channels(websocketpp::log::alevel::all);
83 client.clear_error_channels(websocketpp::log::elevel::all);
84
85 client.init_asio(&g_io);
86 client.set_open_handler(bind(&SingleWebSocketFixture::clientHandleOpen, this, _1));
87 client.set_message_handler(bind(&SingleWebSocketFixture::clientHandleMessage, this, _2));
88 client.set_ping_handler(bind(&SingleWebSocketFixture::clientHandlePing, this));
89
90 websocketpp::lib::error_code ec;
91 websocket::Client::connection_ptr con = client.get_connection("ws://127.0.0.1:20070", ec);
92 BOOST_REQUIRE(!ec);
93
94 client.connect(con);
95 }
96
97 void
98 makeFace(const time::milliseconds& pingInterval = time::milliseconds(10000))
99 {
100 face = make_unique<LpFace>(
101 make_unique<DummyReceiveLinkService>(),
102 make_unique<WebSocketTransport>(serverHdl, ref(server), pingInterval));
103 transport = static_cast<WebSocketTransport*>(face->getTransport());
104 serverReceivedPackets = &static_cast<DummyReceiveLinkService*>(face->getLinkService())->receivedPackets;
105 }
106
107 /** \brief initialize both server and client, and have each other connected, create Transport
108 */
109 void
110 endToEndInitialize(const ip::tcp::endpoint& ep,
111 const time::milliseconds& pingInterval = time::milliseconds(10000),
112 const time::milliseconds& pongTimeout = time::milliseconds(1000))
113 {
114 this->serverListen(ep, pongTimeout);
115 std::string uri = "ws://" + ep.address().to_string() + ":" + to_string(ep.port());
116 this->clientConnect(uri);
117 BOOST_REQUIRE_EQUAL(limitedIo.run(2, // serverHandleOpen, clientHandleOpen
118 time::seconds(1)), LimitedIo::EXCEED_OPS);
119 this->makeFace(pingInterval);
120 }
121
122private:
123 void
124 serverHandleOpen(websocketpp::connection_hdl hdl)
125 {
126 serverHdl = hdl;
127 limitedIo.afterOp();
128 }
129
130 void
131 serverHandleClose()
132 {
133 if (transport == nullptr) {
134 return;
135 }
136
137 transport->close();
138 limitedIo.afterOp();
139 }
140
141 void
142 serverHandleMessage(websocket::Server::message_ptr msg)
143 {
144 if (transport == nullptr) {
145 return;
146 }
147
148 transport->receiveMessage(msg->get_payload());
149 limitedIo.afterOp();
150 }
151
152 void
153 serverHandlePong()
154 {
155 if (transport == nullptr) {
156 return;
157 }
158
159 transport->handlePong();
160 limitedIo.afterOp();
161 }
162
163 void
164 serverHandlePongTimeout()
165 {
166 if (transport == nullptr) {
167 return;
168 }
169
170 transport->handlePongTimeout();
171 limitedIo.afterOp();
172 }
173
174 void
175 clientHandleOpen(websocketpp::connection_hdl hdl)
176 {
177 clientHdl = hdl;
178 limitedIo.afterOp();
179 }
180
181 void
182 clientHandleMessage(websocket::Client::message_ptr msg)
183 {
184 clientReceivedMessages.push_back(msg->get_payload());
185 limitedIo.afterOp();
186 }
187
188 bool
189 clientHandlePing()
190 {
191 limitedIo.afterOp();
192 return clientShouldPong;
193 }
194
195public:
196 LimitedIo limitedIo;
197
198 websocket::Server server;
199 websocketpp::connection_hdl serverHdl;
200 unique_ptr<LpFace> face;
201 WebSocketTransport* transport;
202 std::vector<Transport::Packet>* serverReceivedPackets;
203
204 websocket::Client client;
205 websocketpp::connection_hdl clientHdl;
206 bool clientShouldPong;
207 std::vector<std::string> clientReceivedMessages;
208};
209
210BOOST_FIXTURE_TEST_SUITE(TestWebSocketTransport, SingleWebSocketFixture)
211
212BOOST_AUTO_TEST_CASE(StaticProperties)
213{
214 ip::tcp::endpoint ep(ip::address_v4::loopback(), 20070);
215 this->endToEndInitialize(ep);
216 checkStaticPropertiesInitialized(*transport);
217
218 BOOST_CHECK_EQUAL(transport->getLocalUri(), FaceUri("ws://127.0.0.1:20070"));
219 BOOST_CHECK_EQUAL(transport->getRemoteUri().getScheme(), "wsclient");
220 BOOST_CHECK_EQUAL(transport->getRemoteUri().getHost(), "127.0.0.1");
221 BOOST_CHECK_EQUAL(transport->getRemoteUri().getPath(), "");
222 BOOST_CHECK_EQUAL(transport->getScope(), ndn::nfd::FACE_SCOPE_LOCAL);
223 BOOST_CHECK_EQUAL(transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
224 BOOST_CHECK_EQUAL(transport->getLinkType(), ndn::nfd::LINK_TYPE_POINT_TO_POINT);
225 BOOST_CHECK_EQUAL(transport->getMtu(), MTU_UNLIMITED);
226}
227
228BOOST_AUTO_TEST_CASE(PingPong)
229{
230 ip::tcp::endpoint ep(ip::address_v4::loopback(), 20070);
231 this->endToEndInitialize(ep, time::milliseconds(500), time::milliseconds(300));
232
233 BOOST_CHECK_EQUAL(limitedIo.run(2, // clientHandlePing, serverHandlePong
234 time::milliseconds(1500)), LimitedIo::EXCEED_OPS);
235 BOOST_CHECK_EQUAL(transport->getState(), TransportState::UP);
Junxiao Shi57df2882015-11-11 06:12:35 -0700236 BOOST_CHECK_EQUAL(transport->getCounters().nOutPings, 1);
237 BOOST_CHECK_EQUAL(transport->getCounters().nInPongs, 1);
Yukai Tu2d6d5632015-10-26 11:06:02 -0700238
239 this->clientShouldPong = false;
240 BOOST_CHECK_EQUAL(limitedIo.run(2, // clientHandlePing, serverHandlePongTimeout
241 time::milliseconds(2000)), LimitedIo::EXCEED_OPS);
242 BOOST_CHECK_MESSAGE(transport->getState() == TransportState::FAILED ||
243 transport->getState() == TransportState::CLOSED,
244 "expect FAILED or CLOSED state, actual state=" << transport->getState());
Junxiao Shi57df2882015-11-11 06:12:35 -0700245 BOOST_CHECK_EQUAL(transport->getCounters().nOutPings, 2);
246 BOOST_CHECK_EQUAL(transport->getCounters().nInPongs, 1);
Yukai Tu2d6d5632015-10-26 11:06:02 -0700247}
248
249BOOST_AUTO_TEST_CASE(Send)
250{
251 ip::tcp::endpoint ep(ip::address_v4::loopback(), 20070);
252 this->endToEndInitialize(ep);
253
254 Block pkt1 = ndn::encoding::makeStringBlock(300, "hello");
255 transport->send(Transport::Packet(Block(pkt1)));
256 BOOST_CHECK_EQUAL(limitedIo.run(1, // clientHandleMessage
257 time::milliseconds(1000)), LimitedIo::EXCEED_OPS);
258
259 Block pkt2 = ndn::encoding::makeStringBlock(301, "world!");
260 transport->send(Transport::Packet(Block(pkt2)));
261 BOOST_CHECK_EQUAL(limitedIo.run(1, // clientHandleMessage
262 time::milliseconds(1000)), LimitedIo::EXCEED_OPS);
263
264 BOOST_REQUIRE_EQUAL(clientReceivedMessages.size(), 2);
265 BOOST_CHECK_EQUAL_COLLECTIONS(
266 reinterpret_cast<const uint8_t*>(clientReceivedMessages[0].data()),
267 reinterpret_cast<const uint8_t*>(clientReceivedMessages[0].data()) + clientReceivedMessages[0].size(),
268 pkt1.begin(), pkt1.end());
269 BOOST_CHECK_EQUAL_COLLECTIONS(
270 reinterpret_cast<const uint8_t*>(clientReceivedMessages[1].data()),
271 reinterpret_cast<const uint8_t*>(clientReceivedMessages[1].data()) + clientReceivedMessages[1].size(),
272 pkt2.begin(), pkt2.end());
273}
274
275BOOST_AUTO_TEST_CASE(Receive)
276{
277 ip::tcp::endpoint ep(ip::address_v4::loopback(), 20070);
278 this->endToEndInitialize(ep);
279
280 Block pkt1 = ndn::encoding::makeStringBlock(300, "hello");
281 client.send(clientHdl, pkt1.wire(), pkt1.size(), websocketpp::frame::opcode::binary);
282 BOOST_CHECK_EQUAL(limitedIo.run(1, // serverHandleMessage
283 time::milliseconds(1000)), LimitedIo::EXCEED_OPS);
284
285 Block pkt2 = ndn::encoding::makeStringBlock(301, "world!");
286 client.send(clientHdl, pkt2.wire(), pkt2.size(), websocketpp::frame::opcode::binary);
287 BOOST_CHECK_EQUAL(limitedIo.run(1, // serverHandleMessage
288 time::milliseconds(1000)), LimitedIo::EXCEED_OPS);
289
290 BOOST_REQUIRE_EQUAL(serverReceivedPackets->size(), 2);
291 BOOST_CHECK(serverReceivedPackets->at(0).packet == pkt1);
292 BOOST_CHECK(serverReceivedPackets->at(1).packet == pkt2);
293 BOOST_CHECK_EQUAL(serverReceivedPackets->at(0).remoteEndpoint, serverReceivedPackets->at(1).remoteEndpoint);
294}
295
296BOOST_AUTO_TEST_CASE(ReceiveMalformed)
297{
298 ip::tcp::endpoint ep(ip::address_v4::loopback(), 20070);
299 this->endToEndInitialize(ep);
300
301 Block pkt1 = ndn::encoding::makeStringBlock(300, "hello");
302 client.send(clientHdl, pkt1.wire(), pkt1.size() - 1, // truncated
303 websocketpp::frame::opcode::binary);
304 BOOST_CHECK_EQUAL(limitedIo.run(1, // serverHandleMessage
305 time::milliseconds(1000)), LimitedIo::EXCEED_OPS);
306
307 // bad packet is dropped
308 BOOST_CHECK_EQUAL(transport->getState(), TransportState::UP);
309 BOOST_CHECK_EQUAL(serverReceivedPackets->size(), 0);
310
311 Block pkt2 = ndn::encoding::makeStringBlock(301, "world!");
312 client.send(clientHdl, pkt2.wire(), pkt2.size(), websocketpp::frame::opcode::binary);
313 BOOST_CHECK_EQUAL(limitedIo.run(1, // serverHandleMessage
314 time::milliseconds(1000)), LimitedIo::EXCEED_OPS);
315
316 // next valid packet is still received normally
317 BOOST_REQUIRE_EQUAL(serverReceivedPackets->size(), 1);
318 BOOST_CHECK(serverReceivedPackets->at(0).packet == pkt2);
319}
320
321BOOST_AUTO_TEST_SUITE_END() // TestWebSocketTransport
322BOOST_AUTO_TEST_SUITE_END() // Face
323
324} // namespace tests
325} // namespace face
326} // namespace nfd