Commit 207c40c97a for openssl.org

commit 207c40c97a1f502a39df260c8daf919de2fd9cb2
Author: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Date:   Fri Jun 12 11:53:56 2026 +0200

    Add BIO bio_sock test

    Assisted-by: Claude:claude-opus-4-8

    Reviewed-by: Richard Levitte <levitte@openssl.org>
    Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
    MergeDate: Thu Jul  9 17:39:24 2026
    (Merged from https://github.com/openssl/openssl/pull/30788)

diff --git a/test/unit/build.info b/test/unit/build.info
index c65bb5016a..f215b70ec0 100644
--- a/test/unit/build.info
+++ b/test/unit/build.info
@@ -6,6 +6,15 @@ INCLUDE[crypto/bio/test_bio_addr]=../../include ../../crypto/bio
 DEPEND[crypto/bio/test_bio_addr]=../../libcrypto.a
 WRAP[crypto/bio/test_bio_addr]=BIO_sock_init getnameinfo freeaddrinfo

+PROGRAMS{noinst}=crypto/bio/test_bio_sock
+SOURCE[crypto/bio/test_bio_sock]=crypto/bio/test_bio_sock.c
+INCLUDE[crypto/bio/test_bio_sock]=../../include ../../crypto/bio
+DEPEND[crypto/bio/test_bio_sock]=../../libcrypto.a
+WRAP[crypto/bio/test_bio_sock]=getsockopt setsockopt getsockname ioctl poll \
+    gethostbyname BIO_lookup BIO_socket BIO_listen BIO_closesocket \
+    BIO_ADDRINFO_free BIO_accept_ex BIO_sock_should_retry \
+    BIO_ADDR_hostname_string BIO_ADDR_service_string
+
 PROGRAMS{noinst}=crypto/bio/test_bss_acpt
 SOURCE[crypto/bio/test_bss_acpt]=crypto/bio/test_bss_acpt.c
 WRAP[crypto/bio/test_bss_acpt]=BIO_lookup BIO_socket BIO_listen \
diff --git a/test/unit/crypto/bio/test_bio_sock.c b/test/unit/crypto/bio/test_bio_sock.c
new file mode 100644
index 0000000000..b41f9ed011
--- /dev/null
+++ b/test/unit/crypto/bio/test_bio_sock.c
@@ -0,0 +1,536 @@
+/*
+ * Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include "internal/sockets.h"
+
+#ifndef OPENSSL_NO_SOCK
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include "bio_local.h"
+#include <openssl/bio.h>
+
+#define FAKE_SOCKET 42
+
+#if defined(TCP_NODELAY) && (defined(IPPROTO_TCP) || defined(SOL_TCP))
+#ifdef SOL_TCP
+#define TEST_TCP_LEVEL SOL_TCP
+#else
+#define TEST_TCP_LEVEL IPPROTO_TCP
+#endif
+#endif
+
+/* prototypes for __wrap_* (required by -Wmissing-prototypes) */
+int __wrap_getsockopt(int fd, int level, int optname, void *optval,
+    socklen_t *optlen);
+int __wrap_setsockopt(int fd, int level, int optname, const void *optval,
+    socklen_t optlen);
+int __wrap_getsockname(int fd, struct sockaddr *addr, socklen_t *slen);
+int __wrap_ioctl(int fd, unsigned long request, void *arg);
+int __wrap_poll(struct pollfd *fds, nfds_t nfds, int timeout);
+struct hostent *__wrap_gethostbyname(const char *name);
+int __wrap_BIO_lookup(const char *host, const char *service,
+    enum BIO_lookup_type lookup_type, int family, int socktype,
+    BIO_ADDRINFO **res);
+int __wrap_BIO_socket(int domain, int socktype, int protocol, int options);
+int __wrap_BIO_listen(int sock, const BIO_ADDR *ba, int options);
+int __wrap_BIO_closesocket(int sock);
+void __wrap_BIO_ADDRINFO_free(BIO_ADDRINFO *bai);
+int __wrap_BIO_accept_ex(int accept_sock, BIO_ADDR *addr, int options);
+int __wrap_BIO_sock_should_retry(int i);
+char *__wrap_BIO_ADDR_hostname_string(const BIO_ADDR *ap, int numeric);
+char *__wrap_BIO_ADDR_service_string(const BIO_ADDR *ap, int numeric);
+
+/* wraps */
+
+int __wrap_getsockopt(int fd, int level, int optname, void *optval,
+    socklen_t *optlen)
+{
+    int rc;
+
+    function_called();
+    check_expected(fd);
+    check_expected(level);
+    check_expected(optname);
+    (void)optlen;
+    rc = mock_type(int);
+    if (rc == 0) {
+        if (optval != NULL)
+            *(int *)optval = mock_type(int);
+    } else {
+        errno = mock_type(int);
+    }
+    return rc;
+}
+
+int __wrap_setsockopt(int fd, int level, int optname, const void *optval,
+    socklen_t optlen)
+{
+    int on = (optval != NULL) ? *(const int *)optval : 0;
+
+    function_called();
+    check_expected(fd);
+    check_expected(level);
+    check_expected(optname);
+    check_expected(on);
+    (void)optlen;
+    return mock_type(int);
+}
+
+int __wrap_getsockname(int fd, struct sockaddr *addr, socklen_t *slen)
+{
+    int rc;
+
+    function_called();
+    check_expected(fd);
+    (void)addr;
+    rc = mock_type(int);
+    if (rc == 0 && slen != NULL)
+        *slen = (socklen_t)mock_type(int);
+    return rc;
+}
+
+int __wrap_ioctl(int fd, unsigned long request, void *arg)
+{
+    function_called();
+    check_expected(fd);
+    check_expected(request);
+    (void)arg;
+    return mock_type(int);
+}
+
+int __wrap_poll(struct pollfd *fds, nfds_t nfds, int timeout)
+{
+    int pfd = fds[0].fd;
+    int events = fds[0].events;
+
+    function_called();
+    check_expected(pfd);
+    check_expected(events);
+    (void)nfds;
+    (void)timeout;
+    return mock_type(int);
+}
+
+struct hostent *__wrap_gethostbyname(const char *name)
+{
+    function_called();
+    check_expected_ptr(name);
+    return mock_ptr_type(struct hostent *);
+}
+
+int __wrap_BIO_lookup(const char *host, const char *service,
+    enum BIO_lookup_type lookup_type, int family, int socktype,
+    BIO_ADDRINFO **res)
+{
+    int rc;
+
+    function_called();
+    check_expected(family);
+    check_expected(socktype);
+    check_expected(lookup_type);
+    (void)host;
+    (void)service;
+    rc = mock_type(int);
+    if (rc == 1) {
+        BIO_ADDRINFO *r = mock_ptr_type(BIO_ADDRINFO *);
+
+        if (res != NULL)
+            *res = r;
+    }
+    return rc;
+}
+
+int __wrap_BIO_socket(int domain, int socktype, int protocol, int options)
+{
+    function_called();
+    check_expected(domain);
+    check_expected(socktype);
+    check_expected(protocol);
+    (void)options;
+    return mock_type(int);
+}
+
+int __wrap_BIO_listen(int sock, const BIO_ADDR *ba, int options)
+{
+    function_called();
+    check_expected(sock);
+    check_expected(options);
+    (void)ba;
+    return mock_type(int);
+}
+
+int __wrap_BIO_closesocket(int sock)
+{
+    function_called();
+    check_expected(sock);
+    return mock_type(int);
+}
+
+/* the fake addrinfo handed back by BIO_lookup is static; nothing to release */
+void __wrap_BIO_ADDRINFO_free(BIO_ADDRINFO *bai)
+{
+    (void)bai;
+}
+
+int __wrap_BIO_accept_ex(int accept_sock, BIO_ADDR *addr, int options)
+{
+    function_called();
+    check_expected(accept_sock);
+    check_expected(options);
+    (void)addr;
+    return mock_type(int);
+}
+
+int __wrap_BIO_sock_should_retry(int i)
+{
+    function_called();
+    check_expected(i);
+    return mock_type(int);
+}
+
+char *__wrap_BIO_ADDR_hostname_string(const BIO_ADDR *ap, int numeric)
+{
+    function_called();
+    check_expected(numeric);
+    (void)ap;
+    return mock_ptr_type(char *);
+}
+
+char *__wrap_BIO_ADDR_service_string(const BIO_ADDR *ap, int numeric)
+{
+    function_called();
+    check_expected(numeric);
+    (void)ap;
+    return mock_ptr_type(char *);
+}
+
+/* expectations */
+
+static void expect_getsockopt(int fd, int rc, int value)
+{
+    expect_function_call(__wrap_getsockopt);
+    expect_value(__wrap_getsockopt, fd, fd);
+    expect_value(__wrap_getsockopt, level, SOL_SOCKET);
+    expect_value(__wrap_getsockopt, optname, SO_ERROR);
+    will_return(__wrap_getsockopt, rc);
+    will_return(__wrap_getsockopt, value);
+}
+
+#if defined(TCP_NODELAY) && (defined(IPPROTO_TCP) || defined(SOL_TCP))
+static void expect_setsockopt(int fd, int on, int rc)
+{
+    expect_function_call(__wrap_setsockopt);
+    expect_value(__wrap_setsockopt, fd, fd);
+    expect_value(__wrap_setsockopt, level, TEST_TCP_LEVEL);
+    expect_value(__wrap_setsockopt, optname, TCP_NODELAY);
+    expect_value(__wrap_setsockopt, on, on);
+    will_return(__wrap_setsockopt, rc);
+}
+#endif
+
+static void expect_getsockname(int fd, int rc, socklen_t outlen)
+{
+    expect_function_call(__wrap_getsockname);
+    expect_value(__wrap_getsockname, fd, fd);
+    will_return(__wrap_getsockname, rc);
+    if (rc == 0)
+        will_return(__wrap_getsockname, (int)outlen);
+}
+
+#ifdef FIONBIO
+static void expect_ioctl(int fd, unsigned long request, int rc)
+{
+    expect_function_call(__wrap_ioctl);
+    expect_value(__wrap_ioctl, fd, fd);
+    expect_value(__wrap_ioctl, request, request);
+    will_return(__wrap_ioctl, rc);
+}
+#endif
+
+static void expect_poll(int fd, int events, int rc)
+{
+    expect_function_call(__wrap_poll);
+    expect_value(__wrap_poll, pfd, fd);
+    expect_value(__wrap_poll, events, events);
+    will_return(__wrap_poll, rc);
+}
+
+/* setup */
+
+/* BIO_sock_init / bio_sock_cleanup_int */
+
+static void test_sock_init(void **state)
+{
+    (void)state;
+    assert_int_equal(BIO_sock_init(), 1);
+}
+
+static void test_sock_cleanup(void **state)
+{
+    (void)state;
+    /* no-op on non-Windows; exercised for coverage */
+    bio_sock_cleanup_int();
+}
+
+/* BIO_sock_error */
+
+static void test_sock_error_value(void **state)
+{
+    /* getsockopt succeeds: SO_ERROR value is returned verbatim */
+    (void)state;
+    expect_getsockopt(FAKE_SOCKET, 0, ECONNREFUSED);
+    assert_int_equal(BIO_sock_error(FAKE_SOCKET), ECONNREFUSED);
+}
+
+static void test_sock_error_getsockopt_fails(void **state)
+{
+    /* getsockopt fails: the last socket error (errno) is returned */
+    (void)state;
+    expect_getsockopt(FAKE_SOCKET, -1, EBADF);
+    assert_int_equal(BIO_sock_error(FAKE_SOCKET), EBADF);
+}
+
+/* BIO_socket_ioctl */
+
+#ifdef FIONBIO
+static void test_socket_ioctl_success(void **state)
+{
+    int arg = 1;
+
+    (void)state;
+    expect_ioctl(FAKE_SOCKET, FIONBIO, 0);
+    assert_int_equal(BIO_socket_ioctl(FAKE_SOCKET, FIONBIO, &arg), 0);
+}
+
+static void test_socket_ioctl_error(void **state)
+{
+    int arg = 1;
+
+    (void)state;
+    expect_ioctl(FAKE_SOCKET, FIONBIO, -1);
+    assert_int_equal(BIO_socket_ioctl(FAKE_SOCKET, FIONBIO, &arg), -1);
+}
+#endif
+
+/* BIO_set_tcp_ndelay */
+
+#if defined(TCP_NODELAY) && (defined(IPPROTO_TCP) || defined(SOL_TCP))
+static void test_set_tcp_ndelay_on(void **state)
+{
+    (void)state;
+    expect_setsockopt(FAKE_SOCKET, 1, 0);
+    assert_int_equal(BIO_set_tcp_ndelay(FAKE_SOCKET, 1), 1);
+}
+
+static void test_set_tcp_ndelay_off(void **state)
+{
+    (void)state;
+    expect_setsockopt(FAKE_SOCKET, 0, 0);
+    assert_int_equal(BIO_set_tcp_ndelay(FAKE_SOCKET, 0), 1);
+}
+
+static void test_set_tcp_ndelay_fails(void **state)
+{
+    (void)state;
+    expect_setsockopt(FAKE_SOCKET, 1, -1);
+    assert_int_equal(BIO_set_tcp_ndelay(FAKE_SOCKET, 1), 0);
+}
+#endif
+
+/* BIO_socket_nbio */
+
+#ifdef FIONBIO
+static void test_socket_nbio_enable(void **state)
+{
+    (void)state;
+    expect_ioctl(FAKE_SOCKET, FIONBIO, 0);
+    assert_int_equal(BIO_socket_nbio(FAKE_SOCKET, 1), 1);
+}
+
+static void test_socket_nbio_disable(void **state)
+{
+    (void)state;
+    expect_ioctl(FAKE_SOCKET, FIONBIO, 0);
+    assert_int_equal(BIO_socket_nbio(FAKE_SOCKET, 0), 1);
+}
+
+static void test_socket_nbio_fails(void **state)
+{
+    (void)state;
+    expect_ioctl(FAKE_SOCKET, FIONBIO, -1);
+    assert_int_equal(BIO_socket_nbio(FAKE_SOCKET, 1), 0);
+}
+#endif
+
+/* BIO_sock_info */
+
+static void test_sock_info_success(void **state)
+{
+    union BIO_sock_info_u info;
+    BIO_ADDR addr;
+
+    (void)state;
+    memset(&addr, 0, sizeof(addr));
+    info.addr = &addr;
+    expect_getsockname(FAKE_SOCKET, 0, (socklen_t)sizeof(struct sockaddr_in));
+    assert_int_equal(
+        BIO_sock_info(FAKE_SOCKET, BIO_SOCK_INFO_ADDRESS, &info), 1);
+}
+
+static void test_sock_info_getsockname_fails(void **state)
+{
+    union BIO_sock_info_u info;
+    BIO_ADDR addr;
+
+    (void)state;
+    memset(&addr, 0, sizeof(addr));
+    info.addr = &addr;
+    expect_getsockname(FAKE_SOCKET, -1, 0);
+    assert_int_equal(
+        BIO_sock_info(FAKE_SOCKET, BIO_SOCK_INFO_ADDRESS, &info), 0);
+}
+
+static void test_sock_info_truncated(void **state)
+{
+    /* getsockname reports an address larger than the BIO_ADDR storage */
+    union BIO_sock_info_u info;
+    BIO_ADDR addr;
+
+    (void)state;
+    memset(&addr, 0, sizeof(addr));
+    info.addr = &addr;
+    expect_getsockname(FAKE_SOCKET, 0, (socklen_t)(sizeof(BIO_ADDR) + 1));
+    assert_int_equal(
+        BIO_sock_info(FAKE_SOCKET, BIO_SOCK_INFO_ADDRESS, &info), 0);
+}
+
+static void test_sock_info_unknown_type(void **state)
+{
+    union BIO_sock_info_u info;
+    BIO_ADDR addr;
+
+    (void)state;
+    memset(&addr, 0, sizeof(addr));
+    info.addr = &addr;
+    assert_int_equal(
+        BIO_sock_info(FAKE_SOCKET, (enum BIO_sock_info_type)999, &info), 0);
+}
+
+/* BIO_socket_wait */
+
+static void test_socket_wait_immediate(void **state)
+{
+    /* max_time == 0 returns immediately without polling */
+    (void)state;
+    assert_int_equal(BIO_socket_wait(FAKE_SOCKET, 1, 0), 1);
+}
+
+static void test_socket_wait_bad_fd(void **state)
+{
+    (void)state;
+    assert_int_equal(BIO_socket_wait(-1, 1, time(NULL) + 100), -1);
+}
+
+static void test_socket_wait_past(void **state)
+{
+    /* deadline already elapsed: timeout without polling */
+    (void)state;
+    assert_int_equal(BIO_socket_wait(FAKE_SOCKET, 1, (time_t)1), 0);
+}
+
+static void test_socket_wait_read_ready(void **state)
+{
+    (void)state;
+    expect_poll(FAKE_SOCKET, POLLIN, 1);
+    assert_int_equal(BIO_socket_wait(FAKE_SOCKET, 1, time(NULL) + 100), 1);
+}
+
+static void test_socket_wait_write_ready(void **state)
+{
+    (void)state;
+    expect_poll(FAKE_SOCKET, POLLOUT, 1);
+    assert_int_equal(BIO_socket_wait(FAKE_SOCKET, 0, time(NULL) + 100), 1);
+}
+
+static void test_socket_wait_timeout(void **state)
+{
+    (void)state;
+    expect_poll(FAKE_SOCKET, POLLIN, 0);
+    assert_int_equal(BIO_socket_wait(FAKE_SOCKET, 1, time(NULL) + 100), 0);
+}
+
+static void test_socket_wait_error(void **state)
+{
+    (void)state;
+    expect_poll(FAKE_SOCKET, POLLIN, -1);
+    assert_int_equal(BIO_socket_wait(FAKE_SOCKET, 1, time(NULL) + 100), -1);
+}
+
+/* main */
+
+#define SOCK_TEST(name) cmocka_unit_test(name)
+
+int main(void)
+{
+    const struct CMUnitTest tests[] = {
+        /* BIO_sock_init / cleanup */
+        SOCK_TEST(test_sock_init),
+        SOCK_TEST(test_sock_cleanup),
+        /* BIO_sock_error */
+        SOCK_TEST(test_sock_error_value),
+        SOCK_TEST(test_sock_error_getsockopt_fails),
+#ifdef FIONBIO
+        /* BIO_socket_ioctl */
+        SOCK_TEST(test_socket_ioctl_success),
+        SOCK_TEST(test_socket_ioctl_error),
+#endif
+    /* BIO_set_tcp_ndelay */
+#if defined(TCP_NODELAY) && (defined(IPPROTO_TCP) || defined(SOL_TCP))
+        SOCK_TEST(test_set_tcp_ndelay_on),
+        SOCK_TEST(test_set_tcp_ndelay_off),
+        SOCK_TEST(test_set_tcp_ndelay_fails),
+#endif
+    /* BIO_socket_nbio */
+#ifdef FIONBIO
+        SOCK_TEST(test_socket_nbio_enable),
+        SOCK_TEST(test_socket_nbio_disable),
+        SOCK_TEST(test_socket_nbio_fails),
+#endif
+        /* BIO_sock_info */
+        SOCK_TEST(test_sock_info_success),
+        SOCK_TEST(test_sock_info_getsockname_fails),
+        SOCK_TEST(test_sock_info_truncated),
+        SOCK_TEST(test_sock_info_unknown_type),
+        /* BIO_socket_wait */
+        SOCK_TEST(test_socket_wait_immediate),
+        SOCK_TEST(test_socket_wait_bad_fd),
+        SOCK_TEST(test_socket_wait_past),
+        SOCK_TEST(test_socket_wait_read_ready),
+        SOCK_TEST(test_socket_wait_write_ready),
+        SOCK_TEST(test_socket_wait_timeout),
+        SOCK_TEST(test_socket_wait_error),
+    };
+
+    cmocka_set_message_output(CM_OUTPUT_TAP);
+
+    return cmocka_run_group_tests(tests, NULL, NULL);
+}
+
+#else
+
+int main(void)
+{
+    return 0;
+}
+
+#endif /* OPENSSL_NO_SOCK */