// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#include <openssl/base.h>

#if !defined(OPENSSL_NO_SOCK)

#include <algorithm>
#include <string>

#include <gtest/gtest.h>

#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/mem.h>

#include "../internal.h"
#include "../test/test_util.h"
#include "./internal.h"

#if !defined(OPENSSL_WINDOWS)
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <poll.h>
#include <sys/socket.h>
#include <unistd.h>
#else
#include <fcntl.h>
#include <io.h>
OPENSSL_MSVC_PRAGMA(warning(push, 3))
#include <winsock2.h>
#include <ws2tcpip.h>
OPENSSL_MSVC_PRAGMA(warning(pop))
#endif

#if defined(AF_UNIX) && !defined(OPENSSL_WINDOWS) && !defined(OPENSSL_ANDROID)
#define AWS_LC_HAS_AF_UNIX 1
#endif

#if !defined(OPENSSL_WINDOWS)
using Socket = int;
#define INVALID_SOCKET (-1)
static int closesocket(const int sock) { return close(sock); }
static std::string LastSocketError() { return strerror(errno); }
#else
using Socket = SOCKET;
static std::string LastSocketError() {
  char buf[DECIMAL_SIZE(int) + 1];
  snprintf(buf, sizeof(buf), "%d", WSAGetLastError());
  return buf;
}
#endif

struct SockaddrStorage {
  SockaddrStorage() : storage(), len(sizeof(storage)) {}

  int family() const { return storage.ss_family; }

  sockaddr *addr_mut() { return reinterpret_cast<sockaddr *>(&storage); }
  const sockaddr *addr() const {
    return reinterpret_cast<const sockaddr *>(&storage);
  }

  sockaddr_in ToIPv4() const {
    if (family() != AF_INET || len != sizeof(sockaddr_in)) {
      ADD_FAILURE() << LastSocketError();
      return sockaddr_in();  // Initialize to zero
    }
    // Make a copy so the compiler does not read this as an aliasing violation.
    sockaddr_in ret;  // Initialize to zero
    OPENSSL_memcpy(&ret, &storage, sizeof(ret));
    return ret;
  }

  sockaddr_in6 ToIPv6() const {
    if (family() != AF_INET6 || len != sizeof(sockaddr_in6)) {
      ADD_FAILURE() << LastSocketError();
      return sockaddr_in6();
    }
    // Make a copy so the compiler does not read this as an aliasing violation.
    sockaddr_in6 ret;
    OPENSSL_memcpy(&ret, &storage, sizeof(ret));
    return ret;
  }
#ifdef AWS_LC_HAS_AF_UNIX
  sockaddr_un ToUnix() const {
    if (family() != AF_UNIX || len != sizeof(sockaddr_un)) {
      ADD_FAILURE() << LastSocketError();
      return sockaddr_un();
    }
    // Make a copy so the compiler does not read this as an aliasing violation.
    sockaddr_un ret;
    OPENSSL_memcpy(&ret, &storage, sizeof(ret));
    return ret;
  }
#endif
  bssl::UniquePtr<BIO_ADDR> ToBIO_ADDR() const {
    BIO_ADDR *bap = BIO_ADDR_new();
    if (bap == nullptr) {
      return nullptr;
    }
    switch (family()) {
      case AF_INET: {
        const sockaddr_in *sock =
            reinterpret_cast<const sockaddr_in *>(&storage);
        if (1 != BIO_ADDR_rawmake(bap, AF_INET, &sock->sin_addr,
                                  sizeof(sock->sin_addr), sock->sin_port)) {
          BIO_ADDR_free(bap);
          bap = nullptr;
        }
        break;
      }
#ifdef AF_INET6
      case AF_INET6: {
        const sockaddr_in6 *sock =
            reinterpret_cast<const sockaddr_in6 *>(&storage);
        if (1 != BIO_ADDR_rawmake(bap, AF_INET6, &sock->sin6_addr,
                                  sizeof(sock->sin6_addr), sock->sin6_port)) {
          BIO_ADDR_free(bap);
          bap = nullptr;
        }
        break;
      }
#endif
#ifdef AWS_LC_HAS_AF_UNIX
      case AF_UNIX: {
        const sockaddr_un *sock =
            reinterpret_cast<const sockaddr_un *>(&storage);
        if (1 !=
            BIO_ADDR_rawmake(
                bap, AF_UNIX, &sock->sun_path,
                OPENSSL_strnlen(sock->sun_path, sizeof(sock->sun_path)) + 1,
                0)) {
          BIO_ADDR_free(bap);
          bap = nullptr;
        }

        break;
      }
#endif
      default:
        BIO_ADDR_free(bap);
        bap = nullptr;
        break;
    }
    return bssl::UniquePtr<BIO_ADDR>(bap);
  }

  sockaddr_storage storage;
  socklen_t len;
};

class OwnedSocket {
 public:
  OwnedSocket() = default;
  explicit OwnedSocket(const Socket sock) : sock_(sock) {}
  OwnedSocket(OwnedSocket &&other) { *this = std::move(other); }
  ~OwnedSocket() { reset(); }
  OwnedSocket &operator=(OwnedSocket &&other) {
    reset(other.release());
    return *this;
  }

