Bug 1355947 - Use TestNrSocket to build a fake ICE implementation for testing; r?drno draft
authorDan Minor <dminor@mozilla.com>
Tue, 18 Apr 2017 06:54:34 -0400
changeset 594892 884db9f3c79644771daa349dab99f312dfeb0f51
parent 594891 d8151b5e9785aa92ec2b0016ff2e24456c0ae863
child 633573 72a7f75b50ed225ee077b457040c516aa6422124
push id64193
push userbmo:dminor@mozilla.com
push dateThu, 15 Jun 2017 17:56:19 +0000
reviewersdrno
bugs1355947
milestone56.0a1
Bug 1355947 - Use TestNrSocket to build a fake ICE implementation for testing; r?drno MozReview-Commit-ID: GhSX9t8DmTO
media/mtransport/test/moz.build
media/mtransport/test/test_nr_socket_ice_unittest.cpp
--- a/media/mtransport/test/moz.build
+++ b/media/mtransport/test/moz.build
@@ -11,16 +11,17 @@ if CONFIG['OS_TARGET'] != 'WINNT':
         'multi_tcp_socket_unittest.cpp',
         'nrappkit_unittest.cpp',
         'proxy_tunnel_socket_unittest.cpp',
         'rlogconnector_unittest.cpp',
         'runnable_utils_unittest.cpp',
         'simpletokenbucket_unittest.cpp',
         'sockettransportservice_unittest.cpp',
         'stunserver.cpp',
+        'test_nr_socket_ice_unittest.cpp',
         'test_nr_socket_unittest.cpp',
         'TestSyncRunnable.cpp',
         'transport_unittests.cpp',
         'turn_unittest.cpp',
     ]
 
     if CONFIG['MOZ_SCTP']:
         SOURCES += [
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);
+}
+
+
+}