|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=2 et sw=2 tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 // Original author: ekr@rtfm.com |
|
8 |
|
9 #include <iostream> |
|
10 #include <string> |
|
11 #include <map> |
|
12 |
|
13 #include "sigslot.h" |
|
14 |
|
15 #include "logging.h" |
|
16 #include "nsNetCID.h" |
|
17 #include "nsITimer.h" |
|
18 #include "nsComponentManagerUtils.h" |
|
19 #include "nsThreadUtils.h" |
|
20 #include "nsXPCOM.h" |
|
21 |
|
22 #include "transportflow.h" |
|
23 #include "transportlayer.h" |
|
24 #include "transportlayerloopback.h" |
|
25 |
|
26 #include "mtransport_test_utils.h" |
|
27 #include "runnable_utils.h" |
|
28 #include "usrsctp.h" |
|
29 |
|
30 #define GTEST_HAS_RTTI 0 |
|
31 #include "gtest/gtest.h" |
|
32 #include "gtest_utils.h" |
|
33 |
|
34 |
|
35 using namespace mozilla; |
|
36 |
|
37 MtransportTestUtils *test_utils; |
|
38 static bool sctp_logging = false; |
|
39 static int port_number = 5000; |
|
40 |
|
41 namespace { |
|
42 |
|
43 class TransportTestPeer; |
|
44 |
|
45 class SendPeriodic : public nsITimerCallback { |
|
46 public: |
|
47 SendPeriodic(TransportTestPeer *peer, int to_send) : |
|
48 peer_(peer), |
|
49 to_send_(to_send) {} |
|
50 virtual ~SendPeriodic() {} |
|
51 |
|
52 NS_DECL_THREADSAFE_ISUPPORTS |
|
53 NS_DECL_NSITIMERCALLBACK |
|
54 |
|
55 protected: |
|
56 TransportTestPeer *peer_; |
|
57 int to_send_; |
|
58 }; |
|
59 |
|
60 NS_IMPL_ISUPPORTS(SendPeriodic, nsITimerCallback) |
|
61 |
|
62 |
|
63 class TransportTestPeer : public sigslot::has_slots<> { |
|
64 public: |
|
65 TransportTestPeer(std::string name, int local_port, int remote_port) |
|
66 : name_(name), connected_(false), |
|
67 sent_(0), received_(0), |
|
68 flow_(new TransportFlow()), |
|
69 loopback_(new TransportLayerLoopback()), |
|
70 sctp_(usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, receive_cb, nullptr, 0, nullptr)), |
|
71 timer_(do_CreateInstance(NS_TIMER_CONTRACTID)), |
|
72 periodic_(nullptr) { |
|
73 std::cerr << "Creating TransportTestPeer; flow=" << |
|
74 static_cast<void *>(flow_.get()) << |
|
75 " local=" << local_port << |
|
76 " remote=" << remote_port << std::endl; |
|
77 |
|
78 usrsctp_register_address(static_cast<void *>(this)); |
|
79 int r = usrsctp_set_non_blocking(sctp_, 1); |
|
80 EXPECT_GE(r, 0); |
|
81 |
|
82 struct linger l; |
|
83 l.l_onoff = 1; |
|
84 l.l_linger = 0; |
|
85 r = usrsctp_setsockopt(sctp_, SOL_SOCKET, SO_LINGER, &l, |
|
86 (socklen_t)sizeof(l)); |
|
87 EXPECT_GE(r, 0); |
|
88 |
|
89 struct sctp_event subscription; |
|
90 memset(&subscription, 0, sizeof(subscription)); |
|
91 subscription.se_assoc_id = SCTP_ALL_ASSOC; |
|
92 subscription.se_on = 1; |
|
93 subscription.se_type = SCTP_ASSOC_CHANGE; |
|
94 r = usrsctp_setsockopt(sctp_, IPPROTO_SCTP, SCTP_EVENT, &subscription, |
|
95 sizeof(subscription)); |
|
96 EXPECT_GE(r, 0); |
|
97 |
|
98 memset(&local_addr_, 0, sizeof(local_addr_)); |
|
99 local_addr_.sconn_family = AF_CONN; |
|
100 #if !defined(__Userspace_os_Linux) && !defined(__Userspace_os_Windows) && !defined(__Userspace_os_Android) |
|
101 local_addr_.sconn_len = sizeof(struct sockaddr_conn); |
|
102 #endif |
|
103 local_addr_.sconn_port = htons(local_port); |
|
104 local_addr_.sconn_addr = static_cast<void *>(this); |
|
105 |
|
106 |
|
107 memset(&remote_addr_, 0, sizeof(remote_addr_)); |
|
108 remote_addr_.sconn_family = AF_CONN; |
|
109 #if !defined(__Userspace_os_Linux) && !defined(__Userspace_os_Windows) && !defined(__Userspace_os_Android) |
|
110 remote_addr_.sconn_len = sizeof(struct sockaddr_conn); |
|
111 #endif |
|
112 remote_addr_.sconn_port = htons(remote_port); |
|
113 remote_addr_.sconn_addr = static_cast<void *>(this); |
|
114 |
|
115 nsresult res; |
|
116 res = loopback_->Init(); |
|
117 EXPECT_EQ((nsresult)NS_OK, res); |
|
118 } |
|
119 |
|
120 ~TransportTestPeer() { |
|
121 std::cerr << "Destroying sctp connection flow=" << |
|
122 static_cast<void *>(flow_.get()) << std::endl; |
|
123 usrsctp_close(sctp_); |
|
124 usrsctp_deregister_address(static_cast<void *>(this)); |
|
125 |
|
126 test_utils->sts_target()->Dispatch(WrapRunnable(this, |
|
127 &TransportTestPeer::Disconnect_s), |
|
128 NS_DISPATCH_SYNC); |
|
129 |
|
130 std::cerr << "~TransportTestPeer() completed" << std::endl; |
|
131 } |
|
132 |
|
133 void ConnectSocket(TransportTestPeer *peer) { |
|
134 test_utils->sts_target()->Dispatch(WrapRunnable( |
|
135 this, &TransportTestPeer::ConnectSocket_s, peer), |
|
136 NS_DISPATCH_SYNC); |
|
137 } |
|
138 |
|
139 void ConnectSocket_s(TransportTestPeer *peer) { |
|
140 loopback_->Connect(peer->loopback_); |
|
141 |
|
142 ASSERT_EQ((nsresult)NS_OK, flow_->PushLayer(loopback_)); |
|
143 |
|
144 flow_->SignalPacketReceived.connect(this, &TransportTestPeer::PacketReceived); |
|
145 |
|
146 // SCTP here! |
|
147 ASSERT_TRUE(sctp_); |
|
148 std::cerr << "Calling usrsctp_bind()" << std::endl; |
|
149 int r = usrsctp_bind(sctp_, reinterpret_cast<struct sockaddr *>( |
|
150 &local_addr_), sizeof(local_addr_)); |
|
151 ASSERT_GE(0, r); |
|
152 |
|
153 std::cerr << "Calling usrsctp_connect()" << std::endl; |
|
154 r = usrsctp_connect(sctp_, reinterpret_cast<struct sockaddr *>( |
|
155 &remote_addr_), sizeof(remote_addr_)); |
|
156 ASSERT_GE(0, r); |
|
157 } |
|
158 |
|
159 void Disconnect_s() { |
|
160 if (flow_) { |
|
161 flow_ = nullptr; |
|
162 } |
|
163 } |
|
164 |
|
165 void Disconnect() { |
|
166 loopback_->Disconnect(); |
|
167 } |
|
168 |
|
169 |
|
170 void StartTransfer(size_t to_send) { |
|
171 periodic_ = new SendPeriodic(this, to_send); |
|
172 timer_->SetTarget(test_utils->sts_target()); |
|
173 timer_->InitWithCallback(periodic_, 10, nsITimer::TYPE_REPEATING_SLACK); |
|
174 } |
|
175 |
|
176 void SendOne() { |
|
177 unsigned char buf[100]; |
|
178 memset(buf, sent_ & 0xff, sizeof(buf)); |
|
179 |
|
180 struct sctp_sndinfo info; |
|
181 info.snd_sid = 1; |
|
182 info.snd_flags = 0; |
|
183 info.snd_ppid = 50; // What the heck is this? |
|
184 info.snd_context = 0; |
|
185 info.snd_assoc_id = 0; |
|
186 |
|
187 int r = usrsctp_sendv(sctp_, buf, sizeof(buf), nullptr, 0, |
|
188 static_cast<void *>(&info), |
|
189 sizeof(info), SCTP_SENDV_SNDINFO, 0); |
|
190 ASSERT_TRUE(r >= 0); |
|
191 ASSERT_EQ(sizeof(buf), (size_t)r); |
|
192 |
|
193 ++sent_; |
|
194 } |
|
195 |
|
196 int sent() const { return sent_; } |
|
197 int received() const { return received_; } |
|
198 bool connected() const { return connected_; } |
|
199 |
|
200 static TransportResult SendPacket_s(const unsigned char* data, size_t len, |
|
201 const mozilla::RefPtr<TransportFlow>& flow) { |
|
202 TransportResult res = flow->SendPacket(data, len); |
|
203 delete data; // we always allocate |
|
204 return res; |
|
205 } |
|
206 |
|
207 TransportResult SendPacket(const unsigned char* data, size_t len) { |
|
208 unsigned char *buffer = new unsigned char[len]; |
|
209 memcpy(buffer, data, len); |
|
210 |
|
211 // Uses DISPATCH_NORMAL to avoid possible deadlocks when we're called |
|
212 // from MainThread especially during shutdown (same as DataChannels). |
|
213 // RUN_ON_THREAD short-circuits if already on the STS thread, which is |
|
214 // normal for most transfers outside of connect() and close(). Passes |
|
215 // a refptr to flow_ to avoid any async deletion issues (since we can't |
|
216 // make 'this' into a refptr as it isn't refcounted) |
|
217 RUN_ON_THREAD(test_utils->sts_target(), WrapRunnableNM( |
|
218 &TransportTestPeer::SendPacket_s, buffer, len, flow_), |
|
219 NS_DISPATCH_NORMAL); |
|
220 |
|
221 return 0; |
|
222 } |
|
223 |
|
224 void PacketReceived(TransportFlow * flow, const unsigned char* data, |
|
225 size_t len) { |
|
226 std::cerr << "Received " << len << " bytes" << std::endl; |
|
227 |
|
228 // Pass the data to SCTP |
|
229 |
|
230 usrsctp_conninput(static_cast<void *>(this), data, len, 0); |
|
231 } |
|
232 |
|
233 // Process SCTP notification |
|
234 void Notification(union sctp_notification *msg, size_t len) { |
|
235 ASSERT_EQ(msg->sn_header.sn_length, len); |
|
236 |
|
237 if (msg->sn_header.sn_type == SCTP_ASSOC_CHANGE) { |
|
238 struct sctp_assoc_change *change = &msg->sn_assoc_change; |
|
239 |
|
240 if (change->sac_state == SCTP_COMM_UP) { |
|
241 std::cerr << "Connection up" << std::endl; |
|
242 SetConnected(true); |
|
243 } else { |
|
244 std::cerr << "Connection down" << std::endl; |
|
245 SetConnected(false); |
|
246 } |
|
247 } |
|
248 } |
|
249 |
|
250 void SetConnected(bool state) { |
|
251 connected_ = state; |
|
252 } |
|
253 |
|
254 static int conn_output(void *addr, void *buffer, size_t length, uint8_t tos, uint8_t set_df) { |
|
255 TransportTestPeer *peer = static_cast<TransportTestPeer *>(addr); |
|
256 |
|
257 peer->SendPacket(static_cast<unsigned char *>(buffer), length); |
|
258 |
|
259 return 0; |
|
260 } |
|
261 |
|
262 static int receive_cb(struct socket* sock, union sctp_sockstore addr, |
|
263 void *data, size_t datalen, |
|
264 struct sctp_rcvinfo rcv, int flags, void *ulp_info) { |
|
265 TransportTestPeer *me = static_cast<TransportTestPeer *>( |
|
266 addr.sconn.sconn_addr); |
|
267 MOZ_ASSERT(me); |
|
268 |
|
269 if (flags & MSG_NOTIFICATION) { |
|
270 union sctp_notification *notif = |
|
271 static_cast<union sctp_notification *>(data); |
|
272 |
|
273 me->Notification(notif, datalen); |
|
274 return 0; |
|
275 } |
|
276 |
|
277 me->received_ += datalen; |
|
278 |
|
279 std::cerr << "receive_cb: sock " << sock << " data " << data << "(" << datalen << ") total received bytes = " << me->received_ << std::endl; |
|
280 |
|
281 return 0; |
|
282 } |
|
283 |
|
284 |
|
285 private: |
|
286 std::string name_; |
|
287 bool connected_; |
|
288 size_t sent_; |
|
289 size_t received_; |
|
290 mozilla::RefPtr<TransportFlow> flow_; |
|
291 TransportLayerLoopback *loopback_; |
|
292 |
|
293 struct sockaddr_conn local_addr_; |
|
294 struct sockaddr_conn remote_addr_; |
|
295 struct socket *sctp_; |
|
296 nsCOMPtr<nsITimer> timer_; |
|
297 nsRefPtr<SendPeriodic> periodic_; |
|
298 }; |
|
299 |
|
300 |
|
301 // Implemented here because it calls a method of TransportTestPeer |
|
302 NS_IMETHODIMP SendPeriodic::Notify(nsITimer *timer) { |
|
303 peer_->SendOne(); |
|
304 --to_send_; |
|
305 if (!to_send_) { |
|
306 timer->Cancel(); |
|
307 } |
|
308 return NS_OK; |
|
309 } |
|
310 |
|
311 class TransportTest : public ::testing::Test { |
|
312 public: |
|
313 TransportTest() { |
|
314 } |
|
315 |
|
316 ~TransportTest() { |
|
317 if (p1_) |
|
318 p1_->Disconnect(); |
|
319 if (p2_) |
|
320 p2_->Disconnect(); |
|
321 delete p1_; |
|
322 delete p2_; |
|
323 } |
|
324 |
|
325 static void debug_printf(const char *format, ...) { |
|
326 va_list ap; |
|
327 |
|
328 va_start(ap, format); |
|
329 vprintf(format, ap); |
|
330 va_end(ap); |
|
331 } |
|
332 |
|
333 |
|
334 static void SetUpTestCase() { |
|
335 if (sctp_logging) { |
|
336 usrsctp_init(0, &TransportTestPeer::conn_output, debug_printf); |
|
337 usrsctp_sysctl_set_sctp_debug_on(0xffffffff); |
|
338 } else { |
|
339 usrsctp_init(0, &TransportTestPeer::conn_output, nullptr); |
|
340 } |
|
341 } |
|
342 |
|
343 void SetUp() { |
|
344 } |
|
345 |
|
346 void ConnectSocket(int p1port = 0, int p2port = 0) { |
|
347 if (!p1port) |
|
348 p1port = port_number++; |
|
349 if (!p2port) |
|
350 p2port = port_number++; |
|
351 |
|
352 p1_ = new TransportTestPeer("P1", p1port, p2port); |
|
353 p2_ = new TransportTestPeer("P2", p2port, p1port); |
|
354 |
|
355 p1_->ConnectSocket(p2_); |
|
356 p2_->ConnectSocket(p1_); |
|
357 ASSERT_TRUE_WAIT(p1_->connected(), 2000); |
|
358 ASSERT_TRUE_WAIT(p2_->connected(), 2000); |
|
359 } |
|
360 |
|
361 void TestTransfer(int expected = 1) { |
|
362 std::cerr << "Starting trasnsfer test" << std::endl; |
|
363 p1_->StartTransfer(expected); |
|
364 ASSERT_TRUE_WAIT(p1_->sent() == expected, 10000); |
|
365 ASSERT_TRUE_WAIT(p2_->received() == (expected * 100), 10000); |
|
366 std::cerr << "P2 received " << p2_->received() << std::endl; |
|
367 } |
|
368 |
|
369 protected: |
|
370 TransportTestPeer *p1_; |
|
371 TransportTestPeer *p2_; |
|
372 }; |
|
373 |
|
374 TEST_F(TransportTest, TestConnect) { |
|
375 ConnectSocket(); |
|
376 } |
|
377 |
|
378 TEST_F(TransportTest, TestConnectSymmetricalPorts) { |
|
379 ConnectSocket(5002,5002); |
|
380 } |
|
381 |
|
382 TEST_F(TransportTest, TestTransfer) { |
|
383 ConnectSocket(); |
|
384 TestTransfer(50); |
|
385 } |
|
386 |
|
387 |
|
388 } // end namespace |
|
389 |
|
390 int main(int argc, char **argv) |
|
391 { |
|
392 test_utils = new MtransportTestUtils(); |
|
393 // Start the tests |
|
394 ::testing::InitGoogleTest(&argc, argv); |
|
395 |
|
396 for(int i=0; i<argc; i++) { |
|
397 if (!strcmp(argv[i],"-v")) { |
|
398 sctp_logging = true; |
|
399 } |
|
400 } |
|
401 |
|
402 int rv = RUN_ALL_TESTS(); |
|
403 delete test_utils; |
|
404 return rv; |
|
405 } |