  bool is_valid() const { return sock_ > 0; }
  Socket get() const { return sock_; }
  Socket release() {
    const Socket temp = sock_;
    sock_ = INVALID_SOCKET;
    return temp;
  }

  SockaddrStorage storage() const {
    SockaddrStorage server_addr;
    if (0 != getsockname(get(), server_addr.addr_mut(), &server_addr.len)) {
      ADD_FAILURE() << LastSocketError();
    }
    return server_addr;
  }

  void reset(const Socket sock = INVALID_SOCKET) {
    if (is_valid()) {
      closesocket(sock_);
    }

    sock_ = sock;
  }

 private:
  Socket sock_ = INVALID_SOCKET;
};

static OwnedSocket Bind(const int family, const int type, const sockaddr *addr,
                        const socklen_t addr_len) {
  OwnedSocket sock(socket(family, type, 0));
  if (!sock.is_valid()) {
    return {};
  }

  // If no port given (e.g., addr.sin_port == 0), one is selected arbitrarily.
  if (bind(sock.get(), addr, addr_len) != 0) {
    return {};
  }

  return sock;
}

// Returns 0 when equal, otherwise returns 1.
static int BIO_ADDR_cmp(const BIO_ADDR *a, const BIO_ADDR *b) {
  if (a == b) {
    return 0;
  }
  if (a == NULL || b == NULL) {
    return 1;
  }

  // Compare address families
  int family_a = BIO_ADDR_family(a);
  int family_b = BIO_ADDR_family(b);
  if (family_a == -1) {
    return 1;
  }
  if (family_a != family_b) {
    return 1;
  }

  if (family_a == AF_UNSPEC) {
    return 0;
  }

  // Compare ports
  if (BIO_ADDR_rawport(a) != BIO_ADDR_rawport(b)) {
    return 1;
  }

  // Compare raw addresses
  unsigned char addr_a[PATH_MAX] = {0};
  unsigned char addr_b[PATH_MAX] = {0};
  size_t len_a = sizeof(addr_a);
  size_t len_b = sizeof(addr_b);

  if (!BIO_ADDR_rawaddress(a, nullptr, &len_a)) {
    return 1;
  }
  assert(len_a < sizeof(addr_a));
  if (!BIO_ADDR_rawaddress(b, nullptr, &len_b)) {
    return 1;
  }
  assert(len_b < sizeof(addr_b));

  if (len_a != len_b) {
    return 1;
  }

  len_a += 1;
  len_b += 1;
  if (!BIO_ADDR_rawaddress(a, addr_a, &len_a)) {
    return 1;
  }
  if (!BIO_ADDR_rawaddress(b, addr_b, &len_b)) {
    return 1;
  }

  if (len_a != len_b) {
    return 1;
  }

  return OPENSSL_memcmp(addr_a, addr_b, len_a) != 0;
}



#ifdef AWS_LC_HAS_AF_UNIX
static int prepare_unix_domain_socket(sockaddr_un *sun) {
  assert(sun != NULL);

  OPENSSL_cleanse(sun, sizeof(struct sockaddr_un));
  sun->sun_family = AF_UNIX;

  char dir_buffer[PATH_MAX] = {0};
  char file_buffer[PATH_MAX] = {0};

  const size_t tmp_dir_len = createTempDirPath(dir_buffer);
  const size_t tmp_file_len = createTempFILEpath(file_buffer);

  const size_t tmp_combined_len = tmp_dir_len + tmp_file_len + 1;
  if (tmp_dir_len == 0 || tmp_file_len == 0 ||
      tmp_combined_len >= sizeof(sun->sun_path) ||
      tmp_combined_len >= PATH_MAX) {
    return 0;
  }
  OPENSSL_memcpy((void *)sun->sun_path, (void *)dir_buffer, tmp_dir_len);
  sun->sun_path[tmp_dir_len] = '/';
  OPENSSL_memcpy((void *)(sun->sun_path + tmp_dir_len + 1), (void *)file_buffer,
                 tmp_file_len);
  sun->sun_path[tmp_combined_len] = '\0';
  return 1;
}
#endif

