summaryrefslogtreecommitdiffstats
path: root/dom/media/webrtc/transport/test
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/media/webrtc/transport/test/TestSyncRunnable.cpp56
-rw-r--r--dom/media/webrtc/transport/test/buffered_stun_socket_unittest.cpp245
-rw-r--r--dom/media/webrtc/transport/test/dummysocket.h217
-rw-r--r--dom/media/webrtc/transport/test/gtest_ringbuffer_dumper.h78
-rw-r--r--dom/media/webrtc/transport/test/gtest_utils.h201
-rw-r--r--dom/media/webrtc/transport/test/ice_unittest.cpp4400
-rw-r--r--dom/media/webrtc/transport/test/moz.build104
-rw-r--r--dom/media/webrtc/transport/test/mtransport_test_utils.h57
-rw-r--r--dom/media/webrtc/transport/test/multi_tcp_socket_unittest.cpp501
-rw-r--r--dom/media/webrtc/transport/test/nrappkit_unittest.cpp123
-rw-r--r--dom/media/webrtc/transport/test/proxy_tunnel_socket_unittest.cpp277
-rw-r--r--dom/media/webrtc/transport/test/rlogconnector_unittest.cpp255
-rw-r--r--dom/media/webrtc/transport/test/runnable_utils_unittest.cpp353
-rw-r--r--dom/media/webrtc/transport/test/sctp_unittest.cpp381
-rw-r--r--dom/media/webrtc/transport/test/simpletokenbucket_unittest.cpp114
-rw-r--r--dom/media/webrtc/transport/test/sockettransportservice_unittest.cpp181
-rw-r--r--dom/media/webrtc/transport/test/stunserver.cpp652
-rw-r--r--dom/media/webrtc/transport/test/stunserver.h123
-rw-r--r--dom/media/webrtc/transport/test/test_nr_socket_ice_unittest.cpp409
-rw-r--r--dom/media/webrtc/transport/test/test_nr_socket_unittest.cpp800
-rw-r--r--dom/media/webrtc/transport/test/transport_unittests.cpp1400
-rw-r--r--dom/media/webrtc/transport/test/turn_unittest.cpp432
-rw-r--r--dom/media/webrtc/transport/test/webrtcproxychannel_unittest.cpp754
-rw-r--r--dom/media/webrtc/transport/test_nr_socket.cpp1135
-rw-r--r--dom/media/webrtc/transport/test_nr_socket.h370
25 files changed, 13618 insertions, 0 deletions
diff --git a/dom/media/webrtc/transport/test/TestSyncRunnable.cpp b/dom/media/webrtc/transport/test/TestSyncRunnable.cpp
new file mode 100644
index 0000000000..ca671b4e79
--- /dev/null
+++ b/dom/media/webrtc/transport/test/TestSyncRunnable.cpp
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 12; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+#include "mozilla/SyncRunnable.h"
+
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+nsIThread* gThread = nullptr;
+
+class TestRunnable : public Runnable {
+ public:
+ TestRunnable() : Runnable("TestRunnable"), ran_(false) {}
+
+ NS_IMETHOD Run() override {
+ ran_ = true;
+
+ return NS_OK;
+ }
+
+ bool ran() const { return ran_; }
+
+ private:
+ bool ran_;
+};
+
+class TestSyncRunnable : public ::testing::Test {
+ public:
+ static void SetUpTestCase() {
+ nsresult rv = NS_NewNamedThread("thread", &gThread);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ }
+
+ static void TearDownTestCase() {
+ if (gThread) gThread->Shutdown();
+ }
+};
+
+TEST_F(TestSyncRunnable, TestDispatch) {
+ RefPtr<TestRunnable> r(new TestRunnable());
+ RefPtr<SyncRunnable> s(new SyncRunnable(r));
+ s->DispatchToThread(gThread);
+
+ ASSERT_TRUE(r->ran());
+}
+
+TEST_F(TestSyncRunnable, TestDispatchStatic) {
+ RefPtr<TestRunnable> r(new TestRunnable());
+ SyncRunnable::DispatchToThread(gThread, r);
+ ASSERT_TRUE(r->ran());
+}
diff --git a/dom/media/webrtc/transport/test/buffered_stun_socket_unittest.cpp b/dom/media/webrtc/transport/test/buffered_stun_socket_unittest.cpp
new file mode 100644
index 0000000000..e6a9cd38a2
--- /dev/null
+++ b/dom/media/webrtc/transport/test/buffered_stun_socket_unittest.cpp
@@ -0,0 +1,245 @@
+/* -*- 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
+
+extern "C" {
+#include "nr_api.h"
+#include "nr_socket.h"
+#include "nr_socket_buffered_stun.h"
+#include "transport_addr.h"
+}
+
+#include "stun_msg.h"
+
+#include "dummysocket.h"
+
+#include "nr_socket_prsock.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+using namespace mozilla;
+
+static uint8_t kStunMessage[] = {0x00, 0x01, 0x00, 0x08, 0x21, 0x12, 0xa4,
+ 0x42, 0x9b, 0x90, 0xbe, 0x2c, 0xae, 0x1a,
+ 0x0c, 0xa8, 0xa0, 0xd6, 0x8b, 0x08, 0x80,
+ 0x28, 0x00, 0x04, 0xdb, 0x35, 0x5f, 0xaa};
+static size_t kStunMessageLen = sizeof(kStunMessage);
+
+class BufferedStunSocketTest : public MtransportTest {
+ public:
+ BufferedStunSocketTest()
+ : MtransportTest(), dummy_(nullptr), test_socket_(nullptr) {}
+
+ ~BufferedStunSocketTest() { nr_socket_destroy(&test_socket_); }
+
+ void SetUp() override {
+ MtransportTest::SetUp();
+
+ RefPtr<DummySocket> dummy(new DummySocket());
+
+ int r =
+ nr_socket_buffered_stun_create(dummy->get_nr_socket(), kStunMessageLen,
+ TURN_TCP_FRAMING, &test_socket_);
+ ASSERT_EQ(0, r);
+ dummy_ = std::move(dummy); // Now owned by test_socket_.
+
+ r = nr_str_port_to_transport_addr((char*)"192.0.2.133", 3333, IPPROTO_TCP,
+ &remote_addr_);
+ ASSERT_EQ(0, r);
+
+ r = nr_socket_connect(test_socket_, &remote_addr_);
+ ASSERT_EQ(0, r);
+ }
+
+ nr_socket* socket() { return test_socket_; }
+
+ protected:
+ RefPtr<DummySocket> dummy_;
+ nr_socket* test_socket_;
+ nr_transport_addr remote_addr_;
+};
+
+TEST_F(BufferedStunSocketTest, TestCreate) {}
+
+TEST_F(BufferedStunSocketTest, TestSendTo) {
+ int r = nr_socket_sendto(test_socket_, kStunMessage, kStunMessageLen, 0,
+ &remote_addr_);
+ ASSERT_EQ(0, r);
+
+ dummy_->CheckWriteBuffer(kStunMessage, kStunMessageLen);
+}
+
+TEST_F(BufferedStunSocketTest, TestSendToBuffered) {
+ dummy_->SetWritable(0);
+
+ int r = nr_socket_sendto(test_socket_, kStunMessage, kStunMessageLen, 0,
+ &remote_addr_);
+ ASSERT_EQ(0, r);
+
+ dummy_->CheckWriteBuffer(nullptr, 0);
+
+ dummy_->SetWritable(kStunMessageLen);
+ dummy_->FireWritableCb();
+ dummy_->CheckWriteBuffer(kStunMessage, kStunMessageLen);
+}
+
+TEST_F(BufferedStunSocketTest, TestSendFullThenDrain) {
+ dummy_->SetWritable(0);
+
+ for (;;) {
+ int r = nr_socket_sendto(test_socket_, kStunMessage, kStunMessageLen, 0,
+ &remote_addr_);
+ if (r == R_WOULDBLOCK) break;
+
+ ASSERT_EQ(0, r);
+ }
+
+ // Nothing was written.
+ dummy_->CheckWriteBuffer(nullptr, 0);
+
+ // Now flush.
+ dummy_->SetWritable(kStunMessageLen);
+ dummy_->FireWritableCb();
+ dummy_->ClearWriteBuffer();
+
+ // Verify we can write something.
+ int r = nr_socket_sendto(test_socket_, kStunMessage, kStunMessageLen, 0,
+ &remote_addr_);
+ ASSERT_EQ(0, r);
+
+ // And that it appears.
+ dummy_->CheckWriteBuffer(kStunMessage, kStunMessageLen);
+}
+
+TEST_F(BufferedStunSocketTest, TestSendToPartialBuffered) {
+ dummy_->SetWritable(10);
+
+ int r = nr_socket_sendto(test_socket_, kStunMessage, kStunMessageLen, 0,
+ &remote_addr_);
+ ASSERT_EQ(0, r);
+
+ dummy_->CheckWriteBuffer(kStunMessage, 10);
+ dummy_->ClearWriteBuffer();
+
+ dummy_->SetWritable(kStunMessageLen);
+ dummy_->FireWritableCb();
+ dummy_->CheckWriteBuffer(kStunMessage + 10, kStunMessageLen - 10);
+}
+
+TEST_F(BufferedStunSocketTest, TestSendToReject) {
+ dummy_->SetWritable(0);
+
+ int r = nr_socket_sendto(test_socket_, kStunMessage, kStunMessageLen, 0,
+ &remote_addr_);
+ ASSERT_EQ(0, r);
+
+ dummy_->CheckWriteBuffer(nullptr, 0);
+
+ r = nr_socket_sendto(test_socket_, kStunMessage, kStunMessageLen, 0,
+ &remote_addr_);
+ ASSERT_EQ(R_WOULDBLOCK, r);
+
+ dummy_->CheckWriteBuffer(nullptr, 0);
+}
+
+TEST_F(BufferedStunSocketTest, TestSendToWrongAddr) {
+ nr_transport_addr addr;
+
+ int r = nr_str_port_to_transport_addr((char*)"192.0.2.134", 3333, IPPROTO_TCP,
+ &addr);
+ ASSERT_EQ(0, r);
+
+ r = nr_socket_sendto(test_socket_, kStunMessage, kStunMessageLen, 0, &addr);
+ ASSERT_EQ(R_BAD_DATA, r);
+}
+
+TEST_F(BufferedStunSocketTest, TestReceiveRecvFrom) {
+ dummy_->SetReadBuffer(kStunMessage, kStunMessageLen);
+
+ unsigned char tmp[2048];
+ size_t len;
+ nr_transport_addr addr;
+
+ int r = nr_socket_recvfrom(test_socket_, tmp, sizeof(tmp), &len, 0, &addr);
+ ASSERT_EQ(0, r);
+ ASSERT_EQ(kStunMessageLen, len);
+ ASSERT_EQ(0, memcmp(kStunMessage, tmp, kStunMessageLen));
+ ASSERT_EQ(0, nr_transport_addr_cmp(&addr, &remote_addr_,
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL));
+}
+
+TEST_F(BufferedStunSocketTest, TestReceiveRecvFromPartial) {
+ dummy_->SetReadBuffer(kStunMessage, 15);
+
+ unsigned char tmp[2048];
+ size_t len;
+ nr_transport_addr addr;
+
+ int r = nr_socket_recvfrom(test_socket_, tmp, sizeof(tmp), &len, 0, &addr);
+ ASSERT_EQ(R_WOULDBLOCK, r);
+
+ dummy_->SetReadBuffer(kStunMessage + 15, kStunMessageLen - 15);
+
+ r = nr_socket_recvfrom(test_socket_, tmp, sizeof(tmp), &len, 0, &addr);
+ ASSERT_EQ(0, r);
+ ASSERT_EQ(kStunMessageLen, len);
+ ASSERT_EQ(0, memcmp(kStunMessage, tmp, kStunMessageLen));
+ ASSERT_EQ(0, nr_transport_addr_cmp(&addr, &remote_addr_,
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL));
+
+ r = nr_socket_recvfrom(test_socket_, tmp, sizeof(tmp), &len, 0, &addr);
+ ASSERT_EQ(R_WOULDBLOCK, r);
+}
+
+TEST_F(BufferedStunSocketTest, TestReceiveRecvFromGarbage) {
+ uint8_t garbage[50];
+ memset(garbage, 0xff, sizeof(garbage));
+
+ dummy_->SetReadBuffer(garbage, sizeof(garbage));
+
+ unsigned char tmp[2048];
+ size_t len;
+ nr_transport_addr addr;
+ int r = nr_socket_recvfrom(test_socket_, tmp, sizeof(tmp), &len, 0, &addr);
+ ASSERT_EQ(R_BAD_DATA, r);
+
+ r = nr_socket_recvfrom(test_socket_, tmp, sizeof(tmp), &len, 0, &addr);
+ ASSERT_EQ(R_FAILED, r);
+}
+
+TEST_F(BufferedStunSocketTest, TestReceiveRecvFromTooShort) {
+ dummy_->SetReadBuffer(kStunMessage, kStunMessageLen);
+
+ unsigned char tmp[2048];
+ size_t len;
+ nr_transport_addr addr;
+
+ int r = nr_socket_recvfrom(test_socket_, tmp, kStunMessageLen - 1, &len, 0,
+ &addr);
+ ASSERT_EQ(R_BAD_ARGS, r);
+}
+
+TEST_F(BufferedStunSocketTest, TestReceiveRecvFromReallyLong) {
+ uint8_t garbage[4096];
+ memset(garbage, 0xff, sizeof(garbage));
+ memcpy(garbage, kStunMessage, kStunMessageLen);
+ nr_stun_message_header* hdr =
+ reinterpret_cast<nr_stun_message_header*>(garbage);
+ hdr->length = htons(3000);
+
+ dummy_->SetReadBuffer(garbage, sizeof(garbage));
+
+ unsigned char tmp[4096];
+ size_t len;
+ nr_transport_addr addr;
+
+ int r = nr_socket_recvfrom(test_socket_, tmp, kStunMessageLen - 1, &len, 0,
+ &addr);
+ ASSERT_EQ(R_BAD_DATA, r);
+}
diff --git a/dom/media/webrtc/transport/test/dummysocket.h b/dom/media/webrtc/transport/test/dummysocket.h
new file mode 100644
index 0000000000..6e20a1f7e7
--- /dev/null
+++ b/dom/media/webrtc/transport/test/dummysocket.h
@@ -0,0 +1,217 @@
+/* -*- 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 authors: ekr@rtfm.com; ryan@tokbox.com
+
+#ifndef MTRANSPORT_DUMMY_SOCKET_H_
+#define MTRANSPORT_DUMMY_SOCKET_H_
+
+#include "nr_socket_prsock.h"
+
+extern "C" {
+#include "transport_addr.h"
+}
+
+#include "mediapacket.h"
+#include "mozilla/UniquePtr.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+namespace mozilla {
+
+static UniquePtr<MediaPacket> merge(UniquePtr<MediaPacket> a,
+ UniquePtr<MediaPacket> b) {
+ if (a && a->len() && b && b->len()) {
+ UniquePtr<uint8_t[]> data(new uint8_t[a->len() + b->len()]);
+ memcpy(data.get(), a->data(), a->len());
+ memcpy(data.get() + a->len(), b->data(), b->len());
+
+ UniquePtr<MediaPacket> merged(new MediaPacket);
+ merged->Take(std::move(data), a->len() + b->len());
+ return merged;
+ }
+
+ if (a && a->len()) {
+ return a;
+ }
+
+ if (b && b->len()) {
+ return b;
+ }
+
+ return nullptr;
+}
+
+class DummySocket : public NrSocketBase {
+ public:
+ DummySocket()
+ : writable_(UINT_MAX),
+ write_buffer_(nullptr),
+ readable_(UINT_MAX),
+ read_buffer_(nullptr),
+ cb_(nullptr),
+ cb_arg_(nullptr),
+ self_(nullptr) {}
+
+ // the nr_socket APIs
+ virtual int create(nr_transport_addr* addr) override { return 0; }
+
+ virtual int sendto(const void* msg, size_t len, int flags,
+ const nr_transport_addr* to) override {
+ MOZ_CRASH();
+ return 0;
+ }
+
+ virtual int recvfrom(void* buf, size_t maxlen, size_t* len, int flags,
+ nr_transport_addr* from) override {
+ MOZ_CRASH();
+ return 0;
+ }
+
+ virtual int getaddr(nr_transport_addr* addrp) override {
+ MOZ_CRASH();
+ return 0;
+ }
+
+ virtual void close() override {}
+
+ virtual int connect(const nr_transport_addr* addr) override {
+ nr_transport_addr_copy(&connect_addr_, addr);
+ return 0;
+ }
+
+ virtual int listen(int backlog) override { return 0; }
+
+ virtual int accept(nr_transport_addr* addrp, nr_socket** sockp) override {
+ return 0;
+ }
+
+ virtual int write(const void* msg, size_t len, size_t* written) override {
+ size_t to_write = std::min(len, writable_);
+
+ if (to_write) {
+ UniquePtr<MediaPacket> msgbuf(new MediaPacket);
+ msgbuf->Copy(static_cast<const uint8_t*>(msg), to_write);
+ write_buffer_ = merge(std::move(write_buffer_), std::move(msgbuf));
+ }
+
+ *written = to_write;
+
+ return 0;
+ }
+
+ virtual int read(void* buf, size_t maxlen, size_t* len) override {
+ if (!read_buffer_.get()) {
+ return R_WOULDBLOCK;
+ }
+
+ size_t to_read = std::min(read_buffer_->len(), std::min(maxlen, readable_));
+
+ memcpy(buf, read_buffer_->data(), to_read);
+ *len = to_read;
+
+ if (to_read < read_buffer_->len()) {
+ MediaPacket* newPacket = new MediaPacket;
+ newPacket->Copy(read_buffer_->data() + to_read,
+ read_buffer_->len() - to_read);
+ read_buffer_.reset(newPacket);
+ } else {
+ read_buffer_.reset();
+ }
+
+ return 0;
+ }
+
+ // Implementations of the async_event APIs.
+ // These are no-ops because we handle scheduling manually
+ // for test purposes.
+ virtual int async_wait(int how, NR_async_cb cb, void* cb_arg, char* function,
+ int line) override {
+ EXPECT_EQ(nullptr, cb_);
+ cb_ = cb;
+ cb_arg_ = cb_arg;
+
+ return 0;
+ }
+
+ virtual int cancel(int how) override {
+ cb_ = nullptr;
+ cb_arg_ = nullptr;
+
+ return 0;
+ }
+
+ // Read/Manipulate the current state.
+ void CheckWriteBuffer(const uint8_t* data, size_t len) {
+ if (!len) {
+ EXPECT_EQ(nullptr, write_buffer_.get());
+ } else {
+ EXPECT_NE(nullptr, write_buffer_.get());
+ ASSERT_EQ(len, write_buffer_->len());
+ ASSERT_EQ(0, memcmp(data, write_buffer_->data(), len));
+ }
+ }
+
+ void ClearWriteBuffer() { write_buffer_.reset(); }
+
+ void SetWritable(size_t val) { writable_ = val; }
+
+ void FireWritableCb() {
+ NR_async_cb cb = cb_;
+ void* cb_arg = cb_arg_;
+
+ cb_ = nullptr;
+ cb_arg_ = nullptr;
+
+ cb(this, NR_ASYNC_WAIT_WRITE, cb_arg);
+ }
+
+ void SetReadBuffer(const uint8_t* data, size_t len) {
+ EXPECT_EQ(nullptr, write_buffer_.get());
+ read_buffer_.reset(new MediaPacket);
+ read_buffer_->Copy(data, len);
+ }
+
+ void ClearReadBuffer() { read_buffer_.reset(); }
+
+ void SetReadable(size_t val) { readable_ = val; }
+
+ nr_socket* get_nr_socket() {
+ if (!self_) {
+ int r = nr_socket_create_int(this, vtbl(), &self_);
+ AddRef();
+ if (r) return nullptr;
+ }
+
+ return self_;
+ }
+
+ nr_transport_addr* get_connect_addr() { return &connect_addr_; }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DummySocket, override);
+
+ private:
+ ~DummySocket() = default;
+
+ DISALLOW_COPY_ASSIGN(DummySocket);
+
+ size_t writable_; // Amount we allow someone to write.
+ UniquePtr<MediaPacket> write_buffer_;
+ size_t readable_; // Amount we allow someone to read.
+ UniquePtr<MediaPacket> read_buffer_;
+
+ NR_async_cb cb_;
+ void* cb_arg_;
+ nr_socket* self_;
+
+ nr_transport_addr connect_addr_;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/transport/test/gtest_ringbuffer_dumper.h b/dom/media/webrtc/transport/test/gtest_ringbuffer_dumper.h
new file mode 100644
index 0000000000..25e85c2155
--- /dev/null
+++ b/dom/media/webrtc/transport/test/gtest_ringbuffer_dumper.h
@@ -0,0 +1,78 @@
+/* -*- 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
+
+#ifndef gtest_ringbuffer_dumper_h__
+#define gtest_ringbuffer_dumper_h__
+
+#include "mozilla/SyncRunnable.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+
+#include "mtransport_test_utils.h"
+#include "runnable_utils.h"
+#include "rlogconnector.h"
+
+using mozilla::RLogConnector;
+using mozilla::WrapRunnable;
+
+namespace test {
+class RingbufferDumper : public ::testing::EmptyTestEventListener {
+ public:
+ explicit RingbufferDumper(MtransportTestUtils* test_utils)
+ : test_utils_(test_utils) {}
+
+ void ClearRingBuffer_s() {
+ RLogConnector::CreateInstance();
+ // Set limit to zero to clear the ringbuffer
+ RLogConnector::GetInstance()->SetLogLimit(0);
+ RLogConnector::GetInstance()->SetLogLimit(UINT32_MAX);
+ }
+
+ void DestroyRingBuffer_s() { RLogConnector::DestroyInstance(); }
+
+ void DumpRingBuffer_s() {
+ std::deque<std::string> logs;
+ // Get an unlimited number of log lines, with no filter
+ RLogConnector::GetInstance()->GetAny(0, &logs);
+ for (auto l = logs.begin(); l != logs.end(); ++l) {
+ std::cout << *l << std::endl;
+ }
+ ClearRingBuffer_s();
+ }
+
+ virtual void OnTestStart(const ::testing::TestInfo& testInfo) override {
+ mozilla::SyncRunnable::DispatchToThread(
+ test_utils_->sts_target(),
+ WrapRunnable(this, &RingbufferDumper::ClearRingBuffer_s));
+ }
+
+ virtual void OnTestEnd(const ::testing::TestInfo& testInfo) override {
+ mozilla::SyncRunnable::DispatchToThread(
+ test_utils_->sts_target(),
+ WrapRunnable(this, &RingbufferDumper::DestroyRingBuffer_s));
+ }
+
+ // Called after a failed assertion or a SUCCEED() invocation.
+ virtual void OnTestPartResult(
+ const ::testing::TestPartResult& testResult) override {
+ if (testResult.failed()) {
+ // Dump (and empty) the RLogConnector
+ mozilla::SyncRunnable::DispatchToThread(
+ test_utils_->sts_target(),
+ WrapRunnable(this, &RingbufferDumper::DumpRingBuffer_s));
+ }
+ }
+
+ private:
+ MtransportTestUtils* test_utils_;
+};
+
+} // namespace test
+
+#endif // gtest_ringbuffer_dumper_h__
diff --git a/dom/media/webrtc/transport/test/gtest_utils.h b/dom/media/webrtc/transport/test/gtest_utils.h
new file mode 100644
index 0000000000..40c2570ea1
--- /dev/null
+++ b/dom/media/webrtc/transport/test/gtest_utils.h
@@ -0,0 +1,201 @@
+/* -*- 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/. */
+
+// Utilities to wrap gtest, based on libjingle's gunit
+
+// Some sections of this code are under the following license:
+
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Original author: ekr@rtfm.com
+#ifndef gtest_utils__h__
+#define gtest_utils__h__
+
+#include <iostream>
+
+#include "nspr.h"
+#include "prinrval.h"
+#include "prthread.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+
+#include "gtest_ringbuffer_dumper.h"
+#include "mtransport_test_utils.h"
+#include "nss.h"
+#include "ssl.h"
+
+extern "C" {
+#include "registry.h"
+#include "transport_addr.h"
+}
+
+// Wait up to timeout seconds for expression to be true
+#define WAIT(expression, timeout) \
+ do { \
+ for (PRIntervalTime start = PR_IntervalNow(); \
+ !(expression) && !((PR_IntervalNow() - start) > \
+ PR_MillisecondsToInterval(timeout));) { \
+ PR_Sleep(10); \
+ } \
+ } while (0)
+
+// Same as GTEST_WAIT, but stores the result in res. Used when
+// you also want the result of expression but wish to avoid
+// double evaluation.
+#define WAIT_(expression, timeout, res) \
+ do { \
+ for (PRIntervalTime start = PR_IntervalNow(); \
+ !(res = (expression)) && !((PR_IntervalNow() - start) > \
+ PR_MillisecondsToInterval(timeout));) { \
+ PR_Sleep(10); \
+ } \
+ } while (0)
+
+#define ASSERT_TRUE_WAIT(expression, timeout) \
+ do { \
+ bool res; \
+ WAIT_(expression, timeout, res); \
+ ASSERT_TRUE(res); \
+ } while (0)
+
+#define EXPECT_TRUE_WAIT(expression, timeout) \
+ do { \
+ bool res; \
+ WAIT_(expression, timeout, res); \
+ EXPECT_TRUE(res); \
+ } while (0)
+
+#define ASSERT_EQ_WAIT(expected, actual, timeout) \
+ do { \
+ WAIT(expected == actual, timeout); \
+ ASSERT_EQ(expected, actual); \
+ } while (0)
+
+using test::RingbufferDumper;
+
+class MtransportTest : public ::testing::Test {
+ public:
+ MtransportTest() : test_utils_(nullptr), dumper_(nullptr) {}
+
+ void SetUp() override {
+ test_utils_ = new MtransportTestUtils();
+ NSS_NoDB_Init(nullptr);
+ NSS_SetDomesticPolicy();
+
+ NR_reg_init(NR_REG_MODE_LOCAL);
+
+ // Attempt to load env vars used by tests.
+ GetEnvironment("TURN_SERVER_ADDRESS", turn_server_);
+ GetEnvironment("TURN_SERVER_USER", turn_user_);
+ GetEnvironment("TURN_SERVER_PASSWORD", turn_password_);
+ GetEnvironment("STUN_SERVER_ADDRESS", stun_server_address_);
+ GetEnvironment("STUN_SERVER_HOSTNAME", stun_server_hostname_);
+
+ std::string disable_non_local;
+ GetEnvironment("MOZ_DISABLE_NONLOCAL_CONNECTIONS", disable_non_local);
+ std::string upload_dir;
+ GetEnvironment("MOZ_UPLOAD_DIR", upload_dir);
+
+ if ((!disable_non_local.empty() && disable_non_local != "0") ||
+ !upload_dir.empty()) {
+ // We're assuming that MOZ_UPLOAD_DIR is only set on tbpl;
+ // MOZ_DISABLE_NONLOCAL_CONNECTIONS probably should be set when running
+ // the cpp unit-tests, but is not presently.
+ stun_server_address_ = "";
+ stun_server_hostname_ = "";
+ turn_server_ = "";
+ }
+
+ // Some tests are flaky and need to check if they're supposed to run.
+ webrtc_enabled_ = CheckEnvironmentFlag("MOZ_WEBRTC_TESTS");
+
+ ::testing::TestEventListeners& listeners =
+ ::testing::UnitTest::GetInstance()->listeners();
+
+ dumper_ = new RingbufferDumper(test_utils_);
+ listeners.Append(dumper_);
+ }
+
+ void TearDown() override {
+ ::testing::UnitTest::GetInstance()->listeners().Release(dumper_);
+ delete dumper_;
+ delete test_utils_;
+ }
+
+ void GetEnvironment(const char* aVar, std::string& out) {
+ char* value = getenv(aVar);
+ if (value) {
+ out = value;
+ }
+ }
+
+ bool CheckEnvironmentFlag(const char* aVar) {
+ std::string value;
+ GetEnvironment(aVar, value);
+ return value == "1";
+ }
+
+ bool WarnIfTurnNotConfigured() const {
+ bool configured =
+ !turn_server_.empty() && !turn_user_.empty() && !turn_password_.empty();
+
+ if (configured) {
+ nr_transport_addr addr;
+ if (nr_str_port_to_transport_addr(turn_server_.c_str(), 3478, IPPROTO_UDP,
+ &addr)) {
+ printf(
+ "Invalid TURN_SERVER_ADDRESS \"%s\". Only IP numbers supported.\n",
+ turn_server_.c_str());
+ configured = false;
+ }
+ } else {
+ printf(
+ "Set TURN_SERVER_ADDRESS, TURN_SERVER_USER, and "
+ "TURN_SERVER_PASSWORD\n"
+ "environment variables to run this test\n");
+ }
+
+ return !configured;
+ }
+
+ MtransportTestUtils* test_utils_;
+ RingbufferDumper* dumper_;
+
+ std::string turn_server_;
+ std::string turn_user_;
+ std::string turn_password_;
+ std::string stun_server_address_;
+ std::string stun_server_hostname_;
+
+ bool webrtc_enabled_;
+};
+#endif
diff --git a/dom/media/webrtc/transport/test/ice_unittest.cpp b/dom/media/webrtc/transport/test/ice_unittest.cpp
new file mode 100644
index 0000000000..d87fa0b0da
--- /dev/null
+++ b/dom/media/webrtc/transport/test/ice_unittest.cpp
@@ -0,0 +1,4400 @@
+/* -*- 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 "ssl.h"
+
+#include "mozilla/Preferences.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOM.h"
+
+extern "C" {
+#include "r_types.h"
+#include "async_wait.h"
+#include "async_timer.h"
+#include "r_data.h"
+#include "util.h"
+#include "r_time.h"
+}
+
+#include "ice_ctx.h"
+#include "ice_peer_ctx.h"
+#include "ice_media_stream.h"
+
+#include "nricectx.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 "nsISocketFilter.h"
+#include "mozilla/net/DNS.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*)"stun.l.google.com");
+const std::string kBogusStunServerHostname(
+ (char*)"stun-server-nonexistent.invalid");
+const uint16_t kDefaultStunServerPort = 19305;
+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() {}
+
+ void SetUp() override {
+ MtransportTest::SetUp();
+
+ stun_server_hostname_ = kDefaultStunServerHostname;
+ // 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);
+ ASSERT_TRUE(!stun_server_address_.empty());
+ }
+
+ // Make sure NrIceCtx is in a testable state.
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNM(&NrIceCtx::internal_DeinitializeGlobal));
+
+ // NB: NrIceCtx::internal_DeinitializeGlobal destroys the RLogConnector
+ // singleton.
+ RLogConnector::CreateInstance();
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNM(&TestStunServer::GetInstance, AF_INET));
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNM(&TestStunServer::GetInstance, AF_INET6));
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNM(&TestStunTcpServer::GetInstance, AF_INET));
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNM(&TestStunTcpServer::GetInstance, AF_INET6));
+ }
+
+ void TearDown() override {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNM(&NrIceCtx::internal_DeinitializeGlobal));
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNM(&TestStunServer::ShutdownInstance));
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNM(&TestStunTcpServer::ShutdownInstance));
+
+ RLogConnector::DestroyInstance();
+
+ MtransportTest::TearDown();
+ }
+};
+
+enum TrickleMode { TRICKLE_NONE, TRICKLE_SIMULATE, TRICKLE_REAL };
+
+enum ConsentStatus { CONSENT_FRESH, CONSENT_STALE, CONSENT_EXPIRED };
+
+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 (const auto& pair : pairs) {
+ if (pair.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,
+ const std::string& ufrag,
+ MtransportTestUtils* utils)
+ : peer_(peer),
+ stream_(stream),
+ candidate_(candidate),
+ ufrag_(ufrag),
+ timer_handle_(nullptr),
+ test_utils_(utils) {}
+
+ ~SchedulableTrickleCandidate() {
+ if (timer_handle_) NR_async_timer_cancel(timer_handle_);
+ }
+
+ void Schedule(unsigned int ms) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &SchedulableTrickleCandidate::Schedule_s, ms));
+ }
+
+ 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_; }
+
+ 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_;
+ std::string ufrag_;
+ void* timer_handle_;
+ MtransportTestUtils* test_utils_;
+
+ DISALLOW_COPY_ASSIGN(SchedulableTrickleCandidate);
+};
+
+class IceTestPeer : public sigslot::has_slots<> {
+ public:
+ IceTestPeer(const std::string& name, MtransportTestUtils* utils, bool offerer,
+ const NrIceCtx::Config& config)
+ : name_(name),
+ ice_ctx_(NrIceCtx::Create(name)),
+ offerer_(offerer),
+ candidates_(),
+ stream_counter_(0),
+ 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),
+ simulate_ice_lite_(false),
+ nat_(new TestNat),
+ test_utils_(utils) {
+ ice_ctx_->SignalGatheringStateChange.connect(
+ this, &IceTestPeer::GatheringStateChange);
+ ice_ctx_->SignalConnectionStateChange.connect(
+ this, &IceTestPeer::ConnectionStateChange);
+
+ ice_ctx_->SetIceConfig(config);
+
+ consent_timestamp_.tv_sec = 0;
+ consent_timestamp_.tv_usec = 0;
+ int r = ice_ctx_->SetNat(nat_);
+ (void)r;
+ MOZ_ASSERT(!r);
+ }
+
+ ~IceTestPeer() {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(this, &IceTestPeer::Shutdown));
+
+ // Give the ICE destruction callback time to fire before
+ // we destroy the resolver.
+ PR_Sleep(1000);
+ }
+
+ std::string MakeTransportId(size_t index) const {
+ char id[100];
+ snprintf(id, sizeof(id), "%s:stream%d", name_.c_str(), (int)index);
+ return id;
+ }
+
+ void SetIceCredentials_s(NrIceMediaStream& stream) {
+ static size_t counter = 0;
+ std::ostringstream prefix;
+ prefix << name_ << "-" << counter++;
+ std::string ufrag = prefix.str() + "-ufrag";
+ std::string pwd = prefix.str() + "-pwd";
+ if (mIceCredentials.count(stream.GetId())) {
+ mOldIceCredentials[stream.GetId()] = mIceCredentials[stream.GetId()];
+ }
+ mIceCredentials[stream.GetId()] = std::make_pair(ufrag, pwd);
+ stream.SetIceCredentials(ufrag, pwd);
+ }
+
+ void AddStream_s(int components) {
+ std::string id = MakeTransportId(stream_counter_++);
+
+ RefPtr<NrIceMediaStream> stream =
+ ice_ctx_->CreateStream(id, id, components);
+
+ ASSERT_TRUE(stream);
+ SetIceCredentials_s(*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_->SyncDispatchToSTS(
+ WrapRunnable(this, &IceTestPeer::AddStream_s, components));
+ }
+
+ void RemoveStream_s(size_t index) {
+ ice_ctx_->DestroyStream(MakeTransportId(index));
+ }
+
+ void RemoveStream(size_t index) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &IceTestPeer::RemoveStream_s, index));
+ }
+
+ RefPtr<NrIceMediaStream> GetStream_s(size_t index) {
+ std::string id = MakeTransportId(index);
+ return ice_ctx_->GetStream(id);
+ }
+
+ 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_->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_->SetTurnServers(turn_servers)));
+ }
+
+ void SetTurnServers(const std::vector<NrIceTurnServer> servers) {
+ ASSERT_TRUE(NS_SUCCEEDED(ice_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_->SetResolver(fake_resolver_.AllocateResolver())));
+ }
+
+ void SetDNSResolver() {
+ ASSERT_TRUE(NS_SUCCEEDED(dns_resolver_->Init()));
+ ASSERT_TRUE(
+ NS_SUCCEEDED(ice_ctx_->SetResolver(dns_resolver_->AllocateResolver())));
+ }
+
+ void Gather(bool default_route_only = false,
+ bool obfuscate_host_addresses = false) {
+ nsresult res;
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&res, ice_ctx_, &NrIceCtx::StartGathering,
+ default_route_only, obfuscate_host_addresses));
+
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ }
+
+ void SetCtxFlags(bool default_route_only) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(ice_ctx_, &NrIceCtx::SetCtxFlags, default_route_only));
+ }
+
+ nsTArray<NrIceStunAddr> GetStunAddrs() { return ice_ctx_->GetStunAddrs(); }
+
+ void SetStunAddrs(const nsTArray<NrIceStunAddr>& addrs) {
+ ice_ctx_->SetStunAddrs(addrs);
+ }
+
+ void UseNat() { nat_->enabled_ = true; }
+
+ void SetTimerDivider(int div) { ice_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_->GetGlobalAttributes());
+ if (simulate_ice_lite_) {
+ attrs.push_back("ice-lite");
+ }
+ return attrs;
+ }
+
+ std::vector<std::string> GetAttributes(size_t stream) {
+ std::vector<std::string> v;
+
+ RUN_ON_THREAD(
+ test_utils_->sts_target(),
+ WrapRunnableRet(&v, this, &IceTestPeer::GetAttributes_s, stream));
+
+ return v;
+ }
+
+ std::string FilterCandidate(const std::string& candidate) {
+ if (candidate_filter_) {
+ return candidate_filter_(candidate);
+ }
+ return candidate;
+ }
+
+ std::vector<std::string> GetAttributes_s(size_t index) {
+ std::vector<std::string> attributes;
+
+ auto stream = GetStream_s(index);
+ if (!stream) {
+ EXPECT_TRUE(false) << "No such stream " << index;
+ return attributes;
+ }
+
+ std::vector<std::string> attributes_in = stream->GetAttributes();
+
+ for (const auto& attribute : attributes_in) {
+ if (attribute.find("candidate:") != std::string::npos) {
+ std::string candidate(FilterCandidate(attribute));
+ if (!candidate.empty()) {
+ std::cerr << name_ << " Returning candidate: " << candidate
+ << std::endl;
+ attributes.push_back(candidate);
+ }
+ } else {
+ attributes.push_back(attribute);
+ }
+ }
+
+ return attributes;
+ }
+
+ 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> attributes = GetAttributes(stream);
+
+ int host_net = 0;
+ for (const auto& a : attributes) {
+ if (a.find("typ host") != std::string::npos) {
+ nr_transport_addr addr;
+ std::vector<std::string> tokens = split(a, ' ');
+ 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 index) {
+ auto media_stream = GetStream_s(index);
+ if (!media_stream) {
+ EXPECT_TRUE(false) << "No such stream " << index;
+ return false;
+ }
+ return media_stream->state() == NrIceMediaStream::ICE_OPEN;
+ }
+ bool is_ready(size_t stream) {
+ bool result;
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&result, this, &IceTestPeer::is_ready_s, stream));
+ 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_->SyncDispatchToSTS(
+ WrapRunnable(this, &IceTestPeer::RestartIce_s));
+ }
+
+ void RestartIce_s() {
+ for (auto& stream : ice_ctx_->GetStreams()) {
+ SetIceCredentials_s(*stream);
+ }
+ // 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 RollbackIceRestart() {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &IceTestPeer::RollbackIceRestart_s));
+ }
+
+ void RollbackIceRestart_s() {
+ for (auto& stream : ice_ctx_->GetStreams()) {
+ mIceCredentials[stream->GetId()] = mOldIceCredentials[stream->GetId()];
+ }
+ }
+
+ // 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_->ParseGlobalAttributes(remote->GetGlobalAttributes());
+ ASSERT_FALSE(remote->simulate_ice_lite_ &&
+ (ice_ctx_->GetControlling() == NrIceCtx::ICE_CONTROLLED));
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+
+ for (size_t i = 0; i < stream_counter_; ++i) {
+ auto aStream = GetStream_s(i);
+ if (aStream) {
+ std::vector<std::string> attributes = remote->GetAttributes(i);
+
+ for (auto it = attributes.begin(); it != attributes.end();) {
+ if (trickle_mode == TRICKLE_SIMULATE &&
+ it->find("candidate:") != std::string::npos) {
+ std::cerr << name_ << " Deferring remote candidate: " << *it
+ << std::endl;
+ attributes.erase(it);
+ } else {
+ std::cerr << name_ << " Adding remote attribute: " + *it
+ << std::endl;
+ ++it;
+ }
+ }
+ auto credentials = mIceCredentials[aStream->GetId()];
+ res = aStream->ConnectToPeer(credentials.first, credentials.second,
+ attributes);
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ }
+ }
+
+ if (start) {
+ ice_ctx_->SetControlling(offerer_ ? NrIceCtx::ICE_CONTROLLING
+ : NrIceCtx::ICE_CONTROLLED);
+ // Now start checks
+ res = ice_ctx_->StartChecks();
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ }
+ }
+
+ void Connect(IceTestPeer* remote, TrickleMode trickle_mode,
+ bool start = true) {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(this, &IceTestPeer::Connect_s,
+ remote, trickle_mode, start));
+ }
+
+ 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|
+
+ std::vector<SchedulableTrickleCandidate*>& candidates =
+ ControlTrickle(stream);
+
+ for (auto& candidate : candidates) {
+ candidate->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> attributes = remote_->GetAttributes(stream);
+
+ for (const auto& attribute : attributes) {
+ if (attribute.find("candidate:") != std::string::npos) {
+ controlled_trickle_candidates_[stream].push_back(
+ new SchedulableTrickleCandidate(this, stream, attribute, "",
+ test_utils_));
+ }
+ }
+
+ return controlled_trickle_candidates_[stream];
+ }
+
+ nsresult TrickleCandidate_s(const std::string& candidate,
+ const std::string& ufrag, size_t index) {
+ auto stream = GetStream_s(index);
+ if (!stream) {
+ // stream might have gone away before the trickle timer popped
+ return NS_OK;
+ }
+ return stream->ParseTrickleCandidate(candidate, ufrag, "");
+ }
+
+ 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 (const auto& stream : ice_ctx_->GetStreams()) {
+ for (size_t j = 0; j < stream->components(); ++j) {
+ std::cerr << name_ << " Stream " << stream->GetId() << " component "
+ << j + 1 << std::endl;
+
+ UniquePtr<NrIceCandidate> local;
+ UniquePtr<NrIceCandidate> remote;
+
+ nsresult res = stream->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_->SyncDispatchToSTS(
+ WrapRunnable(this, &IceTestPeer::DumpAndCheckActiveCandidates_s));
+ }
+
+ void Close() {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(ice_ctx_, &NrIceCtx::destroy_peer_ctx));
+ }
+
+ void Shutdown() {
+ std::cerr << name_ << " Shutdown" << std::endl;
+ shutting_down_ = true;
+ for (auto& controlled_trickle_candidate : controlled_trickle_candidates_) {
+ for (auto& cand : controlled_trickle_candidate.second) {
+ delete cand;
+ }
+ }
+
+ ice_ctx_->Destroy();
+ ice_ctx_ = nullptr;
+
+ if (remote_) {
+ remote_->UnsetRemote();
+ remote_ = nullptr;
+ }
+ }
+
+ void UnsetRemote() { remote_ = nullptr; }
+
+ void StartChecks() {
+ nsresult res;
+
+ test_utils_->SyncDispatchToSTS(WrapRunnableRet(
+ &res, ice_ctx_, &NrIceCtx::SetControlling,
+ offerer_ ? NrIceCtx::ICE_CONTROLLING : NrIceCtx::ICE_CONTROLLED));
+ // Now start checks
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&res, ice_ctx_, &NrIceCtx::StartChecks));
+ 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_ << " ATTRIBUTES:" << std::endl;
+ for (const auto& stream : ice_ctx_->GetStreams()) {
+ std::cerr << "Stream " << stream->GetId() << std::endl;
+
+ std::vector<std::string> attributes = stream->GetAttributes();
+
+ for (const auto& attribute : attributes) {
+ std::cerr << attribute << std::endl;
+ }
+ }
+ std::cerr << std::endl;
+ }
+
+ void CandidateInitialized(NrIceMediaStream* stream,
+ const std::string& raw_candidate,
+ const std::string& ufrag,
+ const std::string& mdns_addr,
+ const std::string& actual_addr) {
+ 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 < stream_counter_; ++i) {
+ if (GetStream_s(i) == stream) {
+ ASSERT_GT(remote_->stream_counter_, i);
+ nsresult res = remote_->GetStream_s(i)->ParseTrickleCandidate(
+ candidate, ufrag, "");
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ return;
+ }
+ }
+ ADD_FAILURE() << "No matching stream found for " << stream;
+ }
+ }
+
+ nsresult GetCandidatePairs_s(size_t stream_index,
+ std::vector<NrIceCandidatePair>* pairs) {
+ MOZ_ASSERT(pairs);
+ auto stream = GetStream_s(stream_index);
+ if (!stream) {
+ // Is there a better error for "no such index"?
+ ADD_FAILURE() << "No such media stream index: " << stream_index;
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return stream->GetCandidatePairs(pairs);
+ }
+
+ nsresult GetCandidatePairs(size_t stream_index,
+ std::vector<NrIceCandidatePair>* pairs) {
+ nsresult v;
+ test_utils_->SyncDispatchToSTS(WrapRunnableRet(
+ &v, this, &IceTestPeer::GetCandidatePairs_s, stream_index, pairs));
+ 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 (auto& pair : pairs) {
+ DumpCandidatePair(pair);
+ }
+ std::cerr << "]" << std::endl;
+ }
+
+ void DumpCandidatePairs_s() {
+ std::cerr << "Dumping candidate pairs for all streams [" << std::endl;
+ for (const auto& stream : ice_ctx_->GetStreams()) {
+ DumpCandidatePairs_s(stream.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;
+ }
+ 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 (auto& added_pair : added_pairs) {
+ std::cerr << "Found new candidate pair." << std::endl;
+ DumpCandidatePair(added_pair);
+ }
+
+ for (auto& removed_pair : removed_pairs) {
+ std::cerr << "Pre-existing candidate pair is now missing:" << std::endl;
+ DumpCandidatePair(removed_pair);
+ }
+
+ 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) {
+ auto media_stream = GetStream_s(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) {
+ auto media_stream = GetStream_s(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,
+ const std::string& mdns_addr) {
+ auto media_stream = GetStream_s(i);
+ ASSERT_TRUE(media_stream.get())
+ << "No such stream " << i;
+ media_stream->ParseTrickleCandidate(candidate, "", mdns_addr);
+ }
+
+ void ParseCandidate(size_t i, const std::string& candidate,
+ const std::string& mdns_addr) {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(
+ this, &IceTestPeer::ParseCandidate_s, i, candidate, mdns_addr));
+ }
+
+ void DisableComponent_s(size_t index, int component_id) {
+ ASSERT_LT(index, stream_counter_);
+ auto stream = GetStream_s(index);
+ ASSERT_TRUE(stream.get())
+ << "No such stream " << index;
+ nsresult res = stream->DisableComponent(component_id);
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ }
+
+ void DisableComponent(size_t stream, int component_id) {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(
+ this, &IceTestPeer::DisableComponent_s, stream, component_id));
+ }
+
+ void AssertConsentRefresh_s(size_t index, int component_id,
+ ConsentStatus status) {
+ ASSERT_LT(index, stream_counter_);
+ auto stream = GetStream_s(index);
+ ASSERT_TRUE(stream.get())
+ << "No such stream " << index;
+ bool can_send;
+ struct timeval timestamp;
+ nsresult res =
+ 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_->SyncDispatchToSTS(
+ WrapRunnable(this, &IceTestPeer::AssertConsentRefresh_s, 0, 1, status));
+ }
+
+ void ChangeNetworkState_s(bool online) {
+ ice_ctx_->UpdateNetworkState(online);
+ }
+
+ void ChangeNetworkStateToOffline() {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &IceTestPeer::ChangeNetworkState_s, false));
+ }
+
+ void ChangeNetworkStateToOnline() {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &IceTestPeer::ChangeNetworkState_s, true));
+ }
+
+ void SetControlling(NrIceCtx::Controlling controlling) {
+ nsresult res;
+ test_utils_->SyncDispatchToSTS(WrapRunnableRet(
+ &res, ice_ctx_, &NrIceCtx::SetControlling, controlling));
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ }
+
+ NrIceCtx::Controlling GetControlling() { return ice_ctx_->GetControlling(); }
+
+ void SetTiebreaker(uint64_t tiebreaker) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &IceTestPeer::SetTiebreaker_s, tiebreaker));
+ }
+
+ void SetTiebreaker_s(uint64_t tiebreaker) {
+ ice_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_->SyncDispatchToSTS(WrapRunnableRet(
+ &rv, this, &IceTestPeer::GetDefaultCandidate_s, stream, cand));
+
+ return rv;
+ }
+
+ nsresult GetDefaultCandidate_s(unsigned int index, NrIceCandidate* cand) {
+ return GetStream_s(index)->GetDefaultCandidate(1, cand);
+ }
+
+ private:
+ std::string name_;
+ RefPtr<NrIceCtx> ice_ctx_;
+ bool offerer_;
+ 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_;
+ std::map<std::string, std::pair<std::string, std::string>> mIceCredentials;
+ std::map<std::string, std::pair<std::string, std::string>> mOldIceCredentials;
+ size_t stream_counter_;
+ 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_;
+ bool simulate_ice_lite_;
+ RefPtr<mozilla::TestNat> nat_;
+ MtransportTestUtils* test_utils_;
+};
+
+void SchedulableTrickleCandidate::Trickle() {
+ timer_handle_ = nullptr;
+ nsresult res = peer_->TrickleCandidate_s(candidate_, ufrag_, 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_->SyncDispatchToSTS(WrapRunnable(
+ TestStunServer::GetInstance(AF_INET), &TestStunServer::Reset));
+ if (TestStunServer::GetInstance(AF_INET6)) {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(
+ TestStunServer::GetInstance(AF_INET6), &TestStunServer::Reset));
+ }
+ }
+
+ void TearDown() override {
+ peer_ = nullptr;
+ StunTest::TearDown();
+ }
+
+ void EnsurePeer() {
+ if (!peer_) {
+ peer_ =
+ MakeUnique<IceTestPeer>("P1", test_utils_, true, NrIceCtx::Config());
+ }
+ }
+
+ void Gather(unsigned int waitTime = kDefaultTimeout,
+ bool default_route_only = false,
+ bool obfuscate_host_addresses = false) {
+ EnsurePeer();
+ peer_->Gather(default_route_only, obfuscate_host_addresses);
+
+ 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()));
+
+ if (family == AF_INET6 && !fqdn.empty()) {
+ stun_servers->back().SetUseIPv6IfFqdn();
+ }
+ }
+
+ 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();
+ 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();
+ 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> attributes = peer_->GetAttributes(stream);
+ for (auto& attribute : attributes) {
+ if (std::string::npos != attribute.find(match)) {
+ if (!match2.length() || std::string::npos != attribute.find(match2)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ void DumpAttributes(unsigned int stream) {
+ std::vector<std::string> attributes = peer_->GetAttributes(stream);
+
+ std::cerr << "Attributes for stream " << stream << "->" << attributes.size()
+ << std::endl;
+
+ for (const auto& a : attributes) {
+ std::cerr << "Attribute: " << a << 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();
+ p1_->AddStream(components);
+ p2_->AddStream(components);
+ }
+
+ void RemoveStream(size_t index) {
+ p1_->RemoveStream(index);
+ p2_->RemoveStream(index);
+ }
+
+ void Init(bool setup_stun_servers = true,
+ NrIceCtx::Policy ice_policy = NrIceCtx::ICE_POLICY_ALL) {
+ if (initted_) {
+ return;
+ }
+
+ NrIceCtx::Config config;
+ config.mPolicy = ice_policy;
+
+ p1_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, config);
+ p2_ = MakeUnique<IceTestPeer>("P2", test_utils_, false, config);
+ 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));
+
+ peer->SetStunServers(stun_servers);
+ }
+ }
+
+ bool Gather(unsigned int waitTime = kDefaultTimeout,
+ bool default_route_only = false) {
+ Init();
+
+ 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,
+ TrickleMode mode = TRICKLE_NONE) {
+ 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, mode);
+ caller->Connect(callee, mode);
+
+ if (mode != TRICKLE_SIMULATE) {
+ ASSERT_TRUE_WAIT(caller->ice_connected() && callee->ice_connected(),
+ kDefaultTimeout);
+ ASSERT_TRUE(caller->ready_ct() >= 1 && callee->ready_ct() >= 1);
+ 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 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_->SyncDispatchToSTS(
+ WrapRunnable(this, &WebRtcIceConnectTest::CloseP1));
+ 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();
+
+ if (expect_tx_failure) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(p1, &IceTestPeer::SendFailure, 0, 1));
+ ASSERT_EQ(previousSent, p1->sent());
+ } else {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(p1, &IceTestPeer::SendPacket, 0, 1,
+ reinterpret_cast<const unsigned char*>("TEST"), 4));
+ 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_->SyncDispatchToSTS(
+ WrapRunnable(p1_.get(), &IceTestPeer::SendFailure, 0, 1));
+ }
+
+ 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 - 1);
+ local_addr.addr.ifname[MAXIFNAME - 1] = '\0';
+
+ 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();
+
+ NrIceCtx::InitializeGlobals(NrIceCtx::GlobalConfig());
+
+ // Set up enough of the ICE ctx to allow the packet filter to work
+ ice_ctx_ = NrIceCtx::Create("test");
+
+ 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_->SyncDispatchToSTS(
+ WrapRunnable(this, &WebRtcIcePacketFilterTest::TearDown_s));
+ 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<NrIceCtx> ice_ctx_;
+};
+} // end namespace
+
+TEST_F(WebRtcIceGatherTest, TestGatherFakeStunServerHostnameNoResolver) {
+ if (stun_server_hostname_.empty()) {
+ return;
+ }
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort);
+ peer_->AddStream(1);
+ Gather();
+}
+
+// Disabled because google isn't running any TCP stun servers right now
+TEST_F(WebRtcIceGatherTest,
+ DISABLED_TestGatherFakeStunServerTcpHostnameNoResolver) {
+ if (stun_server_hostname_.empty()) {
+ return;
+ }
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort,
+ kNrIceTransportTcp);
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherFakeStunServerIpAddress) {
+ if (stun_server_address_.empty()) {
+ return;
+ }
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(stun_server_address_, kDefaultStunServerPort);
+ peer_->SetFakeResolver(stun_server_address_, stun_server_hostname_);
+ peer_->AddStream(1);
+ Gather();
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherStunServerIpAddressNoHost) {
+ if (stun_server_address_.empty()) {
+ return;
+ }
+
+ {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ }
+
+ NrIceCtx::Config config;
+ config.mPolicy = NrIceCtx::ICE_POLICY_NO_HOST;
+ peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, config);
+ 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;
+ }
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort);
+ peer_->SetFakeResolver(stun_server_address_, stun_server_hostname_);
+ peer_->AddStream(1);
+ Gather();
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherFakeStunBogusHostname) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort);
+ peer_->SetFakeResolver(stun_server_address_, stun_server_hostname_);
+ peer_->AddStream(1);
+ Gather();
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherDNSStunServerIpAddress) {
+ if (stun_server_address_.empty()) {
+ return;
+ }
+
+ {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ }
+
+ // A srflx candidate is considered redundant and discarded if its address
+ // equals that of a host candidate. (Frequently, a srflx candidate and a host
+ // candidate have equal addresses when the agent is not behind a NAT.) So set
+ // ICE_POLICY_NO_HOST here to ensure that a srflx candidate is not falsely
+ // discarded in this test.
+ NrIceCtx::Config config;
+ config.mPolicy = NrIceCtx::ICE_POLICY_NO_HOST;
+ peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, config);
+
+ peer_->SetStunServer(stun_server_address_, kDefaultStunServerPort);
+ peer_->SetDNSResolver();
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, "typ srflx raddr"));
+}
+
+// Disabled because google isn't running any TCP stun servers right now
+TEST_F(WebRtcIceGatherTest, DISABLED_TestGatherDNSStunServerIpAddressTcp) {
+ if (stun_server_address_.empty()) {
+ return;
+ }
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(stun_server_address_, kDefaultStunServerPort,
+ kNrIceTransportTcp);
+ peer_->SetDNSResolver();
+ peer_->AddStream(1);
+ 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;
+ }
+
+ {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ }
+
+ // A srflx candidate is considered redundant and discarded if its address
+ // equals that of a host candidate. (Frequently, a srflx candidate and a host
+ // candidate have equal addresses when the agent is not behind a NAT.) So set
+ // ICE_POLICY_NO_HOST here to ensure that a srflx candidate is not falsely
+ // discarded in this test.
+ NrIceCtx::Config config;
+ config.mPolicy = NrIceCtx::ICE_POLICY_NO_HOST;
+ peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, config);
+
+ peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort);
+ peer_->SetDNSResolver();
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, "typ srflx raddr"));
+}
+
+// Disabled because google isn't running any TCP stun servers right now
+TEST_F(WebRtcIceGatherTest, DISABLED_TestGatherDNSStunServerHostnameTcp) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort,
+ kNrIceTransportTcp);
+ peer_->SetDNSResolver();
+ peer_->AddStream(1);
+ 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 "));
+}
+
+// Disabled because google isn't running any TCP stun servers right now
+TEST_F(WebRtcIceGatherTest,
+ DISABLED_TestGatherDNSStunServerHostnameBothUdpTcp) {
+ if (stun_server_hostname_.empty()) {
+ return;
+ }
+
+ std::vector<NrIceStunServer> stun_servers;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ 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();
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP "));
+}
+
+// Disabled because google isn't running any TCP stun servers right now
+TEST_F(WebRtcIceGatherTest,
+ DISABLED_TestGatherDNSStunServerIpAddressBothUdpTcp) {
+ if (stun_server_address_.empty()) {
+ return;
+ }
+
+ std::vector<NrIceStunServer> stun_servers;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ 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();
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherDNSStunBogusHostname) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort);
+ peer_->SetDNSResolver();
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
+}
+
+// Disabled because google isn't running any TCP stun servers right now
+TEST_F(WebRtcIceGatherTest, DISABLED_TestGatherDNSStunBogusHostnameTcp) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort,
+ kNrIceTransportTcp);
+ peer_->SetDNSResolver();
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestDefaultCandidate) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort);
+ peer_->AddStream(1);
+ Gather();
+ NrIceCandidate default_candidate;
+ ASSERT_TRUE(NS_SUCCEEDED(peer_->GetDefaultCandidate(0, &default_candidate)));
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherTurn) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ if (turn_server_.empty()) return;
+ peer_->SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_, kNrIceTransportUdp);
+ peer_->AddStream(1);
+ Gather();
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherTurnTcp) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ if (turn_server_.empty()) return;
+ peer_->SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_, kNrIceTransportTcp);
+ peer_->AddStream(1);
+ Gather();
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherDisableComponent) {
+ if (stun_server_hostname_.empty()) {
+ return;
+ }
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort);
+ peer_->AddStream(1);
+ peer_->AddStream(2);
+ peer_->DisableComponent(1, 2);
+ Gather();
+ std::vector<std::string> attributes = peer_->GetAttributes(1);
+
+ for (auto& attribute : attributes) {
+ if (attribute.find("candidate:") != std::string::npos) {
+ size_t sp1 = attribute.find(' ');
+ ASSERT_EQ(0, attribute.compare(sp1 + 1, 1, "1", 1));
+ }
+ }
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherVerifyNoLoopback) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, "127.0.0.1"));
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherAllowLoopback) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ config.mAllowLoopback = true;
+ NrIceCtx::InitializeGlobals(config);
+
+ // Set up peer with loopback allowed.
+ peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, NrIceCtx::Config());
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, "127.0.0.1"));
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherTcpDisabledNoStun) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, " TCP "));
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
+}
+
+TEST_F(WebRtcIceGatherTest, VerifyTestStunServer) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpServerWithResponse("192.0.2.133", 3333);
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.2.133 3333 "));
+}
+
+TEST_F(WebRtcIceGatherTest, VerifyTestStunTcpServer) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunTcpServerWithResponse("192.0.2.233", 3333);
+ peer_->AddStream(1);
+ 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;
+ }
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpServerWithResponse("beef::", 3333);
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " beef:: 3333 "));
+}
+
+TEST_F(WebRtcIceGatherTest, VerifyTestStunServerFQDN) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpServerWithResponse("192.0.2.133", 3333, "stun.example.com");
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.2.133 3333 "));
+}
+
+TEST_F(WebRtcIceGatherTest, VerifyTestStunServerV6FQDN) {
+ if (!TestStunServer::GetInstance(AF_INET6)) {
+ // No V6 addresses
+ return;
+ }
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpServerWithResponse("beef::", 3333, "stun.example.com");
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " beef:: 3333 "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestStunServerReturnsWildcardAddr) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpServerWithResponse("0.0.0.0", 3333);
+ peer_->AddStream(1);
+ Gather(kDefaultTimeout * 3);
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, " 0.0.0.0 "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestStunServerReturnsWildcardAddrV6) {
+ if (!TestStunServer::GetInstance(AF_INET6)) {
+ // No V6 addresses
+ return;
+ }
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpServerWithResponse("::", 3333);
+ peer_->AddStream(1);
+ Gather(kDefaultTimeout * 3);
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, " :: "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestStunServerReturnsPort0) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpServerWithResponse("192.0.2.133", 0);
+ peer_->AddStream(1);
+ Gather(kDefaultTimeout * 3);
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, " 192.0.2.133 0 "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestStunServerReturnsLoopbackAddr) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpServerWithResponse("127.0.0.133", 3333);
+ peer_->AddStream(1);
+ Gather(kDefaultTimeout * 3);
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, " 127.0.0.133 "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestStunServerReturnsLoopbackAddrV6) {
+ if (!TestStunServer::GetInstance(AF_INET6)) {
+ // No V6 addresses
+ return;
+ }
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpServerWithResponse("::1", 3333);
+ peer_->AddStream(1);
+ Gather(kDefaultTimeout * 3);
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, " ::1 "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestStunServerTrickle) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpServerWithResponse("192.0.2.1", 3333);
+ peer_->AddStream(1);
+ 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) {
+ {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ }
+
+ NrIceCtx::Config config;
+ config.mPolicy = NrIceCtx::ICE_POLICY_NO_HOST;
+ peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, config);
+ UseFakeStunUdpServerWithResponse("192.0.2.1", 3333);
+ peer_->AddStream(1);
+ Gather(0);
+ WaitForGather();
+ DumpAttributes(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) {
+ {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ }
+
+ NrIceCtx::Config config;
+ config.mPolicy = NrIceCtx::ICE_POLICY_NO_HOST;
+ peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, config);
+ UseTestStunServer();
+ peer_->AddStream(1);
+ Gather(0);
+ WaitForGather();
+ DumpAttributes(0);
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, "host"));
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, "srflx"));
+}
+
+// Test that srflx candidate is discarded in non-NATted environment if host
+// address obfuscation is not enabled.
+TEST_F(WebRtcIceGatherTest,
+ TestSrflxCandidateDiscardedWithObfuscateHostAddressesNotEnabled) {
+ {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ }
+
+ NrIceCtx::Config config;
+ peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, config);
+ UseTestStunServer();
+ peer_->AddStream(1);
+ Gather(0, false, false);
+ WaitForGather();
+ DumpAttributes(0);
+ EXPECT_TRUE(StreamHasMatchingCandidate(0, "host"));
+ EXPECT_FALSE(StreamHasMatchingCandidate(0, "srflx"));
+}
+
+// Test that srflx candidate is generated in non-NATted environment if host
+// address obfuscation is enabled.
+TEST_F(WebRtcIceGatherTest,
+ TestSrflxCandidateGeneratedWithObfuscateHostAddressesEnabled) {
+ {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ }
+
+ NrIceCtx::Config config;
+ peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, config);
+ UseTestStunServer();
+ peer_->AddStream(1);
+ Gather(0, false, true);
+ WaitForGather();
+ DumpAttributes(0);
+ EXPECT_TRUE(StreamHasMatchingCandidate(0, "host"));
+ EXPECT_TRUE(StreamHasMatchingCandidate(0, "srflx"));
+}
+
+TEST_F(WebRtcIceGatherTest, TestStunTcpServerTrickle) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunTcpServerWithResponse("192.0.3.1", 3333);
+ TestStunTcpServer::GetInstance(AF_INET)->SetDelay(500);
+ peer_->AddStream(1);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpTcpServersWithResponse("192.0.2.1", 3333, "192.0.3.1", 3333);
+ TestStunServer::GetInstance(AF_INET)->SetDropInitialPackets(3);
+ TestStunTcpServer::GetInstance(AF_INET)->SetDelay(500);
+ peer_->AddStream(1);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->AddStream(1);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->AddStream(1);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+}
+
+TEST_F(WebRtcIceConnectTest, TestGatherTcp) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ Init();
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+}
+
+TEST_F(WebRtcIceConnectTest, TestGatherAutoPrioritize) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ Init();
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnect) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectRestartIce) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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, NrIceCtx::Config());
+ InitPeer(p3_.get());
+ p3_->AddStream(1);
+
+ ASSERT_TRUE(GatherCallerAndCallee(p2_.get(), p3_.get()));
+ std::cout << "-------------------------------------------------" << std::endl;
+ ConnectCallerAndCallee(p3_.get(), p2_.get(), TRICKLE_SIMULATE);
+ SendReceive(p1_.get(), p2_.get()); // p1 and p2 are still connected
+ SendReceive(p3_.get(), p2_.get(), true, true); // p3 and p2 not yet connected
+ p2_->SimulateTrickle(0);
+ p3_->SimulateTrickle(0);
+ ASSERT_TRUE_WAIT(p3_->is_ready(0), kDefaultTimeout);
+ ASSERT_TRUE_WAIT(p2_->is_ready(0), kDefaultTimeout);
+ SendReceive(p1_.get(), p2_.get(), false, true); // p1 and p2 not connected
+ SendReceive(p3_.get(), p2_.get()); // p3 and p2 are now connected
+
+ p3_ = nullptr;
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectRestartIceThenAbort) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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, NrIceCtx::Config());
+ InitPeer(p3_.get());
+ p3_->AddStream(1);
+
+ ASSERT_TRUE(GatherCallerAndCallee(p2_.get(), p3_.get()));
+ std::cout << "-------------------------------------------------" << std::endl;
+ p2_->RollbackIceRestart();
+ p2_->Connect(p1_.get(), TRICKLE_NONE);
+ SendReceive(p1_.get(), p2_.get());
+ p3_ = nullptr;
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectIceRestartRoleConflict) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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());
+ p2_->SetControlling(NrIceCtx::ICE_CONTROLLED);
+ ASSERT_EQ(NrIceCtx::ICE_CONTROLLING, p2_->GetControlling())
+ << "ICE restart should not allow role to change, unless ice-lite happens";
+
+ mozilla::UniquePtr<IceTestPeer> p3_;
+ p3_ = MakeUnique<IceTestPeer>("P3", test_utils_, true, NrIceCtx::Config());
+ InitPeer(p3_.get());
+ p3_->AddStream(1);
+ // Set control role for p3 accordingly (with role conflict)
+ p3_->SetControlling(NrIceCtx::ICE_CONTROLLING);
+ ASSERT_EQ(NrIceCtx::ICE_CONTROLLING, p3_->GetControlling());
+
+ ASSERT_TRUE(GatherCallerAndCallee(p2_.get(), p3_.get()));
+ std::cout << "-------------------------------------------------" << std::endl;
+ ConnectCallerAndCallee(p3_.get(), p2_.get());
+ auto p2role = p2_->GetControlling();
+ ASSERT_NE(p2role, p3_->GetControlling()) << "Conflict should be resolved";
+ ASSERT_EQ(NrIceCtx::ICE_CONTROLLED, p1_->GetControlling())
+ << "P1 should be unaffected by role conflict";
+
+ // And again we are not allowed to switch roles at this point any more
+ p1_->SetControlling(NrIceCtx::ICE_CONTROLLING);
+ ASSERT_EQ(NrIceCtx::ICE_CONTROLLED, p1_->GetControlling());
+ p3_->SetControlling(p2role);
+ ASSERT_NE(p2role, p3_->GetControlling());
+
+ p3_ = nullptr;
+}
+
+TEST_F(WebRtcIceConnectTest,
+ TestIceRestartWithMultipleInterfacesAndUserStartingScreenSharing) {
+ const char* FAKE_WIFI_ADDR = "10.0.0.1";
+ const char* FAKE_WIFI_IF_NAME = "wlan9";
+
+ // prepare a fake wifi interface
+ nr_local_addr wifi_addr;
+ wifi_addr.interface.type = NR_INTERFACE_TYPE_WIFI;
+ wifi_addr.interface.estimated_speed = 1000;
+
+ int r = nr_str_port_to_transport_addr(FAKE_WIFI_ADDR, 0, IPPROTO_UDP,
+ &(wifi_addr.addr));
+ ASSERT_EQ(0, r);
+ strncpy(wifi_addr.addr.ifname, FAKE_WIFI_IF_NAME, MAXIFNAME);
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ // setup initial ICE connection between p1_ and p2_
+ UseNat();
+ AddStream(1);
+ SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
+ NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
+ ASSERT_TRUE(Gather(kDefaultTimeout, true));
+ Connect();
+
+ // verify the connection is working
+ SendReceive(p1_.get(), p2_.get());
+
+ // simulate user accepting permissions for screen sharing
+ p2_->SetCtxFlags(false);
+
+ // and having an additional non-default interface
+ nsTArray<NrIceStunAddr> stunAddr = p2_->GetStunAddrs();
+ stunAddr.InsertElementAt(0, NrIceStunAddr(&wifi_addr));
+ p2_->SetStunAddrs(stunAddr);
+
+ std::cout << "-------------------------------------------------" << std::endl;
+
+ // now restart ICE
+ p2_->RestartIce();
+ ASSERT_FALSE(p2_->gathering_complete());
+
+ // verify that we can successfully gather candidates
+ p2_->Gather();
+ EXPECT_TRUE_WAIT(p2_->gathering_complete(), kDefaultTimeout);
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTcp) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ Init();
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ Init();
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ Init(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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ config.mAllowLoopback = true;
+ NrIceCtx::InitializeGlobals(config);
+ Init(false);
+ AddStream(1);
+ SetCandidateFilter(IsLoopbackCandidate);
+ ASSERT_TRUE(Gather());
+ SetExpectedRemoteCandidateAddr("127.0.0.1");
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectBothControllingP1Wins) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ p1_->SimulateIceLite();
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestTrickleBothControllingP1Wins) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ p1_->SimulateIceLite();
+ ConnectTrickle();
+ SimulateTrickle(0);
+ WaitForConnected(1000);
+ AssertCheckingReached();
+}
+
+TEST_F(WebRtcIceConnectTest, TestGatherFullCone) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+}
+
+TEST_F(WebRtcIceConnectTest, TestGatherFullConeAutoPrioritize) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ Init();
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectFullCone) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ AddStream(1);
+ SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
+ NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectNoNatNoHost) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ Init(false, NrIceCtx::ICE_POLICY_NO_HOST);
+ 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);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectFullConeNoHost) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ Init(false, NrIceCtx::ICE_POLICY_NO_HOST);
+ UseTestStunServer();
+ SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
+ NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestGatherAddressRestrictedCone) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ SetFilteringType(TestNat::ADDRESS_DEPENDENT);
+ SetMappingType(TestNat::ENDPOINT_INDEPENDENT);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectAddressRestrictedCone) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ SetFilteringType(TestNat::PORT_DEPENDENT);
+ SetMappingType(TestNat::ENDPOINT_INDEPENDENT);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectPortRestrictedCone) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ SetFilteringType(TestNat::PORT_DEPENDENT);
+ SetMappingType(TestNat::PORT_DEPENDENT);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectSymmetricNat) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ SetFilteringType(TestNat::PORT_DEPENDENT);
+ SetMappingType(TestNat::PORT_DEPENDENT);
+ 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_);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectSymmetricNatAndNoNat) {
+ {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ }
+
+ NrIceCtx::Config config;
+ p1_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, config);
+ p1_->UseNat();
+ p1_->SetFilteringType(TestNat::PORT_DEPENDENT);
+ p1_->SetMappingType(TestNat::PORT_DEPENDENT);
+
+ p2_ = MakeUnique<IceTestPeer>("P2", test_utils_, false, config);
+ 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;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ BlockUdp();
+ 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);
+ AddStream(1);
+ // 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;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ BlockUdp();
+ 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);
+ AddStream(1);
+ ASSERT_TRUE(Gather(kDefaultTimeout * 3));
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTwoComponents) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(2);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTwoComponentsDisableSecond) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(2);
+ ASSERT_TRUE(Gather());
+ p1_->DisableComponent(0, 2);
+ p2_->DisableComponent(0, 2);
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectP2ThenP1) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectP2();
+ PR_Sleep(1000);
+ ConnectP1();
+ WaitForConnectedStreams();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectP2ThenP1Trickle) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectP2();
+ PR_Sleep(1000);
+ ConnectP1(TRICKLE_SIMULATE);
+ SimulateTrickleP1(0);
+ WaitForConnectedStreams();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectP2ThenP1TrickleTwoComponents) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ Init();
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTrickleOneStreamOneComponent) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ SimulateTrickle(0);
+ WaitForConnected(1000);
+ AssertCheckingReached();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTrickleTwoStreamsOneComponent) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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& candidate : candidates) {
+ if (candidate->IsRelay()) {
+ candidate->Schedule(ms);
+ } else {
+ candidate->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:
+ NR_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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ DropTrickleCandidates(p1_->ControlTrickle(0));
+ RealisticTrickleDelay(p2_->ControlTrickle(0));
+ WaitForConnected(1000);
+}
+
+TEST_F(WebRtcIceConnectTest, P2NoTrickle) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ RealisticTrickleDelay(p1_->ControlTrickle(0));
+ DropTrickleCandidates(p2_->ControlTrickle(0));
+ WaitForConnected(1000);
+}
+
+TEST_F(WebRtcIceConnectTest, RemoveAndAddStream) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ AddStream(1);
+ RemoveStream(0);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ RealisticTrickleDelay(p1_->ControlTrickle(1));
+ RealisticTrickleDelay(p2_->ControlTrickle(1));
+ WaitForConnected(1000);
+}
+
+TEST_F(WebRtcIceConnectTest, RemoveStreamDuringConnect) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ Connect();
+ SendReceive();
+}
+
+TEST_F(WebRtcIceConnectTest, TestSendReceiveTcp) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ Init();
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ Init();
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ SetCandidateFilter(IsTcpSoCandidate);
+ SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
+ NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp);
+ Connect();
+ SendReceive();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConsent) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ SetupAndCheckConsent();
+ PR_Sleep(1500);
+ AssertConsentRefresh();
+ SendReceive();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConsentTcp) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ Init();
+ AddStream(1);
+ SetCandidateFilter(IsTcpCandidate);
+ SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
+ NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp);
+ SetupAndCheckConsent();
+ PR_Sleep(1500);
+ AssertConsentRefresh();
+ SendReceive();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConsentIntermittent) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ SetupAndCheckConsent();
+ p1_->SetBlockStun(true);
+ p2_->SetBlockStun(true);
+ WaitForDisconnected();
+ AssertConsentRefresh(CONSENT_STALE);
+ SendReceive();
+ WaitForFailed();
+ AssertConsentRefresh(CONSENT_EXPIRED);
+ SendFailure();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConsentDelayed) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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(200);
+ p2_->SetStunResponseDelay(200);
+ PR_Sleep(1000);
+ AssertConsentRefresh();
+ SendReceive();
+}
+
+TEST_F(WebRtcIceConnectTest, TestNetworkForcedOfflineAndRecovery) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ SetupAndCheckConsent();
+ p1_->ChangeNetworkStateToOffline();
+ ASSERT_TRUE_WAIT(p1_->ice_connected() == 0, kDefaultTimeout);
+ // Next round of consent check should switch it back to online
+ ASSERT_TRUE_WAIT(p1_->ice_connected(), kDefaultTimeout);
+}
+
+TEST_F(WebRtcIceConnectTest, TestNetworkForcedOfflineTwice) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ SetupAndCheckConsent();
+ p2_->ChangeNetworkStateToOffline();
+ ASSERT_TRUE_WAIT(p2_->ice_connected() == 0, kDefaultTimeout);
+ p2_->ChangeNetworkStateToOffline();
+ ASSERT_TRUE_WAIT(p2_->ice_connected() == 0, kDefaultTimeout);
+}
+
+TEST_F(WebRtcIceConnectTest, TestNetworkOnlineDoesntChangeState) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ SetupAndCheckConsent();
+ p2_->ChangeNetworkStateToOnline();
+ ASSERT_TRUE(p2_->ice_connected());
+ PR_Sleep(1500);
+ p2_->ChangeNetworkStateToOnline();
+ ASSERT_TRUE(p2_->ice_connected());
+}
+
+TEST_F(WebRtcIceConnectTest, TestNetworkOnlineTriggersConsent) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ // Let's emulate audio + video w/o rtcp-mux
+ AddStream(2);
+ AddStream(2);
+ SetupAndCheckConsent();
+ p1_->ChangeNetworkStateToOffline();
+ p1_->SetBlockStun(true);
+ ASSERT_TRUE_WAIT(p1_->ice_connected() == 0, kDefaultTimeout);
+ PR_Sleep(1500);
+ ASSERT_TRUE(p1_->ice_connected() == 0);
+ p1_->SetBlockStun(false);
+ p1_->ChangeNetworkStateToOnline();
+ ASSERT_TRUE_WAIT(p1_->ice_connected(), 500);
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTurn) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTurnWithDelay) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_);
+ SetCandidateFilter(SabotageHostCandidateAndDropReflexive);
+ AddStream(1);
+ p1_->Gather();
+ PR_Sleep(500);
+ p2_->Gather();
+ ConnectTrickle(TRICKLE_REAL);
+ WaitForGather();
+ WaitForConnectedStreams();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTurnWithNormalTrickleDelay) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ RealisticTrickleDelay(p1_->ControlTrickle(0));
+ RealisticTrickleDelay(p2_->ControlTrickle(0));
+
+ WaitForConnected();
+ AssertCheckingReached();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTurnWithNormalTrickleDelayOneSided) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ RealisticTrickleDelay(p1_->ControlTrickle(0));
+ p2_->SimulateTrickle(0);
+
+ WaitForConnected();
+ AssertCheckingReached();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTurnWithLargeTrickleDelay) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_);
+ SetCandidateFilter(SabotageHostCandidateAndDropReflexive);
+ AddStream(1);
+ 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;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_, kNrIceTransportTcp);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTurnOnly) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ SetCandidateFilter(IsRelayCandidate);
+ SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
+ NrIceCandidate::Type::ICE_RELAYED);
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTurnTcpOnly) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_, kNrIceTransportTcp);
+ AddStream(1);
+ 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;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_);
+ AddStream(1);
+ 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;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_, kNrIceTransportTcp);
+ AddStream(1);
+ 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;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ SetCandidateFilter(IsRelayCandidate);
+ // UDP is preferred.
+ SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
+ NrIceCandidate::Type::ICE_RELAYED, kNrIceTransportUdp);
+ Connect();
+ SendReceive();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectShutdownOneSide) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectThenDelete();
+}
+
+TEST_F(WebRtcIceConnectTest, TestPollCandPairsBeforeConnect) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ Init(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;
+ }
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ Init(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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ 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) {
+ 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) {
+ std::deque<std::string> logs;
+ std::string substring("CAND-PAIR(");
+ substring += p.codeword;
+ RLogConnector::GetInstance()->Filter(substring, 0, &logs);
+ ASSERT_NE(0U, logs.size());
+ }
+}
+
+// Verify that a bogus candidate doesn't cause crashes on the
+// main thread. See bug 856433.
+TEST_F(WebRtcIceConnectTest, TestBogusCandidate) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ Gather();
+ ConnectTrickle();
+ p1_->ParseCandidate(0, kBogusIceCandidate, "");
+
+ std::vector<NrIceCandidatePair> pairs;
+ nsresult res = p1_->GetCandidatePairs(0, &pairs);
+ ASSERT_EQ(NS_OK, res);
+ ASSERT_EQ(0U, pairs.size());
+}
+
+TEST_F(WebRtcIceConnectTest, TestNonMDNSCandidate) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ Gather();
+ ConnectTrickle();
+ p1_->ParseCandidate(0, kUnreachableHostIceCandidate, "");
+
+ std::vector<NrIceCandidatePair> pairs;
+ nsresult res = p1_->GetCandidatePairs(0, &pairs);
+ ASSERT_EQ(NS_OK, res);
+ ASSERT_EQ(1U, pairs.size());
+ ASSERT_EQ(pairs[0].remote.mdns_addr, "");
+}
+
+TEST_F(WebRtcIceConnectTest, TestMDNSCandidate) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ Gather();
+ ConnectTrickle();
+ p1_->ParseCandidate(0, kUnreachableHostIceCandidate, "host.local");
+
+ std::vector<NrIceCandidatePair> pairs;
+ nsresult res = p1_->GetCandidatePairs(0, &pairs);
+ ASSERT_EQ(NS_OK, res);
+ ASSERT_EQ(1U, pairs.size());
+ ASSERT_EQ(pairs[0].remote.mdns_addr, "host.local");
+}
+
+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(nullptr, &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(nullptr, &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, TestRecvStunBindingRequestWithoutAPendingId) {
+ nr_stun_message* msg;
+ ASSERT_EQ(0, nr_stun_build_req_no_auth(nullptr, &msg));
+
+ msg->header.id.octet[0] = 1;
+ 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.id.octet[0] = 1;
+ 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,
+ TestRecvStunPacketWithoutAPendingIdTcpFramed) {
+ nr_stun_message* msg;
+ ASSERT_EQ(0, nr_stun_build_req_no_auth(nullptr, &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(nullptr, &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(nullptr, &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(nullptr, &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(nullptr, &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(nullptr, &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));
+}
diff --git a/dom/media/webrtc/transport/test/moz.build b/dom/media/webrtc/transport/test/moz.build
new file mode 100644
index 0000000000..69d3a587a5
--- /dev/null
+++ b/dom/media/webrtc/transport/test/moz.build
@@ -0,0 +1,104 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+if CONFIG["OS_TARGET"] != "WINNT":
+
+ if CONFIG["OS_TARGET"] != "Android":
+ SOURCES += [
+ "ice_unittest.cpp",
+ ]
+
+ SOURCES += [
+ "buffered_stun_socket_unittest.cpp",
+ "multi_tcp_socket_unittest.cpp",
+ "nrappkit_unittest.cpp",
+ "proxy_tunnel_socket_unittest.cpp",
+ "rlogconnector_unittest.cpp",
+ "runnable_utils_unittest.cpp",
+ "simpletokenbucket_unittest.cpp",
+ "sockettransportservice_unittest.cpp",
+ "stunserver.cpp",
+ "test_nr_socket_ice_unittest.cpp",
+ "test_nr_socket_unittest.cpp",
+ "TestSyncRunnable.cpp",
+ "transport_unittests.cpp",
+ "turn_unittest.cpp",
+ "webrtcproxychannel_unittest.cpp",
+ ]
+
+ if CONFIG["MOZ_SCTP"]:
+ SOURCES += [
+ "sctp_unittest.cpp",
+ ]
+
+
+for var in ("HAVE_STRDUP", "NR_SOCKET_IS_VOID_PTR", "SCTP_DEBUG"):
+ DEFINES[var] = True
+
+if CONFIG["OS_TARGET"] == "Android":
+ DEFINES["LINUX"] = True
+ DEFINES["ANDROID"] = True
+ LOCAL_INCLUDES += [
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/port/android/include",
+ ]
+
+if CONFIG["OS_TARGET"] == "Linux":
+ DEFINES["LINUX"] = True
+ LOCAL_INCLUDES += [
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/include",
+ ]
+
+if CONFIG["OS_TARGET"] == "Darwin":
+ LOCAL_INCLUDES += [
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/port/darwin/include",
+ ]
+
+if CONFIG["OS_TARGET"] in ("DragonFly", "FreeBSD", "NetBSD", "OpenBSD"):
+ if CONFIG["OS_TARGET"] == "Darwin":
+ DEFINES["DARWIN"] = True
+ else:
+ DEFINES["BSD"] = True
+ LOCAL_INCLUDES += [
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/port/darwin/include",
+ ]
+
+# SCTP DEFINES
+if CONFIG["OS_TARGET"] == "WINNT":
+ DEFINES["WIN"] = True
+ # for stun.h
+ DEFINES["WIN32"] = True
+ DEFINES["__Userspace_os_Windows"] = 1
+else:
+ # Works for Darwin, Linux, Android. Probably doesn't work for others.
+ DEFINES["__Userspace_os_%s" % CONFIG["OS_TARGET"]] = 1
+
+if CONFIG["OS_TARGET"] in ("Darwin", "Android"):
+ DEFINES["GTEST_USE_OWN_TR1_TUPLE"] = 1
+
+LOCAL_INCLUDES += [
+ "/dom/media/webrtc/transport/",
+ "/dom/media/webrtc/transport/third_party/",
+ "/dom/media/webrtc/transport/third_party/nICEr/src/crypto",
+ "/dom/media/webrtc/transport/third_party/nICEr/src/ice",
+ "/dom/media/webrtc/transport/third_party/nICEr/src/net",
+ "/dom/media/webrtc/transport/third_party/nICEr/src/stun",
+ "/dom/media/webrtc/transport/third_party/nICEr/src/util",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/event",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/log",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/plugin",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/port/generic/include",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/registry",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/share",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/stats",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/util/",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr",
+ "/netwerk/sctp/src/",
+ "/xpcom/tests/",
+]
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/dom/media/webrtc/transport/test/mtransport_test_utils.h b/dom/media/webrtc/transport/test/mtransport_test_utils.h
new file mode 100644
index 0000000000..04031c0dc2
--- /dev/null
+++ b/dom/media/webrtc/transport/test/mtransport_test_utils.h
@@ -0,0 +1,57 @@
+/* -*- 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
+
+#ifndef mtransport_test_utils_h__
+#define mtransport_test_utils_h__
+
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+
+#include "nsISerialEventTarget.h"
+#include "nsPISocketTransportService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+
+class MtransportTestUtils {
+ public:
+ MtransportTestUtils() { InitServices(); }
+
+ ~MtransportTestUtils() = default;
+
+ void InitServices() {
+ nsresult rv;
+ sts_target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ sts_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ nsISerialEventTarget* sts_target() { return sts_target_; }
+
+ nsresult SyncDispatchToSTS(nsIRunnable* aRunnable) {
+ return SyncDispatchToSTS(do_AddRef(aRunnable));
+ }
+ nsresult SyncDispatchToSTS(already_AddRefed<nsIRunnable>&& aRunnable) {
+ return NS_DispatchAndSpinEventLoopUntilComplete(
+ "MtransportTestUtils::SyncDispatchToSts"_ns, sts_target_,
+ std::move(aRunnable));
+ }
+
+ private:
+ nsCOMPtr<nsISerialEventTarget> sts_target_;
+ nsCOMPtr<nsPISocketTransportService> sts_;
+};
+
+#define CHECK_ENVIRONMENT_FLAG(envname) \
+ char* test_flag = getenv(envname); \
+ if (!test_flag || strcmp(test_flag, "1")) { \
+ printf("To run this test set %s=1 in your environment\n", envname); \
+ exit(0); \
+ }
+
+#endif
diff --git a/dom/media/webrtc/transport/test/multi_tcp_socket_unittest.cpp b/dom/media/webrtc/transport/test/multi_tcp_socket_unittest.cpp
new file mode 100644
index 0000000000..d0c3ae6e53
--- /dev/null
+++ b/dom/media/webrtc/transport/test/multi_tcp_socket_unittest.cpp
@@ -0,0 +1,501 @@
+/* -*- 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/. */
+
+#include <iostream>
+#include <vector>
+
+#include "mozilla/Atomics.h"
+#include "runnable_utils.h"
+#include "pk11pub.h"
+
+extern "C" {
+#include "nr_api.h"
+#include "nr_socket.h"
+#include "transport_addr.h"
+#include "nr_socket_multi_tcp.h"
+}
+
+#include "stunserver.h"
+
+#include "nricectx.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+using namespace mozilla;
+
+namespace {
+
+class MultiTcpSocketTest : public MtransportTest {
+ public:
+ MultiTcpSocketTest()
+ : MtransportTest(), socks(3, nullptr), readable(false), ice_ctx_() {}
+
+ void SetUp() {
+ MtransportTest::SetUp();
+
+ NrIceCtx::InitializeGlobals(NrIceCtx::GlobalConfig());
+ ice_ctx_ = NrIceCtx::Create("stun");
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNM(&TestStunTcpServer::GetInstance, AF_INET));
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNM(&TestStunTcpServer::GetInstance, AF_INET6));
+ }
+
+ void TearDown() {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &MultiTcpSocketTest::Shutdown_s));
+
+ MtransportTest::TearDown();
+ }
+
+ DISALLOW_COPY_ASSIGN(MultiTcpSocketTest);
+
+ static void SockReadable(NR_SOCKET s, int how, void* arg) {
+ MultiTcpSocketTest* obj = static_cast<MultiTcpSocketTest*>(arg);
+ obj->SetReadable(true);
+ }
+
+ void Shutdown_s() {
+ ice_ctx_ = nullptr;
+ for (auto& sock : socks) {
+ nr_socket_destroy(&sock);
+ }
+ }
+
+ static uint16_t GetRandomPort() {
+ uint16_t result;
+ if (PK11_GenerateRandom((unsigned char*)&result, 2) != SECSuccess) {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+ return result;
+ }
+
+ static uint16_t EnsureEphemeral(uint16_t port) {
+ // IANA ephemeral port range (49152 to 65535)
+ return port | 49152;
+ }
+
+ void Create_s(nr_socket_tcp_type tcp_type, std::string stun_server_addr,
+ uint16_t stun_server_port, nr_socket** sock) {
+ nr_transport_addr local;
+ // Get start of port range for test
+ static unsigned short port_s = GetRandomPort();
+ int r;
+
+ if (!stun_server_addr.empty()) {
+ std::vector<NrIceStunServer> stun_servers;
+ UniquePtr<NrIceStunServer> server(NrIceStunServer::Create(
+ stun_server_addr, stun_server_port, kNrIceTransportTcp));
+ stun_servers.push_back(*server);
+
+ ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetStunServers(stun_servers)));
+ }
+
+ r = 1;
+ for (int tries = 10; tries && r; --tries) {
+ r = nr_str_port_to_transport_addr(
+ (char*)"127.0.0.1", EnsureEphemeral(port_s++), IPPROTO_TCP, &local);
+ ASSERT_EQ(0, r);
+
+ r = nr_socket_multi_tcp_create(ice_ctx_->ctx(), nullptr, &local, tcp_type,
+ 1, 2048, sock);
+ }
+
+ ASSERT_EQ(0, r);
+ printf("Creating socket on %s\n", local.as_string);
+ r = nr_socket_multi_tcp_set_readable_cb(
+ *sock, &MultiTcpSocketTest::SockReadable, this);
+ ASSERT_EQ(0, r);
+ }
+
+ nr_socket* Create(nr_socket_tcp_type tcp_type,
+ std::string stun_server_addr = "",
+ uint16_t stun_server_port = 0) {
+ nr_socket* sock = nullptr;
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &MultiTcpSocketTest::Create_s, tcp_type,
+ stun_server_addr, stun_server_port, &sock));
+ return sock;
+ }
+
+ void Listen_s(nr_socket* sock) {
+ nr_transport_addr addr;
+ int r = nr_socket_getaddr(sock, &addr);
+ ASSERT_EQ(0, r);
+ printf("Listening on %s\n", addr.as_string);
+ r = nr_socket_listen(sock, 5);
+ ASSERT_EQ(0, r);
+ }
+
+ void Listen(nr_socket* sock) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &MultiTcpSocketTest::Listen_s, sock));
+ }
+
+ void Destroy_s(nr_socket* sock) {
+ int r = nr_socket_destroy(&sock);
+ ASSERT_EQ(0, r);
+ }
+
+ void Destroy(nr_socket* sock) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &MultiTcpSocketTest::Destroy_s, sock));
+ }
+
+ void Connect_s(nr_socket* from, nr_socket* to) {
+ nr_transport_addr addr_to;
+ nr_transport_addr addr_from;
+ int r = nr_socket_getaddr(to, &addr_to);
+ ASSERT_EQ(0, r);
+ r = nr_socket_getaddr(from, &addr_from);
+ ASSERT_EQ(0, r);
+ printf("Connecting from %s to %s\n", addr_from.as_string,
+ addr_to.as_string);
+ r = nr_socket_connect(from, &addr_to);
+ ASSERT_EQ(0, r);
+ }
+
+ void Connect(nr_socket* from, nr_socket* to) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &MultiTcpSocketTest::Connect_s, from, to));
+ }
+
+ void ConnectSo_s(nr_socket* so1, nr_socket* so2) {
+ nr_transport_addr addr_so1;
+ nr_transport_addr addr_so2;
+ int r = nr_socket_getaddr(so1, &addr_so1);
+ ASSERT_EQ(0, r);
+ r = nr_socket_getaddr(so2, &addr_so2);
+ ASSERT_EQ(0, r);
+ printf("Connecting SO %s <-> %s\n", addr_so1.as_string, addr_so2.as_string);
+ r = nr_socket_connect(so1, &addr_so2);
+ ASSERT_EQ(0, r);
+ r = nr_socket_connect(so2, &addr_so1);
+ ASSERT_EQ(0, r);
+ }
+
+ void ConnectSo(nr_socket* from, nr_socket* to) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &MultiTcpSocketTest::ConnectSo_s, from, to));
+ }
+
+ void SendDataToAddress_s(nr_socket* from, nr_transport_addr* to,
+ const char* data, size_t len) {
+ nr_transport_addr addr_from;
+
+ int r = nr_socket_getaddr(from, &addr_from);
+ ASSERT_EQ(0, r);
+ printf("Sending %lu bytes %s -> %s\n", (unsigned long)len,
+ addr_from.as_string, to->as_string);
+ r = nr_socket_sendto(from, data, len, 0, to);
+ ASSERT_EQ(0, r);
+ }
+
+ void SendData(nr_socket* from, nr_transport_addr* to, const char* data,
+ size_t len) {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(
+ this, &MultiTcpSocketTest::SendDataToAddress_s, from, to, data, len));
+ }
+
+ void SendDataToSocket_s(nr_socket* from, nr_socket* to, const char* data,
+ size_t len) {
+ nr_transport_addr addr_to;
+
+ int r = nr_socket_getaddr(to, &addr_to);
+ ASSERT_EQ(0, r);
+ SendDataToAddress_s(from, &addr_to, data, len);
+ }
+
+ void SendData(nr_socket* from, nr_socket* to, const char* data, size_t len) {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(
+ this, &MultiTcpSocketTest::SendDataToSocket_s, from, to, data, len));
+ }
+
+ void RecvDataFromAddress_s(nr_transport_addr* expected_from,
+ nr_socket* sent_to, const char* expected_data,
+ size_t expected_len) {
+ SetReadable(false);
+ size_t buflen = expected_len ? expected_len + 1 : 100;
+ char received_data[buflen];
+ nr_transport_addr addr_to;
+ nr_transport_addr retaddr;
+ size_t retlen;
+
+ int r = nr_socket_getaddr(sent_to, &addr_to);
+ ASSERT_EQ(0, r);
+ printf("Receiving %lu bytes %s <- %s\n", (unsigned long)expected_len,
+ addr_to.as_string, expected_from->as_string);
+ r = nr_socket_recvfrom(sent_to, received_data, buflen, &retlen, 0,
+ &retaddr);
+ ASSERT_EQ(0, r);
+ r = nr_transport_addr_cmp(&retaddr, expected_from,
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL);
+ ASSERT_EQ(0, r);
+ // expected_len == 0 means we just expected some data
+ if (expected_len == 0) {
+ ASSERT_GT(retlen, 0U);
+ } else {
+ ASSERT_EQ(expected_len, retlen);
+ r = memcmp(expected_data, received_data, retlen);
+ ASSERT_EQ(0, r);
+ }
+ }
+
+ void RecvData(nr_transport_addr* expected_from, nr_socket* sent_to,
+ const char* expected_data = nullptr, size_t expected_len = 0) {
+ ASSERT_TRUE_WAIT(IsReadable(), 1000);
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &MultiTcpSocketTest::RecvDataFromAddress_s,
+ expected_from, sent_to, expected_data, expected_len));
+ }
+
+ void RecvDataFromSocket_s(nr_socket* expected_from, nr_socket* sent_to,
+ const char* expected_data, size_t expected_len) {
+ nr_transport_addr addr_from;
+
+ int r = nr_socket_getaddr(expected_from, &addr_from);
+ ASSERT_EQ(0, r);
+
+ RecvDataFromAddress_s(&addr_from, sent_to, expected_data, expected_len);
+ }
+
+ void RecvData(nr_socket* expected_from, nr_socket* sent_to,
+ const char* expected_data, size_t expected_len) {
+ ASSERT_TRUE_WAIT(IsReadable(), 1000);
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &MultiTcpSocketTest::RecvDataFromSocket_s,
+ expected_from, sent_to, expected_data, expected_len));
+ }
+
+ void RecvDataFailed_s(nr_socket* sent_to, size_t expected_len,
+ int expected_err) {
+ SetReadable(false);
+ char received_data[expected_len + 1];
+ nr_transport_addr addr_to;
+ nr_transport_addr retaddr;
+ size_t retlen;
+
+ int r = nr_socket_getaddr(sent_to, &addr_to);
+ ASSERT_EQ(0, r);
+ r = nr_socket_recvfrom(sent_to, received_data, expected_len + 1, &retlen, 0,
+ &retaddr);
+ ASSERT_EQ(expected_err, r) << "Expecting receive failure " << expected_err
+ << " on " << addr_to.as_string;
+ }
+
+ void RecvDataFailed(nr_socket* sent_to, size_t expected_len,
+ int expected_err) {
+ ASSERT_TRUE_WAIT(IsReadable(), 1000);
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &MultiTcpSocketTest::RecvDataFailed_s, sent_to,
+ expected_len, expected_err));
+ }
+
+ void TransferData(nr_socket* from, nr_socket* to, const char* data,
+ size_t len) {
+ SendData(from, to, data, len);
+ RecvData(from, to, data, len);
+ }
+
+ protected:
+ bool IsReadable() const { return readable; }
+ void SetReadable(bool r) { readable = r; }
+ std::vector<nr_socket*> socks;
+ Atomic<bool> readable;
+ RefPtr<NrIceCtx> ice_ctx_;
+};
+} // namespace
+
+TEST_F(MultiTcpSocketTest, TestListen) {
+ socks[0] = Create(TCP_TYPE_PASSIVE);
+ Listen(socks[0]);
+}
+
+TEST_F(MultiTcpSocketTest, TestConnect) {
+ socks[0] = Create(TCP_TYPE_PASSIVE);
+ socks[1] = Create(TCP_TYPE_ACTIVE);
+ socks[2] = Create(TCP_TYPE_ACTIVE);
+ Listen(socks[0]);
+ Connect(socks[1], socks[0]);
+ Connect(socks[2], socks[0]);
+}
+
+TEST_F(MultiTcpSocketTest, TestTransmit) {
+ const char data[] = "TestTransmit";
+ socks[0] = Create(TCP_TYPE_ACTIVE);
+ socks[1] = Create(TCP_TYPE_PASSIVE);
+ Listen(socks[1]);
+ Connect(socks[0], socks[1]);
+
+ TransferData(socks[0], socks[1], data, sizeof(data));
+ TransferData(socks[1], socks[0], data, sizeof(data));
+}
+
+TEST_F(MultiTcpSocketTest, TestClosePassive) {
+ const char data[] = "TestClosePassive";
+ socks[0] = Create(TCP_TYPE_ACTIVE);
+ socks[1] = Create(TCP_TYPE_PASSIVE);
+ Listen(socks[1]);
+ Connect(socks[0], socks[1]);
+
+ TransferData(socks[0], socks[1], data, sizeof(data));
+ TransferData(socks[1], socks[0], data, sizeof(data));
+
+ /* We have to destroy as only that calls PR_Close() */
+ std::cerr << "Destructing socket" << std::endl;
+ Destroy(socks[1]);
+
+ RecvDataFailed(socks[0], sizeof(data), R_EOD);
+
+ socks[1] = nullptr;
+}
+
+TEST_F(MultiTcpSocketTest, TestCloseActive) {
+ const char data[] = "TestCloseActive";
+ socks[0] = Create(TCP_TYPE_ACTIVE);
+ socks[1] = Create(TCP_TYPE_PASSIVE);
+ Listen(socks[1]);
+ Connect(socks[0], socks[1]);
+
+ TransferData(socks[0], socks[1], data, sizeof(data));
+ TransferData(socks[1], socks[0], data, sizeof(data));
+
+ /* We have to destroy as only that calls PR_Close() */
+ std::cerr << "Destructing socket" << std::endl;
+ Destroy(socks[0]);
+
+ RecvDataFailed(socks[1], sizeof(data), R_EOD);
+
+ socks[0] = nullptr;
+}
+
+TEST_F(MultiTcpSocketTest, TestTwoSendsBeforeReceives) {
+ const char data1[] = "TestTwoSendsBeforeReceives";
+ const char data2[] = "2nd data";
+ socks[0] = Create(TCP_TYPE_ACTIVE);
+ socks[1] = Create(TCP_TYPE_PASSIVE);
+ Listen(socks[1]);
+ Connect(socks[0], socks[1]);
+
+ SendData(socks[0], socks[1], data1, sizeof(data1));
+ SendData(socks[0], socks[1], data2, sizeof(data2));
+ RecvData(socks[0], socks[1], data1, sizeof(data1));
+ /* ICE TCP framing turns TCP effectively into datagram mode */
+ RecvData(socks[0], socks[1], data2, sizeof(data2));
+}
+
+TEST_F(MultiTcpSocketTest, TestTwoActiveBidirectionalTransmit) {
+ const char data1[] = "TestTwoActiveBidirectionalTransmit";
+ const char data2[] = "ReplyToTheFirstSocket";
+ const char data3[] = "TestMessageFromTheSecondSocket";
+ const char data4[] = "ThisIsAReplyToTheSecondSocket";
+ socks[0] = Create(TCP_TYPE_PASSIVE);
+ socks[1] = Create(TCP_TYPE_ACTIVE);
+ socks[2] = Create(TCP_TYPE_ACTIVE);
+ Listen(socks[0]);
+ Connect(socks[1], socks[0]);
+ Connect(socks[2], socks[0]);
+
+ TransferData(socks[1], socks[0], data1, sizeof(data1));
+ TransferData(socks[0], socks[1], data2, sizeof(data2));
+ TransferData(socks[2], socks[0], data3, sizeof(data3));
+ TransferData(socks[0], socks[2], data4, sizeof(data4));
+}
+
+TEST_F(MultiTcpSocketTest, TestTwoPassiveBidirectionalTransmit) {
+ const char data1[] = "TestTwoPassiveBidirectionalTransmit";
+ const char data2[] = "FirstReply";
+ const char data3[] = "TestTwoPassiveBidirectionalTransmitToTheSecondSock";
+ const char data4[] = "SecondReply";
+ socks[0] = Create(TCP_TYPE_PASSIVE);
+ socks[1] = Create(TCP_TYPE_PASSIVE);
+ socks[2] = Create(TCP_TYPE_ACTIVE);
+ Listen(socks[0]);
+ Listen(socks[1]);
+ Connect(socks[2], socks[0]);
+ Connect(socks[2], socks[1]);
+
+ TransferData(socks[2], socks[0], data1, sizeof(data1));
+ TransferData(socks[0], socks[2], data2, sizeof(data2));
+ TransferData(socks[2], socks[1], data3, sizeof(data3));
+ TransferData(socks[1], socks[2], data4, sizeof(data4));
+}
+
+TEST_F(MultiTcpSocketTest, TestActivePassiveWithStunServerMockup) {
+ /* Fake STUN message able to pass the nr_is_stun_msg check
+ used in nr_socket_buffered_stun */
+ const char stunMessage[] = {'\x00', '\x01', '\x00', '\x04', '\x21', '\x12',
+ '\xa4', '\x42', '\x00', '\x00', '\x00', '\x00',
+ '\x00', '\x00', '\x0c', '\x00', '\x00', '\x00',
+ '\x00', '\x00', '\x1c', '\xed', '\xca', '\xfe'};
+ const char data[] = "TestActivePassiveWithStunServerMockup";
+
+ nr_transport_addr stun_srv_addr;
+ std::string stun_addr;
+ uint16_t stun_port;
+ stun_addr = TestStunTcpServer::GetInstance(AF_INET)->addr();
+ stun_port = TestStunTcpServer::GetInstance(AF_INET)->port();
+ int r = nr_str_port_to_transport_addr(stun_addr.c_str(), stun_port,
+ IPPROTO_TCP, &stun_srv_addr);
+ ASSERT_EQ(0, r);
+
+ socks[0] = Create(TCP_TYPE_PASSIVE, stun_addr, stun_port);
+ Listen(socks[0]);
+ socks[1] = Create(TCP_TYPE_ACTIVE, stun_addr, stun_port);
+
+ /* Send a fake STUN request and expect a STUN error response */
+ SendData(socks[0], &stun_srv_addr, stunMessage, sizeof(stunMessage));
+ RecvData(&stun_srv_addr, socks[0]);
+
+ Connect(socks[1], socks[0]);
+ TransferData(socks[1], socks[0], data, sizeof(data));
+ TransferData(socks[0], socks[1], data, sizeof(data));
+}
+
+TEST_F(MultiTcpSocketTest, TestConnectTwoSo) {
+ socks[0] = Create(TCP_TYPE_SO);
+ socks[1] = Create(TCP_TYPE_SO);
+ ConnectSo(socks[0], socks[1]);
+}
+
+// test works on localhost only with delay applied:
+// tc qdisc add dev lo root netem delay 5ms
+TEST_F(MultiTcpSocketTest, DISABLED_TestTwoSoBidirectionalTransmit) {
+ const char data[] = "TestTwoSoBidirectionalTransmit";
+ socks[0] = Create(TCP_TYPE_SO);
+ socks[1] = Create(TCP_TYPE_SO);
+ ConnectSo(socks[0], socks[1]);
+ TransferData(socks[0], socks[1], data, sizeof(data));
+ TransferData(socks[1], socks[0], data, sizeof(data));
+}
+
+TEST_F(MultiTcpSocketTest, TestBigData) {
+ char buf1[2048];
+ char buf2[1024];
+
+ for (unsigned i = 0; i < sizeof(buf1); ++i) {
+ buf1[i] = i & 0xff;
+ }
+ for (unsigned i = 0; i < sizeof(buf2); ++i) {
+ buf2[i] = (i + 0x80) & 0xff;
+ }
+ socks[0] = Create(TCP_TYPE_ACTIVE);
+ socks[1] = Create(TCP_TYPE_PASSIVE);
+ Listen(socks[1]);
+ Connect(socks[0], socks[1]);
+
+ TransferData(socks[0], socks[1], buf1, sizeof(buf1));
+ TransferData(socks[0], socks[1], buf2, sizeof(buf2));
+ // opposite dir
+ SendData(socks[1], socks[0], buf2, sizeof(buf2));
+ SendData(socks[1], socks[0], buf1, sizeof(buf1));
+ RecvData(socks[1], socks[0], buf2, sizeof(buf2));
+ RecvData(socks[1], socks[0], buf1, sizeof(buf1));
+}
diff --git a/dom/media/webrtc/transport/test/nrappkit_unittest.cpp b/dom/media/webrtc/transport/test/nrappkit_unittest.cpp
new file mode 100644
index 0000000000..b6a63fb993
--- /dev/null
+++ b/dom/media/webrtc/transport/test/nrappkit_unittest.cpp
@@ -0,0 +1,123 @@
+
+/* -*- 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 <iostream>
+
+// nrappkit includes
+extern "C" {
+#include "nr_api.h"
+#include "async_timer.h"
+}
+
+#include "runnable_utils.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+using namespace mozilla;
+
+namespace {
+
+class TimerTest : public MtransportTest {
+ public:
+ TimerTest() : MtransportTest(), handle_(nullptr), fired_(false) {}
+ virtual ~TimerTest() = default;
+
+ int ArmTimer(int timeout) {
+ int ret;
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&ret, this, &TimerTest::ArmTimer_w, timeout));
+
+ return ret;
+ }
+
+ int ArmCancelTimer(int timeout) {
+ int ret;
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&ret, this, &TimerTest::ArmCancelTimer_w, timeout));
+
+ return ret;
+ }
+
+ int ArmTimer_w(int timeout) {
+ return NR_ASYNC_TIMER_SET(timeout, cb, this, &handle_);
+ }
+
+ int ArmCancelTimer_w(int timeout) {
+ int r;
+ r = ArmTimer_w(timeout);
+ if (r) return r;
+
+ return CancelTimer_w();
+ }
+
+ int CancelTimer() {
+ int ret;
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&ret, this, &TimerTest::CancelTimer_w));
+
+ return ret;
+ }
+
+ int CancelTimer_w() { return NR_async_timer_cancel(handle_); }
+
+ int Schedule() {
+ int ret;
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&ret, this, &TimerTest::Schedule_w));
+
+ return ret;
+ }
+
+ int Schedule_w() {
+ NR_ASYNC_SCHEDULE(cb, this);
+
+ return 0;
+ }
+
+ static void cb(NR_SOCKET r, int how, void* arg) {
+ std::cerr << "Timer fired " << std::endl;
+
+ TimerTest* t = static_cast<TimerTest*>(arg);
+
+ t->fired_ = true;
+ }
+
+ protected:
+ void* handle_;
+ bool fired_;
+};
+} // namespace
+
+TEST_F(TimerTest, SimpleTimer) {
+ ArmTimer(100);
+ ASSERT_TRUE_WAIT(fired_, 1000);
+}
+
+TEST_F(TimerTest, CancelTimer) {
+ ArmTimer(1000);
+ CancelTimer();
+ PR_Sleep(2000);
+ ASSERT_FALSE(fired_);
+}
+
+TEST_F(TimerTest, CancelTimer0) {
+ ArmCancelTimer(0);
+ PR_Sleep(100);
+ ASSERT_FALSE(fired_);
+}
+
+TEST_F(TimerTest, ScheduleTest) {
+ Schedule();
+ ASSERT_TRUE_WAIT(fired_, 1000);
+}
diff --git a/dom/media/webrtc/transport/test/proxy_tunnel_socket_unittest.cpp b/dom/media/webrtc/transport/test/proxy_tunnel_socket_unittest.cpp
new file mode 100644
index 0000000000..1b54126dd6
--- /dev/null
+++ b/dom/media/webrtc/transport/test/proxy_tunnel_socket_unittest.cpp
@@ -0,0 +1,277 @@
+/* -*- 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 authors: ekr@rtfm.com; ryan@tokbox.com
+
+#include <vector>
+#include <numeric>
+
+#include "nr_socket_tcp.h"
+#include "WebrtcTCPSocketWrapper.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+using namespace mozilla;
+
+// update TestReadMultipleSizes if you change this
+const std::string kHelloMessage = "HELLO IS IT ME YOU'RE LOOKING FOR?";
+
+class NrTcpSocketTest : public MtransportTest {
+ public:
+ NrTcpSocketTest()
+ : mSProxy(nullptr),
+ nr_socket_(nullptr),
+ mEmptyArray(0),
+ mReadChunkSize(0),
+ mReadChunkSizeIncrement(1),
+ mReadAllowance(-1),
+ mConnected(false) {}
+
+ void SetUp() override {
+ mSProxy = new NrTcpSocket(nullptr);
+ int r = nr_socket_create_int((void*)mSProxy.get(), mSProxy->vtbl(),
+ &nr_socket_);
+ ASSERT_EQ(0, r);
+
+ // fake calling AsyncOpen() due to IPC calls. must be non-null
+ mSProxy->AssignChannel_DoNotUse(new WebrtcTCPSocketWrapper(nullptr));
+ }
+
+ void TearDown() override { mSProxy->close(); }
+
+ static void readable_cb(NR_SOCKET s, int how, void* cb_arg) {
+ NrTcpSocketTest* test = (NrTcpSocketTest*)cb_arg;
+ size_t capacity = std::min(test->mReadChunkSize, test->mReadAllowance);
+ nsTArray<uint8_t> array(capacity);
+ size_t read;
+
+ nr_socket_read(test->nr_socket_, (char*)array.Elements(), array.Capacity(),
+ &read, 0);
+
+ ASSERT_TRUE(read <= array.Capacity());
+ ASSERT_TRUE(test->mReadAllowance >= read);
+
+ array.SetLength(read);
+ test->mData.AppendElements(array);
+ test->mReadAllowance -= read;
+
+ // We may read more bytes each time we're called. This way we can ensure we
+ // consume buffers partially and across multiple buffers.
+ test->mReadChunkSize += test->mReadChunkSizeIncrement;
+
+ if (test->mReadAllowance > 0) {
+ NR_ASYNC_WAIT(s, how, &NrTcpSocketTest::readable_cb, cb_arg);
+ }
+ }
+
+ static void writable_cb(NR_SOCKET s, int how, void* cb_arg) {
+ NrTcpSocketTest* test = (NrTcpSocketTest*)cb_arg;
+ test->mConnected = true;
+ }
+
+ const std::string DataString() {
+ return std::string((char*)mData.Elements(), mData.Length());
+ }
+
+ protected:
+ RefPtr<NrTcpSocket> mSProxy;
+ nr_socket* nr_socket_;
+
+ nsTArray<uint8_t> mData;
+ nsTArray<uint8_t> mEmptyArray;
+
+ uint32_t mReadChunkSize;
+ uint32_t mReadChunkSizeIncrement;
+ uint32_t mReadAllowance;
+
+ bool mConnected;
+};
+
+TEST_F(NrTcpSocketTest, TestCreate) {}
+
+TEST_F(NrTcpSocketTest, TestConnected) {
+ ASSERT_TRUE(!mConnected);
+
+ NR_ASYNC_WAIT(mSProxy, NR_ASYNC_WAIT_WRITE, &NrTcpSocketTest::writable_cb,
+ this);
+
+ // still not connected just registered for writes...
+ ASSERT_TRUE(!mConnected);
+
+ mSProxy->OnConnected("http"_ns);
+
+ ASSERT_TRUE(mConnected);
+}
+
+TEST_F(NrTcpSocketTest, TestRead) {
+ nsTArray<uint8_t> array;
+ array.AppendElements(kHelloMessage.c_str(), kHelloMessage.length());
+
+ NR_ASYNC_WAIT(mSProxy, NR_ASYNC_WAIT_READ, &NrTcpSocketTest::readable_cb,
+ this);
+ // this will read 0 bytes here
+ mSProxy->OnRead(std::move(array));
+
+ ASSERT_EQ(kHelloMessage.length(), mSProxy->CountUnreadBytes());
+
+ // callback is still set but terminated due to 0 byte read
+ // start callbacks again (first read is 0 then 1,2,3,...)
+ mSProxy->OnRead(std::move(mEmptyArray));
+
+ ASSERT_EQ(kHelloMessage.length(), mData.Length());
+ ASSERT_EQ(kHelloMessage, DataString());
+}
+
+TEST_F(NrTcpSocketTest, TestReadConstantConsumeSize) {
+ std::string data;
+
+ // triangle number
+ const int kCount = 32;
+
+ // ~17kb
+ // triangle number formula n*(n+1)/2
+ for (int i = 0; i < kCount * (kCount + 1) / 2; ++i) {
+ data += kHelloMessage;
+ }
+
+ // decreasing buffer sizes
+ for (int i = 0, start = 0; i < kCount; ++i) {
+ int length = (kCount - i) * kHelloMessage.length();
+
+ nsTArray<uint8_t> array;
+ array.AppendElements(data.c_str() + start, length);
+ start += length;
+
+ mSProxy->OnRead(std::move(array));
+ }
+
+ ASSERT_EQ(data.length(), mSProxy->CountUnreadBytes());
+
+ // read same amount each callback
+ mReadChunkSize = 128;
+ mReadChunkSizeIncrement = 0;
+ NR_ASYNC_WAIT(mSProxy, NR_ASYNC_WAIT_READ, &NrTcpSocketTest::readable_cb,
+ this);
+
+ ASSERT_EQ(data.length(), mSProxy->CountUnreadBytes());
+
+ // start callbacks
+ mSProxy->OnRead(std::move(mEmptyArray));
+
+ ASSERT_EQ(data.length(), mData.Length());
+ ASSERT_EQ(data, DataString());
+}
+
+TEST_F(NrTcpSocketTest, TestReadNone) {
+ char buf[4096];
+ size_t read = 0;
+ int r = nr_socket_read(nr_socket_, buf, sizeof(buf), &read, 0);
+
+ ASSERT_EQ(R_WOULDBLOCK, r);
+
+ nsTArray<uint8_t> array;
+ array.AppendElements(kHelloMessage.c_str(), kHelloMessage.length());
+ mSProxy->OnRead(std::move(array));
+
+ ASSERT_EQ(kHelloMessage.length(), mSProxy->CountUnreadBytes());
+
+ r = nr_socket_read(nr_socket_, buf, sizeof(buf), &read, 0);
+
+ ASSERT_EQ(0, r);
+ ASSERT_EQ(kHelloMessage.length(), read);
+ ASSERT_EQ(kHelloMessage, std::string(buf, read));
+}
+
+TEST_F(NrTcpSocketTest, TestReadMultipleSizes) {
+ using namespace std;
+
+ string data;
+ // 515 * kHelloMessage.length() == 17510
+ const size_t kCount = 515;
+ // randomly generated numbers, sums to 17510, 20 numbers
+ vector<int> varyingSizes = {404, 622, 1463, 1597, 1676, 389, 389,
+ 1272, 781, 81, 1030, 1450, 256, 812,
+ 1571, 29, 1045, 911, 643, 1089};
+
+ // changing varyingSizes or the test message breaks this so check here
+ ASSERT_EQ(kCount, 17510 / kHelloMessage.length());
+ ASSERT_EQ(17510, accumulate(varyingSizes.begin(), varyingSizes.end(), 0));
+
+ // ~17kb
+ for (size_t i = 0; i < kCount; ++i) {
+ data += kHelloMessage;
+ }
+
+ nsTArray<uint8_t> array;
+ array.AppendElements(data.c_str(), data.length());
+
+ for (int amountToRead : varyingSizes) {
+ nsTArray<uint8_t> buffer;
+ buffer.AppendElements(array.Elements(), amountToRead);
+ array.RemoveElementsAt(0, amountToRead);
+ mSProxy->OnRead(std::move(buffer));
+ }
+
+ ASSERT_EQ(data.length(), mSProxy->CountUnreadBytes());
+
+ // don't need to read 0 on the first read, so start at 1 and keep going
+ mReadChunkSize = 1;
+ NR_ASYNC_WAIT(mSProxy, NR_ASYNC_WAIT_READ, &NrTcpSocketTest::readable_cb,
+ this);
+ // start callbacks
+ mSProxy->OnRead(std::move(mEmptyArray));
+
+ ASSERT_EQ(data.length(), mData.Length());
+ ASSERT_EQ(data, DataString());
+}
+
+TEST_F(NrTcpSocketTest, TestReadConsumeReadDrain) {
+ std::string data;
+ // ~26kb total; should be even
+ const int kCount = 512;
+
+ // there's some division by 2 here so check that kCount is even
+ ASSERT_EQ(0, kCount % 2);
+
+ for (int i = 0; i < kCount; ++i) {
+ data += kHelloMessage;
+ nsTArray<uint8_t> array;
+ array.AppendElements(kHelloMessage.c_str(), kHelloMessage.length());
+ mSProxy->OnRead(std::move(array));
+ }
+
+ // read half at first
+ mReadAllowance = kCount / 2 * kHelloMessage.length();
+ // start by reading 1 byte
+ mReadChunkSize = 1;
+ NR_ASYNC_WAIT(mSProxy, NR_ASYNC_WAIT_READ, &NrTcpSocketTest::readable_cb,
+ this);
+ mSProxy->OnRead(std::move(mEmptyArray));
+
+ ASSERT_EQ(data.length() / 2, mSProxy->CountUnreadBytes());
+ ASSERT_EQ(data.length() / 2, mData.Length());
+
+ // fill read buffer back up
+ for (int i = 0; i < kCount / 2; ++i) {
+ data += kHelloMessage;
+ nsTArray<uint8_t> array;
+ array.AppendElements(kHelloMessage.c_str(), kHelloMessage.length());
+ mSProxy->OnRead(std::move(array));
+ }
+
+ // remove read limit
+ mReadAllowance = -1;
+ // used entire read allowance so we need to setup a new await
+ NR_ASYNC_WAIT(mSProxy, NR_ASYNC_WAIT_READ, &NrTcpSocketTest::readable_cb,
+ this);
+ // start callbacks
+ mSProxy->OnRead(std::move(mEmptyArray));
+
+ ASSERT_EQ(data.length(), mData.Length());
+ ASSERT_EQ(data, DataString());
+}
diff --git a/dom/media/webrtc/transport/test/rlogconnector_unittest.cpp b/dom/media/webrtc/transport/test/rlogconnector_unittest.cpp
new file mode 100644
index 0000000000..93fabae481
--- /dev/null
+++ b/dom/media/webrtc/transport/test/rlogconnector_unittest.cpp
@@ -0,0 +1,255 @@
+/* -*- 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 */
+
+#include "rlogconnector.h"
+
+extern "C" {
+#include "registry.h"
+#include "r_log.h"
+}
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+
+#include <deque>
+#include <string>
+#include <vector>
+
+using mozilla::RLogConnector;
+
+int NR_LOG_TEST = 0;
+
+class RLogConnectorTest : public ::testing::Test {
+ public:
+ RLogConnectorTest() { Init(); }
+
+ ~RLogConnectorTest() { Free(); }
+
+ static void SetUpTestCase() {
+ NR_reg_init(NR_REG_MODE_LOCAL);
+ r_log_init();
+ /* Would be nice to be able to unregister in the fixture */
+ const char* facility = "rlogconnector_test";
+ r_log_register(const_cast<char*>(facility), &NR_LOG_TEST);
+ }
+
+ void Init() { RLogConnector::CreateInstance(); }
+
+ void Free() { RLogConnector::DestroyInstance(); }
+
+ void ReInit() {
+ Free();
+ Init();
+ }
+};
+
+TEST_F(RLogConnectorTest, TestGetFree) {
+ RLogConnector* instance = RLogConnector::GetInstance();
+ ASSERT_NE(nullptr, instance);
+}
+
+TEST_F(RLogConnectorTest, TestFilterEmpty) {
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->GetAny(0, &logs);
+ ASSERT_EQ(0U, logs.size());
+}
+
+TEST_F(RLogConnectorTest, TestBasicFilter) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->Filter("Test", 0, &logs);
+ ASSERT_EQ(1U, logs.size());
+}
+
+TEST_F(RLogConnectorTest, TestBasicFilterContent) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->Filter("Test", 0, &logs);
+ ASSERT_EQ("Test", logs.back());
+}
+
+TEST_F(RLogConnectorTest, TestFilterAnyFrontMatch) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test");
+ std::vector<std::string> substrings;
+ substrings.push_back("foo");
+ substrings.push_back("Test");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->FilterAny(substrings, 0, &logs);
+ ASSERT_EQ("Test", logs.back());
+}
+
+TEST_F(RLogConnectorTest, TestFilterAnyBackMatch) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test");
+ std::vector<std::string> substrings;
+ substrings.push_back("Test");
+ substrings.push_back("foo");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->FilterAny(substrings, 0, &logs);
+ ASSERT_EQ("Test", logs.back());
+}
+
+TEST_F(RLogConnectorTest, TestFilterAnyBothMatch) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test");
+ std::vector<std::string> substrings;
+ substrings.push_back("Tes");
+ substrings.push_back("est");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->FilterAny(substrings, 0, &logs);
+ ASSERT_EQ("Test", logs.back());
+}
+
+TEST_F(RLogConnectorTest, TestFilterAnyNeitherMatch) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test");
+ std::vector<std::string> substrings;
+ substrings.push_back("tes");
+ substrings.push_back("esT");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->FilterAny(substrings, 0, &logs);
+ ASSERT_EQ(0U, logs.size());
+}
+
+TEST_F(RLogConnectorTest, TestAllMatch) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test1");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test2");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->GetAny(0, &logs);
+ ASSERT_EQ(2U, logs.size());
+}
+
+TEST_F(RLogConnectorTest, TestOrder) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test1");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test2");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->GetAny(0, &logs);
+ ASSERT_EQ("Test2", logs.back());
+ ASSERT_EQ("Test1", logs.front());
+}
+
+TEST_F(RLogConnectorTest, TestNoMatch) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test1");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test2");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->Filter("foo", 0, &logs);
+ ASSERT_EQ(0U, logs.size());
+}
+
+TEST_F(RLogConnectorTest, TestSubstringFilter) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test1");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test2");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->Filter("t1", 0, &logs);
+ ASSERT_EQ(1U, logs.size());
+ ASSERT_EQ("Test1", logs.back());
+}
+
+TEST_F(RLogConnectorTest, TestFilterLimit) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test1");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test2");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test3");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test4");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test5");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test6");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->Filter("Test", 2, &logs);
+ ASSERT_EQ(2U, logs.size());
+ ASSERT_EQ("Test6", logs.back());
+ ASSERT_EQ("Test5", logs.front());
+}
+
+TEST_F(RLogConnectorTest, TestFilterAnyLimit) {
+ r_log(NR_LOG_TEST, LOG_INFO, "TestOne");
+ r_log(NR_LOG_TEST, LOG_INFO, "TestTwo");
+ r_log(NR_LOG_TEST, LOG_INFO, "TestThree");
+ r_log(NR_LOG_TEST, LOG_INFO, "TestFour");
+ r_log(NR_LOG_TEST, LOG_INFO, "TestFive");
+ r_log(NR_LOG_TEST, LOG_INFO, "TestSix");
+ std::vector<std::string> substrings;
+ // Matches Two, Three, Four, and Six
+ substrings.push_back("tT");
+ substrings.push_back("o");
+ substrings.push_back("r");
+ substrings.push_back("S");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->FilterAny(substrings, 2, &logs);
+ ASSERT_EQ(2U, logs.size());
+ ASSERT_EQ("TestSix", logs.back());
+ ASSERT_EQ("TestFour", logs.front());
+}
+
+TEST_F(RLogConnectorTest, TestLimit) {
+ RLogConnector::GetInstance()->SetLogLimit(3);
+ r_log(NR_LOG_TEST, LOG_INFO, "Test1");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test2");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test3");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test4");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test5");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test6");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->GetAny(0, &logs);
+ ASSERT_EQ(3U, logs.size());
+ ASSERT_EQ("Test6", logs.back());
+ ASSERT_EQ("Test4", logs.front());
+}
+
+TEST_F(RLogConnectorTest, TestLimitBulkDiscard) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test1");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test2");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test3");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test4");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test5");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test6");
+ RLogConnector::GetInstance()->SetLogLimit(3);
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->GetAny(0, &logs);
+ ASSERT_EQ(3U, logs.size());
+ ASSERT_EQ("Test6", logs.back());
+ ASSERT_EQ("Test4", logs.front());
+}
+
+TEST_F(RLogConnectorTest, TestIncreaseLimit) {
+ RLogConnector::GetInstance()->SetLogLimit(3);
+ r_log(NR_LOG_TEST, LOG_INFO, "Test1");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test2");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test3");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test4");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test5");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test6");
+ RLogConnector::GetInstance()->SetLogLimit(300);
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->GetAny(0, &logs);
+ ASSERT_EQ(3U, logs.size());
+ ASSERT_EQ("Test6", logs.back());
+ ASSERT_EQ("Test4", logs.front());
+}
+
+TEST_F(RLogConnectorTest, TestClear) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test1");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test2");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test3");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test4");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test5");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test6");
+ RLogConnector::GetInstance()->SetLogLimit(0);
+ RLogConnector::GetInstance()->SetLogLimit(4096);
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->GetAny(0, &logs);
+ ASSERT_EQ(0U, logs.size());
+}
+
+TEST_F(RLogConnectorTest, TestReInit) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test1");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test2");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test3");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test4");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test5");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test6");
+ ReInit();
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->GetAny(0, &logs);
+ ASSERT_EQ(0U, logs.size());
+}
diff --git a/dom/media/webrtc/transport/test/runnable_utils_unittest.cpp b/dom/media/webrtc/transport/test/runnable_utils_unittest.cpp
new file mode 100644
index 0000000000..70707b148f
--- /dev/null
+++ b/dom/media/webrtc/transport/test/runnable_utils_unittest.cpp
@@ -0,0 +1,353 @@
+/* -*- 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 <iostream>
+
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+
+#include "runnable_utils.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+using namespace mozilla;
+
+namespace {
+
+// Helper used to make sure args are properly copied and/or moved.
+struct CtorDtorState {
+ enum class State { Empty, Copy, Explicit, Move, Moved };
+
+ static const char* ToStr(const State& state) {
+ switch (state) {
+ case State::Empty:
+ return "empty";
+ case State::Copy:
+ return "copy";
+ case State::Explicit:
+ return "explicit";
+ case State::Move:
+ return "move";
+ case State::Moved:
+ return "moved";
+ default:
+ return "unknown";
+ }
+ }
+
+ void DumpState() const { std::cerr << ToStr(state_) << std::endl; }
+
+ CtorDtorState() { DumpState(); }
+
+ explicit CtorDtorState(int* destroyed)
+ : dtor_count_(destroyed), state_(State::Explicit) {
+ DumpState();
+ }
+
+ CtorDtorState(const CtorDtorState& other)
+ : dtor_count_(other.dtor_count_), state_(State::Copy) {
+ DumpState();
+ }
+
+ // Clear the other's dtor counter so it's not counted if moved.
+ CtorDtorState(CtorDtorState&& other)
+ : dtor_count_(std::exchange(other.dtor_count_, nullptr)),
+ state_(State::Move) {
+ other.state_ = State::Moved;
+ DumpState();
+ }
+
+ ~CtorDtorState() {
+ const char* const state = ToStr(state_);
+ std::cerr << "Destructor called with end state: " << state << std::endl;
+
+ if (dtor_count_) {
+ ++*dtor_count_;
+ }
+ }
+
+ int* dtor_count_ = nullptr;
+ State state_ = State::Empty;
+};
+
+class Destructor {
+ private:
+ ~Destructor() {
+ std::cerr << "Destructor called" << std::endl;
+ *destroyed_ = true;
+ }
+
+ public:
+ explicit Destructor(bool* destroyed) : destroyed_(destroyed) {}
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Destructor)
+
+ private:
+ bool* destroyed_;
+};
+
+class TargetClass {
+ public:
+ explicit TargetClass(int* ran) : ran_(ran) {}
+
+ void m1(int x) {
+ std::cerr << __FUNCTION__ << " " << x << std::endl;
+ *ran_ = 1;
+ }
+
+ void m2(int x, int y) {
+ std::cerr << __FUNCTION__ << " " << x << " " << y << std::endl;
+ *ran_ = 2;
+ }
+
+ void m1set(bool* z) {
+ std::cerr << __FUNCTION__ << std::endl;
+ *z = true;
+ }
+ int return_int(int x) {
+ std::cerr << __FUNCTION__ << std::endl;
+ return x;
+ }
+
+ void destructor_target_ref(RefPtr<Destructor> destructor) {}
+
+ int* ran_;
+};
+
+class RunnableArgsTest : public MtransportTest {
+ public:
+ RunnableArgsTest() : MtransportTest(), ran_(0), cl_(&ran_) {}
+
+ void Test1Arg() {
+ Runnable* r = WrapRunnable(&cl_, &TargetClass::m1, 1);
+ r->Run();
+ ASSERT_EQ(1, ran_);
+ }
+
+ void Test2Args() {
+ Runnable* r = WrapRunnable(&cl_, &TargetClass::m2, 1, 2);
+ r->Run();
+ ASSERT_EQ(2, ran_);
+ }
+
+ private:
+ int ran_;
+ TargetClass cl_;
+};
+
+class DispatchTest : public MtransportTest {
+ public:
+ DispatchTest() : MtransportTest(), ran_(0), cl_(&ran_) {}
+
+ void SetUp() {
+ MtransportTest::SetUp();
+
+ nsresult rv;
+ target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ }
+
+ void Test1Arg() {
+ Runnable* r = WrapRunnable(&cl_, &TargetClass::m1, 1);
+ NS_DispatchAndSpinEventLoopUntilComplete("DispatchTest::Test1Arg"_ns,
+ target_, do_AddRef(r));
+ ASSERT_EQ(1, ran_);
+ }
+
+ void Test2Args() {
+ Runnable* r = WrapRunnable(&cl_, &TargetClass::m2, 1, 2);
+ NS_DispatchAndSpinEventLoopUntilComplete("DispatchTest::Test2Args"_ns,
+ target_, do_AddRef(r));
+ ASSERT_EQ(2, ran_);
+ }
+
+ void Test1Set() {
+ bool x = false;
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "DispatchTest::Test1Set"_ns, target_,
+ do_AddRef(WrapRunnable(&cl_, &TargetClass::m1set, &x)));
+ ASSERT_TRUE(x);
+ }
+
+ void TestRet() {
+ int z;
+ int x = 10;
+
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "DispatchTest::TestRet"_ns, target_,
+ do_AddRef(WrapRunnableRet(&z, &cl_, &TargetClass::return_int, x)));
+ ASSERT_EQ(10, z);
+ }
+
+ protected:
+ int ran_;
+ TargetClass cl_;
+ nsCOMPtr<nsIEventTarget> target_;
+};
+
+TEST_F(RunnableArgsTest, OneArgument) { Test1Arg(); }
+
+TEST_F(RunnableArgsTest, TwoArguments) { Test2Args(); }
+
+TEST_F(DispatchTest, OneArgument) { Test1Arg(); }
+
+TEST_F(DispatchTest, TwoArguments) { Test2Args(); }
+
+TEST_F(DispatchTest, Test1Set) { Test1Set(); }
+
+TEST_F(DispatchTest, TestRet) { TestRet(); }
+
+void SetNonMethod(TargetClass* cl, int x) { cl->m1(x); }
+
+int SetNonMethodRet(TargetClass* cl, int x) {
+ cl->m1(x);
+
+ return x;
+}
+
+TEST_F(DispatchTest, TestNonMethod) {
+ test_utils_->SyncDispatchToSTS(WrapRunnableNM(SetNonMethod, &cl_, 10));
+
+ ASSERT_EQ(1, ran_);
+}
+
+TEST_F(DispatchTest, TestNonMethodRet) {
+ int z;
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNMRet(&z, SetNonMethodRet, &cl_, 10));
+
+ ASSERT_EQ(1, ran_);
+ ASSERT_EQ(10, z);
+}
+
+TEST_F(DispatchTest, TestDestructorRef) {
+ bool destroyed = false;
+ {
+ RefPtr<Destructor> destructor = new Destructor(&destroyed);
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "DispatchTest::TestDestructorRef"_ns, target_,
+ do_AddRef(WrapRunnable(&cl_, &TargetClass::destructor_target_ref,
+ destructor)));
+ ASSERT_FALSE(destroyed);
+ }
+ ASSERT_TRUE(destroyed);
+
+ // Now try with a move.
+ destroyed = false;
+ {
+ RefPtr<Destructor> destructor = new Destructor(&destroyed);
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "DispatchTest::TestDestructorRef"_ns, target_,
+ do_AddRef(WrapRunnable(&cl_, &TargetClass::destructor_target_ref,
+ std::move(destructor))));
+ ASSERT_TRUE(destroyed);
+ }
+}
+
+TEST_F(DispatchTest, TestMove) {
+ int destroyed = 0;
+ {
+ CtorDtorState state(&destroyed);
+
+ // Dispatch with:
+ // - moved arg
+ // - by-val capture in function should consume a move
+ // - expect destruction in the function scope
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "DispatchTest::TestMove"_ns, target_,
+ do_AddRef(WrapRunnableNM([](CtorDtorState s) {}, std::move(state))));
+ ASSERT_EQ(1, destroyed);
+ }
+ // Still shouldn't count when we go out of scope as it was moved.
+ ASSERT_EQ(1, destroyed);
+
+ {
+ CtorDtorState state(&destroyed);
+
+ // Dispatch with:
+ // - copied arg
+ // - by-val capture in function should consume a move
+ // - expect destruction in the function scope and call scope
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "DispatchTest::TestMove"_ns, target_,
+ do_AddRef(WrapRunnableNM([](CtorDtorState s) {}, state)));
+ ASSERT_EQ(2, destroyed);
+ }
+ // Original state should be destroyed
+ ASSERT_EQ(3, destroyed);
+
+ {
+ CtorDtorState state(&destroyed);
+
+ // Dispatch with:
+ // - moved arg
+ // - by-ref in function should accept a moved arg
+ // - expect destruction in the wrapper invocation scope
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "DispatchTest::TestMove"_ns, target_,
+ do_AddRef(
+ WrapRunnableNM([](const CtorDtorState& s) {}, std::move(state))));
+ ASSERT_EQ(4, destroyed);
+ }
+ // Still shouldn't count when we go out of scope as it was moved.
+ ASSERT_EQ(4, destroyed);
+
+ {
+ CtorDtorState state(&destroyed);
+
+ // Dispatch with:
+ // - moved arg
+ // - r-value function should accept a moved arg
+ // - expect destruction in the wrapper invocation scope
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "DispatchTest::TestMove"_ns, target_,
+ do_AddRef(WrapRunnableNM([](CtorDtorState&& s) {}, std::move(state))));
+ ASSERT_EQ(5, destroyed);
+ }
+ // Still shouldn't count when we go out of scope as it was moved.
+ ASSERT_EQ(5, destroyed);
+}
+
+TEST_F(DispatchTest, TestUniquePtr) {
+ // Test that holding the class in UniquePtr works
+ int ran = 0;
+ auto cl = MakeUnique<TargetClass>(&ran);
+
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "DispatchTest::TestUniquePtr"_ns, target_,
+ do_AddRef(WrapRunnable(std::move(cl), &TargetClass::m1, 1)));
+ ASSERT_EQ(1, ran);
+
+ // Test that UniquePtr works as a param to the runnable
+ int destroyed = 0;
+ {
+ auto state = MakeUnique<CtorDtorState>(&destroyed);
+
+ // Dispatch with:
+ // - moved arg
+ // - Function should move construct from arg
+ // - expect destruction in the wrapper invocation scope
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "DispatchTest::TestUniquePtr"_ns, target_,
+ do_AddRef(WrapRunnableNM([](UniquePtr<CtorDtorState> s) {},
+ std::move(state))));
+ ASSERT_EQ(1, destroyed);
+ }
+ // Still shouldn't count when we go out of scope as it was moved.
+ ASSERT_EQ(1, destroyed);
+}
+
+} // end of namespace
diff --git a/dom/media/webrtc/transport/test/sctp_unittest.cpp b/dom/media/webrtc/transport/test/sctp_unittest.cpp
new file mode 100644
index 0000000000..ea32565fb2
--- /dev/null
+++ b/dom/media/webrtc/transport/test/sctp_unittest.cpp
@@ -0,0 +1,381 @@
+/* -*- 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 <iostream>
+#include <string>
+
+#include "sigslot.h"
+
+#include "nsITimer.h"
+
+#include "transportflow.h"
+#include "transportlayer.h"
+#include "transportlayerloopback.h"
+
+#include "runnable_utils.h"
+#include "usrsctp.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+using namespace mozilla;
+
+static bool sctp_logging = false;
+static int port_number = 5000;
+
+namespace {
+
+class TransportTestPeer;
+
+class SendPeriodic : public nsITimerCallback, public nsINamed {
+ public:
+ SendPeriodic(TransportTestPeer* peer, int to_send)
+ : peer_(peer), to_send_(to_send) {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ protected:
+ virtual ~SendPeriodic() = default;
+
+ TransportTestPeer* peer_;
+ int to_send_;
+};
+
+NS_IMPL_ISUPPORTS(SendPeriodic, nsITimerCallback, nsINamed)
+
+class TransportTestPeer : public sigslot::has_slots<> {
+ public:
+ TransportTestPeer(std::string name, int local_port, int remote_port,
+ MtransportTestUtils* utils)
+ : name_(name),
+ connected_(false),
+ sent_(0),
+ received_(0),
+ flow_(new TransportFlow()),
+ loopback_(new TransportLayerLoopback()),
+ sctp_(usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, receive_cb,
+ nullptr, 0, nullptr)),
+ timer_(NS_NewTimer()),
+ periodic_(nullptr),
+ test_utils_(utils) {
+ std::cerr << "Creating TransportTestPeer; flow="
+ << static_cast<void*>(flow_.get()) << " local=" << local_port
+ << " remote=" << remote_port << std::endl;
+
+ usrsctp_register_address(static_cast<void*>(this));
+ int r = usrsctp_set_non_blocking(sctp_, 1);
+ EXPECT_GE(r, 0);
+
+ struct linger l;
+ l.l_onoff = 1;
+ l.l_linger = 0;
+ r = usrsctp_setsockopt(sctp_, SOL_SOCKET, SO_LINGER, &l,
+ (socklen_t)sizeof(l));
+ EXPECT_GE(r, 0);
+
+ struct sctp_event subscription;
+ memset(&subscription, 0, sizeof(subscription));
+ subscription.se_assoc_id = SCTP_ALL_ASSOC;
+ subscription.se_on = 1;
+ subscription.se_type = SCTP_ASSOC_CHANGE;
+ r = usrsctp_setsockopt(sctp_, IPPROTO_SCTP, SCTP_EVENT, &subscription,
+ sizeof(subscription));
+ EXPECT_GE(r, 0);
+
+ memset(&local_addr_, 0, sizeof(local_addr_));
+ local_addr_.sconn_family = AF_CONN;
+#if !defined(__Userspace_os_Linux) && !defined(__Userspace_os_Windows) && \
+ !defined(__Userspace_os_Android)
+ local_addr_.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+ local_addr_.sconn_port = htons(local_port);
+ local_addr_.sconn_addr = static_cast<void*>(this);
+
+ memset(&remote_addr_, 0, sizeof(remote_addr_));
+ remote_addr_.sconn_family = AF_CONN;
+#if !defined(__Userspace_os_Linux) && !defined(__Userspace_os_Windows) && \
+ !defined(__Userspace_os_Android)
+ remote_addr_.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+ remote_addr_.sconn_port = htons(remote_port);
+ remote_addr_.sconn_addr = static_cast<void*>(this);
+
+ nsresult res;
+ res = loopback_->Init();
+ EXPECT_EQ((nsresult)NS_OK, res);
+ }
+
+ ~TransportTestPeer() {
+ std::cerr << "Destroying sctp connection flow="
+ << static_cast<void*>(flow_.get()) << std::endl;
+ usrsctp_close(sctp_);
+ usrsctp_deregister_address(static_cast<void*>(this));
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &TransportTestPeer::Disconnect_s));
+
+ std::cerr << "~TransportTestPeer() completed" << std::endl;
+ }
+
+ void ConnectSocket(TransportTestPeer* peer) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &TransportTestPeer::ConnectSocket_s, peer));
+ }
+
+ void ConnectSocket_s(TransportTestPeer* peer) {
+ loopback_->Connect(peer->loopback_);
+ ASSERT_EQ((nsresult)NS_OK, loopback_->Init());
+ flow_->PushLayer(loopback_);
+
+ loopback_->SignalPacketReceived.connect(this,
+ &TransportTestPeer::PacketReceived);
+
+ // SCTP here!
+ ASSERT_TRUE(sctp_);
+ std::cerr << "Calling usrsctp_bind()" << std::endl;
+ int r =
+ usrsctp_bind(sctp_, reinterpret_cast<struct sockaddr*>(&local_addr_),
+ sizeof(local_addr_));
+ ASSERT_GE(0, r);
+
+ std::cerr << "Calling usrsctp_connect()" << std::endl;
+ r = usrsctp_connect(sctp_,
+ reinterpret_cast<struct sockaddr*>(&remote_addr_),
+ sizeof(remote_addr_));
+ ASSERT_GE(0, r);
+ }
+
+ void Disconnect_s() {
+ disconnect_all();
+ if (flow_) {
+ flow_ = nullptr;
+ }
+ }
+
+ void Disconnect() { loopback_->Disconnect(); }
+
+ void StartTransfer(size_t to_send) {
+ periodic_ = new SendPeriodic(this, to_send);
+ timer_->SetTarget(test_utils_->sts_target());
+ timer_->InitWithCallback(periodic_, 10, nsITimer::TYPE_REPEATING_SLACK);
+ }
+
+ void SendOne() {
+ unsigned char buf[100];
+ memset(buf, sent_ & 0xff, sizeof(buf));
+
+ struct sctp_sndinfo info;
+ info.snd_sid = 1;
+ info.snd_flags = 0;
+ info.snd_ppid = 50; // What the heck is this?
+ info.snd_context = 0;
+ info.snd_assoc_id = 0;
+
+ int r = usrsctp_sendv(sctp_, buf, sizeof(buf), nullptr, 0,
+ static_cast<void*>(&info), sizeof(info),
+ SCTP_SENDV_SNDINFO, 0);
+ ASSERT_TRUE(r >= 0);
+ ASSERT_EQ(sizeof(buf), (size_t)r);
+
+ ++sent_;
+ }
+
+ int sent() const { return sent_; }
+ int received() const { return received_; }
+ bool connected() const { return connected_; }
+
+ static TransportResult SendPacket_s(UniquePtr<MediaPacket> packet,
+ const RefPtr<TransportFlow>& flow,
+ TransportLayer* layer) {
+ return layer->SendPacket(*packet);
+ }
+
+ TransportResult SendPacket(const unsigned char* data, size_t len) {
+ UniquePtr<MediaPacket> packet(new MediaPacket);
+ packet->Copy(data, len);
+
+ // Uses DISPATCH_NORMAL to avoid possible deadlocks when we're called
+ // from MainThread especially during shutdown (same as DataChannels).
+ // RUN_ON_THREAD short-circuits if already on the STS thread, which is
+ // normal for most transfers outside of connect() and close(). Passes
+ // a refptr to flow_ to avoid any async deletion issues (since we can't
+ // make 'this' into a refptr as it isn't refcounted)
+ RUN_ON_THREAD(test_utils_->sts_target(),
+ WrapRunnableNM(&TransportTestPeer::SendPacket_s,
+ std::move(packet), flow_, loopback_),
+ NS_DISPATCH_NORMAL);
+
+ return 0;
+ }
+
+ void PacketReceived(TransportLayer* layer, MediaPacket& packet) {
+ std::cerr << "Received " << packet.len() << " bytes" << std::endl;
+
+ // Pass the data to SCTP
+
+ usrsctp_conninput(static_cast<void*>(this), packet.data(), packet.len(), 0);
+ }
+
+ // Process SCTP notification
+ void Notification(union sctp_notification* msg, size_t len) {
+ ASSERT_EQ(msg->sn_header.sn_length, len);
+
+ if (msg->sn_header.sn_type == SCTP_ASSOC_CHANGE) {
+ struct sctp_assoc_change* change = &msg->sn_assoc_change;
+
+ if (change->sac_state == SCTP_COMM_UP) {
+ std::cerr << "Connection up" << std::endl;
+ SetConnected(true);
+ } else {
+ std::cerr << "Connection down" << std::endl;
+ SetConnected(false);
+ }
+ }
+ }
+
+ void SetConnected(bool state) { connected_ = state; }
+
+ static int conn_output(void* addr, void* buffer, size_t length, uint8_t tos,
+ uint8_t set_df) {
+ TransportTestPeer* peer = static_cast<TransportTestPeer*>(addr);
+
+ peer->SendPacket(static_cast<unsigned char*>(buffer), length);
+
+ return 0;
+ }
+
+ static int receive_cb(struct socket* sock, union sctp_sockstore addr,
+ void* data, size_t datalen, struct sctp_rcvinfo rcv,
+ int flags, void* ulp_info) {
+ TransportTestPeer* me =
+ static_cast<TransportTestPeer*>(addr.sconn.sconn_addr);
+ MOZ_ASSERT(me);
+
+ if (flags & MSG_NOTIFICATION) {
+ union sctp_notification* notif =
+ static_cast<union sctp_notification*>(data);
+
+ me->Notification(notif, datalen);
+ return 0;
+ }
+
+ me->received_ += datalen;
+
+ std::cerr << "receive_cb: sock " << sock << " data " << data << "("
+ << datalen << ") total received bytes = " << me->received_
+ << std::endl;
+
+ return 0;
+ }
+
+ private:
+ std::string name_;
+ bool connected_;
+ size_t sent_;
+ size_t received_;
+ // Owns the TransportLayerLoopback, but basically does nothing else.
+ RefPtr<TransportFlow> flow_;
+ TransportLayerLoopback* loopback_;
+
+ struct sockaddr_conn local_addr_;
+ struct sockaddr_conn remote_addr_;
+ struct socket* sctp_;
+ nsCOMPtr<nsITimer> timer_;
+ RefPtr<SendPeriodic> periodic_;
+ MtransportTestUtils* test_utils_;
+};
+
+// Implemented here because it calls a method of TransportTestPeer
+NS_IMETHODIMP SendPeriodic::Notify(nsITimer* timer) {
+ peer_->SendOne();
+ --to_send_;
+ if (!to_send_) {
+ timer->Cancel();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SendPeriodic::GetName(nsACString& aName) {
+ aName.AssignLiteral("SendPeriodic");
+ return NS_OK;
+}
+
+class SctpTransportTest : public MtransportTest {
+ public:
+ SctpTransportTest() = default;
+
+ ~SctpTransportTest() = default;
+
+ static void debug_printf(const char* format, ...) {
+ va_list ap;
+
+ va_start(ap, format);
+ vprintf(format, ap);
+ va_end(ap);
+ }
+
+ static void SetUpTestCase() {
+ if (sctp_logging) {
+ usrsctp_init(0, &TransportTestPeer::conn_output, debug_printf);
+ usrsctp_sysctl_set_sctp_debug_on(0xffffffff);
+ } else {
+ usrsctp_init(0, &TransportTestPeer::conn_output, nullptr);
+ }
+ }
+
+ void TearDown() override {
+ if (p1_) p1_->Disconnect();
+ if (p2_) p2_->Disconnect();
+ delete p1_;
+ delete p2_;
+
+ MtransportTest::TearDown();
+ }
+
+ void ConnectSocket(int p1port = 0, int p2port = 0) {
+ if (!p1port) p1port = port_number++;
+ if (!p2port) p2port = port_number++;
+
+ p1_ = new TransportTestPeer("P1", p1port, p2port, test_utils_);
+ p2_ = new TransportTestPeer("P2", p2port, p1port, test_utils_);
+
+ p1_->ConnectSocket(p2_);
+ p2_->ConnectSocket(p1_);
+ ASSERT_TRUE_WAIT(p1_->connected(), 2000);
+ ASSERT_TRUE_WAIT(p2_->connected(), 2000);
+ }
+
+ void TestTransfer(int expected = 1) {
+ std::cerr << "Starting trasnsfer test" << std::endl;
+ p1_->StartTransfer(expected);
+ ASSERT_TRUE_WAIT(p1_->sent() == expected, 10000);
+ ASSERT_TRUE_WAIT(p2_->received() == (expected * 100), 10000);
+ std::cerr << "P2 received " << p2_->received() << std::endl;
+ }
+
+ protected:
+ TransportTestPeer* p1_ = nullptr;
+ TransportTestPeer* p2_ = nullptr;
+};
+
+TEST_F(SctpTransportTest, TestConnect) { ConnectSocket(); }
+
+TEST_F(SctpTransportTest, TestConnectSymmetricalPorts) {
+ ConnectSocket(5002, 5002);
+}
+
+TEST_F(SctpTransportTest, TestTransfer) {
+ ConnectSocket();
+ TestTransfer(50);
+}
+
+} // end namespace
diff --git a/dom/media/webrtc/transport/test/simpletokenbucket_unittest.cpp b/dom/media/webrtc/transport/test/simpletokenbucket_unittest.cpp
new file mode 100644
index 0000000000..66622d795b
--- /dev/null
+++ b/dom/media/webrtc/transport/test/simpletokenbucket_unittest.cpp
@@ -0,0 +1,114 @@
+/* -*- 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 */
+
+#include "simpletokenbucket.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+
+using mozilla::SimpleTokenBucket;
+
+class TestSimpleTokenBucket : public SimpleTokenBucket {
+ public:
+ TestSimpleTokenBucket(size_t bucketSize, size_t tokensPerSecond)
+ : SimpleTokenBucket(bucketSize, tokensPerSecond) {}
+
+ void fastForward(int32_t timeMilliSeconds) {
+ if (timeMilliSeconds >= 0) {
+ last_time_tokens_added_ -= PR_MillisecondsToInterval(timeMilliSeconds);
+ } else {
+ last_time_tokens_added_ += PR_MillisecondsToInterval(-timeMilliSeconds);
+ }
+ }
+};
+
+TEST(SimpleTokenBucketTest, TestConstruct)
+{ TestSimpleTokenBucket b(10, 1); }
+
+TEST(SimpleTokenBucketTest, TestGet)
+{
+ TestSimpleTokenBucket b(10, 1);
+ ASSERT_EQ(5U, b.getTokens(5));
+}
+
+TEST(SimpleTokenBucketTest, TestGetAll)
+{
+ TestSimpleTokenBucket b(10, 1);
+ ASSERT_EQ(10U, b.getTokens(10));
+}
+
+TEST(SimpleTokenBucketTest, TestGetInsufficient)
+{
+ TestSimpleTokenBucket b(10, 1);
+ ASSERT_EQ(5U, b.getTokens(5));
+ ASSERT_EQ(5U, b.getTokens(6));
+}
+
+TEST(SimpleTokenBucketTest, TestGetBucketCount)
+{
+ TestSimpleTokenBucket b(10, 1);
+ ASSERT_EQ(10U, b.getTokens(UINT32_MAX));
+ ASSERT_EQ(5U, b.getTokens(5));
+ ASSERT_EQ(5U, b.getTokens(UINT32_MAX));
+}
+
+TEST(SimpleTokenBucketTest, TestTokenRefill)
+{
+ TestSimpleTokenBucket b(10, 1);
+ ASSERT_EQ(5U, b.getTokens(5));
+ b.fastForward(1000);
+ ASSERT_EQ(6U, b.getTokens(6));
+}
+
+TEST(SimpleTokenBucketTest, TestNoTimeWasted)
+{
+ // Makes sure that when the time elapsed is insufficient to add any
+ // tokens to the bucket, the internal timestamp that is used in this
+ // calculation is not updated (ie; two subsequent 0.5 second elapsed times
+ // counts as a full second)
+ TestSimpleTokenBucket b(10, 1);
+ ASSERT_EQ(5U, b.getTokens(5));
+ b.fastForward(500);
+ ASSERT_EQ(5U, b.getTokens(6));
+ b.fastForward(500);
+ ASSERT_EQ(6U, b.getTokens(6));
+}
+
+TEST(SimpleTokenBucketTest, TestNegativeTime)
+{
+ TestSimpleTokenBucket b(10, 1);
+ b.fastForward(-1000);
+ // Make sure we don't end up with an invalid number of tokens, but otherwise
+ // permit anything.
+ ASSERT_GT(11U, b.getTokens(100));
+}
+
+TEST(SimpleTokenBucketTest, TestEmptyBucket)
+{
+ TestSimpleTokenBucket b(10, 1);
+ ASSERT_EQ(10U, b.getTokens(10));
+ ASSERT_EQ(0U, b.getTokens(10));
+}
+
+TEST(SimpleTokenBucketTest, TestEmptyThenFillBucket)
+{
+ TestSimpleTokenBucket b(10, 1);
+ ASSERT_EQ(10U, b.getTokens(10));
+ ASSERT_EQ(0U, b.getTokens(1));
+ b.fastForward(50000);
+ ASSERT_EQ(10U, b.getTokens(10));
+}
+
+TEST(SimpleTokenBucketTest, TestNoOverflow)
+{
+ TestSimpleTokenBucket b(10, 1);
+ ASSERT_EQ(10U, b.getTokens(10));
+ ASSERT_EQ(0U, b.getTokens(1));
+ b.fastForward(50000);
+ ASSERT_EQ(10U, b.getTokens(11));
+}
diff --git a/dom/media/webrtc/transport/test/sockettransportservice_unittest.cpp b/dom/media/webrtc/transport/test/sockettransportservice_unittest.cpp
new file mode 100644
index 0000000000..ffa87fe91f
--- /dev/null
+++ b/dom/media/webrtc/transport/test/sockettransportservice_unittest.cpp
@@ -0,0 +1,181 @@
+/* -*- 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 <iostream>
+
+#include "prio.h"
+
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+
+#include "nsISocketTransportService.h"
+
+#include "nsASocketHandler.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+using namespace mozilla;
+
+namespace {
+class SocketTransportServiceTest : public MtransportTest {
+ public:
+ SocketTransportServiceTest()
+ : MtransportTest(),
+ received_(0),
+ readpipe_(nullptr),
+ writepipe_(nullptr),
+ registered_(false) {}
+
+ ~SocketTransportServiceTest() {
+ if (readpipe_) PR_Close(readpipe_);
+ if (writepipe_) PR_Close(writepipe_);
+ }
+
+ void SetUp();
+ void RegisterHandler();
+ void SendEvent();
+ void SendPacket();
+
+ void ReceivePacket() { ++received_; }
+
+ void ReceiveEvent() { ++received_; }
+
+ size_t Received() { return received_; }
+
+ private:
+ nsCOMPtr<nsISocketTransportService> stservice_;
+ nsCOMPtr<nsIEventTarget> target_;
+ size_t received_;
+ PRFileDesc* readpipe_;
+ PRFileDesc* writepipe_;
+ bool registered_;
+};
+
+// Received an event.
+class EventReceived : public Runnable {
+ public:
+ explicit EventReceived(SocketTransportServiceTest* test)
+ : Runnable("EventReceived"), test_(test) {}
+
+ NS_IMETHOD Run() override {
+ test_->ReceiveEvent();
+ return NS_OK;
+ }
+
+ SocketTransportServiceTest* test_;
+};
+
+// Register our listener on the socket
+class RegisterEvent : public Runnable {
+ public:
+ explicit RegisterEvent(SocketTransportServiceTest* test)
+ : Runnable("RegisterEvent"), test_(test) {}
+
+ NS_IMETHOD Run() override {
+ test_->RegisterHandler();
+ return NS_OK;
+ }
+
+ SocketTransportServiceTest* test_;
+};
+
+class SocketHandler : public nsASocketHandler {
+ public:
+ explicit SocketHandler(SocketTransportServiceTest* test) : test_(test) {}
+
+ void OnSocketReady(PRFileDesc* fd, int16_t outflags) override {
+ unsigned char buf[1600];
+
+ int32_t rv;
+ rv = PR_Recv(fd, buf, sizeof(buf), 0, PR_INTERVAL_NO_WAIT);
+ if (rv > 0) {
+ std::cerr << "Read " << rv << " bytes" << std::endl;
+ test_->ReceivePacket();
+ }
+ }
+
+ void OnSocketDetached(PRFileDesc* fd) override {}
+
+ void IsLocal(bool* aIsLocal) override {
+ // TODO(jesup): better check? Does it matter? (likely no)
+ *aIsLocal = false;
+ }
+
+ virtual uint64_t ByteCountSent() override { return 0; }
+ virtual uint64_t ByteCountReceived() override { return 0; }
+
+ NS_DECL_ISUPPORTS
+
+ protected:
+ virtual ~SocketHandler() = default;
+
+ private:
+ SocketTransportServiceTest* test_;
+};
+
+NS_IMPL_ISUPPORTS0(SocketHandler)
+
+void SocketTransportServiceTest::SetUp() {
+ MtransportTest::SetUp();
+
+ // Get the transport service as a dispatch target
+ nsresult rv;
+ target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ // Get the transport service as a transport service
+ stservice_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ // Create a loopback pipe
+ PRStatus status = PR_CreatePipe(&readpipe_, &writepipe_);
+ ASSERT_EQ(status, PR_SUCCESS);
+
+ // Register ourselves as a listener for the read side of the
+ // socket. The registration has to happen on the STS thread,
+ // hence this event stuff.
+ rv = target_->Dispatch(new RegisterEvent(this), 0);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_TRUE_WAIT(registered_, 10000);
+}
+
+void SocketTransportServiceTest::RegisterHandler() {
+ nsresult rv;
+
+ rv = stservice_->AttachSocket(readpipe_, new SocketHandler(this));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ registered_ = true;
+}
+
+void SocketTransportServiceTest::SendEvent() {
+ nsresult rv;
+
+ rv = target_->Dispatch(new EventReceived(this), 0);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_TRUE_WAIT(Received() == 1, 10000);
+}
+
+void SocketTransportServiceTest::SendPacket() {
+ unsigned char buffer[1024];
+ memset(buffer, 0, sizeof(buffer));
+
+ int32_t status = PR_Write(writepipe_, buffer, sizeof(buffer));
+ uint32_t size = status & 0xffff;
+ ASSERT_EQ(sizeof(buffer), size);
+}
+
+// The unit tests themselves
+TEST_F(SocketTransportServiceTest, SendEvent) { SendEvent(); }
+
+TEST_F(SocketTransportServiceTest, SendPacket) { SendPacket(); }
+
+} // end namespace
diff --git a/dom/media/webrtc/transport/test/stunserver.cpp b/dom/media/webrtc/transport/test/stunserver.cpp
new file mode 100644
index 0000000000..b5fce21e19
--- /dev/null
+++ b/dom/media/webrtc/transport/test/stunserver.cpp
@@ -0,0 +1,652 @@
+/* -*- 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
+
+/*
+Original code from nICEr and nrappkit.
+
+nICEr copyright:
+
+Copyright (c) 2007, Adobe Systems, Incorporated
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+* Neither the name of Adobe Systems, Network Resonance nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+nrappkit copyright:
+
+ Copyright (C) 2001-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, Inc.
+ All Rights Reserved
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+
+ ekr@rtfm.com Thu Dec 20 20:14:49 2001
+*/
+#include "logging.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mediapacket.h"
+
+// mozilla/utils.h defines this as well
+#ifdef UNIMPLEMENTED
+# undef UNIMPLEMENTED
+#endif
+
+extern "C" {
+#include "nr_api.h"
+#include "async_wait.h"
+#include "async_timer.h"
+#include "nr_socket.h"
+#include "nr_socket_local.h"
+#include "transport_addr.h"
+#include "stun_util.h"
+#include "registry.h"
+#include "nr_socket_buffered_stun.h"
+}
+
+#include "stunserver.h"
+
+#include <string>
+
+MOZ_MTLOG_MODULE("stunserver");
+
+namespace mozilla {
+
+// Wrapper nr_socket which allows us to lie to the stun server about the
+// IP address.
+struct nr_socket_wrapped {
+ nr_socket* sock_;
+ nr_transport_addr addr_;
+};
+
+static int nr_socket_wrapped_destroy(void** objp) {
+ if (!objp || !*objp) return 0;
+
+ nr_socket_wrapped* wrapped = static_cast<nr_socket_wrapped*>(*objp);
+ *objp = nullptr;
+
+ delete wrapped;
+
+ return 0;
+}
+
+static int nr_socket_wrapped_sendto(void* obj, const void* msg, size_t len,
+ int flags, const nr_transport_addr* addr) {
+ nr_socket_wrapped* wrapped = static_cast<nr_socket_wrapped*>(obj);
+
+ return nr_socket_sendto(wrapped->sock_, msg, len, flags, &wrapped->addr_);
+}
+
+static int nr_socket_wrapped_recvfrom(void* obj, void* restrict buf,
+ size_t maxlen, size_t* len, int flags,
+ nr_transport_addr* addr) {
+ nr_socket_wrapped* wrapped = static_cast<nr_socket_wrapped*>(obj);
+
+ return nr_socket_recvfrom(wrapped->sock_, buf, maxlen, len, flags, addr);
+}
+
+static int nr_socket_wrapped_getfd(void* obj, NR_SOCKET* fd) {
+ nr_socket_wrapped* wrapped = static_cast<nr_socket_wrapped*>(obj);
+
+ return nr_socket_getfd(wrapped->sock_, fd);
+}
+
+static int nr_socket_wrapped_getaddr(void* obj, nr_transport_addr* addrp) {
+ nr_socket_wrapped* wrapped = static_cast<nr_socket_wrapped*>(obj);
+
+ return nr_socket_getaddr(wrapped->sock_, addrp);
+}
+
+static int nr_socket_wrapped_close(void* obj) { MOZ_CRASH(); }
+
+static int nr_socket_wrapped_set_send_addr(nr_socket* sock,
+ nr_transport_addr* addr) {
+ nr_socket_wrapped* wrapped = static_cast<nr_socket_wrapped*>(sock->obj);
+
+ return nr_transport_addr_copy(&wrapped->addr_, addr);
+}
+
+static nr_socket_vtbl nr_socket_wrapped_vtbl = {2,
+ nr_socket_wrapped_destroy,
+ nr_socket_wrapped_sendto,
+ nr_socket_wrapped_recvfrom,
+ nr_socket_wrapped_getfd,
+ nr_socket_wrapped_getaddr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nr_socket_wrapped_close,
+ nullptr,
+ nullptr};
+
+int nr_socket_wrapped_create(nr_socket* inner, nr_socket** outp) {
+ auto wrapped = MakeUnique<nr_socket_wrapped>();
+
+ wrapped->sock_ = inner;
+
+ int r = nr_socket_create_int(wrapped.get(), &nr_socket_wrapped_vtbl, outp);
+ if (r) return r;
+
+ Unused << wrapped.release();
+ return 0;
+}
+
+// Instance static.
+// Note: Calling Create() at static init time is not going to be safe, since
+// we have no reason to expect this will be initted to a nullptr yet.
+TestStunServer* TestStunServer::instance;
+TestStunTcpServer* TestStunTcpServer::instance;
+TestStunServer* TestStunServer::instance6;
+TestStunTcpServer* TestStunTcpServer::instance6;
+uint16_t TestStunServer::instance_port = 3478;
+uint16_t TestStunTcpServer::instance_port = 3478;
+
+TestStunServer::~TestStunServer() {
+ // TODO(ekr@rtfm.com): Put this on the right thread.
+
+ // Unhook callback from our listen socket.
+ if (listen_sock_) {
+ NR_SOCKET fd;
+ if (!nr_socket_getfd(listen_sock_, &fd)) {
+ NR_ASYNC_CANCEL(fd, NR_ASYNC_WAIT_READ);
+ }
+ }
+
+ // Free up stun context and network resources
+ nr_stun_server_ctx_destroy(&stun_server_);
+ nr_socket_destroy(&listen_sock_);
+ nr_socket_destroy(&send_sock_);
+
+ // Make sure we aren't still waiting on a deferred response timer to pop
+ if (timer_handle_) NR_async_timer_cancel(timer_handle_);
+
+ delete response_addr_;
+}
+
+int TestStunServer::SetInternalPort(nr_local_addr* addr, uint16_t port) {
+ if (nr_transport_addr_set_port(&addr->addr, port)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't set port");
+ return R_INTERNAL;
+ }
+
+ if (nr_transport_addr_fmt_addr_string(&addr->addr)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't re-set addr string");
+ return R_INTERNAL;
+ }
+
+ return 0;
+}
+
+int TestStunServer::TryOpenListenSocket(nr_local_addr* addr, uint16_t port) {
+ int r = SetInternalPort(addr, port);
+
+ if (r) return r;
+
+ if (nr_socket_local_create(nullptr, &addr->addr, &listen_sock_)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't create listen socket");
+ return R_ALREADY;
+ }
+
+ return 0;
+}
+
+static int addressFamilyToIpVersion(int address_family) {
+ switch (address_family) {
+ case AF_INET:
+ return NR_IPV4;
+ case AF_INET6:
+ return NR_IPV6;
+ default:
+ MOZ_CRASH();
+ }
+ return NR_IPV4;
+}
+
+int TestStunServer::Initialize(int address_family) {
+ static const size_t max_addrs = 100;
+ nr_local_addr addrs[max_addrs];
+ int addr_ct;
+ int r;
+ int i;
+
+ r = nr_stun_find_local_addresses(addrs, max_addrs, &addr_ct);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't retrieve addresses");
+ return R_INTERNAL;
+ }
+
+ // removes duplicates and, based on prefs, loopback and link_local addrs
+ r = nr_stun_filter_local_addresses(addrs, &addr_ct);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't filter addresses");
+ return R_INTERNAL;
+ }
+
+ if (addr_ct < 1) {
+ MOZ_MTLOG(ML_ERROR, "No local addresses");
+ return R_INTERNAL;
+ }
+
+ for (i = 0; i < addr_ct; ++i) {
+ if (addrs[i].addr.ip_version == addressFamilyToIpVersion(address_family)) {
+ break;
+ }
+ }
+
+ if (i == addr_ct) {
+ MOZ_MTLOG(ML_ERROR, "No local addresses of the configured IP version");
+ return R_INTERNAL;
+ }
+
+ int tries = 100;
+ while (tries--) {
+ // Bind on configured port (default 3478)
+ r = TryOpenListenSocket(&addrs[i], instance_port);
+ // We interpret R_ALREADY to mean the addr is probably in use. Try another.
+ // Otherwise, it either worked or it didn't, and we check below.
+ if (r != R_ALREADY) {
+ break;
+ }
+ ++instance_port;
+ }
+
+ if (r) {
+ return R_INTERNAL;
+ }
+
+ r = nr_socket_wrapped_create(listen_sock_, &send_sock_);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't create send socket");
+ return R_INTERNAL;
+ }
+
+ r = nr_stun_server_ctx_create(const_cast<char*>("Test STUN server"),
+ &stun_server_);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't create STUN server");
+ return R_INTERNAL;
+ }
+
+ // Cache the address and port.
+ char addr_string[INET6_ADDRSTRLEN];
+ r = nr_transport_addr_get_addrstring(&addrs[i].addr, addr_string,
+ sizeof(addr_string));
+ if (r) {
+ MOZ_MTLOG(ML_ERROR,
+ "Failed to convert listen addr to a string representation");
+ return R_INTERNAL;
+ }
+
+ listen_addr_ = addr_string;
+ listen_port_ = instance_port;
+
+ return 0;
+}
+
+UniquePtr<TestStunServer> TestStunServer::Create(int address_family) {
+ NR_reg_init(NR_REG_MODE_LOCAL);
+
+ UniquePtr<TestStunServer> server(new TestStunServer());
+
+ if (server->Initialize(address_family)) return nullptr;
+
+ NR_SOCKET fd;
+ int r = nr_socket_getfd(server->listen_sock_, &fd);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't get fd");
+ return nullptr;
+ }
+
+ NR_ASYNC_WAIT(fd, NR_ASYNC_WAIT_READ, &TestStunServer::readable_cb,
+ server.get());
+
+ return server;
+}
+
+void TestStunServer::ConfigurePort(uint16_t port) { instance_port = port; }
+
+TestStunServer* TestStunServer::GetInstance(int address_family) {
+ switch (address_family) {
+ case AF_INET:
+ if (!instance) instance = Create(address_family).release();
+
+ MOZ_ASSERT(instance);
+ return instance;
+ case AF_INET6:
+ if (!instance6) instance6 = Create(address_family).release();
+
+ return instance6;
+ default:
+ MOZ_CRASH();
+ }
+}
+
+void TestStunServer::ShutdownInstance() {
+ delete instance;
+ instance = nullptr;
+ delete instance6;
+ instance6 = nullptr;
+}
+
+struct DeferredStunOperation {
+ DeferredStunOperation(TestStunServer* server, const char* data, size_t len,
+ nr_transport_addr* addr, nr_socket* sock)
+ : server_(server), buffer_(), sock_(sock) {
+ buffer_.Copy(reinterpret_cast<const uint8_t*>(data), len);
+ nr_transport_addr_copy(&addr_, addr);
+ }
+
+ TestStunServer* server_;
+ MediaPacket buffer_;
+ nr_transport_addr addr_;
+ nr_socket* sock_;
+};
+
+void TestStunServer::Process(const uint8_t* msg, size_t len,
+ nr_transport_addr* addr, nr_socket* sock) {
+ if (!sock) {
+ sock = send_sock_;
+ }
+
+ // Set the wrapped address so that the response goes to the right place.
+ nr_socket_wrapped_set_send_addr(sock, addr);
+
+ nr_stun_server_process_request(
+ stun_server_, sock, const_cast<char*>(reinterpret_cast<const char*>(msg)),
+ len, response_addr_ ? response_addr_ : addr, NR_STUN_AUTH_RULE_OPTIONAL);
+}
+
+void TestStunServer::process_cb(NR_SOCKET s, int how, void* cb_arg) {
+ DeferredStunOperation* op = static_cast<DeferredStunOperation*>(cb_arg);
+ op->server_->timer_handle_ = nullptr;
+ op->server_->Process(op->buffer_.data(), op->buffer_.len(), &op->addr_,
+ op->sock_);
+
+ delete op;
+}
+
+nr_socket* TestStunServer::GetReceivingSocket(NR_SOCKET s) {
+ return listen_sock_;
+}
+
+nr_socket* TestStunServer::GetSendingSocket(nr_socket* sock) {
+ return send_sock_;
+}
+
+void TestStunServer::readable_cb(NR_SOCKET s, int how, void* cb_arg) {
+ TestStunServer* server = static_cast<TestStunServer*>(cb_arg);
+
+ char message[max_stun_message_size];
+ size_t message_len;
+ nr_transport_addr addr;
+ nr_socket* recv_sock = server->GetReceivingSocket(s);
+ if (!recv_sock) {
+ MOZ_MTLOG(ML_ERROR, "Failed to lookup receiving socket");
+ return;
+ }
+ nr_socket* send_sock = server->GetSendingSocket(recv_sock);
+
+ /* Re-arm. */
+ NR_ASYNC_WAIT(s, NR_ASYNC_WAIT_READ, &TestStunServer::readable_cb, server);
+
+ if (nr_socket_recvfrom(recv_sock, message, sizeof(message), &message_len, 0,
+ &addr)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't read STUN message");
+ return;
+ }
+
+ MOZ_MTLOG(ML_DEBUG, "Received data of length " << message_len);
+
+ // If we have initial dropping set, check at this point.
+ std::string key(addr.as_string);
+
+ if (server->received_ct_.count(key) == 0) {
+ server->received_ct_[key] = 0;
+ }
+
+ ++server->received_ct_[key];
+
+ if (!server->active_ || (server->received_ct_[key] <= server->initial_ct_)) {
+ MOZ_MTLOG(ML_DEBUG, "Dropping message #" << server->received_ct_[key]
+ << " from " << key);
+ return;
+ }
+
+ if (server->delay_ms_) {
+ NR_ASYNC_TIMER_SET(server->delay_ms_, process_cb,
+ new DeferredStunOperation(server, message, message_len,
+ &addr, send_sock),
+ &server->timer_handle_);
+ } else {
+ server->Process(reinterpret_cast<const uint8_t*>(message), message_len,
+ &addr, send_sock);
+ }
+}
+
+void TestStunServer::SetActive(bool active) { active_ = active; }
+
+void TestStunServer::SetDelay(uint32_t delay_ms) { delay_ms_ = delay_ms; }
+
+void TestStunServer::SetDropInitialPackets(uint32_t count) {
+ initial_ct_ = count;
+}
+
+nsresult TestStunServer::SetResponseAddr(nr_transport_addr* addr) {
+ delete response_addr_;
+
+ response_addr_ = new nr_transport_addr();
+
+ int r = nr_transport_addr_copy(response_addr_, addr);
+ if (r) return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+nsresult TestStunServer::SetResponseAddr(const std::string& addr,
+ uint16_t port) {
+ nr_transport_addr addr2;
+
+ int r =
+ nr_str_port_to_transport_addr(addr.c_str(), port, IPPROTO_UDP, &addr2);
+ if (r) return NS_ERROR_FAILURE;
+
+ return SetResponseAddr(&addr2);
+}
+
+void TestStunServer::Reset() {
+ delay_ms_ = 0;
+ if (timer_handle_) {
+ NR_async_timer_cancel(timer_handle_);
+ timer_handle_ = nullptr;
+ }
+ delete response_addr_;
+ response_addr_ = nullptr;
+ received_ct_.clear();
+}
+
+// TestStunTcpServer
+
+void TestStunTcpServer::ConfigurePort(uint16_t port) { instance_port = port; }
+
+TestStunTcpServer* TestStunTcpServer::GetInstance(int address_family) {
+ switch (address_family) {
+ case AF_INET:
+ if (!instance) instance = Create(address_family).release();
+
+ MOZ_ASSERT(instance);
+ return instance;
+ case AF_INET6:
+ if (!instance6) instance6 = Create(address_family).release();
+
+ return instance6;
+ default:
+ MOZ_CRASH();
+ }
+}
+
+void TestStunTcpServer::ShutdownInstance() {
+ delete instance;
+ instance = nullptr;
+ delete instance6;
+ instance6 = nullptr;
+}
+
+int TestStunTcpServer::TryOpenListenSocket(nr_local_addr* addr, uint16_t port) {
+ addr->addr.protocol = IPPROTO_TCP;
+
+ int r = SetInternalPort(addr, port);
+
+ if (r) return r;
+
+ nr_socket* sock;
+ if (nr_socket_local_create(nullptr, &addr->addr, &sock)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't create listen tcp socket");
+ return R_ALREADY;
+ }
+
+ if (nr_socket_buffered_stun_create(sock, 2048, TURN_TCP_FRAMING,
+ &listen_sock_)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't create listen tcp socket");
+ return R_ALREADY;
+ }
+
+ if (nr_socket_listen(listen_sock_, 10)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't listen on socket");
+ return R_ALREADY;
+ }
+
+ return 0;
+}
+
+nr_socket* TestStunTcpServer::GetReceivingSocket(NR_SOCKET s) {
+ return connections_[s];
+}
+
+nr_socket* TestStunTcpServer::GetSendingSocket(nr_socket* sock) { return sock; }
+
+void TestStunTcpServer::accept_cb(NR_SOCKET s, int how, void* cb_arg) {
+ TestStunTcpServer* server = static_cast<TestStunTcpServer*>(cb_arg);
+ nr_socket *newsock, *bufsock, *wrapsock;
+ nr_transport_addr remote_addr;
+ NR_SOCKET fd;
+
+ /* rearm */
+ NR_ASYNC_WAIT(s, NR_ASYNC_WAIT_READ, &TestStunTcpServer::accept_cb, cb_arg);
+
+ /* accept */
+ if (nr_socket_accept(server->listen_sock_, &remote_addr, &newsock)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't accept incoming tcp connection");
+ return;
+ }
+
+ if (nr_socket_buffered_stun_create(newsock, 2048, TURN_TCP_FRAMING,
+ &bufsock)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't create connected tcp socket");
+ nr_socket_destroy(&newsock);
+ return;
+ }
+
+ nr_socket_buffered_set_connected_to(bufsock, &remote_addr);
+
+ if (nr_socket_wrapped_create(bufsock, &wrapsock)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't wrap connected tcp socket");
+ nr_socket_destroy(&bufsock);
+ return;
+ }
+
+ if (nr_socket_getfd(wrapsock, &fd)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't get fd from connected tcp socket");
+ nr_socket_destroy(&wrapsock);
+ return;
+ }
+
+ server->connections_[fd] = wrapsock;
+
+ NR_ASYNC_WAIT(fd, NR_ASYNC_WAIT_READ, &TestStunServer::readable_cb, server);
+}
+
+UniquePtr<TestStunTcpServer> TestStunTcpServer::Create(int address_family) {
+ NR_reg_init(NR_REG_MODE_LOCAL);
+
+ UniquePtr<TestStunTcpServer> server(new TestStunTcpServer());
+
+ if (server->Initialize(address_family)) {
+ return nullptr;
+ }
+
+ NR_SOCKET fd;
+ if (nr_socket_getfd(server->listen_sock_, &fd)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't get tcp fd");
+ return nullptr;
+ }
+
+ NR_ASYNC_WAIT(fd, NR_ASYNC_WAIT_READ, &TestStunTcpServer::accept_cb,
+ server.get());
+
+ return server;
+}
+
+TestStunTcpServer::~TestStunTcpServer() {
+ for (auto it = connections_.begin(); it != connections_.end();) {
+ NR_ASYNC_CANCEL(it->first, NR_ASYNC_WAIT_READ);
+ nr_socket_destroy(&it->second);
+ connections_.erase(it++);
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/test/stunserver.h b/dom/media/webrtc/transport/test/stunserver.h
new file mode 100644
index 0000000000..4903cb89ad
--- /dev/null
+++ b/dom/media/webrtc/transport/test/stunserver.h
@@ -0,0 +1,123 @@
+/* -*- 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
+
+#ifndef stunserver_h__
+#define stunserver_h__
+
+#include <map>
+#include <string>
+#include "nsError.h"
+#include "mozilla/UniquePtr.h"
+
+typedef struct nr_stun_server_ctx_ nr_stun_server_ctx;
+typedef struct nr_socket_ nr_socket;
+typedef struct nr_local_addr_ nr_local_addr;
+
+namespace mozilla {
+
+class TestStunServer {
+ public:
+ // Generally, you should only call API in this class from the same thread that
+ // the initial |GetInstance| call was made from.
+ static TestStunServer* GetInstance(int address_family = AF_INET);
+ static void ShutdownInstance();
+ // |ConfigurePort| will only have an effect if called before the first call
+ // to |GetInstance| (possibly following a |ShutdownInstance| call)
+ static void ConfigurePort(uint16_t port);
+ // AF_INET, AF_INET6
+ static UniquePtr<TestStunServer> Create(int address_family);
+
+ virtual ~TestStunServer();
+
+ void SetActive(bool active);
+ void SetDelay(uint32_t delay_ms);
+ void SetDropInitialPackets(uint32_t count);
+ const std::string& addr() const { return listen_addr_; }
+ uint16_t port() const { return listen_port_; }
+
+ // These should only be called from the same thread as the initial
+ // |GetInstance| call.
+ nsresult SetResponseAddr(nr_transport_addr* addr);
+ nsresult SetResponseAddr(const std::string& addr, uint16_t port);
+
+ void Reset();
+
+ static const size_t max_stun_message_size = 4096;
+
+ virtual nr_socket* GetReceivingSocket(NR_SOCKET s);
+ virtual nr_socket* GetSendingSocket(nr_socket* sock);
+
+ protected:
+ TestStunServer()
+ : listen_port_(0),
+ listen_sock_(nullptr),
+ send_sock_(nullptr),
+ stun_server_(nullptr),
+ active_(true),
+ delay_ms_(0),
+ initial_ct_(0),
+ response_addr_(nullptr),
+ timer_handle_(nullptr) {}
+
+ int SetInternalPort(nr_local_addr* addr, uint16_t port);
+ int Initialize(int address_family);
+
+ static void readable_cb(NR_SOCKET sock, int how, void* cb_arg);
+
+ private:
+ void Process(const uint8_t* msg, size_t len, nr_transport_addr* addr_in,
+ nr_socket* sock);
+ virtual int TryOpenListenSocket(nr_local_addr* addr, uint16_t port);
+ static void process_cb(NR_SOCKET sock, int how, void* cb_arg);
+
+ protected:
+ std::string listen_addr_;
+ uint16_t listen_port_;
+ nr_socket* listen_sock_;
+ nr_socket* send_sock_;
+ nr_stun_server_ctx* stun_server_;
+
+ private:
+ bool active_;
+ uint32_t delay_ms_;
+ uint32_t initial_ct_;
+ nr_transport_addr* response_addr_;
+ void* timer_handle_;
+ std::map<std::string, uint32_t> received_ct_;
+
+ static TestStunServer* instance;
+ static TestStunServer* instance6;
+ static uint16_t instance_port;
+};
+
+class TestStunTcpServer : public TestStunServer {
+ public:
+ static TestStunTcpServer* GetInstance(int address_family);
+ static void ShutdownInstance();
+ static void ConfigurePort(uint16_t port);
+ virtual ~TestStunTcpServer();
+
+ virtual nr_socket* GetReceivingSocket(NR_SOCKET s);
+ virtual nr_socket* GetSendingSocket(nr_socket* sock);
+
+ protected:
+ TestStunTcpServer() = default;
+ static void accept_cb(NR_SOCKET sock, int how, void* cb_arg);
+
+ private:
+ virtual int TryOpenListenSocket(nr_local_addr* addr, uint16_t port);
+ static UniquePtr<TestStunTcpServer> Create(int address_family);
+
+ static TestStunTcpServer* instance;
+ static TestStunTcpServer* instance6;
+ static uint16_t instance_port;
+
+ std::map<NR_SOCKET, nr_socket*> connections_;
+};
+} // End of namespace mozilla
+#endif
diff --git a/dom/media/webrtc/transport/test/test_nr_socket_ice_unittest.cpp b/dom/media/webrtc/transport/test/test_nr_socket_ice_unittest.cpp
new file mode 100644
index 0000000000..b55b05f10c
--- /dev/null
+++ b/dom/media/webrtc/transport/test/test_nr_socket_ice_unittest.cpp
@@ -0,0 +1,409 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Some of this code is taken from nricectx.cpp and nricemediastream.cpp
+// which in turn contains code cut-and-pasted from nICEr. Copyright is:
+
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+* Neither the name of Adobe Systems, Network Resonance nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+extern "C" {
+#include "ice_ctx.h"
+#include "ice_peer_ctx.h"
+#include "nICEr/src/net/transport_addr.h"
+}
+
+#include "mtransport_test_utils.h"
+#include "nricectx.h"
+#include "nricemediastream.h"
+#include "runnable_utils.h"
+#include "test_nr_socket.h"
+
+namespace mozilla {
+
+static unsigned int kDefaultTimeout = 7000;
+
+class IcePeer {
+ public:
+ IcePeer(const char* name, TestNat* nat, UINT4 flags,
+ MtransportTestUtils* test_utils)
+ : name_(name),
+ ice_checking_(false),
+ ice_connected_(false),
+ ice_disconnected_(false),
+ gather_cb_(false),
+ stream_ready_(false),
+ stream_failed_(false),
+ ice_ctx_(nullptr),
+ peer_ctx_(nullptr),
+ nat_(nat),
+ test_utils_(test_utils) {
+ nr_ice_ctx_create(const_cast<char*>(name_.c_str()), flags, &ice_ctx_);
+
+ if (nat_) {
+ nr_socket_factory* factory;
+ nat_->create_socket_factory(&factory);
+ nr_ice_ctx_set_socket_factory(ice_ctx_, factory);
+ }
+
+ // Create the handler objects
+ ice_handler_vtbl_ = new nr_ice_handler_vtbl();
+ ice_handler_vtbl_->select_pair = &IcePeer::select_pair;
+ ice_handler_vtbl_->stream_ready = &IcePeer::stream_ready;
+ ice_handler_vtbl_->stream_failed = &IcePeer::stream_failed;
+ ice_handler_vtbl_->ice_connected = &IcePeer::ice_connected;
+ ice_handler_vtbl_->msg_recvd = &IcePeer::msg_recvd;
+ ice_handler_vtbl_->ice_checking = &IcePeer::ice_checking;
+ ice_handler_vtbl_->ice_disconnected = &IcePeer::ice_disconnected;
+
+ ice_handler_ = new nr_ice_handler();
+ ice_handler_->vtbl = ice_handler_vtbl_;
+ ice_handler_->obj = this;
+
+ nr_ice_peer_ctx_create(ice_ctx_, ice_handler_,
+ const_cast<char*>(name_.c_str()), &peer_ctx_);
+
+ nr_ice_add_media_stream(ice_ctx_, const_cast<char*>(name_.c_str()), "ufrag",
+ "pass", 2, &ice_media_stream_);
+ EXPECT_EQ(2UL, GetStreamAttributes().size());
+
+ nr_ice_media_stream_initialize(ice_ctx_, ice_media_stream_);
+ }
+
+ virtual ~IcePeer() { Destroy(); }
+
+ void Destroy() {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(this, &IcePeer::Destroy_s));
+ }
+
+ void Destroy_s() {
+ nr_ice_peer_ctx_destroy(&peer_ctx_);
+ delete ice_handler_;
+ delete ice_handler_vtbl_;
+ nr_ice_ctx_destroy(&ice_ctx_);
+ }
+
+ void Gather(bool default_route_only = false) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &IcePeer::Gather_s, default_route_only));
+ }
+
+ void Gather_s(bool default_route_only = false) {
+ int r = nr_ice_gather(ice_ctx_, &IcePeer::gather_cb, this);
+ ASSERT_TRUE(r == 0 || r == R_WOULDBLOCK);
+ }
+
+ std::vector<std::string> GetStreamAttributes() {
+ std::vector<std::string> attributes;
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&attributes, this, &IcePeer::GetStreamAttributes_s));
+ return attributes;
+ }
+
+ std::vector<std::string> GetStreamAttributes_s() {
+ char** attrs = nullptr;
+ int attrct;
+ std::vector<std::string> ret;
+
+ int r =
+ nr_ice_media_stream_get_attributes(ice_media_stream_, &attrs, &attrct);
+ EXPECT_EQ(0, r);
+
+ for (int i = 0; i < attrct; i++) {
+ ret.push_back(std::string(attrs[i]));
+ RFREE(attrs[i]);
+ }
+ RFREE(attrs);
+
+ return ret;
+ }
+
+ std::vector<std::string> GetGlobalAttributes() {
+ std::vector<std::string> attributes;
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&attributes, this, &IcePeer::GetGlobalAttributes_s));
+ return attributes;
+ }
+
+ std::vector<std::string> GetGlobalAttributes_s() {
+ char** attrs = nullptr;
+ int attrct;
+ std::vector<std::string> ret;
+
+ nr_ice_get_global_attributes(ice_ctx_, &attrs, &attrct);
+
+ for (int i = 0; i < attrct; i++) {
+ ret.push_back(std::string(attrs[i]));
+ RFREE(attrs[i]);
+ }
+ RFREE(attrs);
+
+ return ret;
+ }
+
+ void ParseGlobalAttributes(std::vector<std::string> attrs) {
+ std::vector<char*> attrs_in;
+ attrs_in.reserve(attrs.size());
+ for (auto& attr : attrs) {
+ attrs_in.push_back(const_cast<char*>(attr.c_str()));
+ }
+
+ int r = nr_ice_peer_ctx_parse_global_attributes(
+ peer_ctx_, attrs_in.empty() ? nullptr : &attrs_in[0], attrs_in.size());
+ ASSERT_EQ(0, r);
+ }
+
+ void SetControlling(bool controlling) {
+ peer_ctx_->controlling = controlling ? 1 : 0;
+ }
+
+ void SetRemoteAttributes(std::vector<std::string> attributes) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &IcePeer::SetRemoteAttributes_s, attributes));
+ }
+
+ void SetRemoteAttributes_s(std::vector<std::string> attributes) {
+ int r;
+
+ std::vector<char*> attrs;
+ attrs.reserve(attributes.size());
+ for (auto& attr : attributes) {
+ attrs.push_back(const_cast<char*>(attr.c_str()));
+ }
+
+ if (!attrs.empty()) {
+ r = nr_ice_peer_ctx_parse_stream_attributes(peer_ctx_, ice_media_stream_,
+ &attrs[0], attrs.size());
+ ASSERT_EQ(0, r);
+ }
+ }
+
+ void StartChecks() {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(this, &IcePeer::StartChecks_s));
+ }
+
+ void StartChecks_s() {
+ int r = nr_ice_peer_ctx_pair_candidates(peer_ctx_);
+ ASSERT_EQ(0, r);
+
+ r = nr_ice_peer_ctx_start_checks2(peer_ctx_, 1);
+ ASSERT_EQ(0, r);
+ }
+
+ // Handler callbacks
+ static int select_pair(void* obj, nr_ice_media_stream* stream,
+ int component_id, nr_ice_cand_pair** potentials,
+ int potential_ct) {
+ return 0;
+ }
+
+ static int stream_ready(void* obj, nr_ice_media_stream* stream) {
+ IcePeer* peer = static_cast<IcePeer*>(obj);
+ peer->stream_ready_ = true;
+ return 0;
+ }
+
+ static int stream_failed(void* obj, nr_ice_media_stream* stream) {
+ IcePeer* peer = static_cast<IcePeer*>(obj);
+ peer->stream_failed_ = true;
+ return 0;
+ }
+
+ static int ice_checking(void* obj, nr_ice_peer_ctx* pctx) {
+ IcePeer* peer = static_cast<IcePeer*>(obj);
+ peer->ice_checking_ = true;
+ return 0;
+ }
+
+ static int ice_connected(void* obj, nr_ice_peer_ctx* pctx) {
+ IcePeer* peer = static_cast<IcePeer*>(obj);
+ peer->ice_connected_ = true;
+ return 0;
+ }
+
+ static int ice_disconnected(void* obj, nr_ice_peer_ctx* pctx) {
+ IcePeer* peer = static_cast<IcePeer*>(obj);
+ peer->ice_disconnected_ = true;
+ return 0;
+ }
+
+ static int msg_recvd(void* obj, nr_ice_peer_ctx* pctx,
+ nr_ice_media_stream* stream, int component_id,
+ UCHAR* msg, int len) {
+ return 0;
+ }
+
+ static void gather_cb(NR_SOCKET s, int h, void* arg) {
+ IcePeer* peer = static_cast<IcePeer*>(arg);
+ peer->gather_cb_ = true;
+ }
+
+ std::string name_;
+
+ bool ice_checking_;
+ bool ice_connected_;
+ bool ice_disconnected_;
+ bool gather_cb_;
+ bool stream_ready_;
+ bool stream_failed_;
+
+ nr_ice_ctx* ice_ctx_;
+ nr_ice_handler* ice_handler_;
+ nr_ice_handler_vtbl* ice_handler_vtbl_;
+ nr_ice_media_stream* ice_media_stream_;
+ nr_ice_peer_ctx* peer_ctx_;
+ TestNat* nat_;
+ MtransportTestUtils* test_utils_;
+};
+
+class TestNrSocketIceUnitTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ NSS_NoDB_Init(nullptr);
+ NSS_SetDomesticPolicy();
+
+ test_utils_ = new MtransportTestUtils();
+ test_utils2_ = new MtransportTestUtils();
+
+ NrIceCtx::InitializeGlobals(NrIceCtx::GlobalConfig());
+ }
+
+ void TearDown() override {
+ delete test_utils_;
+ delete test_utils2_;
+ }
+
+ MtransportTestUtils* test_utils_;
+ MtransportTestUtils* test_utils2_;
+};
+
+TEST_F(TestNrSocketIceUnitTest, TestIcePeer) {
+ IcePeer peer("IcePeer", nullptr, NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION,
+ test_utils_);
+ ASSERT_NE(peer.ice_ctx_, nullptr);
+ ASSERT_NE(peer.peer_ctx_, nullptr);
+ ASSERT_NE(peer.ice_media_stream_, nullptr);
+ ASSERT_EQ(2UL, peer.GetStreamAttributes().size())
+ << "Should have ice-ufrag and ice-pwd";
+ peer.Gather();
+ ASSERT_LT(2UL, peer.GetStreamAttributes().size())
+ << "Should have ice-ufrag, ice-pwd, and at least one candidate.";
+}
+
+TEST_F(TestNrSocketIceUnitTest, TestIcePeersNoNAT) {
+ IcePeer peer("IcePeer", nullptr, NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION,
+ test_utils_);
+ IcePeer peer2("IcePeer2", nullptr, NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION,
+ test_utils2_);
+ peer.SetControlling(true);
+ peer2.SetControlling(false);
+
+ peer.Gather();
+ peer2.Gather();
+ std::vector<std::string> attrs = peer.GetGlobalAttributes();
+ peer2.ParseGlobalAttributes(attrs);
+ std::vector<std::string> attributes = peer.GetStreamAttributes();
+ peer2.SetRemoteAttributes(attributes);
+
+ attrs = peer2.GetGlobalAttributes();
+ peer.ParseGlobalAttributes(attrs);
+ attributes = peer2.GetStreamAttributes();
+ peer.SetRemoteAttributes(attributes);
+ peer2.StartChecks();
+ peer.StartChecks();
+
+ ASSERT_TRUE_WAIT(peer.ice_connected_, kDefaultTimeout);
+ ASSERT_TRUE_WAIT(peer2.ice_connected_, kDefaultTimeout);
+}
+
+TEST_F(TestNrSocketIceUnitTest, TestIcePeersPacketLoss) {
+ IcePeer peer("IcePeer", nullptr, NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION,
+ test_utils_);
+
+ RefPtr<TestNat> nat(new TestNat);
+ class NatDelegate : public TestNat::NatDelegate {
+ public:
+ NatDelegate() : messages(0) {}
+
+ int on_read(TestNat* nat, void* buf, size_t maxlen, size_t* len) override {
+ return 0;
+ }
+
+ int on_sendto(TestNat* nat, const void* msg, size_t len, int flags,
+ const nr_transport_addr* to) override {
+ ++messages;
+ // 25% packet loss
+ if (messages % 4 == 0) {
+ return 1;
+ }
+ return 0;
+ }
+
+ int on_write(TestNat* nat, const void* msg, size_t len,
+ size_t* written) override {
+ return 0;
+ }
+
+ int messages;
+ } delegate;
+ nat->nat_delegate_ = &delegate;
+
+ IcePeer peer2("IcePeer2", nat, NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION,
+ test_utils2_);
+ peer.SetControlling(true);
+ peer2.SetControlling(false);
+
+ peer.Gather();
+ peer2.Gather();
+ std::vector<std::string> attrs = peer.GetGlobalAttributes();
+ peer2.ParseGlobalAttributes(attrs);
+ std::vector<std::string> attributes = peer.GetStreamAttributes();
+ peer2.SetRemoteAttributes(attributes);
+
+ attrs = peer2.GetGlobalAttributes();
+ peer.ParseGlobalAttributes(attrs);
+ attributes = peer2.GetStreamAttributes();
+ peer.SetRemoteAttributes(attributes);
+ peer2.StartChecks();
+ peer.StartChecks();
+
+ ASSERT_TRUE_WAIT(peer.ice_connected_, kDefaultTimeout);
+ ASSERT_TRUE_WAIT(peer2.ice_connected_, kDefaultTimeout);
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/test/test_nr_socket_unittest.cpp b/dom/media/webrtc/transport/test/test_nr_socket_unittest.cpp
new file mode 100644
index 0000000000..af2779accd
--- /dev/null
+++ b/dom/media/webrtc/transport/test/test_nr_socket_unittest.cpp
@@ -0,0 +1,800 @@
+/* -*- 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
+
+#include <cstddef>
+
+extern "C" {
+#include "r_errors.h"
+#include "async_wait.h"
+}
+
+#include "test_nr_socket.h"
+
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.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 {
+ SyncDispatchToSTS(WrapRunnable(this, &TestNrSocketTest::TearDown_s));
+
+ 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) {
+ SyncDispatchToSTS(WrapRunnable(this, &TestNrSocketTest::CreatePublicAddrs_s,
+ count, ip_str, proto));
+ }
+
+ 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;
+ SyncDispatchToSTS(WrapRunnableRet(&result, this,
+ &TestNrSocketTest::CreatePrivateAddrs_s,
+ size, ip_str, proto));
+ 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;
+ SyncDispatchToSTS(WrapRunnableRet(
+ &result, this, &TestNrSocketTest::SendData_s, from, via));
+ 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);
+ SyncDispatchToSTS(WrapRunnableRet(&result, this,
+ &TestNrSocketTest::RecvData_s, to,
+ sender_external_address));
+
+ 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;
+ SyncDispatchToSTS(
+ WrapRunnableRet(&r, this, &TestNrSocketTest::SendDataTcp_s, from));
+ 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;
+ }
+
+ SyncDispatchToSTS(WrapRunnableRet(
+ &r, this, &TestNrSocketTest::RecvDataTcp_s, accepted_sock));
+ 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;
+ }
+
+ SyncDispatchToSTS(WrapRunnableRet(
+ &r, this, &TestNrSocketTest::SendDataTcp_s, accepted_sock));
+ 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;
+ }
+
+ SyncDispatchToSTS(
+ WrapRunnableRet(&r, this, &TestNrSocketTest::RecvDataTcp_s, from));
+ 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;
+ SyncDispatchToSTS(WrapRunnableRet(&r, this, &TestNrSocketTest::GetAddress_s,
+ sock, address));
+ 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, &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;
+ SyncDispatchToSTS(
+ WrapRunnableRet(&r, this, &TestNrSocketTest::Listen_s, to));
+ if (r) {
+ std::cerr << "Listen_s failed: " << r << std::endl;
+ return false;
+ }
+
+ SyncDispatchToSTS(
+ WrapRunnableRet(&r, this, &TestNrSocketTest::Connect_s, from, to));
+ 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;
+ }
+
+ SyncDispatchToSTS(WrapRunnableRet(&r, this, &TestNrSocketTest::Accept_s, to,
+ accepted_sock));
+
+ if (r) {
+ std::cerr << "Accept_s failed: " << r << std::endl;
+ return false;
+ }
+ return true;
+ }
+
+ bool WaitForSocketState(NrSocketBase* sock, int state) {
+ MOZ_ASSERT(sock);
+ SyncDispatchToSTS(WrapRunnable(
+ this, &TestNrSocketTest::WaitForSocketState_s, sock, state));
+
+ bool res;
+ WAIT_(wait_done_for_main_, 500, res);
+ wait_done_for_main_ = false;
+
+ if (!res) {
+ SyncDispatchToSTS(
+ WrapRunnable(this, &TestNrSocketTest::CancelWait_s, sock, state));
+ }
+
+ 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);
+ }
+
+ void SyncDispatchToSTS(nsIRunnable* runnable) {
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "TestNrSocketTest::SyncDispatchToSTS"_ns, sts_, do_AddRef(runnable));
+ }
+
+ 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::NrSocketBase;
+using mozilla::TestNat;
+using mozilla::TestNrSocketTest;
+
+TEST_F(TestNrSocketTest, UnsafePortRejectedUDP) {
+ nr_transport_addr address;
+ ASSERT_FALSE(nr_str_port_to_transport_addr("127.0.0.1",
+ // ssh
+ 22, IPPROTO_UDP, &address));
+ ASSERT_TRUE(NrSocketBase::IsForbiddenAddress(&address));
+}
+
+TEST_F(TestNrSocketTest, UnsafePortRejectedTCP) {
+ nr_transport_addr address;
+ ASSERT_FALSE(nr_str_port_to_transport_addr("127.0.0.1",
+ // ssh
+ 22, IPPROTO_TCP, &address));
+ ASSERT_TRUE(NrSocketBase::IsForbiddenAddress(&address));
+}
+
+TEST_F(TestNrSocketTest, SafePortAcceptedUDP) {
+ nr_transport_addr address;
+ ASSERT_FALSE(nr_str_port_to_transport_addr("127.0.0.1",
+ // stuns
+ 5349, IPPROTO_UDP, &address));
+ ASSERT_FALSE(NrSocketBase::IsForbiddenAddress(&address));
+}
+
+TEST_F(TestNrSocketTest, SafePortAcceptedTCP) {
+ nr_transport_addr address;
+ ASSERT_FALSE(nr_str_port_to_transport_addr("127.0.0.1",
+ // turns
+ 5349, IPPROTO_TCP, &address));
+ ASSERT_FALSE(NrSocketBase::IsForbiddenAddress(&address));
+}
+
+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]));
+}
diff --git a/dom/media/webrtc/transport/test/transport_unittests.cpp b/dom/media/webrtc/transport/test/transport_unittests.cpp
new file mode 100644
index 0000000000..28f9359afb
--- /dev/null
+++ b/dom/media/webrtc/transport/test/transport_unittests.cpp
@@ -0,0 +1,1400 @@
+
+/* 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 <iostream>
+#include <string>
+#include <algorithm>
+#include <functional>
+
+#ifdef XP_MACOSX
+// ensure that Apple Security kit enum goes before "sslproto.h"
+# include <CoreFoundation/CFAvailability.h>
+# include <Security/CipherSuite.h>
+#endif
+
+#include "mozilla/UniquePtr.h"
+
+#include "sigslot.h"
+
+#include "logging.h"
+#include "ssl.h"
+#include "sslexp.h"
+#include "sslproto.h"
+
+#include "nsThreadUtils.h"
+
+#include "mediapacket.h"
+#include "dtlsidentity.h"
+#include "nricectx.h"
+#include "nricemediastream.h"
+#include "transportflow.h"
+#include "transportlayer.h"
+#include "transportlayerdtls.h"
+#include "transportlayerice.h"
+#include "transportlayerlog.h"
+#include "transportlayerloopback.h"
+
+#include "runnable_utils.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+using namespace mozilla;
+MOZ_MTLOG_MODULE("mtransport")
+
+const uint8_t kTlsChangeCipherSpecType = 0x14;
+const uint8_t kTlsHandshakeType = 0x16;
+
+const uint8_t kTlsHandshakeCertificate = 0x0b;
+const uint8_t kTlsHandshakeServerKeyExchange = 0x0c;
+
+const uint8_t kTlsFakeChangeCipherSpec[] = {
+ kTlsChangeCipherSpecType, // Type
+ 0xfe,
+ 0xff, // Version
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x10, // Fictitious sequence #
+ 0x00,
+ 0x01, // Length
+ 0x01 // Value
+};
+
+// Layer class which can't be initialized.
+class TransportLayerDummy : public TransportLayer {
+ public:
+ TransportLayerDummy(bool allow_init, bool* destroyed)
+ : allow_init_(allow_init), destroyed_(destroyed) {
+ *destroyed_ = false;
+ }
+
+ virtual ~TransportLayerDummy() { *destroyed_ = true; }
+
+ nsresult InitInternal() override {
+ return allow_init_ ? NS_OK : NS_ERROR_FAILURE;
+ }
+
+ TransportResult SendPacket(MediaPacket& packet) override {
+ MOZ_CRASH(); // Should never be called.
+ return 0;
+ }
+
+ TRANSPORT_LAYER_ID("lossy")
+
+ private:
+ bool allow_init_;
+ bool* destroyed_;
+};
+
+class Inspector {
+ public:
+ virtual ~Inspector() = default;
+
+ virtual void Inspect(TransportLayer* layer, const unsigned char* data,
+ size_t len) = 0;
+};
+
+// Class to simulate various kinds of network lossage
+class TransportLayerLossy : public TransportLayer {
+ public:
+ TransportLayerLossy() : loss_mask_(0), packet_(0), inspector_(nullptr) {}
+ ~TransportLayerLossy() = default;
+
+ TransportResult SendPacket(MediaPacket& packet) override {
+ MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "SendPacket(" << packet.len() << ")");
+
+ if (loss_mask_ & (1 << (packet_ % 32))) {
+ MOZ_MTLOG(ML_NOTICE, "Dropping packet");
+ ++packet_;
+ return packet.len();
+ }
+ if (inspector_) {
+ inspector_->Inspect(this, packet.data(), packet.len());
+ }
+
+ ++packet_;
+
+ return downward_->SendPacket(packet);
+ }
+
+ void SetLoss(uint32_t packet) { loss_mask_ |= (1 << (packet & 32)); }
+
+ void SetInspector(UniquePtr<Inspector> inspector) {
+ inspector_ = std::move(inspector);
+ }
+
+ void StateChange(TransportLayer* layer, State state) { TL_SET_STATE(state); }
+
+ void PacketReceived(TransportLayer* layer, MediaPacket& packet) {
+ SignalPacketReceived(this, packet);
+ }
+
+ TRANSPORT_LAYER_ID("lossy")
+
+ protected:
+ void WasInserted() override {
+ downward_->SignalPacketReceived.connect(
+ this, &TransportLayerLossy::PacketReceived);
+ downward_->SignalStateChange.connect(this,
+ &TransportLayerLossy::StateChange);
+
+ TL_SET_STATE(downward_->state());
+ }
+
+ private:
+ uint32_t loss_mask_;
+ uint32_t packet_;
+ UniquePtr<Inspector> inspector_;
+};
+
+// Process DTLS Records
+#define CHECK_LENGTH(expected) \
+ do { \
+ EXPECT_GE(remaining(), expected); \
+ if (remaining() < expected) return false; \
+ } while (0)
+
+class TlsParser {
+ public:
+ TlsParser(const unsigned char* data, size_t len) : buffer_(), offset_(0) {
+ buffer_.Copy(data, len);
+ }
+
+ bool Read(unsigned char* val) {
+ if (remaining() < 1) {
+ return false;
+ }
+ *val = *ptr();
+ consume(1);
+ return true;
+ }
+
+ // Read an integral type of specified width.
+ bool Read(uint32_t* val, size_t len) {
+ if (len > sizeof(uint32_t)) return false;
+
+ *val = 0;
+
+ for (size_t i = 0; i < len; ++i) {
+ unsigned char tmp;
+
+ if (!Read(&tmp)) return false;
+
+ (*val) = ((*val) << 8) + tmp;
+ }
+
+ return true;
+ }
+
+ bool Read(unsigned char* val, size_t len) {
+ if (remaining() < len) {
+ return false;
+ }
+
+ if (val) {
+ memcpy(val, ptr(), len);
+ }
+ consume(len);
+
+ return true;
+ }
+
+ private:
+ size_t remaining() const { return buffer_.len() - offset_; }
+ const uint8_t* ptr() const { return buffer_.data() + offset_; }
+ void consume(size_t len) { offset_ += len; }
+
+ MediaPacket buffer_;
+ size_t offset_;
+};
+
+class DtlsRecordParser {
+ public:
+ DtlsRecordParser(const unsigned char* data, size_t len)
+ : buffer_(), offset_(0) {
+ buffer_.Copy(data, len);
+ }
+
+ bool NextRecord(uint8_t* ct, UniquePtr<MediaPacket>* buffer) {
+ if (!remaining()) return false;
+
+ CHECK_LENGTH(13U);
+ const uint8_t* ctp = reinterpret_cast<const uint8_t*>(ptr());
+ consume(11); // ct + version + length
+
+ const uint16_t* tmp = reinterpret_cast<const uint16_t*>(ptr());
+ size_t length = ntohs(*tmp);
+ consume(2);
+
+ CHECK_LENGTH(length);
+ auto db = MakeUnique<MediaPacket>();
+ db->Copy(ptr(), length);
+ consume(length);
+
+ *ct = *ctp;
+ *buffer = std::move(db);
+
+ return true;
+ }
+
+ private:
+ size_t remaining() const { return buffer_.len() - offset_; }
+ const uint8_t* ptr() const { return buffer_.data() + offset_; }
+ void consume(size_t len) { offset_ += len; }
+
+ MediaPacket buffer_;
+ size_t offset_;
+};
+
+// Inspector that parses out DTLS records and passes
+// them on.
+class DtlsRecordInspector : public Inspector {
+ public:
+ virtual void Inspect(TransportLayer* layer, const unsigned char* data,
+ size_t len) {
+ DtlsRecordParser parser(data, len);
+
+ uint8_t ct;
+ UniquePtr<MediaPacket> buf;
+ while (parser.NextRecord(&ct, &buf)) {
+ OnRecord(layer, ct, buf->data(), buf->len());
+ }
+ }
+
+ virtual void OnRecord(TransportLayer* layer, uint8_t content_type,
+ const unsigned char* record, size_t len) = 0;
+};
+
+// Inspector that injects arbitrary packets based on
+// DTLS records of various types.
+class DtlsInspectorInjector : public DtlsRecordInspector {
+ public:
+ DtlsInspectorInjector(uint8_t packet_type, uint8_t handshake_type,
+ const unsigned char* data, size_t len)
+ : packet_type_(packet_type), handshake_type_(handshake_type) {
+ packet_.Copy(data, len);
+ }
+
+ virtual void OnRecord(TransportLayer* layer, uint8_t content_type,
+ const unsigned char* data, size_t len) {
+ // Only inject once.
+ if (!packet_.data()) {
+ return;
+ }
+
+ // Check that the first byte is as requested.
+ if (content_type != packet_type_) {
+ return;
+ }
+
+ if (handshake_type_ != 0xff) {
+ // Check that the packet is plausibly long enough.
+ if (len < 1) {
+ return;
+ }
+
+ // Check that the handshake type is as requested.
+ if (data[0] != handshake_type_) {
+ return;
+ }
+ }
+
+ layer->SendPacket(packet_);
+ packet_.Reset();
+ }
+
+ private:
+ uint8_t packet_type_;
+ uint8_t handshake_type_;
+ MediaPacket packet_;
+};
+
+// Make a copy of the first instance of a message.
+class DtlsInspectorRecordHandshakeMessage : public DtlsRecordInspector {
+ public:
+ explicit DtlsInspectorRecordHandshakeMessage(uint8_t handshake_type)
+ : handshake_type_(handshake_type), buffer_() {}
+
+ virtual void OnRecord(TransportLayer* layer, uint8_t content_type,
+ const unsigned char* data, size_t len) {
+ // Only do this once.
+ if (buffer_.len()) {
+ return;
+ }
+
+ // Check that the first byte is as requested.
+ if (content_type != kTlsHandshakeType) {
+ return;
+ }
+
+ TlsParser parser(data, len);
+ unsigned char message_type;
+ // Read the handshake message type.
+ if (!parser.Read(&message_type)) {
+ return;
+ }
+ if (message_type != handshake_type_) {
+ return;
+ }
+
+ uint32_t length;
+ if (!parser.Read(&length, 3)) {
+ return;
+ }
+
+ uint32_t message_seq;
+ if (!parser.Read(&message_seq, 2)) {
+ return;
+ }
+
+ uint32_t fragment_offset;
+ if (!parser.Read(&fragment_offset, 3)) {
+ return;
+ }
+
+ uint32_t fragment_length;
+ if (!parser.Read(&fragment_length, 3)) {
+ return;
+ }
+
+ if ((fragment_offset != 0) || (fragment_length != length)) {
+ // This shouldn't happen because all current tests where we
+ // are using this code don't fragment.
+ return;
+ }
+
+ UniquePtr<uint8_t[]> buffer(new uint8_t[length]);
+ if (!parser.Read(buffer.get(), length)) {
+ return;
+ }
+ buffer_.Take(std::move(buffer), length);
+ }
+
+ const MediaPacket& buffer() { return buffer_; }
+
+ private:
+ uint8_t handshake_type_;
+ MediaPacket buffer_;
+};
+
+class TlsServerKeyExchangeECDHE {
+ public:
+ bool Parse(const unsigned char* data, size_t len) {
+ TlsParser parser(data, len);
+
+ uint8_t curve_type;
+ if (!parser.Read(&curve_type)) {
+ return false;
+ }
+
+ if (curve_type != 3) { // named_curve
+ return false;
+ }
+
+ uint32_t named_curve;
+ if (!parser.Read(&named_curve, 2)) {
+ return false;
+ }
+
+ uint32_t point_length;
+ if (!parser.Read(&point_length, 1)) {
+ return false;
+ }
+
+ UniquePtr<uint8_t[]> key(new uint8_t[point_length]);
+ if (!parser.Read(key.get(), point_length)) {
+ return false;
+ }
+ public_key_.Take(std::move(key), point_length);
+
+ return true;
+ }
+
+ MediaPacket public_key_;
+};
+
+namespace {
+class TransportTestPeer : public sigslot::has_slots<> {
+ public:
+ TransportTestPeer(nsCOMPtr<nsIEventTarget> target, std::string name,
+ MtransportTestUtils* utils)
+ : name_(name),
+ offerer_(name == "P1"),
+ target_(target),
+ received_packets_(0),
+ received_bytes_(0),
+ flow_(new TransportFlow(name)),
+ loopback_(new TransportLayerLoopback()),
+ logging_(new TransportLayerLogging()),
+ lossy_(new TransportLayerLossy()),
+ dtls_(new TransportLayerDtls()),
+ identity_(DtlsIdentity::Generate()),
+ ice_ctx_(),
+ streams_(),
+ peer_(nullptr),
+ gathering_complete_(false),
+ digest_("sha-1"),
+ enabled_cipersuites_(),
+ disabled_cipersuites_(),
+ test_utils_(utils) {
+ NrIceCtx::InitializeGlobals(NrIceCtx::GlobalConfig());
+ ice_ctx_ = NrIceCtx::Create(name);
+ std::vector<NrIceStunServer> stun_servers;
+ UniquePtr<NrIceStunServer> server(NrIceStunServer::Create(
+ std::string((char*)"stun.services.mozilla.com"), 3478));
+ stun_servers.push_back(*server);
+ EXPECT_TRUE(NS_SUCCEEDED(ice_ctx_->SetStunServers(stun_servers)));
+
+ dtls_->SetIdentity(identity_);
+ dtls_->SetRole(offerer_ ? TransportLayerDtls::SERVER
+ : TransportLayerDtls::CLIENT);
+
+ nsresult res = identity_->ComputeFingerprint(&digest_);
+ EXPECT_TRUE(NS_SUCCEEDED(res));
+ EXPECT_EQ(20u, digest_.value_.size());
+ }
+
+ ~TransportTestPeer() {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &TransportTestPeer::DestroyFlow));
+ }
+
+ void DestroyFlow() {
+ disconnect_all();
+ if (flow_) {
+ loopback_->Disconnect();
+ flow_ = nullptr;
+ }
+ ice_ctx_->Destroy();
+ ice_ctx_ = nullptr;
+ streams_.clear();
+ }
+
+ void DisconnectDestroyFlow() {
+ test_utils_->SyncDispatchToSTS(NS_NewRunnableFunction(__func__, [this] {
+ loopback_->Disconnect();
+ disconnect_all(); // Disconnect from the signals;
+ flow_ = nullptr;
+ }));
+ }
+
+ void SetDtlsAllowAll() {
+ nsresult res = dtls_->SetVerificationAllowAll();
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ }
+
+ void SetAlpn(std::string str, bool withDefault, std::string extra = "") {
+ std::set<std::string> alpn;
+ alpn.insert(str); // the one we want to select
+ if (!extra.empty()) {
+ alpn.insert(extra);
+ }
+ nsresult res = dtls_->SetAlpn(alpn, withDefault ? str : "");
+ ASSERT_EQ(NS_OK, res);
+ }
+
+ const std::string& GetAlpn() const { return dtls_->GetNegotiatedAlpn(); }
+
+ void SetDtlsPeer(TransportTestPeer* peer, int digests, unsigned int damage) {
+ unsigned int mask = 1;
+
+ for (int i = 0; i < digests; i++) {
+ DtlsDigest digest_to_set(peer->digest_);
+
+ if (damage & mask) digest_to_set.value_.data()[0]++;
+
+ nsresult res = dtls_->SetVerificationDigest(digest_to_set);
+
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+
+ mask <<= 1;
+ }
+ }
+
+ void SetupSrtp() {
+ std::vector<uint16_t> srtp_ciphers =
+ TransportLayerDtls::GetDefaultSrtpCiphers();
+ SetSrtpCiphers(srtp_ciphers);
+ }
+
+ void SetSrtpCiphers(std::vector<uint16_t>& srtp_ciphers) {
+ ASSERT_TRUE(NS_SUCCEEDED(dtls_->SetSrtpCiphers(srtp_ciphers)));
+ }
+
+ void ConnectSocket_s(TransportTestPeer* peer) {
+ nsresult res;
+ res = loopback_->Init();
+ ASSERT_EQ((nsresult)NS_OK, res);
+
+ loopback_->Connect(peer->loopback_);
+ ASSERT_EQ((nsresult)NS_OK, loopback_->Init());
+ ASSERT_EQ((nsresult)NS_OK, logging_->Init());
+ ASSERT_EQ((nsresult)NS_OK, lossy_->Init());
+ ASSERT_EQ((nsresult)NS_OK, dtls_->Init());
+ dtls_->Chain(lossy_);
+ lossy_->Chain(logging_);
+ logging_->Chain(loopback_);
+
+ flow_->PushLayer(loopback_);
+ flow_->PushLayer(logging_);
+ flow_->PushLayer(lossy_);
+ flow_->PushLayer(dtls_);
+
+ if (dtls_->state() != TransportLayer::TS_ERROR) {
+ // Don't execute these blocks if DTLS didn't initialize.
+ TweakCiphers(dtls_->internal_fd());
+ if (post_setup_) {
+ post_setup_(dtls_->internal_fd());
+ }
+ }
+
+ dtls_->SignalPacketReceived.connect(this,
+ &TransportTestPeer::PacketReceived);
+ }
+
+ void TweakCiphers(PRFileDesc* fd) {
+ for (unsigned short& enabled_cipersuite : enabled_cipersuites_) {
+ SSL_CipherPrefSet(fd, enabled_cipersuite, PR_TRUE);
+ }
+ for (unsigned short& disabled_cipersuite : disabled_cipersuites_) {
+ SSL_CipherPrefSet(fd, disabled_cipersuite, PR_FALSE);
+ }
+ }
+
+ void ConnectSocket(TransportTestPeer* peer) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &TransportTestPeer::ConnectSocket_s, peer));
+ }
+
+ nsresult InitIce_s() {
+ nsresult rv = ice_->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = dtls_->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+ dtls_->Chain(ice_);
+ flow_->PushLayer(ice_);
+ flow_->PushLayer(dtls_);
+ return NS_OK;
+ }
+
+ void InitIce() {
+ nsresult res;
+
+ // Attach our slots
+ ice_ctx_->SignalGatheringStateChange.connect(
+ this, &TransportTestPeer::GatheringStateChange);
+
+ char name[100];
+ snprintf(name, sizeof(name), "%s:stream%d", name_.c_str(),
+ (int)streams_.size());
+
+ // Create the media stream
+ RefPtr<NrIceMediaStream> stream = ice_ctx_->CreateStream(name, name, 1);
+
+ ASSERT_TRUE(stream != nullptr);
+ stream->SetIceCredentials("ufrag", "pass");
+ streams_.push_back(stream);
+
+ // Listen for candidates
+ stream->SignalCandidate.connect(this, &TransportTestPeer::GotCandidate);
+
+ // Create the transport layer
+ ice_ = new TransportLayerIce();
+ ice_->SetParameters(stream, 1);
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&res, this, &TransportTestPeer::InitIce_s));
+
+ ASSERT_EQ((nsresult)NS_OK, res);
+
+ // Listen for media events
+ dtls_->SignalPacketReceived.connect(this,
+ &TransportTestPeer::PacketReceived);
+ dtls_->SignalStateChange.connect(this, &TransportTestPeer::StateChanged);
+
+ // Start gathering
+ test_utils_->SyncDispatchToSTS(WrapRunnableRet(
+ &res, ice_ctx_, &NrIceCtx::StartGathering, false, false));
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ }
+
+ void ConnectIce(TransportTestPeer* peer) {
+ peer_ = peer;
+
+ // If gathering is already complete, push the candidates over
+ if (gathering_complete_) GatheringComplete();
+ }
+
+ // New candidate
+ void GotCandidate(NrIceMediaStream* stream, const std::string& candidate,
+ const std::string& ufrag, const std::string& mdns_addr,
+ const std::string& actual_addr) {
+ std::cerr << "Got candidate " << candidate << " (ufrag=" << ufrag << ")"
+ << std::endl;
+ }
+
+ void GatheringStateChange(NrIceCtx* ctx, NrIceCtx::GatheringState state) {
+ (void)ctx;
+ if (state == NrIceCtx::ICE_CTX_GATHER_COMPLETE) {
+ GatheringComplete();
+ }
+ }
+
+ // Gathering complete, so send our candidates and start
+ // connecting on the other peer.
+ void GatheringComplete() {
+ nsresult res;
+
+ // Don't send to the other side
+ if (!peer_) {
+ gathering_complete_ = true;
+ return;
+ }
+
+ // First send attributes
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&res, peer_->ice_ctx_, &NrIceCtx::ParseGlobalAttributes,
+ ice_ctx_->GetGlobalAttributes()));
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+
+ for (size_t i = 0; i < streams_.size(); ++i) {
+ test_utils_->SyncDispatchToSTS(WrapRunnableRet(
+ &res, peer_->streams_[i], &NrIceMediaStream::ConnectToPeer, "ufrag",
+ "pass", streams_[i]->GetAttributes()));
+
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ }
+
+ // Start checks on the other peer.
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&res, peer_->ice_ctx_, &NrIceCtx::StartChecks));
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ }
+
+ // WrapRunnable/lambda and move semantics (MediaPacket is not copyable) don't
+ // get along yet, so we need a wrapper. Gross.
+ static TransportResult SendPacketWrapper(TransportLayer* layer,
+ MediaPacket* packet) {
+ return layer->SendPacket(*packet);
+ }
+
+ TransportResult SendPacket(MediaPacket& packet) {
+ TransportResult ret;
+
+ test_utils_->SyncDispatchToSTS(WrapRunnableNMRet(
+ &ret, &TransportTestPeer::SendPacketWrapper, dtls_, &packet));
+
+ return ret;
+ }
+
+ void StateChanged(TransportLayer* layer, TransportLayer::State state) {
+ if (state == TransportLayer::TS_OPEN) {
+ std::cerr << "Now connected" << std::endl;
+ }
+ }
+
+ void PacketReceived(TransportLayer* layer, MediaPacket& packet) {
+ std::cerr << "Received " << packet.len() << " bytes" << std::endl;
+ ++received_packets_;
+ received_bytes_ += packet.len();
+ }
+
+ void SetLoss(uint32_t loss) { lossy_->SetLoss(loss); }
+
+ void SetCombinePackets(bool combine) { loopback_->CombinePackets(combine); }
+
+ void SetInspector(UniquePtr<Inspector> inspector) {
+ lossy_->SetInspector(std::move(inspector));
+ }
+
+ void SetInspector(Inspector* in) {
+ UniquePtr<Inspector> inspector(in);
+
+ lossy_->SetInspector(std::move(inspector));
+ }
+
+ void SetCipherSuiteChanges(const std::vector<uint16_t>& enableThese,
+ const std::vector<uint16_t>& disableThese) {
+ disabled_cipersuites_ = disableThese;
+ enabled_cipersuites_ = enableThese;
+ }
+
+ void SetPostSetup(const std::function<void(PRFileDesc*)>& setup) {
+ post_setup_ = std::move(setup);
+ }
+
+ TransportLayer::State state() {
+ TransportLayer::State tstate;
+
+ RUN_ON_THREAD(test_utils_->sts_target(),
+ WrapRunnableRet(&tstate, dtls_, &TransportLayer::state));
+
+ return tstate;
+ }
+
+ bool connected() { return state() == TransportLayer::TS_OPEN; }
+
+ bool failed() { return state() == TransportLayer::TS_ERROR; }
+
+ size_t receivedPackets() { return received_packets_; }
+
+ size_t receivedBytes() { return received_bytes_; }
+
+ uint16_t cipherSuite() const {
+ nsresult rv;
+ uint16_t cipher;
+ RUN_ON_THREAD(
+ test_utils_->sts_target(),
+ WrapRunnableRet(&rv, dtls_, &TransportLayerDtls::GetCipherSuite,
+ &cipher));
+
+ if (NS_FAILED(rv)) {
+ return TLS_NULL_WITH_NULL_NULL; // i.e., not good
+ }
+ return cipher;
+ }
+
+ uint16_t srtpCipher() const {
+ nsresult rv;
+ uint16_t cipher;
+ RUN_ON_THREAD(test_utils_->sts_target(),
+ WrapRunnableRet(&rv, dtls_,
+ &TransportLayerDtls::GetSrtpCipher, &cipher));
+ if (NS_FAILED(rv)) {
+ return 0; // the SRTP equivalent of TLS_NULL_WITH_NULL_NULL
+ }
+ return cipher;
+ }
+
+ private:
+ std::string name_;
+ bool offerer_;
+ nsCOMPtr<nsIEventTarget> target_;
+ size_t received_packets_;
+ size_t received_bytes_;
+ RefPtr<TransportFlow> flow_;
+ TransportLayerLoopback* loopback_;
+ TransportLayerLogging* logging_;
+ TransportLayerLossy* lossy_;
+ TransportLayerDtls* dtls_;
+ TransportLayerIce* ice_;
+ RefPtr<DtlsIdentity> identity_;
+ RefPtr<NrIceCtx> ice_ctx_;
+ std::vector<RefPtr<NrIceMediaStream> > streams_;
+ TransportTestPeer* peer_;
+ bool gathering_complete_;
+ DtlsDigest digest_;
+ std::vector<uint16_t> enabled_cipersuites_;
+ std::vector<uint16_t> disabled_cipersuites_;
+ MtransportTestUtils* test_utils_;
+ std::function<void(PRFileDesc* fd)> post_setup_ = nullptr;
+};
+
+class TransportTest : public MtransportTest {
+ public:
+ TransportTest() {
+ fds_[0] = nullptr;
+ fds_[1] = nullptr;
+ p1_ = nullptr;
+ p2_ = nullptr;
+ }
+
+ void TearDown() override {
+ delete p1_;
+ delete p2_;
+
+ // Can't detach these
+ // PR_Close(fds_[0]);
+ // PR_Close(fds_[1]);
+ MtransportTest::TearDown();
+ }
+
+ void DestroyPeerFlows() {
+ p1_->DisconnectDestroyFlow();
+ p2_->DisconnectDestroyFlow();
+ }
+
+ void SetUp() override {
+ MtransportTest::SetUp();
+
+ nsresult rv;
+ target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ Reset();
+ }
+
+ void Reset() {
+ if (p1_) {
+ delete p1_;
+ }
+ if (p2_) {
+ delete p2_;
+ }
+ p1_ = new TransportTestPeer(target_, "P1", test_utils_);
+ p2_ = new TransportTestPeer(target_, "P2", test_utils_);
+ }
+
+ void SetupSrtp() {
+ p1_->SetupSrtp();
+ p2_->SetupSrtp();
+ }
+
+ void SetDtlsPeer(int digests = 1, unsigned int damage = 0) {
+ p1_->SetDtlsPeer(p2_, digests, damage);
+ p2_->SetDtlsPeer(p1_, digests, damage);
+ }
+
+ void SetDtlsAllowAll() {
+ p1_->SetDtlsAllowAll();
+ p2_->SetDtlsAllowAll();
+ }
+
+ void SetAlpn(std::string first, std::string second,
+ bool withDefaults = true) {
+ if (!first.empty()) {
+ p1_->SetAlpn(first, withDefaults, "bogus");
+ }
+ if (!second.empty()) {
+ p2_->SetAlpn(second, withDefaults);
+ }
+ }
+
+ void CheckAlpn(std::string first, std::string second) {
+ ASSERT_EQ(first, p1_->GetAlpn());
+ ASSERT_EQ(second, p2_->GetAlpn());
+ }
+
+ void ConnectSocket() {
+ ConnectSocketInternal();
+ ASSERT_TRUE_WAIT(p1_->connected(), 10000);
+ ASSERT_TRUE_WAIT(p2_->connected(), 10000);
+
+ ASSERT_EQ(p1_->cipherSuite(), p2_->cipherSuite());
+ ASSERT_EQ(p1_->srtpCipher(), p2_->srtpCipher());
+ }
+
+ void ConnectSocketExpectFail() {
+ ConnectSocketInternal();
+ ASSERT_TRUE_WAIT(p1_->failed(), 10000);
+ ASSERT_TRUE_WAIT(p2_->failed(), 10000);
+ }
+
+ void ConnectSocketExpectState(TransportLayer::State s1,
+ TransportLayer::State s2) {
+ ConnectSocketInternal();
+ ASSERT_EQ_WAIT(s1, p1_->state(), 10000);
+ ASSERT_EQ_WAIT(s2, p2_->state(), 10000);
+ }
+
+ void ConnectIce() {
+ p1_->InitIce();
+ p2_->InitIce();
+ p1_->ConnectIce(p2_);
+ p2_->ConnectIce(p1_);
+ ASSERT_TRUE_WAIT(p1_->connected(), 10000);
+ ASSERT_TRUE_WAIT(p2_->connected(), 10000);
+ }
+
+ void TransferTest(size_t count, size_t bytes = 1024) {
+ unsigned char buf[bytes];
+
+ for (size_t i = 0; i < count; ++i) {
+ memset(buf, count & 0xff, sizeof(buf));
+ MediaPacket packet;
+ packet.Copy(buf, sizeof(buf));
+ TransportResult rv = p1_->SendPacket(packet);
+ ASSERT_TRUE(rv > 0);
+ }
+
+ std::cerr << "Received == " << p2_->receivedPackets() << " packets"
+ << std::endl;
+ ASSERT_TRUE_WAIT(count == p2_->receivedPackets(), 10000);
+ ASSERT_TRUE((count * sizeof(buf)) == p2_->receivedBytes());
+ }
+
+ protected:
+ void ConnectSocketInternal() {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(p1_, &TransportTestPeer::ConnectSocket, p2_));
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(p2_, &TransportTestPeer::ConnectSocket, p1_));
+ }
+
+ PRFileDesc* fds_[2];
+ TransportTestPeer* p1_;
+ TransportTestPeer* p2_;
+ nsCOMPtr<nsIEventTarget> target_;
+};
+
+TEST_F(TransportTest, TestNoDtlsVerificationSettings) {
+ ConnectSocketExpectFail();
+}
+
+static void DisableChaCha(TransportTestPeer* peer) {
+ // On ARM, ChaCha20Poly1305 might be preferred; disable it for the tests that
+ // want to check the cipher suite. It doesn't matter which peer disables the
+ // suite, disabling on either side has the same effect.
+ std::vector<uint16_t> chachaSuites;
+ chachaSuites.push_back(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256);
+ chachaSuites.push_back(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256);
+ peer->SetCipherSuiteChanges(std::vector<uint16_t>(), chachaSuites);
+}
+
+TEST_F(TransportTest, TestConnect) {
+ SetDtlsPeer();
+ DisableChaCha(p1_);
+ ConnectSocket();
+
+ // check that we got the right suite
+ ASSERT_EQ(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, p1_->cipherSuite());
+
+ // no SRTP on this one
+ ASSERT_EQ(0, p1_->srtpCipher());
+}
+
+TEST_F(TransportTest, TestConnectSrtp) {
+ SetupSrtp();
+ SetDtlsPeer();
+ DisableChaCha(p2_);
+ ConnectSocket();
+
+ ASSERT_EQ(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, p1_->cipherSuite());
+
+ // SRTP is on with default value
+ ASSERT_EQ(kDtlsSrtpAeadAes128Gcm, p1_->srtpCipher());
+}
+
+TEST_F(TransportTest, TestConnectDestroyFlowsMainThread) {
+ SetDtlsPeer();
+ ConnectSocket();
+ DestroyPeerFlows();
+}
+
+TEST_F(TransportTest, TestConnectAllowAll) {
+ SetDtlsAllowAll();
+ ConnectSocket();
+}
+
+TEST_F(TransportTest, TestConnectAlpn) {
+ SetDtlsPeer();
+ SetAlpn("a", "a");
+ ConnectSocket();
+ CheckAlpn("a", "a");
+}
+
+TEST_F(TransportTest, TestConnectAlpnMismatch) {
+ SetDtlsPeer();
+ SetAlpn("something", "different");
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, TestConnectAlpnServerDefault) {
+ SetDtlsPeer();
+ SetAlpn("def", "");
+ // server allows default, client doesn't support
+ ConnectSocket();
+ CheckAlpn("def", "");
+}
+
+TEST_F(TransportTest, TestConnectAlpnClientDefault) {
+ SetDtlsPeer();
+ SetAlpn("", "clientdef");
+ // client allows default, but server will ignore the extension
+ ConnectSocket();
+ CheckAlpn("", "clientdef");
+}
+
+TEST_F(TransportTest, TestConnectClientNoAlpn) {
+ SetDtlsPeer();
+ // Here the server has ALPN, but no default is allowed.
+ // Reminder: p1 == server, p2 == client
+ SetAlpn("server-nodefault", "", false);
+ // The server doesn't see the extension, so negotiates without it.
+ // But then the server is forced to close when it discovers that ALPN wasn't
+ // negotiated; the client sees a close.
+ ConnectSocketExpectState(TransportLayer::TS_ERROR, TransportLayer::TS_CLOSED);
+}
+
+TEST_F(TransportTest, TestConnectServerNoAlpn) {
+ SetDtlsPeer();
+ SetAlpn("", "client-nodefault", false);
+ // The client aborts; the server doesn't realize this is a problem and just
+ // sees the close.
+ ConnectSocketExpectState(TransportLayer::TS_CLOSED, TransportLayer::TS_ERROR);
+}
+
+TEST_F(TransportTest, TestConnectNoDigest) {
+ SetDtlsPeer(0, 0);
+
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, TestConnectBadDigest) {
+ SetDtlsPeer(1, 1);
+
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, TestConnectTwoDigests) {
+ SetDtlsPeer(2, 0);
+
+ ConnectSocket();
+}
+
+TEST_F(TransportTest, TestConnectTwoDigestsFirstBad) {
+ SetDtlsPeer(2, 1);
+
+ ConnectSocket();
+}
+
+TEST_F(TransportTest, TestConnectTwoDigestsSecondBad) {
+ SetDtlsPeer(2, 2);
+
+ ConnectSocket();
+}
+
+TEST_F(TransportTest, TestConnectTwoDigestsBothBad) {
+ SetDtlsPeer(2, 3);
+
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, TestConnectInjectCCS) {
+ SetDtlsPeer();
+ p2_->SetInspector(MakeUnique<DtlsInspectorInjector>(
+ kTlsHandshakeType, kTlsHandshakeCertificate, kTlsFakeChangeCipherSpec,
+ sizeof(kTlsFakeChangeCipherSpec)));
+
+ ConnectSocket();
+}
+
+TEST_F(TransportTest, TestConnectVerifyNewECDHE) {
+ SetDtlsPeer();
+ DtlsInspectorRecordHandshakeMessage* i1 =
+ new DtlsInspectorRecordHandshakeMessage(kTlsHandshakeServerKeyExchange);
+ p1_->SetInspector(i1);
+ ConnectSocket();
+ TlsServerKeyExchangeECDHE dhe1;
+ ASSERT_TRUE(dhe1.Parse(i1->buffer().data(), i1->buffer().len()));
+
+ Reset();
+ SetDtlsPeer();
+ DtlsInspectorRecordHandshakeMessage* i2 =
+ new DtlsInspectorRecordHandshakeMessage(kTlsHandshakeServerKeyExchange);
+ p1_->SetInspector(i2);
+ ConnectSocket();
+ TlsServerKeyExchangeECDHE dhe2;
+ ASSERT_TRUE(dhe2.Parse(i2->buffer().data(), i2->buffer().len()));
+
+ // Now compare these two to see if they are the same.
+ ASSERT_FALSE((dhe1.public_key_.len() == dhe2.public_key_.len()) &&
+ (!memcmp(dhe1.public_key_.data(), dhe2.public_key_.data(),
+ dhe1.public_key_.len())));
+}
+
+TEST_F(TransportTest, TestConnectVerifyReusedECDHE) {
+ auto set_reuse_ecdhe_key = [](PRFileDesc* fd) {
+ // TransportLayerDtls automatically sets this pref to false
+ // so set it back for test.
+ // This is pretty gross. Dig directly into the NSS FD. The problem
+ // is that we are testing a feature which TransaportLayerDtls doesn't
+ // expose.
+ SECStatus rv = SSL_OptionSet(fd, SSL_REUSE_SERVER_ECDHE_KEY, PR_TRUE);
+ ASSERT_EQ(SECSuccess, rv);
+ };
+
+ SetDtlsPeer();
+ DtlsInspectorRecordHandshakeMessage* i1 =
+ new DtlsInspectorRecordHandshakeMessage(kTlsHandshakeServerKeyExchange);
+ p1_->SetInspector(i1);
+ p1_->SetPostSetup(set_reuse_ecdhe_key);
+ ConnectSocket();
+ TlsServerKeyExchangeECDHE dhe1;
+ ASSERT_TRUE(dhe1.Parse(i1->buffer().data(), i1->buffer().len()));
+
+ Reset();
+ SetDtlsPeer();
+ DtlsInspectorRecordHandshakeMessage* i2 =
+ new DtlsInspectorRecordHandshakeMessage(kTlsHandshakeServerKeyExchange);
+
+ p1_->SetInspector(i2);
+ p1_->SetPostSetup(set_reuse_ecdhe_key);
+
+ ConnectSocket();
+ TlsServerKeyExchangeECDHE dhe2;
+ ASSERT_TRUE(dhe2.Parse(i2->buffer().data(), i2->buffer().len()));
+
+ // Now compare these two to see if they are the same.
+ ASSERT_EQ(dhe1.public_key_.len(), dhe2.public_key_.len());
+ ASSERT_TRUE(!memcmp(dhe1.public_key_.data(), dhe2.public_key_.data(),
+ dhe1.public_key_.len()));
+}
+
+TEST_F(TransportTest, TestTransfer) {
+ SetDtlsPeer();
+ ConnectSocket();
+ TransferTest(1);
+}
+
+TEST_F(TransportTest, TestTransferMaxSize) {
+ SetDtlsPeer();
+ ConnectSocket();
+ /* transportlayerdtls uses a 9216 bytes buffer - as this test uses the
+ * loopback implementation it does not have to take into account the extra
+ * bytes added by the DTLS layer below. */
+ TransferTest(1, 9216);
+}
+
+TEST_F(TransportTest, TestTransferMultiple) {
+ SetDtlsPeer();
+ ConnectSocket();
+ TransferTest(3);
+}
+
+TEST_F(TransportTest, TestTransferCombinedPackets) {
+ SetDtlsPeer();
+ ConnectSocket();
+ p2_->SetCombinePackets(true);
+ TransferTest(3);
+}
+
+TEST_F(TransportTest, TestConnectLoseFirst) {
+ SetDtlsPeer();
+ p1_->SetLoss(0);
+ ConnectSocket();
+ TransferTest(1);
+}
+
+TEST_F(TransportTest, TestConnectIce) {
+ SetDtlsPeer();
+ ConnectIce();
+}
+
+TEST_F(TransportTest, TestTransferIceMaxSize) {
+ SetDtlsPeer();
+ ConnectIce();
+ /* nICEr and transportlayerdtls both use 9216 bytes buffers. But the DTLS
+ * layer add extra bytes to the packet, which size depends on chosen cipher
+ * etc. Sending more then 9216 bytes works, but on the receiving side the call
+ * to PR_recvfrom() will truncate any packet bigger then nICEr's buffer size
+ * of 9216 bytes, which then results in the DTLS layer discarding the packet.
+ * Therefore we leave some headroom (according to
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1214269#c29 256 bytes should
+ * be save choice) here for the DTLS bytes to make it safely into the
+ * receiving buffer in nICEr. */
+ TransferTest(1, 8960);
+}
+
+TEST_F(TransportTest, TestTransferIceMultiple) {
+ SetDtlsPeer();
+ ConnectIce();
+ TransferTest(3);
+}
+
+TEST_F(TransportTest, TestTransferIceCombinedPackets) {
+ SetDtlsPeer();
+ ConnectIce();
+ p2_->SetCombinePackets(true);
+ TransferTest(3);
+}
+
+// test the default configuration against a peer that supports only
+// one of the mandatory-to-implement suites, which should succeed
+static void ConfigureOneCipher(TransportTestPeer* peer, uint16_t suite) {
+ std::vector<uint16_t> justOne;
+ justOne.push_back(suite);
+ std::vector<uint16_t> everythingElse(
+ SSL_GetImplementedCiphers(),
+ SSL_GetImplementedCiphers() + SSL_GetNumImplementedCiphers());
+ std::remove(everythingElse.begin(), everythingElse.end(), suite);
+ peer->SetCipherSuiteChanges(justOne, everythingElse);
+}
+
+TEST_F(TransportTest, TestCipherMismatch) {
+ SetDtlsPeer();
+ ConfigureOneCipher(p1_, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256);
+ ConfigureOneCipher(p2_, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA);
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, TestCipherMandatoryOnlyGcm) {
+ SetDtlsPeer();
+ ConfigureOneCipher(p1_, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256);
+ ConnectSocket();
+ ASSERT_EQ(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, p1_->cipherSuite());
+}
+
+TEST_F(TransportTest, TestCipherMandatoryOnlyCbc) {
+ SetDtlsPeer();
+ ConfigureOneCipher(p1_, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA);
+ ConnectSocket();
+ ASSERT_EQ(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, p1_->cipherSuite());
+}
+
+TEST_F(TransportTest, TestSrtpMismatch) {
+ std::vector<uint16_t> setA;
+ setA.push_back(kDtlsSrtpAes128CmHmacSha1_80);
+ std::vector<uint16_t> setB;
+ setB.push_back(kDtlsSrtpAes128CmHmacSha1_32);
+
+ p1_->SetSrtpCiphers(setA);
+ p2_->SetSrtpCiphers(setB);
+ SetDtlsPeer();
+ ConnectSocketExpectFail();
+
+ ASSERT_EQ(0, p1_->srtpCipher());
+ ASSERT_EQ(0, p2_->srtpCipher());
+}
+
+static SECStatus NoopXtnHandler(PRFileDesc* fd, SSLHandshakeType message,
+ const uint8_t* data, unsigned int len,
+ SSLAlertDescription* alert, void* arg) {
+ return SECSuccess;
+}
+
+static PRBool WriteFixedXtn(PRFileDesc* fd, SSLHandshakeType message,
+ uint8_t* data, unsigned int* len,
+ unsigned int max_len, void* arg) {
+ // When we enable TLS 1.3, change ssl_hs_server_hello here to
+ // ssl_hs_encrypted_extensions. At the same time, add a test that writes to
+ // ssl_hs_server_hello, which should fail.
+ if (message != ssl_hs_client_hello && message != ssl_hs_server_hello) {
+ return false;
+ }
+
+ auto v = reinterpret_cast<std::vector<uint8_t>*>(arg);
+ memcpy(data, &((*v)[0]), v->size());
+ *len = v->size();
+ return true;
+}
+
+// Note that |value| needs to be readable after this function returns.
+static void InstallBadSrtpExtensionWriter(TransportTestPeer* peer,
+ std::vector<uint8_t>* value) {
+ peer->SetPostSetup([value](PRFileDesc* fd) {
+ // Override the handler that is installed by the DTLS setup.
+ SECStatus rv = SSL_InstallExtensionHooks(
+ fd, ssl_use_srtp_xtn, WriteFixedXtn, value, NoopXtnHandler, nullptr);
+ ASSERT_EQ(SECSuccess, rv);
+ });
+}
+
+TEST_F(TransportTest, TestSrtpErrorServerSendsTwoSrtpCiphers) {
+ // Server (p1_) sends an extension with two values, and empty MKI.
+ std::vector<uint8_t> xtn = {0x04, 0x00, 0x01, 0x00, 0x02, 0x00};
+ InstallBadSrtpExtensionWriter(p1_, &xtn);
+ SetupSrtp();
+ SetDtlsPeer();
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, TestSrtpErrorServerSendsTwoMki) {
+ // Server (p1_) sends an MKI.
+ std::vector<uint8_t> xtn = {0x02, 0x00, 0x01, 0x01, 0x00};
+ InstallBadSrtpExtensionWriter(p1_, &xtn);
+ SetupSrtp();
+ SetDtlsPeer();
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, TestSrtpErrorServerSendsUnknownValue) {
+ std::vector<uint8_t> xtn = {0x02, 0x9a, 0xf1, 0x00};
+ InstallBadSrtpExtensionWriter(p1_, &xtn);
+ SetupSrtp();
+ SetDtlsPeer();
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, TestSrtpErrorServerSendsOverflow) {
+ std::vector<uint8_t> xtn = {0x32, 0x00, 0x01, 0x00};
+ InstallBadSrtpExtensionWriter(p1_, &xtn);
+ SetupSrtp();
+ SetDtlsPeer();
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, TestSrtpErrorServerSendsUnevenList) {
+ std::vector<uint8_t> xtn = {0x01, 0x00, 0x00};
+ InstallBadSrtpExtensionWriter(p1_, &xtn);
+ SetupSrtp();
+ SetDtlsPeer();
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, TestSrtpErrorClientSendsUnevenList) {
+ std::vector<uint8_t> xtn = {0x01, 0x00, 0x00};
+ InstallBadSrtpExtensionWriter(p2_, &xtn);
+ SetupSrtp();
+ SetDtlsPeer();
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, OnlyServerSendsSrtpXtn) {
+ p1_->SetupSrtp();
+ SetDtlsPeer();
+ // This should connect, but with no SRTP extension neogtiated.
+ // The client side might negotiate a data channel only.
+ ConnectSocket();
+ ASSERT_NE(TLS_NULL_WITH_NULL_NULL, p1_->cipherSuite());
+ ASSERT_EQ(0, p1_->srtpCipher());
+}
+
+TEST_F(TransportTest, OnlyClientSendsSrtpXtn) {
+ p2_->SetupSrtp();
+ SetDtlsPeer();
+ // This should connect, but with no SRTP extension neogtiated.
+ // The server side might negotiate a data channel only.
+ ConnectSocket();
+ ASSERT_NE(TLS_NULL_WITH_NULL_NULL, p1_->cipherSuite());
+ ASSERT_EQ(0, p1_->srtpCipher());
+}
+
+class TransportSrtpParameterTest
+ : public TransportTest,
+ public ::testing::WithParamInterface<uint16_t> {};
+
+INSTANTIATE_TEST_SUITE_P(
+ SrtpParamInit, TransportSrtpParameterTest,
+ ::testing::ValuesIn(TransportLayerDtls::GetDefaultSrtpCiphers()));
+
+TEST_P(TransportSrtpParameterTest, TestSrtpCiphersMismatchCombinations) {
+ uint16_t cipher = GetParam();
+ std::cerr << "Checking cipher: " << cipher << std::endl;
+
+ p1_->SetupSrtp();
+
+ std::vector<uint16_t> setB;
+ setB.push_back(cipher);
+
+ p2_->SetSrtpCiphers(setB);
+ SetDtlsPeer();
+ ConnectSocket();
+
+ ASSERT_EQ(cipher, p1_->srtpCipher());
+ ASSERT_EQ(cipher, p2_->srtpCipher());
+}
+
+// NSS doesn't support DHE suites on the server end.
+// This checks to see if we barf when that's the only option available.
+TEST_F(TransportTest, TestDheOnlyFails) {
+ SetDtlsPeer();
+
+ // p2_ is the client
+ // setting this on p1_ (the server) causes NSS to assert
+ ConfigureOneCipher(p2_, TLS_DHE_RSA_WITH_AES_128_CBC_SHA);
+ ConnectSocketExpectFail();
+}
+
+} // end namespace
diff --git a/dom/media/webrtc/transport/test/turn_unittest.cpp b/dom/media/webrtc/transport/test/turn_unittest.cpp
new file mode 100644
index 0000000000..ae5d0386d9
--- /dev/null
+++ b/dom/media/webrtc/transport/test/turn_unittest.cpp
@@ -0,0 +1,432 @@
+/* -*- 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
+
+// Some code copied from nICEr. License is:
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+* Neither the name of Adobe Systems, Network Resonance nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <stdlib.h>
+#include <iostream>
+
+#include "runnable_utils.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+#define USE_TURN
+
+// nICEr includes
+extern "C" {
+#include "nr_api.h"
+#include "transport_addr.h"
+#include "nr_crypto.h"
+#include "nr_socket.h"
+#include "nr_socket_local.h"
+#include "nr_socket_buffered_stun.h"
+#include "stun_client_ctx.h"
+#include "turn_client_ctx.h"
+}
+
+#include "nricectx.h"
+
+using namespace mozilla;
+
+static std::string kDummyTurnServer("192.0.2.1"); // From RFC 5737
+
+class TurnClient : public MtransportTest {
+ public:
+ TurnClient()
+ : MtransportTest(),
+ real_socket_(nullptr),
+ net_socket_(nullptr),
+ buffered_socket_(nullptr),
+ net_fd_(nullptr),
+ turn_ctx_(nullptr),
+ allocated_(false),
+ received_(0),
+ protocol_(IPPROTO_UDP) {}
+
+ ~TurnClient() = default;
+
+ static void SetUpTestCase() {
+ NrIceCtx::InitializeGlobals(NrIceCtx::GlobalConfig());
+ }
+
+ void SetTcp() { protocol_ = IPPROTO_TCP; }
+
+ void Init_s() {
+ int r;
+ nr_transport_addr addr;
+ r = nr_ip4_port_to_transport_addr(0, 0, protocol_, &addr);
+ ASSERT_EQ(0, r);
+
+ r = nr_socket_local_create(nullptr, &addr, &real_socket_);
+ ASSERT_EQ(0, r);
+
+ if (protocol_ == IPPROTO_TCP) {
+ int r = nr_socket_buffered_stun_create(
+ real_socket_, 100000, TURN_TCP_FRAMING, &buffered_socket_);
+ ASSERT_EQ(0, r);
+ net_socket_ = buffered_socket_;
+ } else {
+ net_socket_ = real_socket_;
+ }
+
+ r = nr_str_port_to_transport_addr(turn_server_.c_str(), 3478, protocol_,
+ &addr);
+ ASSERT_EQ(0, r);
+
+ std::vector<unsigned char> password_vec(turn_password_.begin(),
+ turn_password_.end());
+ Data password;
+ INIT_DATA(password, &password_vec[0], password_vec.size());
+ r = nr_turn_client_ctx_create("test", net_socket_, turn_user_.c_str(),
+ &password, &addr, nullptr, &turn_ctx_);
+ ASSERT_EQ(0, r);
+
+ r = nr_socket_getfd(net_socket_, &net_fd_);
+ ASSERT_EQ(0, r);
+
+ NR_ASYNC_WAIT(net_fd_, NR_ASYNC_WAIT_READ, socket_readable_cb, (void*)this);
+ }
+
+ void TearDown_s() {
+ nr_turn_client_ctx_destroy(&turn_ctx_);
+ if (net_fd_) {
+ NR_ASYNC_CANCEL(net_fd_, NR_ASYNC_WAIT_READ);
+ }
+
+ nr_socket_destroy(&buffered_socket_);
+ }
+
+ void TearDown() {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(this, &TurnClient::TearDown_s));
+ }
+
+ void Allocate_s() {
+ Init_s();
+ ASSERT_TRUE(turn_ctx_);
+
+ int r = nr_turn_client_allocate(turn_ctx_, allocate_success_cb, this);
+ ASSERT_EQ(0, r);
+ }
+
+ void Allocate(bool expect_success = true) {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(this, &TurnClient::Allocate_s));
+
+ if (expect_success) {
+ ASSERT_TRUE_WAIT(allocated_, 5000);
+ } else {
+ PR_Sleep(10000);
+ ASSERT_FALSE(allocated_);
+ }
+ }
+
+ void Allocated() {
+ if (turn_ctx_->state != NR_TURN_CLIENT_STATE_ALLOCATED) {
+ std::cerr << "Allocation failed" << std::endl;
+ return;
+ }
+ allocated_ = true;
+
+ int r;
+ nr_transport_addr addr;
+
+ r = nr_turn_client_get_relayed_address(turn_ctx_, &addr);
+ ASSERT_EQ(0, r);
+
+ relay_addr_ = addr.as_string;
+
+ std::cerr << "Allocation succeeded with addr=" << relay_addr_ << std::endl;
+ }
+
+ void Deallocate_s() {
+ ASSERT_TRUE(turn_ctx_);
+
+ std::cerr << "De-Allocating..." << std::endl;
+ int r = nr_turn_client_deallocate(turn_ctx_);
+ ASSERT_EQ(0, r);
+ }
+
+ void Deallocate() {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &TurnClient::Deallocate_s));
+ }
+
+ void RequestPermission_s(const std::string& target) {
+ nr_transport_addr addr;
+ int r;
+
+ // Expected pattern here is "IP4:127.0.0.1:3487"
+ ASSERT_EQ(0, target.compare(0, 4, "IP4:"));
+
+ size_t offset = target.rfind(':');
+ ASSERT_NE(std::string::npos, offset);
+
+ std::string host = target.substr(4, offset - 4);
+ std::string port = target.substr(offset + 1);
+
+ r = nr_str_port_to_transport_addr(host.c_str(), atoi(port.c_str()),
+ IPPROTO_UDP, &addr);
+ ASSERT_EQ(0, r);
+
+ r = nr_turn_client_ensure_perm(turn_ctx_, &addr);
+ ASSERT_EQ(0, r);
+ }
+
+ void RequestPermission(const std::string& target) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &TurnClient::RequestPermission_s, target));
+ }
+
+ void Readable(NR_SOCKET s, int how, void* arg) {
+ // Re-arm
+ std::cerr << "Socket is readable" << std::endl;
+ NR_ASYNC_WAIT(s, how, socket_readable_cb, arg);
+
+ UCHAR buf[8192];
+ size_t len_s;
+ nr_transport_addr addr;
+
+ int r = nr_socket_recvfrom(net_socket_, buf, sizeof(buf), &len_s, 0, &addr);
+ if (r) {
+ std::cerr << "Error reading from socket" << std::endl;
+ return;
+ }
+
+ ASSERT_LT(len_s, (size_t)INT_MAX);
+ int len = (int)len_s;
+
+ if (nr_is_stun_response_message(buf, len)) {
+ std::cerr << "STUN response" << std::endl;
+ r = nr_turn_client_process_response(turn_ctx_, buf, len, &addr);
+
+ if (r && r != R_REJECTED && r != R_RETRY) {
+ std::cerr << "Error processing STUN: " << r << std::endl;
+ }
+ } else if (nr_is_stun_indication_message(buf, len)) {
+ std::cerr << "STUN indication" << std::endl;
+
+ /* Process the indication */
+ unsigned char data[NR_STUN_MAX_MESSAGE_SIZE];
+ size_t datal;
+ nr_transport_addr remote_addr;
+
+ r = nr_turn_client_parse_data_indication(
+ turn_ctx_, &addr, buf, len, data, &datal, sizeof(data), &remote_addr);
+ ASSERT_EQ(0, r);
+ std::cerr << "Received " << datal << " bytes from "
+ << remote_addr.as_string << std::endl;
+
+ received_ += datal;
+
+ for (size_t i = 0; i < datal; i++) {
+ ASSERT_EQ(i & 0xff, data[i]);
+ }
+ } else {
+ if (nr_is_stun_message(buf, len)) {
+ std::cerr << "STUN message of unexpected type" << std::endl;
+ } else {
+ std::cerr << "Not a STUN message" << std::endl;
+ }
+ return;
+ }
+ }
+
+ void SendTo_s(const std::string& target, int expect_return) {
+ nr_transport_addr addr;
+ int r;
+
+ // Expected pattern here is "IP4:127.0.0.1:3487"
+ ASSERT_EQ(0, target.compare(0, 4, "IP4:"));
+
+ size_t offset = target.rfind(':');
+ ASSERT_NE(std::string::npos, offset);
+
+ std::string host = target.substr(4, offset - 4);
+ std::string port = target.substr(offset + 1);
+
+ r = nr_str_port_to_transport_addr(host.c_str(), atoi(port.c_str()),
+ IPPROTO_UDP, &addr);
+ ASSERT_EQ(0, r);
+
+ unsigned char test[100];
+ for (size_t i = 0; i < sizeof(test); i++) {
+ test[i] = i & 0xff;
+ }
+
+ std::cerr << "Sending test message to " << target << " ..." << std::endl;
+
+ r = nr_turn_client_send_indication(turn_ctx_, test, sizeof(test), 0, &addr);
+ if (expect_return >= 0) {
+ ASSERT_EQ(expect_return, r);
+ }
+ }
+
+ void SendTo(const std::string& target, int expect_return = 0) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &TurnClient::SendTo_s, target, expect_return));
+ }
+
+ int received() const { return received_; }
+
+ static void socket_readable_cb(NR_SOCKET s, int how, void* arg) {
+ static_cast<TurnClient*>(arg)->Readable(s, how, arg);
+ }
+
+ static void allocate_success_cb(NR_SOCKET s, int how, void* arg) {
+ static_cast<TurnClient*>(arg)->Allocated();
+ }
+
+ protected:
+ std::string turn_server_;
+ nr_socket* real_socket_;
+ nr_socket* net_socket_;
+ nr_socket* buffered_socket_;
+ NR_SOCKET net_fd_;
+ nr_turn_client_ctx* turn_ctx_;
+ std::string relay_addr_;
+ bool allocated_;
+ int received_;
+ int protocol_;
+};
+
+TEST_F(TurnClient, Allocate) {
+ if (WarnIfTurnNotConfigured()) return;
+
+ Allocate();
+}
+
+TEST_F(TurnClient, AllocateTcp) {
+ if (WarnIfTurnNotConfigured()) return;
+
+ SetTcp();
+ Allocate();
+}
+
+TEST_F(TurnClient, AllocateAndHold) {
+ if (WarnIfTurnNotConfigured()) return;
+
+ Allocate();
+ PR_Sleep(20000);
+ ASSERT_TRUE(turn_ctx_->state == NR_TURN_CLIENT_STATE_ALLOCATED);
+}
+
+TEST_F(TurnClient, SendToSelf) {
+ if (WarnIfTurnNotConfigured()) return;
+
+ Allocate();
+ SendTo(relay_addr_);
+ ASSERT_TRUE_WAIT(received() == 100, 5000);
+ SendTo(relay_addr_);
+ ASSERT_TRUE_WAIT(received() == 200, 1000);
+}
+
+TEST_F(TurnClient, SendToSelfTcp) {
+ if (WarnIfTurnNotConfigured()) return;
+
+ SetTcp();
+ Allocate();
+ SendTo(relay_addr_);
+ ASSERT_TRUE_WAIT(received() == 100, 5000);
+ SendTo(relay_addr_);
+ ASSERT_TRUE_WAIT(received() == 200, 1000);
+}
+
+TEST_F(TurnClient, PermissionDenied) {
+ if (WarnIfTurnNotConfigured()) return;
+
+ Allocate();
+ RequestPermission(relay_addr_);
+ PR_Sleep(1000);
+
+ /* Fake a 403 response */
+ nr_turn_permission* perm;
+ perm = STAILQ_FIRST(&turn_ctx_->permissions);
+ ASSERT_TRUE(perm);
+ while (perm) {
+ perm->stun->last_error_code = 403;
+ std::cerr << "Set 403's on permission" << std::endl;
+ perm = STAILQ_NEXT(perm, entry);
+ }
+
+ SendTo(relay_addr_, R_NOT_PERMITTED);
+ ASSERT_TRUE(received() == 0);
+
+ // TODO: We should check if we can still send to a second destination, but
+ // we would need a second TURN client as one client can only handle one
+ // allocation (maybe as part of bug 1128128 ?).
+}
+
+TEST_F(TurnClient, DeallocateReceiveFailure) {
+ if (WarnIfTurnNotConfigured()) return;
+
+ Allocate();
+ SendTo(relay_addr_);
+ ASSERT_TRUE_WAIT(received() == 100, 5000);
+ Deallocate();
+ turn_ctx_->state = NR_TURN_CLIENT_STATE_ALLOCATED;
+ SendTo(relay_addr_);
+ PR_Sleep(1000);
+ ASSERT_TRUE(received() == 100);
+}
+
+TEST_F(TurnClient, DeallocateReceiveFailureTcp) {
+ if (WarnIfTurnNotConfigured()) return;
+
+ SetTcp();
+ Allocate();
+ SendTo(relay_addr_);
+ ASSERT_TRUE_WAIT(received() == 100, 5000);
+ Deallocate();
+ turn_ctx_->state = NR_TURN_CLIENT_STATE_ALLOCATED;
+ /* Either the connection got closed by the TURN server already, then the send
+ * is going to fail, which we simply ignore. Or the connection is still alive
+ * and we cand send the data, but it should not get forwarded to us. In either
+ * case we should not receive more data. */
+ SendTo(relay_addr_, -1);
+ PR_Sleep(1000);
+ ASSERT_TRUE(received() == 100);
+}
+
+TEST_F(TurnClient, AllocateDummyServer) {
+ if (WarnIfTurnNotConfigured()) return;
+
+ turn_server_ = kDummyTurnServer;
+ Allocate(false);
+}
diff --git a/dom/media/webrtc/transport/test/webrtcproxychannel_unittest.cpp b/dom/media/webrtc/transport/test/webrtcproxychannel_unittest.cpp
new file mode 100644
index 0000000000..5bfddc7a3f
--- /dev/null
+++ b/dom/media/webrtc/transport/test/webrtcproxychannel_unittest.cpp
@@ -0,0 +1,754 @@
+/* -*- 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/. */
+
+#include <algorithm>
+#include <mutex>
+
+#include "mozilla/net/WebrtcTCPSocket.h"
+#include "mozilla/net/WebrtcTCPSocketCallback.h"
+
+#include "nsISocketTransport.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+static const uint32_t kDefaultTestTimeout = 2000;
+static const char kReadData[] = "Hello, World!";
+static const size_t kReadDataLength = sizeof(kReadData) - 1;
+static const std::string kReadDataString =
+ std::string(kReadData, kReadDataLength);
+static int kDataLargeOuterLoopCount = 128;
+static int kDataLargeInnerLoopCount = 1024;
+
+namespace mozilla {
+
+using namespace net;
+using namespace testing;
+
+class WebrtcTCPSocketTestCallback;
+
+class FakeSocketTransportProvider : public nsISocketTransport {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsISocketTransport
+ NS_IMETHOD GetHost(nsACString& aHost) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetPort(int32_t* aPort) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetScriptableOriginAttributes(
+ JSContext* cx, JS::MutableHandle<JS::Value> aOriginAttributes) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetScriptableOriginAttributes(
+ JSContext* cx, JS::Handle<JS::Value> aOriginAttributes) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ virtual nsresult GetOriginAttributes(
+ mozilla::OriginAttributes* _retval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ virtual nsresult SetOriginAttributes(
+ const mozilla::OriginAttributes& aOriginAttrs) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetPeerAddr(mozilla::net::NetAddr* _retval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetSelfAddr(mozilla::net::NetAddr* _retval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD Bind(mozilla::net::NetAddr* aLocalAddr) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetScriptablePeerAddr(nsINetAddr** _retval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetScriptableSelfAddr(nsINetAddr** _retval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetTlsSocketControl(
+ nsITLSSocketControl** aTLSSocketControl) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetSecurityCallbacks(
+ nsIInterfaceRequestor** aSecurityCallbacks) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetSecurityCallbacks(
+ nsIInterfaceRequestor* aSecurityCallbacks) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD IsAlive(bool* _retval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetTimeout(uint32_t aType, uint32_t* _retval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetTimeout(uint32_t aType, uint32_t aValue) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetLinger(bool aPolarity, int16_t aTimeout) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetReuseAddrPort(bool reuseAddrPort) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetConnectionFlags(uint32_t* aConnectionFlags) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetConnectionFlags(uint32_t aConnectionFlags) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetIsPrivate(bool) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetTlsFlags(uint32_t* aTlsFlags) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetTlsFlags(uint32_t aTlsFlags) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetQoSBits(uint8_t* aQoSBits) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetQoSBits(uint8_t aQoSBits) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetRecvBufferSize(uint32_t* aRecvBufferSize) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetSendBufferSize(uint32_t* aSendBufferSize) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetKeepaliveEnabled(bool* aKeepaliveEnabled) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetKeepaliveEnabled(bool aKeepaliveEnabled) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetKeepaliveVals(int32_t keepaliveIdleTime,
+ int32_t keepaliveRetryInterval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetResetIPFamilyPreference(
+ bool* aResetIPFamilyPreference) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetEchConfigUsed(bool* aEchConfigUsed) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetEchConfig(const nsACString& aEchConfig) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD ResolvedByTRR(bool* _retval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetEffectiveTRRMode(
+ nsIRequest::TRRMode* aEffectiveTRRMode) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetTrrSkipReason(nsITRRSkipReason::value* aSkipReason) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetRetryDnsIfPossible(bool* aRetryDns) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetStatus(nsresult* aStatus) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+
+ // nsITransport
+ NS_IMETHOD OpenInputStream(uint32_t aFlags, uint32_t aSegmentSize,
+ uint32_t aSegmentCount,
+ nsIInputStream** _retval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD OpenOutputStream(uint32_t aFlags, uint32_t aSegmentSize,
+ uint32_t aSegmentCount,
+ nsIOutputStream** _retval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetEventSink(nsITransportEventSink* aSink,
+ nsIEventTarget* aEventTarget) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+
+ // fake except for these methods which are OK to call
+ // nsISocketTransport
+ NS_IMETHOD SetRecvBufferSize(uint32_t aRecvBufferSize) override {
+ return NS_OK;
+ }
+ NS_IMETHOD SetSendBufferSize(uint32_t aSendBufferSize) override {
+ return NS_OK;
+ }
+ // nsITransport
+ NS_IMETHOD Close(nsresult aReason) override { return NS_OK; }
+
+ protected:
+ virtual ~FakeSocketTransportProvider() = default;
+};
+
+NS_IMPL_ISUPPORTS(FakeSocketTransportProvider, nsISocketTransport, nsITransport)
+
+// Implements some common elements to WebrtcTCPSocketTestOutputStream and
+// WebrtcTCPSocketTestInputStream.
+class WebrtcTCPSocketTestStream {
+ public:
+ WebrtcTCPSocketTestStream();
+
+ void Fail() { mMustFail = true; }
+
+ size_t DataLength();
+ template <typename T>
+ void AppendElements(const T* aBuffer, size_t aLength);
+
+ protected:
+ virtual ~WebrtcTCPSocketTestStream() = default;
+
+ nsTArray<uint8_t> mData;
+ std::mutex mDataMutex;
+
+ bool mMustFail;
+};
+
+WebrtcTCPSocketTestStream::WebrtcTCPSocketTestStream() : mMustFail(false) {}
+
+template <typename T>
+void WebrtcTCPSocketTestStream::AppendElements(const T* aBuffer,
+ size_t aLength) {
+ std::lock_guard<std::mutex> guard(mDataMutex);
+ mData.AppendElements(aBuffer, aLength);
+}
+
+size_t WebrtcTCPSocketTestStream::DataLength() {
+ std::lock_guard<std::mutex> guard(mDataMutex);
+ return mData.Length();
+}
+
+class WebrtcTCPSocketTestInputStream : public nsIAsyncInputStream,
+ public WebrtcTCPSocketTestStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIASYNCINPUTSTREAM
+ NS_DECL_NSIINPUTSTREAM
+
+ WebrtcTCPSocketTestInputStream()
+ : mMaxReadSize(1024 * 1024), mAllowCallbacks(false) {}
+
+ void DoCallback();
+ void CallCallback(const nsCOMPtr<nsIInputStreamCallback>& aCallback);
+ void AllowCallbacks() { mAllowCallbacks = true; }
+
+ size_t mMaxReadSize;
+
+ protected:
+ virtual ~WebrtcTCPSocketTestInputStream() = default;
+
+ private:
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mCallbackTarget;
+
+ bool mAllowCallbacks;
+};
+
+NS_IMPL_ISUPPORTS(WebrtcTCPSocketTestInputStream, nsIAsyncInputStream,
+ nsIInputStream)
+
+nsresult WebrtcTCPSocketTestInputStream::AsyncWait(
+ nsIInputStreamCallback* aCallback, uint32_t aFlags,
+ uint32_t aRequestedCount, nsIEventTarget* aEventTarget) {
+ MOZ_ASSERT(!aEventTarget, "no event target should be set");
+
+ mCallback = aCallback;
+ mCallbackTarget = NS_GetCurrentThread();
+
+ if (mAllowCallbacks && DataLength() > 0) {
+ DoCallback();
+ }
+
+ return NS_OK;
+}
+
+nsresult WebrtcTCPSocketTestInputStream::CloseWithStatus(nsresult aStatus) {
+ return Close();
+}
+
+nsresult WebrtcTCPSocketTestInputStream::Close() { return NS_OK; }
+
+nsresult WebrtcTCPSocketTestInputStream::Available(uint64_t* aAvailable) {
+ *aAvailable = DataLength();
+ return NS_OK;
+}
+
+nsresult WebrtcTCPSocketTestInputStream::StreamStatus() { return NS_OK; }
+
+nsresult WebrtcTCPSocketTestInputStream::Read(char* aBuffer, uint32_t aCount,
+ uint32_t* aRead) {
+ std::lock_guard<std::mutex> guard(mDataMutex);
+ if (mMustFail) {
+ return NS_ERROR_FAILURE;
+ }
+ *aRead = std::min({(size_t)aCount, mData.Length(), mMaxReadSize});
+ memcpy(aBuffer, mData.Elements(), *aRead);
+ mData.RemoveElementsAt(0, *aRead);
+ return *aRead > 0 ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
+}
+
+nsresult WebrtcTCPSocketTestInputStream::ReadSegments(nsWriteSegmentFun aWriter,
+ void* aClosure,
+ uint32_t aCount,
+ uint32_t* _retval) {
+ MOZ_ASSERT(false);
+ return NS_OK;
+}
+
+nsresult WebrtcTCPSocketTestInputStream::IsNonBlocking(bool* aIsNonBlocking) {
+ *aIsNonBlocking = true;
+ return NS_OK;
+}
+
+void WebrtcTCPSocketTestInputStream::CallCallback(
+ const nsCOMPtr<nsIInputStreamCallback>& aCallback) {
+ aCallback->OnInputStreamReady(this);
+}
+
+void WebrtcTCPSocketTestInputStream::DoCallback() {
+ if (mCallback) {
+ mCallbackTarget->Dispatch(
+ NewRunnableMethod<const nsCOMPtr<nsIInputStreamCallback>&>(
+ "WebrtcTCPSocketTestInputStream::DoCallback", this,
+ &WebrtcTCPSocketTestInputStream::CallCallback,
+ std::move(mCallback)));
+
+ mCallbackTarget = nullptr;
+ }
+}
+
+class WebrtcTCPSocketTestOutputStream : public nsIAsyncOutputStream,
+ public WebrtcTCPSocketTestStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+ NS_DECL_NSIOUTPUTSTREAM
+
+ WebrtcTCPSocketTestOutputStream() : mMaxWriteSize(1024 * 1024) {}
+
+ void DoCallback();
+ void CallCallback(const nsCOMPtr<nsIOutputStreamCallback>& aCallback);
+
+ std::string DataString();
+
+ uint32_t mMaxWriteSize;
+
+ protected:
+ virtual ~WebrtcTCPSocketTestOutputStream() = default;
+
+ private:
+ nsCOMPtr<nsIOutputStreamCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mCallbackTarget;
+};
+
+NS_IMPL_ISUPPORTS(WebrtcTCPSocketTestOutputStream, nsIAsyncOutputStream,
+ nsIOutputStream)
+
+nsresult WebrtcTCPSocketTestOutputStream::AsyncWait(
+ nsIOutputStreamCallback* aCallback, uint32_t aFlags,
+ uint32_t aRequestedCount, nsIEventTarget* aEventTarget) {
+ MOZ_ASSERT(!aEventTarget, "no event target should be set");
+
+ mCallback = aCallback;
+ mCallbackTarget = NS_GetCurrentThread();
+
+ return NS_OK;
+}
+
+nsresult WebrtcTCPSocketTestOutputStream::CloseWithStatus(nsresult aStatus) {
+ return Close();
+}
+
+nsresult WebrtcTCPSocketTestOutputStream::Close() { return NS_OK; }
+
+nsresult WebrtcTCPSocketTestOutputStream::Flush() { return NS_OK; }
+
+nsresult WebrtcTCPSocketTestOutputStream::StreamStatus() {
+ return mMustFail ? NS_ERROR_FAILURE : NS_OK;
+}
+
+nsresult WebrtcTCPSocketTestOutputStream::Write(const char* aBuffer,
+ uint32_t aCount,
+ uint32_t* aWrote) {
+ if (mMustFail) {
+ return NS_ERROR_FAILURE;
+ }
+ *aWrote = std::min(aCount, mMaxWriteSize);
+ AppendElements(aBuffer, *aWrote);
+ return NS_OK;
+}
+
+nsresult WebrtcTCPSocketTestOutputStream::WriteSegments(
+ nsReadSegmentFun aReader, void* aClosure, uint32_t aCount,
+ uint32_t* _retval) {
+ MOZ_ASSERT(false);
+ return NS_OK;
+}
+
+nsresult WebrtcTCPSocketTestOutputStream::WriteFrom(nsIInputStream* aFromStream,
+ uint32_t aCount,
+ uint32_t* _retval) {
+ MOZ_ASSERT(false);
+ return NS_OK;
+}
+
+nsresult WebrtcTCPSocketTestOutputStream::IsNonBlocking(bool* aIsNonBlocking) {
+ *aIsNonBlocking = true;
+ return NS_OK;
+}
+
+void WebrtcTCPSocketTestOutputStream::CallCallback(
+ const nsCOMPtr<nsIOutputStreamCallback>& aCallback) {
+ aCallback->OnOutputStreamReady(this);
+}
+
+void WebrtcTCPSocketTestOutputStream::DoCallback() {
+ if (mCallback) {
+ mCallbackTarget->Dispatch(
+ NewRunnableMethod<const nsCOMPtr<nsIOutputStreamCallback>&>(
+ "WebrtcTCPSocketTestOutputStream::CallCallback", this,
+ &WebrtcTCPSocketTestOutputStream::CallCallback,
+ std::move(mCallback)));
+
+ mCallbackTarget = nullptr;
+ }
+}
+
+std::string WebrtcTCPSocketTestOutputStream::DataString() {
+ std::lock_guard<std::mutex> guard(mDataMutex);
+ return std::string((char*)mData.Elements(), mData.Length());
+}
+
+// Fake as in not the real WebrtcTCPSocket but real enough
+class FakeWebrtcTCPSocket : public WebrtcTCPSocket {
+ public:
+ explicit FakeWebrtcTCPSocket(WebrtcTCPSocketCallback* aCallback)
+ : WebrtcTCPSocket(aCallback) {}
+
+ protected:
+ virtual ~FakeWebrtcTCPSocket() = default;
+
+ void InvokeOnClose(nsresult aReason) override;
+ void InvokeOnConnected() override;
+ void InvokeOnRead(nsTArray<uint8_t>&& aReadData) override;
+};
+
+void FakeWebrtcTCPSocket::InvokeOnClose(nsresult aReason) {
+ mProxyCallbacks->OnClose(aReason);
+}
+
+void FakeWebrtcTCPSocket::InvokeOnConnected() {
+ mProxyCallbacks->OnConnected("http"_ns);
+}
+
+void FakeWebrtcTCPSocket::InvokeOnRead(nsTArray<uint8_t>&& aReadData) {
+ mProxyCallbacks->OnRead(std::move(aReadData));
+}
+
+class WebrtcTCPSocketTest : public MtransportTest {
+ public:
+ WebrtcTCPSocketTest()
+ : MtransportTest(),
+ mSocketThread(nullptr),
+ mSocketTransport(nullptr),
+ mInputStream(nullptr),
+ mOutputStream(nullptr),
+ mChannel(nullptr),
+ mCallback(nullptr),
+ mOnCloseCalled(false),
+ mOnConnectedCalled(false) {}
+
+ // WebrtcTCPSocketCallback forwards from mCallback
+ void OnClose(nsresult aReason);
+ void OnConnected(const nsACString& aProxyType);
+ void OnRead(nsTArray<uint8_t>&& aReadData);
+
+ void SetUp() override;
+ void TearDown() override;
+
+ void DoTransportAvailable();
+
+ std::string ReadDataAsString();
+ std::string GetDataLarge();
+
+ nsCOMPtr<nsIEventTarget> mSocketThread;
+
+ nsCOMPtr<nsISocketTransport> mSocketTransport;
+ RefPtr<WebrtcTCPSocketTestInputStream> mInputStream;
+ RefPtr<WebrtcTCPSocketTestOutputStream> mOutputStream;
+ RefPtr<FakeWebrtcTCPSocket> mChannel;
+ RefPtr<WebrtcTCPSocketTestCallback> mCallback;
+
+ bool mOnCloseCalled;
+ bool mOnConnectedCalled;
+
+ size_t ReadDataLength();
+ template <typename T>
+ void AppendReadData(const T* aBuffer, size_t aLength);
+
+ private:
+ nsTArray<uint8_t> mReadData;
+ std::mutex mReadDataMutex;
+};
+
+class WebrtcTCPSocketTestCallback : public WebrtcTCPSocketCallback {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebrtcTCPSocketTestCallback, override)
+
+ explicit WebrtcTCPSocketTestCallback(WebrtcTCPSocketTest* aTest)
+ : mTest(aTest) {}
+
+ // WebrtcTCPSocketCallback
+ void OnClose(nsresult aReason) override;
+ void OnConnected(const nsACString& aProxyType) override;
+ void OnRead(nsTArray<uint8_t>&& aReadData) override;
+
+ protected:
+ virtual ~WebrtcTCPSocketTestCallback() = default;
+
+ private:
+ WebrtcTCPSocketTest* mTest;
+};
+
+void WebrtcTCPSocketTest::SetUp() {
+ nsresult rv;
+ // WebrtcTCPSocket's threading model is the same as mtransport
+ // all socket operations are done on the socket thread
+ // callbacks are invoked on the main thread
+ mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ mSocketTransport = new FakeSocketTransportProvider();
+ mInputStream = new WebrtcTCPSocketTestInputStream();
+ mOutputStream = new WebrtcTCPSocketTestOutputStream();
+ mCallback = new WebrtcTCPSocketTestCallback(this);
+ mChannel = new FakeWebrtcTCPSocket(mCallback.get());
+}
+
+void WebrtcTCPSocketTest::TearDown() {}
+
+// WebrtcTCPSocketCallback
+void WebrtcTCPSocketTest::OnRead(nsTArray<uint8_t>&& aReadData) {
+ AppendReadData(aReadData.Elements(), aReadData.Length());
+}
+
+void WebrtcTCPSocketTest::OnConnected(const nsACString& aProxyType) {
+ mOnConnectedCalled = true;
+}
+
+void WebrtcTCPSocketTest::OnClose(nsresult aReason) { mOnCloseCalled = true; }
+
+void WebrtcTCPSocketTest::DoTransportAvailable() {
+ if (!mSocketThread->IsOnCurrentThread()) {
+ mSocketThread->Dispatch(
+ NS_NewRunnableFunction("DoTransportAvailable", [this]() -> void {
+ nsresult rv;
+ rv = mChannel->OnTransportAvailable(mSocketTransport, mInputStream,
+ mOutputStream);
+ ASSERT_EQ(NS_OK, rv);
+ }));
+ } else {
+ // should always be called on the main thread
+ MOZ_ASSERT(0);
+ }
+}
+
+std::string WebrtcTCPSocketTest::ReadDataAsString() {
+ std::lock_guard<std::mutex> guard(mReadDataMutex);
+ return std::string((char*)mReadData.Elements(), mReadData.Length());
+}
+
+std::string WebrtcTCPSocketTest::GetDataLarge() {
+ std::string data;
+ for (int i = 0; i < kDataLargeOuterLoopCount * kDataLargeInnerLoopCount;
+ ++i) {
+ data += kReadData;
+ }
+ return data;
+}
+
+template <typename T>
+void WebrtcTCPSocketTest::AppendReadData(const T* aBuffer, size_t aLength) {
+ std::lock_guard<std::mutex> guard(mReadDataMutex);
+ mReadData.AppendElements(aBuffer, aLength);
+}
+
+size_t WebrtcTCPSocketTest::ReadDataLength() {
+ std::lock_guard<std::mutex> guard(mReadDataMutex);
+ return mReadData.Length();
+}
+
+void WebrtcTCPSocketTestCallback::OnClose(nsresult aReason) {
+ mTest->OnClose(aReason);
+}
+
+void WebrtcTCPSocketTestCallback::OnConnected(const nsACString& aProxyType) {
+ mTest->OnConnected(aProxyType);
+}
+
+void WebrtcTCPSocketTestCallback::OnRead(nsTArray<uint8_t>&& aReadData) {
+ mTest->OnRead(std::move(aReadData));
+}
+
+} // namespace mozilla
+
+typedef mozilla::WebrtcTCPSocketTest WebrtcTCPSocketTest;
+
+TEST_F(WebrtcTCPSocketTest, SetUp) {}
+
+TEST_F(WebrtcTCPSocketTest, TransportAvailable) {
+ DoTransportAvailable();
+ ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+}
+
+TEST_F(WebrtcTCPSocketTest, Read) {
+ DoTransportAvailable();
+ ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+
+ mInputStream->AppendElements(kReadData, kReadDataLength);
+ mInputStream->DoCallback();
+
+ ASSERT_TRUE_WAIT(ReadDataAsString() == kReadDataString, kDefaultTestTimeout);
+}
+
+TEST_F(WebrtcTCPSocketTest, Write) {
+ DoTransportAvailable();
+ ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+
+ nsTArray<uint8_t> data;
+ data.AppendElements(kReadData, kReadDataLength);
+ mChannel->Write(std::move(data));
+
+ ASSERT_TRUE_WAIT(mChannel->CountUnwrittenBytes() == kReadDataLength,
+ kDefaultTestTimeout);
+
+ mOutputStream->DoCallback();
+
+ ASSERT_TRUE_WAIT(mOutputStream->DataString() == kReadDataString,
+ kDefaultTestTimeout);
+}
+
+TEST_F(WebrtcTCPSocketTest, ReadFail) {
+ DoTransportAvailable();
+ ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+
+ mInputStream->AppendElements(kReadData, kReadDataLength);
+ mInputStream->Fail();
+ mInputStream->DoCallback();
+
+ ASSERT_TRUE_WAIT(mOnCloseCalled, kDefaultTestTimeout);
+ ASSERT_EQ(0U, ReadDataLength());
+}
+
+TEST_F(WebrtcTCPSocketTest, WriteFail) {
+ DoTransportAvailable();
+ ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+
+ nsTArray<uint8_t> array;
+ array.AppendElements(kReadData, kReadDataLength);
+ mChannel->Write(std::move(array));
+
+ ASSERT_TRUE_WAIT(mChannel->CountUnwrittenBytes() == kReadDataLength,
+ kDefaultTestTimeout);
+
+ mOutputStream->Fail();
+ mOutputStream->DoCallback();
+
+ ASSERT_TRUE_WAIT(mOnCloseCalled, kDefaultTestTimeout);
+ ASSERT_EQ(0U, mOutputStream->DataLength());
+}
+
+TEST_F(WebrtcTCPSocketTest, ReadLarge) {
+ DoTransportAvailable();
+ ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+
+ const std::string data = GetDataLarge();
+
+ mInputStream->AppendElements(data.c_str(), data.length());
+ // make sure reading loops more than once
+ mInputStream->mMaxReadSize = 3072;
+ mInputStream->AllowCallbacks();
+ mInputStream->DoCallback();
+
+ ASSERT_TRUE_WAIT(ReadDataAsString() == data, kDefaultTestTimeout);
+}
+
+TEST_F(WebrtcTCPSocketTest, WriteLarge) {
+ DoTransportAvailable();
+ ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+
+ const std::string data = GetDataLarge();
+
+ for (int i = 0; i < kDataLargeOuterLoopCount; ++i) {
+ nsTArray<uint8_t> array;
+ int chunkSize = kReadDataString.length() * kDataLargeInnerLoopCount;
+ int offset = i * chunkSize;
+ array.AppendElements(data.c_str() + offset, chunkSize);
+ mChannel->Write(std::move(array));
+ }
+
+ ASSERT_TRUE_WAIT(mChannel->CountUnwrittenBytes() == data.length(),
+ kDefaultTestTimeout);
+
+ // make sure writing loops more than once per write request
+ mOutputStream->mMaxWriteSize = 1024;
+ mOutputStream->DoCallback();
+
+ ASSERT_TRUE_WAIT(mOutputStream->DataString() == data, kDefaultTestTimeout);
+}
diff --git a/dom/media/webrtc/transport/test_nr_socket.cpp b/dom/media/webrtc/transport/test_nr_socket.cpp
new file mode 100644
index 0000000000..064013a8b7
--- /dev/null
+++ b/dom/media/webrtc/transport/test_nr_socket.cpp
@@ -0,0 +1,1135 @@
+/* -*- 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/. */
+/*
+ */
+
+/*
+Based partially on original code from nICEr and nrappkit.
+
+nICEr copyright:
+
+Copyright (c) 2007, Adobe Systems, Incorporated
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+* Neither the name of Adobe Systems, Network Resonance nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+nrappkit copyright:
+
+ Copyright (C) 2001-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, Inc.
+ All Rights Reserved
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+
+ ekr@rtfm.com Thu Dec 20 20:14:49 2001
+*/
+
+// Original author: bcampen@mozilla.com [:bwc]
+
+extern "C" {
+#include "stun_msg.h" // for NR_STUN_MAX_MESSAGE_SIZE
+#include "async_wait.h"
+#include "async_timer.h"
+#include "nr_socket.h"
+#include "stun.h"
+#include "transport_addr.h"
+}
+
+#include "mozilla/RefPtr.h"
+#include "test_nr_socket.h"
+
+namespace mozilla {
+
+static int test_nat_socket_create(void* obj, nr_transport_addr* addr,
+ nr_socket** sockp) {
+ RefPtr<NrSocketBase> sock = new TestNrSocket(static_cast<TestNat*>(obj));
+
+ int r, _status;
+
+ r = sock->create(addr);
+ if (r) ABORT(r);
+
+ r = nr_socket_create_int(static_cast<void*>(sock), sock->vtbl(), sockp);
+ if (r) ABORT(r);
+
+ _status = 0;
+
+ {
+ // We will release this reference in destroy(), not exactly the normal
+ // ownership model, but it is what it is.
+ NrSocketBase* dummy = sock.forget().take();
+ (void)dummy;
+ }
+
+abort:
+ return _status;
+}
+
+static int test_nat_socket_factory_destroy(void** obj) {
+ TestNat* nat = static_cast<TestNat*>(*obj);
+ *obj = nullptr;
+ nat->Release();
+ return 0;
+}
+
+static nr_socket_factory_vtbl test_nat_socket_factory_vtbl = {
+ test_nat_socket_create, test_nat_socket_factory_destroy};
+
+/* static */
+TestNat::NatBehavior TestNat::ToNatBehavior(const std::string& type) {
+ if (type.empty() || !type.compare("ENDPOINT_INDEPENDENT")) {
+ return TestNat::ENDPOINT_INDEPENDENT;
+ }
+ if (!type.compare("ADDRESS_DEPENDENT")) {
+ return TestNat::ADDRESS_DEPENDENT;
+ }
+ if (!type.compare("PORT_DEPENDENT")) {
+ return TestNat::PORT_DEPENDENT;
+ }
+
+ MOZ_ASSERT(false, "Invalid NAT behavior");
+ return TestNat::ENDPOINT_INDEPENDENT;
+}
+
+bool TestNat::has_port_mappings() const {
+ for (TestNrSocket* sock : sockets_) {
+ if (sock->has_port_mappings()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool TestNat::is_my_external_tuple(const nr_transport_addr& addr) const {
+ for (TestNrSocket* sock : sockets_) {
+ if (sock->is_my_external_tuple(addr)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool TestNat::is_an_internal_tuple(const nr_transport_addr& addr) const {
+ for (TestNrSocket* sock : sockets_) {
+ nr_transport_addr addr_behind_nat;
+ if (sock->getaddr(&addr_behind_nat)) {
+ MOZ_CRASH("TestNrSocket::getaddr failed!");
+ }
+
+ if (!nr_transport_addr_cmp(&addr, &addr_behind_nat,
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+int TestNat::create_socket_factory(nr_socket_factory** factorypp) {
+ int r = nr_socket_factory_create_int(this, &test_nat_socket_factory_vtbl,
+ factorypp);
+ if (!r) {
+ AddRef();
+ }
+ return r;
+}
+
+void TestNat::set_proxy_config(
+ std::shared_ptr<NrSocketProxyConfig> aProxyConfig) {
+ proxy_config_ = std::move(aProxyConfig);
+}
+
+TestNrSocket::TestNrSocket(TestNat* nat)
+ : nat_(nat), tls_(false), timer_handle_(nullptr) {
+ nat_->insert_socket(this);
+}
+
+TestNrSocket::~TestNrSocket() { nat_->erase_socket(this); }
+
+RefPtr<NrSocketBase> TestNrSocket::create_external_socket(
+ const nr_transport_addr& dest_addr) const {
+ MOZ_ASSERT(nat_->enabled_);
+ MOZ_ASSERT(!nat_->is_an_internal_tuple(dest_addr));
+
+ int r;
+ nr_transport_addr nat_external_addr;
+
+ // Open the socket on an arbitrary port, on the same address.
+ if ((r = nr_transport_addr_copy(&nat_external_addr,
+ &internal_socket_->my_addr()))) {
+ r_log(LOG_GENERIC, LOG_CRIT, "%s: Failure in nr_transport_addr_copy: %d",
+ __FUNCTION__, r);
+ return nullptr;
+ }
+
+ if ((r = nr_transport_addr_set_port(&nat_external_addr, 0))) {
+ r_log(LOG_GENERIC, LOG_CRIT,
+ "%s: Failure in nr_transport_addr_set_port: %d", __FUNCTION__, r);
+ return nullptr;
+ }
+
+ RefPtr<NrSocketBase> external_socket;
+ r = NrSocketBase::CreateSocket(&nat_external_addr, &external_socket,
+ nat_->proxy_config_);
+
+ if (r) {
+ r_log(LOG_GENERIC, LOG_CRIT, "%s: Failure in NrSocket::create: %d",
+ __FUNCTION__, r);
+ return nullptr;
+ }
+
+ return external_socket;
+}
+
+int TestNrSocket::create(nr_transport_addr* addr) {
+ tls_ = addr->tls;
+
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %p create %s", this,
+ addr->as_string);
+ return NrSocketBase::CreateSocket(addr, &internal_socket_, nullptr);
+}
+
+int TestNrSocket::getaddr(nr_transport_addr* addrp) {
+ return internal_socket_->getaddr(addrp);
+}
+
+void TestNrSocket::close() {
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %p %s closing", this,
+ internal_socket_->my_addr().as_string);
+ if (timer_handle_) {
+ NR_async_timer_cancel(timer_handle_);
+ timer_handle_ = nullptr;
+ }
+ internal_socket_->close();
+ for (RefPtr<PortMapping>& port_mapping : port_mappings_) {
+ port_mapping->external_socket_->close();
+ }
+}
+
+int TestNrSocket::listen(int backlog) {
+ MOZ_ASSERT(internal_socket_->my_addr().protocol == IPPROTO_TCP);
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %p %s listening", this,
+ internal_socket_->my_addr().as_string);
+
+ return internal_socket_->listen(backlog);
+}
+
+int TestNrSocket::accept(nr_transport_addr* addrp, nr_socket** sockp) {
+ MOZ_ASSERT(internal_socket_->my_addr().protocol == IPPROTO_TCP);
+ int r = internal_socket_->accept(addrp, sockp);
+ if (r) {
+ return r;
+ }
+
+ if (nat_->enabled_ && !nat_->is_an_internal_tuple(*addrp)) {
+ nr_socket_destroy(sockp);
+ return R_IO_ERROR;
+ }
+
+ return 0;
+}
+
+void TestNrSocket::process_delayed_cb(NR_SOCKET s, int how, void* cb_arg) {
+ DeferredPacket* op = static_cast<DeferredPacket*>(cb_arg);
+ op->socket_->timer_handle_ = nullptr;
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s sending delayed STUN response",
+ op->internal_socket_->my_addr().as_string);
+ op->internal_socket_->sendto(op->buffer_.data(), op->buffer_.len(),
+ op->flags_, &op->to_);
+
+ delete op;
+}
+
+int TestNrSocket::sendto(const void* msg, size_t len, int flags,
+ const nr_transport_addr* to) {
+ MOZ_ASSERT(internal_socket_->my_addr().protocol != IPPROTO_TCP);
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %p %s %s", this, __FUNCTION__,
+ to->as_string);
+
+ if (nat_->nat_delegate_ &&
+ nat_->nat_delegate_->on_sendto(nat_, msg, len, flags, to)) {
+ return nat_->error_code_for_drop_;
+ }
+
+ UCHAR* buf = static_cast<UCHAR*>(const_cast<void*>(msg));
+ if (nat_->block_stun_ && nr_is_stun_message(buf, len)) {
+ return nat_->error_code_for_drop_;
+ }
+
+ if (nr_is_stun_request_message(buf, len) &&
+ maybe_send_fake_response(buf, len, to)) {
+ return 0;
+ }
+
+ /* TODO: improve the functionality of this in bug 1253657 */
+ if (!nat_->enabled_ || nat_->is_an_internal_tuple(*to)) {
+ if (nat_->delay_stun_resp_ms_ && nr_is_stun_response_message(buf, len)) {
+ NR_ASYNC_TIMER_SET(
+ nat_->delay_stun_resp_ms_, process_delayed_cb,
+ new DeferredPacket(this, msg, len, flags, to, internal_socket_),
+ &timer_handle_);
+ return 0;
+ }
+ return internal_socket_->sendto(msg, len, flags, to);
+ }
+
+ destroy_stale_port_mappings();
+
+ if (to->protocol == IPPROTO_UDP && nat_->block_udp_) {
+ return nat_->error_code_for_drop_;
+ }
+
+ // Choose our port mapping based on our most selective criteria
+ PortMapping* port_mapping = get_port_mapping(
+ *to, std::max(nat_->filtering_type_, nat_->mapping_type_));
+
+ if (!port_mapping) {
+ // See if we have already made the external socket we need to use.
+ PortMapping* similar_port_mapping =
+ get_port_mapping(*to, nat_->mapping_type_);
+ RefPtr<NrSocketBase> external_socket;
+
+ if (similar_port_mapping) {
+ external_socket = similar_port_mapping->external_socket_;
+ } else {
+ external_socket = create_external_socket(*to);
+ if (!external_socket) {
+ MOZ_ASSERT(false);
+ return R_INTERNAL;
+ }
+ }
+
+ port_mapping = create_port_mapping(*to, external_socket);
+ port_mappings_.push_back(port_mapping);
+
+ if (poll_flags() & PR_POLL_READ) {
+ // Make sure the new port mapping is ready to receive traffic if the
+ // TestNrSocket is already waiting.
+ port_mapping->async_wait(NR_ASYNC_WAIT_READ, socket_readable_callback,
+ this, (char*)__FUNCTION__, __LINE__);
+ }
+ }
+
+ // We probably don't want to propagate the flags, since this is a simulated
+ // external IP address.
+ return port_mapping->sendto(msg, len, *to);
+}
+
+int TestNrSocket::recvfrom(void* buf, size_t maxlen, size_t* len, int flags,
+ nr_transport_addr* from) {
+ MOZ_ASSERT(internal_socket_->my_addr().protocol != IPPROTO_TCP);
+
+ if (!read_buffer_.empty()) {
+ UdpPacket& packet = read_buffer_.front();
+ *len = std::min(maxlen, packet.buffer_->len());
+ memcpy(buf, packet.buffer_->data(), *len);
+ nr_transport_addr_copy(from, &packet.remote_address_);
+ read_buffer_.pop_front();
+ return 0;
+ }
+
+ int r;
+ bool ingress_allowed = false;
+
+ if (readable_socket_) {
+ // If any of the external sockets got data, see if it will be passed through
+ r = readable_socket_->recvfrom(buf, maxlen, len, 0, from);
+ const nr_transport_addr to = readable_socket_->my_addr();
+ readable_socket_ = nullptr;
+ if (!r) {
+ PortMapping* port_mapping_used;
+ ingress_allowed = allow_ingress(to, *from, &port_mapping_used);
+ if (ingress_allowed) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s received from %s via %s",
+ internal_socket_->my_addr().as_string, from->as_string,
+ port_mapping_used->external_socket_->my_addr().as_string);
+ if (nat_->refresh_on_ingress_) {
+ port_mapping_used->last_used_ = PR_IntervalNow();
+ }
+ }
+ }
+ } else {
+ // If no external socket has data, see if there's any data that was sent
+ // directly to the TestNrSocket, and eat it if it isn't supposed to get
+ // through.
+ r = internal_socket_->recvfrom(buf, maxlen, len, flags, from);
+ if (!r) {
+ // We do not use allow_ingress() here because that only handles traffic
+ // landing on an external port.
+ ingress_allowed = (!nat_->enabled_ || nat_->is_an_internal_tuple(*from));
+ if (!ingress_allowed) {
+ r_log(LOG_GENERIC, LOG_INFO,
+ "TestNrSocket %s denying ingress from %s: "
+ "Not behind the same NAT",
+ internal_socket_->my_addr().as_string, from->as_string);
+ } else {
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s received from %s",
+ internal_socket_->my_addr().as_string, from->as_string);
+ }
+ }
+ }
+
+ // Kinda bad that we are forced to give the app a readable callback and then
+ // say "Oh, never mind...", but the alternative is to totally decouple the
+ // callbacks from STS and the callbacks the app sets. On the bright side, this
+ // speeds up unit tests where we are verifying that ingress is forbidden,
+ // since they'll get a readable callback and then an error, instead of having
+ // to wait for a timeout.
+ if (!ingress_allowed) {
+ *len = 0;
+ r = R_WOULDBLOCK;
+ }
+
+ return r;
+}
+
+bool TestNrSocket::allow_ingress(const nr_transport_addr& to,
+ const nr_transport_addr& from,
+ PortMapping** port_mapping_used) const {
+ // This is only called for traffic arriving at a port mapping
+ MOZ_ASSERT(nat_->enabled_);
+ MOZ_ASSERT(!nat_->is_an_internal_tuple(from));
+
+ // Find the port mapping (if any) that this packet landed on
+ for (PortMapping* port_mapping : port_mappings_) {
+ if (!nr_transport_addr_cmp(&to, &port_mapping->external_socket_->my_addr(),
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL)) {
+ *port_mapping_used = port_mapping;
+ }
+ }
+
+ if (NS_WARN_IF(!(*port_mapping_used))) {
+ MOZ_ASSERT(false);
+ r_log(LOG_GENERIC, LOG_INFO,
+ "TestNrSocket %s denying ingress from %s: "
+ "No port mapping for this local port! What?",
+ internal_socket_->my_addr().as_string, from.as_string);
+ return false;
+ }
+
+ if (!port_mapping_matches(**port_mapping_used, from, nat_->filtering_type_)) {
+ r_log(LOG_GENERIC, LOG_INFO,
+ "TestNrSocket %s denying ingress from %s: "
+ "Filtered (no port mapping for source)",
+ internal_socket_->my_addr().as_string, from.as_string);
+ return false;
+ }
+
+ if (is_port_mapping_stale(**port_mapping_used)) {
+ r_log(LOG_GENERIC, LOG_INFO,
+ "TestNrSocket %s denying ingress from %s: "
+ "Stale port mapping",
+ internal_socket_->my_addr().as_string, from.as_string);
+ return false;
+ }
+
+ if (!nat_->allow_hairpinning_ && nat_->is_my_external_tuple(from)) {
+ r_log(LOG_GENERIC, LOG_INFO,
+ "TestNrSocket %s denying ingress from %s: "
+ "Hairpinning disallowed",
+ internal_socket_->my_addr().as_string, from.as_string);
+ return false;
+ }
+
+ return true;
+}
+
+int TestNrSocket::connect(const nr_transport_addr* addr) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %p %s connecting to %s", this,
+ internal_socket_->my_addr().as_string, addr->as_string);
+
+ if (connect_invoked_ || !port_mappings_.empty()) {
+ MOZ_CRASH("TestNrSocket::connect() called more than once!");
+ return R_INTERNAL;
+ }
+
+ if (maybe_get_redirect_targets(addr).isSome()) {
+ // If we are simulating STUN redirects for |addr|, we need to pretend that
+ // the TCP connection worked, since |addr| probably does not actually point
+ // at something that exists.
+ connect_fake_stun_address_.reset(new nr_transport_addr);
+ nr_transport_addr_copy(connect_fake_stun_address_.get(), addr);
+
+ // We dispatch this, otherwise nICEr can trip over its shoelaces
+ GetCurrentSerialEventTarget()->Dispatch(
+ NS_NewRunnableFunction("Async writeable callback for TestNrSocket",
+ [this, self = RefPtr<TestNrSocket>(this)] {
+ if (poll_flags() & PR_POLL_WRITE) {
+ fire_callback(NR_ASYNC_WAIT_WRITE);
+ }
+ }));
+
+ return R_WOULDBLOCK;
+ }
+
+ if (!nat_->enabled_ ||
+ addr->protocol == IPPROTO_UDP // Horrible hack to allow default address
+ // discovery to work. Only works because
+ // we don't normally connect on UDP.
+ || nat_->is_an_internal_tuple(*addr)) {
+ // This will set connect_invoked_
+ return internal_socket_->connect(addr);
+ }
+
+ RefPtr<NrSocketBase> external_socket(create_external_socket(*addr));
+ if (!external_socket) {
+ return R_INTERNAL;
+ }
+
+ PortMapping* port_mapping = create_port_mapping(*addr, external_socket);
+ port_mappings_.push_back(port_mapping);
+ int r = port_mapping->external_socket_->connect(addr);
+ if (r && r != R_WOULDBLOCK) {
+ return r;
+ }
+
+ port_mapping->last_used_ = PR_IntervalNow();
+
+ if (poll_flags() & PR_POLL_READ) {
+ port_mapping->async_wait(NR_ASYNC_WAIT_READ,
+ port_mapping_tcp_passthrough_callback, this,
+ (char*)__FUNCTION__, __LINE__);
+ }
+
+ return r;
+}
+
+int TestNrSocket::write(const void* msg, size_t len, size_t* written) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %p %s writing", this,
+ internal_socket_->my_addr().as_string);
+
+ UCHAR* buf = static_cast<UCHAR*>(const_cast<void*>(msg));
+
+ if (nat_->nat_delegate_ &&
+ nat_->nat_delegate_->on_write(nat_, msg, len, written)) {
+ return R_INTERNAL;
+ }
+
+ if (nat_->block_stun_ && nr_is_stun_message(buf, len)) {
+ // Should cause this socket to be abandoned
+ r_log(LOG_GENERIC, LOG_DEBUG,
+ "TestNrSocket %s dropping outgoing TCP "
+ "because it is configured to drop STUN",
+ my_addr().as_string);
+ return R_INTERNAL;
+ }
+
+ if (nr_is_stun_request_message(buf, len) && connect_fake_stun_address_ &&
+ maybe_send_fake_response(buf, len, connect_fake_stun_address_.get())) {
+ return 0;
+ }
+
+ if (nat_->block_tcp_ && !tls_) {
+ // Should cause this socket to be abandoned
+ r_log(LOG_GENERIC, LOG_DEBUG,
+ "TestNrSocket %s dropping outgoing TCP "
+ "because it is configured to drop TCP",
+ my_addr().as_string);
+ return R_INTERNAL;
+ }
+
+ if (nat_->block_tls_ && tls_) {
+ // Should cause this socket to be abandoned
+ r_log(LOG_GENERIC, LOG_DEBUG,
+ "TestNrSocket %s dropping outgoing TLS "
+ "because it is configured to drop TLS",
+ my_addr().as_string);
+ return R_INTERNAL;
+ }
+
+ if (port_mappings_.empty()) {
+ // The no-nat case, just pass call through.
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s writing",
+ my_addr().as_string);
+
+ return internal_socket_->write(msg, len, written);
+ }
+ destroy_stale_port_mappings();
+ if (port_mappings_.empty()) {
+ r_log(LOG_GENERIC, LOG_DEBUG,
+ "TestNrSocket %s dropping outgoing TCP "
+ "because the port mapping was stale",
+ my_addr().as_string);
+ return R_INTERNAL;
+ }
+ // This is TCP only
+ MOZ_ASSERT(port_mappings_.size() == 1);
+ r_log(LOG_GENERIC, LOG_DEBUG, "PortMapping %s -> %s writing",
+ port_mappings_.front()->external_socket_->my_addr().as_string,
+ port_mappings_.front()->remote_address_.as_string);
+ port_mappings_.front()->last_used_ = PR_IntervalNow();
+ return port_mappings_.front()->external_socket_->write(msg, len, written);
+}
+
+int TestNrSocket::read(void* buf, size_t maxlen, size_t* len) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %p %s reading", this,
+ internal_socket_->my_addr().as_string);
+
+ if (!read_buffer_.empty()) {
+ r_log(LOG_GENERIC, LOG_DEBUG,
+ "TestNrSocket %p %s has stuff in read_buffer_", this,
+ internal_socket_->my_addr().as_string);
+ UdpPacket packet(std::move(read_buffer_.front()));
+ read_buffer_.pop_front();
+ *len = std::min(maxlen, packet.buffer_->len());
+ memcpy(buf, packet.buffer_->data(), *len);
+ if (*len != packet.buffer_->len()) {
+ // Put remaining bytes in new packet, at the front.
+ read_buffer_.emplace_front(packet.buffer_->data() + *len,
+ packet.buffer_->len() - *len,
+ packet.remote_address_);
+ }
+ return 0;
+ }
+
+ if (connect_fake_stun_address_) {
+ return R_WOULDBLOCK;
+ }
+
+ int r;
+
+ if (port_mappings_.empty()) {
+ r = internal_socket_->read(buf, maxlen, len);
+ } else {
+ MOZ_ASSERT(port_mappings_.size() == 1);
+ r = port_mappings_.front()->external_socket_->read(buf, maxlen, len);
+ if (!r && nat_->refresh_on_ingress_) {
+ port_mappings_.front()->last_used_ = PR_IntervalNow();
+ }
+ }
+
+ if (r) {
+ return r;
+ }
+
+ if (nat_->nat_delegate_ &&
+ nat_->nat_delegate_->on_read(nat_, buf, maxlen, len)) {
+ return R_INTERNAL;
+ }
+
+ if (nat_->block_tcp_ && !tls_) {
+ // Should cause this socket to be abandoned
+ return R_INTERNAL;
+ }
+
+ if (nat_->block_tls_ && tls_) {
+ // Should cause this socket to be abandoned
+ return R_INTERNAL;
+ }
+
+ UCHAR* cbuf = static_cast<UCHAR*>(const_cast<void*>(buf));
+ if (nat_->block_stun_ && nr_is_stun_message(cbuf, *len)) {
+ // Should cause this socket to be abandoned
+ return R_INTERNAL;
+ }
+
+ return r;
+}
+
+int TestNrSocket::async_wait(int how, NR_async_cb cb, void* cb_arg,
+ char* function, int line) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s waiting for %s",
+ internal_socket_->my_addr().as_string,
+ how == NR_ASYNC_WAIT_READ ? "read" : "write");
+
+ int r;
+
+ if (how == NR_ASYNC_WAIT_READ) {
+ NrSocketBase::async_wait(how, cb, cb_arg, function, line);
+ if (!read_buffer_.empty()) {
+ fire_readable_callback();
+ return 0;
+ }
+
+ // Make sure we're waiting on the socket for the internal address
+ r = internal_socket_->async_wait(how, socket_readable_callback, this,
+ function, line);
+ } else {
+ if (connect_fake_stun_address_) {
+ // Fake TCP connection case; register the callback on this socket, not
+ // a real one.
+ return NrSocketBase::async_wait(how, cb, cb_arg, function, line);
+ }
+
+ // For write, just use the readiness of the internal socket, since we queue
+ // everything for the port mappings.
+ r = internal_socket_->async_wait(how, cb, cb_arg, function, line);
+ }
+
+ if (r) {
+ r_log(LOG_GENERIC, LOG_ERR,
+ "TestNrSocket %s failed to async_wait for "
+ "internal socket: %d\n",
+ internal_socket_->my_addr().as_string, r);
+ return r;
+ }
+
+ if (is_tcp_connection_behind_nat()) {
+ // Bypass all port-mapping related logic
+ return 0;
+ }
+
+ if (internal_socket_->my_addr().protocol == IPPROTO_TCP) {
+ // For a TCP connection through a simulated NAT, these signals are
+ // just passed through.
+ MOZ_ASSERT(port_mappings_.size() == 1);
+
+ return port_mappings_.front()->async_wait(
+ how, port_mapping_tcp_passthrough_callback, this, function, line);
+ }
+ if (how == NR_ASYNC_WAIT_READ) {
+ // For UDP port mappings, we decouple the writeable callbacks
+ for (PortMapping* port_mapping : port_mappings_) {
+ // Be ready to receive traffic on our port mappings
+ r = port_mapping->async_wait(how, socket_readable_callback, this,
+ function, line);
+ if (r) {
+ r_log(LOG_GENERIC, LOG_ERR,
+ "TestNrSocket %s failed to async_wait for "
+ "port mapping: %d\n",
+ internal_socket_->my_addr().as_string, r);
+ return r;
+ }
+ }
+ }
+
+ return 0;
+}
+
+void TestNrSocket::cancel_port_mapping_async_wait(int how) {
+ for (PortMapping* port_mapping : port_mappings_) {
+ port_mapping->cancel(how);
+ }
+}
+
+int TestNrSocket::cancel(int how) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s stop waiting for %s",
+ internal_socket_->my_addr().as_string,
+ how == NR_ASYNC_WAIT_READ ? "read" : "write");
+
+ if (connect_fake_stun_address_) {
+ return NrSocketBase::cancel(how);
+ }
+
+ // Writable callbacks are decoupled except for the TCP case
+ if (how == NR_ASYNC_WAIT_READ ||
+ internal_socket_->my_addr().protocol == IPPROTO_TCP) {
+ cancel_port_mapping_async_wait(how);
+ }
+
+ return internal_socket_->cancel(how);
+}
+
+bool TestNrSocket::has_port_mappings() const { return !port_mappings_.empty(); }
+
+bool TestNrSocket::is_my_external_tuple(const nr_transport_addr& addr) const {
+ for (PortMapping* port_mapping : port_mappings_) {
+ nr_transport_addr port_mapping_addr;
+ if (port_mapping->external_socket_->getaddr(&port_mapping_addr)) {
+ MOZ_CRASH("NrSocket::getaddr failed!");
+ }
+
+ if (!nr_transport_addr_cmp(&addr, &port_mapping_addr,
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool TestNrSocket::is_port_mapping_stale(
+ const PortMapping& port_mapping) const {
+ PRIntervalTime now = PR_IntervalNow();
+ PRIntervalTime elapsed_ticks = now - port_mapping.last_used_;
+ uint32_t idle_duration = PR_IntervalToMilliseconds(elapsed_ticks);
+ return idle_duration > nat_->mapping_timeout_;
+}
+
+void TestNrSocket::destroy_stale_port_mappings() {
+ for (auto i = port_mappings_.begin(); i != port_mappings_.end();) {
+ auto temp = i;
+ ++i;
+ if (is_port_mapping_stale(**temp)) {
+ r_log(LOG_GENERIC, LOG_INFO,
+ "TestNrSocket %s destroying port mapping %s -> %s",
+ internal_socket_->my_addr().as_string,
+ (*temp)->external_socket_->my_addr().as_string,
+ (*temp)->remote_address_.as_string);
+
+ port_mappings_.erase(temp);
+ }
+ }
+}
+
+void TestNrSocket::socket_readable_callback(void* real_sock_v, int how,
+ void* test_sock_v) {
+ TestNrSocket* test_socket = static_cast<TestNrSocket*>(test_sock_v);
+ NrSocketBase* real_socket = static_cast<NrSocketBase*>(real_sock_v);
+
+ test_socket->on_socket_readable(real_socket);
+}
+
+void TestNrSocket::on_socket_readable(NrSocketBase* real_socket) {
+ if (!readable_socket_ && (real_socket != internal_socket_)) {
+ readable_socket_ = real_socket;
+ }
+
+ fire_readable_callback();
+}
+
+void TestNrSocket::fire_readable_callback() {
+ MOZ_ASSERT(poll_flags() & PR_POLL_READ);
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %p %s ready for read", this,
+ internal_socket_->my_addr().as_string);
+ fire_callback(NR_ASYNC_WAIT_READ);
+}
+
+void TestNrSocket::port_mapping_writeable_callback(void* ext_sock_v, int how,
+ void* test_sock_v) {
+ TestNrSocket* test_socket = static_cast<TestNrSocket*>(test_sock_v);
+ NrSocketBase* external_socket = static_cast<NrSocketBase*>(ext_sock_v);
+
+ test_socket->write_to_port_mapping(external_socket);
+}
+
+void TestNrSocket::write_to_port_mapping(NrSocketBase* external_socket) {
+ MOZ_ASSERT(internal_socket_->my_addr().protocol != IPPROTO_TCP);
+
+ int r = 0;
+ for (PortMapping* port_mapping : port_mappings_) {
+ if (port_mapping->external_socket_ == external_socket) {
+ // If the send succeeds, or if there was nothing to send, we keep going
+ r = port_mapping->send_from_queue();
+ if (r) {
+ break;
+ }
+ }
+ }
+
+ if (r == R_WOULDBLOCK) {
+ // Re-register for writeable callbacks, since we still have stuff to send
+ NR_ASYNC_WAIT(external_socket, NR_ASYNC_WAIT_WRITE,
+ &TestNrSocket::port_mapping_writeable_callback, this);
+ }
+}
+
+void TestNrSocket::port_mapping_tcp_passthrough_callback(void* ext_sock_v,
+ int how,
+ void* test_sock_v) {
+ TestNrSocket* test_socket = static_cast<TestNrSocket*>(test_sock_v);
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s firing %s callback",
+ test_socket->internal_socket_->my_addr().as_string,
+ how == NR_ASYNC_WAIT_READ ? "readable" : "writeable");
+
+ test_socket->internal_socket_->fire_callback(how);
+}
+
+bool TestNrSocket::is_tcp_connection_behind_nat() const {
+ return internal_socket_->my_addr().protocol == IPPROTO_TCP &&
+ port_mappings_.empty();
+}
+
+TestNrSocket::PortMapping* TestNrSocket::get_port_mapping(
+ const nr_transport_addr& remote_address,
+ TestNat::NatBehavior filter) const {
+ for (PortMapping* port_mapping : port_mappings_) {
+ if (port_mapping_matches(*port_mapping, remote_address, filter)) {
+ return port_mapping;
+ }
+ }
+ return nullptr;
+}
+
+/* static */
+bool TestNrSocket::port_mapping_matches(const PortMapping& port_mapping,
+ const nr_transport_addr& remote_addr,
+ TestNat::NatBehavior filter) {
+ int compare_flags;
+ switch (filter) {
+ case TestNat::ENDPOINT_INDEPENDENT:
+ compare_flags = NR_TRANSPORT_ADDR_CMP_MODE_PROTOCOL;
+ break;
+ case TestNat::ADDRESS_DEPENDENT:
+ compare_flags = NR_TRANSPORT_ADDR_CMP_MODE_ADDR;
+ break;
+ case TestNat::PORT_DEPENDENT:
+ compare_flags = NR_TRANSPORT_ADDR_CMP_MODE_ALL;
+ break;
+ }
+
+ return !nr_transport_addr_cmp(&remote_addr, &port_mapping.remote_address_,
+ compare_flags);
+}
+
+TestNrSocket::PortMapping* TestNrSocket::create_port_mapping(
+ const nr_transport_addr& remote_address,
+ const RefPtr<NrSocketBase>& external_socket) const {
+ r_log(LOG_GENERIC, LOG_INFO, "TestNrSocket %s creating port mapping %s -> %s",
+ internal_socket_->my_addr().as_string,
+ external_socket->my_addr().as_string, remote_address.as_string);
+
+ return new PortMapping(remote_address, external_socket);
+}
+
+TestNrSocket::PortMapping::PortMapping(
+ const nr_transport_addr& remote_address,
+ const RefPtr<NrSocketBase>& external_socket)
+ : external_socket_(external_socket) {
+ nr_transport_addr_copy(&remote_address_, &remote_address);
+}
+
+int TestNrSocket::PortMapping::send_from_queue() {
+ MOZ_ASSERT(remote_address_.protocol != IPPROTO_TCP);
+ int r = 0;
+
+ while (!send_queue_.empty()) {
+ UdpPacket& packet = send_queue_.front();
+ r_log(LOG_GENERIC, LOG_DEBUG,
+ "PortMapping %s -> %s sending from queue to %s",
+ external_socket_->my_addr().as_string, remote_address_.as_string,
+ packet.remote_address_.as_string);
+
+ r = external_socket_->sendto(packet.buffer_->data(), packet.buffer_->len(),
+ 0, &packet.remote_address_);
+
+ if (r) {
+ if (r != R_WOULDBLOCK) {
+ r_log(LOG_GENERIC, LOG_ERR, "%s: Fatal error %d, stop trying",
+ __FUNCTION__, r);
+ send_queue_.clear();
+ } else {
+ r_log(LOG_GENERIC, LOG_DEBUG, "Would block, will retry later");
+ }
+ break;
+ }
+
+ send_queue_.pop_front();
+ }
+
+ return r;
+}
+
+int TestNrSocket::PortMapping::sendto(const void* msg, size_t len,
+ const nr_transport_addr& to) {
+ MOZ_ASSERT(remote_address_.protocol != IPPROTO_TCP);
+ r_log(LOG_GENERIC, LOG_DEBUG, "PortMapping %s -> %s sending to %s",
+ external_socket_->my_addr().as_string, remote_address_.as_string,
+ to.as_string);
+
+ last_used_ = PR_IntervalNow();
+ int r = external_socket_->sendto(msg, len, 0, &to);
+
+ if (r == R_WOULDBLOCK) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "Enqueueing UDP packet to %s", to.as_string);
+ send_queue_.emplace_back(msg, len, to);
+ return 0;
+ }
+ if (r) {
+ r_log(LOG_GENERIC, LOG_ERR, "Error: %d", r);
+ }
+
+ return r;
+}
+
+int TestNrSocket::PortMapping::async_wait(int how, NR_async_cb cb, void* cb_arg,
+ char* function, int line) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "PortMapping %s -> %s waiting for %s",
+ external_socket_->my_addr().as_string, remote_address_.as_string,
+ how == NR_ASYNC_WAIT_READ ? "read" : "write");
+
+ return external_socket_->async_wait(how, cb, cb_arg, function, line);
+}
+
+int TestNrSocket::PortMapping::cancel(int how) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "PortMapping %s -> %s stop waiting for %s",
+ external_socket_->my_addr().as_string, remote_address_.as_string,
+ how == NR_ASYNC_WAIT_READ ? "read" : "write");
+
+ return external_socket_->cancel(how);
+}
+
+class nr_stun_message_deleter {
+ public:
+ nr_stun_message_deleter() = default;
+ void operator()(nr_stun_message* msg) const { nr_stun_message_destroy(&msg); }
+};
+
+bool TestNrSocket::maybe_send_fake_response(const void* msg, size_t len,
+ const nr_transport_addr* to) {
+ Maybe<nsTArray<nsCString>> redirect_targets = maybe_get_redirect_targets(to);
+ if (!redirect_targets.isSome()) {
+ return false;
+ }
+
+ std::unique_ptr<nr_stun_message, nr_stun_message_deleter> request;
+ {
+ nr_stun_message* temp = nullptr;
+ if (NS_WARN_IF(nr_stun_message_create2(&temp, (unsigned char*)msg, len))) {
+ return false;
+ }
+ request.reset(temp);
+ }
+
+ if (NS_WARN_IF(nr_stun_decode_message(request.get(), nullptr, nullptr))) {
+ return false;
+ }
+
+ std::unique_ptr<nr_stun_message, nr_stun_message_deleter> response;
+ {
+ nr_stun_message* temp = nullptr;
+ if (nr_stun_message_create(&temp)) {
+ MOZ_CRASH("nr_stun_message_create failed!");
+ }
+ response.reset(temp);
+ }
+
+ nr_stun_form_error_response(request.get(), response.get(), 300,
+ (char*)"Try alternate");
+
+ int port = 0;
+ if (nr_transport_addr_get_port(to, &port)) {
+ MOZ_CRASH();
+ }
+
+ for (const nsCString& address : *redirect_targets) {
+ r_log(LOG_GENERIC, LOG_DEBUG,
+ "TestNrSocket attempting to add alternate server %s", address.Data());
+ nr_transport_addr addr;
+ if (NS_WARN_IF(nr_str_port_to_transport_addr(address.Data(), port,
+ IPPROTO_UDP, &addr))) {
+ continue;
+ }
+ if (nr_stun_message_add_alternate_server_attribute(response.get(), &addr)) {
+ MOZ_CRASH("nr_stun_message_add_alternate_server_attribute failed!");
+ }
+ }
+
+ if (nr_stun_encode_message(response.get())) {
+ MOZ_CRASH("nr_stun_encode_message failed!");
+ }
+
+ nr_transport_addr response_from;
+ if (nr_transport_addr_is_wildcard(to)) {
+ // |to| points to an FQDN, and nICEr is delegating DNS lookup to us; we
+ // aren't _actually_ going to do that though, so we select a bogus address
+ // for the response to come from. TEST-NET is a fairly reasonable thing to
+ // use for this.
+ int port = 0;
+ if (nr_transport_addr_get_port(to, &port)) {
+ MOZ_CRASH();
+ }
+ switch (to->ip_version) {
+ case NR_IPV4:
+ if (nr_str_port_to_transport_addr("198.51.100.1", port, to->protocol,
+ &response_from)) {
+ MOZ_CRASH();
+ }
+ break;
+ case NR_IPV6:
+ if (nr_str_port_to_transport_addr("::ffff:198.51.100.1", port,
+ to->protocol, &response_from)) {
+ MOZ_CRASH();
+ }
+ break;
+ default:
+ MOZ_CRASH();
+ }
+ } else {
+ nr_transport_addr_copy(&response_from, to);
+ }
+
+ read_buffer_.emplace_back(response->buffer, response->length, response_from);
+
+ // We dispatch this, otherwise nICEr can trip over its shoelaces
+ r_log(LOG_GENERIC, LOG_DEBUG,
+ "TestNrSocket %p scheduling callback for redirect response", this);
+ GetCurrentSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ "Async readable callback for TestNrSocket",
+ [this, self = RefPtr<TestNrSocket>(this)] {
+ if (poll_flags() & PR_POLL_READ) {
+ fire_readable_callback();
+ } else {
+ r_log(LOG_GENERIC, LOG_DEBUG,
+ "TestNrSocket %p deferring callback for redirect response",
+ this);
+ }
+ }));
+
+ return true;
+}
+
+Maybe<nsTArray<nsCString>> TestNrSocket::maybe_get_redirect_targets(
+ const nr_transport_addr* to) const {
+ Maybe<nsTArray<nsCString>> result;
+
+ // 256 is overkill, but it hardly matters
+ char addrstring[256];
+ if (nr_transport_addr_get_addrstring(to, addrstring, 256)) {
+ MOZ_CRASH("nr_transport_addr_get_addrstring failed!");
+ }
+
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket checking redirect rules for %s",
+ addrstring);
+ auto it = nat_->stun_redirect_map_.find(nsCString(addrstring));
+ if (it != nat_->stun_redirect_map_.end()) {
+ result = Some(it->second);
+ }
+
+ return result;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/test_nr_socket.h b/dom/media/webrtc/transport/test_nr_socket.h
new file mode 100644
index 0000000000..c6a796c063
--- /dev/null
+++ b/dom/media/webrtc/transport/test_nr_socket.h
@@ -0,0 +1,370 @@
+/* -*- 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/. */
+/*
+ */
+
+/*
+Based partially on original code from nICEr and nrappkit.
+
+nICEr copyright:
+
+Copyright (c) 2007, Adobe Systems, Incorporated
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+* Neither the name of Adobe Systems, Network Resonance nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+nrappkit copyright:
+
+ Copyright (C) 2001-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, Inc.
+ All Rights Reserved
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+
+ ekr@rtfm.com Thu Dec 20 20:14:49 2001
+*/
+
+// Original author: bcampen@mozilla.com [:bwc]
+
+#ifndef test_nr_socket__
+#define test_nr_socket__
+
+extern "C" {
+#include "transport_addr.h"
+}
+
+#include "nr_socket_prsock.h"
+
+extern "C" {
+#include "nr_socket.h"
+}
+
+#include <set>
+#include <map>
+#include <list>
+#include <string>
+
+#include "mozilla/UniquePtr.h"
+#include "prinrval.h"
+#include "mediapacket.h"
+
+namespace mozilla {
+
+class TestNrSocket;
+class NrSocketProxyConfig;
+
+/**
+ * A group of TestNrSockets that behave as if they were behind the same NAT.
+ * @note We deliberately avoid addref/release of TestNrSocket here to avoid
+ * masking lifetime errors elsewhere.
+ */
+class TestNat {
+ public:
+ /**
+ * This allows TestNat traffic to be passively inspected.
+ * If a non-zero (error) value is returned, the packet will be dropped,
+ * allowing for tests to extend how packet manipulation is done by
+ * TestNat with having to modify TestNat itself.
+ */
+ class NatDelegate {
+ public:
+ virtual int on_read(TestNat* nat, void* buf, size_t maxlen,
+ size_t* len) = 0;
+ virtual int on_sendto(TestNat* nat, const void* msg, size_t len, int flags,
+ const nr_transport_addr* to) = 0;
+ virtual int on_write(TestNat* nat, const void* msg, size_t len,
+ size_t* written) = 0;
+ };
+
+ typedef enum {
+ /** For mapping, one port is used for all destinations.
+ * For filtering, allow any external address/port. */
+ ENDPOINT_INDEPENDENT,
+
+ /** For mapping, one port for each destination address (for any port).
+ * For filtering, allow incoming traffic from addresses that outgoing
+ * traffic has been sent to. */
+ ADDRESS_DEPENDENT,
+
+ /** For mapping, one port for each destination address/port.
+ * For filtering, allow incoming traffic only from addresses/ports that
+ * outgoing traffic has been sent to. */
+ PORT_DEPENDENT,
+ } NatBehavior;
+
+ TestNat()
+ : enabled_(false),
+ filtering_type_(ENDPOINT_INDEPENDENT),
+ mapping_type_(ENDPOINT_INDEPENDENT),
+ mapping_timeout_(30000),
+ allow_hairpinning_(false),
+ refresh_on_ingress_(false),
+ block_udp_(false),
+ block_stun_(false),
+ block_tcp_(false),
+ block_tls_(false),
+ error_code_for_drop_(0),
+ delay_stun_resp_ms_(0),
+ nat_delegate_(nullptr),
+ sockets_() {}
+
+ bool has_port_mappings() const;
+
+ // Helps determine whether we're hairpinning
+ bool is_my_external_tuple(const nr_transport_addr& addr) const;
+ bool is_an_internal_tuple(const nr_transport_addr& addr) const;
+
+ int create_socket_factory(nr_socket_factory** factorypp);
+
+ void insert_socket(TestNrSocket* socket) { sockets_.insert(socket); }
+
+ void erase_socket(TestNrSocket* socket) { sockets_.erase(socket); }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestNat);
+
+ static NatBehavior ToNatBehavior(const std::string& type);
+
+ void set_proxy_config(std::shared_ptr<NrSocketProxyConfig> aProxyConfig);
+
+ bool enabled_;
+ TestNat::NatBehavior filtering_type_;
+ TestNat::NatBehavior mapping_type_;
+ uint32_t mapping_timeout_;
+ bool allow_hairpinning_;
+ bool refresh_on_ingress_;
+ bool block_udp_;
+ bool block_stun_;
+ bool block_tcp_;
+ bool block_tls_;
+ bool error_code_for_drop_;
+ /* Note: this can only delay a single response so far (bug 1253657) */
+ uint32_t delay_stun_resp_ms_;
+
+ // When we see an outgoing STUN request with a destination address or
+ // destination FQDN that matches a key in this map, we respond with a STUN/300
+ // with a list of ALTERNATE-SERVER fields based on the value in this map.
+ std::map<nsCString, CopyableTArray<nsCString>> stun_redirect_map_;
+
+ NatDelegate* nat_delegate_;
+ std::shared_ptr<NrSocketProxyConfig> proxy_config_;
+
+ private:
+ std::set<TestNrSocket*> sockets_;
+
+ ~TestNat() = default;
+};
+
+/**
+ * Subclass of NrSocketBase that can simulate things like being behind a NAT,
+ * packet loss, latency, packet rewriting, etc. Also exposes some stuff that
+ * assists in diagnostics.
+ * This is accomplished by wrapping an "internal" socket (that handles traffic
+ * behind the NAT), and a collection of "external" sockets (that handle traffic
+ * into/out of the NAT)
+ */
+class TestNrSocket : public NrSocketBase {
+ public:
+ explicit TestNrSocket(TestNat* nat);
+
+ bool has_port_mappings() const;
+ bool is_my_external_tuple(const nr_transport_addr& addr) const;
+
+ // Overrides of NrSocketBase
+ int create(nr_transport_addr* addr) override;
+ int sendto(const void* msg, size_t len, int flags,
+ const nr_transport_addr* to) override;
+ int recvfrom(void* buf, size_t maxlen, size_t* len, int flags,
+ nr_transport_addr* from) override;
+ int getaddr(nr_transport_addr* addrp) override;
+ void close() override;
+ int connect(const nr_transport_addr* addr) override;
+ int write(const void* msg, size_t len, size_t* written) override;
+ int read(void* buf, size_t maxlen, size_t* len) override;
+
+ int listen(int backlog) override;
+ int accept(nr_transport_addr* addrp, nr_socket** sockp) override;
+ int async_wait(int how, NR_async_cb cb, void* cb_arg, char* function,
+ int line) override;
+ int cancel(int how) override;
+
+ // Need override since this is virtual in NrSocketBase
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestNrSocket, override)
+
+ private:
+ virtual ~TestNrSocket();
+
+ class UdpPacket {
+ public:
+ UdpPacket(const void* msg, size_t len, const nr_transport_addr& addr)
+ : buffer_(new MediaPacket) {
+ buffer_->Copy(static_cast<const uint8_t*>(msg), len);
+ nr_transport_addr_copy(&remote_address_, &addr);
+ }
+
+ UdpPacket(UdpPacket&& aOrig) = default;
+
+ ~UdpPacket() = default;
+
+ nr_transport_addr remote_address_;
+ UniquePtr<MediaPacket> buffer_;
+ };
+
+ class PortMapping {
+ public:
+ PortMapping(const nr_transport_addr& remote_address,
+ const RefPtr<NrSocketBase>& external_socket);
+
+ int sendto(const void* msg, size_t len, const nr_transport_addr& to);
+ int async_wait(int how, NR_async_cb cb, void* cb_arg, char* function,
+ int line);
+ int cancel(int how);
+ int send_from_queue();
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PortMapping);
+
+ PRIntervalTime last_used_;
+ RefPtr<NrSocketBase> external_socket_;
+ // For non-symmetric, most of the data here doesn't matter
+ nr_transport_addr remote_address_;
+
+ private:
+ ~PortMapping() { external_socket_->close(); }
+
+ // If external_socket_ returns E_WOULDBLOCK, we don't want to propagate
+ // that to the code using the TestNrSocket. We can also perhaps use this
+ // to help simulate things like latency.
+ std::list<UdpPacket> send_queue_;
+ };
+
+ struct DeferredPacket {
+ DeferredPacket(TestNrSocket* sock, const void* data, size_t len, int flags,
+ const nr_transport_addr* addr,
+ RefPtr<NrSocketBase> internal_socket)
+ : socket_(sock),
+ buffer_(),
+ flags_(flags),
+ internal_socket_(internal_socket) {
+ buffer_.Copy(reinterpret_cast<const uint8_t*>(data), len);
+ nr_transport_addr_copy(&to_, addr);
+ }
+
+ TestNrSocket* socket_;
+ MediaPacket buffer_;
+ int flags_;
+ nr_transport_addr to_;
+ RefPtr<NrSocketBase> internal_socket_;
+ };
+
+ bool is_port_mapping_stale(const PortMapping& port_mapping) const;
+ bool allow_ingress(const nr_transport_addr& to, const nr_transport_addr& from,
+ PortMapping** port_mapping_used) const;
+ void destroy_stale_port_mappings();
+
+ static void socket_readable_callback(void* real_sock_v, int how,
+ void* test_sock_v);
+ void on_socket_readable(NrSocketBase* external_or_internal_socket);
+ void fire_readable_callback();
+
+ static void port_mapping_tcp_passthrough_callback(void* ext_sock_v, int how,
+ void* test_sock_v);
+ void cancel_port_mapping_async_wait(int how);
+
+ static void port_mapping_writeable_callback(void* ext_sock_v, int how,
+ void* test_sock_v);
+ void write_to_port_mapping(NrSocketBase* external_socket);
+ bool is_tcp_connection_behind_nat() const;
+
+ PortMapping* get_port_mapping(const nr_transport_addr& remote_addr,
+ TestNat::NatBehavior filter) const;
+ static bool port_mapping_matches(const PortMapping& port_mapping,
+ const nr_transport_addr& remote_addr,
+ TestNat::NatBehavior filter);
+ PortMapping* create_port_mapping(
+ const nr_transport_addr& remote_addr,
+ const RefPtr<NrSocketBase>& external_socket) const;
+ RefPtr<NrSocketBase> create_external_socket(
+ const nr_transport_addr& remote_addr) const;
+
+ static void process_delayed_cb(NR_SOCKET s, int how, void* cb_arg);
+
+ bool maybe_send_fake_response(const void* msg, size_t len,
+ const nr_transport_addr* to);
+ Maybe<nsTArray<nsCString>> maybe_get_redirect_targets(
+ const nr_transport_addr* to) const;
+
+ RefPtr<NrSocketBase> readable_socket_;
+ // The socket for the "internal" address; used to talk to stuff behind the
+ // same nat.
+ RefPtr<NrSocketBase> internal_socket_;
+ RefPtr<TestNat> nat_;
+ bool tls_;
+ // Since our comparison logic is different depending on what kind of NAT
+ // we simulate, and the STL does not make it very easy to switch out the
+ // comparison function at runtime, and these lists are going to be very
+ // small anyway, we just brute-force it.
+ std::list<RefPtr<PortMapping>> port_mappings_;
+
+ void* timer_handle_;
+
+ // Just used for fake stun responses right now. Not _necessarily_ just UDP
+ // stuff, UdpPacket just has what we need to make this work for UDP.
+ std::list<UdpPacket> read_buffer_;
+ std::unique_ptr<nr_transport_addr> connect_fake_stun_address_;
+};
+
+} // namespace mozilla
+
+#endif // test_nr_socket__