/* -*- 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 <algorithm>
#include <deque>
#include <iostream>
#include <limits>
#include <map>
#include <string>
#include <vector>

#include "sigslot.h"

#include "logging.h"
#include "nspr.h"
#include "nss.h"
#include "ssl.h"

#include "mozilla/Preferences.h"
#include "nsThreadUtils.h"
#include "nsXPCOM.h"

#include "nricectxhandler.h"
#include "nricemediastream.h"
#include "nriceresolverfake.h"
#include "nriceresolver.h"
#include "nrinterfaceprioritizer.h"
#include "gtest_ringbuffer_dumper.h"
#include "rlogconnector.h"
#include "runnable_utils.h"
#include "stunserver.h"
#include "nr_socket_prsock.h"
#include "test_nr_socket.h"
#include "ice_ctx.h"
#include "stun_socket_filter.h"
#include "mozilla/net/DNS.h"

#include "ice_ctx.h"
#include "ice_peer_ctx.h"
#include "ice_media_stream.h"

extern "C" {
#include "async_timer.h"
#include "r_data.h"
#include "util.h"
#include "r_time.h"
}

#define GTEST_HAS_RTTI 0
#include "gtest/gtest.h"
#include "gtest_utils.h"


using namespace mozilla;

static unsigned int kDefaultTimeout = 7000;

//TODO(nils@mozilla.com): This should get replaced with some non-external
//solution like discussed in bug 860775.
const std::string kDefaultStunServerHostname(
    (char *)"global.stun.twilio.com");
const std::string kBogusStunServerHostname(
    (char *)"stun-server-nonexistent.invalid");
const uint16_t kDefaultStunServerPort=3478;
const std::string kBogusIceCandidate(
    (char *)"candidate:0 2 UDP 2113601790 192.168.178.20 50769 typ");

const std::string kUnreachableHostIceCandidate(
    (char *)"candidate:0 1 UDP 2113601790 192.168.178.20 50769 typ host");

namespace {

// DNS resolution helper code
static std::string
Resolve(const std::string& fqdn, int address_family)
{
  struct addrinfo hints;
  memset(&hints, 0, sizeof(hints));
  hints.ai_family = address_family;
  hints.ai_protocol = IPPROTO_UDP;
  struct addrinfo *res;
  int err = getaddrinfo(fqdn.c_str(), nullptr, &hints, &res);
  if (err) {
    std::cerr << "Error in getaddrinfo: " << err << std::endl;
    return "";
  }

  char str_addr[64] = {0};
  switch (res->ai_family) {
    case AF_INET:
      inet_ntop(
          AF_INET,
          &reinterpret_cast<struct sockaddr_in*>(res->ai_addr)->sin_addr,
          str_addr,
          sizeof(str_addr));
      break;
    case AF_INET6:
      inet_ntop(
          AF_INET6,
          &reinterpret_cast<struct sockaddr_in6*>(res->ai_addr)->sin6_addr,
          str_addr,
          sizeof(str_addr));
      break;
    default:
      std::cerr << "Got unexpected address family in DNS lookup: "
                << res->ai_family << std::endl;
      freeaddrinfo(res);
      return "";
  }

  if (!strlen(str_addr)) {
    std::cerr << "inet_ntop failed" << std::endl;
  }

  freeaddrinfo(res);
  return str_addr;
}

class StunTest : public MtransportTest {
public:
  StunTest() : MtransportTest() {
    stun_server_hostname_ = kDefaultStunServerHostname;
  }

  void SetUp() override {
    MtransportTest::SetUp();

    // If only a STUN server FQDN was provided, look up its IP address for the
    // address-only tests.
    if (stun_server_address_.empty() && !stun_server_hostname_.empty()) {
      stun_server_address_ = Resolve(stun_server_hostname_, AF_INET);
    }

    // Make sure NrIceCtx is in a testable state.
    test_utils_->sts_target()->Dispatch(
        WrapRunnableNM(&NrIceCtx::internal_DeinitializeGlobal),
        NS_DISPATCH_SYNC);

    // NB: NrIceCtx::internal_DeinitializeGlobal destroys the RLogConnector
    // singleton.
    RLogConnector::CreateInstance();

    test_utils_->sts_target()->Dispatch(
        WrapRunnableNM(&TestStunServer::GetInstance, AF_INET),
                       NS_DISPATCH_SYNC);
    test_utils_->sts_target()->Dispatch(
        WrapRunnableNM(&TestStunServer::GetInstance, AF_INET6),
                       NS_DISPATCH_SYNC);

    test_utils_->sts_target()->Dispatch(
        WrapRunnableNM(&TestStunTcpServer::GetInstance, AF_INET),
                       NS_DISPATCH_SYNC);
    test_utils_->sts_target()->Dispatch(
        WrapRunnableNM(&TestStunTcpServer::GetInstance, AF_INET6),
                       NS_DISPATCH_SYNC);
  }

  void TearDown() override {
    test_utils_->sts_target()->Dispatch(
        WrapRunnableNM(&NrIceCtx::internal_DeinitializeGlobal),
        NS_DISPATCH_SYNC);

    test_utils_->sts_target()->Dispatch(
        WrapRunnableNM(&TestStunServer::ShutdownInstance), NS_DISPATCH_SYNC);

    test_utils_->sts_target()->Dispatch(
        WrapRunnableNM(&TestStunTcpServer::ShutdownInstance), NS_DISPATCH_SYNC);

    RLogConnector::DestroyInstance();

    MtransportTest::TearDown();
  }
};

enum TrickleMode { TRICKLE_NONE, TRICKLE_SIMULATE, TRICKLE_REAL };

enum ConsentStatus { CONSENT_FRESH, CONSENT_STALE, CONSENT_EXPIRED};

const unsigned int ICE_TEST_PEER_OFFERER = (1 << 0);
const unsigned int ICE_TEST_PEER_ALLOW_LOOPBACK = (1 << 1);
const unsigned int ICE_TEST_PEER_ENABLED_TCP = (1 << 2);
const unsigned int ICE_TEST_PEER_ALLOW_LINK_LOCAL = (1 << 3);

typedef std::string (*CandidateFilter)(const std::string& candidate);

std::vector<std::string> split(const std::string &s, char delim) {
  std::vector<std::string> elems;
  std::stringstream ss(s);
  std::string item;
  while (std::getline(ss, item, delim)) {
    elems.push_back(item);
  }
  return elems;
}

static std::string IsSrflxCandidate(const std::string& candidate) {
  std::vector<std::string> tokens = split(candidate, ' ');
  if ((tokens.at(6) == "typ") && (tokens.at(7) == "srflx")) {
    return candidate;
  }
  return std::string();
}

static std::string IsRelayCandidate(const std::string& candidate) {
  if (candidate.find("typ relay") != std::string::npos) {
    return candidate;
  }
  return std::string();
}

static std::string IsTcpCandidate(const std::string& candidate) {
  if (candidate.find("TCP") != std::string::npos) {
    return candidate;
  }
  return std::string();
}

static std::string IsTcpSoCandidate(const std::string& candidate) {
  if (candidate.find("tcptype so") != std::string::npos) {
      return candidate;
    }
  return std::string();
}

static std::string IsLoopbackCandidate(const std::string& candidate) {
  if (candidate.find("127.0.0.") != std::string::npos) {
    return candidate;
  }
  return std::string();
}

static std::string IsIpv4Candidate(const std::string& candidate) {
  std::vector<std::string> tokens = split(candidate, ' ');
  if (tokens.at(4).find(":") == std::string::npos) {
    return candidate;
  }
  return std::string();
}

static std::string SabotageHostCandidateAndDropReflexive(
    const std::string& candidate) {
  if (candidate.find("typ srflx") != std::string::npos) {
    return std::string();
  }

  if (candidate.find("typ host") != std::string::npos) {
    return kUnreachableHostIceCandidate;
  }

  return candidate;
}

bool ContainsSucceededPair(const std::vector<NrIceCandidatePair>& pairs) {
  for (size_t i = 0; i < pairs.size(); ++i) {
    if (pairs[i].state == NrIceCandidatePair::STATE_SUCCEEDED) {
      return true;
    }
  }
  return false;
}

// Note: Does not correspond to any notion of prioritization; this is just
// so we can use stl containers/algorithms that need a comparator
bool operator<(const NrIceCandidate& lhs,
               const NrIceCandidate& rhs) {
  if (lhs.cand_addr.host == rhs.cand_addr.host) {
    if (lhs.cand_addr.port == rhs.cand_addr.port) {
      if (lhs.cand_addr.transport == rhs.cand_addr.transport) {
        if (lhs.type == rhs.type) {
          return lhs.tcp_type < rhs.tcp_type;
        }
        return lhs.type < rhs.type;
      }
      return lhs.cand_addr.transport < rhs.cand_addr.transport;
    }
    return lhs.cand_addr.port < rhs.cand_addr.port;
  }
  return lhs.cand_addr.host < rhs.cand_addr.host;
}

bool operator==(const NrIceCandidate& lhs,
                const NrIceCandidate& rhs) {
  return !((lhs < rhs) || (rhs < lhs));
}

class IceCandidatePairCompare {
  public:
    bool operator()(const NrIceCandidatePair& lhs,
                    const NrIceCandidatePair& rhs) const {
      if (lhs.priority == rhs.priority) {
        if (lhs.local == rhs.local) {
          if (lhs.remote == rhs.remote) {
            return lhs.codeword < rhs.codeword;
          }
          return lhs.remote < rhs.remote;
        }
        return lhs.local < rhs.local;
      }
      return lhs.priority < rhs.priority;
    }
};

class IceTestPeer;

class SchedulableTrickleCandidate {
  public:
    SchedulableTrickleCandidate(IceTestPeer *peer,
                                size_t stream,
                                const std::string &candidate,
                                MtransportTestUtils* utils) :
      peer_(peer),
      stream_(stream),
      candidate_(candidate),
      timer_handle_(nullptr),
      test_utils_(utils) {
    }

    ~SchedulableTrickleCandidate() {
      if (timer_handle_)
        NR_async_timer_cancel(timer_handle_);
    }

    void Schedule(unsigned int ms) {
      test_utils_->sts_target()->Dispatch(
          WrapRunnable(this, &SchedulableTrickleCandidate::Schedule_s, ms),
          NS_DISPATCH_SYNC);
    }

    void Schedule_s(unsigned int ms) {
      MOZ_ASSERT(!timer_handle_);
      NR_ASYNC_TIMER_SET(ms, Trickle_cb, this, &timer_handle_);
    }

    static void Trickle_cb(NR_SOCKET s, int how, void *cb_arg) {
      static_cast<SchedulableTrickleCandidate*>(cb_arg)->Trickle();
    }

    void Trickle();

    std::string& Candidate() {
      return candidate_;
    }

    const std::string& Candidate() const {
      return candidate_;
    }

    size_t Stream() const {
      return stream_;
    }

    bool IsHost() const {
      return candidate_.find("typ host") != std::string::npos;
    }

    bool IsReflexive() const {
      return candidate_.find("typ srflx") != std::string::npos;
    }

    bool IsRelay() const {
      return candidate_.find("typ relay") != std::string::npos;
    }

  private:
    IceTestPeer *peer_;
    size_t stream_;
    std::string candidate_;
    void *timer_handle_;
    MtransportTestUtils* test_utils_;

    DISALLOW_COPY_ASSIGN(SchedulableTrickleCandidate);
};

class IceTestPeer : public sigslot::has_slots<> {
 public:
  // TODO(ekr@rtfm.com): Convert to flags when NrIceCtx::Create() does.
  // Bug 1193437.
  IceTestPeer(const std::string& name, MtransportTestUtils* utils,
              bool offerer,
              bool allow_loopback = false, bool enable_tcp = true,
              bool allow_link_local = false,
              NrIceCtx::Policy ice_policy = NrIceCtx::ICE_POLICY_ALL) :
      name_(name),
      ice_ctx_(NrIceCtxHandler::Create(name, offerer, allow_loopback,
                                       enable_tcp, allow_link_local,
                                       ice_policy)),
      candidates_(),
      shutting_down_(false),
      gathering_complete_(false),
      ready_ct_(0),
      ice_connected_(false),
      ice_failed_(false),
      ice_reached_checking_(false),
      received_(0),
      sent_(0),
      fake_resolver_(),
      dns_resolver_(new NrIceResolver()),
      remote_(nullptr),
      candidate_filter_(nullptr),
      expected_local_type_(NrIceCandidate::ICE_HOST),
      expected_local_transport_(kNrIceTransportUdp),
      expected_remote_type_(NrIceCandidate::ICE_HOST),
      trickle_mode_(TRICKLE_NONE),
      trickled_(0),
      simulate_ice_lite_(false),
      nat_(new TestNat),
      test_utils_(utils) {
    ice_ctx_->ctx()->SignalGatheringStateChange.connect(
        this,
        &IceTestPeer::GatheringStateChange);
    ice_ctx_->ctx()->SignalConnectionStateChange.connect(
        this,
        &IceTestPeer::ConnectionStateChange);

    consent_timestamp_.tv_sec = 0;
    consent_timestamp_.tv_usec = 0;
    int r = ice_ctx_->ctx()->SetNat(nat_);
    (void)r;
    MOZ_ASSERT(!r);
  }

  ~IceTestPeer() {
    test_utils_->sts_target()->Dispatch(WrapRunnable(this,
                                                    &IceTestPeer::Shutdown),
        NS_DISPATCH_SYNC);

    // Give the ICE destruction callback time to fire before
    // we destroy the resolver.
    PR_Sleep(1000);
  }

  void AddStream_s(int components) {
    char name[100];
    snprintf(name, sizeof(name), "%s:stream%d", name_.c_str(),
             (int)ice_ctx_->ctx()->GetStreamCount());

    RefPtr<NrIceMediaStream> stream =
        ice_ctx_->CreateStream(static_cast<char *>(name), components);
    ice_ctx_->ctx()->SetStream(ice_ctx_->ctx()->GetStreamCount(), stream);

    ASSERT_TRUE(stream);
    stream->SignalCandidate.connect(this, &IceTestPeer::CandidateInitialized);
    stream->SignalReady.connect(this, &IceTestPeer::StreamReady);
    stream->SignalFailed.connect(this, &IceTestPeer::StreamFailed);
    stream->SignalPacketReceived.connect(this, &IceTestPeer::PacketReceived);
  }

  void AddStream(int components)
  {
    test_utils_->sts_target()->Dispatch(
        WrapRunnable(this, &IceTestPeer::AddStream_s, components),
        NS_DISPATCH_SYNC);
  }

  void RemoveStream_s(size_t index) {
    ice_ctx_->ctx()->SetStream(index, nullptr);
  }

  void RemoveStream(size_t index) {
    test_utils_->sts_target()->Dispatch(
        WrapRunnable(this, &IceTestPeer::RemoveStream_s, index),
        NS_DISPATCH_SYNC);
  }

  void SetStunServer(const std::string addr, uint16_t port,
                     const char* transport = kNrIceTransportUdp) {
    if (addr.empty()) {
      // Happens when MOZ_DISABLE_NONLOCAL_CONNECTIONS is set
      return;
    }

    std::vector<NrIceStunServer> stun_servers;
    UniquePtr<NrIceStunServer> server(NrIceStunServer::Create(
        addr, port, transport));
    stun_servers.push_back(*server);
    SetStunServers(stun_servers);
  }

  void SetStunServers(const std::vector<NrIceStunServer> &servers) {
    ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->ctx()->SetStunServers(servers)));
  }

  void UseTestStunServer() {
    SetStunServer(TestStunServer::GetInstance(AF_INET)->addr(),
                  TestStunServer::GetInstance(AF_INET)->port());
  }

  void SetTurnServer(const std::string addr, uint16_t port,
                     const std::string username,
                     const std::string password,
                     const char* transport) {
    std::vector<unsigned char> password_vec(password.begin(), password.end());
    SetTurnServer(addr, port, username, password_vec, transport);
  }


  void SetTurnServer(const std::string addr, uint16_t port,
                     const std::string username,
                     const std::vector<unsigned char> password,
                     const char* transport) {
    std::vector<NrIceTurnServer> turn_servers;
    UniquePtr<NrIceTurnServer> server(NrIceTurnServer::Create(
        addr, port, username, password, transport));
    turn_servers.push_back(*server);
    ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->ctx()->SetTurnServers(turn_servers)));
  }

  void SetTurnServers(const std::vector<NrIceTurnServer> servers) {
    ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->ctx()->SetTurnServers(servers)));
  }

  void SetFakeResolver(const std::string& ip,
                       const std::string& fqdn) {
    ASSERT_TRUE(NS_SUCCEEDED(dns_resolver_->Init()));
    if (!ip.empty() && !fqdn.empty()) {
      PRNetAddr addr;
      PRStatus status = PR_StringToNetAddr(ip.c_str(), &addr);
      addr.inet.port = kDefaultStunServerPort;
      ASSERT_EQ(PR_SUCCESS, status);
      fake_resolver_.SetAddr(fqdn, addr);
    }
    ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->ctx()->SetResolver(
        fake_resolver_.AllocateResolver())));
  }

  void SetDNSResolver() {
    ASSERT_TRUE(NS_SUCCEEDED(dns_resolver_->Init()));
    ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->ctx()->SetResolver(
        dns_resolver_->AllocateResolver())));
  }

  void Gather(bool default_route_only = false) {
    nsresult res;

    test_utils_->sts_target()->Dispatch(
        WrapRunnableRet(&res,
                        ice_ctx_->ctx(),
                        &NrIceCtx::StartGathering,
                        default_route_only,
                        false),
        NS_DISPATCH_SYNC);

    ASSERT_TRUE(NS_SUCCEEDED(res));
  }

  void UseNat() {
    nat_->enabled_ = true;
  }

  void SetTimerDivider(int div) {
    ice_ctx_->ctx()->internal_SetTimerAccelarator(div);
  }

  void SetStunResponseDelay(uint32_t delay) {
    nat_->delay_stun_resp_ms_ = delay;
  }

  void SetFilteringType(TestNat::NatBehavior type) {
    MOZ_ASSERT(!nat_->has_port_mappings());
    nat_->filtering_type_ = type;
  }

  void SetMappingType(TestNat::NatBehavior type) {
    MOZ_ASSERT(!nat_->has_port_mappings());
    nat_->mapping_type_ = type;
  }

  void SetBlockUdp(bool block) {
    MOZ_ASSERT(!nat_->has_port_mappings());
    nat_->block_udp_ = block;
  }

  void SetBlockStun(bool block) {
    nat_->block_stun_ = block;
  }

  // Get various pieces of state
  std::vector<std::string> GetGlobalAttributes() {
    std::vector<std::string> attrs(ice_ctx_->ctx()->GetGlobalAttributes());
    if (simulate_ice_lite_) {
      attrs.push_back("ice-lite");
    }
    return attrs;
  }

  std::vector<std::string> GetCandidates(size_t stream) {
    std::vector<std::string> v;

    RUN_ON_THREAD(
        test_utils_->sts_target(),
        WrapRunnableRet(&v, this, &IceTestPeer::GetCandidates_s, stream));

    return v;
  }

  std::string FilterCandidate(const std::string& candidate) {
    if (candidate_filter_) {
      return candidate_filter_(candidate);
    }
    return candidate;
  }

  std::vector<std::string> GetCandidates_s(size_t stream) {
    std::vector<std::string> candidates;

    if (stream >= ice_ctx_->ctx()->GetStreamCount() ||
        !ice_ctx_->ctx()->GetStream(stream)) {
      EXPECT_TRUE(false) << "No such stream " << stream;
      return candidates;
    }

    std::vector<std::string> candidates_in =
      ice_ctx_->ctx()->GetStream(stream)->GetCandidates();

    for (size_t i=0; i < candidates_in.size(); i++) {
      std::string candidate(FilterCandidate(candidates_in[i]));
      if (!candidate.empty()) {
        std::cerr << name_ << " Returning candidate: "
                           << candidate << std::endl;
        candidates.push_back(candidate);
      }
    }

    return candidates;
  }

  void SetExpectedTypes(NrIceCandidate::Type local,
                        NrIceCandidate::Type remote,
                        std::string local_transport = kNrIceTransportUdp) {
    expected_local_type_ = local;
    expected_local_transport_ = local_transport;
    expected_remote_type_ = remote;
  }

  void SetExpectedRemoteCandidateAddr(const std::string& addr) {
    expected_remote_addr_ = addr;
  }

  int GetCandidatesPrivateIpv4Range(size_t stream) {
    std::vector<std::string> candidates = GetCandidates(stream);

    int host_net = 0;
    for (auto c : candidates) {
      if (c.find("typ host") != std::string::npos) {
        nr_transport_addr addr;
        std::vector<std::string> tokens = split(c, ' ');
        int r = nr_str_port_to_transport_addr(tokens.at(4).c_str(), 0, IPPROTO_UDP, &addr);
        MOZ_ASSERT(!r);
        if (!r && (addr.ip_version == NR_IPV4)) {
          int n = nr_transport_addr_get_private_addr_range(&addr);
          if (n) {
            if (host_net) {
              // TODO: add support for multiple private interfaces
              std::cerr << "This test doesn't support multiple private interfaces";
              return -1;
            }
            host_net = n;
          }
        }
      }
    }
    return host_net;
  }

  bool gathering_complete() { return gathering_complete_; }
  int ready_ct() { return ready_ct_; }
  bool is_ready_s(size_t stream) {
    RefPtr<NrIceMediaStream> media_stream = ice_ctx_->ctx()->GetStream(stream);
    if (!media_stream) {
      EXPECT_TRUE(false) << "No such stream " << stream;
      return false;
    }
    return media_stream->state() == NrIceMediaStream::ICE_OPEN;
  }
  bool is_ready(size_t stream)
  {
    bool result;
    test_utils_->sts_target()->Dispatch(
        WrapRunnableRet(&result, this, &IceTestPeer::is_ready_s, stream),
        NS_DISPATCH_SYNC);
    return result;
  }
  bool ice_connected() { return ice_connected_; }
  bool ice_failed() { return ice_failed_; }
  bool ice_reached_checking() { return ice_reached_checking_; }
  size_t received() { return received_; }
  size_t sent() { return sent_; }


  void RestartIce() {
    test_utils_->sts_target()->Dispatch(
        WrapRunnable(this,
                     &IceTestPeer::RestartIce_s,
                     ice_ctx_->CreateCtx()),
        NS_DISPATCH_SYNC);
  }


  void RestartIce_s(RefPtr<NrIceCtx> new_ctx) {
    ice_ctx_->BeginIceRestart(new_ctx);

    // set signals for the newly restarted ctx
    ice_ctx_->ctx()->SignalGatheringStateChange.connect(
        this,
        &IceTestPeer::GatheringStateChange);
    ice_ctx_->ctx()->SignalConnectionStateChange.connect(
        this,
        &IceTestPeer::ConnectionStateChange);

    // take care of some local bookkeeping
    ready_ct_ = 0;
    gathering_complete_ = false;
    ice_connected_ = false;
    ice_failed_ = false;
    ice_reached_checking_ = false;
    remote_ = nullptr;
  }


  void FinalizeIceRestart() {
    test_utils_->sts_target()->Dispatch(
        WrapRunnable(this, &IceTestPeer::FinalizeIceRestart_s),
        NS_DISPATCH_SYNC);
  }


  void FinalizeIceRestart_s() {
    ice_ctx_->FinalizeIceRestart();
  }


  void RollbackIceRestart() {
    test_utils_->sts_target()->Dispatch(
        WrapRunnable(this, &IceTestPeer::RollbackIceRestart_s),
        NS_DISPATCH_SYNC);
  }


  void RollbackIceRestart_s() {
    ice_ctx_->RollbackIceRestart();
  }


  // Start connecting to another peer
  void Connect_s(IceTestPeer *remote, TrickleMode trickle_mode,
                 bool start = true) {
    nsresult res;

    remote_ = remote;

    trickle_mode_ = trickle_mode;
    ice_connected_ = false;
    ice_failed_ = false;
    ice_reached_checking_ = false;
    res = ice_ctx_->ctx()->ParseGlobalAttributes(remote->GetGlobalAttributes());
    ASSERT_TRUE(NS_SUCCEEDED(res));

    if (trickle_mode == TRICKLE_NONE ||
        trickle_mode == TRICKLE_REAL) {
      for (size_t i=0; i<ice_ctx_->ctx()->GetStreamCount(); ++i) {
        RefPtr<NrIceMediaStream> aStream = ice_ctx_->ctx()->GetStream(i);
        if (!aStream || aStream->HasParsedAttributes()) {
          continue;
        }
        std::vector<std::string> candidates =
            remote->GetCandidates(i);

        for (size_t j=0; j<candidates.size(); ++j) {
          std::cerr << name_ << " Adding remote candidate: " + candidates[j] << std::endl;
        }
        res = aStream->ParseAttributes(candidates);
        ASSERT_TRUE(NS_SUCCEEDED(res));
      }
    } else {
      // Parse empty attributes and then trickle them out later
      for (size_t i=0; i<ice_ctx_->ctx()->GetStreamCount(); ++i) {
        RefPtr<NrIceMediaStream> aStream = ice_ctx_->ctx()->GetStream(i);
        if (!aStream || aStream->HasParsedAttributes()) {
          continue;
        }
        std::vector<std::string> empty_attrs;
        std::cout << "Calling ParseAttributes on stream " << i << std::endl;
        res = aStream->ParseAttributes(empty_attrs);
        ASSERT_TRUE(NS_SUCCEEDED(res));
      }
    }

    if (start) {
      // Now start checks
      res = ice_ctx_->ctx()->StartChecks();
      ASSERT_TRUE(NS_SUCCEEDED(res));
    }
  }

  void Connect(IceTestPeer *remote, TrickleMode trickle_mode,
               bool start = true) {
    test_utils_->sts_target()->Dispatch(
        WrapRunnable(
            this, &IceTestPeer::Connect_s, remote, trickle_mode, start),
        NS_DISPATCH_SYNC);
  }

  void SimulateTrickle(size_t stream) {
    std::cerr << name_ << " Doing trickle for stream " << stream << std::endl;
    // If we are in trickle deferred mode, now trickle in the candidates
    // for |stream|

    // We should be safe here since stream changes happen on STS thread.
    ASSERT_GT(remote_->ice_ctx_->ctx()->GetStreamCount(), stream);
    ASSERT_TRUE(remote_->ice_ctx_->ctx()->GetStream(stream).get());

    std::vector<SchedulableTrickleCandidate*>& candidates =
      ControlTrickle(stream);

    for (auto i = candidates.begin(); i != candidates.end(); ++i) {
      (*i)->Schedule(0);
    }
  }

  // Allows test case to completely control when/if candidates are trickled
  // (test could also do things like insert extra trickle candidates, or
  // change existing ones, or insert duplicates, really anything is fair game)
  std::vector<SchedulableTrickleCandidate*>& ControlTrickle(size_t stream) {
    std::cerr << "Doing controlled trickle for stream " << stream << std::endl;

    std::vector<std::string> candidates =
      remote_->GetCandidates(stream);

    for (size_t j=0; j<candidates.size(); j++) {
      controlled_trickle_candidates_[stream].push_back(
          new SchedulableTrickleCandidate(
              this, stream, candidates[j], test_utils_));
    }

    return controlled_trickle_candidates_[stream];
  }

  nsresult TrickleCandidate_s(const std::string &candidate, size_t stream) {
    if (!ice_ctx_->ctx()->GetStream(stream)) {
      // stream might have gone away before the trickle timer popped
      return NS_OK;
    }
    return ice_ctx_->ctx()->GetStream(stream)->ParseTrickleCandidate(candidate);
  }

  void DumpCandidate(std::string which, const NrIceCandidate& cand) {
    std::string type;
    std::string tcp_type;

    std::string addr;
    int port;

    if (which.find("Remote") != std::string::npos) {
      addr = cand.cand_addr.host;
      port = cand.cand_addr.port;
    }
    else {
      addr = cand.local_addr.host;
      port = cand.local_addr.port;
    }
    switch(cand.type) {
      case NrIceCandidate::ICE_HOST:
        type = "host";
        break;
      case NrIceCandidate::ICE_SERVER_REFLEXIVE:
        type = "srflx";
        break;
      case NrIceCandidate::ICE_PEER_REFLEXIVE:
        type = "prflx";
        break;
      case NrIceCandidate::ICE_RELAYED:
        type = "relay";
        if (which.find("Local") != std::string::npos) {
          type += "(" + cand.local_addr.transport + ")";
        }
        break;
      default:
        FAIL();
    };

    switch(cand.tcp_type) {
      case NrIceCandidate::ICE_NONE:
        break;
      case NrIceCandidate::ICE_ACTIVE:
        tcp_type = " tcptype=active";
        break;
      case NrIceCandidate::ICE_PASSIVE:
        tcp_type = " tcptype=passive";
        break;
      case NrIceCandidate::ICE_SO:
        tcp_type = " tcptype=so";
        break;
      default:
        FAIL();
    };


    std::cerr << which
              << " --> "
              << type
              << " "
              << addr
              << ":"
              << port
              << "/"
              << cand.cand_addr.transport
              << tcp_type
              << " codeword="
              << cand.codeword
              << std::endl;
  }

  void DumpAndCheckActiveCandidates_s() {
    std::cerr << name_ << " Active candidates:" << std::endl;
    for (size_t i=0; i < ice_ctx_->ctx()->GetStreamCount(); ++i) {
      if (!ice_ctx_->ctx()->GetStream(i)) {
        continue;
      }

      for (size_t j=0; j < ice_ctx_->ctx()->GetStream(i)->components(); ++j) {
        std::cerr << name_ << " Stream " << i
                           << " component " << j+1 << std::endl;

        UniquePtr<NrIceCandidate> local;
        UniquePtr<NrIceCandidate> remote;

        nsresult res = ice_ctx_->ctx()->GetStream(i)->GetActivePair(j+1,
                                                                    &local,
                                                                    &remote);
        if (res == NS_ERROR_NOT_AVAILABLE) {
          std::cerr << "Component unpaired or disabled." << std::endl;
        } else {
          ASSERT_TRUE(NS_SUCCEEDED(res));
          DumpCandidate("Local  ", *local);
          /* Depending on timing, and the whims of the network
           * stack/configuration we're running on top of, prflx is always a
           * possibility. */
          if (expected_local_type_ == NrIceCandidate::ICE_HOST) {
            ASSERT_NE(NrIceCandidate::ICE_SERVER_REFLEXIVE, local->type);
            ASSERT_NE(NrIceCandidate::ICE_RELAYED, local->type);
          } else {
            ASSERT_EQ(expected_local_type_, local->type);
          }
          ASSERT_EQ(expected_local_transport_, local->local_addr.transport);
          DumpCandidate("Remote ", *remote);
          /* Depending on timing, and the whims of the network
           * stack/configuration we're running on top of, prflx is always a
           * possibility. */
          if (expected_remote_type_ == NrIceCandidate::ICE_HOST) {
            ASSERT_NE(NrIceCandidate::ICE_SERVER_REFLEXIVE, remote->type);
            ASSERT_NE(NrIceCandidate::ICE_RELAYED, remote->type);
          } else {
            ASSERT_EQ(expected_remote_type_, remote->type);
          }
          if (!expected_remote_addr_.empty()) {
            ASSERT_EQ(expected_remote_addr_, remote->cand_addr.host);
          }
        }
      }
    }
  }

  void DumpAndCheckActiveCandidates() {
    test_utils_->sts_target()->Dispatch(
      WrapRunnable(this, &IceTestPeer::DumpAndCheckActiveCandidates_s),
      NS_DISPATCH_SYNC);
  }

  void Close() {
    test_utils_->sts_target()->Dispatch(
      WrapRunnable(ice_ctx_->ctx(), &NrIceCtx::destroy_peer_ctx),
      NS_DISPATCH_SYNC);
  }

  void Shutdown() {
    std::cerr << name_ << " Shutdown" << std::endl;
    shutting_down_ = true;
    for (auto s = controlled_trickle_candidates_.begin();
         s != controlled_trickle_candidates_.end();
         ++s) {
      for (auto cand = s->second.begin(); cand != s->second.end(); ++cand) {
        delete *cand;
      }
    }

    ice_ctx_ = nullptr;

    if (remote_) {
      remote_->UnsetRemote();
      remote_ = nullptr;
    }
  }

  void UnsetRemote()
  {
    remote_ = nullptr;
  }

  void StartChecks() {
    nsresult res;

    // Now start checks
    test_utils_->sts_target()->Dispatch(
        WrapRunnableRet(&res, ice_ctx_->ctx(), &NrIceCtx::StartChecks),
        NS_DISPATCH_SYNC);
    ASSERT_TRUE(NS_SUCCEEDED(res));
  }

  // Handle events
  void GatheringStateChange(NrIceCtx* ctx,
                            NrIceCtx::GatheringState state) {
    if (shutting_down_) {
      return;
    }
    if (state != NrIceCtx::ICE_CTX_GATHER_COMPLETE) {
      return;
    }

    std::cerr << name_ << " Gathering complete" << std::endl;
    gathering_complete_ = true;

    std::cerr << name_ << " CANDIDATES:" << std::endl;
    for (size_t i=0; i<ice_ctx_->ctx()->GetStreamCount(); ++i) {
      std::cerr << "Stream " << name_ << std::endl;

      if (!ice_ctx_->ctx()->GetStream(i)) {
        std::cerr << "DISABLED" << std::endl;
        continue;
      }

      std::vector<std::string> candidates =
          ice_ctx_->ctx()->GetStream(i)->GetCandidates();

      for(size_t j=0; j<candidates.size(); ++j) {
        std::cerr << candidates[j] << std::endl;
      }
    }
    std::cerr << std::endl;

  }

  void CandidateInitialized(NrIceMediaStream *stream, const std::string &raw_candidate) {
    std::string candidate(FilterCandidate(raw_candidate));
    if (candidate.empty()) {
      return;
    }
    std::cerr << "Candidate for stream " << stream->name() << " initialized: "
      << candidate << std::endl;
    candidates_[stream->name()].push_back(candidate);

    // If we are connected, then try to trickle to the other side.
    if (remote_ && remote_->remote_ && (trickle_mode_ != TRICKLE_SIMULATE)) {
      // first, find the index of the stream we've been given so
      // we can get the corresponding stream on the remote side
      for (size_t i=0; i<ice_ctx_->ctx()->GetStreamCount(); ++i) {
        if (ice_ctx_->ctx()->GetStream(i) == stream) {
          RefPtr<NrIceCtx> ctx = remote_->ice_ctx_->ctx();
          ASSERT_GT(ctx->GetStreamCount(), i);
          nsresult res = ctx->GetStream(i)->ParseTrickleCandidate(candidate);
          ASSERT_TRUE(NS_SUCCEEDED(res));
          ++trickled_;
          return;
        }
      }
      ADD_FAILURE() << "No matching stream found for " << stream;
    }
  }

  nsresult GetCandidatePairs_s(size_t stream_index,
                               std::vector<NrIceCandidatePair>* pairs)
  {
    MOZ_ASSERT(pairs);
    if (stream_index >= ice_ctx_->ctx()->GetStreamCount() ||
        !ice_ctx_->ctx()->GetStream(stream_index)) {
      // Is there a better error for "no such index"?
      ADD_FAILURE() << "No such media stream index: " << stream_index;
      return NS_ERROR_INVALID_ARG;
    }

    return ice_ctx_->ctx()->GetStream(stream_index)->GetCandidatePairs(pairs);
  }

  nsresult GetCandidatePairs(size_t stream_index,
                             std::vector<NrIceCandidatePair>* pairs) {
    nsresult v;
    test_utils_->sts_target()->Dispatch(
        WrapRunnableRet(&v, this,
                        &IceTestPeer::GetCandidatePairs_s,
                        stream_index,
                        pairs),
        NS_DISPATCH_SYNC);
    return v;
  }

  void DumpCandidatePair(const NrIceCandidatePair& pair) {
      std::cerr << std::endl;
      DumpCandidate("Local", pair.local);
      DumpCandidate("Remote", pair.remote);
      std::cerr << "state = " << pair.state
                << " priority = " << pair.priority
                << " nominated = " << pair.nominated
                << " selected = " << pair.selected
                << " codeword = " << pair.codeword << std::endl;
  }

  void DumpCandidatePairs_s(NrIceMediaStream *stream) {
    std::vector<NrIceCandidatePair> pairs;
    nsresult res = stream->GetCandidatePairs(&pairs);
    ASSERT_TRUE(NS_SUCCEEDED(res));

    std::cerr << "Begin list of candidate pairs [" << std::endl;

    for (std::vector<NrIceCandidatePair>::iterator p = pairs.begin();
         p != pairs.end(); ++p) {
      DumpCandidatePair(*p);
    }
    std::cerr << "]" << std::endl;
  }

  void DumpCandidatePairs_s() {
    std::cerr << "Dumping candidate pairs for all streams [" << std::endl;
    for (size_t s = 0; s < ice_ctx_->ctx()->GetStreamCount(); ++s) {
      if (!ice_ctx_->ctx()->GetStream(s)) {
        continue;
      }
      DumpCandidatePairs_s(ice_ctx_->ctx()->GetStream(s).get());
    }
    std::cerr << "]" << std::endl;
  }

  bool CandidatePairsPriorityDescending(const std::vector<NrIceCandidatePair>&
                                        pairs) {
    // Verify that priority is descending
    uint64_t priority = std::numeric_limits<uint64_t>::max();

    for (size_t p = 0; p < pairs.size(); ++p) {
      if (priority < pairs[p].priority) {
        std::cerr << "Priority increased in subsequent pairs:" << std::endl;
        DumpCandidatePair(pairs[p-1]);
        DumpCandidatePair(pairs[p]);
        return false;
      } else if (priority == pairs[p].priority) {
        if (!IceCandidatePairCompare()(pairs[p], pairs[p-1]) &&
            !IceCandidatePairCompare()(pairs[p-1], pairs[p])) {
          std::cerr << "Ignoring identical pair from trigger check" << std::endl;
        } else {
          std::cerr << "Duplicate priority in subseqent pairs:" << std::endl;
          DumpCandidatePair(pairs[p-1]);
          DumpCandidatePair(pairs[p]);
          return false;
        }
      }
      priority = pairs[p].priority;
    }
    return true;
  }

  void UpdateAndValidateCandidatePairs(size_t stream_index,
                                       std::vector<NrIceCandidatePair>*
                                       new_pairs) {
    std::vector<NrIceCandidatePair> old_pairs = *new_pairs;
    GetCandidatePairs(stream_index, new_pairs);
    ASSERT_TRUE(CandidatePairsPriorityDescending(*new_pairs)) << "New list of "
            "candidate pairs is either not sorted in priority order, or has "
            "duplicate priorities.";
    ASSERT_TRUE(CandidatePairsPriorityDescending(old_pairs)) << "Old list of "
            "candidate pairs is either not sorted in priority order, or has "
            "duplicate priorities. This indicates some bug in the test case.";
    std::vector<NrIceCandidatePair> added_pairs;
    std::vector<NrIceCandidatePair> removed_pairs;

    // set_difference computes the set of elements that are present in the
    // first set, but not the second
    // NrIceCandidatePair::operator< compares based on the priority, local
    // candidate, and remote candidate in that order. This means this will
    // catch cases where the priority has remained the same, but one of the
    // candidates has changed.
    std::set_difference((*new_pairs).begin(),
                        (*new_pairs).end(),
                        old_pairs.begin(),
                        old_pairs.end(),
                        std::inserter(added_pairs, added_pairs.begin()),
                        IceCandidatePairCompare());

    std::set_difference(old_pairs.begin(),
                        old_pairs.end(),
                        (*new_pairs).begin(),
                        (*new_pairs).end(),
                        std::inserter(removed_pairs, removed_pairs.begin()),
                        IceCandidatePairCompare());

    for (std::vector<NrIceCandidatePair>::iterator a = added_pairs.begin();
         a != added_pairs.end(); ++a) {
        std::cerr << "Found new candidate pair." << std::endl;
        DumpCandidatePair(*a);
    }

    for (std::vector<NrIceCandidatePair>::iterator r = removed_pairs.begin();
         r != removed_pairs.end(); ++r) {
        std::cerr << "Pre-existing candidate pair is now missing:" << std::endl;
        DumpCandidatePair(*r);
    }

    ASSERT_TRUE(removed_pairs.empty()) << "At least one candidate pair has "
                                          "gone missing.";
  }

  void StreamReady(NrIceMediaStream *stream) {
    ++ready_ct_;
    std::cerr << name_ << " Stream ready for " << stream->name()
                       << " ct=" << ready_ct_ << std::endl;
    DumpCandidatePairs_s(stream);
  }
  void StreamFailed(NrIceMediaStream *stream) {
    std::cerr << name_ << " Stream failed for " << stream->name()
                       << " ct=" << ready_ct_ << std::endl;
    DumpCandidatePairs_s(stream);
  }

  void ConnectionStateChange(NrIceCtx* ctx,
                             NrIceCtx::ConnectionState state) {
    (void)ctx;
    switch (state) {
      case NrIceCtx::ICE_CTX_INIT:
        break;
      case NrIceCtx::ICE_CTX_CHECKING:
        std::cerr << name_ << " ICE reached checking" << std::endl;
        ice_reached_checking_ = true;
        break;
      case NrIceCtx::ICE_CTX_CONNECTED:
        std::cerr << name_ << " ICE connected" << std::endl;
        ice_connected_ = true;
        break;
      case NrIceCtx::ICE_CTX_COMPLETED:
        std::cerr << name_ << " ICE completed" << std::endl;
        break;
      case NrIceCtx::ICE_CTX_FAILED:
        std::cerr << name_ << " ICE failed" << std::endl;
        ice_failed_ = true;
        break;
      case NrIceCtx::ICE_CTX_DISCONNECTED:
        std::cerr << name_ << " ICE disconnected" << std::endl;
        ice_connected_ = false;
        break;
      default:
        MOZ_CRASH();
    }
  }

  void PacketReceived(NrIceMediaStream *stream, int component, const unsigned char *data,
                      int len) {
    std::cerr << name_ << ": received " << len << " bytes" << std::endl;
    ++received_;
  }

  void SendPacket(int stream, int component, const unsigned char *data,
                  int len) {
    RefPtr<NrIceMediaStream> media_stream = ice_ctx_->ctx()->GetStream(stream);
    if (!media_stream) {
      ADD_FAILURE() << "No such stream " << stream;
      return;
    }

    ASSERT_TRUE(NS_SUCCEEDED(media_stream->SendPacket(component, data, len)));

    ++sent_;
    std::cerr << name_ << ": sent " << len << " bytes" << std::endl;
  }

  void SendFailure(int stream, int component) {
    RefPtr<NrIceMediaStream> media_stream = ice_ctx_->ctx()->GetStream(stream);
    if (!media_stream) {
      ADD_FAILURE() << "No such stream " << stream;
      return;
    }

    const std::string d("FAIL");
    ASSERT_TRUE(NS_FAILED(media_stream->SendPacket(component,
      reinterpret_cast<const unsigned char *>(d.c_str()), d.length())));

    std::cerr << name_ << ": send failed as expected" << std::endl;
  }

  void SetCandidateFilter(CandidateFilter filter) {
    candidate_filter_ = filter;
  }

  void ParseCandidate_s(size_t i, const std::string& candidate) {
    ASSERT_TRUE(ice_ctx_->ctx()->GetStream(i).get()) << "No such stream " << i;

    std::vector<std::string> attributes;

    attributes.push_back(candidate);
    ice_ctx_->ctx()->GetStream(i)->ParseAttributes(attributes);
  }

  void ParseCandidate(size_t i, const std::string& candidate)
  {
    test_utils_->sts_target()->Dispatch(
        WrapRunnable(this,
                        &IceTestPeer::ParseCandidate_s,
                        i,
                        candidate),
        NS_DISPATCH_SYNC);
  }

  void DisableComponent_s(size_t stream, int component_id) {
    ASSERT_LT(stream, ice_ctx_->ctx()->GetStreamCount());
    ASSERT_TRUE(ice_ctx_->ctx()->GetStream(stream).get()) << "No such stream "
                                                          << stream;
    nsresult res =
      ice_ctx_->ctx()->GetStream(stream)->DisableComponent(component_id);
    ASSERT_TRUE(NS_SUCCEEDED(res));
  }

  void DisableComponent(size_t stream, int component_id)
  {
    test_utils_->sts_target()->Dispatch(
        WrapRunnable(this,
                        &IceTestPeer::DisableComponent_s,
                        stream,
                        component_id),
        NS_DISPATCH_SYNC);
  }

  void AssertConsentRefresh_s(size_t stream, int component_id, ConsentStatus status) {
    ASSERT_LT(stream, ice_ctx_->ctx()->GetStreamCount());
    ASSERT_TRUE(ice_ctx_->ctx()->GetStream(stream).get()) << "No such stream "
                                                          << stream;
    bool can_send;
    struct timeval timestamp;
    nsresult res = ice_ctx_->ctx()->GetStream(stream)->
                    GetConsentStatus(component_id, &can_send, &timestamp);
    ASSERT_TRUE(NS_SUCCEEDED(res));
    if (status == CONSENT_EXPIRED) {
      ASSERT_EQ(can_send, 0);
    } else {
      ASSERT_EQ(can_send, 1);
    }
    if (consent_timestamp_.tv_sec) {
      if (status == CONSENT_FRESH) {
        ASSERT_EQ(r_timeval_cmp(&timestamp, &consent_timestamp_), 1);
      } else {
        ASSERT_EQ(r_timeval_cmp(&timestamp, &consent_timestamp_), 0);
      }
    }
    consent_timestamp_.tv_sec = timestamp.tv_sec;
    consent_timestamp_.tv_usec = timestamp.tv_usec;
    std::cerr << name_ << ": new consent timestamp = " <<
      consent_timestamp_.tv_sec << "." << consent_timestamp_.tv_usec <<
      std::endl;
  }

  void AssertConsentRefresh(ConsentStatus status) {
    test_utils_->sts_target()->Dispatch(
        WrapRunnable(this,
                        &IceTestPeer::AssertConsentRefresh_s,
                        0,
                        1,
                        status),
        NS_DISPATCH_SYNC);
  }

  int trickled() { return trickled_; }

  void SetControlling(NrIceCtx::Controlling controlling) {
    nsresult res;
    test_utils_->sts_target()->Dispatch(
        WrapRunnableRet(&res, ice_ctx_->ctx(),
                        &NrIceCtx::SetControlling,
                        controlling),
        NS_DISPATCH_SYNC);
    ASSERT_TRUE(NS_SUCCEEDED(res));
  }

  NrIceCtx::Controlling GetControlling() {
    return ice_ctx_->ctx()->GetControlling();
  }

  void SetTiebreaker(uint64_t tiebreaker) {
    test_utils_->sts_target()->Dispatch(
        WrapRunnable(this,
                     &IceTestPeer::SetTiebreaker_s,
                     tiebreaker),
        NS_DISPATCH_SYNC);
  }

  void SetTiebreaker_s(uint64_t tiebreaker) {
    ice_ctx_->ctx()->peer()->tiebreaker = tiebreaker;
  }

  void SimulateIceLite() {
    simulate_ice_lite_ = true;
    SetControlling(NrIceCtx::ICE_CONTROLLED);
  }

  nsresult GetDefaultCandidate(unsigned int stream, NrIceCandidate* cand) {
    nsresult rv;

    test_utils_->sts_target()->Dispatch(
        WrapRunnableRet(&rv, this,
                        &IceTestPeer::GetDefaultCandidate_s,
                        stream, cand),
        NS_DISPATCH_SYNC);

    return rv;
  }

  nsresult GetDefaultCandidate_s(unsigned int stream, NrIceCandidate* cand) {
    return ice_ctx_->ctx()->GetStream(stream)->GetDefaultCandidate(1, cand);
  }

 private:
  std::string name_;
  RefPtr<NrIceCtxHandler> ice_ctx_;
  std::map<std::string, std::vector<std::string> > candidates_;
  // Maps from stream id to list of remote trickle candidates
  std::map<size_t, std::vector<SchedulableTrickleCandidate*> >
    controlled_trickle_candidates_;
  bool shutting_down_;
  bool gathering_complete_;
  int ready_ct_;
  bool ice_connected_;
  bool ice_failed_;
  bool ice_reached_checking_;
  size_t received_;
  size_t sent_;
  struct timeval consent_timestamp_;
  NrIceResolverFake fake_resolver_;
  RefPtr<NrIceResolver> dns_resolver_;
  IceTestPeer *remote_;
  CandidateFilter candidate_filter_;
  NrIceCandidate::Type expected_local_type_;
  std::string expected_local_transport_;
  NrIceCandidate::Type expected_remote_type_;
  std::string expected_remote_addr_;
  TrickleMode trickle_mode_;
  int trickled_;
  bool simulate_ice_lite_;
  RefPtr<mozilla::TestNat> nat_;
  MtransportTestUtils* test_utils_;
};