static OwnedSocket ListenLoopback(int type, int addr_family = 0,
                                  int backlog = 1) {
  OwnedSocket sock;
  if (addr_family == 0 || addr_family == AF_INET6) {
    // Try binding to IPv6
    sockaddr_in6 sin6;
    OPENSSL_cleanse(&sin6, sizeof(sin6));
    sin6.sin6_family = AF_INET6;
    if (inet_pton(AF_INET6, "::1", &sin6.sin6_addr) != 1) {
      ADD_FAILURE() << LastSocketError();
      return OwnedSocket();
    }
    sock = Bind(AF_INET6, type, reinterpret_cast<const sockaddr *>(&sin6),
                sizeof(sin6));
  }
  if (addr_family == AF_INET || (addr_family == 0 && !sock.is_valid())) {
    // Try binding to IPv4.
    sockaddr_in sin;
    OPENSSL_cleanse(&sin, sizeof(sin));
    sin.sin_family = AF_INET;
    if (inet_pton(AF_INET, "127.0.0.1", &sin.sin_addr) != 1) {
      ADD_FAILURE() << LastSocketError();
      return OwnedSocket();
    }
    sock = Bind(AF_INET, type, reinterpret_cast<const sockaddr *>(&sin),
                sizeof(sin));

    if (!sock.is_valid()) {
      ADD_FAILURE() << LastSocketError();
      return OwnedSocket();
    }
  }
#ifdef AWS_LC_HAS_AF_UNIX
  if (addr_family == AF_UNIX) {
    sockaddr_un sun;
    if (1 != prepare_unix_domain_socket(&sun)) {
      ADD_FAILURE() << LastSocketError();
      return OwnedSocket();
    }

    // Try binding to Unix domain socket
    sock = Bind(AF_UNIX, type, reinterpret_cast<const sockaddr *>(&sun),
                sizeof(sun));
    if (!sock.is_valid()) {
      ADD_FAILURE() << LastSocketError();
      unlink((char *)sun.sun_path);
      return OwnedSocket();
    }

    // Set socket permissions (optional)
    if (chmod(sun.sun_path, 0660) == -1) {
      ADD_FAILURE() << LastSocketError();
      unlink((char *)sun.sun_path);
      return OwnedSocket();
    }
  }
#endif
  // Mark the socket as being used to accept incoming connection requests using
  // accept(2). Socket must be of type SOCK_STREAM or SOCK_SEQPACKET.
  if (type == SOCK_STREAM || type == SOCK_SEQPACKET) {
    if (listen(sock.get(), backlog) != 0) {
      ADD_FAILURE() << LastSocketError();
      return OwnedSocket();
    }
  }

  return sock;
}

static bool SocketSetNonBlocking(Socket sock) {
#if defined(OPENSSL_WINDOWS)
  u_long arg = 1;
  return ioctlsocket(sock, FIONBIO, &arg) == 0;
#else
  int flags = fcntl(sock, F_GETFL, 0);
  if (flags < 0) {
    return false;
  }
  flags |= O_NONBLOCK;
  return fcntl(sock, F_SETFL, flags) == 0;
#endif
}

enum class WaitType { kRead, kWrite };

static bool WaitForSocket(Socket sock, WaitType wait_type) {
  // Use an arbitrary 5-second timeout, so the test doesn't hang indefinitely if
  // there's an issue.
  static constexpr int kTimeoutSeconds = 5;
#if defined(OPENSSL_WINDOWS)
  fd_set read_set, write_set;
  FD_ZERO(&read_set);
  FD_ZERO(&write_set);
  fd_set *wait_set = wait_type == WaitType::kRead ? &read_set : &write_set;
  FD_SET(sock, wait_set);
  timeval timeout = {kTimeoutSeconds, 0};
  if (select(0 /* unused on Windows */, &read_set, &write_set, nullptr,
             &timeout) <= 0) {
    return false;
  }
  return FD_ISSET(sock, wait_set);
#else
  const short events = wait_type == WaitType::kRead ? POLLIN : POLLOUT;
  pollfd fd = {.fd = sock, .events = events, .revents = 0};
  return poll(&fd, 1, kTimeoutSeconds * 1000) == 1 && (fd.revents & events);
#endif
}

TEST(BIOTest, SocketConnect) {
  static constexpr char kTestMessage[] = "test";
  const OwnedSocket listening_sock = ListenLoopback(SOCK_STREAM);
  ASSERT_TRUE(listening_sock.is_valid()) << LastSocketError();

  SockaddrStorage addr;
  ASSERT_EQ(getsockname(listening_sock.get(), addr.addr_mut(), &addr.len), 0)
      << LastSocketError();

  char hostname[80];
  if (addr.family() == AF_INET6) {
    snprintf(hostname, sizeof(hostname), "[::1]:%d",
             ntohs(addr.ToIPv6().sin6_port));
  } else {
    snprintf(hostname, sizeof(hostname), "127.0.0.1:%d",
             ntohs(addr.ToIPv4().sin_port));
  }

  // Connect to it with a connect BIO.
  const bssl::UniquePtr<BIO> bio(BIO_new_connect(hostname));
  ASSERT_TRUE(bio);

  // Write a test message to the BIO. This is assumed to be smaller than the
  // transport buffer.
  ASSERT_EQ(static_cast<int>(sizeof(kTestMessage)),
            BIO_write(bio.get(), kTestMessage, sizeof(kTestMessage)))
      << LastSocketError();

  // Accept the socket.
  const OwnedSocket sock(
      accept(listening_sock.get(), addr.addr_mut(), &addr.len));
  ASSERT_TRUE(sock.is_valid()) << LastSocketError();

  // Check the same message is read back out.
  char buf[sizeof(kTestMessage)];
  ASSERT_EQ(static_cast<int>(sizeof(kTestMessage)),
            recv(sock.get(), buf, sizeof(buf), 0))
      << LastSocketError();
  ASSERT_EQ(Bytes(kTestMessage, sizeof(kTestMessage)), Bytes(buf, sizeof(buf)));
}

