michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // Original author: ekr@rtfm.com michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "sigslot.h" michael@0: michael@0: #include "logging.h" michael@0: #include "nspr.h" michael@0: #include "nss.h" michael@0: #include "ssl.h" michael@0: michael@0: #include "mozilla/Scoped.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsXPCOM.h" michael@0: michael@0: #include "nricectx.h" michael@0: #include "nricemediastream.h" michael@0: #include "nriceresolverfake.h" michael@0: #include "nriceresolver.h" michael@0: #include "nrinterfaceprioritizer.h" michael@0: #include "mtransport_test_utils.h" michael@0: #include "gtest_ringbuffer_dumper.h" michael@0: #include "rlogringbuffer.h" michael@0: #include "runnable_utils.h" michael@0: #include "stunserver.h" michael@0: // TODO(bcampen@mozilla.com): Big fat hack since the build system doesn't give michael@0: // us a clean way to add object files to a single executable. michael@0: #include "stunserver.cpp" michael@0: #include "stun_udp_socket_filter.h" michael@0: #include "mozilla/net/DNS.h" michael@0: michael@0: #define GTEST_HAS_RTTI 0 michael@0: #include "gtest/gtest.h" michael@0: #include "gtest_utils.h" michael@0: michael@0: using namespace mozilla; michael@0: MtransportTestUtils *test_utils; michael@0: michael@0: bool stream_added = false; michael@0: michael@0: static int kDefaultTimeout = 7000; michael@0: michael@0: const std::string kDefaultStunServerAddress((char *)"23.21.150.121"); michael@0: const std::string kDefaultStunServerHostname( michael@0: (char *)"ec2-23-21-150-121.compute-1.amazonaws.com"); michael@0: const std::string kBogusStunServerHostname( michael@0: (char *)"stun-server-nonexistent.invalid"); michael@0: const uint16_t kDefaultStunServerPort=3478; michael@0: const std::string kBogusIceCandidate( michael@0: (char *)"candidate:0 2 UDP 2113601790 192.168.178.20 50769 typ"); michael@0: michael@0: std::string g_stun_server_address(kDefaultStunServerAddress); michael@0: std::string g_stun_server_hostname(kDefaultStunServerHostname); michael@0: std::string g_turn_server; michael@0: std::string g_turn_user; michael@0: std::string g_turn_password; michael@0: michael@0: namespace { michael@0: michael@0: enum TrickleMode { TRICKLE_NONE, TRICKLE_SIMULATE, TRICKLE_REAL }; michael@0: michael@0: typedef bool (*CandidateFilter)(const std::string& candidate); michael@0: michael@0: static bool IsRelayCandidate(const std::string& candidate) { michael@0: return candidate.find("typ relay") != std::string::npos; michael@0: } michael@0: michael@0: bool ContainsSucceededPair(const std::vector& pairs) { michael@0: for (size_t i = 0; i < pairs.size(); ++i) { michael@0: if (pairs[i].state == NrIceCandidatePair::STATE_SUCCEEDED) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: // Note: Does not correspond to any notion of prioritization; this is just michael@0: // so we can use stl containers/algorithms that need a comparator michael@0: bool operator<(const NrIceCandidate& lhs, michael@0: const NrIceCandidate& rhs) { michael@0: if (lhs.cand_addr.host == rhs.cand_addr.host) { michael@0: if (lhs.cand_addr.port == rhs.cand_addr.port) { michael@0: if (lhs.cand_addr.transport == rhs.cand_addr.transport) { michael@0: return lhs.type < rhs.type; michael@0: } michael@0: return lhs.cand_addr.transport < rhs.cand_addr.transport; michael@0: } michael@0: return lhs.cand_addr.port < rhs.cand_addr.port; michael@0: } michael@0: return lhs.cand_addr.host < rhs.cand_addr.host; michael@0: } michael@0: michael@0: bool operator==(const NrIceCandidate& lhs, michael@0: const NrIceCandidate& rhs) { michael@0: return !((lhs < rhs) || (rhs < lhs)); michael@0: } michael@0: michael@0: class IceCandidatePairCompare { michael@0: public: michael@0: bool operator()(const NrIceCandidatePair& lhs, michael@0: const NrIceCandidatePair& rhs) const { michael@0: if (lhs.priority == rhs.priority) { michael@0: if (lhs.local == rhs.local) { michael@0: if (lhs.remote == rhs.remote) { michael@0: return lhs.codeword < rhs.codeword; michael@0: } michael@0: return lhs.remote < rhs.remote; michael@0: } michael@0: return lhs.local < rhs.local; michael@0: } michael@0: return lhs.priority < rhs.priority; michael@0: } michael@0: }; michael@0: michael@0: class IceTestPeer : public sigslot::has_slots<> { michael@0: public: michael@0: michael@0: IceTestPeer(const std::string& name, bool offerer, bool set_priorities) : michael@0: name_(name), michael@0: ice_ctx_(NrIceCtx::Create(name, offerer, set_priorities)), michael@0: streams_(), michael@0: candidates_(), michael@0: gathering_complete_(false), michael@0: ready_ct_(0), michael@0: ice_complete_(false), michael@0: received_(0), michael@0: sent_(0), michael@0: fake_resolver_(), michael@0: dns_resolver_(new NrIceResolver()), michael@0: remote_(nullptr), michael@0: candidate_filter_(nullptr), michael@0: expected_local_type_(NrIceCandidate::ICE_HOST), michael@0: expected_local_transport_(kNrIceTransportUdp), michael@0: expected_remote_type_(NrIceCandidate::ICE_HOST), michael@0: trickle_mode_(TRICKLE_NONE), michael@0: trickled_(0) { michael@0: ice_ctx_->SignalGatheringStateChange.connect( michael@0: this, michael@0: &IceTestPeer::GatheringStateChange); michael@0: ice_ctx_->SignalConnectionStateChange.connect( michael@0: this, michael@0: &IceTestPeer::ConnectionStateChange); michael@0: } michael@0: michael@0: ~IceTestPeer() { michael@0: test_utils->sts_target()->Dispatch(WrapRunnable(this, michael@0: &IceTestPeer::Shutdown), michael@0: NS_DISPATCH_SYNC); michael@0: michael@0: // Give the ICE destruction callback time to fire before michael@0: // we destroy the resolver. michael@0: PR_Sleep(1000); michael@0: } michael@0: michael@0: void AddStream(int components) { michael@0: char name[100]; michael@0: snprintf(name, sizeof(name), "%s:stream%d", name_.c_str(), michael@0: (int)streams_.size()); michael@0: michael@0: mozilla::RefPtr stream = michael@0: ice_ctx_->CreateStream(static_cast(name), components); michael@0: michael@0: ASSERT_TRUE(stream); michael@0: streams_.push_back(stream); michael@0: stream->SignalCandidate.connect(this, &IceTestPeer::CandidateInitialized); michael@0: stream->SignalReady.connect(this, &IceTestPeer::StreamReady); michael@0: stream->SignalFailed.connect(this, &IceTestPeer::StreamFailed); michael@0: stream->SignalPacketReceived.connect(this, &IceTestPeer::PacketReceived); michael@0: } michael@0: michael@0: void SetStunServer(const std::string addr, uint16_t port) { michael@0: std::vector stun_servers; michael@0: ScopedDeletePtr server(NrIceStunServer::Create(addr, michael@0: port)); michael@0: stun_servers.push_back(*server); michael@0: ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetStunServers(stun_servers))); michael@0: } michael@0: michael@0: void SetTurnServer(const std::string addr, uint16_t port, michael@0: const std::string username, michael@0: const std::string password, michael@0: const char* transport) { michael@0: std::vector password_vec(password.begin(), password.end()); michael@0: SetTurnServer(addr, port, username, password_vec, transport); michael@0: } michael@0: michael@0: michael@0: void SetTurnServer(const std::string addr, uint16_t port, michael@0: const std::string username, michael@0: const std::vector password, michael@0: const char* transport) { michael@0: std::vector turn_servers; michael@0: ScopedDeletePtr server(NrIceTurnServer::Create( michael@0: addr, port, username, password, transport)); michael@0: turn_servers.push_back(*server); michael@0: ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetTurnServers(turn_servers))); michael@0: } michael@0: michael@0: void SetTurnServers(const std::vector servers) { michael@0: ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetTurnServers(servers))); michael@0: } michael@0: michael@0: void SetFakeResolver() { michael@0: ASSERT_TRUE(NS_SUCCEEDED(dns_resolver_->Init())); michael@0: PRNetAddr addr; michael@0: PRStatus status = PR_StringToNetAddr(g_stun_server_address.c_str(), michael@0: &addr); michael@0: addr.inet.port = kDefaultStunServerPort; michael@0: ASSERT_EQ(PR_SUCCESS, status); michael@0: fake_resolver_.SetAddr(g_stun_server_hostname, addr); michael@0: ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetResolver( michael@0: fake_resolver_.AllocateResolver()))); michael@0: } michael@0: michael@0: void SetDNSResolver() { michael@0: ASSERT_TRUE(NS_SUCCEEDED(dns_resolver_->Init())); michael@0: ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetResolver( michael@0: dns_resolver_->AllocateResolver()))); michael@0: } michael@0: michael@0: void Gather() { michael@0: nsresult res; michael@0: michael@0: test_utils->sts_target()->Dispatch( michael@0: WrapRunnableRet(ice_ctx_, &NrIceCtx::StartGathering, &res), michael@0: NS_DISPATCH_SYNC); michael@0: michael@0: ASSERT_TRUE(NS_SUCCEEDED(res)); michael@0: } michael@0: michael@0: // Get various pieces of state michael@0: std::vector GetGlobalAttributes() { michael@0: return ice_ctx_->GetGlobalAttributes(); michael@0: } michael@0: michael@0: std::vector GetCandidates(size_t stream) { michael@0: std::vector v; michael@0: michael@0: RUN_ON_THREAD( michael@0: test_utils->sts_target(), michael@0: WrapRunnableRet(this, &IceTestPeer::GetCandidates_s, stream, &v), michael@0: NS_DISPATCH_SYNC); michael@0: michael@0: return v; michael@0: } michael@0: michael@0: std::vector GetCandidates_s(size_t stream) { michael@0: std::vector candidates; michael@0: michael@0: if (stream >= streams_.size()) michael@0: return candidates; michael@0: michael@0: std::vector candidates_in = michael@0: streams_[stream]->GetCandidates(); michael@0: michael@0: michael@0: for (size_t i=0; i < candidates_in.size(); i++) { michael@0: if ((!candidate_filter_) || candidate_filter_(candidates_in[i])) { michael@0: std::cerr << "Returning candidate: " << candidates_in[i] << std::endl; michael@0: candidates.push_back(candidates_in[i]); michael@0: } michael@0: } michael@0: michael@0: return candidates; michael@0: } michael@0: michael@0: void SetExpectedTypes(NrIceCandidate::Type local, michael@0: NrIceCandidate::Type remote, michael@0: std::string local_transport = kNrIceTransportUdp) { michael@0: expected_local_type_ = local; michael@0: expected_local_transport_ = local_transport; michael@0: expected_remote_type_ = remote; michael@0: } michael@0: michael@0: bool gathering_complete() { return gathering_complete_; } michael@0: int ready_ct() { return ready_ct_; } michael@0: bool is_ready(size_t stream) { michael@0: return streams_[stream]->state() == NrIceMediaStream::ICE_OPEN; michael@0: } michael@0: bool ice_complete() { return ice_complete_; } michael@0: size_t received() { return received_; } michael@0: size_t sent() { return sent_; } michael@0: michael@0: // Start connecting to another peer michael@0: void Connect_s(IceTestPeer *remote, TrickleMode trickle_mode, michael@0: bool start = true) { michael@0: nsresult res; michael@0: michael@0: remote_ = remote; michael@0: michael@0: trickle_mode_ = trickle_mode; michael@0: res = ice_ctx_->ParseGlobalAttributes(remote->GetGlobalAttributes()); michael@0: ASSERT_TRUE(NS_SUCCEEDED(res)); michael@0: michael@0: if (trickle_mode == TRICKLE_NONE || michael@0: trickle_mode == TRICKLE_REAL) { michael@0: for (size_t i=0; i candidates = michael@0: remote->GetCandidates(i); michael@0: michael@0: for (size_t j=0; jParseAttributes(candidates); michael@0: ASSERT_TRUE(NS_SUCCEEDED(res)); michael@0: } michael@0: } else { michael@0: // Parse empty attributes and then trickle them out later michael@0: for (size_t i=0; i empty_attrs; michael@0: res = streams_[i]->ParseAttributes(empty_attrs); michael@0: ASSERT_TRUE(NS_SUCCEEDED(res)); michael@0: } michael@0: } michael@0: michael@0: if (start) { michael@0: // Now start checks michael@0: res = ice_ctx_->StartChecks(); michael@0: ASSERT_TRUE(NS_SUCCEEDED(res)); michael@0: } michael@0: } michael@0: michael@0: void Connect(IceTestPeer *remote, TrickleMode trickle_mode, michael@0: bool start = true) { michael@0: test_utils->sts_target()->Dispatch( michael@0: WrapRunnable( michael@0: this, &IceTestPeer::Connect_s, remote, trickle_mode, start), michael@0: NS_DISPATCH_SYNC); michael@0: } michael@0: michael@0: void SimulateTrickle(size_t stream) { michael@0: std::cerr << "Doing trickle for stream " << stream << std::endl; michael@0: // If we are in trickle deferred mode, now trickle in the candidates michael@0: // for |stream} michael@0: nsresult res; michael@0: michael@0: ASSERT_GT(remote_->streams_.size(), stream); michael@0: michael@0: std::vector candidates = michael@0: remote_->GetCandidates(stream); michael@0: michael@0: for (size_t j=0; jsts_target()->Dispatch( michael@0: WrapRunnableRet(streams_[stream], michael@0: &NrIceMediaStream::ParseTrickleCandidate, michael@0: candidates[j], michael@0: &res), NS_DISPATCH_SYNC); michael@0: michael@0: ASSERT_TRUE(NS_SUCCEEDED(res)); michael@0: } michael@0: } michael@0: michael@0: void DumpCandidate(std::string which, const NrIceCandidate& cand) { michael@0: std::string type; michael@0: michael@0: switch(cand.type) { michael@0: case NrIceCandidate::ICE_HOST: michael@0: type = "host"; michael@0: break; michael@0: case NrIceCandidate::ICE_SERVER_REFLEXIVE: michael@0: type = "srflx"; michael@0: break; michael@0: case NrIceCandidate::ICE_PEER_REFLEXIVE: michael@0: type = "prflx"; michael@0: break; michael@0: case NrIceCandidate::ICE_RELAYED: michael@0: type = "relay"; michael@0: if (which.find("Local") != std::string::npos) { michael@0: type += "(" + cand.local_addr.transport + ")"; michael@0: } michael@0: break; michael@0: default: michael@0: FAIL(); michael@0: }; michael@0: michael@0: std::cerr << which michael@0: << " --> " michael@0: << type michael@0: << " " michael@0: << cand.local_addr.host michael@0: << ":" michael@0: << cand.local_addr.port michael@0: << " codeword=" michael@0: << cand.codeword michael@0: << std::endl; michael@0: } michael@0: michael@0: void DumpAndCheckActiveCandidates_s() { michael@0: std::cerr << "Active candidates:" << std::endl; michael@0: for (size_t i=0; i < streams_.size(); ++i) { michael@0: for (int j=0; j < streams_[i]->components(); ++j) { michael@0: std::cerr << "Stream " << i << " component " << j+1 << std::endl; michael@0: michael@0: NrIceCandidate *local; michael@0: NrIceCandidate *remote; michael@0: michael@0: nsresult res = streams_[i]->GetActivePair(j+1, &local, &remote); michael@0: if (res == NS_ERROR_NOT_AVAILABLE) { michael@0: std::cerr << "Component unpaired or disabled." << std::endl; michael@0: } else { michael@0: ASSERT_TRUE(NS_SUCCEEDED(res)); michael@0: DumpCandidate("Local ", *local); michael@0: ASSERT_EQ(expected_local_type_, local->type); michael@0: ASSERT_EQ(expected_local_transport_, local->local_addr.transport); michael@0: DumpCandidate("Remote ", *remote); michael@0: ASSERT_EQ(expected_remote_type_, remote->type); michael@0: delete local; michael@0: delete remote; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void DumpAndCheckActiveCandidates() { michael@0: test_utils->sts_target()->Dispatch( michael@0: WrapRunnable(this, &IceTestPeer::DumpAndCheckActiveCandidates_s), michael@0: NS_DISPATCH_SYNC); michael@0: } michael@0: michael@0: void Close() { michael@0: test_utils->sts_target()->Dispatch( michael@0: WrapRunnable(ice_ctx_, &NrIceCtx::destroy_peer_ctx), michael@0: NS_DISPATCH_SYNC); michael@0: } michael@0: michael@0: void Shutdown() { michael@0: ice_ctx_ = nullptr; michael@0: } michael@0: michael@0: void StartChecks() { michael@0: nsresult res; michael@0: michael@0: // Now start checks michael@0: test_utils->sts_target()->Dispatch( michael@0: WrapRunnableRet(ice_ctx_, &NrIceCtx::StartChecks, &res), michael@0: NS_DISPATCH_SYNC); michael@0: ASSERT_TRUE(NS_SUCCEEDED(res)); michael@0: } michael@0: michael@0: // Handle events michael@0: void GatheringStateChange(NrIceCtx* ctx, michael@0: NrIceCtx::GatheringState state) { michael@0: (void)ctx; michael@0: if (state != NrIceCtx::ICE_CTX_GATHER_COMPLETE) { michael@0: return; michael@0: } michael@0: michael@0: std::cerr << "Gathering complete for " << name_ << std::endl; michael@0: gathering_complete_ = true; michael@0: michael@0: std::cerr << "CANDIDATES:" << std::endl; michael@0: for (size_t i=0; i candidates = michael@0: streams_[i]->GetCandidates(); michael@0: michael@0: for(size_t j=0; jname()].push_back(candidate); michael@0: michael@0: // If we are connected, then try to trickle to the michael@0: // other side. michael@0: if (remote_ && remote_->remote_) { michael@0: std::vector >::iterator it = michael@0: std::find(streams_.begin(), streams_.end(), stream); michael@0: ASSERT_NE(streams_.end(), it); michael@0: size_t index = it - streams_.begin(); michael@0: michael@0: ASSERT_GT(remote_->streams_.size(), index); michael@0: nsresult res = remote_->streams_[index]->ParseTrickleCandidate( michael@0: candidate); michael@0: ASSERT_TRUE(NS_SUCCEEDED(res)); michael@0: ++trickled_; michael@0: } michael@0: } michael@0: michael@0: nsresult GetCandidatePairs(size_t stream_index, michael@0: std::vector* pairs) { michael@0: MOZ_ASSERT(pairs); michael@0: if (stream_index >= streams_.size()) { michael@0: // Is there a better error for "no such index"? michael@0: ADD_FAILURE() << "No such media stream index: " << stream_index; michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: nsresult res; michael@0: test_utils->sts_target()->Dispatch( michael@0: WrapRunnableRet(streams_[stream_index], michael@0: &NrIceMediaStream::GetCandidatePairs, michael@0: pairs, michael@0: &res), michael@0: NS_DISPATCH_SYNC); michael@0: return res; michael@0: } michael@0: michael@0: void DumpCandidatePair(const NrIceCandidatePair& pair) { michael@0: std::cerr << std::endl; michael@0: DumpCandidate("Local", pair.local); michael@0: DumpCandidate("Remote", pair.remote); michael@0: std::cerr << "state = " << pair.state michael@0: << " priority = " << pair.priority michael@0: << " nominated = " << pair.nominated michael@0: << " selected = " << pair.selected michael@0: << " codeword = " << pair.codeword << std::endl; michael@0: } michael@0: michael@0: void DumpCandidatePairs(NrIceMediaStream *stream) { michael@0: std::vector pairs; michael@0: nsresult res = stream->GetCandidatePairs(&pairs); michael@0: ASSERT_TRUE(NS_SUCCEEDED(res)); michael@0: michael@0: std::cerr << "Begin list of candidate pairs [" << std::endl; michael@0: michael@0: for (std::vector::iterator p = pairs.begin(); michael@0: p != pairs.end(); ++p) { michael@0: DumpCandidatePair(*p); michael@0: } michael@0: std::cerr << "]" << std::endl; michael@0: } michael@0: michael@0: void DumpCandidatePairs() { michael@0: std::cerr << "Dumping candidate pairs for all streams [" << std::endl; michael@0: for (size_t s = 0; s < streams_.size(); ++s) { michael@0: DumpCandidatePairs(streams_[s]); michael@0: } michael@0: std::cerr << "]" << std::endl; michael@0: } michael@0: michael@0: bool CandidatePairsPriorityDescending(const std::vector& michael@0: pairs) { michael@0: // Verify that priority is descending michael@0: uint64_t priority = std::numeric_limits::max(); michael@0: michael@0: for (size_t p = 0; p < pairs.size(); ++p) { michael@0: if (priority < pairs[p].priority) { michael@0: std::cerr << "Priority increased in subsequent pairs:" << std::endl; michael@0: DumpCandidatePair(pairs[p-1]); michael@0: DumpCandidatePair(pairs[p]); michael@0: return false; michael@0: } else if (priority == pairs[p].priority) { michael@0: std::cerr << "Duplicate priority in subseqent pairs:" << std::endl; michael@0: DumpCandidatePair(pairs[p-1]); michael@0: DumpCandidatePair(pairs[p]); michael@0: return false; michael@0: } michael@0: priority = pairs[p].priority; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void UpdateAndValidateCandidatePairs(size_t stream_index, michael@0: std::vector* michael@0: new_pairs) { michael@0: std::vector old_pairs = *new_pairs; michael@0: GetCandidatePairs(stream_index, new_pairs); michael@0: ASSERT_TRUE(CandidatePairsPriorityDescending(*new_pairs)) << "New list of " michael@0: "candidate pairs is either not sorted in priority order, or has " michael@0: "duplicate priorities."; michael@0: ASSERT_TRUE(CandidatePairsPriorityDescending(old_pairs)) << "Old list of " michael@0: "candidate pairs is either not sorted in priority order, or has " michael@0: "duplicate priorities. This indicates some bug in the test case."; michael@0: std::vector added_pairs; michael@0: std::vector removed_pairs; michael@0: michael@0: // set_difference computes the set of elements that are present in the michael@0: // first set, but not the second michael@0: // NrIceCandidatePair::operator< compares based on the priority, local michael@0: // candidate, and remote candidate in that order. This means this will michael@0: // catch cases where the priority has remained the same, but one of the michael@0: // candidates has changed. michael@0: std::set_difference((*new_pairs).begin(), michael@0: (*new_pairs).end(), michael@0: old_pairs.begin(), michael@0: old_pairs.end(), michael@0: std::inserter(added_pairs, added_pairs.begin()), michael@0: IceCandidatePairCompare()); michael@0: michael@0: std::set_difference(old_pairs.begin(), michael@0: old_pairs.end(), michael@0: (*new_pairs).begin(), michael@0: (*new_pairs).end(), michael@0: std::inserter(removed_pairs, removed_pairs.begin()), michael@0: IceCandidatePairCompare()); michael@0: michael@0: for (std::vector::iterator a = added_pairs.begin(); michael@0: a != added_pairs.end(); ++a) { michael@0: std::cerr << "Found new candidate pair." << std::endl; michael@0: DumpCandidatePair(*a); michael@0: } michael@0: michael@0: for (std::vector::iterator r = removed_pairs.begin(); michael@0: r != removed_pairs.end(); ++r) { michael@0: std::cerr << "Pre-existing candidate pair is now missing:" << std::endl; michael@0: DumpCandidatePair(*r); michael@0: } michael@0: michael@0: ASSERT_TRUE(removed_pairs.empty()) << "At least one candidate pair has " michael@0: "gone missing."; michael@0: } michael@0: michael@0: void StreamReady(NrIceMediaStream *stream) { michael@0: ++ready_ct_; michael@0: std::cerr << "Stream ready " << stream->name() << " ct=" << ready_ct_ << std::endl; michael@0: DumpCandidatePairs(stream); michael@0: } michael@0: void StreamFailed(NrIceMediaStream *stream) { michael@0: std::cerr << "Stream failed " << stream->name() << " ct=" << ready_ct_ << std::endl; michael@0: DumpCandidatePairs(stream); michael@0: } michael@0: michael@0: void ConnectionStateChange(NrIceCtx* ctx, michael@0: NrIceCtx::ConnectionState state) { michael@0: (void)ctx; michael@0: if (state != NrIceCtx::ICE_CTX_OPEN) { michael@0: return; michael@0: } michael@0: std::cerr << "ICE completed " << name_ << std::endl; michael@0: ice_complete_ = true; michael@0: } michael@0: michael@0: void PacketReceived(NrIceMediaStream *stream, int component, const unsigned char *data, michael@0: int len) { michael@0: std::cerr << "Received " << len << " bytes" << std::endl; michael@0: ++received_; michael@0: } michael@0: michael@0: void SendPacket(int stream, int component, const unsigned char *data, michael@0: int len) { michael@0: ASSERT_TRUE(NS_SUCCEEDED(streams_[stream]->SendPacket(component, data, len))); michael@0: michael@0: ++sent_; michael@0: std::cerr << "Sent " << len << " bytes" << std::endl; michael@0: } michael@0: michael@0: void SetCandidateFilter(CandidateFilter filter) { michael@0: candidate_filter_ = filter; michael@0: } michael@0: michael@0: // Allow us to parse candidates directly on the current thread. michael@0: void ParseCandidate(size_t i, const std::string& candidate) { michael@0: std::vector attributes; michael@0: michael@0: attributes.push_back(candidate); michael@0: streams_[i]->ParseAttributes(attributes); michael@0: } michael@0: michael@0: void DisableComponent(size_t stream, int component_id) { michael@0: ASSERT_LT(stream, streams_.size()); michael@0: nsresult res = streams_[stream]->DisableComponent(component_id); michael@0: ASSERT_TRUE(NS_SUCCEEDED(res)); michael@0: } michael@0: michael@0: int trickled() { return trickled_; } michael@0: michael@0: private: michael@0: std::string name_; michael@0: nsRefPtr ice_ctx_; michael@0: std::vector > streams_; michael@0: std::map > candidates_; michael@0: bool gathering_complete_; michael@0: int ready_ct_; michael@0: bool ice_complete_; michael@0: size_t received_; michael@0: size_t sent_; michael@0: NrIceResolverFake fake_resolver_; michael@0: nsRefPtr dns_resolver_; michael@0: IceTestPeer *remote_; michael@0: CandidateFilter candidate_filter_; michael@0: NrIceCandidate::Type expected_local_type_; michael@0: std::string expected_local_transport_; michael@0: NrIceCandidate::Type expected_remote_type_; michael@0: TrickleMode trickle_mode_; michael@0: int trickled_; michael@0: }; michael@0: michael@0: class IceGatherTest : public ::testing::Test { michael@0: public: michael@0: void SetUp() { michael@0: test_utils->sts_target()->Dispatch(WrapRunnable(TestStunServer::GetInstance(), michael@0: &TestStunServer::Reset), michael@0: NS_DISPATCH_SYNC); michael@0: peer_ = new IceTestPeer("P1", true, false); michael@0: peer_->AddStream(1); michael@0: } michael@0: michael@0: void Gather(bool wait = true) { michael@0: peer_->Gather(); michael@0: michael@0: if (wait) { michael@0: WaitForGather(); michael@0: } michael@0: } michael@0: michael@0: void WaitForGather() { michael@0: ASSERT_TRUE_WAIT(peer_->gathering_complete(), kDefaultTimeout); michael@0: } michael@0: michael@0: void UseFakeStunServerWithResponse(const std::string& fake_addr, michael@0: uint16_t fake_port) { michael@0: TestStunServer::GetInstance()->SetResponseAddr(fake_addr, fake_port); michael@0: // Sets an additional stun server michael@0: peer_->SetStunServer(TestStunServer::GetInstance()->addr(), michael@0: TestStunServer::GetInstance()->port()); michael@0: } michael@0: michael@0: // NB: Only does substring matching, watch out for stuff like "1.2.3.4" michael@0: // matching "21.2.3.47". " 1.2.3.4 " should not have false positives. michael@0: bool StreamHasMatchingCandidate(unsigned int stream, michael@0: const std::string& match) { michael@0: std::vector candidates = peer_->GetCandidates(stream); michael@0: for (size_t c = 0; c < candidates.size(); ++c) { michael@0: if (std::string::npos != candidates[c].find(match)) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: protected: michael@0: mozilla::ScopedDeletePtr peer_; michael@0: }; michael@0: michael@0: class IceConnectTest : public ::testing::Test { michael@0: public: michael@0: IceConnectTest() : initted_(false) {} michael@0: michael@0: void SetUp() { michael@0: nsresult rv; michael@0: target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); michael@0: ASSERT_TRUE(NS_SUCCEEDED(rv)); michael@0: } michael@0: michael@0: void AddStream(const std::string& name, int components) { michael@0: Init(false); michael@0: p1_->AddStream(components); michael@0: p2_->AddStream(components); michael@0: } michael@0: michael@0: void Init(bool set_priorities) { michael@0: if (!initted_) { michael@0: p1_ = new IceTestPeer("P1", true, set_priorities); michael@0: p2_ = new IceTestPeer("P2", false, set_priorities); michael@0: } michael@0: initted_ = true; michael@0: } michael@0: michael@0: bool Gather(bool wait) { michael@0: Init(false); michael@0: p1_->SetStunServer(g_stun_server_address, kDefaultStunServerPort); michael@0: p2_->SetStunServer(g_stun_server_address, kDefaultStunServerPort); michael@0: p1_->Gather(); michael@0: p2_->Gather(); michael@0: michael@0: if (wait) { michael@0: EXPECT_TRUE_WAIT(p1_->gathering_complete(), kDefaultTimeout); michael@0: if (!p1_->gathering_complete()) michael@0: return false; michael@0: EXPECT_TRUE_WAIT(p2_->gathering_complete(), kDefaultTimeout); michael@0: if (!p2_->gathering_complete()) michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void SetTurnServer(const std::string addr, uint16_t port, michael@0: const std::string username, michael@0: const std::string password, michael@0: const char* transport = kNrIceTransportUdp) { michael@0: p1_->SetTurnServer(addr, port, username, password, transport); michael@0: p2_->SetTurnServer(addr, port, username, password, transport); michael@0: } michael@0: michael@0: void SetTurnServers(const std::vector& servers) { michael@0: p1_->SetTurnServers(servers); michael@0: p2_->SetTurnServers(servers); michael@0: } michael@0: michael@0: void SetCandidateFilter(CandidateFilter filter, bool both=true) { michael@0: p1_->SetCandidateFilter(filter); michael@0: if (both) { michael@0: p2_->SetCandidateFilter(filter); michael@0: } michael@0: } michael@0: michael@0: void Connect() { michael@0: p1_->Connect(p2_, TRICKLE_NONE); michael@0: p2_->Connect(p1_, TRICKLE_NONE); michael@0: michael@0: ASSERT_TRUE_WAIT(p1_->ready_ct() == 1 && p2_->ready_ct() == 1, michael@0: kDefaultTimeout); michael@0: ASSERT_TRUE_WAIT(p1_->ice_complete() && p2_->ice_complete(), michael@0: kDefaultTimeout); michael@0: michael@0: p1_->DumpAndCheckActiveCandidates(); michael@0: p2_->DumpAndCheckActiveCandidates(); michael@0: } michael@0: michael@0: void SetExpectedTypes(NrIceCandidate::Type local, NrIceCandidate::Type remote, michael@0: std::string transport = kNrIceTransportUdp) { michael@0: p1_->SetExpectedTypes(local, remote, transport); michael@0: p2_->SetExpectedTypes(local, remote, transport); michael@0: } michael@0: michael@0: void SetExpectedTypes(NrIceCandidate::Type local1, NrIceCandidate::Type remote1, michael@0: NrIceCandidate::Type local2, NrIceCandidate::Type remote2) { michael@0: p1_->SetExpectedTypes(local1, remote1); michael@0: p2_->SetExpectedTypes(local2, remote2); michael@0: } michael@0: michael@0: void ConnectP1(TrickleMode mode = TRICKLE_NONE) { michael@0: p1_->Connect(p2_, mode); michael@0: } michael@0: michael@0: void ConnectP2(TrickleMode mode = TRICKLE_NONE) { michael@0: p2_->Connect(p1_, mode); michael@0: } michael@0: michael@0: void WaitForComplete(int expected_streams = 1) { michael@0: ASSERT_TRUE_WAIT(p1_->ready_ct() == expected_streams && michael@0: p2_->ready_ct() == expected_streams, kDefaultTimeout); michael@0: ASSERT_TRUE_WAIT(p1_->ice_complete() && p2_->ice_complete(), michael@0: kDefaultTimeout); michael@0: } michael@0: michael@0: void WaitForGather() { michael@0: ASSERT_TRUE_WAIT(p1_->gathering_complete(), kDefaultTimeout); michael@0: ASSERT_TRUE_WAIT(p2_->gathering_complete(), kDefaultTimeout); michael@0: } michael@0: michael@0: void ConnectTrickle(TrickleMode trickle = TRICKLE_SIMULATE) { michael@0: p1_->Connect(p2_, trickle); michael@0: p2_->Connect(p1_, trickle); michael@0: } michael@0: michael@0: void SimulateTrickle(size_t stream) { michael@0: p1_->SimulateTrickle(stream); michael@0: p2_->SimulateTrickle(stream); michael@0: ASSERT_TRUE_WAIT(p1_->is_ready(stream), kDefaultTimeout); michael@0: ASSERT_TRUE_WAIT(p2_->is_ready(stream), kDefaultTimeout); michael@0: } michael@0: michael@0: void SimulateTrickleP1(size_t stream) { michael@0: p1_->SimulateTrickle(stream); michael@0: } michael@0: michael@0: void SimulateTrickleP2(size_t stream) { michael@0: p2_->SimulateTrickle(stream); michael@0: } michael@0: michael@0: void VerifyConnected() { michael@0: } michael@0: michael@0: void CloseP1() { michael@0: p1_->Close(); michael@0: } michael@0: michael@0: void ConnectThenDelete() { michael@0: p1_->Connect(p2_, TRICKLE_NONE, true); michael@0: p2_->Connect(p1_, TRICKLE_NONE, false); michael@0: test_utils->sts_target()->Dispatch(WrapRunnable(this, michael@0: &IceConnectTest::CloseP1), michael@0: NS_DISPATCH_SYNC); michael@0: p2_->StartChecks(); michael@0: michael@0: // Wait to see if we crash michael@0: PR_Sleep(PR_MillisecondsToInterval(kDefaultTimeout)); michael@0: } michael@0: michael@0: void SendReceive() { michael@0: // p1_->Send(2); michael@0: test_utils->sts_target()->Dispatch( michael@0: WrapRunnable(p1_.get(), michael@0: &IceTestPeer::SendPacket, 0, 1, michael@0: reinterpret_cast("TEST"), 4), michael@0: NS_DISPATCH_SYNC); michael@0: ASSERT_EQ(1u, p1_->sent()); michael@0: ASSERT_TRUE_WAIT(p2_->received() == 1, 1000); michael@0: } michael@0: michael@0: protected: michael@0: bool initted_; michael@0: nsCOMPtr target_; michael@0: mozilla::ScopedDeletePtr p1_; michael@0: mozilla::ScopedDeletePtr p2_; michael@0: }; michael@0: michael@0: class PrioritizerTest : public ::testing::Test { michael@0: public: michael@0: PrioritizerTest(): michael@0: prioritizer_(nullptr) {} michael@0: michael@0: ~PrioritizerTest() { michael@0: if (prioritizer_) { michael@0: nr_interface_prioritizer_destroy(&prioritizer_); michael@0: } michael@0: } michael@0: michael@0: void SetPriorizer(nr_interface_prioritizer *prioritizer) { michael@0: prioritizer_ = prioritizer; michael@0: } michael@0: michael@0: void AddInterface(const std::string& num, int type, int estimated_speed) { michael@0: std::string str_addr = "10.0.0." + num; michael@0: std::string ifname = "eth" + num; michael@0: nr_local_addr local_addr; michael@0: local_addr.interface.type = type; michael@0: local_addr.interface.estimated_speed = estimated_speed; michael@0: michael@0: int r = nr_ip4_str_port_to_transport_addr(str_addr.c_str(), 0, michael@0: IPPROTO_UDP, &(local_addr.addr)); michael@0: ASSERT_EQ(0, r); michael@0: strncpy(local_addr.addr.ifname, ifname.c_str(), MAXIFNAME); michael@0: michael@0: r = nr_interface_prioritizer_add_interface(prioritizer_, &local_addr); michael@0: ASSERT_EQ(0, r); michael@0: r = nr_interface_prioritizer_sort_preference(prioritizer_); michael@0: ASSERT_EQ(0, r); michael@0: } michael@0: michael@0: void HasLowerPreference(const std::string& num1, const std::string& num2) { michael@0: std::string key1 = "eth" + num1 + ":10.0.0." + num1; michael@0: std::string key2 = "eth" + num2 + ":10.0.0." + num2; michael@0: UCHAR pref1, pref2; michael@0: int r = nr_interface_prioritizer_get_priority(prioritizer_, key1.c_str(), &pref1); michael@0: ASSERT_EQ(0, r); michael@0: r = nr_interface_prioritizer_get_priority(prioritizer_, key2.c_str(), &pref2); michael@0: ASSERT_EQ(0, r); michael@0: ASSERT_LE(pref1, pref2); michael@0: } michael@0: michael@0: private: michael@0: nr_interface_prioritizer *prioritizer_; michael@0: }; michael@0: michael@0: class PacketFilterTest : public ::testing::Test { michael@0: public: michael@0: PacketFilterTest(): filter_(nullptr) {} michael@0: michael@0: void SetUp() { michael@0: nsCOMPtr handler = michael@0: do_GetService(NS_STUN_UDP_SOCKET_FILTER_HANDLER_CONTRACTID); michael@0: handler->NewFilter(getter_AddRefs(filter_)); michael@0: } michael@0: michael@0: void TestIncoming(const uint8_t* data, uint32_t len, michael@0: uint8_t from_addr, int from_port, michael@0: bool expected_result) { michael@0: mozilla::net::NetAddr addr; michael@0: MakeNetAddr(&addr, from_addr, from_port); michael@0: bool result; michael@0: nsresult rv = filter_->FilterPacket(&addr, data, len, michael@0: nsIUDPSocketFilter::SF_INCOMING, michael@0: &result); michael@0: ASSERT_EQ(NS_OK, rv); michael@0: ASSERT_EQ(expected_result, result); michael@0: } michael@0: michael@0: void TestOutgoing(const uint8_t* data, uint32_t len, michael@0: uint8_t to_addr, int to_port, michael@0: bool expected_result) { michael@0: mozilla::net::NetAddr addr; michael@0: MakeNetAddr(&addr, to_addr, to_port); michael@0: bool result; michael@0: nsresult rv = filter_->FilterPacket(&addr, data, len, michael@0: nsIUDPSocketFilter::SF_OUTGOING, michael@0: &result); michael@0: ASSERT_EQ(NS_OK, rv); michael@0: ASSERT_EQ(expected_result, result); michael@0: } michael@0: michael@0: private: michael@0: void MakeNetAddr(mozilla::net::NetAddr* net_addr, michael@0: uint8_t last_digit, uint16_t port) { michael@0: net_addr->inet.family = AF_INET; michael@0: net_addr->inet.ip = 192 << 24 | 168 << 16 | 1 << 8 | last_digit; michael@0: net_addr->inet.port = port; michael@0: } michael@0: michael@0: nsCOMPtr filter_; michael@0: }; michael@0: } // end namespace michael@0: michael@0: TEST_F(IceGatherTest, TestGatherFakeStunServerHostnameNoResolver) { michael@0: peer_->SetStunServer(g_stun_server_hostname, kDefaultStunServerPort); michael@0: Gather(); michael@0: } michael@0: michael@0: TEST_F(IceGatherTest, TestGatherFakeStunServerIpAddress) { michael@0: peer_->SetStunServer(g_stun_server_address, kDefaultStunServerPort); michael@0: peer_->SetFakeResolver(); michael@0: Gather(); michael@0: } michael@0: michael@0: TEST_F(IceGatherTest, TestGatherFakeStunServerHostname) { michael@0: peer_->SetStunServer(g_stun_server_hostname, kDefaultStunServerPort); michael@0: peer_->SetFakeResolver(); michael@0: Gather(); michael@0: } michael@0: michael@0: TEST_F(IceGatherTest, TestGatherFakeStunBogusHostname) { michael@0: peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort); michael@0: peer_->SetFakeResolver(); michael@0: Gather(); michael@0: } michael@0: michael@0: TEST_F(IceGatherTest, TestGatherDNSStunServerIpAddress) { michael@0: peer_->SetStunServer(g_stun_server_address, kDefaultStunServerPort); michael@0: peer_->SetDNSResolver(); michael@0: Gather(); michael@0: // TODO(jib@mozilla.com): ensure we get server reflexive candidates Bug 848094 michael@0: } michael@0: michael@0: TEST_F(IceGatherTest, TestGatherDNSStunServerHostname) { michael@0: peer_->SetStunServer(g_stun_server_hostname, kDefaultStunServerPort); michael@0: peer_->SetDNSResolver(); michael@0: Gather(); michael@0: } michael@0: michael@0: TEST_F(IceGatherTest, TestGatherDNSStunBogusHostname) { michael@0: peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort); michael@0: peer_->SetDNSResolver(); michael@0: Gather(); michael@0: } michael@0: michael@0: TEST_F(IceGatherTest, TestGatherTurn) { michael@0: if (g_turn_server.empty()) michael@0: return; michael@0: peer_->SetTurnServer(g_turn_server, kDefaultStunServerPort, michael@0: g_turn_user, g_turn_password, kNrIceTransportUdp); michael@0: Gather(); michael@0: } michael@0: michael@0: TEST_F(IceGatherTest, TestGatherTurnTcp) { michael@0: if (g_turn_server.empty()) michael@0: return; michael@0: peer_->SetTurnServer(g_turn_server, kDefaultStunServerPort, michael@0: g_turn_user, g_turn_password, kNrIceTransportTcp); michael@0: Gather(); michael@0: } michael@0: michael@0: TEST_F(IceGatherTest, TestGatherDisableComponent) { michael@0: peer_->SetStunServer(kDefaultStunServerHostname, kDefaultStunServerPort); michael@0: peer_->AddStream(2); michael@0: peer_->DisableComponent(1, 2); michael@0: Gather(); michael@0: std::vector candidates = michael@0: peer_->GetCandidates(1); michael@0: michael@0: for (size_t i=0; iParseCandidate(0, kBogusIceCandidate); michael@0: } michael@0: michael@0: TEST_F(IceGatherTest, VerifyTestStunServer) { michael@0: UseFakeStunServerWithResponse("192.0.2.133", 3333); michael@0: Gather(); michael@0: ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.2.133 3333 ")); michael@0: } michael@0: michael@0: TEST_F(IceGatherTest, TestStunServerReturnsWildcardAddr) { michael@0: UseFakeStunServerWithResponse("0.0.0.0", 3333); michael@0: Gather(); michael@0: ASSERT_FALSE(StreamHasMatchingCandidate(0, " 0.0.0.0 ")); michael@0: } michael@0: michael@0: TEST_F(IceGatherTest, TestStunServerReturnsPort0) { michael@0: UseFakeStunServerWithResponse("192.0.2.133", 0); michael@0: Gather(); michael@0: ASSERT_FALSE(StreamHasMatchingCandidate(0, " 192.0.2.133 0 ")); michael@0: } michael@0: michael@0: TEST_F(IceGatherTest, TestStunServerReturnsLoopbackAddr) { michael@0: UseFakeStunServerWithResponse("127.0.0.133", 3333); michael@0: Gather(); michael@0: ASSERT_FALSE(StreamHasMatchingCandidate(0, " 127.0.0.133 ")); michael@0: } michael@0: michael@0: TEST_F(IceGatherTest, TestStunServerTrickle) { michael@0: UseFakeStunServerWithResponse("192.0.2.1", 3333); michael@0: TestStunServer::GetInstance()->SetActive(false); michael@0: Gather(false); michael@0: ASSERT_FALSE(StreamHasMatchingCandidate(0, "192.0.2.1")); michael@0: TestStunServer::GetInstance()->SetActive(true); michael@0: WaitForGather(); michael@0: ASSERT_TRUE(StreamHasMatchingCandidate(0, "192.0.2.1")); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestGather) { michael@0: AddStream("first", 1); michael@0: ASSERT_TRUE(Gather(true)); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestGatherAutoPrioritize) { michael@0: Init(false); michael@0: AddStream("first", 1); michael@0: ASSERT_TRUE(Gather(true)); michael@0: } michael@0: michael@0: michael@0: TEST_F(IceConnectTest, TestConnect) { michael@0: AddStream("first", 1); michael@0: ASSERT_TRUE(Gather(true)); michael@0: Connect(); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestConnectTwoComponents) { michael@0: AddStream("first", 2); michael@0: ASSERT_TRUE(Gather(true)); michael@0: Connect(); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestConnectTwoComponentsDisableSecond) { michael@0: AddStream("first", 2); michael@0: ASSERT_TRUE(Gather(true)); michael@0: p1_->DisableComponent(0, 2); michael@0: p2_->DisableComponent(0, 2); michael@0: Connect(); michael@0: } michael@0: michael@0: michael@0: TEST_F(IceConnectTest, TestConnectP2ThenP1) { michael@0: AddStream("first", 1); michael@0: ASSERT_TRUE(Gather(true)); michael@0: ConnectP2(); michael@0: PR_Sleep(1000); michael@0: ConnectP1(); michael@0: WaitForComplete(); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestConnectP2ThenP1Trickle) { michael@0: AddStream("first", 1); michael@0: ASSERT_TRUE(Gather(true)); michael@0: ConnectP2(); michael@0: PR_Sleep(1000); michael@0: ConnectP1(TRICKLE_SIMULATE); michael@0: SimulateTrickleP1(0); michael@0: WaitForComplete(); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestConnectP2ThenP1TrickleTwoComponents) { michael@0: AddStream("first", 1); michael@0: AddStream("second", 2); michael@0: ASSERT_TRUE(Gather(true)); michael@0: ConnectP2(); michael@0: PR_Sleep(1000); michael@0: ConnectP1(TRICKLE_SIMULATE); michael@0: SimulateTrickleP1(0); michael@0: std::cerr << "Sleeping between trickle streams" << std::endl; michael@0: PR_Sleep(1000); // Give this some time to settle but not complete michael@0: // all of ICE. michael@0: SimulateTrickleP1(1); michael@0: WaitForComplete(2); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestConnectAutoPrioritize) { michael@0: Init(false); michael@0: AddStream("first", 1); michael@0: ASSERT_TRUE(Gather(true)); michael@0: Connect(); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestConnectTrickleOneStreamOneComponent) { michael@0: AddStream("first", 1); michael@0: ASSERT_TRUE(Gather(true)); michael@0: ConnectTrickle(); michael@0: SimulateTrickle(0); michael@0: ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000); michael@0: ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestConnectTrickleTwoStreamsOneComponent) { michael@0: AddStream("first", 1); michael@0: AddStream("second", 1); michael@0: ASSERT_TRUE(Gather(true)); michael@0: ConnectTrickle(); michael@0: SimulateTrickle(0); michael@0: SimulateTrickle(1); michael@0: ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000); michael@0: ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestConnectRealTrickleOneStreamOneComponent) { michael@0: AddStream("first", 1); michael@0: AddStream("second", 1); michael@0: ASSERT_TRUE(Gather(false)); michael@0: ConnectTrickle(TRICKLE_REAL); michael@0: ASSERT_TRUE_WAIT(p1_->ice_complete(), kDefaultTimeout); michael@0: ASSERT_TRUE_WAIT(p2_->ice_complete(), kDefaultTimeout); michael@0: WaitForGather(); // ICE can complete before we finish gathering. michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestSendReceive) { michael@0: AddStream("first", 1); michael@0: ASSERT_TRUE(Gather(true)); michael@0: Connect(); michael@0: SendReceive(); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestConnectTurn) { michael@0: if (g_turn_server.empty()) michael@0: return; michael@0: michael@0: AddStream("first", 1); michael@0: SetTurnServer(g_turn_server, kDefaultStunServerPort, michael@0: g_turn_user, g_turn_password); michael@0: ASSERT_TRUE(Gather(true)); michael@0: Connect(); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestConnectTurnTcp) { michael@0: if (g_turn_server.empty()) michael@0: return; michael@0: michael@0: AddStream("first", 1); michael@0: SetTurnServer(g_turn_server, kDefaultStunServerPort, michael@0: g_turn_user, g_turn_password, kNrIceTransportTcp); michael@0: ASSERT_TRUE(Gather(true)); michael@0: Connect(); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestConnectTurnOnly) { michael@0: if (g_turn_server.empty()) michael@0: return; michael@0: michael@0: AddStream("first", 1); michael@0: SetTurnServer(g_turn_server, kDefaultStunServerPort, michael@0: g_turn_user, g_turn_password); michael@0: ASSERT_TRUE(Gather(true)); michael@0: SetCandidateFilter(IsRelayCandidate); michael@0: SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED, michael@0: NrIceCandidate::Type::ICE_RELAYED); michael@0: Connect(); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestConnectTurnTcpOnly) { michael@0: if (g_turn_server.empty()) michael@0: return; michael@0: michael@0: AddStream("first", 1); michael@0: SetTurnServer(g_turn_server, kDefaultStunServerPort, michael@0: g_turn_user, g_turn_password, kNrIceTransportTcp); michael@0: ASSERT_TRUE(Gather(true)); michael@0: SetCandidateFilter(IsRelayCandidate); michael@0: SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED, michael@0: NrIceCandidate::Type::ICE_RELAYED, michael@0: kNrIceTransportTcp); michael@0: Connect(); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestSendReceiveTurnOnly) { michael@0: if (g_turn_server.empty()) michael@0: return; michael@0: michael@0: AddStream("first", 1); michael@0: SetTurnServer(g_turn_server, kDefaultStunServerPort, michael@0: g_turn_user, g_turn_password); michael@0: ASSERT_TRUE(Gather(true)); michael@0: SetCandidateFilter(IsRelayCandidate); michael@0: SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED, michael@0: NrIceCandidate::Type::ICE_RELAYED); michael@0: Connect(); michael@0: SendReceive(); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestSendReceiveTurnTcpOnly) { michael@0: if (g_turn_server.empty()) michael@0: return; michael@0: michael@0: AddStream("first", 1); michael@0: SetTurnServer(g_turn_server, kDefaultStunServerPort, michael@0: g_turn_user, g_turn_password, kNrIceTransportTcp); michael@0: ASSERT_TRUE(Gather(true)); michael@0: SetCandidateFilter(IsRelayCandidate); michael@0: SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED, michael@0: NrIceCandidate::Type::ICE_RELAYED, michael@0: kNrIceTransportTcp); michael@0: Connect(); michael@0: SendReceive(); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestSendReceiveTurnBothOnly) { michael@0: if (g_turn_server.empty()) michael@0: return; michael@0: michael@0: AddStream("first", 1); michael@0: std::vector turn_servers; michael@0: std::vector password_vec(g_turn_password.begin(), michael@0: g_turn_password.end()); michael@0: turn_servers.push_back(*NrIceTurnServer::Create( michael@0: g_turn_server, kDefaultStunServerPort, michael@0: g_turn_user, password_vec, kNrIceTransportTcp)); michael@0: turn_servers.push_back(*NrIceTurnServer::Create( michael@0: g_turn_server, kDefaultStunServerPort, michael@0: g_turn_user, password_vec, kNrIceTransportUdp)); michael@0: SetTurnServers(turn_servers); michael@0: ASSERT_TRUE(Gather(true)); michael@0: SetCandidateFilter(IsRelayCandidate); michael@0: // UDP is preferred. michael@0: SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED, michael@0: NrIceCandidate::Type::ICE_RELAYED, michael@0: kNrIceTransportUdp); michael@0: Connect(); michael@0: SendReceive(); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestConnectShutdownOneSide) { michael@0: AddStream("first", 1); michael@0: ASSERT_TRUE(Gather(true)); michael@0: ConnectThenDelete(); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestPollCandPairsBeforeConnect) { michael@0: AddStream("first", 1); michael@0: ASSERT_TRUE(Gather(true)); michael@0: michael@0: std::vector pairs; michael@0: nsresult res = p1_->GetCandidatePairs(0, &pairs); michael@0: // There should be no candidate pairs prior to calling Connect() michael@0: ASSERT_TRUE(NS_FAILED(res)); michael@0: ASSERT_EQ(0U, pairs.size()); michael@0: michael@0: res = p2_->GetCandidatePairs(0, &pairs); michael@0: ASSERT_TRUE(NS_FAILED(res)); michael@0: ASSERT_EQ(0U, pairs.size()); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestPollCandPairsAfterConnect) { michael@0: AddStream("first", 1); michael@0: ASSERT_TRUE(Gather(true)); michael@0: Connect(); michael@0: michael@0: std::vector pairs; michael@0: nsresult r = p1_->GetCandidatePairs(0, &pairs); michael@0: ASSERT_EQ(NS_OK, r); michael@0: // How detailed of a check do we want to do here? If the turn server is michael@0: // functioning, we'll get at least two pairs, but this is probably not michael@0: // something we should assume. michael@0: ASSERT_NE(0U, pairs.size()); michael@0: ASSERT_TRUE(p1_->CandidatePairsPriorityDescending(pairs)); michael@0: ASSERT_TRUE(ContainsSucceededPair(pairs)); michael@0: pairs.clear(); michael@0: michael@0: r = p2_->GetCandidatePairs(0, &pairs); michael@0: ASSERT_EQ(NS_OK, r); michael@0: ASSERT_NE(0U, pairs.size()); michael@0: ASSERT_TRUE(p2_->CandidatePairsPriorityDescending(pairs)); michael@0: ASSERT_TRUE(ContainsSucceededPair(pairs)); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestPollCandPairsDuringConnect) { michael@0: AddStream("first", 1); michael@0: ASSERT_TRUE(Gather(true)); michael@0: michael@0: p1_->Connect(p2_, TRICKLE_NONE, false); michael@0: p2_->Connect(p1_, TRICKLE_NONE, false); michael@0: michael@0: std::vector pairs1; michael@0: std::vector pairs2; michael@0: michael@0: p1_->StartChecks(); michael@0: p1_->UpdateAndValidateCandidatePairs(0, &pairs1); michael@0: p2_->UpdateAndValidateCandidatePairs(0, &pairs2); michael@0: michael@0: p2_->StartChecks(); michael@0: p1_->UpdateAndValidateCandidatePairs(0, &pairs1); michael@0: p2_->UpdateAndValidateCandidatePairs(0, &pairs2); michael@0: michael@0: WaitForComplete(); michael@0: p1_->UpdateAndValidateCandidatePairs(0, &pairs1); michael@0: p2_->UpdateAndValidateCandidatePairs(0, &pairs2); michael@0: ASSERT_TRUE(ContainsSucceededPair(pairs1)); michael@0: ASSERT_TRUE(ContainsSucceededPair(pairs2)); michael@0: } michael@0: michael@0: TEST_F(IceConnectTest, TestRLogRingBuffer) { michael@0: RLogRingBuffer::CreateInstance(); michael@0: AddStream("first", 1); michael@0: ASSERT_TRUE(Gather(true)); michael@0: michael@0: p1_->Connect(p2_, TRICKLE_NONE, false); michael@0: p2_->Connect(p1_, TRICKLE_NONE, false); michael@0: michael@0: std::vector pairs1; michael@0: std::vector pairs2; michael@0: michael@0: p1_->StartChecks(); michael@0: p1_->UpdateAndValidateCandidatePairs(0, &pairs1); michael@0: p2_->UpdateAndValidateCandidatePairs(0, &pairs2); michael@0: michael@0: p2_->StartChecks(); michael@0: p1_->UpdateAndValidateCandidatePairs(0, &pairs1); michael@0: p2_->UpdateAndValidateCandidatePairs(0, &pairs2); michael@0: michael@0: WaitForComplete(); michael@0: p1_->UpdateAndValidateCandidatePairs(0, &pairs1); michael@0: p2_->UpdateAndValidateCandidatePairs(0, &pairs2); michael@0: ASSERT_TRUE(ContainsSucceededPair(pairs1)); michael@0: ASSERT_TRUE(ContainsSucceededPair(pairs2)); michael@0: michael@0: for (auto p = pairs1.begin(); p != pairs1.end(); ++p) { michael@0: std::deque logs; michael@0: std::string substring("CAND-PAIR("); michael@0: substring += p->codeword; michael@0: RLogRingBuffer::GetInstance()->Filter(substring, 0, &logs); michael@0: ASSERT_NE(0U, logs.size()); michael@0: } michael@0: michael@0: for (auto p = pairs2.begin(); p != pairs2.end(); ++p) { michael@0: std::deque logs; michael@0: std::string substring("CAND-PAIR("); michael@0: substring += p->codeword; michael@0: RLogRingBuffer::GetInstance()->Filter(substring, 0, &logs); michael@0: ASSERT_NE(0U, logs.size()); michael@0: } michael@0: michael@0: RLogRingBuffer::DestroyInstance(); michael@0: } michael@0: michael@0: TEST_F(PrioritizerTest, TestPrioritizer) { michael@0: SetPriorizer(::mozilla::CreateInterfacePrioritizer()); michael@0: michael@0: AddInterface("0", NR_INTERFACE_TYPE_VPN, 100); // unknown vpn michael@0: AddInterface("1", NR_INTERFACE_TYPE_VPN | NR_INTERFACE_TYPE_WIRED, 100); // wired vpn michael@0: AddInterface("2", NR_INTERFACE_TYPE_VPN | NR_INTERFACE_TYPE_WIFI, 100); // wifi vpn michael@0: AddInterface("3", NR_INTERFACE_TYPE_VPN | NR_INTERFACE_TYPE_MOBILE, 100); // wifi vpn michael@0: AddInterface("4", NR_INTERFACE_TYPE_WIRED, 1000); // wired, high speed michael@0: AddInterface("5", NR_INTERFACE_TYPE_WIRED, 10); // wired, low speed michael@0: AddInterface("6", NR_INTERFACE_TYPE_WIFI, 10); // wifi, low speed michael@0: AddInterface("7", NR_INTERFACE_TYPE_WIFI, 1000); // wifi, high speed michael@0: AddInterface("8", NR_INTERFACE_TYPE_MOBILE, 10); // mobile, low speed michael@0: AddInterface("9", NR_INTERFACE_TYPE_MOBILE, 1000); // mobile, high speed michael@0: AddInterface("10", NR_INTERFACE_TYPE_UNKNOWN, 10); // unknown, low speed michael@0: AddInterface("11", NR_INTERFACE_TYPE_UNKNOWN, 1000); // unknown, high speed michael@0: michael@0: // expected preference "4" > "5" > "1" > "7" > "6" > "2" > "9" > "8" > "3" > "11" > "10" > "0" michael@0: michael@0: HasLowerPreference("0", "10"); michael@0: HasLowerPreference("10", "11"); michael@0: HasLowerPreference("11", "3"); michael@0: HasLowerPreference("3", "8"); michael@0: HasLowerPreference("8", "9"); michael@0: HasLowerPreference("9", "2"); michael@0: HasLowerPreference("2", "6"); michael@0: HasLowerPreference("6", "7"); michael@0: HasLowerPreference("7", "1"); michael@0: HasLowerPreference("1", "5"); michael@0: HasLowerPreference("5", "4"); michael@0: } michael@0: michael@0: TEST_F(PacketFilterTest, TestSendNonStunPacket) { michael@0: const unsigned char data[] = "12345abcde"; michael@0: TestOutgoing(data, sizeof(data), 123, 45, false); michael@0: } michael@0: michael@0: TEST_F(PacketFilterTest, TestRecvNonStunPacket) { michael@0: const unsigned char data[] = "12345abcde"; michael@0: TestIncoming(data, sizeof(data), 123, 45, false); michael@0: } michael@0: michael@0: TEST_F(PacketFilterTest, TestSendStunPacket) { michael@0: nr_stun_message *msg; michael@0: ASSERT_EQ(0, nr_stun_build_req_no_auth(NULL, &msg)); michael@0: msg->header.type = NR_STUN_MSG_BINDING_REQUEST; michael@0: ASSERT_EQ(0, nr_stun_encode_message(msg)); michael@0: TestOutgoing(msg->buffer, msg->length, 123, 45, true); michael@0: ASSERT_EQ(0, nr_stun_message_destroy(&msg)); michael@0: } michael@0: michael@0: TEST_F(PacketFilterTest, TestRecvStunPacketWithoutAPendingId) { michael@0: nr_stun_message *msg; michael@0: ASSERT_EQ(0, nr_stun_build_req_no_auth(NULL, &msg)); michael@0: michael@0: msg->header.id.octet[0] = 1; michael@0: msg->header.type = NR_STUN_MSG_BINDING_REQUEST; michael@0: ASSERT_EQ(0, nr_stun_encode_message(msg)); michael@0: TestOutgoing(msg->buffer, msg->length, 123, 45, true); michael@0: michael@0: msg->header.id.octet[0] = 0; michael@0: msg->header.type = NR_STUN_MSG_BINDING_RESPONSE; michael@0: ASSERT_EQ(0, nr_stun_encode_message(msg)); michael@0: TestIncoming(msg->buffer, msg->length, 123, 45, true); michael@0: michael@0: ASSERT_EQ(0, nr_stun_message_destroy(&msg)); michael@0: } michael@0: michael@0: TEST_F(PacketFilterTest, TestRecvStunPacketWithoutAPendingAddress) { michael@0: nr_stun_message *msg; michael@0: ASSERT_EQ(0, nr_stun_build_req_no_auth(NULL, &msg)); michael@0: michael@0: msg->header.type = NR_STUN_MSG_BINDING_REQUEST; michael@0: ASSERT_EQ(0, nr_stun_encode_message(msg)); michael@0: TestOutgoing(msg->buffer, msg->length, 123, 45, true); michael@0: michael@0: msg->header.type = NR_STUN_MSG_BINDING_RESPONSE; michael@0: ASSERT_EQ(0, nr_stun_encode_message(msg)); michael@0: TestIncoming(msg->buffer, msg->length, 123, 46, false); michael@0: TestIncoming(msg->buffer, msg->length, 124, 45, false); michael@0: michael@0: ASSERT_EQ(0, nr_stun_message_destroy(&msg)); michael@0: } michael@0: michael@0: TEST_F(PacketFilterTest, TestRecvStunPacketWithPendingIdAndAddress) { michael@0: nr_stun_message *msg; michael@0: ASSERT_EQ(0, nr_stun_build_req_no_auth(NULL, &msg)); michael@0: michael@0: msg->header.type = NR_STUN_MSG_BINDING_REQUEST; michael@0: ASSERT_EQ(0, nr_stun_encode_message(msg)); michael@0: TestOutgoing(msg->buffer, msg->length, 123, 45, true); michael@0: michael@0: msg->header.type = NR_STUN_MSG_BINDING_RESPONSE; michael@0: ASSERT_EQ(0, nr_stun_encode_message(msg)); michael@0: TestIncoming(msg->buffer, msg->length, 123, 45, true); michael@0: michael@0: // Test whitelist by filtering non-stun packets. michael@0: const unsigned char data[] = "12345abcde"; michael@0: michael@0: // 123:45 is white-listed. michael@0: TestOutgoing(data, sizeof(data), 123, 45, true); michael@0: TestIncoming(data, sizeof(data), 123, 45, true); michael@0: michael@0: // Indications pass as well. michael@0: msg->header.type = NR_STUN_MSG_BINDING_INDICATION; michael@0: ASSERT_EQ(0, nr_stun_encode_message(msg)); michael@0: TestOutgoing(msg->buffer, msg->length, 123, 45, true); michael@0: TestIncoming(msg->buffer, msg->length, 123, 45, true); michael@0: michael@0: // Packets from and to other address are still disallowed. michael@0: TestOutgoing(data, sizeof(data), 123, 46, false); michael@0: TestIncoming(data, sizeof(data), 123, 46, false); michael@0: TestOutgoing(data, sizeof(data), 124, 45, false); michael@0: TestIncoming(data, sizeof(data), 124, 45, false); michael@0: michael@0: ASSERT_EQ(0, nr_stun_message_destroy(&msg)); michael@0: } michael@0: michael@0: TEST_F(PacketFilterTest, TestSendNonRequestStunPacket) { michael@0: nr_stun_message *msg; michael@0: ASSERT_EQ(0, nr_stun_build_req_no_auth(NULL, &msg)); michael@0: michael@0: msg->header.type = NR_STUN_MSG_BINDING_RESPONSE; michael@0: ASSERT_EQ(0, nr_stun_encode_message(msg)); michael@0: TestOutgoing(msg->buffer, msg->length, 123, 45, false); michael@0: michael@0: // Send a packet so we allow the incoming request. michael@0: msg->header.type = NR_STUN_MSG_BINDING_REQUEST; michael@0: ASSERT_EQ(0, nr_stun_encode_message(msg)); michael@0: TestOutgoing(msg->buffer, msg->length, 123, 45, true); michael@0: michael@0: // This packet makes us able to send a response. michael@0: msg->header.type = NR_STUN_MSG_BINDING_REQUEST; michael@0: ASSERT_EQ(0, nr_stun_encode_message(msg)); michael@0: TestIncoming(msg->buffer, msg->length, 123, 45, true); michael@0: michael@0: msg->header.type = NR_STUN_MSG_BINDING_RESPONSE; michael@0: ASSERT_EQ(0, nr_stun_encode_message(msg)); michael@0: TestOutgoing(msg->buffer, msg->length, 123, 45, true); michael@0: michael@0: ASSERT_EQ(0, nr_stun_message_destroy(&msg)); michael@0: } michael@0: michael@0: static std::string get_environment(const char *name) { michael@0: char *value = getenv(name); michael@0: michael@0: if (!value) michael@0: return ""; michael@0: michael@0: return value; michael@0: } michael@0: michael@0: int main(int argc, char **argv) michael@0: { michael@0: #ifdef LINUX michael@0: // This test can cause intermittent oranges on the builders on Linux michael@0: CHECK_ENVIRONMENT_FLAG("MOZ_WEBRTC_TESTS") michael@0: #endif michael@0: michael@0: g_turn_server = get_environment("TURN_SERVER_ADDRESS"); michael@0: g_turn_user = get_environment("TURN_SERVER_USER"); michael@0: g_turn_password = get_environment("TURN_SERVER_PASSWORD"); michael@0: michael@0: if (g_turn_server.empty() || michael@0: g_turn_user.empty(), michael@0: g_turn_password.empty()) { michael@0: printf( michael@0: "Set TURN_SERVER_ADDRESS, TURN_SERVER_USER, and TURN_SERVER_PASSWORD\n" michael@0: "environment variables to run this test\n"); michael@0: g_turn_server=""; michael@0: } michael@0: michael@0: std::string tmp = get_environment("STUN_SERVER_ADDRESS"); michael@0: if (tmp != "") michael@0: g_stun_server_address = tmp; michael@0: michael@0: michael@0: tmp = get_environment("STUN_SERVER_HOSTNAME"); michael@0: if (tmp != "") michael@0: g_stun_server_hostname = tmp; michael@0: michael@0: test_utils = new MtransportTestUtils(); michael@0: NSS_NoDB_Init(nullptr); michael@0: NSS_SetDomesticPolicy(); michael@0: michael@0: // Start the tests michael@0: ::testing::InitGoogleTest(&argc, argv); michael@0: michael@0: ::testing::TestEventListeners& listeners = michael@0: ::testing::UnitTest::GetInstance()->listeners(); michael@0: // Adds a listener to the end. Google Test takes the ownership. michael@0: michael@0: listeners.Append(new test::RingbufferDumper(test_utils)); michael@0: test_utils->sts_target()->Dispatch( michael@0: WrapRunnableNM(&TestStunServer::GetInstance), NS_DISPATCH_SYNC); michael@0: michael@0: int rv = RUN_ALL_TESTS(); michael@0: michael@0: test_utils->sts_target()->Dispatch( michael@0: WrapRunnableNM(&TestStunServer::ShutdownInstance), NS_DISPATCH_SYNC); michael@0: michael@0: delete test_utils; michael@0: return rv; michael@0: }