void SchedulableTrickleCandidate::Trickle() {
  timer_handle_ = nullptr;
  nsresult res = peer_->TrickleCandidate_s(candidate_, stream_);
  ASSERT_TRUE(NS_SUCCEEDED(res));
}

class WebRtcIceGatherTest : public StunTest {
 public:
  void SetUp() override {
    StunTest::SetUp();

    Preferences::SetInt("media.peerconnection.ice.tcp_so_sock_count", 3);

    test_utils_->sts_target()->Dispatch(
        WrapRunnable(TestStunServer::GetInstance(AF_INET),
                     &TestStunServer::Reset),
        NS_DISPATCH_SYNC);
    if (TestStunServer::GetInstance(AF_INET6)) {
      test_utils_->sts_target()->Dispatch(
          WrapRunnable(TestStunServer::GetInstance(AF_INET6),
                       &TestStunServer::Reset),
          NS_DISPATCH_SYNC);
    }
  }

  void TearDown() override {
    peer_ = nullptr;
    StunTest::TearDown();
  }

  void EnsurePeer(const unsigned int flags = ICE_TEST_PEER_OFFERER) {
    if (!peer_) {
      peer_ = MakeUnique<IceTestPeer>("P1", test_utils_,
                                      flags & ICE_TEST_PEER_OFFERER,
                                      flags & ICE_TEST_PEER_ALLOW_LOOPBACK,
                                      flags & ICE_TEST_PEER_ENABLED_TCP,
                                      flags & ICE_TEST_PEER_ALLOW_LINK_LOCAL);
      peer_->AddStream(1);
    }
  }