TEST(BIOTest, SocketNonBlocking) {
  OwnedSocket listening_sock = ListenLoopback(SOCK_STREAM);
  ASSERT_TRUE(listening_sock.is_valid()) << LastSocketError();

  // Connect to |listening_sock|.
  SockaddrStorage addr;
  ASSERT_EQ(getsockname(listening_sock.get(), addr.addr_mut(), &addr.len), 0)
      << LastSocketError();
  OwnedSocket connect_sock(socket(addr.family(), SOCK_STREAM, 0));
  ASSERT_TRUE(connect_sock.is_valid()) << LastSocketError();
  ASSERT_EQ(connect(connect_sock.get(), addr.addr(), addr.len), 0)
      << LastSocketError();
  ASSERT_TRUE(SocketSetNonBlocking(connect_sock.get())) << LastSocketError();
  bssl::UniquePtr<BIO> connect_bio(
      BIO_new_socket(connect_sock.get(), BIO_NOCLOSE));
  ASSERT_TRUE(connect_bio);

  // Make a corresponding accepting socket.
  OwnedSocket accept_sock(
      accept(listening_sock.get(), addr.addr_mut(), &addr.len));
  ASSERT_TRUE(accept_sock.is_valid()) << LastSocketError();
  ASSERT_TRUE(SocketSetNonBlocking(accept_sock.get())) << LastSocketError();
  bssl::UniquePtr<BIO> accept_bio(
      BIO_new_socket(accept_sock.get(), BIO_NOCLOSE));
  ASSERT_TRUE(accept_bio);

  // Exchange data through the socket.
  static constexpr char kTestMessage[] = "hello, world";

  // Reading from |accept_bio| should not block.
  char buf[sizeof(kTestMessage)];
  int ret = BIO_read(accept_bio.get(), buf, sizeof(buf));
  EXPECT_EQ(ret, -1);
  EXPECT_TRUE(BIO_should_read(accept_bio.get())) << LastSocketError();

  // Writing to |connect_bio| should eventually overflow the transport buffers
  // and also give a retryable error.
  int bytes_written = 0;
  for (;;) {
    ret = BIO_write(connect_bio.get(), kTestMessage, sizeof(kTestMessage));
    if (ret <= 0) {
      EXPECT_EQ(ret, -1);
      EXPECT_TRUE(BIO_should_write(connect_bio.get())) << LastSocketError();
      break;
    }
    bytes_written += ret;
  }
  EXPECT_GT(bytes_written, 0);

  // |accept_bio| should readable. Drain it. Note data is not always available
  // from loopback immediately, notably on macOS, so wait for the socket first.
  int bytes_read = 0;
  while (bytes_read < bytes_written) {
    ASSERT_TRUE(WaitForSocket(accept_sock.get(), WaitType::kRead))
        << LastSocketError();
    ret = BIO_read(accept_bio.get(), buf, sizeof(buf));
    ASSERT_GT(ret, 0);
    bytes_read += ret;
  }

  // |connect_bio| should become writeable again.
  ASSERT_TRUE(WaitForSocket(accept_sock.get(), WaitType::kWrite))
      << LastSocketError();
  ret = BIO_write(connect_bio.get(), kTestMessage, sizeof(kTestMessage));
  EXPECT_EQ(static_cast<int>(sizeof(kTestMessage)), ret);

  ASSERT_TRUE(WaitForSocket(accept_sock.get(), WaitType::kRead))
      << LastSocketError();
  ret = BIO_read(accept_bio.get(), buf, sizeof(buf));
  EXPECT_EQ(static_cast<int>(sizeof(kTestMessage)), ret);
  EXPECT_EQ(Bytes(buf), Bytes(kTestMessage));

  // Close one socket. We should get an EOF out the other.
  connect_bio.reset();
  connect_sock.reset();

  ASSERT_TRUE(WaitForSocket(accept_sock.get(), WaitType::kRead))
      << LastSocketError();
  ret = BIO_read(accept_bio.get(), buf, sizeof(buf));
  EXPECT_EQ(ret, 0) << LastSocketError();
  EXPECT_FALSE(BIO_should_read(accept_bio.get()));
}

static bssl::UniquePtr<BIO> create_server_bio(int addr_family, int type) {
  // Create a server socket.
  OwnedSocket server_sock(ListenLoopback(type, addr_family));
  if (!server_sock.is_valid()) {
    if (addr_family != AF_INET6) {
      // Some CodeBuild environments don't support IPv6
      ADD_FAILURE() << LastSocketError();
    }
    return nullptr;
  }

  // Wrap the server socket in a BIO.
  return bssl::UniquePtr<BIO>(BIO_new_dgram(server_sock.release(), BIO_CLOSE));
}

static bssl::UniquePtr<BIO> create_client_bio(int addr_family, int type) {
  OwnedSocket client_sock;

#ifdef AWS_LC_HAS_AF_UNIX
  // Create a client socket.
  if (addr_family == AF_UNIX) {
    // Unix domain sockets must be configured with a file in order to
    // receive a message.
    sockaddr_un sun;
    if (1 != prepare_unix_domain_socket(&sun)) {
      ADD_FAILURE() << LastSocketError();
      return nullptr;
    }

    // Try binding to Unix domain socket
    client_sock = Bind(AF_UNIX, type, reinterpret_cast<const sockaddr *>(&sun),
                       sizeof(sun));
  } else
#endif
  {
    client_sock.reset(socket(addr_family, type, 0));
  }
  if (!client_sock.is_valid()) {
    ADD_FAILURE() << LastSocketError();
    return nullptr;
  }

  // Wrap the client socket in a BIO.
  return bssl::UniquePtr<BIO>(BIO_new_dgram(client_sock.release(), BIO_CLOSE));
}

