/* -*- 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: bcampen@mozilla.com

extern "C" {
#include "stun_msg.h" // for NR_STUN_MAX_MESSAGE_SIZE
#include "stun_util.h"
#include "nr_api.h"
#include "async_wait.h"
#include "nr_socket.h"
#include "nr_socket_local.h"
#include "stun_hint.h"
#include "local_addr.h"
#include "registry.h"
}

#include "test_nr_socket.h"

#include "nsCOMPtr.h"
#include "nsNetCID.h"
#include "nsServiceManagerUtils.h"
#include "nsAutoPtr.h"
#include "runnable_utils.h"

#include <vector>

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

#define DATA_BUF_SIZE 1024

namespace mozilla {

class TestNrSocketTest : public MtransportTest {
 public:
  TestNrSocketTest() :
    MtransportTest(),
    wait_done_for_main_(false),
    sts_(),
    public_addrs_(),
    private_addrs_(),
    nats_() {
  }

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

    // Get the transport service as a dispatch target
    nsresult rv;
    sts_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
    EXPECT_TRUE(NS_SUCCEEDED(rv)) << "Failed to get STS: " << (int)rv;
  }

  void TearDown() override {
    sts_->Dispatch(WrapRunnable(this, &TestNrSocketTest::TearDown_s),
                   NS_DISPATCH_SYNC);

    MtransportTest::TearDown();
  }

  void TearDown_s() {
    public_addrs_.clear();
    private_addrs_.clear();
    nats_.clear();
    sts_ = nullptr;
  }

  RefPtr<TestNrSocket> CreateTestNrSocket_s(const char *ip_str,
                                            int proto,
                                            TestNat *nat) {
    // If no nat is supplied, we create a default NAT which is disabled. This
    // is how we simulate a non-natted socket.
    RefPtr<TestNrSocket> sock(new TestNrSocket(nat ? nat : new TestNat));
    nr_transport_addr address;
    nr_str_port_to_transport_addr(ip_str, 0, proto, &address);
    int r = sock->create(&address);
    if (r) {
      return nullptr;
    }
    return sock;
  }

  void CreatePublicAddrs(size_t count,
                         const char *ip_str = "127.0.0.1",
                         int proto = IPPROTO_UDP) {
    sts_->Dispatch(
        WrapRunnable(this,
                     &TestNrSocketTest::CreatePublicAddrs_s,
                     count,
                     ip_str,
                     proto),
        NS_DISPATCH_SYNC);
  }

  void CreatePublicAddrs_s(size_t count, const char* ip_str, int proto) {
    while (count--) {
      auto sock = CreateTestNrSocket_s(ip_str, proto, nullptr);
      ASSERT_TRUE(sock) << "Failed to create socket";
      public_addrs_.push_back(sock);
    }
  }

  RefPtr<TestNat> CreatePrivateAddrs(size_t size,
                                     const char* ip_str = "127.0.0.1",
                                     int proto = IPPROTO_UDP) {
    RefPtr<TestNat> result;
    sts_->Dispatch(
        WrapRunnableRet(&result,
                        this,
                        &TestNrSocketTest::CreatePrivateAddrs_s,
                        size,
                        ip_str,
                        proto),
        NS_DISPATCH_SYNC);
    return result;
  }

  RefPtr<TestNat> CreatePrivateAddrs_s(size_t count,
                                       const char* ip_str,
                                       int proto) {
    RefPtr<TestNat> nat(new TestNat);
    while (count--) {
      auto sock = CreateTestNrSocket_s(ip_str, proto, nat);
      if (!sock) {
        EXPECT_TRUE(false) << "Failed to create socket";
        break;
      }
      private_addrs_.push_back(sock);
    }
    nat->enabled_ = true;
    nats_.push_back(nat);
    return nat;
  }

  bool CheckConnectivityVia(
      TestNrSocket *from,
      TestNrSocket *to,
      const nr_transport_addr &via,
      nr_transport_addr *sender_external_address = nullptr) {
    MOZ_ASSERT(from);

    if (!WaitForWriteable(from)) {
      return false;
    }

    int result = 0;
    sts_->Dispatch(WrapRunnableRet(&result,
                                   this,
                                   &TestNrSocketTest::SendData_s,
                                   from,
                                   via),
                   NS_DISPATCH_SYNC);
    if (result) {
      return false;
    }

    if (!WaitForReadable(to)) {
      return false;
    }

    nr_transport_addr dummy_outparam;
    if (!sender_external_address) {
      sender_external_address = &dummy_outparam;
    }

    MOZ_ASSERT(to);
    sts_->Dispatch(WrapRunnableRet(&result,
                                   this,
                                   &TestNrSocketTest::RecvData_s,
                                   to,
                                   sender_external_address),
                   NS_DISPATCH_SYNC);

    return !result;
  }

  bool CheckConnectivity(
      TestNrSocket *from,
      TestNrSocket *to,
      nr_transport_addr *sender_external_address = nullptr) {
    nr_transport_addr destination_address;
    int r = GetAddress(to, &destination_address);
    if (r) {
      return false;
    }

    return CheckConnectivityVia(from,
                                to,
                                destination_address,
                                sender_external_address);
  }

  bool CheckTcpConnectivity(TestNrSocket *from, TestNrSocket *to) {
    NrSocketBase *accepted_sock;
    if (!Connect(from, to, &accepted_sock)) {
      std::cerr << "Connect failed" << std::endl;
      return false;
    }

    // write on |from|, recv on |accepted_sock|
    if (!WaitForWriteable(from)) {
      std::cerr << __LINE__ << "WaitForWriteable (1) failed" << std::endl;
      return false;
    }

    int r;
    sts_->Dispatch(WrapRunnableRet(&r,
                                   this,
                                   &TestNrSocketTest::SendDataTcp_s,
                                   from),
                   NS_DISPATCH_SYNC);
    if (r) {
      std::cerr << "SendDataTcp_s (1) failed" << std::endl;
      return false;
    }

    if (!WaitForReadable(accepted_sock)) {
      std::cerr << __LINE__ << "WaitForReadable (1) failed" << std::endl;
      return false;
    }

    sts_->Dispatch(WrapRunnableRet(&r,
                                   this,
                                   &TestNrSocketTest::RecvDataTcp_s,
                                   accepted_sock),
                   NS_DISPATCH_SYNC);
    if (r) {
      std::cerr << "RecvDataTcp_s (1) failed" << std::endl;
      return false;
    }

    if (!WaitForWriteable(accepted_sock)) {
      std::cerr << __LINE__ << "WaitForWriteable (2) failed" << std::endl;
      return false;
    }

    sts_->Dispatch(WrapRunnableRet(&r,
                                   this,
                                   &TestNrSocketTest::SendDataTcp_s,
                                   accepted_sock),
                   NS_DISPATCH_SYNC);
    if (r) {
      std::cerr << "SendDataTcp_s (2) failed" << std::endl;
      return false;
    }

    if (!WaitForReadable(from)) {
      std::cerr << __LINE__ << "WaitForReadable (2) failed" << std::endl;
      return false;
    }

    sts_->Dispatch(WrapRunnableRet(&r,
                                   this,
                                   &TestNrSocketTest::RecvDataTcp_s,
                                   from),
                   NS_DISPATCH_SYNC);
    if (r) {
      std::cerr << "RecvDataTcp_s (2) failed" << std::endl;
      return false;
    }

    return true;
  }

  int GetAddress(TestNrSocket *sock, nr_transport_addr_ *address) {
    MOZ_ASSERT(sock);
    MOZ_ASSERT(address);
    int r;
    sts_->Dispatch(WrapRunnableRet(&r,
                                   this,
                                   &TestNrSocketTest::GetAddress_s,
                                   sock,
                                   address),
                   NS_DISPATCH_SYNC);
    return r;
  }

  int GetAddress_s(TestNrSocket *sock, nr_transport_addr *address) {
    return sock->getaddr(address);
  }

  int SendData_s(TestNrSocket *from, const nr_transport_addr &to) {
    // It is up to caller to ensure that |from| is writeable.
    const char buf[] = "foobajooba";
    return from->sendto(buf, sizeof(buf), 0,
        // TODO(bug 1170299): Remove const_cast when no longer necessary
        const_cast<nr_transport_addr*>(&to));
  }

  int SendDataTcp_s(NrSocketBase *from) {
    // It is up to caller to ensure that |from| is writeable.
    const char buf[] = "foobajooba";
    size_t written;
    return from->write(buf, sizeof(buf), &written);
  }

  int RecvData_s(TestNrSocket *to, nr_transport_addr *from) {
    // It is up to caller to ensure that |to| is readable
    char buf[DATA_BUF_SIZE];
    size_t len;
    // Maybe check that data matches?
    int r = to->recvfrom(buf, sizeof(buf), &len, 0, from);
    if (!r && (len == 0)) {
      r = R_INTERNAL;
    }
    return r;
  }

  int RecvDataTcp_s(NrSocketBase *to) {
    // It is up to caller to ensure that |to| is readable
    char buf[DATA_BUF_SIZE];
    size_t len;
    // Maybe check that data matches?
    int r = to->read(buf, sizeof(buf), &len);
    if (!r && (len == 0)) {
      r = R_INTERNAL;
    }
    return r;
  }

  int Listen_s(TestNrSocket *to) {
    // listen on |to|
    int r = to->listen(1);
    if (r) {
      return r;
    }
    return 0;
  }

  int Connect_s(TestNrSocket *from, TestNrSocket *to) {
    // connect on |from|
    nr_transport_addr destination_address;
    int r = to->getaddr(&destination_address);
    if (r) {
      return r;
    }

    r = from->connect(&destination_address);
    if (r) {
      return r;
    }

    return 0;
  }

  int Accept_s(TestNrSocket *to, NrSocketBase **accepted_sock) {
    nr_socket *sock;
    nr_transport_addr source_address;
    int r = to->accept(&source_address, &sock);
    if (r) {
      return r;
    }

    *accepted_sock = reinterpret_cast<NrSocketBase*>(sock->obj);
    return 0;
  }

  bool Connect(TestNrSocket *from,
               TestNrSocket *to,
               NrSocketBase **accepted_sock) {
    int r;
    sts_->Dispatch(WrapRunnableRet(&r,
                                   this,
                                   &TestNrSocketTest::Listen_s,
                                   to),
                   NS_DISPATCH_SYNC);
    if (r) {
      std::cerr << "Listen_s failed: " << r << std::endl;
      return false;
    }

    sts_->Dispatch(WrapRunnableRet(&r,
                                   this,
                                   &TestNrSocketTest::Connect_s,
                                   from,
                                   to),
                   NS_DISPATCH_SYNC);
    if (r && r != R_WOULDBLOCK) {
      std::cerr << "Connect_s failed: " << r << std::endl;
      return false;
    }

    if (!WaitForReadable(to)) {
      std::cerr << "WaitForReadable failed" << std::endl;
      return false;
    }

    sts_->Dispatch(WrapRunnableRet(&r,
                                   this,
                                   &TestNrSocketTest::Accept_s,
                                   to,
                                   accepted_sock),
                   NS_DISPATCH_SYNC);

    if (r) {
      std::cerr << "Accept_s failed: " << r << std::endl;
      return false;
    }
    return true;
  }


  bool WaitForSocketState(NrSocketBase *sock, int state) {
    MOZ_ASSERT(sock);
    sts_->Dispatch(WrapRunnable(this,
                                &TestNrSocketTest::WaitForSocketState_s,
                                sock,
                                state),
                   NS_DISPATCH_SYNC);

    bool res;
    WAIT_(wait_done_for_main_, 500, res);
    wait_done_for_main_ = false;

    if (!res) {
      sts_->Dispatch(WrapRunnable(this,
                                  &TestNrSocketTest::CancelWait_s,
                                  sock,
                                  state),
                     NS_DISPATCH_SYNC);
    }

    return res;
  }

  void WaitForSocketState_s(NrSocketBase *sock, int state) {
     NR_ASYNC_WAIT(sock, state, &WaitDone, this);
  }

  void CancelWait_s(NrSocketBase *sock, int state) {
     sock->cancel(state);
  }

  bool WaitForReadable(NrSocketBase *sock) {
    return WaitForSocketState(sock, NR_ASYNC_WAIT_READ);
  }

  bool WaitForWriteable(NrSocketBase *sock) {
    return WaitForSocketState(sock, NR_ASYNC_WAIT_WRITE);
  }

  static void WaitDone(void *sock, int how, void *test_fixture) {
    TestNrSocketTest *test = static_cast<TestNrSocketTest*>(test_fixture);
    test->wait_done_for_main_ = true;
  }

  // Simple busywait boolean for the test cases to spin on.
  Atomic<bool> wait_done_for_main_;

  nsCOMPtr<nsIEventTarget> sts_;
  std::vector<RefPtr<TestNrSocket>> public_addrs_;
  std::vector<RefPtr<TestNrSocket>> private_addrs_;
  std::vector<RefPtr<TestNat>> nats_;
};

} // namespace mozilla

using mozilla::TestNrSocketTest;
using mozilla::TestNat;

TEST_F(TestNrSocketTest, PublicConnectivity) {
  CreatePublicAddrs(2);

  ASSERT_TRUE(CheckConnectivity(public_addrs_[0], public_addrs_[1]));
  ASSERT_TRUE(CheckConnectivity(public_addrs_[1], public_addrs_[0]));
  ASSERT_TRUE(CheckConnectivity(public_addrs_[0], public_addrs_[0]));
  ASSERT_TRUE(CheckConnectivity(public_addrs_[1], public_addrs_[1]));
}

TEST_F(TestNrSocketTest, PrivateConnectivity) {
  RefPtr<TestNat> nat(CreatePrivateAddrs(2));
  nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
  nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;

  ASSERT_TRUE(CheckConnectivity(private_addrs_[0], private_addrs_[1]));
  ASSERT_TRUE(CheckConnectivity(private_addrs_[1], private_addrs_[0]));
  ASSERT_TRUE(CheckConnectivity(private_addrs_[0], private_addrs_[0]));
  ASSERT_TRUE(CheckConnectivity(private_addrs_[1], private_addrs_[1]));
}

TEST_F(TestNrSocketTest, NoConnectivityWithoutPinhole) {
  RefPtr<TestNat> nat(CreatePrivateAddrs(1));
  nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
  nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
  CreatePublicAddrs(1);

  ASSERT_FALSE(CheckConnectivity(public_addrs_[0], private_addrs_[0]));
}

TEST_F(TestNrSocketTest, NoConnectivityBetweenSubnets) {
  RefPtr<TestNat> nat1(CreatePrivateAddrs(1));
  nat1->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
  nat1->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
  RefPtr<TestNat> nat2(CreatePrivateAddrs(1));
  nat2->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
  nat2->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;

  ASSERT_FALSE(CheckConnectivity(private_addrs_[0], private_addrs_[1]));
  ASSERT_FALSE(CheckConnectivity(private_addrs_[1], private_addrs_[0]));
  ASSERT_TRUE(CheckConnectivity(private_addrs_[0], private_addrs_[0]));
  ASSERT_TRUE(CheckConnectivity(private_addrs_[1], private_addrs_[1]));
}

TEST_F(TestNrSocketTest, FullConeAcceptIngress) {
  RefPtr<TestNat> nat(CreatePrivateAddrs(1));
  nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
  nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
  CreatePublicAddrs(2);

  nr_transport_addr sender_external_address;
  // Open pinhole to public IP 0
  ASSERT_TRUE(CheckConnectivity(private_addrs_[0],
                                public_addrs_[0],
                                &sender_external_address));

  // Verify that return traffic works
  ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0],
                                   private_addrs_[0],
                                   sender_external_address));

  // Verify that other public IP can use the pinhole
  ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1],
                                   private_addrs_[0],
                                   sender_external_address));
}

TEST_F(TestNrSocketTest, FullConeOnePinhole) {
  RefPtr<TestNat> nat(CreatePrivateAddrs(1));
  nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
  nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
  CreatePublicAddrs(2);

  nr_transport_addr sender_external_address;
  // Open pinhole to public IP 0
  ASSERT_TRUE(CheckConnectivity(private_addrs_[0],
                                public_addrs_[0],
                                &sender_external_address));

  // Verify that return traffic works
  ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0],
                                   private_addrs_[0],
                                   sender_external_address));

  // Send traffic to other public IP, verify that it uses the same pinhole
  nr_transport_addr sender_external_address2;
  ASSERT_TRUE(CheckConnectivity(private_addrs_[0],
                                public_addrs_[1],
                                &sender_external_address2));
  ASSERT_FALSE(nr_transport_addr_cmp(&sender_external_address,
                                     &sender_external_address2,
                                     NR_TRANSPORT_ADDR_CMP_MODE_ALL))
    << "addr1: " << sender_external_address.as_string << " addr2: "
    << sender_external_address2.as_string;
}

// OS 10.6 doesn't seem to allow us to open ports on 127.0.0.2, and while linux
// does allow this, it has other behavior (see below) that prevents this test
// from working.
TEST_F(TestNrSocketTest, DISABLED_AddressRestrictedCone) {
  RefPtr<TestNat> nat(CreatePrivateAddrs(1));
  nat->filtering_type_ = TestNat::ADDRESS_DEPENDENT;
  nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
  CreatePublicAddrs(2, "127.0.0.1");
  CreatePublicAddrs(1, "127.0.0.2");

  nr_transport_addr sender_external_address;
  // Open pinhole to public IP 0
  ASSERT_TRUE(CheckConnectivity(private_addrs_[0],
                                public_addrs_[0],
                                &sender_external_address));

  // Verify that return traffic works
  ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0],
                                   private_addrs_[0],
                                   sender_external_address));

  // Verify that another address on the same host can use the pinhole
  ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1],
                                   private_addrs_[0],
                                   sender_external_address));

  // Linux has a tendency to monkey around with source addresses, doing
  // stuff like substituting 127.0.0.1 for packets sent by 127.0.0.2, and even
  // going as far as substituting localhost for a packet sent from a real IP
  // address when the destination is localhost. The only way to make this test
  // work on linux is to have two real IP addresses.
#ifndef __linux__
  // Verify that an address on a different host can't use the pinhole
  ASSERT_FALSE(CheckConnectivityVia(public_addrs_[2],
                                   private_addrs_[0],
                                   sender_external_address));
#endif

  // Send traffic to other public IP, verify that it uses the same pinhole
  nr_transport_addr sender_external_address2;
  ASSERT_TRUE(CheckConnectivity(private_addrs_[0],
                                public_addrs_[1],
                                &sender_external_address2));
  ASSERT_FALSE(nr_transport_addr_cmp(&sender_external_address,
                                     &sender_external_address2,
                                     NR_TRANSPORT_ADDR_CMP_MODE_ALL))
    << "addr1: " << sender_external_address.as_string << " addr2: "
    << sender_external_address2.as_string;

  // Verify that the other public IP can now use the pinhole
  ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1],
                                   private_addrs_[0],
                                   sender_external_address2));

  // Send traffic to other public IP, verify that it uses the same pinhole
  nr_transport_addr sender_external_address3;
  ASSERT_TRUE(CheckConnectivity(private_addrs_[0],
                                public_addrs_[2],
                                &sender_external_address3));
  ASSERT_FALSE(nr_transport_addr_cmp(&sender_external_address,
                                     &sender_external_address3,
                                     NR_TRANSPORT_ADDR_CMP_MODE_ALL))
    << "addr1: " << sender_external_address.as_string << " addr2: "
    << sender_external_address3.as_string;

  // Verify that the other public IP can now use the pinhole
  ASSERT_TRUE(CheckConnectivityVia(public_addrs_[2],
                                   private_addrs_[0],
                                   sender_external_address3));
}

TEST_F(TestNrSocketTest, RestrictedCone) {
  RefPtr<TestNat> nat(CreatePrivateAddrs(1));
  nat->filtering_type_ = TestNat::PORT_DEPENDENT;
  nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
  CreatePublicAddrs(2);

  nr_transport_addr sender_external_address;
  // Open pinhole to public IP 0
  ASSERT_TRUE(CheckConnectivity(private_addrs_[0],
                                public_addrs_[0],
                                &sender_external_address));

  // Verify that return traffic works
  ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0],
                                   private_addrs_[0],
                                   sender_external_address));

  // Verify that other public IP cannot use the pinhole
  ASSERT_FALSE(CheckConnectivityVia(public_addrs_[1],
                                    private_addrs_[0],
                                    sender_external_address));

  // Send traffic to other public IP, verify that it uses the same pinhole
  nr_transport_addr sender_external_address2;
  ASSERT_TRUE(CheckConnectivity(private_addrs_[0],
                                public_addrs_[1],
                                &sender_external_address2));
  ASSERT_FALSE(nr_transport_addr_cmp(&sender_external_address,
                                     &sender_external_address2,
                                     NR_TRANSPORT_ADDR_CMP_MODE_ALL))
    << "addr1: " << sender_external_address.as_string << " addr2: "
    << sender_external_address2.as_string;

  // Verify that the other public IP can now use the pinhole
  ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1],
                                   private_addrs_[0],
                                   sender_external_address2));
}

TEST_F(TestNrSocketTest, PortDependentMappingFullCone) {
  RefPtr<TestNat> nat(CreatePrivateAddrs(1));
  nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
  nat->mapping_type_ = TestNat::PORT_DEPENDENT;
  CreatePublicAddrs(2);

  nr_transport_addr sender_external_address0;
  // Open pinhole to public IP 0
  ASSERT_TRUE(CheckConnectivity(private_addrs_[0],
                                public_addrs_[0],
                                &sender_external_address0));

  // Verify that return traffic works
  ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0],
                                   private_addrs_[0],
                                   sender_external_address0));

  // Verify that other public IP can use the pinhole
  ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1],
                                   private_addrs_[0],
                                   sender_external_address0));

  // Send traffic to other public IP, verify that it uses a different pinhole
  nr_transport_addr sender_external_address1;
  ASSERT_TRUE(CheckConnectivity(private_addrs_[0],
                                public_addrs_[1],
                                &sender_external_address1));
  ASSERT_TRUE(nr_transport_addr_cmp(&sender_external_address0,
                                    &sender_external_address1,
                                    NR_TRANSPORT_ADDR_CMP_MODE_ALL))
    << "addr1: " << sender_external_address0.as_string << " addr2: "
    << sender_external_address1.as_string;

  // Verify that return traffic works
  ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1],
                                   private_addrs_[0],
                                   sender_external_address1));

  // Verify that other public IP can use the original pinhole
  ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0],
                                   private_addrs_[0],
                                   sender_external_address1));
}

TEST_F(TestNrSocketTest, Symmetric) {
  RefPtr<TestNat> nat(CreatePrivateAddrs(1));
  nat->filtering_type_ = TestNat::PORT_DEPENDENT;
  nat->mapping_type_ = TestNat::PORT_DEPENDENT;
  CreatePublicAddrs(2);

  nr_transport_addr sender_external_address;
  // Open pinhole to public IP 0
  ASSERT_TRUE(CheckConnectivity(private_addrs_[0],
                                public_addrs_[0],
                                &sender_external_address));

  // Verify that return traffic works
  ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0],
                                   private_addrs_[0],
                                   sender_external_address));

  // Verify that other public IP cannot use the pinhole
  ASSERT_FALSE(CheckConnectivityVia(public_addrs_[1],
                                    private_addrs_[0],
                                    sender_external_address));

  // Send traffic to other public IP, verify that it uses a new pinhole
  nr_transport_addr sender_external_address2;
  ASSERT_TRUE(CheckConnectivity(private_addrs_[0],
                                public_addrs_[1],
                                &sender_external_address2));
  ASSERT_TRUE(nr_transport_addr_cmp(&sender_external_address,
                                    &sender_external_address2,
                                    NR_TRANSPORT_ADDR_CMP_MODE_ALL));

  // Verify that the other public IP can use the new pinhole
  ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1],
                                   private_addrs_[0],
                                   sender_external_address2));
}

TEST_F(TestNrSocketTest, BlockUdp) {
  RefPtr<TestNat> nat(CreatePrivateAddrs(2));
  nat->block_udp_ = true;
  CreatePublicAddrs(1);

  nr_transport_addr sender_external_address;
  ASSERT_FALSE(CheckConnectivity(private_addrs_[0],
                                 public_addrs_[0],
                                 &sender_external_address));

  // Make sure UDP behind the NAT still works
  ASSERT_TRUE(CheckConnectivity(private_addrs_[0],
                                private_addrs_[1]));
  ASSERT_TRUE(CheckConnectivity(private_addrs_[1],
                                private_addrs_[0]));
}

TEST_F(TestNrSocketTest, DenyHairpinning) {
  RefPtr<TestNat> nat(CreatePrivateAddrs(2));
  nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
  nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
  CreatePublicAddrs(1);

  nr_transport_addr sender_external_address;
  // Open pinhole to public IP 0
  ASSERT_TRUE(CheckConnectivity(private_addrs_[0],
                                public_addrs_[0],
                                &sender_external_address));

  // Verify that hairpinning is disallowed
  ASSERT_FALSE(CheckConnectivityVia(private_addrs_[1],
                                    private_addrs_[0],
                                    sender_external_address));
}

TEST_F(TestNrSocketTest, AllowHairpinning) {
  RefPtr<TestNat> nat(CreatePrivateAddrs(2));
  nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
  nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
  nat->mapping_timeout_ = 30000;
  nat->allow_hairpinning_ = true;
  CreatePublicAddrs(1);

  nr_transport_addr sender_external_address;
  // Open pinhole to public IP 0, obtain external address
  ASSERT_TRUE(CheckConnectivity(private_addrs_[0],
                                public_addrs_[0],
                                &sender_external_address));

  // Verify that hairpinning is allowed
  ASSERT_TRUE(CheckConnectivityVia(private_addrs_[1],
                                   private_addrs_[0],
                                   sender_external_address));
}

TEST_F(TestNrSocketTest, FullConeTimeout) {
  RefPtr<TestNat> nat(CreatePrivateAddrs(1));
  nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
  nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
  nat->mapping_timeout_ = 200;
  CreatePublicAddrs(2);

  nr_transport_addr sender_external_address;
  // Open pinhole to public IP 0
  ASSERT_TRUE(CheckConnectivity(private_addrs_[0],
                                public_addrs_[0],
                                &sender_external_address));

  // Verify that return traffic works
  ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0],
                                   private_addrs_[0],
                                   sender_external_address));

  PR_Sleep(201);

  // Verify that return traffic does not work
  ASSERT_FALSE(CheckConnectivityVia(public_addrs_[0],
                                    private_addrs_[0],
                                    sender_external_address));
}

TEST_F(TestNrSocketTest, PublicConnectivityTcp)
{
  CreatePublicAddrs(2, "127.0.0.1", IPPROTO_TCP);

  ASSERT_TRUE(CheckTcpConnectivity(public_addrs_[0], public_addrs_[1]));
}

TEST_F(TestNrSocketTest, PrivateConnectivityTcp) {
  RefPtr<TestNat> nat(CreatePrivateAddrs(2, "127.0.0.1", IPPROTO_TCP));
  nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
  nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;

  ASSERT_TRUE(CheckTcpConnectivity(private_addrs_[0], private_addrs_[1]));
}

TEST_F(TestNrSocketTest, PrivateToPublicConnectivityTcp)
{
  RefPtr<TestNat> nat(CreatePrivateAddrs(1, "127.0.0.1", IPPROTO_TCP));
  nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
  nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
  CreatePublicAddrs(1, "127.0.0.1", IPPROTO_TCP);

  ASSERT_TRUE(CheckTcpConnectivity(private_addrs_[0], public_addrs_[0]));
}

TEST_F(TestNrSocketTest, NoConnectivityBetweenSubnetsTcp)
{
  RefPtr<TestNat> nat1(CreatePrivateAddrs(1, "127.0.0.1", IPPROTO_TCP));
  nat1->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
  nat1->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
  RefPtr<TestNat> nat2(CreatePrivateAddrs(1, "127.0.0.1", IPPROTO_TCP));
  nat2->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
  nat2->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;

  ASSERT_FALSE(CheckTcpConnectivity(private_addrs_[0], private_addrs_[1]));
}

TEST_F(TestNrSocketTest, NoConnectivityPublicToPrivateTcp)
{
  RefPtr<TestNat> nat(CreatePrivateAddrs(1, "127.0.0.1", IPPROTO_TCP));
  nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
  nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
  CreatePublicAddrs(1, "127.0.0.1", IPPROTO_TCP);

  ASSERT_FALSE(CheckTcpConnectivity(public_addrs_[0], private_addrs_[0]));
}