  void Gather(unsigned int waitTime = kDefaultTimeout) {
    EnsurePeer();
    peer_->Gather();

    if (waitTime) {
      WaitForGather(waitTime);
    }
  }

  void WaitForGather(unsigned int waitTime = kDefaultTimeout) {
    ASSERT_TRUE_WAIT(peer_->gathering_complete(), waitTime);
  }

  void AddStunServerWithResponse(
      const std::string& fake_addr,
      uint16_t fake_port,
      const std::string& fqdn,
      const std::string& proto,
      std::vector<NrIceStunServer>* stun_servers) {
    int family;
    if (fake_addr.find(':') != std::string::npos) {
      family = AF_INET6;
    } else {
      family = AF_INET;
    }

    std::string stun_addr;
    uint16_t stun_port;
    if (proto == kNrIceTransportUdp) {
      TestStunServer::GetInstance(family)->SetResponseAddr(fake_addr,
                                                           fake_port);
      stun_addr = TestStunServer::GetInstance(family)->addr();
      stun_port = TestStunServer::GetInstance(family)->port();
    } else if (proto == kNrIceTransportTcp) {
      TestStunTcpServer::GetInstance(family)->SetResponseAddr(fake_addr,
                                                              fake_port);
      stun_addr = TestStunTcpServer::GetInstance(family)->addr();
      stun_port = TestStunTcpServer::GetInstance(family)->port();
    } else {
      MOZ_CRASH();
    }

    if (!fqdn.empty()) {
      peer_->SetFakeResolver(stun_addr, fqdn);
      stun_addr = fqdn;
    }

    stun_servers->push_back(*NrIceStunServer::Create(stun_addr,
                                                     stun_port,
                                                     proto.c_str()));
  }

  void UseFakeStunUdpServerWithResponse(
      const std::string& fake_addr,
      uint16_t fake_port,
      const std::string& fqdn = std::string()) {
    EnsurePeer();
    std::vector<NrIceStunServer> stun_servers;
    AddStunServerWithResponse(fake_addr, fake_port, fqdn, "udp", &stun_servers);
    peer_->SetStunServers(stun_servers);
  }

  void UseFakeStunTcpServerWithResponse(
      const std::string& fake_addr,
      uint16_t fake_port,
      const std::string& fqdn = std::string()) {
    EnsurePeer(ICE_TEST_PEER_OFFERER | ICE_TEST_PEER_ENABLED_TCP);
    std::vector<NrIceStunServer> stun_servers;
    AddStunServerWithResponse(fake_addr, fake_port, fqdn, "tcp", &stun_servers);
    peer_->SetStunServers(stun_servers);
  }

  void UseFakeStunUdpTcpServersWithResponse(
      const std::string& fake_udp_addr,
      uint16_t fake_udp_port,
      const std::string& fake_tcp_addr,
      uint16_t fake_tcp_port) {
    EnsurePeer(ICE_TEST_PEER_OFFERER | ICE_TEST_PEER_ENABLED_TCP);
    std::vector<NrIceStunServer> stun_servers;
    AddStunServerWithResponse(fake_udp_addr,
                              fake_udp_port,
                              "", // no fqdn
                              "udp",
                              &stun_servers);
    AddStunServerWithResponse(fake_tcp_addr,
                              fake_tcp_port,
                              "", // no fqdn
                              "tcp",
                              &stun_servers);

    peer_->SetStunServers(stun_servers);
  }

  void UseTestStunServer() {
    TestStunServer::GetInstance(AF_INET)->Reset();
    peer_->SetStunServer(TestStunServer::GetInstance(AF_INET)->addr(),
                         TestStunServer::GetInstance(AF_INET)->port());
  }