static void test_send_receive(bssl::UniquePtr<BIO> &sender_bio,
                              bssl::UniquePtr<BIO> &receiver_bio) {
  static constexpr char kTestMessage[] = "test";

  // Send a message
  ASSERT_EQ((int)strlen(kTestMessage) + 1,
            BIO_write(sender_bio.get(), kTestMessage, sizeof(kTestMessage)))
      << LastSocketError();
  // BIO_flush is a no-op, but test it anyway.
  ASSERT_EQ(1, BIO_flush(sender_bio.get())) << LastSocketError();

  // Receive a message
  char buff[1024];
  OPENSSL_cleanse(buff, sizeof(buff));
  ASSERT_EQ((int)strlen(kTestMessage) + 1,
            BIO_read(receiver_bio.get(), buff, sizeof(buff)))
      << LastSocketError();

  // Verify the message received matches the message sent.
  ASSERT_EQ(Bytes(buff), Bytes(kTestMessage));
}

static void test_puts_receive(bssl::UniquePtr<BIO> &sender_bio,
                              bssl::UniquePtr<BIO> &receiver_bio) {
  static constexpr char kTestMessage[] = "test";

  // Send a message
  ASSERT_EQ((int)strlen(kTestMessage), BIO_puts(sender_bio.get(), kTestMessage))
      << LastSocketError();
  // BIO_flush is a no-op, but test it anyway.
  ASSERT_EQ(1, BIO_flush(sender_bio.get())) << LastSocketError();

  // Receive a message.
  char buff[1024];
  OPENSSL_cleanse(buff, sizeof(buff));
  // `BIO_puts` does not send the \0 byte at the end of the string.
  ASSERT_EQ((int)strlen(kTestMessage),
            BIO_read(receiver_bio.get(), buff, sizeof(buff)))
      << LastSocketError();
  buff[strlen(kTestMessage)] = '\0';

  // Verify the message received matches the message sent.
  ASSERT_EQ(Bytes(buff), Bytes(kTestMessage));
}

class BIODgramTest : public testing::TestWithParam<int> {};


#if defined(AF_INET6)
#if defined(AWS_LC_HAS_AF_UNIX)
INSTANTIATE_TEST_SUITE_P(AddrFamily, BIODgramTest,
                         testing::Values(AF_INET, AF_INET6, AF_UNIX));
#else
INSTANTIATE_TEST_SUITE_P(AddrFamily, BIODgramTest,
                         testing::Values(AF_INET, AF_INET6));
#endif
#elif defined(AWS_LC_HAS_AF_UNIX)
INSTANTIATE_TEST_SUITE_P(AddrFamily, BIODgramTest,
                         testing::Values(AF_INET, AF_UNIX));
#else
INSTANTIATE_TEST_SUITE_P(AddrFamily, BIODgramTest, testing::Values(AF_INET));
#endif

TEST_P(BIODgramTest, SocketDatagramSetPeer) {
  int addr_family = GetParam();
  // Wrap the server socket in a BIO.
  bssl::UniquePtr<BIO> server_bio = create_server_bio(addr_family, SOCK_DGRAM);
  if (!server_bio && addr_family == AF_INET6) {
    // Some CodeBuild environments don't support IPv6
    GTEST_SKIP() << "IPv6 not supported";
    return;
  }
#if defined(__MINGW32__) && defined(AWS_LC_HAS_AF_UNIX)
  if (addr_family == AF_UNIX) {
    // Wine (is not an emulator) doesn't properly support Unix domain sockets
    GTEST_SKIP() << "Unix domain sockets not supported";
    return;
  }
#endif
  ASSERT_TRUE(server_bio) << LastSocketError();
  ASSERT_EQ(1, BIO_get_close(server_bio.get())) << LastSocketError();

  OwnedSocket server_sock(BIO_get_fd(server_bio.get(), NULL));
  ASSERT_EQ(1, BIO_set_close(server_bio.get(), BIO_NOCLOSE))
      << LastSocketError();
  SockaddrStorage server_addr = server_sock.storage();

  // Get the server socket's address
  bssl::UniquePtr<BIO> client_bio =
      create_client_bio(server_addr.family(), SOCK_DGRAM);
  ASSERT_TRUE(client_bio) << LastSocketError();

  // "Connect" the client to server
  bssl::UniquePtr<BIO_ADDR> server_bio_addr = server_addr.ToBIO_ADDR();
  ASSERT_TRUE(server_bio_addr);
  if (addr_family == AF_UNIX) {
    ASSERT_EQ(0, BIO_ADDR_rawport(server_bio_addr.get()));
  } else {
    ASSERT_LT(0, BIO_ADDR_rawport(server_bio_addr.get()));
  }
  ASSERT_EQ(1, BIO_dgram_set_peer(client_bio.get(), server_bio_addr.get()))
      << LastSocketError();

  // Create a copy of the server address for comparison
  bssl::UniquePtr<BIO_ADDR> bio_server_addr_copy(
      BIO_ADDR_dup(server_bio_addr.get()));
  ASSERT_TRUE(bio_server_addr_copy);

  ASSERT_EQ(0, BIO_ADDR_cmp(bio_server_addr_copy.get(), server_bio_addr.get()))
      << LastSocketError();

  // Get peer and verify it matches
  bssl::UniquePtr<BIO_ADDR> peer_addr(BIO_ADDR_new());
  ASSERT_GT(BIO_dgram_get_peer(client_bio.get(), peer_addr.get()), 0)
      << LastSocketError();

  ASSERT_EQ(0, BIO_ADDR_cmp(bio_server_addr_copy.get(), peer_addr.get()))
      << LastSocketError();


  test_send_receive(client_bio, server_bio);
  ASSERT_EQ(0, BIO_dgram_send_timedout(client_bio.get()));
  ASSERT_EQ(0, BIO_dgram_recv_timedout(server_bio.get()));
  test_send_receive(server_bio, client_bio);
  test_puts_receive(client_bio, server_bio);
}

