michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // Original author: ekr@rtfm.com michael@0: michael@0: // Some code copied from nICEr. License is: michael@0: /* michael@0: Copyright (c) 2007, Adobe Systems, Incorporated michael@0: All rights reserved. michael@0: michael@0: Redistribution and use in source and binary forms, with or without michael@0: modification, are permitted provided that the following conditions are michael@0: met: michael@0: michael@0: * Redistributions of source code must retain the above copyright michael@0: notice, this list of conditions and the following disclaimer. michael@0: michael@0: * Redistributions in binary form must reproduce the above copyright michael@0: notice, this list of conditions and the following disclaimer in the michael@0: documentation and/or other materials provided with the distribution. michael@0: michael@0: * Neither the name of Adobe Systems, Network Resonance nor the names of its michael@0: contributors may be used to endorse or promote products derived from michael@0: this software without specific prior written permission. michael@0: michael@0: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR michael@0: A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT michael@0: OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, michael@0: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT michael@0: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, michael@0: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY michael@0: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE michael@0: OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: */ michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include "sigslot.h" michael@0: michael@0: #include "logging.h" michael@0: #include "nspr.h" michael@0: #include "nss.h" michael@0: #include "ssl.h" michael@0: michael@0: #include "mozilla/Scoped.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsXPCOM.h" michael@0: michael@0: #include "mtransport_test_utils.h" michael@0: #include "runnable_utils.h" michael@0: michael@0: #define GTEST_HAS_RTTI 0 michael@0: #include "gtest/gtest.h" michael@0: #include "gtest_utils.h" michael@0: michael@0: // nICEr includes michael@0: extern "C" { michael@0: #include "nr_api.h" michael@0: #include "registry.h" michael@0: #include "async_timer.h" michael@0: #include "r_crc32.h" michael@0: #include "ice_util.h" michael@0: #include "transport_addr.h" michael@0: #include "nr_crypto.h" michael@0: #include "nr_socket.h" michael@0: #include "nr_socket_local.h" michael@0: #include "nr_socket_buffered_stun.h" michael@0: #include "stun_client_ctx.h" michael@0: #include "turn_client_ctx.h" michael@0: } michael@0: michael@0: #include "nricemediastream.h" michael@0: #include "nricectx.h" michael@0: michael@0: michael@0: MtransportTestUtils *test_utils; michael@0: michael@0: using namespace mozilla; michael@0: michael@0: std::string g_turn_server; michael@0: std::string g_turn_user; michael@0: std::string g_turn_password; michael@0: michael@0: std::string kDummyTurnServer("192.0.2.1"); // From RFC 5737 michael@0: michael@0: class TurnClient : public ::testing::Test { michael@0: public: michael@0: TurnClient() michael@0: : turn_server_(g_turn_server), michael@0: real_socket_(nullptr), michael@0: net_socket_(nullptr), michael@0: buffered_socket_(nullptr), michael@0: net_fd_(nullptr), michael@0: turn_ctx_(nullptr), michael@0: allocated_(false), michael@0: received_(0), michael@0: protocol_(IPPROTO_UDP) { michael@0: } michael@0: michael@0: ~TurnClient() { michael@0: } michael@0: michael@0: void SetTcp() { michael@0: protocol_ = IPPROTO_TCP; michael@0: } michael@0: michael@0: void Init_s() { michael@0: int r; michael@0: nr_transport_addr addr; michael@0: r = nr_ip4_port_to_transport_addr(0, 0, protocol_, &addr); michael@0: ASSERT_EQ(0, r); michael@0: michael@0: r = nr_socket_local_create(&addr, &real_socket_); michael@0: ASSERT_EQ(0, r); michael@0: michael@0: if (protocol_ == IPPROTO_TCP) { michael@0: int r = michael@0: nr_socket_buffered_stun_create(real_socket_, 100000, michael@0: &buffered_socket_); michael@0: ASSERT_EQ(0, r); michael@0: net_socket_ = buffered_socket_; michael@0: } else { michael@0: net_socket_ = real_socket_; michael@0: } michael@0: michael@0: r = nr_ip4_str_port_to_transport_addr(turn_server_.c_str(), 3478, michael@0: protocol_, &addr); michael@0: ASSERT_EQ(0, r); michael@0: michael@0: std::vector password_vec( michael@0: g_turn_password.begin(), g_turn_password.end()); michael@0: Data password; michael@0: INIT_DATA(password, &password_vec[0], password_vec.size()); michael@0: r = nr_turn_client_ctx_create("test", net_socket_, michael@0: g_turn_user.c_str(), michael@0: &password, michael@0: &addr, &turn_ctx_); michael@0: ASSERT_EQ(0, r); michael@0: michael@0: r = nr_socket_getfd(net_socket_, &net_fd_); michael@0: ASSERT_EQ(0, r); michael@0: michael@0: NR_ASYNC_WAIT(net_fd_, NR_ASYNC_WAIT_READ, socket_readable_cb, michael@0: (void *)this); michael@0: } michael@0: michael@0: void TearDown_s() { michael@0: nr_turn_client_ctx_destroy(&turn_ctx_); michael@0: if (net_fd_) { michael@0: NR_ASYNC_CANCEL(net_fd_, NR_ASYNC_WAIT_READ); michael@0: } michael@0: michael@0: nr_socket_destroy(&buffered_socket_); michael@0: } michael@0: michael@0: void TearDown() { michael@0: RUN_ON_THREAD(test_utils->sts_target(), michael@0: WrapRunnable(this, &TurnClient::TearDown_s), michael@0: NS_DISPATCH_SYNC); michael@0: } michael@0: michael@0: void Allocate_s() { michael@0: Init_s(); michael@0: ASSERT_TRUE(turn_ctx_); michael@0: michael@0: int r = nr_turn_client_allocate(turn_ctx_, michael@0: allocate_success_cb, michael@0: this); michael@0: ASSERT_EQ(0, r); michael@0: } michael@0: michael@0: void Allocate(bool expect_success=true) { michael@0: RUN_ON_THREAD(test_utils->sts_target(), michael@0: WrapRunnable(this, &TurnClient::Allocate_s), michael@0: NS_DISPATCH_SYNC); michael@0: michael@0: if (expect_success) { michael@0: ASSERT_TRUE_WAIT(allocated_, 5000); michael@0: } michael@0: else { michael@0: PR_Sleep(10000); michael@0: ASSERT_FALSE(allocated_); michael@0: } michael@0: } michael@0: michael@0: void Allocated() { michael@0: if (turn_ctx_->state!=NR_TURN_CLIENT_STATE_ALLOCATED) { michael@0: std::cerr << "Allocation failed" << std::endl; michael@0: return; michael@0: } michael@0: allocated_ = true; michael@0: michael@0: int r; michael@0: nr_transport_addr addr; michael@0: michael@0: r = nr_turn_client_get_relayed_address(turn_ctx_, &addr); michael@0: ASSERT_EQ(0, r); michael@0: michael@0: relay_addr_ = addr.as_string; michael@0: michael@0: std::cerr << "Allocation succeeded with addr=" << relay_addr_ << std::endl; michael@0: } michael@0: michael@0: void Readable(NR_SOCKET s, int how, void *arg) { michael@0: // Re-arm michael@0: std::cerr << "Socket is readable" << std::endl; michael@0: NR_ASYNC_WAIT(s, how, socket_readable_cb, arg); michael@0: michael@0: UCHAR buf[8192]; michael@0: size_t len_s; michael@0: nr_transport_addr addr; michael@0: michael@0: int r = nr_socket_recvfrom(net_socket_, buf, sizeof(buf), &len_s, 0, &addr); michael@0: if (r) { michael@0: std::cerr << "Error reading from socket" << std::endl; michael@0: return; michael@0: } michael@0: michael@0: ASSERT_LT(len_s, (size_t)INT_MAX); michael@0: int len = (int)len_s; michael@0: michael@0: if (nr_is_stun_response_message(buf, len)) { michael@0: std::cerr << "STUN response" << std::endl; michael@0: r = nr_turn_client_process_response(turn_ctx_, buf, len, &addr); michael@0: michael@0: if (r && r != R_REJECTED && r != R_RETRY) { michael@0: std::cerr << "Error processing STUN: " << r << std::endl; michael@0: } michael@0: } else if (nr_is_stun_indication_message(buf, len)) { michael@0: std::cerr << "STUN indication" << std::endl; michael@0: michael@0: /* Process the indication */ michael@0: unsigned char data[NR_STUN_MAX_MESSAGE_SIZE]; michael@0: size_t datal; michael@0: nr_transport_addr remote_addr; michael@0: michael@0: r = nr_turn_client_parse_data_indication(turn_ctx_, &addr, michael@0: buf, len, michael@0: data, &datal, sizeof(data), michael@0: &remote_addr); michael@0: ASSERT_EQ(0, r); michael@0: std::cerr << "Received " << datal << " bytes from " michael@0: << remote_addr.as_string << std::endl; michael@0: michael@0: received_ += datal; michael@0: michael@0: for (size_t i=0; i < datal; i++) { michael@0: ASSERT_EQ(i & 0xff, data[i]); michael@0: } michael@0: } michael@0: else { michael@0: if (nr_is_stun_message(buf, len)) { michael@0: std::cerr << "STUN message of unexpected type" << std::endl; michael@0: } else { michael@0: std::cerr << "Not a STUN message" << std::endl; michael@0: } michael@0: return; michael@0: } michael@0: } michael@0: michael@0: void SendTo_s(const std::string& target) { michael@0: nr_transport_addr addr; michael@0: int r; michael@0: michael@0: // Expected pattern here is "IP4:127.0.0.1:3487" michael@0: ASSERT_EQ(0, target.compare(0, 4, "IP4:")); michael@0: michael@0: size_t offset = target.rfind(':'); michael@0: ASSERT_NE(offset, std::string::npos); michael@0: michael@0: std::string host = target.substr(4, offset - 4); michael@0: std::string port = target.substr(offset + 1); michael@0: michael@0: r = nr_ip4_str_port_to_transport_addr(host.c_str(), michael@0: atoi(port.c_str()), michael@0: IPPROTO_UDP, michael@0: &addr); michael@0: ASSERT_EQ(0, r); michael@0: michael@0: unsigned char test[100]; michael@0: for (size_t i=0; ists_target(), michael@0: WrapRunnable(this, &TurnClient::SendTo_s, target), michael@0: NS_DISPATCH_SYNC); michael@0: } michael@0: michael@0: int received() const { return received_; } michael@0: michael@0: static void socket_readable_cb(NR_SOCKET s, int how, void *arg) { michael@0: static_cast(arg)->Readable(s, how, arg); michael@0: } michael@0: michael@0: static void allocate_success_cb(NR_SOCKET s, int how, void *arg){ michael@0: static_cast(arg)->Allocated(); michael@0: } michael@0: michael@0: protected: michael@0: std::string turn_server_; michael@0: nr_socket *real_socket_; michael@0: nr_socket *net_socket_; michael@0: nr_socket *buffered_socket_; michael@0: NR_SOCKET net_fd_; michael@0: nr_turn_client_ctx *turn_ctx_; michael@0: std::string relay_addr_; michael@0: bool allocated_; michael@0: int received_; michael@0: int protocol_; michael@0: }; michael@0: michael@0: TEST_F(TurnClient, Allocate) { michael@0: Allocate(); michael@0: } michael@0: michael@0: TEST_F(TurnClient, AllocateTcp) { michael@0: SetTcp(); michael@0: Allocate(); michael@0: } michael@0: michael@0: TEST_F(TurnClient, AllocateAndHold) { michael@0: Allocate(); michael@0: PR_Sleep(20000); michael@0: } michael@0: michael@0: TEST_F(TurnClient, SendToSelf) { michael@0: Allocate(); michael@0: SendTo(relay_addr_); michael@0: ASSERT_TRUE_WAIT(received() == 100, 1000); michael@0: PR_Sleep(10000); // Wait 10 seconds to make sure the michael@0: // CreatePermission has time to complete/fail. michael@0: SendTo(relay_addr_); michael@0: ASSERT_TRUE_WAIT(received() == 200, 1000); michael@0: } michael@0: michael@0: michael@0: TEST_F(TurnClient, SendToSelfTcp) { michael@0: SetTcp(); michael@0: Allocate(); michael@0: SendTo(relay_addr_); michael@0: ASSERT_TRUE_WAIT(received() == 100, 1000); michael@0: PR_Sleep(10000); // Wait 10 seconds to make sure the michael@0: // CreatePermission has time to complete/fail. michael@0: SendTo(relay_addr_); michael@0: ASSERT_TRUE_WAIT(received() == 200, 1000); michael@0: } michael@0: michael@0: TEST_F(TurnClient, AllocateDummyServer) { michael@0: turn_server_ = kDummyTurnServer; michael@0: Allocate(false); michael@0: } michael@0: michael@0: static std::string get_environment(const char *name) { michael@0: char *value = getenv(name); michael@0: michael@0: if (!value) michael@0: return ""; michael@0: michael@0: return value; michael@0: } michael@0: michael@0: int main(int argc, char **argv) michael@0: { michael@0: g_turn_server = get_environment("TURN_SERVER_ADDRESS"); michael@0: g_turn_user = get_environment("TURN_SERVER_USER"); michael@0: g_turn_password = get_environment("TURN_SERVER_PASSWORD"); michael@0: michael@0: if (g_turn_server.empty() || michael@0: g_turn_user.empty(), michael@0: g_turn_password.empty()) { michael@0: printf( michael@0: "Set TURN_SERVER_ADDRESS, TURN_SERVER_USER, and TURN_SERVER_PASSWORD\n" michael@0: "environment variables to run this test\n"); michael@0: return 0; michael@0: } michael@0: { michael@0: nr_transport_addr addr; michael@0: if (nr_ip4_str_port_to_transport_addr(g_turn_server.c_str(), 3478, michael@0: IPPROTO_UDP, &addr)) { michael@0: printf("Invalid TURN_SERVER_ADDRESS \"%s\". Only IP numbers supported.\n", michael@0: g_turn_server.c_str()); michael@0: return 0; michael@0: } michael@0: } michael@0: test_utils = new MtransportTestUtils(); michael@0: NSS_NoDB_Init(nullptr); michael@0: NSS_SetDomesticPolicy(); michael@0: michael@0: // Set up the ICE registry, etc. michael@0: // TODO(ekr@rtfm.com): Clean up michael@0: std::string dummy("dummy"); michael@0: RUN_ON_THREAD(test_utils->sts_target(), michael@0: WrapRunnableNM(&NrIceCtx::Create, michael@0: dummy, false, false), michael@0: NS_DISPATCH_SYNC); michael@0: michael@0: // Start the tests michael@0: ::testing::InitGoogleTest(&argc, argv); michael@0: michael@0: int rv = RUN_ALL_TESTS(); michael@0: delete test_utils; michael@0: return rv; michael@0: }