  // NB: Only does substring matching, watch out for stuff like "1.2.3.4"
  // matching "21.2.3.47". " 1.2.3.4 " should not have false positives.
  bool StreamHasMatchingCandidate(unsigned int stream,
                                  const std::string& match,
                                  const std::string& match2 = "") {
    std::vector<std::string> candidates = peer_->GetCandidates(stream);
    for (size_t c = 0; c < candidates.size(); ++c) {
      if (std::string::npos != candidates[c].find(match)) {
        if (!match2.length() ||
            std::string::npos != candidates[c].find(match2)) {
          return true;
        }
      }
    }
    return false;
  }

  void DumpCandidates(unsigned int stream) {
    std::vector<std::string> candidates = peer_->GetCandidates(stream);

    std::cerr << "Candidates for stream " << stream << "->"
              << candidates.size() << std::endl;

    for (auto c : candidates) {
      std::cerr << "Candidate: " << c << std::endl;
    }
  }

 protected:
  mozilla::UniquePtr<IceTestPeer> peer_;
};

class WebRtcIceConnectTest : public StunTest {
 public:
  WebRtcIceConnectTest() :
    initted_(false),
    test_stun_server_inited_(false),
    use_nat_(false),
    filtering_type_(TestNat::ENDPOINT_INDEPENDENT),
    mapping_type_(TestNat::ENDPOINT_INDEPENDENT),
    block_udp_(false) {}

  void SetUp() override {
    StunTest::SetUp();

    nsresult rv;
    target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
    ASSERT_TRUE(NS_SUCCEEDED(rv));
  }

  void TearDown() override {
    p1_ = nullptr;
    p2_ = nullptr;

    StunTest::TearDown();
  }

  void AddStream(int components) {
    Init(false, false);
    p1_->AddStream(components);
    p2_->AddStream(components);
  }

  void RemoveStream(size_t index) {
    p1_->RemoveStream(index);
    p2_->RemoveStream(index);
  }

  void Init(bool allow_loopback,
            bool enable_tcp,
            bool setup_stun_servers = true,
            NrIceCtx::Policy ice_policy = NrIceCtx::ICE_POLICY_ALL) {
    if (initted_) {
      return;
    }

    p1_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, allow_loopback,
                                  enable_tcp, false, ice_policy);
    p2_ = MakeUnique<IceTestPeer>("P2", test_utils_, false, allow_loopback,
                                  enable_tcp, false, ice_policy);
    InitPeer(p1_.get(), setup_stun_servers);
    InitPeer(p2_.get(), setup_stun_servers);

    initted_ = true;
  }

  void InitPeer(IceTestPeer* peer, bool setup_stun_servers = true) {
    if (use_nat_) {
      // If we enable nat simulation, but still use a real STUN server somewhere
      // on the internet, we will see failures if there is a real NAT in
      // addition to our simulated one, particularly if it disallows
      // hairpinning.
      if (setup_stun_servers) {
        InitTestStunServer();
        peer->UseTestStunServer();
      }
      peer->UseNat();
      peer->SetFilteringType(filtering_type_);
      peer->SetMappingType(mapping_type_);
      peer->SetBlockUdp(block_udp_);
    } else if (setup_stun_servers) {
      std::vector<NrIceStunServer> stun_servers;

      stun_servers.push_back(*NrIceStunServer::Create(stun_server_address_,
                                                      kDefaultStunServerPort, kNrIceTransportUdp));
      stun_servers.push_back(*NrIceStunServer::Create(stun_server_address_,
                                                      kDefaultStunServerPort, kNrIceTransportTcp));

      peer->SetStunServers(stun_servers);
    }
  }

  bool Gather(unsigned int waitTime = kDefaultTimeout,
              bool default_route_only = false) {
    Init(false, false);

    return GatherCallerAndCallee(p1_.get(),
                                 p2_.get(),
                                 waitTime,
                                 default_route_only);
  }

  bool GatherCallerAndCallee(IceTestPeer* caller,
                             IceTestPeer* callee,
                             unsigned int waitTime = kDefaultTimeout,
                             bool default_route_only = false) {
    caller->Gather(default_route_only);
    callee->Gather(default_route_only);

    if (waitTime) {
      EXPECT_TRUE_WAIT(caller->gathering_complete(), waitTime);
      if (!caller->gathering_complete())
        return false;
      EXPECT_TRUE_WAIT(callee->gathering_complete(), waitTime);
      if (!callee->gathering_complete())
        return false;
    }
    return true;
  }

  void UseNat() {
    // to be useful, this method should be called before Init
    ASSERT_FALSE(initted_);
    use_nat_ = true;
  }

  void SetFilteringType(TestNat::NatBehavior type) {
    // to be useful, this method should be called before Init
    ASSERT_FALSE(initted_);
    filtering_type_ = type;
  }

  void SetMappingType(TestNat::NatBehavior type) {
    // to be useful, this method should be called before Init
    ASSERT_FALSE(initted_);
    mapping_type_ = type;
  }

  void BlockUdp() {
    // note: |block_udp_| is used only in InitPeer.
    // Use IceTestPeer::SetBlockUdp to act on the peer directly.
    block_udp_ = true;
  }

  void SetupAndCheckConsent() {
    p1_->SetTimerDivider(10);
    p2_->SetTimerDivider(10);
    ASSERT_TRUE(Gather());
    Connect();
    p1_->AssertConsentRefresh(CONSENT_FRESH);
    p2_->AssertConsentRefresh(CONSENT_FRESH);
    SendReceive();
  }

  void AssertConsentRefresh(ConsentStatus status = CONSENT_FRESH) {
    p1_->AssertConsentRefresh(status);
    p2_->AssertConsentRefresh(status);
  }

  void InitTestStunServer() {
    if (test_stun_server_inited_) {
      return;
    }

    std::cerr << "Resetting TestStunServer" << std::endl;
    TestStunServer::GetInstance(AF_INET)->Reset();
    test_stun_server_inited_ = true;
  }

  void UseTestStunServer() {
    InitTestStunServer();
    p1_->UseTestStunServer();
    p2_->UseTestStunServer();
  }

  void SetTurnServer(const std::string addr, uint16_t port,
                     const std::string username,
                     const std::string password,
                     const char* transport = kNrIceTransportUdp) {
    p1_->SetTurnServer(addr, port, username, password, transport);
    p2_->SetTurnServer(addr, port, username, password, transport);
  }

  void SetTurnServers(const std::vector<NrIceTurnServer>& servers) {
    p1_->SetTurnServers(servers);
    p2_->SetTurnServers(servers);
  }

  void SetCandidateFilter(CandidateFilter filter, bool both=true) {
    p1_->SetCandidateFilter(filter);
    if (both) {
      p2_->SetCandidateFilter(filter);
    }
  }

  void Connect() {
    ConnectCallerAndCallee(p1_.get(), p2_.get());
  }

  void ConnectCallerAndCallee(IceTestPeer* caller, IceTestPeer* callee) {
    ASSERT_TRUE(caller->ready_ct() == 0);
    ASSERT_TRUE(caller->ice_connected() == 0);
    ASSERT_TRUE(caller->ice_reached_checking() == 0);
    ASSERT_TRUE(callee->ready_ct() == 0);
    ASSERT_TRUE(callee->ice_connected() == 0);
    ASSERT_TRUE(callee->ice_reached_checking() == 0);

    // IceTestPeer::Connect grabs attributes from the first arg, and
    // gives them to |this|, meaning that callee->Connect(caller, ...)
    // simulates caller sending an offer to callee. Order matters here
    // because it determines which peer is controlling.
    callee->Connect(caller, TRICKLE_NONE);
    caller->Connect(callee, TRICKLE_NONE);

    ASSERT_TRUE_WAIT(caller->ready_ct() == 1 && callee->ready_ct() == 1,
                     kDefaultTimeout);
    ASSERT_TRUE_WAIT(caller->ice_connected() && callee->ice_connected(),
                     kDefaultTimeout);

    ASSERT_TRUE(caller->ice_reached_checking());
    ASSERT_TRUE(callee->ice_reached_checking());

    caller->DumpAndCheckActiveCandidates();
    callee->DumpAndCheckActiveCandidates();
  }

  void SetExpectedTypes(NrIceCandidate::Type local, NrIceCandidate::Type remote,
                        std::string transport = kNrIceTransportUdp) {
    p1_->SetExpectedTypes(local, remote, transport);
    p2_->SetExpectedTypes(local, remote, transport);
  }

  void SetExpectedTypes(NrIceCandidate::Type local1, NrIceCandidate::Type remote1,
                        NrIceCandidate::Type local2, NrIceCandidate::Type remote2) {
    p1_->SetExpectedTypes(local1, remote1);
    p2_->SetExpectedTypes(local2, remote2);
  }

  void SetExpectedRemoteCandidateAddr(const std::string& addr) {
    p1_->SetExpectedRemoteCandidateAddr(addr);
    p2_->SetExpectedRemoteCandidateAddr(addr);
  }

  void ConnectP1(TrickleMode mode = TRICKLE_NONE) {
    p1_->Connect(p2_.get(), mode);
  }

  void ConnectP2(TrickleMode mode = TRICKLE_NONE) {
    p2_->Connect(p1_.get(), mode);
  }

  void WaitForConnectedStreams(int expected_streams = 1) {
    ASSERT_TRUE_WAIT(p1_->ready_ct() == expected_streams &&
                     p2_->ready_ct() == expected_streams, kDefaultTimeout);
    ASSERT_TRUE_WAIT(p1_->ice_connected() && p2_->ice_connected(),
                     kDefaultTimeout);
  }

  void AssertCheckingReached() {
    ASSERT_TRUE(p1_->ice_reached_checking());
    ASSERT_TRUE(p2_->ice_reached_checking());
  }

  void WaitForConnected(unsigned int timeout = kDefaultTimeout) {
    ASSERT_TRUE_WAIT(p1_->ice_connected(), timeout);
    ASSERT_TRUE_WAIT(p2_->ice_connected(), timeout);
  }

  void WaitForGather() {
    ASSERT_TRUE_WAIT(p1_->gathering_complete(), kDefaultTimeout);
    ASSERT_TRUE_WAIT(p2_->gathering_complete(), kDefaultTimeout);
  }

  void WaitForDisconnected(unsigned int timeout = kDefaultTimeout) {
    ASSERT_TRUE(p1_->ice_connected());
    ASSERT_TRUE(p2_->ice_connected());
    ASSERT_TRUE_WAIT(p1_->ice_connected() == 0 &&
                     p2_->ice_connected() == 0,
                     timeout);
  }

  void WaitForFailed(unsigned int timeout = kDefaultTimeout) {
    ASSERT_TRUE_WAIT(p1_->ice_failed() &&
                     p2_->ice_failed(),
                     timeout);
  }

  void ConnectTrickle(TrickleMode trickle = TRICKLE_SIMULATE) {
    p2_->Connect(p1_.get(), trickle);
    p1_->Connect(p2_.get(), trickle);
  }

  void SimulateTrickle(size_t stream) {
    p1_->SimulateTrickle(stream);
    p2_->SimulateTrickle(stream);
    ASSERT_TRUE_WAIT(p1_->is_ready(stream), kDefaultTimeout);
    ASSERT_TRUE_WAIT(p2_->is_ready(stream), kDefaultTimeout);
  }

  void SimulateTrickleP1(size_t stream) {
    p1_->SimulateTrickle(stream);
  }

  void SimulateTrickleP2(size_t stream) {
    p2_->SimulateTrickle(stream);
  }

  void CloseP1() {
    p1_->Close();
  }

  void ConnectThenDelete() {
    p2_->Connect(p1_.get(), TRICKLE_NONE, false);
    p1_->Connect(p2_.get(), TRICKLE_NONE, true);
    test_utils_->sts_target()->Dispatch(WrapRunnable(this,
                                                    &WebRtcIceConnectTest::CloseP1),
                                       NS_DISPATCH_SYNC);
    p2_->StartChecks();

    // Wait to see if we crash
    PR_Sleep(PR_MillisecondsToInterval(kDefaultTimeout));
  }

  // default is p1_ sending to p2_
  void SendReceive() {
    SendReceive(p1_.get(), p2_.get());
  }

  void SendReceive(IceTestPeer *p1, IceTestPeer *p2,
                   bool expect_tx_failure = false,
                   bool expect_rx_failure = false) {
    size_t previousSent = p1->sent();
    size_t previousReceived = p2->received();

    test_utils_->sts_target()->Dispatch(
        WrapRunnable(p1,
                     &IceTestPeer::SendPacket, 0, 1,
                     reinterpret_cast<const unsigned char *>("TEST"), 4),
        NS_DISPATCH_SYNC);

    if (expect_tx_failure) {
      ASSERT_EQ(previousSent, p1->sent());
    } else {
      ASSERT_EQ(previousSent+1, p1->sent());
    }
    if (expect_rx_failure) {
      usleep(1000);
      ASSERT_EQ(previousReceived, p2->received());
    } else {
      ASSERT_TRUE_WAIT(p2->received() == previousReceived+1, 1000);
    }
  }

  void SendFailure() {
    test_utils_->sts_target()->Dispatch(
        WrapRunnable(p1_.get(),
                     &IceTestPeer::SendFailure, 0, 1),
        NS_DISPATCH_SYNC);
  }

 protected:
  bool initted_;
  bool test_stun_server_inited_;
  nsCOMPtr<nsIEventTarget> target_;
  mozilla::UniquePtr<IceTestPeer> p1_;
  mozilla::UniquePtr<IceTestPeer> p2_;
  bool use_nat_;
  TestNat::NatBehavior filtering_type_;
  TestNat::NatBehavior mapping_type_;
  bool block_udp_;
};

class WebRtcIcePrioritizerTest : public StunTest {
 public:
  WebRtcIcePrioritizerTest():
    prioritizer_(nullptr) {}

  ~WebRtcIcePrioritizerTest() {
    if (prioritizer_) {
      nr_interface_prioritizer_destroy(&prioritizer_);
    }
  }

  void SetPriorizer(nr_interface_prioritizer *prioritizer) {
    prioritizer_ = prioritizer;
  }

  void AddInterface(const std::string& num, int type, int estimated_speed) {
    std::string str_addr = "10.0.0." + num;
    std::string ifname = "eth" + num;
    nr_local_addr local_addr;
    local_addr.interface.type = type;
    local_addr.interface.estimated_speed = estimated_speed;

    int r = nr_str_port_to_transport_addr(str_addr.c_str(), 0,
                                          IPPROTO_UDP, &(local_addr.addr));
    ASSERT_EQ(0, r);
    strncpy(local_addr.addr.ifname, ifname.c_str(), MAXIFNAME);

    r = nr_interface_prioritizer_add_interface(prioritizer_, &local_addr);
    ASSERT_EQ(0, r);
    r = nr_interface_prioritizer_sort_preference(prioritizer_);
    ASSERT_EQ(0, r);
  }

  void HasLowerPreference(const std::string& num1, const std::string& num2) {
    std::string key1 = "eth" + num1 + ":10.0.0." + num1;
    std::string key2 = "eth" + num2 + ":10.0.0." + num2;
    UCHAR pref1, pref2;
    int r = nr_interface_prioritizer_get_priority(prioritizer_, key1.c_str(), &pref1);
    ASSERT_EQ(0, r);
    r = nr_interface_prioritizer_get_priority(prioritizer_, key2.c_str(), &pref2);
    ASSERT_EQ(0, r);
    ASSERT_LE(pref1, pref2);
  }

 private:
  nr_interface_prioritizer *prioritizer_;
};

class WebRtcIcePacketFilterTest : public StunTest {
 public:
  WebRtcIcePacketFilterTest(): udp_filter_(nullptr),
                               tcp_filter_(nullptr) {}

  void SetUp() {
    StunTest::SetUp();

    // Set up enough of the ICE ctx to allow the packet filter to work
    ice_ctx_ = NrIceCtxHandler::Create("test", true);

    nsCOMPtr<nsISocketFilterHandler> udp_handler =
      do_GetService(NS_STUN_UDP_SOCKET_FILTER_HANDLER_CONTRACTID);
    ASSERT_TRUE(udp_handler);
    udp_handler->NewFilter(getter_AddRefs(udp_filter_));

    nsCOMPtr<nsISocketFilterHandler> tcp_handler =
      do_GetService(NS_STUN_TCP_SOCKET_FILTER_HANDLER_CONTRACTID);
    ASSERT_TRUE(tcp_handler);
    tcp_handler->NewFilter(getter_AddRefs(tcp_filter_));
  }

  void TearDown() {
    test_utils_->sts_target()->Dispatch(WrapRunnable(this,
                                       &WebRtcIcePacketFilterTest::TearDown_s),
                                       NS_DISPATCH_SYNC);
    StunTest::TearDown();
  }

  void TearDown_s() {
    ice_ctx_ = nullptr;
  }

  void TestIncoming(const uint8_t* data, uint32_t len,
                    uint8_t from_addr, int from_port,
                    bool expected_result) {
    mozilla::net::NetAddr addr;
    MakeNetAddr(&addr, from_addr, from_port);
    bool result;
    nsresult rv = udp_filter_->FilterPacket(&addr, data, len,
                                            nsISocketFilter::SF_INCOMING,
                                            &result);
    ASSERT_EQ(NS_OK, rv);
    ASSERT_EQ(expected_result, result);
  }

  void TestIncomingTcp(const uint8_t* data, uint32_t len,
                       bool expected_result) {
    mozilla::net::NetAddr addr;
    bool result;
    nsresult rv = tcp_filter_->FilterPacket(&addr, data, len,
                                            nsISocketFilter::SF_INCOMING,
                                            &result);
    ASSERT_EQ(NS_OK, rv);
    ASSERT_EQ(expected_result, result);
  }

  void TestIncomingTcpFramed(const uint8_t* data, uint32_t len,
                             bool expected_result) {
    mozilla::net::NetAddr addr;
    bool result;
    uint8_t* framed_data = new uint8_t[len+2];
    framed_data[0] = htons(len);
    memcpy(&framed_data[2], data, len);
    nsresult rv = tcp_filter_->FilterPacket(&addr, framed_data, len+2,
                                            nsISocketFilter::SF_INCOMING,
                                            &result);
    ASSERT_EQ(NS_OK, rv);
    ASSERT_EQ(expected_result, result);
    delete[] framed_data;
  }

