new file mode 100644
--- /dev/null
+++ b/media/mtransport/test/test_nr_socket_ice_unittest.cpp
@@ -0,0 +1,425 @@
+/* -*- 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/. */
+
+// Some of this code is taken from nricectx.cpp and nricemediastream.cpp
+// which in turn contains code cut-and-pasted from nICEr. Copyright is:
+
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+* Neither the name of Adobe Systems, Network Resonance nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+#include "nss.h"
+#include "ssl.h"
+
+extern "C" {
+#include "stun_msg.h"
+#include "ice_ctx.h"
+#include "ice_peer_ctx.h"
+#include "nICEr/src/net/transport_addr.h"
+}
+
+#include "mtransport_test_utils.h"
+#include "nricectx.h"
+#include "nricemediastream.h"
+#include "runnable_utils.h"
+#include "test_nr_socket.h"
+
+namespace mozilla {
+
+static unsigned int kDefaultTimeout = 7000;
+
+class IcePeer {
+
+public:
+ IcePeer(const char* name, TestNat* nat, UINT4 flags,
+ MtransportTestUtils* test_utils)
+ : name_(name)
+ , ice_checking_(false)
+ , ice_connected_(false)
+ , ice_disconnected_(false)
+ , gather_cb_(false)
+ , stream_ready_(false)
+ , stream_failed_(false)
+ , ice_ctx_(nullptr)
+ , peer_ctx_(nullptr)
+ , nat_(nat)
+ , test_utils_(test_utils)
+ {
+ nr_ice_ctx_create(const_cast<char *>(name_.c_str()), flags, &ice_ctx_);
+
+ if (nat_) {
+ nr_socket_factory* factory;
+ nat_->create_socket_factory(&factory);
+ nr_ice_ctx_set_socket_factory(ice_ctx_, factory);
+ }
+
+ // Create the handler objects
+ ice_handler_vtbl_ = new nr_ice_handler_vtbl();
+ ice_handler_vtbl_->select_pair = &IcePeer::select_pair;
+ ice_handler_vtbl_->stream_ready = &IcePeer::stream_ready;
+ ice_handler_vtbl_->stream_failed = &IcePeer::stream_failed;
+ ice_handler_vtbl_->ice_connected = &IcePeer::ice_connected;
+ ice_handler_vtbl_->msg_recvd = &IcePeer::msg_recvd;
+ ice_handler_vtbl_->ice_checking = &IcePeer::ice_checking;
+ ice_handler_vtbl_->ice_disconnected = &IcePeer::ice_disconnected;
+
+ ice_handler_ = new nr_ice_handler();
+ ice_handler_->vtbl = ice_handler_vtbl_;
+ ice_handler_->obj = this;
+
+ nr_ice_peer_ctx_create(ice_ctx_, ice_handler_,
+ const_cast<char *>(name_.c_str()),
+ &peer_ctx_);
+
+ nr_ice_add_media_stream(ice_ctx_,
+ const_cast<char *>(name_.c_str()),
+ 2, &ice_media_stream_);
+
+ nr_ice_media_stream_initialize(ice_ctx_, ice_media_stream_);
+ }
+
+ virtual ~IcePeer()
+ {
+ Destroy();
+ }
+
+ void Destroy()
+ {
+ test_utils_->sts_target()->Dispatch(
+ WrapRunnable(this,
+ &IcePeer::Destroy_s),
+ NS_DISPATCH_SYNC);
+ }
+
+ void Destroy_s()
+ {
+ nr_ice_peer_ctx_destroy(&peer_ctx_);
+ delete ice_handler_;
+ delete ice_handler_vtbl_;
+ nr_ice_ctx_destroy(&ice_ctx_);
+ }
+
+ void Gather(bool default_route_only=false)
+ {
+ test_utils_->sts_target()->Dispatch(
+ WrapRunnable(this,
+ &IcePeer::Gather_s, default_route_only),
+ NS_DISPATCH_SYNC);
+ }
+
+ void Gather_s(bool default_route_only=false)
+ {
+ int r = nr_ice_gather(ice_ctx_, &IcePeer::gather_cb, this);
+ ASSERT_TRUE(r == 0 || r == R_WOULDBLOCK);
+ }
+
+ std::vector<std::string> GetLocalCandidates() const {
+ char attr[256];
+ std::vector<std::string> candidates;
+ nr_ice_component* comp = STAILQ_FIRST(&ice_media_stream_->components);
+ while(comp){
+ if (comp->state != NR_ICE_COMPONENT_DISABLED) {
+ nr_ice_candidate *cand = TAILQ_FIRST(&comp->candidates);
+ while(cand){
+ int r = nr_ice_format_candidate_attribute(cand, attr, 255);
+ if (r == 0) {
+ candidates.push_back(attr);
+ }
+
+ cand = TAILQ_NEXT(cand, entry_comp);
+ }
+ }
+
+ comp = STAILQ_NEXT(comp, entry);
+ }
+
+ return candidates;
+ }
+
+ std::vector<std::string> GetGlobalAttributes() {
+
+ char **attrs = nullptr;
+ int attrct;
+ std::vector<std::string> ret;
+
+ nr_ice_get_global_attributes(ice_ctx_, &attrs, &attrct);
+
+ for (int i=0; i<attrct; i++) {
+ ret.push_back(std::string(attrs[i]));
+ RFREE(attrs[i]);
+ }
+ RFREE(attrs);
+
+ return ret;
+ }
+
+ void ParseGlobalAttributes(std::vector<std::string> attrs) {
+ std::vector<char *> attrs_in;
+
+ for (auto& attr : attrs) {
+ attrs_in.push_back(const_cast<char *>(attr.c_str()));
+ }
+
+ int r = nr_ice_peer_ctx_parse_global_attributes(peer_ctx_,
+ attrs_in.size() ?
+ &attrs_in[0] : nullptr,
+ attrs_in.size());
+ ASSERT_EQ(0, r);
+ }
+
+ void SetControlling(bool controlling) {
+ peer_ctx_->controlling = controlling ? 1 : 0;
+ }
+
+ void SetRemoteAttributes(std::vector<std::string> attributes) {
+ int r;
+
+ std::vector<char*> attrs;
+ for (auto& attr: attributes) {
+ attrs.push_back(const_cast<char*>(attr.c_str()));
+ }
+
+ if (attrs.size()) {
+ r = nr_ice_peer_ctx_parse_stream_attributes(peer_ctx_, ice_media_stream_, &attrs[0], attrs.size());
+ ASSERT_EQ(0, r);
+ }
+ }
+
+ void StartChecks() {
+ test_utils_->sts_target()->Dispatch(
+ WrapRunnable(this,
+ &IcePeer::StartChecks_s),
+ NS_DISPATCH_SYNC);
+ }
+
+ void StartChecks_s() {
+ int r = nr_ice_peer_ctx_pair_candidates(peer_ctx_);
+ ASSERT_EQ(0, r);
+
+ r = nr_ice_peer_ctx_start_checks2(peer_ctx_, 1);
+ ASSERT_EQ(0, r);
+ }
+
+ // Handler callbacks
+ static int select_pair(void *obj, nr_ice_media_stream *stream,
+ int component_id, nr_ice_cand_pair **potentials,
+ int potential_ct) {
+ return 0;
+ }
+
+ static int stream_ready(void *obj, nr_ice_media_stream *stream) {
+ IcePeer* peer = static_cast<IcePeer*>(obj);
+ peer->stream_ready_ = true;
+ return 0;
+ }
+
+ static int stream_failed(void *obj, nr_ice_media_stream *stream) {
+ IcePeer* peer = static_cast<IcePeer*>(obj);
+ peer->stream_failed_ = true;
+ return 0;
+ }
+
+ static int ice_checking(void *obj, nr_ice_peer_ctx *pctx) {
+ IcePeer* peer = static_cast<IcePeer*>(obj);
+ peer->ice_checking_ = true;
+ return 0;
+ }
+
+ static int ice_connected(void *obj, nr_ice_peer_ctx *pctx) {
+ IcePeer* peer = static_cast<IcePeer*>(obj);
+ peer->ice_connected_ = true;
+ return 0;
+ }
+
+ static int ice_disconnected(void *obj, nr_ice_peer_ctx *pctx) {
+ IcePeer* peer = static_cast<IcePeer*>(obj);
+ peer->ice_disconnected_ = true;
+ return 0;
+ }
+
+ static int msg_recvd(void *obj, nr_ice_peer_ctx *pctx,
+ nr_ice_media_stream *stream, int component_id,
+ UCHAR *msg, int len) {
+ return 0;
+ }
+
+ static void gather_cb(NR_SOCKET s, int h, void *arg) {
+ IcePeer* peer = static_cast<IcePeer*>(arg);
+ peer->gather_cb_ = true;
+ }
+
+ std::string name_;
+
+ bool ice_checking_;
+ bool ice_connected_;
+ bool ice_disconnected_;
+ bool gather_cb_;
+ bool stream_ready_;
+ bool stream_failed_;
+
+ nr_ice_ctx* ice_ctx_;
+ nr_ice_handler* ice_handler_;
+ nr_ice_handler_vtbl* ice_handler_vtbl_;
+ nr_ice_media_stream* ice_media_stream_;
+ nr_ice_peer_ctx* peer_ctx_;
+ TestNat* nat_;
+ MtransportTestUtils* test_utils_;
+};
+
+class TestNrSocketIceUnitTest : public ::testing::Test {
+
+public:
+ void SetUp() override
+ {
+ NSS_NoDB_Init(nullptr);
+ NSS_SetDomesticPolicy();
+
+ test_utils_ = new MtransportTestUtils();
+ test_utils2_ = new MtransportTestUtils();
+
+ NrIceCtx::InitializeGlobals(false, false, false);
+ }
+
+ void TearDown() override
+ {
+ delete test_utils_;
+ delete test_utils2_;
+ }
+
+ MtransportTestUtils* test_utils_;
+ MtransportTestUtils* test_utils2_;
+
+};
+
+TEST_F(TestNrSocketIceUnitTest, TestIcePeer) {
+ IcePeer peer("IcePeer", nullptr, NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION,
+ test_utils_);
+ ASSERT_NE(peer.ice_ctx_, nullptr);
+ ASSERT_NE(peer.peer_ctx_, nullptr);
+ ASSERT_NE(peer.ice_media_stream_, nullptr);
+ peer.Gather();
+ std::vector<std::string> attrs = peer.GetGlobalAttributes();
+ ASSERT_NE(attrs.size(), 0UL);
+ std::vector<std::string> candidates = peer.GetLocalCandidates();
+ ASSERT_NE(candidates.size(), 0UL);
+}
+
+TEST_F(TestNrSocketIceUnitTest, TestIcePeersNoNAT) {
+ IcePeer peer("IcePeer", nullptr, NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION,
+ test_utils_);
+ IcePeer peer2("IcePeer2", nullptr, NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION,
+ test_utils2_);
+ peer.SetControlling(true);
+ peer2.SetControlling(false);
+
+ peer.Gather();
+ peer2.Gather();
+ std::vector<std::string> attrs = peer.GetGlobalAttributes();
+ peer2.ParseGlobalAttributes(attrs);
+ std::vector<std::string> candidates = peer.GetLocalCandidates();
+ peer2.SetRemoteAttributes(candidates);
+
+ attrs = peer2.GetGlobalAttributes();
+ peer.ParseGlobalAttributes(attrs);
+ candidates = peer2.GetLocalCandidates();
+ peer.SetRemoteAttributes(candidates);
+ peer2.StartChecks();
+ peer.StartChecks();
+
+ ASSERT_TRUE_WAIT(peer.ice_connected_, kDefaultTimeout);
+ ASSERT_TRUE_WAIT(peer2.ice_connected_, kDefaultTimeout);
+}
+
+TEST_F(TestNrSocketIceUnitTest, TestIcePeersPacketLoss) {
+ IcePeer peer("IcePeer", nullptr, NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION,
+ test_utils_);
+
+ RefPtr<TestNat> nat(new TestNat);
+ class NatDelegate : public TestNat::NatDelegate {
+ public:
+ NatDelegate()
+ : messages(0) {}
+
+ int on_read(TestNat *nat, void *buf, size_t maxlen, size_t *len) override
+ {
+ return 0;
+ }
+
+ int on_sendto(TestNat *nat, const void *msg, size_t len,
+ int flags, nr_transport_addr *to) override
+ {
+ ++messages;
+ // 25% packet loss
+ if (messages % 4 == 0) {
+ return 1;
+ }
+ return 0;
+ }
+
+ int on_write(TestNat *nat, const void *msg, size_t len, size_t *written) override
+ {
+ return 0;
+ }
+
+ int messages;
+ } delegate;
+ nat->nat_delegate_ = &delegate;
+
+ IcePeer peer2("IcePeer2", nat, NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION,
+ test_utils2_);
+ peer.SetControlling(true);
+ peer2.SetControlling(false);
+
+ peer.Gather();
+ peer2.Gather();
+ std::vector<std::string> attrs = peer.GetGlobalAttributes();
+ peer2.ParseGlobalAttributes(attrs);
+ std::vector<std::string> candidates = peer.GetLocalCandidates();
+ peer2.SetRemoteAttributes(candidates);
+
+ attrs = peer2.GetGlobalAttributes();
+ peer.ParseGlobalAttributes(attrs);
+ candidates = peer2.GetLocalCandidates();
+ peer.SetRemoteAttributes(candidates);
+ peer2.StartChecks();
+ peer.StartChecks();
+
+ ASSERT_TRUE_WAIT(peer.ice_connected_, kDefaultTimeout);
+ ASSERT_TRUE_WAIT(peer2.ice_connected_, kDefaultTimeout);
+}
+
+
+}