/*
 *  Copyright 2004 The WebRTC Project Authors. All rights reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "rtc_base/physical_socket_server.h"

#include <signal.h>

#include <algorithm>
#include <memory>

#include "rtc_base/gunit.h"
#include "rtc_base/ip_address.h"
#include "rtc_base/logging.h"
#include "rtc_base/net_helpers.h"
#include "rtc_base/network_monitor.h"
#include "rtc_base/socket_unittest.h"
#include "rtc_base/test_utils.h"
#include "rtc_base/thread.h"
#include "test/gtest.h"

namespace rtc {

#define MAYBE_SKIP_IPV4                        \
  if (!HasIPv4Enabled()) {                     \
    RTC_LOG(LS_INFO) << "No IPv4... skipping"; \
    return;                                    \
  }

#define MAYBE_SKIP_IPV6                        \
  if (!HasIPv6Enabled()) {                     \
    RTC_LOG(LS_INFO) << "No IPv6... skipping"; \
    return;                                    \
  }

class PhysicalSocketTest;

class FakeSocketDispatcher : public SocketDispatcher {
 public:
  explicit FakeSocketDispatcher(PhysicalSocketServer* ss)
      : SocketDispatcher(ss) {}

  FakeSocketDispatcher(SOCKET s, PhysicalSocketServer* ss)
      : SocketDispatcher(s, ss) {}

 protected:
  SOCKET DoAccept(SOCKET socket, sockaddr* addr, socklen_t* addrlen) override;
  int DoSend(SOCKET socket, const char* buf, int len, int flags) override;
  int DoSendTo(SOCKET socket,
               const char* buf,
               int len,
               int flags,
               const struct sockaddr* dest_addr,
               socklen_t addrlen) override;
};

class FakePhysicalSocketServer : public PhysicalSocketServer {
 public:
  explicit FakePhysicalSocketServer(PhysicalSocketTest* test) : test_(test) {}

  Socket* CreateSocket(int family, int type) override {
    SocketDispatcher* dispatcher = new FakeSocketDispatcher(this);
    if (!dispatcher->Create(family, type)) {
      delete dispatcher;
      return nullptr;
    }
    return dispatcher;
  }

  Socket* WrapSocket(SOCKET s) override {
    SocketDispatcher* dispatcher = new FakeSocketDispatcher(s, this);
    if (!dispatcher->Initialize()) {
      delete dispatcher;
      return nullptr;
    }
    return dispatcher;
  }

  PhysicalSocketTest* GetTest() const { return test_; }

 private:
  PhysicalSocketTest* test_;
};

class FakeNetworkBinder : public NetworkBinderInterface {
 public:
  NetworkBindingResult BindSocketToNetwork(int, const IPAddress&) override {
    ++num_binds_;
    return result_;
  }

  void set_result(NetworkBindingResult result) { result_ = result; }

  int num_binds() { return num_binds_; }

 private:
  NetworkBindingResult result_ = NetworkBindingResult::SUCCESS;
  int num_binds_ = 0;
};

class PhysicalSocketTest : public SocketTest {
 public:
  // Set flag to simluate failures when calling "::accept" on a Socket.
  void SetFailAccept(bool fail) { fail_accept_ = fail; }
  bool FailAccept() const { return fail_accept_; }

  // Maximum size to ::send to a socket. Set to < 0 to disable limiting.
  void SetMaxSendSize(int max_size) { max_send_size_ = max_size; }
  int MaxSendSize() const { return max_send_size_; }

 protected:
  PhysicalSocketTest()
      : SocketTest(&server_),
        server_(this),
        thread_(&server_),
        fail_accept_(false),
        max_send_size_(-1) {}

  void ConnectInternalAcceptError(const IPAddress& loopback);
  void WritableAfterPartialWrite(const IPAddress& loopback);

  FakePhysicalSocketServer server_;
  rtc::AutoSocketServerThread thread_;
  bool fail_accept_;
  int max_send_size_;
};

SOCKET FakeSocketDispatcher::DoAccept(SOCKET socket,
                                      sockaddr* addr,
                                      socklen_t* addrlen) {
  FakePhysicalSocketServer* ss =
      static_cast<FakePhysicalSocketServer*>(socketserver());
  if (ss->GetTest()->FailAccept()) {
    return INVALID_SOCKET;
  }

  return SocketDispatcher::DoAccept(socket, addr, addrlen);
}

int FakeSocketDispatcher::DoSend(SOCKET socket,
                                 const char* buf,
                                 int len,
                                 int flags) {
  FakePhysicalSocketServer* ss =
      static_cast<FakePhysicalSocketServer*>(socketserver());
  if (ss->GetTest()->MaxSendSize() >= 0) {
    len = std::min(len, ss->GetTest()->MaxSendSize());
  }

  return SocketDispatcher::DoSend(socket, buf, len, flags);
}

int FakeSocketDispatcher::DoSendTo(SOCKET socket,
                                   const char* buf,
                                   int len,
                                   int flags,
                                   const struct sockaddr* dest_addr,
                                   socklen_t addrlen) {
  FakePhysicalSocketServer* ss =
      static_cast<FakePhysicalSocketServer*>(socketserver());
  if (ss->GetTest()->MaxSendSize() >= 0) {
    len = std::min(len, ss->GetTest()->MaxSendSize());
  }

  return SocketDispatcher::DoSendTo(socket, buf, len, flags, dest_addr,
                                    addrlen);
}

TEST_F(PhysicalSocketTest, TestConnectIPv4) {
  MAYBE_SKIP_IPV4;
  SocketTest::TestConnectIPv4();
}

TEST_F(PhysicalSocketTest, TestConnectIPv6) {
  SocketTest::TestConnectIPv6();
}

TEST_F(PhysicalSocketTest, TestConnectWithDnsLookupIPv4) {
  MAYBE_SKIP_IPV4;
  SocketTest::TestConnectWithDnsLookupIPv4();
}

TEST_F(PhysicalSocketTest, TestConnectWithDnsLookupIPv6) {
  SocketTest::TestConnectWithDnsLookupIPv6();
}

TEST_F(PhysicalSocketTest, TestConnectFailIPv4) {
  MAYBE_SKIP_IPV4;
  SocketTest::TestConnectFailIPv4();
}

void PhysicalSocketTest::ConnectInternalAcceptError(const IPAddress& loopback) {
  webrtc::testing::StreamSink sink;
  SocketAddress accept_addr;

  // Create two clients.
  std::unique_ptr<Socket> client1(
      server_.CreateSocket(loopback.family(), SOCK_STREAM));
  sink.Monitor(client1.get());
  EXPECT_EQ(Socket::CS_CLOSED, client1->GetState());
  EXPECT_TRUE(IsUnspecOrEmptyIP(client1->GetLocalAddress().ipaddr()));

  std::unique_ptr<Socket> client2(
      server_.CreateSocket(loopback.family(), SOCK_STREAM));
  sink.Monitor(client2.get());
  EXPECT_EQ(Socket::CS_CLOSED, client2->GetState());
  EXPECT_TRUE(IsUnspecOrEmptyIP(client2->GetLocalAddress().ipaddr()));

  // Create server and listen.
  std::unique_ptr<Socket> server(
      server_.CreateSocket(loopback.family(), SOCK_STREAM));
  sink.Monitor(server.get());
  EXPECT_EQ(0, server->Bind(SocketAddress(loopback, 0)));
  EXPECT_EQ(0, server->Listen(5));
  EXPECT_EQ(Socket::CS_CONNECTING, server->GetState());

  // Ensure no pending server connections, since we haven't done anything yet.
  EXPECT_FALSE(sink.Check(server.get(), webrtc::testing::SSE_READ));
  EXPECT_TRUE(nullptr == server->Accept(&accept_addr));
  EXPECT_TRUE(accept_addr.IsNil());

  // Attempt first connect to listening socket.
  EXPECT_EQ(0, client1->Connect(server->GetLocalAddress()));
  EXPECT_FALSE(client1->GetLocalAddress().IsNil());
  EXPECT_NE(server->GetLocalAddress(), client1->GetLocalAddress());

  // Client is connecting, outcome not yet determined.
  EXPECT_EQ(Socket::CS_CONNECTING, client1->GetState());
  EXPECT_FALSE(sink.Check(client1.get(), webrtc::testing::SSE_OPEN));
  EXPECT_FALSE(sink.Check(client1.get(), webrtc::testing::SSE_CLOSE));

  // Server has pending connection, try to accept it (will fail).
  EXPECT_TRUE_WAIT((sink.Check(server.get(), webrtc::testing::SSE_READ)),
                   kTimeout);
  // Simulate "::accept" returning an error.
  SetFailAccept(true);
  std::unique_ptr<Socket> accepted(server->Accept(&accept_addr));
  EXPECT_FALSE(accepted);
  ASSERT_TRUE(accept_addr.IsNil());

  // Ensure no more pending server connections.
  EXPECT_FALSE(sink.Check(server.get(), webrtc::testing::SSE_READ));
  EXPECT_TRUE(nullptr == server->Accept(&accept_addr));
  EXPECT_TRUE(accept_addr.IsNil());

  // Attempt second connect to listening socket.
  EXPECT_EQ(0, client2->Connect(server->GetLocalAddress()));
  EXPECT_FALSE(client2->GetLocalAddress().IsNil());
  EXPECT_NE(server->GetLocalAddress(), client2->GetLocalAddress());

  // Client is connecting, outcome not yet determined.
  EXPECT_EQ(Socket::CS_CONNECTING, client2->GetState());
  EXPECT_FALSE(sink.Check(client2.get(), webrtc::testing::SSE_OPEN));
  EXPECT_FALSE(sink.Check(client2.get(), webrtc::testing::SSE_CLOSE));

  // Server has pending connection, try to accept it (will succeed).
  EXPECT_TRUE_WAIT((sink.Check(server.get(), webrtc::testing::SSE_READ)),
                   kTimeout);
  SetFailAccept(false);
  std::unique_ptr<Socket> accepted2(server->Accept(&accept_addr));
  ASSERT_TRUE(accepted2);
  EXPECT_FALSE(accept_addr.IsNil());
  EXPECT_EQ(accepted2->GetRemoteAddress(), accept_addr);
}

TEST_F(PhysicalSocketTest, TestConnectAcceptErrorIPv4) {
  MAYBE_SKIP_IPV4;
  ConnectInternalAcceptError(kIPv4Loopback);
}

TEST_F(PhysicalSocketTest, TestConnectAcceptErrorIPv6) {
  MAYBE_SKIP_IPV6;
  ConnectInternalAcceptError(kIPv6Loopback);
}

void PhysicalSocketTest::WritableAfterPartialWrite(const IPAddress& loopback) {
  // Simulate a really small maximum send size.
  const int kMaxSendSize = 128;
  SetMaxSendSize(kMaxSendSize);

  // Run the default send/receive socket tests with a smaller amount of data
  // to avoid long running times due to the small maximum send size.
  const size_t kDataSize = 128 * 1024;
  TcpInternal(loopback, kDataSize, kMaxSendSize);
}

// https://bugs.chromium.org/p/webrtc/issues/detail?id=6167
#if defined(WEBRTC_WIN)
#define MAYBE_TestWritableAfterPartialWriteIPv4 \
  DISABLED_TestWritableAfterPartialWriteIPv4
#else
#define MAYBE_TestWritableAfterPartialWriteIPv4 \
  TestWritableAfterPartialWriteIPv4
#endif
TEST_F(PhysicalSocketTest, MAYBE_TestWritableAfterPartialWriteIPv4) {
  MAYBE_SKIP_IPV4;
  WritableAfterPartialWrite(kIPv4Loopback);
}

// https://bugs.chromium.org/p/webrtc/issues/detail?id=6167
#if defined(WEBRTC_WIN)
#define MAYBE_TestWritableAfterPartialWriteIPv6 \
  DISABLED_TestWritableAfterPartialWriteIPv6
#else
#define MAYBE_TestWritableAfterPartialWriteIPv6 \
  TestWritableAfterPartialWriteIPv6
#endif
TEST_F(PhysicalSocketTest, MAYBE_TestWritableAfterPartialWriteIPv6) {
  MAYBE_SKIP_IPV6;
  WritableAfterPartialWrite(kIPv6Loopback);
}

TEST_F(PhysicalSocketTest, TestConnectFailIPv6) {
  SocketTest::TestConnectFailIPv6();
}

TEST_F(PhysicalSocketTest, TestConnectWithDnsLookupFailIPv4) {
  MAYBE_SKIP_IPV4;
  SocketTest::TestConnectWithDnsLookupFailIPv4();
}

TEST_F(PhysicalSocketTest, TestConnectWithDnsLookupFailIPv6) {
  SocketTest::TestConnectWithDnsLookupFailIPv6();
}

TEST_F(PhysicalSocketTest, TestConnectWithClosedSocketIPv4) {
  MAYBE_SKIP_IPV4;
  SocketTest::TestConnectWithClosedSocketIPv4();
}

TEST_F(PhysicalSocketTest, TestConnectWithClosedSocketIPv6) {
  SocketTest::TestConnectWithClosedSocketIPv6();
}

TEST_F(PhysicalSocketTest, TestConnectWhileNotClosedIPv4) {
  MAYBE_SKIP_IPV4;
  SocketTest::TestConnectWhileNotClosedIPv4();
}

TEST_F(PhysicalSocketTest, TestConnectWhileNotClosedIPv6) {
  SocketTest::TestConnectWhileNotClosedIPv6();
}

TEST_F(PhysicalSocketTest, TestServerCloseDuringConnectIPv4) {
  MAYBE_SKIP_IPV4;
  SocketTest::TestServerCloseDuringConnectIPv4();
}

TEST_F(PhysicalSocketTest, TestServerCloseDuringConnectIPv6) {
  SocketTest::TestServerCloseDuringConnectIPv6();
}

TEST_F(PhysicalSocketTest, TestClientCloseDuringConnectIPv4) {
  MAYBE_SKIP_IPV4;
  SocketTest::TestClientCloseDuringConnectIPv4();
}

TEST_F(PhysicalSocketTest, TestClientCloseDuringConnectIPv6) {
  SocketTest::TestClientCloseDuringConnectIPv6();
}

TEST_F(PhysicalSocketTest, TestServerCloseIPv4) {
  MAYBE_SKIP_IPV4;
  SocketTest::TestServerCloseIPv4();
}

TEST_F(PhysicalSocketTest, TestServerCloseIPv6) {
  SocketTest::TestServerCloseIPv6();
}

TEST_F(PhysicalSocketTest, TestCloseInClosedCallbackIPv4) {
  MAYBE_SKIP_IPV4;
  SocketTest::TestCloseInClosedCallbackIPv4();
}

TEST_F(PhysicalSocketTest, TestCloseInClosedCallbackIPv6) {
  SocketTest::TestCloseInClosedCallbackIPv6();
}

TEST_F(PhysicalSocketTest, TestDeleteInReadCallbackIPv4) {
  MAYBE_SKIP_IPV4;
  SocketTest::TestDeleteInReadCallbackIPv4();
}

TEST_F(PhysicalSocketTest, TestDeleteInReadCallbackIPv6) {
  SocketTest::TestDeleteInReadCallbackIPv6();
}

TEST_F(PhysicalSocketTest, TestSocketServerWaitIPv4) {
  MAYBE_SKIP_IPV4;
  SocketTest::TestSocketServerWaitIPv4();
}

TEST_F(PhysicalSocketTest, TestSocketServerWaitIPv6) {
  SocketTest::TestSocketServerWaitIPv6();
}

TEST_F(PhysicalSocketTest, TestTcpIPv4) {
  MAYBE_SKIP_IPV4;
  SocketTest::TestTcpIPv4();
}

TEST_F(PhysicalSocketTest, TestTcpIPv6) {
  SocketTest::TestTcpIPv6();
}

TEST_F(PhysicalSocketTest, TestUdpIPv4) {
  MAYBE_SKIP_IPV4;
  SocketTest::TestUdpIPv4();
}

TEST_F(PhysicalSocketTest, TestUdpIPv6) {
  SocketTest::TestUdpIPv6();
}

// Disable for TSan v2, see
// https://code.google.com/p/webrtc/issues/detail?id=3498 for details.
// Also disable for MSan, see:
// https://code.google.com/p/webrtc/issues/detail?id=4958
// TODO(deadbeef): Enable again once test is reimplemented to be unflaky.
// Also disable for ASan.
// Disabled on Android: https://code.google.com/p/webrtc/issues/detail?id=4364
// Disabled on Linux: https://bugs.chromium.org/p/webrtc/issues/detail?id=5233
#if defined(THREAD_SANITIZER) || defined(MEMORY_SANITIZER) || \
    defined(ADDRESS_SANITIZER) || defined(WEBRTC_ANDROID) ||  \
    defined(WEBRTC_LINUX)
#define MAYBE_TestUdpReadyToSendIPv4 DISABLED_TestUdpReadyToSendIPv4
#else
#define MAYBE_TestUdpReadyToSendIPv4 TestUdpReadyToSendIPv4
#endif
TEST_F(PhysicalSocketTest, MAYBE_TestUdpReadyToSendIPv4) {
  MAYBE_SKIP_IPV4;
  SocketTest::TestUdpReadyToSendIPv4();
}

// https://bugs.chromium.org/p/webrtc/issues/detail?id=6167
#if defined(WEBRTC_WIN)
#define MAYBE_TestUdpReadyToSendIPv6 DISABLED_TestUdpReadyToSendIPv6
#else
#define MAYBE_TestUdpReadyToSendIPv6 TestUdpReadyToSendIPv6
#endif
TEST_F(PhysicalSocketTest, MAYBE_TestUdpReadyToSendIPv6) {
  SocketTest::TestUdpReadyToSendIPv6();
}

TEST_F(PhysicalSocketTest, TestGetSetOptionsIPv4) {
  MAYBE_SKIP_IPV4;
  SocketTest::TestGetSetOptionsIPv4();
}

TEST_F(PhysicalSocketTest, TestGetSetOptionsIPv6) {
  SocketTest::TestGetSetOptionsIPv6();
}

#if defined(WEBRTC_POSIX)

// We don't get recv timestamps on Mac.
#if !defined(WEBRTC_MAC)
TEST_F(PhysicalSocketTest, TestSocketRecvTimestampIPv4) {
  MAYBE_SKIP_IPV4;
  SocketTest::TestSocketRecvTimestampIPv4();
}

TEST_F(PhysicalSocketTest, TestSocketRecvTimestampIPv6) {
  SocketTest::TestSocketRecvTimestampIPv6();
}
#endif

// Verify that if the socket was unable to be bound to a real network interface
// (not loopback), Bind will return an error.
TEST_F(PhysicalSocketTest,
       BindFailsIfNetworkBinderFailsForNonLoopbackInterface) {
  MAYBE_SKIP_IPV4;
  FakeNetworkBinder fake_network_binder;
  server_.set_network_binder(&fake_network_binder);
  std::unique_ptr<Socket> socket(server_.CreateSocket(AF_INET, SOCK_DGRAM));
  fake_network_binder.set_result(NetworkBindingResult::FAILURE);
  EXPECT_EQ(-1, socket->Bind(SocketAddress("192.168.0.1", 0)));
  server_.set_network_binder(nullptr);
}

// Network binder shouldn't be used if the socket is bound to the "any" IP.
TEST_F(PhysicalSocketTest, NetworkBinderIsNotUsedForAnyIp) {
  MAYBE_SKIP_IPV4;
  FakeNetworkBinder fake_network_binder;
  server_.set_network_binder(&fake_network_binder);
  std::unique_ptr<Socket> socket(server_.CreateSocket(AF_INET, SOCK_DGRAM));
  EXPECT_EQ(0, socket->Bind(SocketAddress("0.0.0.0", 0)));
  EXPECT_EQ(0, fake_network_binder.num_binds());
  server_.set_network_binder(nullptr);
}

// For a loopback interface, failures to bind to the interface should be
// tolerated.
TEST_F(PhysicalSocketTest,
       BindSucceedsIfNetworkBinderFailsForLoopbackInterface) {
  MAYBE_SKIP_IPV4;
  FakeNetworkBinder fake_network_binder;
  server_.set_network_binder(&fake_network_binder);
  std::unique_ptr<Socket> socket(server_.CreateSocket(AF_INET, SOCK_DGRAM));
  fake_network_binder.set_result(NetworkBindingResult::FAILURE);
  EXPECT_EQ(0, socket->Bind(SocketAddress(kIPv4Loopback, 0)));
  server_.set_network_binder(nullptr);
}

#endif

}  // namespace rtc
