blob: afa3629713b1971c69b7214248cc0fc9e67238e5 [file] [log] [blame]
Eric Newberrya98bf932015-09-21 00:58:47 -07001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/**
Weiwei Liu280d7dd2016-03-02 23:19:26 -07003 * Copyright (c) 2014-2016, Regents of the University of California,
Eric Newberrya98bf932015-09-21 00:58:47 -07004 * 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
Junxiao Shicde37ad2015-12-24 01:02:05 -070026#include "face/websocket-channel.hpp"
Weiwei Liu280d7dd2016-03-02 23:19:26 -070027#include "face/websocket-transport.hpp"
Junxiao Shicde37ad2015-12-24 01:02:05 -070028
Weiwei Liu280d7dd2016-03-02 23:19:26 -070029#include "tests/limited-io.hpp"
Junxiao Shicde37ad2015-12-24 01:02:05 -070030#include "tests/test-common.hpp"
Eric Newberrya98bf932015-09-21 00:58:47 -070031
32namespace nfd {
Junxiao Shicde37ad2015-12-24 01:02:05 -070033namespace tests {
Eric Newberrya98bf932015-09-21 00:58:47 -070034
Junxiao Shicde37ad2015-12-24 01:02:05 -070035BOOST_AUTO_TEST_SUITE(Face)
Eric Newberrya98bf932015-09-21 00:58:47 -070036
Weiwei Liu280d7dd2016-03-02 23:19:26 -070037using nfd::Face;
38namespace ip = boost::asio::ip;
Junxiao Shicde37ad2015-12-24 01:02:05 -070039
Weiwei Liu280d7dd2016-03-02 23:19:26 -070040class WebSocketChannelFixture : public BaseFixture
41{
42protected:
43 WebSocketChannelFixture()
44 : clientShouldPong(true)
45 , m_nextPort(20070)
46 {
47 }
48
49 unique_ptr<WebSocketChannel>
50 makeChannel(const ip::address& addr, uint16_t port = 0)
51 {
52 if (port == 0)
53 port = m_nextPort++;
54
55 return make_unique<WebSocketChannel>(websocket::Endpoint(addr, port));
56 }
57
58 void
59 listen(const ip::address& addr,
60 const time::milliseconds& pingInterval = time::seconds(10),
61 const time::milliseconds& pongTimeout = time::seconds(1))
62 {
63 listenerEp = websocket::Endpoint(addr, 20030);
64 listenerChannel = makeChannel(addr, 20030);
65 listenerChannel->setPingInterval(pingInterval);
66 listenerChannel->setPongTimeout(pongTimeout);
67 listenerChannel->listen(bind(&WebSocketChannelFixture::listenerOnFaceCreated, this, _1));
68 }
69
70 void
71 clientConnect(websocket::Client& client)
72 {
73 client.clear_access_channels(websocketpp::log::alevel::all);
74 client.clear_error_channels(websocketpp::log::elevel::all);
75
76 client.init_asio(&g_io);
77 client.set_open_handler(bind(&WebSocketChannelFixture::clientHandleOpen, this, _1));
78 client.set_message_handler(bind(&WebSocketChannelFixture::clientHandleMessage, this, _1, _2));
79 client.set_ping_handler(bind(&WebSocketChannelFixture::clientHandlePing, this, _1, _2));
80
81 std::string uri = "ws://" + listenerEp.address().to_string() + ":" + to_string(listenerEp.port());
82 websocketpp::lib::error_code ec;
83 websocket::Client::connection_ptr con = client.get_connection(uri, ec);
84 BOOST_REQUIRE_EQUAL(ec, websocketpp::lib::error_code());
85
86 client.connect(con);
87 }
88
89 void
90 initialize(const ip::address& addr,
91 const time::milliseconds& pingInterval = time::seconds(10),
92 const time::milliseconds& pongTimeout = time::seconds(1))
93 {
94 listen(addr, pingInterval, pongTimeout);
95 clientConnect(client);
96 BOOST_REQUIRE_EQUAL(limitedIo.run(2, // listenerOnFaceCreated, clientHandleOpen
97 time::seconds(1)), LimitedIo::EXCEED_OPS);
98 BOOST_REQUIRE_EQUAL(listenerChannel->size(), 1);
99 }
100
101 void
102 clientSendInterest(const Interest& interest)
103 {
104 const Block& payload = interest.wireEncode();
105 client.send(clientHandle, payload.wire(), payload.size(), websocketpp::frame::opcode::binary);
106 }
107
108private:
109 void
110 listenerOnFaceCreated(const shared_ptr<Face>& newFace)
111 {
112 BOOST_REQUIRE(newFace != nullptr);
113 newFace->afterReceiveInterest.connect(bind(&WebSocketChannelFixture::faceAfterReceiveInterest, this, _1));
114 connectFaceClosedSignal(*newFace, [this] { limitedIo.afterOp(); });
115 listenerFaces.push_back(newFace);
116 limitedIo.afterOp();
117 }
118
119 void
120 faceAfterReceiveInterest(const Interest& interest)
121 {
122 faceReceivedInterests.push_back(interest);
123 limitedIo.afterOp();
124 }
125
126 void
127 clientHandleOpen(websocketpp::connection_hdl hdl)
128 {
129 clientHandle = hdl;
130 limitedIo.afterOp();
131 }
132
133 void
134 clientHandleMessage(websocketpp::connection_hdl, websocket::Client::message_ptr msg)
135 {
136 clientReceivedMessages.push_back(msg->get_payload());
137 limitedIo.afterOp();
138 }
139
140 bool
141 clientHandlePing(websocketpp::connection_hdl, std::string)
142 {
143 auto now = time::steady_clock::now();
144 if (m_prevPingRecvTime != time::steady_clock::TimePoint()) {
145 measuredPingInterval = now - m_prevPingRecvTime;
146 }
147 m_prevPingRecvTime = now;
148
149 limitedIo.afterOp();
150 return clientShouldPong;
151 }
152
153protected:
154 LimitedIo limitedIo;
155
156 websocket::Endpoint listenerEp;
157 unique_ptr<WebSocketChannel> listenerChannel;
158 std::vector<shared_ptr<Face>> listenerFaces;
159 std::vector<Interest> faceReceivedInterests;
160
161 websocket::Client client;
162 websocketpp::connection_hdl clientHandle;
163 std::vector<std::string> clientReceivedMessages;
164 time::steady_clock::Duration measuredPingInterval;
165 bool clientShouldPong; // set clientShouldPong false to disable the pong response,
166 // which will cause timeout in listenerChannel
167
168private:
169 uint16_t m_nextPort;
170 time::steady_clock::TimePoint m_prevPingRecvTime;
171};
172
173BOOST_FIXTURE_TEST_SUITE(TestWebSocketChannel, WebSocketChannelFixture)
174
175BOOST_AUTO_TEST_CASE(Uri)
176{
177 websocket::Endpoint ep(ip::address_v4::loopback(), 20070);
178 auto channel = makeChannel(ep.address(), ep.port());
179 BOOST_CHECK_EQUAL(channel->getUri(), FaceUri("ws://127.0.0.1:20070"));
180}
181
182BOOST_AUTO_TEST_CASE(Listen)
183{
184 auto channel = makeChannel(ip::address_v4());
185 BOOST_CHECK_EQUAL(channel->isListening(), false);
186
187 channel->listen(nullptr);
188 BOOST_CHECK_EQUAL(channel->isListening(), true);
189
190 // listen() is idempotent
191 BOOST_CHECK_NO_THROW(channel->listen(nullptr));
192 BOOST_CHECK_EQUAL(channel->isListening(), true);
193}
194
195BOOST_AUTO_TEST_CASE(MultipleAccepts)
196{
197 listen(ip::address_v4::loopback());
198
199 BOOST_CHECK_EQUAL(listenerChannel->isListening(), true);
200 BOOST_CHECK_EQUAL(listenerChannel->size(), 0);
201
202 websocket::Client client1;
203 clientConnect(client1);
204 BOOST_CHECK_EQUAL(limitedIo.run(2, // listenerOnFaceCreated, clientHandleOpen
205 time::seconds(1)), LimitedIo::EXCEED_OPS);
206 BOOST_CHECK_EQUAL(listenerChannel->size(), 1);
207
208 websocket::Client client2;
209 websocket::Client client3;
210 clientConnect(client2);
211 clientConnect(client3);
212 BOOST_CHECK_EQUAL(limitedIo.run(4, // 2 listenerOnFaceCreated, 2 clientHandleOpen
213 time::seconds(1)), LimitedIo::EXCEED_OPS);
214 BOOST_CHECK_EQUAL(listenerChannel->size(), 3);
215
216 // check face persistency
217 for (const auto& face : listenerFaces) {
218 BOOST_CHECK_EQUAL(face->getPersistency(), ndn::nfd::FACE_PERSISTENCY_ON_DEMAND);
219 }
220}
221
222BOOST_AUTO_TEST_CASE(Send)
223{
224 initialize(ip::address_v4::loopback());
225 auto transport = listenerFaces.front()->getTransport();
226
227 Block pkt1 = ndn::encoding::makeStringBlock(300, "hello");
228 transport->send(face::Transport::Packet(Block(pkt1)));
229 BOOST_CHECK_EQUAL(limitedIo.run(1, // clientHandleMessage
230 time::seconds(1)), LimitedIo::EXCEED_OPS);
231
232 Block pkt2 = ndn::encoding::makeStringBlock(301, "world!");
233 transport->send(face::Transport::Packet(Block(pkt2)));
234 BOOST_CHECK_EQUAL(limitedIo.run(1, // clientHandleMessage
235 time::seconds(1)), LimitedIo::EXCEED_OPS);
236
237 BOOST_REQUIRE_EQUAL(clientReceivedMessages.size(), 2);
238 BOOST_CHECK_EQUAL_COLLECTIONS(
239 reinterpret_cast<const uint8_t*>(clientReceivedMessages[0].data()),
240 reinterpret_cast<const uint8_t*>(clientReceivedMessages[0].data()) + clientReceivedMessages[0].size(),
241 pkt1.begin(), pkt1.end());
242 BOOST_CHECK_EQUAL_COLLECTIONS(
243 reinterpret_cast<const uint8_t*>(clientReceivedMessages[1].data()),
244 reinterpret_cast<const uint8_t*>(clientReceivedMessages[1].data()) + clientReceivedMessages[1].size(),
245 pkt2.begin(), pkt2.end());
246}
247
248BOOST_AUTO_TEST_CASE(Receive)
249{
250 initialize(ip::address_v4::loopback());
251
252 // use network-layer packets here, otherwise GenericLinkService
253 // won't recognize the packet type and will discard it
254 auto interest1 = makeInterest("ndn:/TpnzGvW9R");
255 auto interest2 = makeInterest("ndn:/QWiIMfj5sL");
256
257 clientSendInterest(*interest1);
258 BOOST_CHECK_EQUAL(limitedIo.run(1, // faceAfterReceiveInterest
259 time::seconds(1)), LimitedIo::EXCEED_OPS);
260
261 clientSendInterest(*interest2);
262 BOOST_CHECK_EQUAL(limitedIo.run(1, // faceAfterReceiveInterest
263 time::seconds(1)), LimitedIo::EXCEED_OPS);
264
265 BOOST_REQUIRE_EQUAL(faceReceivedInterests.size(), 2);
266 BOOST_CHECK_EQUAL(faceReceivedInterests[0].getName(), interest1->getName());
267 BOOST_CHECK_EQUAL(faceReceivedInterests[1].getName(), interest2->getName());
268}
269
270BOOST_AUTO_TEST_CASE(FaceClosure)
271{
272 initialize(ip::address_v4::loopback());
273
274 listenerFaces.front()->close();
275 BOOST_CHECK_EQUAL(listenerChannel->size(), 0);
276}
277
278BOOST_AUTO_TEST_CASE(RemoteClose)
279{
280 initialize(ip::address_v4::loopback());
281
282 client.close(clientHandle, websocketpp::close::status::going_away, "");
283 BOOST_CHECK_EQUAL(limitedIo.run(1, // faceClosedSignal
284 time::seconds(1)), LimitedIo::EXCEED_OPS);
285 BOOST_CHECK_EQUAL(listenerChannel->size(), 0);
286}
287
288BOOST_AUTO_TEST_CASE(SetPingInterval)
289{
290 auto pingInterval = time::milliseconds(300);
291 initialize(ip::address_v4::loopback(), pingInterval, time::milliseconds(1000));
292
293 BOOST_CHECK_EQUAL(limitedIo.run(2, // clientHandlePing
294 time::seconds(1)), LimitedIo::EXCEED_OPS);
295 BOOST_CHECK_LE(measuredPingInterval, pingInterval * 1.1);
296 BOOST_CHECK_GE(measuredPingInterval, pingInterval * 0.9);
297}
298
299BOOST_AUTO_TEST_CASE(SetPongTimeOut)
300{
301 initialize(ip::address_v4::loopback(), time::milliseconds(500), time::milliseconds(300));
302
303 clientShouldPong = false;
304 BOOST_CHECK_EQUAL(limitedIo.run(2, // clientHandlePing, faceClosedSignal
305 time::seconds(2)), LimitedIo::EXCEED_OPS);
306 BOOST_CHECK_EQUAL(listenerChannel->size(), 0);
307
308 auto transport = static_cast<face::WebSocketTransport*>(listenerFaces.front()->getTransport());
309 BOOST_CHECK(transport->getState() == face::TransportState::FAILED ||
310 transport->getState() == face::TransportState::CLOSED);
311 BOOST_CHECK_EQUAL(transport->getCounters().nOutPings, 1);
312 BOOST_CHECK_EQUAL(transport->getCounters().nInPongs, 0);
313}
Junxiao Shicde37ad2015-12-24 01:02:05 -0700314
315BOOST_AUTO_TEST_SUITE_END() // TestWebSocketChannel
316BOOST_AUTO_TEST_SUITE_END() // Face
317
318} // namespace tests
Eric Newberrya98bf932015-09-21 00:58:47 -0700319} // namespace nfd