// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/renderer_host/p2p/socket_host_tcp_server.h"

#include <stdint.h>

#include <list>

#include "content/browser/renderer_host/p2p/socket_host_tcp.h"
#include "content/browser/renderer_host/p2p/socket_host_test_utils.h"
#include "net/base/completion_callback.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::_;
using ::testing::DeleteArg;
using ::testing::DoAll;
using ::testing::Return;

namespace {

class FakeServerSocket : public net::ServerSocket {
 public:
  FakeServerSocket()
      : listening_(false),
        accept_socket_(NULL) {
  }

  ~FakeServerSocket() override {}

  bool listening() { return listening_; }

  void AddIncoming(net::StreamSocket* socket) {
    if (!accept_callback_.is_null()) {
      DCHECK(incoming_sockets_.empty());
      accept_socket_->reset(socket);
      accept_socket_ = NULL;

      // This copy is necessary because this implementation of ServerSocket
      // bases logic on the null-ness of |accept_callback_| in the bound
      // callback.
      net::CompletionCallback cb = accept_callback_;
      accept_callback_.Reset();
      cb.Run(net::OK);
    } else {
      incoming_sockets_.push_back(socket);
    }
  }

  int Listen(const net::IPEndPoint& address, int backlog) override {
    local_address_ = address;
    listening_ = true;
    return net::OK;
  }

  int GetLocalAddress(net::IPEndPoint* address) const override {
    *address = local_address_;
    return net::OK;
  }

  int Accept(scoped_ptr<net::StreamSocket>* socket,
             const net::CompletionCallback& callback) override {
    DCHECK(socket);
    if (!incoming_sockets_.empty()) {
      socket->reset(incoming_sockets_.front());
      incoming_sockets_.pop_front();
      return net::OK;
    } else {
      accept_socket_ = socket;
      accept_callback_ = callback;
      return net::ERR_IO_PENDING;
    }
  }

 private:
  bool listening_;

  net::IPEndPoint local_address_;

  scoped_ptr<net::StreamSocket>* accept_socket_;
  net::CompletionCallback accept_callback_;

  std::list<net::StreamSocket*> incoming_sockets_;
};

}  // namespace

namespace content {

class P2PSocketHostTcpServerTest : public testing::Test {
 protected:
  void SetUp() override {
    socket_ = new FakeServerSocket();
    socket_host_.reset(
        new P2PSocketHostTcpServer(&sender_, 0, P2P_SOCKET_TCP_CLIENT));
    socket_host_->socket_.reset(socket_);

    EXPECT_CALL(
        sender_,
        Send(MatchMessage(static_cast<uint32_t>(P2PMsg_OnSocketCreated::ID))))
        .WillOnce(DoAll(DeleteArg<0>(), Return(true)));

    P2PHostAndIPEndPoint dest;
    dest.ip_address = ParseAddress(kTestIpAddress1, kTestPort1);

    socket_host_->Init(ParseAddress(kTestLocalIpAddress, 0),
                       dest);
    EXPECT_TRUE(socket_->listening());
  }

  // Needed by the chilt classes because only this class is a friend
  // of P2PSocketHostTcp.
  net::StreamSocket* GetSocketFormTcpSocketHost(P2PSocketHostTcp* host) {
    return host->socket_.get();
  }

  MockIPCSender sender_;
  FakeServerSocket* socket_;  // Owned by |socket_host_|.
  scoped_ptr<P2PSocketHostTcpServer> socket_host_;
};

// Accept incoming connection.
TEST_F(P2PSocketHostTcpServerTest, Accept) {
  FakeSocket* incoming = new FakeSocket(NULL);
  incoming->SetLocalAddress(ParseAddress(kTestLocalIpAddress, kTestPort1));
  net::IPEndPoint addr = ParseAddress(kTestIpAddress1, kTestPort1);
  incoming->SetPeerAddress(addr);

  EXPECT_CALL(sender_, Send(MatchIncomingSocketMessage(addr)))
      .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
  socket_->AddIncoming(incoming);

  const int kAcceptedSocketId = 1;

  scoped_ptr<P2PSocketHost> new_host(
      socket_host_->AcceptIncomingTcpConnection(addr, kAcceptedSocketId));
  ASSERT_TRUE(new_host.get() != NULL);
  EXPECT_EQ(incoming, GetSocketFormTcpSocketHost(
      reinterpret_cast<P2PSocketHostTcp*>(new_host.get())));
}

// Accept 2 simultaneous connections.
TEST_F(P2PSocketHostTcpServerTest, Accept2) {
  FakeSocket* incoming1 = new FakeSocket(NULL);
  incoming1->SetLocalAddress(ParseAddress(kTestLocalIpAddress, kTestPort1));
  net::IPEndPoint addr1 = ParseAddress(kTestIpAddress1, kTestPort1);
  incoming1->SetPeerAddress(addr1);
  FakeSocket* incoming2 = new FakeSocket(NULL);
  incoming2->SetLocalAddress(ParseAddress(kTestLocalIpAddress, kTestPort1));
  net::IPEndPoint addr2 = ParseAddress(kTestIpAddress2, kTestPort2);
  incoming2->SetPeerAddress(addr2);

  EXPECT_CALL(sender_, Send(MatchIncomingSocketMessage(addr1)))
      .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
  EXPECT_CALL(sender_, Send(MatchIncomingSocketMessage(addr2)))
      .WillOnce(DoAll(DeleteArg<0>(), Return(true)));
  socket_->AddIncoming(incoming1);
  socket_->AddIncoming(incoming2);

  const int kAcceptedSocketId1 = 1;
  const int kAcceptedSocketId2 = 2;

  scoped_ptr<P2PSocketHost> new_host1(
      socket_host_->AcceptIncomingTcpConnection(addr1, kAcceptedSocketId1));
  ASSERT_TRUE(new_host1.get() != NULL);
  EXPECT_EQ(incoming1, GetSocketFormTcpSocketHost(
      reinterpret_cast<P2PSocketHostTcp*>(new_host1.get())));
  scoped_ptr<P2PSocketHost> new_host2(
      socket_host_->AcceptIncomingTcpConnection(addr2, kAcceptedSocketId2));
  ASSERT_TRUE(new_host2.get() != NULL);
  EXPECT_EQ(incoming2, GetSocketFormTcpSocketHost(
      reinterpret_cast<P2PSocketHostTcp*>(new_host2.get())));
}

}  // namespace content