  void TestOutgoing(const uint8_t* data, uint32_t len,
                    uint8_t to_addr, int to_port,
                    bool expected_result) {
    mozilla::net::NetAddr addr;
    MakeNetAddr(&addr, to_addr, to_port);
    bool result;
    nsresult rv = udp_filter_->FilterPacket(&addr, data, len,
                                            nsISocketFilter::SF_OUTGOING,
                                            &result);
    ASSERT_EQ(NS_OK, rv);
    ASSERT_EQ(expected_result, result);
  }

  void TestOutgoingTcp(const uint8_t* data, uint32_t len,
                       bool expected_result) {
    mozilla::net::NetAddr addr;
    bool result;
    nsresult rv = tcp_filter_->FilterPacket(&addr, data, len,
                                            nsISocketFilter::SF_OUTGOING,
                                            &result);
    ASSERT_EQ(NS_OK, rv);
    ASSERT_EQ(expected_result, result);
  }

  void TestOutgoingTcpFramed(const uint8_t* data, uint32_t len,
                             bool expected_result) {
    mozilla::net::NetAddr addr;
    bool result;
    uint8_t* framed_data = new uint8_t[len+2];
    framed_data[0] = htons(len);
    memcpy(&framed_data[2], data, len);
    nsresult rv = tcp_filter_->FilterPacket(&addr, framed_data, len+2,
                                            nsISocketFilter::SF_OUTGOING,
                                            &result);
    ASSERT_EQ(NS_OK, rv);
    ASSERT_EQ(expected_result, result);
    delete[] framed_data;
  }

 private:
  void MakeNetAddr(mozilla::net::NetAddr* net_addr,
                   uint8_t last_digit, uint16_t port) {
    net_addr->inet.family = AF_INET;
    net_addr->inet.ip = 192 << 24 | 168 << 16 | 1 << 8 | last_digit;
    net_addr->inet.port = port;
  }

  nsCOMPtr<nsISocketFilter> udp_filter_;
  nsCOMPtr<nsISocketFilter> tcp_filter_;
  RefPtr<NrIceCtxHandler> ice_ctx_;
};
}  // end namespace

TEST_F(WebRtcIceGatherTest, TestGatherFakeStunServerHostnameNoResolver) {
  if (stun_server_hostname_.empty()) {
    return;
  }

  EnsurePeer();
  peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort);
  Gather();
}

TEST_F(WebRtcIceGatherTest, TestGatherFakeStunServerTcpHostnameNoResolver) {
  if (stun_server_hostname_.empty()) {
    return;
  }

  EnsurePeer(ICE_TEST_PEER_OFFERER | ICE_TEST_PEER_ENABLED_TCP);
  peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort,
    kNrIceTransportTcp);
  Gather();
  ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP "));
}

TEST_F(WebRtcIceGatherTest, TestGatherFakeStunServerIpAddress) {
  if (stun_server_address_.empty()) {
    return;
  }

  EnsurePeer();
  peer_->SetStunServer(stun_server_address_, kDefaultStunServerPort);
  peer_->SetFakeResolver(stun_server_address_, stun_server_hostname_);
  Gather();
}

TEST_F(WebRtcIceGatherTest, TestGatherStunServerIpAddressNoHost) {
  if (stun_server_address_.empty()) {
    return;
  }

  peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, false, false, false, NrIceCtx::ICE_POLICY_NO_HOST);
  peer_->AddStream(1);
  peer_->SetStunServer(stun_server_address_, kDefaultStunServerPort);
  peer_->SetFakeResolver(stun_server_address_, stun_server_hostname_);
  Gather();
  ASSERT_FALSE(StreamHasMatchingCandidate(0, " host "));
}

TEST_F(WebRtcIceGatherTest, TestGatherFakeStunServerHostname) {
  if (stun_server_hostname_.empty()) {
    return;
  }

  EnsurePeer();
  peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort);
  peer_->SetFakeResolver(stun_server_address_, stun_server_hostname_);
  Gather();
}

TEST_F(WebRtcIceGatherTest, TestGatherFakeStunBogusHostname) {
  EnsurePeer();
  peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort);
  peer_->SetFakeResolver(stun_server_address_, stun_server_hostname_);
  Gather();
}

TEST_F(WebRtcIceGatherTest, TestGatherDNSStunServerIpAddress) {
  if (stun_server_address_.empty()) {
    return;
  }

  EnsurePeer();
  peer_->SetStunServer(stun_server_address_, kDefaultStunServerPort);
  peer_->SetDNSResolver();
  Gather();
  ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
  ASSERT_TRUE(StreamHasMatchingCandidate(0, "typ srflx raddr"));
}

TEST_F(WebRtcIceGatherTest, TestGatherDNSStunServerIpAddressTcp) {
  if (stun_server_address_.empty()) {
    return;
  }

  EnsurePeer(ICE_TEST_PEER_OFFERER | ICE_TEST_PEER_ENABLED_TCP);
  peer_->SetStunServer(stun_server_address_, kDefaultStunServerPort,
    kNrIceTransportTcp);
  peer_->SetDNSResolver();
  Gather();
  ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype passive"));
  ASSERT_FALSE(StreamHasMatchingCandidate(0, "tcptype passive", " 9 "));
  ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype so"));
  ASSERT_FALSE(StreamHasMatchingCandidate(0, "tcptype so", " 9 "));
  ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype active", " 9 "));
}

TEST_F(WebRtcIceGatherTest, TestGatherDNSStunServerHostname) {
  if (stun_server_hostname_.empty()) {
    return;
  }

  EnsurePeer();
  peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort);
  peer_->SetDNSResolver();
  Gather();
  ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
  ASSERT_TRUE(StreamHasMatchingCandidate(0, "typ srflx raddr"));
}

TEST_F(WebRtcIceGatherTest, TestGatherDNSStunServerHostnameTcp) {
  EnsurePeer(ICE_TEST_PEER_OFFERER | ICE_TEST_PEER_ENABLED_TCP);
  peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort,
    kNrIceTransportTcp);
  peer_->SetDNSResolver();
  Gather();
  ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype passive"));
  ASSERT_FALSE(StreamHasMatchingCandidate(0, "tcptype passive", " 9 "));
  ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype so"));
  ASSERT_FALSE(StreamHasMatchingCandidate(0, "tcptype so", " 9 "));
  ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype active", " 9 "));
}

TEST_F(WebRtcIceGatherTest, TestGatherDNSStunServerHostnameBothUdpTcp) {
  if (stun_server_hostname_.empty()) {
    return;
  }

  std::vector<NrIceStunServer> stun_servers;

  EnsurePeer(ICE_TEST_PEER_OFFERER | ICE_TEST_PEER_ENABLED_TCP);
  stun_servers.push_back(*NrIceStunServer::Create(stun_server_hostname_,
    kDefaultStunServerPort, kNrIceTransportUdp));
  stun_servers.push_back(*NrIceStunServer::Create(stun_server_hostname_,
    kDefaultStunServerPort, kNrIceTransportTcp));
  peer_->SetStunServers(stun_servers);
  peer_->SetDNSResolver();
  Gather();
  ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
  ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP "));
}

TEST_F(WebRtcIceGatherTest, TestGatherDNSStunServerIpAddressBothUdpTcp) {
  if (stun_server_address_.empty()) {
    return;
  }

  std::vector<NrIceStunServer> stun_servers;

  EnsurePeer(ICE_TEST_PEER_OFFERER | ICE_TEST_PEER_ENABLED_TCP);
  stun_servers.push_back(*NrIceStunServer::Create(stun_server_address_,
    kDefaultStunServerPort, kNrIceTransportUdp));
  stun_servers.push_back(*NrIceStunServer::Create(stun_server_address_,
    kDefaultStunServerPort, kNrIceTransportTcp));
  peer_->SetStunServers(stun_servers);
  peer_->SetDNSResolver();
  Gather();
  ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
  ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP "));
}

TEST_F(WebRtcIceGatherTest, TestGatherDNSStunBogusHostname) {
  EnsurePeer();
  peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort);
  peer_->SetDNSResolver();
  Gather();
  ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
}

TEST_F(WebRtcIceGatherTest, TestGatherDNSStunBogusHostnameTcp) {
  EnsurePeer(ICE_TEST_PEER_OFFERER | ICE_TEST_PEER_ENABLED_TCP);
  peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort,
    kNrIceTransportTcp);
  peer_->SetDNSResolver();
  Gather();
  ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP "));
}

TEST_F(WebRtcIceGatherTest, TestDefaultCandidate) {
  EnsurePeer();
  peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort);
  Gather();
  NrIceCandidate default_candidate;
  ASSERT_TRUE(NS_SUCCEEDED(peer_->GetDefaultCandidate(0, &default_candidate)));
}

TEST_F(WebRtcIceGatherTest, TestGatherTurn) {
  EnsurePeer();
  if (turn_server_.empty())
    return;
  peer_->SetTurnServer(turn_server_, kDefaultStunServerPort,
                       turn_user_, turn_password_, kNrIceTransportUdp);
  Gather();
}

TEST_F(WebRtcIceGatherTest, TestGatherTurnTcp) {
  EnsurePeer();
  if (turn_server_.empty())
    return;
  peer_->SetTurnServer(turn_server_, kDefaultStunServerPort,
                       turn_user_, turn_password_, kNrIceTransportTcp);
  Gather();
}

TEST_F(WebRtcIceGatherTest, TestGatherDisableComponent) {
  if (stun_server_hostname_.empty()) {
    return;
  }

  EnsurePeer();
  peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort);
  peer_->AddStream(2);
  peer_->DisableComponent(1, 2);
  Gather();
  std::vector<std::string> candidates =
    peer_->GetCandidates(1);

  for (size_t i=0; i<candidates.size(); ++i) {
    size_t sp1 = candidates[i].find(' ');
    ASSERT_EQ(0, candidates[i].compare(sp1+1, 1, "1", 1));
  }
}

TEST_F(WebRtcIceGatherTest, TestGatherVerifyNoLoopback) {
  Gather();
  ASSERT_FALSE(StreamHasMatchingCandidate(0, "127.0.0.1"));
}

TEST_F(WebRtcIceGatherTest, TestGatherAllowLoopback) {
  // Set up peer with loopback allowed.
  peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, true);
  peer_->AddStream(1);
  Gather();
  ASSERT_TRUE(StreamHasMatchingCandidate(0, "127.0.0.1"));
}

TEST_F(WebRtcIceGatherTest, TestGatherTcpDisabled) {
  // Set up peer with tcp disabled.
  peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, false, false);
  peer_->AddStream(1);
  Gather();
  ASSERT_FALSE(StreamHasMatchingCandidate(0, " TCP "));
  ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
}

// Verify that a bogus candidate doesn't cause crashes on the
// main thread. See bug 856433.
TEST_F(WebRtcIceGatherTest, TestBogusCandidate) {
  Gather();
  peer_->ParseCandidate(0, kBogusIceCandidate);
}

TEST_F(WebRtcIceGatherTest, VerifyTestStunServer) {
  UseFakeStunUdpServerWithResponse("192.0.2.133", 3333);
  Gather();
  ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.2.133 3333 "));
}

TEST_F(WebRtcIceGatherTest, VerifyTestStunTcpServer) {
  UseFakeStunTcpServerWithResponse("192.0.2.233", 3333);
  Gather();
  ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.2.233 3333 typ srflx",
    " tcptype "));
}

TEST_F(WebRtcIceGatherTest, VerifyTestStunServerV6) {
  if (!TestStunServer::GetInstance(AF_INET6)) {
    // No V6 addresses
    return;
  }
  UseFakeStunUdpServerWithResponse("beef::", 3333);
  Gather();
  ASSERT_TRUE(StreamHasMatchingCandidate(0, " beef:: 3333 "));
}

TEST_F(WebRtcIceGatherTest, VerifyTestStunServerFQDN) {
  UseFakeStunUdpServerWithResponse("192.0.2.133", 3333, "stun.example.com");
  Gather();
  ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.2.133 3333 "));
}

TEST_F(WebRtcIceGatherTest, VerifyTestStunServerV6FQDN) {
  if (!TestStunServer::GetInstance(AF_INET6)) {
    // No V6 addresses
    return;
  }
  UseFakeStunUdpServerWithResponse("beef::", 3333, "stun.example.com");
  Gather();
  ASSERT_TRUE(StreamHasMatchingCandidate(0, " beef:: 3333 "));
}

TEST_F(WebRtcIceGatherTest, TestStunServerReturnsWildcardAddr) {
  UseFakeStunUdpServerWithResponse("0.0.0.0", 3333);
  Gather(kDefaultTimeout * 3);
  ASSERT_FALSE(StreamHasMatchingCandidate(0, " 0.0.0.0 "));
}

TEST_F(WebRtcIceGatherTest, TestStunServerReturnsWildcardAddrV6) {
  if (!TestStunServer::GetInstance(AF_INET6)) {
    // No V6 addresses
    return;
  }
  UseFakeStunUdpServerWithResponse("::", 3333);
  Gather(kDefaultTimeout * 3);
  ASSERT_FALSE(StreamHasMatchingCandidate(0, " :: "));
}

TEST_F(WebRtcIceGatherTest, TestStunServerReturnsPort0) {
  UseFakeStunUdpServerWithResponse("192.0.2.133", 0);
  Gather(kDefaultTimeout * 3);
  ASSERT_FALSE(StreamHasMatchingCandidate(0, " 192.0.2.133 0 "));
}

TEST_F(WebRtcIceGatherTest, TestStunServerReturnsLoopbackAddr) {
  UseFakeStunUdpServerWithResponse("127.0.0.133", 3333);
  Gather(kDefaultTimeout * 3);
  ASSERT_FALSE(StreamHasMatchingCandidate(0, " 127.0.0.133 "));
}

TEST_F(WebRtcIceGatherTest, TestStunServerReturnsLoopbackAddrV6) {
  if (!TestStunServer::GetInstance(AF_INET6)) {
    // No V6 addresses
    return;
  }
  UseFakeStunUdpServerWithResponse("::1", 3333);
  Gather(kDefaultTimeout * 3);
  ASSERT_FALSE(StreamHasMatchingCandidate(0, " ::1 "));
}

TEST_F(WebRtcIceGatherTest, TestStunServerTrickle) {
  UseFakeStunUdpServerWithResponse("192.0.2.1", 3333);
  TestStunServer::GetInstance(AF_INET)->SetDropInitialPackets(3);
  Gather(0);
  ASSERT_FALSE(StreamHasMatchingCandidate(0, "192.0.2.1"));
  WaitForGather();
  ASSERT_TRUE(StreamHasMatchingCandidate(0, "192.0.2.1"));
}

// Test no host with our fake STUN server and apparently NATted.
TEST_F(WebRtcIceGatherTest, TestFakeStunServerNatedNoHost) {
  peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, false, false, false, NrIceCtx::ICE_POLICY_NO_HOST);
  peer_->AddStream(1);
  UseFakeStunUdpServerWithResponse("192.0.2.1", 3333);
  Gather(0);
  WaitForGather();
  DumpCandidates(0);
  ASSERT_FALSE(StreamHasMatchingCandidate(0, "host"));
  ASSERT_TRUE(StreamHasMatchingCandidate(0, "srflx"));
  NrIceCandidate default_candidate;
  nsresult rv = peer_->GetDefaultCandidate(0, &default_candidate);
  if (NS_SUCCEEDED(rv)) {
    ASSERT_NE(NrIceCandidate::ICE_HOST, default_candidate.type);
  }
}

// Test no host with our fake STUN server and apparently non-NATted.
TEST_F(WebRtcIceGatherTest, TestFakeStunServerNoNatNoHost) {
  peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, false, false, false, NrIceCtx::ICE_POLICY_NO_HOST);
  peer_->AddStream(1);
  UseTestStunServer();
  Gather(0);
  WaitForGather();
  DumpCandidates(0);
  ASSERT_FALSE(StreamHasMatchingCandidate(0, "host"));
  ASSERT_TRUE(StreamHasMatchingCandidate(0, "srflx"));
}

TEST_F(WebRtcIceGatherTest, TestStunTcpServerTrickle) {
  UseFakeStunTcpServerWithResponse("192.0.3.1", 3333);
  TestStunTcpServer::GetInstance(AF_INET)->SetDelay(500);
  Gather(0);
  ASSERT_FALSE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype "));
  WaitForGather();
  ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype "));
}

TEST_F(WebRtcIceGatherTest, TestStunTcpAndUdpServerTrickle) {
  UseFakeStunUdpTcpServersWithResponse("192.0.2.1", 3333, "192.0.3.1", 3333);
  TestStunServer::GetInstance(AF_INET)->SetDropInitialPackets(3);
  TestStunTcpServer::GetInstance(AF_INET)->SetDelay(500);
  Gather(0);
  ASSERT_FALSE(StreamHasMatchingCandidate(0, "192.0.2.1", "UDP"));
  ASSERT_FALSE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype "));
  WaitForGather();
  ASSERT_TRUE(StreamHasMatchingCandidate(0, "192.0.2.1", "UDP"));
  ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype "));
}

TEST_F(WebRtcIceGatherTest, TestSetIceControlling) {
  EnsurePeer();
  peer_->SetControlling(NrIceCtx::ICE_CONTROLLING);
  NrIceCtx::Controlling controlling = peer_->GetControlling();
  ASSERT_EQ(NrIceCtx::ICE_CONTROLLING, controlling);
  // SetControlling should only allow setting this once
  peer_->SetControlling(NrIceCtx::ICE_CONTROLLED);
  controlling = peer_->GetControlling();
  ASSERT_EQ(NrIceCtx::ICE_CONTROLLING, controlling);
}