TEST_P(BIODgramTest, SocketDatagramSetConnected) {
  int addr_family = GetParam();
  // Wrap the server socket in a BIO.
  bssl::UniquePtr<BIO> server_bio = create_server_bio(addr_family, SOCK_DGRAM);
  if (!server_bio && addr_family == AF_INET6) {
    // Some CodeBuild environments don't support IPv6
    GTEST_SKIP() << "IPv6 not supported";
    return;
  }
  ASSERT_TRUE(server_bio) << LastSocketError();

  OwnedSocket server_sock(BIO_get_fd(server_bio.get(), NULL));
  ASSERT_EQ(1, BIO_set_close(server_bio.get(), BIO_NOCLOSE))
      << LastSocketError();
  SockaddrStorage server_addr = server_sock.storage();

  // Get the server socket's address
  bssl::UniquePtr<BIO> client_bio =
      create_client_bio(server_addr.family(), SOCK_DGRAM);
  ASSERT_TRUE(client_bio) << LastSocketError();

  int client_fd = 0;
  int result = BIO_get_fd(client_bio.get(), &client_fd);
  ASSERT_EQ(result, client_fd);
  ASSERT_EQ(1, BIO_set_close(server_bio.get(), BIO_NOCLOSE))
      << LastSocketError();

  // "Connect" the client to server
  ASSERT_EQ(connect(client_fd, server_addr.addr(), server_addr.len), 0)
      << LastSocketError();
  bssl::UniquePtr<BIO_ADDR> server_bio_addr = server_addr.ToBIO_ADDR();
  ASSERT_TRUE(server_bio_addr);
  if (addr_family == AF_UNIX) {
    ASSERT_EQ(0, BIO_ADDR_rawport(server_bio_addr.get()));
  } else {
    ASSERT_LT(0, BIO_ADDR_rawport(server_bio_addr.get()));
  }
  ASSERT_EQ(1, BIO_ctrl_set_connected(client_bio.get(), server_bio_addr.get()))
      << LastSocketError();

  test_send_receive(client_bio, server_bio);
  test_send_receive(server_bio, client_bio);

  ASSERT_EQ(1, BIO_ctrl_set_connected(client_bio.get(), NULL))
      << LastSocketError();

  static constexpr char kTestMessage[] = "test";

  // Behavior is different on Linux
  int expected_result =
#if defined(OPENSSL_LINUX)
      addr_family == AF_INET6
          ? (int)OPENSSL_strnlen((const char *)kTestMessage, 32) + 1
          : -1;
#else
      -1;
#endif

#if defined(__MINGW32__)
  // Mingw environments are inconsistent as to this behavior.
  if (addr_family == AF_INET6) {
    return;
  }
#endif
  ASSERT_EQ(expected_result,
            BIO_write(client_bio.get(), kTestMessage, sizeof(kTestMessage)))
      << LastSocketError();
}

TEST_P(BIODgramTest, SocketDatagramConnect) {
  int addr_family = GetParam();
  // Wrap the server socket in a BIO.
  bssl::UniquePtr<BIO> server_bio = create_server_bio(addr_family, SOCK_DGRAM);
  if (!server_bio && addr_family == AF_INET6) {
    // Some CodeBuild environments don't support IPv6
    GTEST_SKIP() << "IPv6 not supported";
    return;
  }
  ASSERT_TRUE(server_bio) << LastSocketError();

  OwnedSocket server_sock(BIO_get_fd(server_bio.get(), NULL));
  ASSERT_EQ(1, BIO_set_close(server_bio.get(), BIO_NOCLOSE))
      << LastSocketError();
  SockaddrStorage server_addr = server_sock.storage();

  // Get the server socket's address
  bssl::UniquePtr<BIO> client_bio =
      create_client_bio(server_addr.family(), SOCK_DGRAM);
  ASSERT_TRUE(client_bio) << LastSocketError();

  // "Connect" the client to server
  bssl::UniquePtr<BIO_ADDR> server_bio_addr = server_addr.ToBIO_ADDR();
  ASSERT_TRUE(server_bio_addr);
  if (addr_family == AF_UNIX) {
    ASSERT_EQ(0, BIO_ADDR_rawport(server_bio_addr.get()));
  } else {
    ASSERT_LT(0, BIO_ADDR_rawport(server_bio_addr.get()));
  }
  ASSERT_TRUE(server_bio_addr);
  ASSERT_EQ(1, BIO_ctrl_dgram_connect(client_bio.get(), server_bio_addr.get()))
      << LastSocketError();
  ;

  test_send_receive(client_bio, server_bio);
  test_send_receive(server_bio, client_bio);
}


