media/mtransport/test/transport_unittests.cpp

branch
TOR_BUG_9701
changeset 8
97036ab72558
equal deleted inserted replaced
-1:000000000000 0:621de59dee1a
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 "nspr.h"
17 #include "nss.h"
18 #include "ssl.h"
19
20 #include "nsThreadUtils.h"
21 #include "nsXPCOM.h"
22
23 #include "dtlsidentity.h"
24 #include "nricectx.h"
25 #include "nricemediastream.h"
26 #include "transportflow.h"
27 #include "transportlayer.h"
28 #include "transportlayerdtls.h"
29 #include "transportlayerice.h"
30 #include "transportlayerlog.h"
31 #include "transportlayerloopback.h"
32
33 #include "mtransport_test_utils.h"
34 #include "runnable_utils.h"
35
36 #define GTEST_HAS_RTTI 0
37 #include "gtest/gtest.h"
38 #include "gtest_utils.h"
39
40 using namespace mozilla;
41 MOZ_MTLOG_MODULE("mtransport")
42
43 MtransportTestUtils *test_utils;
44
45 // Layer class which can't be initialized.
46 class TransportLayerDummy : public TransportLayer {
47 public:
48 TransportLayerDummy(bool allow_init, bool *destroyed)
49 : allow_init_(allow_init),
50 destroyed_(destroyed) {
51 *destroyed_ = false;
52 }
53
54 virtual ~TransportLayerDummy() {
55 *destroyed_ = true;
56 }
57
58 virtual nsresult InitInternal() {
59 return allow_init_ ? NS_OK : NS_ERROR_FAILURE;
60 }
61
62 virtual TransportResult SendPacket(const unsigned char *data, size_t len) {
63 MOZ_CRASH(); // Should never be called.
64 return 0;
65 }
66
67 TRANSPORT_LAYER_ID("lossy")
68
69 private:
70 bool allow_init_;
71 bool *destroyed_;
72 };
73
74 // Class to simulate various kinds of network lossage
75 class TransportLayerLossy : public TransportLayer {
76 public:
77 TransportLayerLossy() : loss_mask_(0), packet_(0) {}
78 ~TransportLayerLossy () {}
79
80 virtual TransportResult SendPacket(const unsigned char *data, size_t len) {
81 MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "SendPacket(" << len << ")");
82
83 if (loss_mask_ & (1 << (packet_ % 32))) {
84 MOZ_MTLOG(ML_NOTICE, "Dropping packet");
85 ++packet_;
86 return len;
87 }
88
89 ++packet_;
90
91 return downward_->SendPacket(data, len);
92 }
93
94 void SetLoss(uint32_t packet) {
95 loss_mask_ |= (1 << (packet & 32));
96 }
97
98 void StateChange(TransportLayer *layer, State state) {
99 TL_SET_STATE(state);
100 }
101
102 void PacketReceived(TransportLayer *layer, const unsigned char *data,
103 size_t len) {
104 SignalPacketReceived(this, data, len);
105 }
106
107 TRANSPORT_LAYER_ID("lossy")
108
109 protected:
110 virtual void WasInserted() {
111 downward_->SignalPacketReceived.
112 connect(this,
113 &TransportLayerLossy::PacketReceived);
114 downward_->SignalStateChange.
115 connect(this,
116 &TransportLayerLossy::StateChange);
117
118 TL_SET_STATE(downward_->state());
119 }
120
121 private:
122 uint32_t loss_mask_;
123 uint32_t packet_;
124 };
125
126 namespace {
127 class TransportTestPeer : public sigslot::has_slots<> {
128 public:
129 TransportTestPeer(nsCOMPtr<nsIEventTarget> target, std::string name)
130 : name_(name), target_(target),
131 received_(0), flow_(new TransportFlow(name)),
132 loopback_(new TransportLayerLoopback()),
133 logging_(new TransportLayerLogging()),
134 lossy_(new TransportLayerLossy()),
135 dtls_(new TransportLayerDtls()),
136 identity_(DtlsIdentity::Generate()),
137 ice_ctx_(NrIceCtx::Create(name,
138 name == "P2" ?
139 TransportLayerDtls::CLIENT :
140 TransportLayerDtls::SERVER)),
141 streams_(), candidates_(),
142 peer_(nullptr),
143 gathering_complete_(false)
144 {
145 std::vector<NrIceStunServer> stun_servers;
146 ScopedDeletePtr<NrIceStunServer> server(NrIceStunServer::Create(
147 std::string((char *)"216.93.246.14"), 3478));
148 stun_servers.push_back(*server);
149 EXPECT_TRUE(NS_SUCCEEDED(ice_ctx_->SetStunServers(stun_servers)));
150
151 dtls_->SetIdentity(identity_);
152 dtls_->SetRole(name == "P2" ?
153 TransportLayerDtls::CLIENT :
154 TransportLayerDtls::SERVER);
155
156 nsresult res = identity_->ComputeFingerprint("sha-1",
157 fingerprint_,
158 sizeof(fingerprint_),
159 &fingerprint_len_);
160 EXPECT_TRUE(NS_SUCCEEDED(res));
161 EXPECT_EQ(20u, fingerprint_len_);
162 }
163
164 ~TransportTestPeer() {
165 test_utils->sts_target()->Dispatch(
166 WrapRunnable(this, &TransportTestPeer::DestroyFlow),
167 NS_DISPATCH_SYNC);
168 }
169
170
171 void DestroyFlow() {
172 if (flow_) {
173 loopback_->Disconnect();
174 flow_ = nullptr;
175 }
176 ice_ctx_ = nullptr;
177 }
178
179 void DisconnectDestroyFlow() {
180 loopback_->Disconnect();
181 disconnect_all(); // Disconnect from the signals;
182 flow_ = nullptr;
183 }
184
185 void SetDtlsAllowAll() {
186 nsresult res = dtls_->SetVerificationAllowAll();
187 ASSERT_TRUE(NS_SUCCEEDED(res));
188 }
189
190 void SetDtlsPeer(TransportTestPeer *peer, int digests, unsigned int damage) {
191 unsigned int mask = 1;
192
193 for (int i=0; i<digests; i++) {
194 unsigned char fingerprint_to_set[TransportLayerDtls::kMaxDigestLength];
195
196 memcpy(fingerprint_to_set,
197 peer->fingerprint_,
198 peer->fingerprint_len_);
199 if (damage & mask)
200 fingerprint_to_set[0]++;
201
202 nsresult res = dtls_->SetVerificationDigest(
203 "sha-1",
204 fingerprint_to_set,
205 peer->fingerprint_len_);
206
207 ASSERT_TRUE(NS_SUCCEEDED(res));
208
209 mask <<= 1;
210 }
211 }
212
213
214 void ConnectSocket_s(TransportTestPeer *peer) {
215 nsresult res;
216 res = loopback_->Init();
217 ASSERT_EQ((nsresult)NS_OK, res);
218
219 loopback_->Connect(peer->loopback_);
220
221 ASSERT_EQ((nsresult)NS_OK, flow_->PushLayer(loopback_));
222 ASSERT_EQ((nsresult)NS_OK, flow_->PushLayer(logging_));
223 ASSERT_EQ((nsresult)NS_OK, flow_->PushLayer(lossy_));
224 ASSERT_EQ((nsresult)NS_OK, flow_->PushLayer(dtls_));
225
226 flow_->SignalPacketReceived.connect(this, &TransportTestPeer::PacketReceived);
227 }
228
229 void ConnectSocket(TransportTestPeer *peer) {
230 RUN_ON_THREAD(test_utils->sts_target(),
231 WrapRunnable(this, & TransportTestPeer::ConnectSocket_s,
232 peer),
233 NS_DISPATCH_SYNC);
234 }
235
236 void InitIce() {
237 nsresult res;
238
239 // Attach our slots
240 ice_ctx_->SignalGatheringStateChange.
241 connect(this, &TransportTestPeer::GatheringStateChange);
242
243 char name[100];
244 snprintf(name, sizeof(name), "%s:stream%d", name_.c_str(),
245 (int)streams_.size());
246
247 // Create the media stream
248 mozilla::RefPtr<NrIceMediaStream> stream =
249 ice_ctx_->CreateStream(static_cast<char *>(name), 1);
250 ASSERT_TRUE(stream != nullptr);
251 streams_.push_back(stream);
252
253 // Listen for candidates
254 stream->SignalCandidate.
255 connect(this, &TransportTestPeer::GotCandidate);
256
257 // Create the transport layer
258 ice_ = new TransportLayerIce(name, ice_ctx_, stream, 1);
259
260 // Assemble the stack
261 nsAutoPtr<std::queue<mozilla::TransportLayer *> > layers(
262 new std::queue<mozilla::TransportLayer *>);
263 layers->push(ice_);
264 layers->push(dtls_);
265
266 test_utils->sts_target()->Dispatch(
267 WrapRunnableRet(flow_, &TransportFlow::PushLayers, layers, &res),
268 NS_DISPATCH_SYNC);
269
270 ASSERT_EQ((nsresult)NS_OK, res);
271
272 // Listen for media events
273 flow_->SignalPacketReceived.connect(this, &TransportTestPeer::PacketReceived);
274 flow_->SignalStateChange.connect(this, &TransportTestPeer::StateChanged);
275
276 // Start gathering
277 test_utils->sts_target()->Dispatch(
278 WrapRunnableRet(ice_ctx_, &NrIceCtx::StartGathering, &res),
279 NS_DISPATCH_SYNC);
280 ASSERT_TRUE(NS_SUCCEEDED(res));
281 }
282
283 void ConnectIce(TransportTestPeer *peer) {
284 peer_ = peer;
285
286 // If gathering is already complete, push the candidates over
287 if (gathering_complete_)
288 GatheringComplete();
289 }
290
291 // New candidate
292 void GotCandidate(NrIceMediaStream *stream, const std::string &candidate) {
293 std::cerr << "Got candidate " << candidate << std::endl;
294 candidates_[stream->name()].push_back(candidate);
295 }
296
297 void GatheringStateChange(NrIceCtx* ctx,
298 NrIceCtx::GatheringState state) {
299 (void)ctx;
300 if (state == NrIceCtx::ICE_CTX_GATHER_COMPLETE) {
301 GatheringComplete();
302 }
303 }
304
305 // Gathering complete, so send our candidates and start
306 // connecting on the other peer.
307 void GatheringComplete() {
308 nsresult res;
309
310 // Don't send to the other side
311 if (!peer_) {
312 gathering_complete_ = true;
313 return;
314 }
315
316 // First send attributes
317 test_utils->sts_target()->Dispatch(
318 WrapRunnableRet(peer_->ice_ctx_,
319 &NrIceCtx::ParseGlobalAttributes,
320 ice_ctx_->GetGlobalAttributes(), &res),
321 NS_DISPATCH_SYNC);
322 ASSERT_TRUE(NS_SUCCEEDED(res));
323
324 for (size_t i=0; i<streams_.size(); ++i) {
325 test_utils->sts_target()->Dispatch(
326 WrapRunnableRet(peer_->streams_[i], &NrIceMediaStream::ParseAttributes,
327 candidates_[streams_[i]->name()], &res), NS_DISPATCH_SYNC);
328
329 ASSERT_TRUE(NS_SUCCEEDED(res));
330 }
331
332 // Start checks on the other peer.
333 test_utils->sts_target()->Dispatch(
334 WrapRunnableRet(peer_->ice_ctx_, &NrIceCtx::StartChecks, &res),
335 NS_DISPATCH_SYNC);
336 ASSERT_TRUE(NS_SUCCEEDED(res));
337 }
338
339 TransportResult SendPacket(const unsigned char* data, size_t len) {
340 TransportResult ret;
341 test_utils->sts_target()->Dispatch(
342 WrapRunnableRet(flow_, &TransportFlow::SendPacket, data, len, &ret),
343 NS_DISPATCH_SYNC);
344
345 return ret;
346 }
347
348
349 void StateChanged(TransportFlow *flow, TransportLayer::State state) {
350 if (state == TransportLayer::TS_OPEN) {
351 std::cerr << "Now connected" << std::endl;
352 }
353 }
354
355 void PacketReceived(TransportFlow * flow, const unsigned char* data,
356 size_t len) {
357 std::cerr << "Received " << len << " bytes" << std::endl;
358 ++received_;
359 }
360
361 void SetLoss(uint32_t loss) {
362 lossy_->SetLoss(loss);
363 }
364
365 TransportLayer::State state() {
366 TransportLayer::State tstate;
367
368 RUN_ON_THREAD(test_utils->sts_target(),
369 WrapRunnableRet(flow_, &TransportFlow::state, &tstate),
370 NS_DISPATCH_SYNC);
371
372 return tstate;
373 }
374
375 bool connected() {
376 return state() == TransportLayer::TS_OPEN;
377 }
378
379 bool failed() {
380 return state() == TransportLayer::TS_ERROR;
381 }
382
383 size_t received() { return received_; }
384
385 private:
386 std::string name_;
387 nsCOMPtr<nsIEventTarget> target_;
388 size_t received_;
389 mozilla::RefPtr<TransportFlow> flow_;
390 TransportLayerLoopback *loopback_;
391 TransportLayerLogging *logging_;
392 TransportLayerLossy *lossy_;
393 TransportLayerDtls *dtls_;
394 TransportLayerIce *ice_;
395 mozilla::RefPtr<DtlsIdentity> identity_;
396 mozilla::RefPtr<NrIceCtx> ice_ctx_;
397 std::vector<mozilla::RefPtr<NrIceMediaStream> > streams_;
398 std::map<std::string, std::vector<std::string> > candidates_;
399 TransportTestPeer *peer_;
400 bool gathering_complete_;
401 unsigned char fingerprint_[TransportLayerDtls::kMaxDigestLength];
402 size_t fingerprint_len_;
403 };
404
405
406 class TransportTest : public ::testing::Test {
407 public:
408 TransportTest() {
409 fds_[0] = nullptr;
410 fds_[1] = nullptr;
411 }
412
413 ~TransportTest() {
414 delete p1_;
415 delete p2_;
416
417 // Can't detach these
418 // PR_Close(fds_[0]);
419 // PR_Close(fds_[1]);
420 }
421
422 void DestroyPeerFlows() {
423 p1_->DisconnectDestroyFlow();
424 p2_->DisconnectDestroyFlow();
425 }
426
427 void SetUp() {
428 nsresult rv;
429 target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
430 ASSERT_TRUE(NS_SUCCEEDED(rv));
431
432 p1_ = new TransportTestPeer(target_, "P1");
433 p2_ = new TransportTestPeer(target_, "P2");
434 }
435
436 void SetDtlsPeer(int digests = 1, unsigned int damage = 0) {
437 p1_->SetDtlsPeer(p2_, digests, damage);
438 p2_->SetDtlsPeer(p1_, digests, damage);
439 }
440
441 void SetDtlsAllowAll() {
442 p1_->SetDtlsAllowAll();
443 p2_->SetDtlsAllowAll();
444 }
445
446 void ConnectSocket() {
447 test_utils->sts_target()->Dispatch(
448 WrapRunnable(p1_, &TransportTestPeer::ConnectSocket, p2_),
449 NS_DISPATCH_SYNC);
450 test_utils->sts_target()->Dispatch(
451 WrapRunnable(p2_, &TransportTestPeer::ConnectSocket, p1_),
452 NS_DISPATCH_SYNC);
453
454 ASSERT_TRUE_WAIT(p1_->connected(), 10000);
455 ASSERT_TRUE_WAIT(p2_->connected(), 10000);
456 }
457
458 void ConnectSocketExpectFail() {
459 test_utils->sts_target()->Dispatch(
460 WrapRunnable(p1_, &TransportTestPeer::ConnectSocket, p2_),
461 NS_DISPATCH_SYNC);
462 test_utils->sts_target()->Dispatch(
463 WrapRunnable(p2_, &TransportTestPeer::ConnectSocket, p1_),
464 NS_DISPATCH_SYNC);
465 ASSERT_TRUE_WAIT(p1_->failed(), 10000);
466 ASSERT_TRUE_WAIT(p2_->failed(), 10000);
467 }
468
469 void InitIce() {
470 p1_->InitIce();
471 p2_->InitIce();
472 }
473
474 void ConnectIce() {
475 p1_->InitIce();
476 p2_->InitIce();
477 p1_->ConnectIce(p2_);
478 p2_->ConnectIce(p1_);
479 ASSERT_TRUE_WAIT(p1_->connected(), 10000);
480 ASSERT_TRUE_WAIT(p2_->connected(), 10000);
481 }
482
483 void TransferTest(size_t count) {
484 unsigned char buf[1000];
485
486 for (size_t i= 0; i<count; ++i) {
487 memset(buf, count & 0xff, sizeof(buf));
488 TransportResult rv = p1_->SendPacket(buf, sizeof(buf));
489 ASSERT_TRUE(rv > 0);
490 }
491
492 std::cerr << "Received == " << p2_->received() << std::endl;
493 ASSERT_TRUE_WAIT(count == p2_->received(), 10000);
494 }
495
496 protected:
497 PRFileDesc *fds_[2];
498 TransportTestPeer *p1_;
499 TransportTestPeer *p2_;
500 nsCOMPtr<nsIEventTarget> target_;
501 };
502
503
504 TEST_F(TransportTest, TestNoDtlsVerificationSettings) {
505 ConnectSocketExpectFail();
506 }
507
508 TEST_F(TransportTest, TestConnect) {
509 SetDtlsPeer();
510 ConnectSocket();
511 }
512
513 TEST_F(TransportTest, TestConnectDestroyFlowsMainThread) {
514 SetDtlsPeer();
515 ConnectSocket();
516 DestroyPeerFlows();
517 }
518
519 TEST_F(TransportTest, TestConnectAllowAll) {
520 SetDtlsAllowAll();
521 ConnectSocket();
522 }
523
524 TEST_F(TransportTest, TestConnectBadDigest) {
525 SetDtlsPeer(1, 1);
526
527 ConnectSocketExpectFail();
528 }
529
530 TEST_F(TransportTest, TestConnectTwoDigests) {
531 SetDtlsPeer(2, 0);
532
533 ConnectSocket();
534 }
535
536 TEST_F(TransportTest, TestConnectTwoDigestsFirstBad) {
537 SetDtlsPeer(2, 1);
538
539 ConnectSocketExpectFail();
540 }
541
542 TEST_F(TransportTest, TestConnectTwoDigestsSecondBad) {
543 SetDtlsPeer(2, 2);
544
545 ConnectSocketExpectFail();
546 }
547
548 TEST_F(TransportTest, TestConnectTwoDigestsBothBad) {
549 SetDtlsPeer(2, 3);
550
551 ConnectSocketExpectFail();
552 }
553
554 TEST_F(TransportTest, TestTransfer) {
555 SetDtlsPeer();
556 ConnectSocket();
557 TransferTest(1);
558 }
559
560 TEST_F(TransportTest, TestConnectLoseFirst) {
561 SetDtlsPeer();
562 p1_->SetLoss(0);
563 ConnectSocket();
564 TransferTest(1);
565 }
566
567 TEST_F(TransportTest, TestConnectIce) {
568 SetDtlsPeer();
569 ConnectIce();
570 }
571
572 TEST_F(TransportTest, TestTransferIce) {
573 SetDtlsPeer();
574 ConnectIce();
575 TransferTest(1);
576 }
577
578 TEST(PushTests, LayerFail) {
579 TransportFlow flow;
580 nsresult rv;
581 bool destroyed1, destroyed2;
582
583 rv = flow.PushLayer(new TransportLayerDummy(true, &destroyed1));
584 ASSERT_TRUE(NS_SUCCEEDED(rv));
585
586 rv = flow.PushLayer(new TransportLayerDummy(false, &destroyed2));
587 ASSERT_TRUE(NS_FAILED(rv));
588
589 ASSERT_EQ(TransportLayer::TS_ERROR, flow.state());
590 ASSERT_EQ(true, destroyed1);
591 ASSERT_EQ(true, destroyed2);
592
593 rv = flow.PushLayer(new TransportLayerDummy(true, &destroyed1));
594 ASSERT_TRUE(NS_FAILED(rv));
595 ASSERT_EQ(true, destroyed1);
596 }
597
598
599 TEST(PushTests, LayersFail) {
600 TransportFlow flow;
601 nsresult rv;
602 bool destroyed1, destroyed2, destroyed3;
603
604 rv = flow.PushLayer(new TransportLayerDummy(true, &destroyed1));
605 ASSERT_TRUE(NS_SUCCEEDED(rv));
606
607 nsAutoPtr<std::queue<TransportLayer *> > layers(
608 new std::queue<TransportLayer *>());
609
610 layers->push(new TransportLayerDummy(true, &destroyed2));
611 layers->push(new TransportLayerDummy(false, &destroyed3));
612
613 rv = flow.PushLayers(layers);
614 ASSERT_TRUE(NS_FAILED(rv));
615
616 ASSERT_EQ(TransportLayer::TS_ERROR, flow.state());
617 ASSERT_EQ(true, destroyed1);
618 ASSERT_EQ(true, destroyed2);
619 ASSERT_EQ(true, destroyed3);
620
621 layers = new std::queue<TransportLayer *>();
622 layers->push(new TransportLayerDummy(true, &destroyed2));
623 layers->push(new TransportLayerDummy(true, &destroyed3));
624 rv = flow.PushLayers(layers);
625
626 ASSERT_TRUE(NS_FAILED(rv));
627 ASSERT_EQ(true, destroyed2);
628 ASSERT_EQ(true, destroyed3);
629 }
630
631 } // end namespace
632
633 int main(int argc, char **argv)
634 {
635 test_utils = new MtransportTestUtils();
636
637 NSS_NoDB_Init(nullptr);
638 NSS_SetDomesticPolicy();
639 // Start the tests
640 ::testing::InitGoogleTest(&argc, argv);
641
642 int rv = RUN_ALL_TESTS();
643 delete test_utils;
644 return rv;
645 }

mercurial