TEST_F(WebRtcIceGatherTest, TestSetIceControlled) {
  EnsurePeer();
  peer_->SetControlling(NrIceCtx::ICE_CONTROLLED);
  NrIceCtx::Controlling controlling = peer_->GetControlling();
  ASSERT_EQ(NrIceCtx::ICE_CONTROLLED, controlling);
  // SetControlling should only allow setting this once
  peer_->SetControlling(NrIceCtx::ICE_CONTROLLING);
  controlling = peer_->GetControlling();
  ASSERT_EQ(NrIceCtx::ICE_CONTROLLED, controlling);
}

TEST_F(WebRtcIceConnectTest, TestGather) {
  AddStream(1);
  ASSERT_TRUE(Gather());
}

TEST_F(WebRtcIceConnectTest, TestGatherTcp) {
  Init(false, true);
  AddStream(1);
  ASSERT_TRUE(Gather());
}

TEST_F(WebRtcIceConnectTest, TestGatherAutoPrioritize) {
  Init(false, false);
  AddStream(1);
  ASSERT_TRUE(Gather());
}


TEST_F(WebRtcIceConnectTest, TestConnect) {
  AddStream(1);
  ASSERT_TRUE(Gather());
  Connect();
}


TEST_F(WebRtcIceConnectTest, TestConnectRestartIce) {
  AddStream(1);
  ASSERT_TRUE(Gather());
  Connect();
  SendReceive(p1_.get(), p2_.get());

  p2_->RestartIce();
  ASSERT_FALSE(p2_->gathering_complete());

  // verify p1 and p2 streams are still connected after restarting ice on p2
  SendReceive(p1_.get(), p2_.get());

  mozilla::UniquePtr<IceTestPeer> p3_;
  p3_ = MakeUnique<IceTestPeer>("P3", test_utils_, true, false, false, false);
  InitPeer(p3_.get());
  p3_->AddStream(1);

  p2_->AddStream(1);
  ASSERT_TRUE(GatherCallerAndCallee(p2_.get(), p3_.get()));
  std::cout << "-------------------------------------------------" << std::endl;
  ConnectCallerAndCallee(p3_.get(), p2_.get());
  SendReceive(p1_.get(), p2_.get()); // p1 and p2 still connected
  SendReceive(p3_.get(), p2_.get()); // p3 and p2 are now connected

  p2_->FinalizeIceRestart();
  SendReceive(p3_.get(), p2_.get()); // p3 and p2 are still connected

  SendReceive(p1_.get(), p2_.get(), false, true); // p1 and p2 not connected

  p3_ = nullptr;
}

TEST_F(WebRtcIceConnectTest, TestConnectRestartIceThenAbort) {
  AddStream(1);
  ASSERT_TRUE(Gather());
  Connect();
  SendReceive(p1_.get(), p2_.get());

  p2_->RestartIce();
  ASSERT_FALSE(p2_->gathering_complete());

  // verify p1 and p2 streams are still connected after restarting ice on p2
  SendReceive(p1_.get(), p2_.get());

  mozilla::UniquePtr<IceTestPeer> p3_;
  p3_ = MakeUnique<IceTestPeer>("P3", test_utils_, true, false, false, false);
  InitPeer(p3_.get());
  p3_->AddStream(1);

  p2_->AddStream(1);
  ASSERT_TRUE(GatherCallerAndCallee(p2_.get(), p3_.get()));
  std::cout << "-------------------------------------------------" << std::endl;
  ConnectCallerAndCallee(p3_.get(), p2_.get());
  SendReceive(p1_.get(), p2_.get()); // p1 and p2 still connected
  SendReceive(p3_.get(), p2_.get()); // p3 and p2 are now connected

  p2_->RollbackIceRestart();
  SendReceive(p1_.get(), p2_.get()); // p1 and p2 are still connected

  SendReceive(p3_.get(), p2_.get(), false, true); // p3 and p2 not connected

  p3_ = nullptr;
}

TEST_F(WebRtcIceConnectTest, TestConnectSetControllingAfterIceRestart) {
  AddStream(1);
  ASSERT_TRUE(Gather());
  // Just for fun lets do this with switched rolls
  p1_->SetControlling(NrIceCtx::ICE_CONTROLLED);
  p2_->SetControlling(NrIceCtx::ICE_CONTROLLING);
  Connect();
  SendReceive(p1_.get(), p2_.get());
  // Set rolls should not switch by connecting
  ASSERT_EQ(NrIceCtx::ICE_CONTROLLED, p1_->GetControlling());
  ASSERT_EQ(NrIceCtx::ICE_CONTROLLING, p2_->GetControlling());

  p2_->RestartIce();
  ASSERT_FALSE(p2_->gathering_complete());
  // ICE restart should allow us to set control role again
  p2_->SetControlling(NrIceCtx::ICE_CONTROLLED);
  ASSERT_EQ(NrIceCtx::ICE_CONTROLLED, p2_->GetControlling());
  // But still only allowed to set control role once
  p2_->SetControlling(NrIceCtx::ICE_CONTROLLING);
  ASSERT_EQ(NrIceCtx::ICE_CONTROLLED, p2_->GetControlling());

  mozilla::UniquePtr<IceTestPeer> p3_;
  p3_ = MakeUnique<IceTestPeer>("P3", test_utils_, true, false, false, false);
  InitPeer(p3_.get());
  p3_->AddStream(1);
  // Set control role for p3 accordingly (w/o role conflict)
  p3_->SetControlling(NrIceCtx::ICE_CONTROLLING);
  ASSERT_EQ(NrIceCtx::ICE_CONTROLLING, p3_->GetControlling());

  p2_->AddStream(1);
  ASSERT_TRUE(GatherCallerAndCallee(p2_.get(), p3_.get()));
  std::cout << "-------------------------------------------------" << std::endl;
  ConnectCallerAndCallee(p3_.get(), p2_.get());
  // Again connecting should not result in role switch
  ASSERT_EQ(NrIceCtx::ICE_CONTROLLED, p2_->GetControlling());
  ASSERT_EQ(NrIceCtx::ICE_CONTROLLING, p3_->GetControlling());

  p2_->FinalizeIceRestart();
  // And again we are not allowed to switch roles at this point any more
  p2_->SetControlling(NrIceCtx::ICE_CONTROLLING);
  ASSERT_EQ(NrIceCtx::ICE_CONTROLLED, p2_->GetControlling());
  p3_->SetControlling(NrIceCtx::ICE_CONTROLLED);
  ASSERT_EQ(NrIceCtx::ICE_CONTROLLING, p3_->GetControlling());

  p3_ = nullptr;
}

TEST_F(WebRtcIceConnectTest, TestConnectTcp) {
  Init(false, true);
  AddStream(1);
  ASSERT_TRUE(Gather());
  SetCandidateFilter(IsTcpCandidate);
  SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
    NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp);
  Connect();
}

//TCP SO tests works on localhost only with delay applied:
//  tc qdisc add dev lo root netem delay 10ms
TEST_F(WebRtcIceConnectTest, DISABLED_TestConnectTcpSo) {
  Init(false, true);
  AddStream(1);
  ASSERT_TRUE(Gather());
  SetCandidateFilter(IsTcpSoCandidate);
  SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
    NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp);
  Connect();
}

// Disabled because this breaks with hairpinning.
TEST_F(WebRtcIceConnectTest, DISABLED_TestConnectNoHost) {
  Init(false, false, false, NrIceCtx::ICE_POLICY_NO_HOST);
  AddStream(1);
  ASSERT_TRUE(Gather());
  SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
    NrIceCandidate::Type::ICE_SERVER_REFLEXIVE, kNrIceTransportTcp);
  Connect();
}

TEST_F(WebRtcIceConnectTest, TestLoopbackOnlySortOf) {
  Init(true, false, false);
  AddStream(1);
  SetCandidateFilter(IsLoopbackCandidate);
  ASSERT_TRUE(Gather());
  SetExpectedRemoteCandidateAddr("127.0.0.1");
  Connect();
}

TEST_F(WebRtcIceConnectTest, TestConnectBothControllingP1Wins) {
  AddStream(1);
  p1_->SetTiebreaker(1);
  p2_->SetTiebreaker(0);
  ASSERT_TRUE(Gather());
  p1_->SetControlling(NrIceCtx::ICE_CONTROLLING);
  p2_->SetControlling(NrIceCtx::ICE_CONTROLLING);
  Connect();
}

TEST_F(WebRtcIceConnectTest, TestConnectBothControllingP2Wins) {
  AddStream(1);
  p1_->SetTiebreaker(0);
  p2_->SetTiebreaker(1);
  ASSERT_TRUE(Gather());
  p1_->SetControlling(NrIceCtx::ICE_CONTROLLING);
  p2_->SetControlling(NrIceCtx::ICE_CONTROLLING);
  Connect();
}

TEST_F(WebRtcIceConnectTest, TestConnectIceLiteOfferer) {
  AddStream(1);
  ASSERT_TRUE(Gather());
  p1_->SimulateIceLite();
  Connect();
}

TEST_F(WebRtcIceConnectTest, TestTrickleBothControllingP1Wins) {
  AddStream(1);
  p1_->SetTiebreaker(1);
  p2_->SetTiebreaker(0);
  ASSERT_TRUE(Gather());
  p1_->SetControlling(NrIceCtx::ICE_CONTROLLING);
  p2_->SetControlling(NrIceCtx::ICE_CONTROLLING);
  ConnectTrickle();
  SimulateTrickle(0);
  WaitForConnected(1000);
  AssertCheckingReached();
}

TEST_F(WebRtcIceConnectTest, TestTrickleBothControllingP2Wins) {
  AddStream(1);
  p1_->SetTiebreaker(0);
  p2_->SetTiebreaker(1);
  ASSERT_TRUE(Gather());
  p1_->SetControlling(NrIceCtx::ICE_CONTROLLING);
  p2_->SetControlling(NrIceCtx::ICE_CONTROLLING);
  ConnectTrickle();
  SimulateTrickle(0);
  WaitForConnected(1000);
  AssertCheckingReached();
}

TEST_F(WebRtcIceConnectTest, TestTrickleIceLiteOfferer) {
  AddStream(1);
  ASSERT_TRUE(Gather());
  p1_->SimulateIceLite();
  ConnectTrickle();
  SimulateTrickle(0);
  WaitForConnected(1000);
  AssertCheckingReached();
}

TEST_F(WebRtcIceConnectTest, TestGatherFullCone) {
  UseNat();
  AddStream(1);
  ASSERT_TRUE(Gather());
}

TEST_F(WebRtcIceConnectTest, TestGatherFullConeAutoPrioritize) {
  UseNat();
  Init(true, false);
  AddStream(1);
  ASSERT_TRUE(Gather());
}


TEST_F(WebRtcIceConnectTest, TestConnectFullCone) {
  UseNat();
  AddStream(1);
  SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
                   NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
  ASSERT_TRUE(Gather());
  Connect();
}

TEST_F(WebRtcIceConnectTest, TestConnectNoNatNoHost) {
  Init(false, false, false, NrIceCtx::ICE_POLICY_NO_HOST);
  AddStream(1);
  UseTestStunServer();
  // Because we are connecting from our host candidate to the
  // other side's apparent srflx (which is also their host)
  // we see a host/srflx pair.
  SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
                   NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
  ASSERT_TRUE(Gather());
  Connect();
}

TEST_F(WebRtcIceConnectTest, TestConnectFullConeNoHost) {
  UseNat();
  Init(false, false, false, NrIceCtx::ICE_POLICY_NO_HOST);
  AddStream(1);
  UseTestStunServer();
  SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
                   NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
  ASSERT_TRUE(Gather());
  Connect();
}

TEST_F(WebRtcIceConnectTest, TestGatherAddressRestrictedCone) {
  UseNat();
  SetFilteringType(TestNat::ADDRESS_DEPENDENT);
  SetMappingType(TestNat::ENDPOINT_INDEPENDENT);
  AddStream(1);
  ASSERT_TRUE(Gather());
}

TEST_F(WebRtcIceConnectTest, TestConnectAddressRestrictedCone) {
  UseNat();
  SetFilteringType(TestNat::ADDRESS_DEPENDENT);
  SetMappingType(TestNat::ENDPOINT_INDEPENDENT);
  AddStream(1);
  SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
                   NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
  ASSERT_TRUE(Gather());
  Connect();
}

TEST_F(WebRtcIceConnectTest, TestGatherPortRestrictedCone) {
  UseNat();
  SetFilteringType(TestNat::PORT_DEPENDENT);
  SetMappingType(TestNat::ENDPOINT_INDEPENDENT);
  AddStream(1);
  ASSERT_TRUE(Gather());
}

TEST_F(WebRtcIceConnectTest, TestConnectPortRestrictedCone) {
  UseNat();
  SetFilteringType(TestNat::PORT_DEPENDENT);
  SetMappingType(TestNat::ENDPOINT_INDEPENDENT);
  AddStream(1);
  SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
                   NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
  ASSERT_TRUE(Gather());
  Connect();
}

TEST_F(WebRtcIceConnectTest, TestGatherSymmetricNat) {
  UseNat();
  SetFilteringType(TestNat::PORT_DEPENDENT);
  SetMappingType(TestNat::PORT_DEPENDENT);
  AddStream(1);
  ASSERT_TRUE(Gather());
}

TEST_F(WebRtcIceConnectTest, TestConnectSymmetricNat) {
  if (turn_server_.empty())
    return;

  UseNat();
  SetFilteringType(TestNat::PORT_DEPENDENT);
  SetMappingType(TestNat::PORT_DEPENDENT);
  AddStream(1);
  p1_->SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                        NrIceCandidate::Type::ICE_RELAYED);
  p2_->SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                        NrIceCandidate::Type::ICE_RELAYED);
  SetTurnServer(turn_server_, kDefaultStunServerPort,
                turn_user_, turn_password_);
  ASSERT_TRUE(Gather());
  Connect();
}

TEST_F(WebRtcIceConnectTest, TestConnectSymmetricNatAndNoNat) {
  p1_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, false, false);
  p1_->UseNat();
  p1_->SetFilteringType(TestNat::PORT_DEPENDENT);
  p1_->SetMappingType(TestNat::PORT_DEPENDENT);

  p2_ = MakeUnique<IceTestPeer>("P2", test_utils_, false, false, false);
  initted_ = true;

  AddStream(1);
  p1_->SetExpectedTypes(NrIceCandidate::Type::ICE_PEER_REFLEXIVE,
                        NrIceCandidate::Type::ICE_HOST);
  p2_->SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
                        NrIceCandidate::Type::ICE_PEER_REFLEXIVE);
  ASSERT_TRUE(Gather());
  Connect();
}

TEST_F(WebRtcIceConnectTest, TestGatherNatBlocksUDP) {
  if (turn_server_.empty())
    return;

  UseNat();
  BlockUdp();
  AddStream(1);
  std::vector<NrIceTurnServer> turn_servers;
  std::vector<unsigned char> password_vec(turn_password_.begin(),
                                          turn_password_.end());
  turn_servers.push_back(
      *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort,
                               turn_user_, password_vec, kNrIceTransportTcp));
  turn_servers.push_back(
      *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort,
                               turn_user_, password_vec, kNrIceTransportUdp));
  SetTurnServers(turn_servers);
  // We have to wait for the UDP-based stuff to time out.
  ASSERT_TRUE(Gather(kDefaultTimeout * 3));
}

TEST_F(WebRtcIceConnectTest, TestConnectNatBlocksUDP) {
  if (turn_server_.empty())
    return;

  UseNat();
  BlockUdp();
  AddStream(1);
  std::vector<NrIceTurnServer> turn_servers;
  std::vector<unsigned char> password_vec(turn_password_.begin(),
                                          turn_password_.end());
  turn_servers.push_back(
      *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort,
                               turn_user_, password_vec, kNrIceTransportTcp));
  turn_servers.push_back(
      *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort,
                               turn_user_, password_vec, kNrIceTransportUdp));
  SetTurnServers(turn_servers);
  p1_->SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                        NrIceCandidate::Type::ICE_RELAYED,
                        kNrIceTransportTcp);
  p2_->SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                        NrIceCandidate::Type::ICE_RELAYED,
                        kNrIceTransportTcp);
  ASSERT_TRUE(Gather(kDefaultTimeout * 3));
  Connect();
}

TEST_F(WebRtcIceConnectTest, TestConnectTwoComponents) {
  AddStream(2);
  ASSERT_TRUE(Gather());
  Connect();
}

TEST_F(WebRtcIceConnectTest, TestConnectTwoComponentsDisableSecond) {
  AddStream(2);
  ASSERT_TRUE(Gather());
  p1_->DisableComponent(0, 2);
  p2_->DisableComponent(0, 2);
  Connect();
}


TEST_F(WebRtcIceConnectTest, TestConnectP2ThenP1) {
  AddStream(1);
  ASSERT_TRUE(Gather());
  ConnectP2();
  PR_Sleep(1000);
  ConnectP1();
  WaitForConnectedStreams();
}

TEST_F(WebRtcIceConnectTest, TestConnectP2ThenP1Trickle) {
  AddStream(1);
  ASSERT_TRUE(Gather());
  ConnectP2();
  PR_Sleep(1000);
  ConnectP1(TRICKLE_SIMULATE);
  SimulateTrickleP1(0);
  WaitForConnectedStreams();
}

TEST_F(WebRtcIceConnectTest, TestConnectP2ThenP1TrickleTwoComponents) {
  AddStream(1);
  AddStream(2);
  ASSERT_TRUE(Gather());
  ConnectP2();
  PR_Sleep(1000);
  ConnectP1(TRICKLE_SIMULATE);
  SimulateTrickleP1(0);
  std::cerr << "Sleeping between trickle streams" << std::endl;
  PR_Sleep(1000);  // Give this some time to settle but not complete
                   // all of ICE.
  SimulateTrickleP1(1);
  WaitForConnectedStreams(2);
}

TEST_F(WebRtcIceConnectTest, TestConnectAutoPrioritize) {
  Init(false, false);
  AddStream(1);
  ASSERT_TRUE(Gather());
  Connect();
}

TEST_F(WebRtcIceConnectTest, TestConnectTrickleOneStreamOneComponent) {
  AddStream(1);
  ASSERT_TRUE(Gather());
  ConnectTrickle();
  SimulateTrickle(0);
  WaitForConnected(1000);
  AssertCheckingReached();
}