TEST_P(BIODgramTest, SocketDatagramTimeouts) {
  int addr_family = GetParam();
  // Wrap the server socket in a BIO.
  bssl::UniquePtr<BIO> bio = create_server_bio(addr_family, SOCK_DGRAM);
  if (!bio && addr_family == AF_INET6) {
    // Some CodeBuild environments don't support IPv6
    GTEST_SKIP() << "IPv6 not supported";
    return;
  }
#if defined(__MINGW32__) && defined(AWS_LC_HAS_AF_UNIX)
  if (addr_family == AF_UNIX) {
    // Wine (is not an emulator) doesn't properly support Unix domain sockets
    GTEST_SKIP() << "Unix domain sockets not supported";
    return;
  }
#endif
  ASSERT_TRUE(bio) << LastSocketError();

  // Test receive timeout
  struct timeval recv_timeout_set = {3, 500000}; // 3.5 seconds
  ASSERT_EQ(1, BIO_ctrl(bio.get(), BIO_CTRL_DGRAM_SET_RECV_TIMEOUT, 0, &recv_timeout_set))
      << LastSocketError();

  struct timeval recv_timeout_get = {0, 0};
  ASSERT_EQ(sizeof(struct timeval), (size_t)BIO_ctrl(bio.get(), BIO_CTRL_DGRAM_GET_RECV_TIMEOUT, 0, &recv_timeout_get))
      << LastSocketError();

  // Verify the timeout values match what we set (allowing for small variations in microseconds)
  EXPECT_EQ(recv_timeout_set.tv_sec, recv_timeout_get.tv_sec);
  // Allow for small variations in microseconds that might occur on different systems
  EXPECT_NEAR(recv_timeout_set.tv_usec, recv_timeout_get.tv_usec, 5000);

  // Test send timeout
  struct timeval send_timeout_set = {2, 750000}; // 2.75 seconds
  ASSERT_EQ(1, BIO_ctrl(bio.get(), BIO_CTRL_DGRAM_SET_SEND_TIMEOUT, 0, &send_timeout_set))
      << LastSocketError();

  struct timeval send_timeout_get = {0, 0};
  ASSERT_EQ(sizeof(struct timeval), (size_t)BIO_ctrl(bio.get(), BIO_CTRL_DGRAM_GET_SEND_TIMEOUT, 0, &send_timeout_get))
      << LastSocketError();

  // Verify the timeout values match what we set (allowing for small variations in microseconds)
  EXPECT_EQ(send_timeout_set.tv_sec, send_timeout_get.tv_sec);
  // Allow for small variations in microseconds that might occur on different systems
  EXPECT_NEAR(send_timeout_set.tv_usec, send_timeout_get.tv_usec, 5000);
}

TEST_P(BIODgramTest, SocketDatagramTimeoutBehavior) {
  int addr_family = GetParam();

#if defined(__MINGW32__) && defined(AWS_LC_HAS_AF_UNIX)
  if (addr_family == AF_UNIX) {
    // Wine (is not an emulator) doesn't properly support Unix domain sockets
    GTEST_SKIP() << "Unix domain sockets not supported";
    return;
  }
#endif

  // Part 1: Test receive timeout
  {
    bssl::UniquePtr<BIO> bio = create_server_bio(addr_family, SOCK_DGRAM);
    if (!bio && addr_family == AF_INET6) {
      // Some CodeBuild environments don't support IPv6
      GTEST_SKIP() << "IPv6 not supported";
      return;
    }
    ASSERT_TRUE(bio) << LastSocketError();

    // Set a very short receive timeout (100ms)
    struct timeval recv_timeout = {0, 100000}; // 0.1 seconds
    ASSERT_EQ(1, BIO_ctrl(bio.get(), BIO_CTRL_DGRAM_SET_RECV_TIMEOUT, 0, &recv_timeout))
        << LastSocketError();

    // Try to read from the socket. Since no data is being sent, it should time out.
    char buffer[256];
    int ret = BIO_read(bio.get(), buffer, sizeof(buffer));

    // The read should fail.
    EXPECT_EQ(-1, ret);

    // Check if the timeout was detected correctly.
    EXPECT_EQ(1, BIO_dgram_recv_timedout(bio.get()));
    EXPECT_EQ(0, BIO_dgram_send_timedout(bio.get()));
  }

  // Triggering a send timeout requires filling the kernel's send buffer, which
  // is unreliable. The OS may drop packets or have a very large buffer,
  // preventing the send call from blocking. The API's get/set functionality
  // is verified in the |SocketDatagramTimeouts| test.
}

class BIOAddrTest : public ::testing::Test {
 protected:
  void SetUp() override {
    addr1 = BIO_ADDR_new();
    addr2 = BIO_ADDR_new();
    ASSERT_NE(addr1, nullptr);
    ASSERT_NE(addr2, nullptr);
  }

  void TearDown() override {
    BIO_ADDR_free(addr1);
    BIO_ADDR_free(addr2);
  }

  BIO_ADDR *addr1;
  BIO_ADDR *addr2;
};

