/*
 *  Copyright 2017 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/openssl_adapter.h"

#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>

#include "absl/strings/string_view.h"
#include "api/test/rtc_error_matchers.h"      // IWYU pragma: keep
#include "api/units/time_delta.h"             // IWYU pragma: keep
#include "rtc_base/logging.h"                 // IWYU pragma: keep
#include "rtc_base/net_helpers.h"             // IWYU pragma: keep
#include "rtc_base/physical_socket_server.h"  // IWYU pragma: keep
#include "rtc_base/socket.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/ssl_certificate.h"
#include "rtc_base/ssl_stream_adapter.h"  // IWYU pragma: keep
#include "rtc_base/strings/string_builder.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/run_loop.h"
#include "test/wait_until.h"  // IWYU pragma: keep

namespace webrtc {
namespace {

class MockAsyncSocket : public Socket {
 public:
  ~MockAsyncSocket() override = default;
  MOCK_METHOD(Socket*, Accept, (SocketAddress*), (override));
  MOCK_METHOD(SocketAddress, GetLocalAddress, (), (const, override));
  MOCK_METHOD(SocketAddress, GetRemoteAddress, (), (const, override));
  MOCK_METHOD(int, Bind, (const SocketAddress&), (override));
  MOCK_METHOD(int, Connect, (const SocketAddress&), (override));
  MOCK_METHOD(int, Send, (const void*, size_t), (override));
  MOCK_METHOD(int,
              SendTo,
              (const void*, size_t, const SocketAddress&),
              (override));
  MOCK_METHOD(int, Recv, (void*, size_t, int64_t*), (override));
  MOCK_METHOD(int,
              RecvFrom,
              (void*, size_t, SocketAddress*, int64_t*),
              (override));
  MOCK_METHOD(int, Listen, (int), (override));
  MOCK_METHOD(int, Close, (), (override));
  MOCK_METHOD(int, GetError, (), (const, override));
  MOCK_METHOD(void, SetError, (int), (override));
  MOCK_METHOD(ConnState, GetState, (), (const, override));
  MOCK_METHOD(int, GetOption, (Option, int*), (override));
  MOCK_METHOD(int, SetOption, (Option, int), (override));
};

class MockCertVerifier : public SSLCertificateVerifier {
 public:
  ~MockCertVerifier() override = default;
  MOCK_METHOD(bool, Verify, (const SSLCertificate&), (override));
};

#if defined(WEBRTC_EXCLUDE_BUILT_IN_SSL_ROOT_CERTS)
// Helper class to handle SSL connection events and state for testing.
class SSLConnectionHandler {
 public:
  explicit SSLConnectionHandler(absl::string_view hostname)
      : hostname_(hostname) {}
  void OnConnectEvent(Socket* socket) {
    RTC_LOG(LS_INFO) << "OnConnectEvent - Socket state: " << socket->GetState();
    OpenSSLAdapter* ssl_socket = static_cast<OpenSSLAdapter*>(socket);
    if (!ssl_started_ && ssl_socket->GetState() == Socket::CS_CONNECTED) {
      ssl_started_ = true;
      RTC_LOG(LS_INFO) << "TCP connected, starting SSL handshake...";
      int result = ssl_socket->StartSSL(hostname_);
      if (result != 0) {
        RTC_LOG(LS_ERROR) << "StartSSL failed with error: " << result;
        has_error_ = true;
      }
    } else if (ssl_started_ && ssl_socket->GetState() == Socket::CS_CONNECTED) {
      RTC_LOG(LS_INFO) << "SSL handshake completed!";
      ssl_connected_ = true;
    }
  }
  void OnReadEvent(Socket* socket) {
    RTC_LOG(LS_INFO) << "OnReadEvent - Socket state: " << socket->GetState();
    if (ssl_started_ && !ssl_connected_) {
      RTC_LOG(LS_INFO) << "SSL handshake completed via ReadEvent!";
      ssl_connected_ = true;
    }
  }
  void OnCloseEvent(Socket* socket, int err) {
    RTC_LOG(LS_INFO) << "OnCloseEvent - error: " << err
                     << ", state: " << socket->GetState();
    if (err != 0) {
      has_error_ = true;
    }
  }
  bool IsSSLConnected() const { return ssl_connected_; }
  bool HasError() const { return has_error_; }

 private:
  const absl::string_view hostname_;
  bool ssl_started_ = false;
  bool ssl_connected_ = false;
  bool has_error_ = false;
};

#endif  // WEBRTC_EXCLUDE_BUILT_IN_SSL_ROOT_CERTS

}  // namespace

using ::testing::_;
using ::testing::Return;

TEST(OpenSSLAdapterTest, TestTransformAlpnProtocols) {
  EXPECT_EQ("", TransformAlpnProtocols(std::vector<std::string>()));

  // Protocols larger than 255 characters (whose size can't be fit in a byte),
  // can't be converted, and an empty string will be returned.
  std::string large_protocol(256, 'a');
  EXPECT_EQ("",
            TransformAlpnProtocols(std::vector<std::string>{large_protocol}));

  // One protocol test.
  std::vector<std::string> alpn_protos{"h2"};
  StringBuilder sb;
  sb << static_cast<char>(2) << "h2";
  std::string expected_response = sb.Release();
  EXPECT_EQ(expected_response, TransformAlpnProtocols(alpn_protos));

  // Standard protocols test (h2,http/1.1).
  alpn_protos.push_back("http/1.1");
  StringBuilder sb2;
  sb2 << static_cast<char>(2) << "h2" << static_cast<char>(8) << "http/1.1";
  expected_response = sb2.Release();
  EXPECT_EQ(expected_response, TransformAlpnProtocols(alpn_protos));
}

// Verifies that SSLStart works when OpenSSLAdapter is started in standalone
// mode.
TEST(OpenSSLAdapterTest, TestBeginSSLBeforeConnection) {
  test::RunLoop main_thread;
  Socket* async_socket = new MockAsyncSocket();
  OpenSSLAdapter adapter(async_socket);
  EXPECT_EQ(adapter.StartSSL("webrtc.org"), 0);
}

#if defined(WEBRTC_EXCLUDE_BUILT_IN_SSL_ROOT_CERTS)
// This test is for validation of https://bugs.webrtc.org/451479054
// Since this path is not normally tested by bots, manually set
// rtc_builtin_ssl_root_certificates=false in GN to
// build and run this test.
TEST(OpenSSLAdaptorTest, TestRealSSLConnection) {
  PhysicalSocketServer socket_server;
  test::RunLoop main_thread(&socket_server);

  constexpr absl::string_view kHostname = "webrtc.org";
  constexpr int kPort = 443;
  constexpr TimeDelta kTimeout = TimeDelta::Millis(10000);

  Socket* async_socket = socket_server.CreateSocket(AF_INET, SOCK_STREAM);
  ASSERT_NE(async_socket, nullptr);

  std::unique_ptr<MockCertVerifier> mock_verifier =
      std::make_unique<MockCertVerifier>();
  EXPECT_CALL(*mock_verifier, Verify(_))
      .WillRepeatedly([]([[maybe_unused]] const SSLCertificate& cert) {
        RTC_LOG(LS_INFO) << "MockCertVerifier: assuming certificate is valid";
        return true;
      });

  std::unique_ptr<OpenSSLAdapter> ssl_adapter =
      std::make_unique<OpenSSLAdapter>(
          async_socket, /* ssl_session_cache=*/nullptr,
          /* ssl_cert_verifier=*/mock_verifier.get());
  ssl_adapter->SetRole(SSL_CLIENT);

  SSLConnectionHandler handler(kHostname);
  ssl_adapter->SubscribeConnectEvent(
      &handler, [&handler](Socket* socket) { handler.OnConnectEvent(socket); });
  ssl_adapter->SignalReadEvent.connect(&handler,
                                       &SSLConnectionHandler::OnReadEvent);
  ssl_adapter->SubscribeCloseEvent(&handler,
                                   [&handler](Socket* socket, int error) {
                                     handler.OnCloseEvent(socket, error);
                                   });

  SocketAddress addr(kHostname, kPort);
  int connect_result = ssl_adapter->Connect(addr);
  EXPECT_TRUE(connect_result == 0 || ssl_adapter->IsBlocking());

  // Wait for SSL handshake to complete.
  EXPECT_TRUE(WaitUntil([&] { return handler.IsSSLConnected(); },
                        {.timeout = kTimeout}))
      << "SSL handshake failed. Socket state: " << ssl_adapter->GetState()
      << ", Has error: " << handler.HasError();

  // Verify the connection is established.
  EXPECT_EQ(ssl_adapter->GetState(), Socket::CS_CONNECTED);
  EXPECT_FALSE(handler.HasError());

  RTC_LOG(LS_INFO) << "SSL handshake completed successfully!";
}