TEST_F(WebRtcIceConnectTest, TestConnectTrickleTwoStreamsOneComponent) {
  AddStream(1);
  AddStream(1);
  ASSERT_TRUE(Gather());
  ConnectTrickle();
  SimulateTrickle(0);
  SimulateTrickle(1);
  WaitForConnected(1000);
  AssertCheckingReached();
}

void RealisticTrickleDelay(
    std::vector<SchedulableTrickleCandidate*>& candidates) {
  for (size_t i = 0; i < candidates.size(); ++i) {
    SchedulableTrickleCandidate* cand = candidates[i];
    if (cand->IsHost()) {
      cand->Schedule(i*10);
    } else if (cand->IsReflexive()) {
      cand->Schedule(i*10 + 100);
    } else if (cand->IsRelay()) {
      cand->Schedule(i*10 + 200);
    }
  }
}

void DelayRelayCandidates(
    std::vector<SchedulableTrickleCandidate*>& candidates,
    unsigned int ms) {
  for (auto i = candidates.begin(); i != candidates.end(); ++i) {
    if ((*i)->IsRelay()) {
      (*i)->Schedule(ms);
    } else {
      (*i)->Schedule(0);
    }
  }
}

void AddNonPairableCandidates(
    std::vector<SchedulableTrickleCandidate*>& candidates,
    IceTestPeer *peer, size_t stream, int net_type,
    MtransportTestUtils* test_utils_) {
  for (int i=1; i<5; i++) {
    if (net_type == i)
      continue;
    switch (i) {
      case 1:
        candidates.push_back(new SchedulableTrickleCandidate(peer, stream,
                   "candidate:0 1 UDP 2113601790 10.0.0.1 12345 typ host",
                   test_utils_));
        break;
      case 2:
        candidates.push_back(new SchedulableTrickleCandidate(peer, stream,
                   "candidate:0 1 UDP 2113601791 172.16.1.1 12345 typ host",
                   test_utils_));
        break;
      case 3:
        candidates.push_back(new SchedulableTrickleCandidate(peer, stream,
                   "candidate:0 1 UDP 2113601792 192.168.0.1 12345 typ host",
                   test_utils_));
        break;
      case 4:
        candidates.push_back(new SchedulableTrickleCandidate(peer, stream,
                   "candidate:0 1 UDP 2113601793 100.64.1.1 12345 typ host",
                   test_utils_));
        break;
      default:
        UNIMPLEMENTED;
    }
  }

  for (auto i = candidates.rbegin(); i != candidates.rend(); ++i) {
    std::cerr << "Scheduling candidate: " << (*i)->Candidate().c_str() << std::endl;
    (*i)->Schedule(0);
  }
}

void DropTrickleCandidates(
    std::vector<SchedulableTrickleCandidate*>& candidates) {
}

TEST_F(WebRtcIceConnectTest, TestConnectTrickleAddStreamDuringICE) {
  AddStream(1);
  ASSERT_TRUE(Gather());
  ConnectTrickle();
  RealisticTrickleDelay(p1_->ControlTrickle(0));
  RealisticTrickleDelay(p2_->ControlTrickle(0));
  AddStream(1);
  RealisticTrickleDelay(p1_->ControlTrickle(1));
  RealisticTrickleDelay(p2_->ControlTrickle(1));
  WaitForConnected(1000);
  AssertCheckingReached();
}

TEST_F(WebRtcIceConnectTest, TestConnectTrickleAddStreamAfterICE) {
  AddStream(1);
  ASSERT_TRUE(Gather());
  ConnectTrickle();
  RealisticTrickleDelay(p1_->ControlTrickle(0));
  RealisticTrickleDelay(p2_->ControlTrickle(0));
  WaitForConnected(1000);
  AddStream(1);
  ASSERT_TRUE(Gather());
  ConnectTrickle();
  RealisticTrickleDelay(p1_->ControlTrickle(1));
  RealisticTrickleDelay(p2_->ControlTrickle(1));
  WaitForConnected(1000);
  AssertCheckingReached();
}

TEST_F(WebRtcIceConnectTest, RemoveStream) {
  AddStream(1);
  AddStream(1);
  ASSERT_TRUE(Gather());
  ConnectTrickle();
  RealisticTrickleDelay(p1_->ControlTrickle(0));
  RealisticTrickleDelay(p2_->ControlTrickle(0));
  RealisticTrickleDelay(p1_->ControlTrickle(1));
  RealisticTrickleDelay(p2_->ControlTrickle(1));
  WaitForConnected(1000);

  RemoveStream(0);
  ASSERT_TRUE(Gather());
  ConnectTrickle();
}

TEST_F(WebRtcIceConnectTest, P1NoTrickle) {
  AddStream(1);
  ASSERT_TRUE(Gather());
  ConnectTrickle();
  DropTrickleCandidates(p1_->ControlTrickle(0));
  RealisticTrickleDelay(p2_->ControlTrickle(0));
  WaitForConnected(1000);
}

TEST_F(WebRtcIceConnectTest, P2NoTrickle) {
  AddStream(1);
  ASSERT_TRUE(Gather());
  ConnectTrickle();
  RealisticTrickleDelay(p1_->ControlTrickle(0));
  DropTrickleCandidates(p2_->ControlTrickle(0));
  WaitForConnected(1000);
}

TEST_F(WebRtcIceConnectTest, RemoveAndAddStream) {
  AddStream(1);
  AddStream(1);
  ASSERT_TRUE(Gather());
  ConnectTrickle();
  RealisticTrickleDelay(p1_->ControlTrickle(0));
  RealisticTrickleDelay(p2_->ControlTrickle(0));
  RealisticTrickleDelay(p1_->ControlTrickle(1));
  RealisticTrickleDelay(p2_->ControlTrickle(1));
  WaitForConnected(1000);

  RemoveStream(0);
  AddStream(1);
  ASSERT_TRUE(Gather());
  ConnectTrickle();
  RealisticTrickleDelay(p1_->ControlTrickle(2));
  RealisticTrickleDelay(p2_->ControlTrickle(2));
  WaitForConnected(1000);
}

TEST_F(WebRtcIceConnectTest, RemoveStreamBeforeGather) {
  AddStream(1);
  AddStream(1);
  ASSERT_TRUE(Gather(0));
  RemoveStream(0);
  WaitForGather();
  ConnectTrickle();
  RealisticTrickleDelay(p1_->ControlTrickle(1));
  RealisticTrickleDelay(p2_->ControlTrickle(1));
  WaitForConnected(1000);
}

TEST_F(WebRtcIceConnectTest, RemoveStreamDuringGather) {
  AddStream(1);
  AddStream(1);
  RemoveStream(0);
  ASSERT_TRUE(Gather());
  ConnectTrickle();
  RealisticTrickleDelay(p1_->ControlTrickle(1));
  RealisticTrickleDelay(p2_->ControlTrickle(1));
  WaitForConnected(1000);
}

TEST_F(WebRtcIceConnectTest, RemoveStreamDuringConnect) {
  AddStream(1);
  AddStream(1);
  ASSERT_TRUE(Gather());
  ConnectTrickle();
  RealisticTrickleDelay(p1_->ControlTrickle(0));
  RealisticTrickleDelay(p2_->ControlTrickle(0));
  RealisticTrickleDelay(p1_->ControlTrickle(1));
  RealisticTrickleDelay(p2_->ControlTrickle(1));
  RemoveStream(0);
  WaitForConnected(1000);
}

TEST_F(WebRtcIceConnectTest, TestConnectRealTrickleOneStreamOneComponent) {
  AddStream(1);
  AddStream(1);
  ASSERT_TRUE(Gather(0));
  ConnectTrickle(TRICKLE_REAL);
  WaitForConnected();
  WaitForGather();  // ICE can complete before we finish gathering.
  AssertCheckingReached();
}

TEST_F(WebRtcIceConnectTest, TestSendReceive) {
  AddStream(1);
  ASSERT_TRUE(Gather());
  Connect();
  SendReceive();
}

TEST_F(WebRtcIceConnectTest, TestSendReceiveTcp) {
  Init(false, true);
  AddStream(1);
  ASSERT_TRUE(Gather());
  SetCandidateFilter(IsTcpCandidate);
  SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
    NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp);
  Connect();
  SendReceive();
}

//TCP SO tests works on localhost only with delay applied:
//  tc qdisc add dev lo root netem delay 10ms
TEST_F(WebRtcIceConnectTest, DISABLED_TestSendReceiveTcpSo) {
  Init(false, true);
  AddStream(1);
  ASSERT_TRUE(Gather());
  SetCandidateFilter(IsTcpSoCandidate);
  SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
    NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp);
  Connect();
  SendReceive();
}

TEST_F(WebRtcIceConnectTest, TestConsent) {
  AddStream(1);
  SetupAndCheckConsent();
  PR_Sleep(1500);
  AssertConsentRefresh();
  SendReceive();
}

TEST_F(WebRtcIceConnectTest, TestConsentTcp) {
  Init(false, true);
  AddStream(1);
  SetCandidateFilter(IsTcpCandidate);
  SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
    NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp);
  SetupAndCheckConsent();
  PR_Sleep(1500);
  AssertConsentRefresh();
  SendReceive();
}

TEST_F(WebRtcIceConnectTest, TestConsentIntermittent) {
  AddStream(1);
  SetupAndCheckConsent();
  p1_->SetBlockStun(true);
  p2_->SetBlockStun(true);
  WaitForDisconnected();
  AssertConsentRefresh(CONSENT_STALE);
  SendReceive();
  p1_->SetBlockStun(false);
  p2_->SetBlockStun(false);
  WaitForConnected();
  AssertConsentRefresh();
  SendReceive();
  p1_->SetBlockStun(true);
  p2_->SetBlockStun(true);
  WaitForDisconnected();
  AssertConsentRefresh(CONSENT_STALE);
  SendReceive();
  p1_->SetBlockStun(false);
  p2_->SetBlockStun(false);
  WaitForConnected();
  AssertConsentRefresh();
}

TEST_F(WebRtcIceConnectTest, TestConsentTimeout) {
  AddStream(1);
  SetupAndCheckConsent();
  p1_->SetBlockStun(true);
  p2_->SetBlockStun(true);
  WaitForDisconnected();
  AssertConsentRefresh(CONSENT_STALE);
  SendReceive();
  WaitForFailed();
  AssertConsentRefresh(CONSENT_EXPIRED);
  SendFailure();
}

TEST_F(WebRtcIceConnectTest, TestConsentDelayed) {
  AddStream(1);
  SetupAndCheckConsent();
  /* Note: We don't have a list of STUN transaction IDs of the previously timed
           out consent requests. Thus responses after sending the next consent
           request are ignored. */
  p1_->SetStunResponseDelay(300);
  p2_->SetStunResponseDelay(300);
  PR_Sleep(1000);
  AssertConsentRefresh();
  SendReceive();
}

TEST_F(WebRtcIceConnectTest, TestConnectTurn) {
  if (turn_server_.empty())
    return;

  AddStream(1);
  SetTurnServer(turn_server_, kDefaultStunServerPort,
                turn_user_, turn_password_);
  ASSERT_TRUE(Gather());
  Connect();
}

TEST_F(WebRtcIceConnectTest, TestConnectTurnWithDelay) {
  if (turn_server_.empty())
    return;

  AddStream(1);
  SetTurnServer(turn_server_, kDefaultStunServerPort,
                turn_user_, turn_password_);
  SetCandidateFilter(SabotageHostCandidateAndDropReflexive);
  p1_->Gather();
  PR_Sleep(500);
  p2_->Gather();
  ConnectTrickle(TRICKLE_REAL);
  WaitForGather();
  WaitForConnectedStreams();
}

TEST_F(WebRtcIceConnectTest, TestConnectTurnWithNormalTrickleDelay) {
  if (turn_server_.empty())
    return;

  AddStream(1);
  SetTurnServer(turn_server_, kDefaultStunServerPort,
                turn_user_, turn_password_);
  ASSERT_TRUE(Gather());
  ConnectTrickle();
  RealisticTrickleDelay(p1_->ControlTrickle(0));
  RealisticTrickleDelay(p2_->ControlTrickle(0));

  WaitForConnected();
  AssertCheckingReached();
}

TEST_F(WebRtcIceConnectTest, TestConnectTurnWithNormalTrickleDelayOneSided) {
  if (turn_server_.empty())
    return;

  AddStream(1);
  SetTurnServer(turn_server_, kDefaultStunServerPort,
                turn_user_, turn_password_);
  ASSERT_TRUE(Gather());
  ConnectTrickle();
  RealisticTrickleDelay(p1_->ControlTrickle(0));
  p2_->SimulateTrickle(0);

  WaitForConnected();
  AssertCheckingReached();
}

TEST_F(WebRtcIceConnectTest, TestConnectTurnWithLargeTrickleDelay) {
  if (turn_server_.empty())
    return;

  AddStream(1);
  SetTurnServer(turn_server_, kDefaultStunServerPort,
                turn_user_, turn_password_);
  SetCandidateFilter(SabotageHostCandidateAndDropReflexive);
  ASSERT_TRUE(Gather());
  ConnectTrickle();
  // Trickle host candidates immediately, but delay relay candidates
  DelayRelayCandidates(p1_->ControlTrickle(0), 3700);
  DelayRelayCandidates(p2_->ControlTrickle(0), 3700);

  WaitForConnected();
  AssertCheckingReached();
}

TEST_F(WebRtcIceConnectTest, TestConnectTurnTcp) {
  if (turn_server_.empty())
    return;

  AddStream(1);
  SetTurnServer(turn_server_, kDefaultStunServerPort,
                turn_user_, turn_password_, kNrIceTransportTcp);
  ASSERT_TRUE(Gather());
  Connect();
}

TEST_F(WebRtcIceConnectTest, TestConnectTurnOnly) {
  if (turn_server_.empty())
    return;

  AddStream(1);
  SetTurnServer(turn_server_, kDefaultStunServerPort,
                turn_user_, turn_password_);
  ASSERT_TRUE(Gather());
  SetCandidateFilter(IsRelayCandidate);
  SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                   NrIceCandidate::Type::ICE_RELAYED);
  Connect();
}

TEST_F(WebRtcIceConnectTest, TestConnectTurnTcpOnly) {
  if (turn_server_.empty())
    return;

  AddStream(1);
  SetTurnServer(turn_server_, kDefaultStunServerPort,
                turn_user_, turn_password_, kNrIceTransportTcp);
  ASSERT_TRUE(Gather());
  SetCandidateFilter(IsRelayCandidate);
  SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                   NrIceCandidate::Type::ICE_RELAYED,
                   kNrIceTransportTcp);
  Connect();
}

TEST_F(WebRtcIceConnectTest, TestSendReceiveTurnOnly) {
  if (turn_server_.empty())
    return;

  AddStream(1);
  SetTurnServer(turn_server_, kDefaultStunServerPort,
                turn_user_, turn_password_);
  ASSERT_TRUE(Gather());
  SetCandidateFilter(IsRelayCandidate);
  SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                   NrIceCandidate::Type::ICE_RELAYED);
  Connect();
  SendReceive();
}

TEST_F(WebRtcIceConnectTest, TestSendReceiveTurnTcpOnly) {
  if (turn_server_.empty())
    return;

  AddStream(1);
  SetTurnServer(turn_server_, kDefaultStunServerPort,
                turn_user_, turn_password_, kNrIceTransportTcp);
  ASSERT_TRUE(Gather());
  SetCandidateFilter(IsRelayCandidate);
  SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                   NrIceCandidate::Type::ICE_RELAYED,
                   kNrIceTransportTcp);
  Connect();
  SendReceive();
}

TEST_F(WebRtcIceConnectTest, TestSendReceiveTurnBothOnly) {
  if (turn_server_.empty())
    return;

  AddStream(1);
  std::vector<NrIceTurnServer> turn_servers;
  std::vector<unsigned char> password_vec(turn_password_.begin(),
                                          turn_password_.end());
  turn_servers.push_back(*NrIceTurnServer::Create(
                           turn_server_, kDefaultStunServerPort,
                           turn_user_, password_vec, kNrIceTransportTcp));
  turn_servers.push_back(*NrIceTurnServer::Create(
                           turn_server_, kDefaultStunServerPort,
                           turn_user_, password_vec, kNrIceTransportUdp));
  SetTurnServers(turn_servers);
  ASSERT_TRUE(Gather());
  SetCandidateFilter(IsRelayCandidate);
  // UDP is preferred.
  SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
                   NrIceCandidate::Type::ICE_RELAYED,
                   kNrIceTransportUdp);
  Connect();
  SendReceive();
}

TEST_F(WebRtcIceConnectTest, TestConnectShutdownOneSide) {
  AddStream(1);
  ASSERT_TRUE(Gather());
  ConnectThenDelete();
}

TEST_F(WebRtcIceConnectTest, TestPollCandPairsBeforeConnect) {
  AddStream(1);
  ASSERT_TRUE(Gather());

  std::vector<NrIceCandidatePair> pairs;
  nsresult res = p1_->GetCandidatePairs(0, &pairs);
  // There should be no candidate pairs prior to calling Connect()
  ASSERT_EQ(NS_OK, res);
  ASSERT_EQ(0U, pairs.size());

  res = p2_->GetCandidatePairs(0, &pairs);
  ASSERT_EQ(NS_OK, res);
  ASSERT_EQ(0U, pairs.size());
}

TEST_F(WebRtcIceConnectTest, TestPollCandPairsAfterConnect) {
  AddStream(1);
  ASSERT_TRUE(Gather());
  Connect();

  std::vector<NrIceCandidatePair> pairs;
  nsresult r = p1_->GetCandidatePairs(0, &pairs);
  ASSERT_EQ(NS_OK, r);
  // How detailed of a check do we want to do here? If the turn server is
  // functioning, we'll get at least two pairs, but this is probably not
  // something we should assume.
  ASSERT_NE(0U, pairs.size());
  ASSERT_TRUE(p1_->CandidatePairsPriorityDescending(pairs));
  ASSERT_TRUE(ContainsSucceededPair(pairs));
  pairs.clear();

  r = p2_->GetCandidatePairs(0, &pairs);
  ASSERT_EQ(NS_OK, r);
  ASSERT_NE(0U, pairs.size());
  ASSERT_TRUE(p2_->CandidatePairsPriorityDescending(pairs));
  ASSERT_TRUE(ContainsSucceededPair(pairs));
}

