/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ // Original author: ekr@rtfm.com #include #include #include #include #include "sigslot.h" #include "nspr.h" #include "nss.h" #include "ssl.h" #include "mozilla/Scoped.h" #include "nsThreadUtils.h" #include "nsXPCOM.h" #include "logging.h" #include "nricectx.h" #include "nricemediastream.h" #include "mtransport_test_utils.h" #include "runnable_utils.h" #define GTEST_HAS_RTTI 0 #include "gtest/gtest.h" #include "gtest_utils.h" using namespace mozilla; MtransportTestUtils *test_utils; bool stream_added = false; namespace { enum TrickleMode { TRICKLE_NONE, TRICKLE_DEFERRED }; class IceTestPeer : public sigslot::has_slots<> { public: IceTestPeer(const std::string& name, bool offerer, bool set_priorities) : name_(name), ice_ctx_(NrIceCtx::Create(name, offerer, set_priorities)), streams_(), candidates_(), gathering_complete_(false), ready_ct_(0), ice_complete_(false), received_(0), sent_(0), remote_(nullptr) { ice_ctx_->SignalGatheringCompleted.connect(this, &IceTestPeer::GatheringComplete); ice_ctx_->SignalCompleted.connect(this, &IceTestPeer::IceCompleted); } void AddStream(int components) { char name[100]; snprintf(name, sizeof(name), "%s:stream%d", name_.c_str(), (int)streams_.size()); mozilla::RefPtr stream = ice_ctx_->CreateStream(static_cast(name), components); ASSERT_TRUE(stream); streams_.push_back(stream); stream->SignalCandidate.connect(this, &IceTestPeer::GotCandidate); stream->SignalReady.connect(this, &IceTestPeer::StreamReady); stream->SignalPacketReceived.connect(this, &IceTestPeer::PacketReceived); } void Gather() { nsresult res; std::vector stun_servers; ScopedDeletePtr server(NrIceStunServer::Create( std::string((char *)"216.93.246.14"), 3478)); stun_servers.push_back(*server); ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetStunServers(stun_servers))); test_utils->sts_target()->Dispatch( WrapRunnableRet(ice_ctx_, &NrIceCtx::StartGathering, &res), NS_DISPATCH_SYNC); ASSERT_TRUE(NS_SUCCEEDED(res)); } // Get various pieces of state std::vector GetGlobalAttributes() { return ice_ctx_->GetGlobalAttributes(); } std::vector& GetCandidates(const std::string &name) { return candidates_[name]; } bool gathering_complete() { return gathering_complete_; } int ready_ct() { return ready_ct_; } bool is_ready(size_t stream) { return streams_[stream]->state() == NrIceMediaStream::ICE_OPEN; } bool ice_complete() { return ice_complete_; } size_t received() { return received_; } size_t sent() { return sent_; } // Start connecting to another peer void Connect(IceTestPeer *remote, TrickleMode trickle_mode, bool start = true) { nsresult res; remote_ = remote; test_utils->sts_target()->Dispatch( WrapRunnableRet(ice_ctx_, &NrIceCtx::ParseGlobalAttributes, remote->GetGlobalAttributes(), &res), NS_DISPATCH_SYNC); ASSERT_TRUE(NS_SUCCEEDED(res)); if (trickle_mode == TRICKLE_NONE) { for (size_t i=0; ists_target()->Dispatch( WrapRunnableRet(streams_[i], &NrIceMediaStream::ParseAttributes, remote->GetCandidates(remote->streams_[i]->name()), &res), NS_DISPATCH_SYNC); ASSERT_TRUE(NS_SUCCEEDED(res)); } } else { // Parse empty attributes and then trickle them out later for (size_t i=0; i empty_attrs; test_utils->sts_target()->Dispatch( WrapRunnableRet(streams_[i], &NrIceMediaStream::ParseAttributes, empty_attrs, &res), NS_DISPATCH_SYNC); ASSERT_TRUE(NS_SUCCEEDED(res)); } } if (start) { // Now start checks test_utils->sts_target()->Dispatch( WrapRunnableRet(ice_ctx_, &NrIceCtx::StartChecks, &res), NS_DISPATCH_SYNC); ASSERT_TRUE(NS_SUCCEEDED(res)); } } void DoTrickle(size_t stream) { std::cerr << "Doing trickle for stream " << stream << std::endl; // If we are in trickle deferred mode, now trickle in the candidates // for |stream} nsresult res; ASSERT_GT(remote_->streams_.size(), stream); std::vector candidates = remote_->GetCandidates(remote_->streams_[stream]->name()); for (size_t j=0; jsts_target()->Dispatch( WrapRunnableRet(streams_[stream], &NrIceMediaStream::ParseTrickleCandidate, candidates[j], &res), NS_DISPATCH_SYNC); ASSERT_TRUE(NS_SUCCEEDED(res)); } } void Close() { ice_ctx_->destroy_peer_ctx(); } void StartChecks() { nsresult res; // Now start checks test_utils->sts_target()->Dispatch( WrapRunnableRet(ice_ctx_, &NrIceCtx::StartChecks, &res), NS_DISPATCH_SYNC); ASSERT_TRUE(NS_SUCCEEDED(res)); } // Handle events void GatheringComplete(NrIceCtx *ctx) { gathering_complete_ = true; } void GotCandidate(NrIceMediaStream *stream, const std::string &candidate) { std::cout << "Got candidate " << candidate << std::endl; candidates_[stream->name()].push_back(candidate); } void StreamReady(NrIceMediaStream *stream) { std::cout << "Stream ready " << stream->name() << std::endl; ++ready_ct_; } void IceCompleted(NrIceCtx *ctx) { std::cout << "ICE completed " << name_ << std::endl; ice_complete_ = true; } void PacketReceived(NrIceMediaStream *stream, int component, const unsigned char *data, int len) { std::cerr << "Received " << len << " bytes" << std::endl; ++received_; } void SendPacket(int stream, int component, const unsigned char *data, int len) { ASSERT_TRUE(NS_SUCCEEDED(streams_[stream]->SendPacket(component, data, len))); ++sent_; std::cerr << "Sent " << len << " bytes" << std::endl; } private: std::string name_; nsRefPtr ice_ctx_; std::vector > streams_; std::map > candidates_; bool gathering_complete_; int ready_ct_; bool ice_complete_; size_t received_; size_t sent_; IceTestPeer *remote_; }; class IceTest : public ::testing::Test { public: IceTest() : initted_(false) {} void SetUp() { nsresult rv; target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); ASSERT_TRUE(NS_SUCCEEDED(rv)); } void AddStream(const std::string& name, int components) { Init(false); p1_->AddStream(components); p2_->AddStream(components); } void Init(bool set_priorities) { if (!initted_) { p1_ = new IceTestPeer("P1", true, set_priorities); p2_ = new IceTestPeer("P2", false, set_priorities); } initted_ = true; } bool Gather(bool wait) { Init(false); p1_->Gather(); p2_->Gather(); EXPECT_TRUE_WAIT(p1_->gathering_complete(), 10000); if (!p1_->gathering_complete()) return false; EXPECT_TRUE_WAIT(p2_->gathering_complete(), 10000); if (!p2_->gathering_complete()) return false; return true; } void Connect() { p1_->Connect(p2_, TRICKLE_NONE); p2_->Connect(p1_, TRICKLE_NONE); ASSERT_TRUE_WAIT(p1_->ready_ct() == 1 && p2_->ready_ct() == 1, 5000); ASSERT_TRUE_WAIT(p1_->ice_complete() && p2_->ice_complete(), 5000); } void ConnectTrickle() { p1_->Connect(p2_, TRICKLE_DEFERRED); p2_->Connect(p1_, TRICKLE_DEFERRED); } void DoTrickle(size_t stream) { p1_->DoTrickle(stream); p2_->DoTrickle(stream); ASSERT_TRUE_WAIT(p1_->is_ready(stream), 5000); ASSERT_TRUE_WAIT(p2_->is_ready(stream), 5000); } void VerifyConnected() { } void CloseP1() { p1_->Close(); } void ConnectThenDelete() { p1_->Connect(p2_, TRICKLE_NONE, true); p2_->Connect(p1_, TRICKLE_NONE, false); test_utils->sts_target()->Dispatch(WrapRunnable(this, &IceTest::CloseP1), NS_DISPATCH_SYNC); p2_->StartChecks(); // Wait to see if we crash PR_Sleep(PR_MillisecondsToInterval(5000)); } void SendReceive() { // p1_->Send(2); p1_->SendPacket(0, 1, reinterpret_cast("TEST"), 4); ASSERT_EQ(1, p1_->sent()); ASSERT_TRUE_WAIT(p2_->received() == 1, 1000); } protected: bool initted_; nsCOMPtr target_; mozilla::ScopedDeletePtr p1_; mozilla::ScopedDeletePtr p2_; }; } // end namespace TEST_F(IceTest, TestGather) { AddStream("first", 1); ASSERT_TRUE(Gather(true)); } TEST_F(IceTest, TestGatherAutoPrioritize) { Init(false); AddStream("first", 1); ASSERT_TRUE(Gather(true)); } TEST_F(IceTest, TestConnect) { AddStream("first", 1); ASSERT_TRUE(Gather(true)); Connect(); } TEST_F(IceTest, TestConnectAutoPrioritize) { Init(false); AddStream("first", 1); ASSERT_TRUE(Gather(true)); Connect(); } TEST_F(IceTest, TestConnectTrickleOneStreamOneComponent) { AddStream("first", 1); ASSERT_TRUE(Gather(true)); ConnectTrickle(); DoTrickle(0); ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000); ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000); } TEST_F(IceTest, TestConnectTrickleTwoStreamsOneComponent) { AddStream("first", 1); AddStream("second", 1); ASSERT_TRUE(Gather(true)); ConnectTrickle(); DoTrickle(0); DoTrickle(1); ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000); ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000); } TEST_F(IceTest, TestSendReceive) { AddStream("first", 1); ASSERT_TRUE(Gather(true)); Connect(); SendReceive(); } TEST_F(IceTest, TestConnectShutdownOneSide) { AddStream("first", 1); ASSERT_TRUE(Gather(true)); ConnectThenDelete(); } int main(int argc, char **argv) { #ifdef LINUX // This test can cause intermittent oranges on the builders on Linux CHECK_ENVIRONMENT_FLAG("MOZ_WEBRTC_TESTS") #endif test_utils = new MtransportTestUtils(); NSS_NoDB_Init(nullptr); NSS_SetDomesticPolicy(); // Start the tests ::testing::InitGoogleTest(&argc, argv); int rv = RUN_ALL_TESTS(); delete test_utils; return rv; }