#endif  // WEBRTC_EXCLUDE_BUILT_IN_SSL_ROOT_CERTS

// Verifies that the adapter factory can create new adapters.
TEST(OpenSSLAdapterFactoryTest, CreateSingleOpenSSLAdapter) {
  test::RunLoop main_thread;
  OpenSSLAdapterFactory adapter_factory;
  Socket* async_socket = new MockAsyncSocket();
  auto simple_adapter = std::unique_ptr<OpenSSLAdapter>(
      adapter_factory.CreateAdapter(async_socket));
  EXPECT_NE(simple_adapter, nullptr);
}

// Verifies that setting a custom verifier still allows for adapters to be
// created.
TEST(OpenSSLAdapterFactoryTest, CreateWorksWithCustomVerifier) {
  test::RunLoop main_thread;
  MockCertVerifier* mock_verifier = new MockCertVerifier();
  EXPECT_CALL(*mock_verifier, Verify(_)).WillRepeatedly(Return(true));
  auto cert_verifier = std::unique_ptr<SSLCertificateVerifier>(mock_verifier);

  OpenSSLAdapterFactory adapter_factory;
  adapter_factory.SetCertVerifier(cert_verifier.get());
  Socket* async_socket = new MockAsyncSocket();
  auto simple_adapter = std::unique_ptr<OpenSSLAdapter>(
      adapter_factory.CreateAdapter(async_socket));
  EXPECT_NE(simple_adapter, nullptr);
}

}  // namespace webrtc