// TODO Bug 1259842 - disabled until we find a better way to handle two
// candidates from different RFC1918 ranges
TEST_F(WebRtcIceConnectTest, DISABLED_TestHostCandPairingFilter) {
  Init(false, false, false);
  AddStream(1);
  ASSERT_TRUE(Gather());
  SetCandidateFilter(IsIpv4Candidate);

  int host_net = p1_->GetCandidatesPrivateIpv4Range(0);
  if (host_net <= 0) {
    // TODO bug 1226838: make this work with multiple private IPs
    FAIL() << "This test needs exactly one private IPv4 host candidate to work" << std::endl;
  }

  ConnectTrickle();
  AddNonPairableCandidates(p1_->ControlTrickle(0), p1_.get(), 0, host_net, test_utils_);
  AddNonPairableCandidates(p2_->ControlTrickle(0), p2_.get(), 0, host_net, test_utils_);

  std::vector<NrIceCandidatePair> pairs;
  p1_->GetCandidatePairs(0, &pairs);
  for (auto p : pairs) {
    std::cerr << "Verifying pair:" << std::endl;
    p1_->DumpCandidatePair(p);
    nr_transport_addr addr;
    nr_str_port_to_transport_addr(p.local.local_addr.host.c_str(), 0, IPPROTO_UDP, &addr);
    ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) == host_net);
    nr_str_port_to_transport_addr(p.remote.cand_addr.host.c_str(), 0, IPPROTO_UDP, &addr);
    ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) == host_net);
  }
}

// TODO Bug 1226838 - See Comment 2 - this test can't work as written
TEST_F(WebRtcIceConnectTest, DISABLED_TestSrflxCandPairingFilter) {
  if (stun_server_address_.empty()) {
    return;
  }

  Init(false, false, false);
  AddStream(1);
  ASSERT_TRUE(Gather());
  SetCandidateFilter(IsSrflxCandidate);

  if (p1_->GetCandidatesPrivateIpv4Range(0) <= 0) {
    // TODO bug 1226838: make this work with public IP addresses
    std::cerr << "Don't run this test at IETF meetings!" << std::endl;
    FAIL() << "This test needs one private IPv4 host candidate to work" << std::endl;
  }

  ConnectTrickle();
  SimulateTrickleP1(0);
  SimulateTrickleP2(0);

  std::vector<NrIceCandidatePair> pairs;
  p1_->GetCandidatePairs(0, &pairs);
  for (auto p : pairs) {
    std::cerr << "Verifying P1 pair:" << std::endl;
    p1_->DumpCandidatePair(p);
    nr_transport_addr addr;
    nr_str_port_to_transport_addr(p.local.local_addr.host.c_str(), 0, IPPROTO_UDP, &addr);
    ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) != 0);
    nr_str_port_to_transport_addr(p.remote.cand_addr.host.c_str(), 0, IPPROTO_UDP, &addr);
    ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) == 0);
  }
  p2_->GetCandidatePairs(0, &pairs);
  for (auto p : pairs) {
    std::cerr << "Verifying P2 pair:" << std::endl;
    p2_->DumpCandidatePair(p);
    nr_transport_addr addr;
    nr_str_port_to_transport_addr(p.local.local_addr.host.c_str(), 0, IPPROTO_UDP, &addr);
    ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) != 0);
    nr_str_port_to_transport_addr(p.remote.cand_addr.host.c_str(), 0, IPPROTO_UDP, &addr);
    ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) == 0);
  }
}

TEST_F(WebRtcIceConnectTest, TestPollCandPairsDuringConnect) {
  AddStream(1);
  ASSERT_TRUE(Gather());

  p2_->Connect(p1_.get(), TRICKLE_NONE, false);
  p1_->Connect(p2_.get(), TRICKLE_NONE, false);

  std::vector<NrIceCandidatePair> pairs1;
  std::vector<NrIceCandidatePair> pairs2;

  p1_->StartChecks();
  p1_->UpdateAndValidateCandidatePairs(0, &pairs1);
  p2_->UpdateAndValidateCandidatePairs(0, &pairs2);

  p2_->StartChecks();
  p1_->UpdateAndValidateCandidatePairs(0, &pairs1);
  p2_->UpdateAndValidateCandidatePairs(0, &pairs2);

  WaitForConnectedStreams();
  p1_->UpdateAndValidateCandidatePairs(0, &pairs1);
  p2_->UpdateAndValidateCandidatePairs(0, &pairs2);
  ASSERT_TRUE(ContainsSucceededPair(pairs1));
  ASSERT_TRUE(ContainsSucceededPair(pairs2));
}

TEST_F(WebRtcIceConnectTest, TestRLogConnector) {
  AddStream(1);
  ASSERT_TRUE(Gather());

  p2_->Connect(p1_.get(), TRICKLE_NONE, false);
  p1_->Connect(p2_.get(), TRICKLE_NONE, false);

  std::vector<NrIceCandidatePair> pairs1;
  std::vector<NrIceCandidatePair> pairs2;

  p1_->StartChecks();
  p1_->UpdateAndValidateCandidatePairs(0, &pairs1);
  p2_->UpdateAndValidateCandidatePairs(0, &pairs2);

  p2_->StartChecks();
  p1_->UpdateAndValidateCandidatePairs(0, &pairs1);
  p2_->UpdateAndValidateCandidatePairs(0, &pairs2);

  WaitForConnectedStreams();
  p1_->UpdateAndValidateCandidatePairs(0, &pairs1);
  p2_->UpdateAndValidateCandidatePairs(0, &pairs2);
  ASSERT_TRUE(ContainsSucceededPair(pairs1));
  ASSERT_TRUE(ContainsSucceededPair(pairs2));

  for (auto p = pairs1.begin(); p != pairs1.end(); ++p) {
    std::deque<std::string> logs;
    std::string substring("CAND-PAIR(");
    substring += p->codeword;
    RLogConnector::GetInstance()->Filter(substring, 0, &logs);
    ASSERT_NE(0U, logs.size());
  }

  for (auto p = pairs2.begin(); p != pairs2.end(); ++p) {
    std::deque<std::string> logs;
    std::string substring("CAND-PAIR(");
    substring += p->codeword;
    RLogConnector::GetInstance()->Filter(substring, 0, &logs);
    ASSERT_NE(0U, logs.size());
  }
}

TEST_F(WebRtcIcePrioritizerTest, TestPrioritizer) {
  SetPriorizer(::mozilla::CreateInterfacePrioritizer());

  AddInterface("0", NR_INTERFACE_TYPE_VPN, 100); // unknown vpn
  AddInterface("1", NR_INTERFACE_TYPE_VPN | NR_INTERFACE_TYPE_WIRED, 100); // wired vpn
  AddInterface("2", NR_INTERFACE_TYPE_VPN | NR_INTERFACE_TYPE_WIFI, 100); // wifi vpn
  AddInterface("3", NR_INTERFACE_TYPE_VPN | NR_INTERFACE_TYPE_MOBILE, 100); // wifi vpn
  AddInterface("4", NR_INTERFACE_TYPE_WIRED, 1000); // wired, high speed
  AddInterface("5", NR_INTERFACE_TYPE_WIRED, 10); // wired, low speed
  AddInterface("6", NR_INTERFACE_TYPE_WIFI, 10); // wifi, low speed
  AddInterface("7", NR_INTERFACE_TYPE_WIFI, 1000); // wifi, high speed
  AddInterface("8", NR_INTERFACE_TYPE_MOBILE, 10); // mobile, low speed
  AddInterface("9", NR_INTERFACE_TYPE_MOBILE, 1000); // mobile, high speed
  AddInterface("10", NR_INTERFACE_TYPE_UNKNOWN, 10); // unknown, low speed
  AddInterface("11", NR_INTERFACE_TYPE_UNKNOWN, 1000); // unknown, high speed

  // expected preference "4" > "5" > "1" > "7" > "6" > "2" > "9" > "8" > "3" > "11" > "10" > "0"

  HasLowerPreference("0", "10");
  HasLowerPreference("10", "11");
  HasLowerPreference("11", "3");
  HasLowerPreference("3", "8");
  HasLowerPreference("8", "9");
  HasLowerPreference("9", "2");
  HasLowerPreference("2", "6");
  HasLowerPreference("6", "7");
  HasLowerPreference("7", "1");
  HasLowerPreference("1", "5");
  HasLowerPreference("5", "4");
}

TEST_F(WebRtcIcePacketFilterTest, TestSendNonStunPacket) {
  const unsigned char data[] = "12345abcde";
  TestOutgoing(data, sizeof(data), 123, 45, false);
  TestOutgoingTcp(data, sizeof(data), false);
}

TEST_F(WebRtcIcePacketFilterTest, TestRecvNonStunPacket) {
  const unsigned char data[] = "12345abcde";
  TestIncoming(data, sizeof(data), 123, 45, false);
  TestIncomingTcp(data, sizeof(data), true);
}

TEST_F(WebRtcIcePacketFilterTest, TestSendStunPacket) {
  nr_stun_message *msg;
  ASSERT_EQ(0, nr_stun_build_req_no_auth(NULL, &msg));
  msg->header.type = NR_STUN_MSG_BINDING_REQUEST;
  ASSERT_EQ(0, nr_stun_encode_message(msg));
  TestOutgoing(msg->buffer, msg->length, 123, 45, true);
  TestOutgoingTcp(msg->buffer, msg->length, true);
  TestOutgoingTcpFramed(msg->buffer, msg->length, true);
  ASSERT_EQ(0, nr_stun_message_destroy(&msg));
}

TEST_F(WebRtcIcePacketFilterTest, TestRecvStunPacketWithoutAPendingId) {
  nr_stun_message *msg;
  ASSERT_EQ(0, nr_stun_build_req_no_auth(NULL, &msg));

  msg->header.id.octet[0] = 1;
  msg->header.type = NR_STUN_MSG_BINDING_REQUEST;
  ASSERT_EQ(0, nr_stun_encode_message(msg));
  TestOutgoing(msg->buffer, msg->length, 123, 45, true);
  TestOutgoingTcp(msg->buffer, msg->length, true);

  msg->header.id.octet[0] = 0;
  msg->header.type = NR_STUN_MSG_BINDING_RESPONSE;
  ASSERT_EQ(0, nr_stun_encode_message(msg));
  TestIncoming(msg->buffer, msg->length, 123, 45, true);
  TestIncomingTcp(msg->buffer, msg->length, true);

  ASSERT_EQ(0, nr_stun_message_destroy(&msg));
}

TEST_F(WebRtcIcePacketFilterTest, TestRecvStunPacketWithoutAPendingIdTcpFramed) {
  nr_stun_message *msg;
  ASSERT_EQ(0, nr_stun_build_req_no_auth(NULL, &msg));

  msg->header.id.octet[0] = 1;
  msg->header.type = NR_STUN_MSG_BINDING_REQUEST;
  ASSERT_EQ(0, nr_stun_encode_message(msg));
  TestOutgoingTcpFramed(msg->buffer, msg->length, true);

  msg->header.id.octet[0] = 0;
  msg->header.type = NR_STUN_MSG_BINDING_RESPONSE;
  ASSERT_EQ(0, nr_stun_encode_message(msg));
  TestIncomingTcpFramed(msg->buffer, msg->length, true);

  ASSERT_EQ(0, nr_stun_message_destroy(&msg));
}

TEST_F(WebRtcIcePacketFilterTest, TestRecvStunPacketWithoutAPendingAddress) {
  nr_stun_message *msg;
  ASSERT_EQ(0, nr_stun_build_req_no_auth(NULL, &msg));

  msg->header.type = NR_STUN_MSG_BINDING_REQUEST;
  ASSERT_EQ(0, nr_stun_encode_message(msg));
  TestOutgoing(msg->buffer, msg->length, 123, 45, true);
  // nothing to test here for the TCP filter

  msg->header.type = NR_STUN_MSG_BINDING_RESPONSE;
  ASSERT_EQ(0, nr_stun_encode_message(msg));
  TestIncoming(msg->buffer, msg->length, 123, 46, false);
  TestIncoming(msg->buffer, msg->length, 124, 45, false);

  ASSERT_EQ(0, nr_stun_message_destroy(&msg));
}

TEST_F(WebRtcIcePacketFilterTest, TestRecvStunPacketWithPendingIdAndAddress) {
  nr_stun_message *msg;
  ASSERT_EQ(0, nr_stun_build_req_no_auth(NULL, &msg));

  msg->header.type = NR_STUN_MSG_BINDING_REQUEST;
  ASSERT_EQ(0, nr_stun_encode_message(msg));
  TestOutgoing(msg->buffer, msg->length, 123, 45, true);
  TestOutgoingTcp(msg->buffer, msg->length, true);

  msg->header.type = NR_STUN_MSG_BINDING_RESPONSE;
  ASSERT_EQ(0, nr_stun_encode_message(msg));
  TestIncoming(msg->buffer, msg->length, 123, 45, true);
  TestIncomingTcp(msg->buffer, msg->length, true);

  // Test whitelist by filtering non-stun packets.
  const unsigned char data[] = "12345abcde";

  // 123:45 is white-listed.
  TestOutgoing(data, sizeof(data), 123, 45, true);
  TestOutgoingTcp(data, sizeof(data), true);
  TestIncoming(data, sizeof(data), 123, 45, true);
  TestIncomingTcp(data, sizeof(data), true);

  // Indications pass as well.
  msg->header.type = NR_STUN_MSG_BINDING_INDICATION;
  ASSERT_EQ(0, nr_stun_encode_message(msg));
  TestOutgoing(msg->buffer, msg->length, 123, 45, true);
  TestOutgoingTcp(msg->buffer, msg->length, true);
  TestIncoming(msg->buffer, msg->length, 123, 45, true);
  TestIncomingTcp(msg->buffer, msg->length, true);

  // Packets from and to other address are still disallowed.
  // Note: this doesn't apply for TCP connections
  TestOutgoing(data, sizeof(data), 123, 46, false);
  TestIncoming(data, sizeof(data), 123, 46, false);
  TestOutgoing(data, sizeof(data), 124, 45, false);
  TestIncoming(data, sizeof(data), 124, 45, false);

  ASSERT_EQ(0, nr_stun_message_destroy(&msg));
}

TEST_F(WebRtcIcePacketFilterTest, TestRecvStunPacketWithPendingIdTcpFramed) {
  nr_stun_message *msg;
  ASSERT_EQ(0, nr_stun_build_req_no_auth(NULL, &msg));

  msg->header.type = NR_STUN_MSG_BINDING_REQUEST;
  ASSERT_EQ(0, nr_stun_encode_message(msg));
  TestOutgoingTcpFramed(msg->buffer, msg->length, true);

  msg->header.type = NR_STUN_MSG_BINDING_RESPONSE;
  ASSERT_EQ(0, nr_stun_encode_message(msg));
  TestIncomingTcpFramed(msg->buffer, msg->length, true);

  // Test whitelist by filtering non-stun packets.
  const unsigned char data[] = "12345abcde";

  TestOutgoingTcpFramed(data, sizeof(data), true);
  TestIncomingTcpFramed(data, sizeof(data), true);

  ASSERT_EQ(0, nr_stun_message_destroy(&msg));
}

TEST_F(WebRtcIcePacketFilterTest, TestSendNonRequestStunPacket) {
  nr_stun_message *msg;
  ASSERT_EQ(0, nr_stun_build_req_no_auth(NULL, &msg));

  msg->header.type = NR_STUN_MSG_BINDING_RESPONSE;
  ASSERT_EQ(0, nr_stun_encode_message(msg));
  TestOutgoing(msg->buffer, msg->length, 123, 45, false);
  TestOutgoingTcp(msg->buffer, msg->length, false);

  // Send a packet so we allow the incoming request.
  msg->header.type = NR_STUN_MSG_BINDING_REQUEST;
  ASSERT_EQ(0, nr_stun_encode_message(msg));
  TestOutgoing(msg->buffer, msg->length, 123, 45, true);
  TestOutgoingTcp(msg->buffer, msg->length, true);

  // This packet makes us able to send a response.
  msg->header.type = NR_STUN_MSG_BINDING_REQUEST;
  ASSERT_EQ(0, nr_stun_encode_message(msg));
  TestIncoming(msg->buffer, msg->length, 123, 45, true);
  TestIncomingTcp(msg->buffer, msg->length, true);

  msg->header.type = NR_STUN_MSG_BINDING_RESPONSE;
  ASSERT_EQ(0, nr_stun_encode_message(msg));
  TestOutgoing(msg->buffer, msg->length, 123, 45, true);
  TestOutgoingTcp(msg->buffer, msg->length, true);

  ASSERT_EQ(0, nr_stun_message_destroy(&msg));
}

TEST_F(WebRtcIcePacketFilterTest, TestRecvDataPacketWithAPendingAddress) {
  nr_stun_message *msg;
  ASSERT_EQ(0, nr_stun_build_req_no_auth(NULL, &msg));

  msg->header.type = NR_STUN_MSG_BINDING_REQUEST;
  ASSERT_EQ(0, nr_stun_encode_message(msg));
  TestOutgoing(msg->buffer, msg->length, 123, 45, true);
  TestOutgoingTcp(msg->buffer, msg->length, true);

  const unsigned char data[] = "12345abcde";
  TestIncoming(data, sizeof(data), 123, 45, true);
  TestIncomingTcp(data, sizeof(data), true);

  ASSERT_EQ(0, nr_stun_message_destroy(&msg));
}

TEST(WebRtcIceInternalsTest, TestAddBogusAttribute) {
  nr_stun_message *req;
  ASSERT_EQ(0, nr_stun_message_create(&req));
  Data *data;
  ASSERT_EQ(0, r_data_alloc(&data, 3000));
  memset(data->data, 'A', data->len);
  ASSERT_TRUE(nr_stun_message_add_message_integrity_attribute(req, data));
  ASSERT_EQ(0, r_data_destroy(&data));
  ASSERT_EQ(0, nr_stun_message_destroy(&req));
}