// Tests for BIO_ADDR_copy
TEST_F(BIOAddrTest, CopyNullDst) {
  EXPECT_EQ(BIO_ADDR_copy(nullptr, addr1), 0);
}

TEST_F(BIOAddrTest, CopyNullSrc) {
  EXPECT_EQ(BIO_ADDR_copy(addr1, nullptr), 0);
}

TEST_F(BIOAddrTest, CopyUnspecifiedAddress) {
  // AF_UNSPEC is already set by default in a new BIO_ADDR
  EXPECT_EQ(BIO_ADDR_copy(addr1, addr2), 1);
  EXPECT_EQ(BIO_ADDR_cmp(addr1, addr2), 0);
}

TEST_F(BIOAddrTest, CopySpecifiedAddressIPv4) {
  long addr = INADDR_LOOPBACK;
  EXPECT_EQ(BIO_ADDR_rawmake(addr2, AF_INET, &addr, sizeof(in_addr), htons(443)), 1);

  EXPECT_EQ(BIO_ADDR_copy(addr1, addr2), 1);
  EXPECT_EQ(BIO_ADDR_cmp(addr1, addr2), 0);
}

#ifdef AF_INET6
TEST_F(BIOAddrTest, CopySpecifiedAddressIPv6) {
  EXPECT_EQ(BIO_ADDR_rawmake(addr2, AF_INET6, &in6addr_loopback,
                             sizeof(in6addr_loopback), htons(443)),
            1);
  EXPECT_EQ(BIO_ADDR_copy(addr1, addr2), 1);
  EXPECT_EQ(BIO_ADDR_cmp(addr1, addr2), 0);
}
#endif

#ifdef AWS_LC_HAS_AF_UNIX
TEST_F(BIOAddrTest, CopySpecifiedAddressUnix) {
  const char *path = "/tmp/test.sock";

  EXPECT_EQ(BIO_ADDR_rawmake(addr2, AF_UNIX, path,
                             OPENSSL_strnlen(path, sizeof(sockaddr_un::sun_path)), 0),
            1);
  EXPECT_EQ(BIO_ADDR_copy(addr1, addr2), 1);
  EXPECT_EQ(BIO_ADDR_cmp(addr1, addr2), 0);
}
#endif

// Tests for BIO_ADDR_dup
TEST_F(BIOAddrTest, DupNull) { EXPECT_EQ(BIO_ADDR_dup(nullptr), nullptr); }

TEST_F(BIOAddrTest, DupUnspecifiedAddress) {
  BIO_ADDR *dup = BIO_ADDR_dup(addr1);
  ASSERT_NE(dup, nullptr);
  EXPECT_EQ(BIO_ADDR_cmp(dup, addr1), 0);
  BIO_ADDR_free(dup);
}

TEST_F(BIOAddrTest, DupSpecifiedAddressIPv4) {
  uint32_t addr = INADDR_LOOPBACK;
  EXPECT_EQ(BIO_ADDR_rawmake(addr1, AF_INET, &addr, sizeof(sockaddr_in::sin_addr), htons(443)), 1);

  BIO_ADDR *dup = BIO_ADDR_dup(addr1);
  ASSERT_NE(dup, nullptr);
  EXPECT_EQ(BIO_ADDR_cmp(dup, addr1), 0);
  BIO_ADDR_free(dup);
}

#ifdef AF_INET6
TEST_F(BIOAddrTest, DupSpecifiedAddressIPv6) {
  EXPECT_EQ(BIO_ADDR_rawmake(addr1, AF_INET6, &in6addr_loopback,
                             sizeof(in6addr_loopback), htons(443)),
            1);

  BIO_ADDR *dup = BIO_ADDR_dup(addr1);
  ASSERT_NE(dup, nullptr);
  EXPECT_EQ(BIO_ADDR_cmp(dup, addr1), 0);
  BIO_ADDR_free(dup);
}
#endif

#ifdef AWS_LC_HAS_AF_UNIX
TEST_F(BIOAddrTest, DupSpecifiedAddressUnix) {
  const char *path = "/tmp/test.sock";
  EXPECT_EQ(BIO_ADDR_rawmake(addr1, AF_UNIX, path,
                             OPENSSL_strnlen(path, sizeof(sockaddr_un::sun_path)), 0),
            1);

  BIO_ADDR *dup = BIO_ADDR_dup(addr1);
  ASSERT_NE(dup, nullptr);
  EXPECT_EQ(BIO_ADDR_cmp(dup, addr1), 0);
  BIO_ADDR_free(dup);
}
#endif

// Tests for BIO_ADDR_clear
TEST_F(BIOAddrTest, ClearAddress) {
  uint32_t addr = INADDR_LOOPBACK;
  EXPECT_EQ(BIO_ADDR_rawmake(addr1, AF_INET, &addr, 4, htons(443)), 1);

  // Create a copy before clearing
  BIO_ADDR *before_clear = BIO_ADDR_dup(addr1);
  ASSERT_NE(before_clear, nullptr);

  BIO_ADDR_clear(addr1);

  EXPECT_EQ(BIO_ADDR_cmp(addr1, before_clear), 1);
  EXPECT_EQ(addr1->sa.sa_family, AF_UNSPEC);

  BIO_ADDR_free(before_clear);
}

#endif  // !OPENSSL_NO_SOCK
