This is an automated email from the ASF dual-hosted git repository.

mpochatkin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 31b90ef636a IGNITE-27939 C++ 3.0: Pass MSG_NOSIGNAL to the SSL layer 
(#7674)
31b90ef636a is described below

commit 31b90ef636a311d93e0e027e224f4d81b689211a
Author: Dmitriy Zabotlin <[email protected]>
AuthorDate: Tue Mar 3 10:46:50 2026 +0200

    IGNITE-27939 C++ 3.0: Pass MSG_NOSIGNAL to the SSL layer (#7674)
    
    Co-authored-by: dzabotlin <[email protected]>
---
 .../platforms/cpp/ignite/network/CMakeLists.txt    |   1 +
 .../network/detail/linux/linux_async_client.cpp    |   4 +-
 .../detail/linux/linux_async_worker_thread.cpp     |   5 +
 .../cpp/ignite/network/detail/linux/sockets.cpp    |  24 ++
 .../cpp/ignite/network/detail/linux/sockets.h      |  32 +++
 .../network/detail/linux/tcp_socket_client.h       |   4 +-
 .../cpp/ignite/network/detail/win/sockets.cpp      |  59 +++-
 .../cpp/ignite/network/detail/win/sockets.h        |   6 +
 .../ignite/network/detail/win/tcp_socket_client.h  |   4 +-
 .../cpp/ignite/network/ssl/secure_data_filter.cpp  |  90 +-----
 .../cpp/ignite/network/ssl/secure_data_filter.h    |  30 +-
 .../ignite/network/ssl/secure_socket_client.cpp    | 319 +++++++--------------
 .../cpp/ignite/network/ssl/secure_socket_client.h  |  66 ++---
 .../cpp/ignite/network/ssl/ssl_connection.cpp      | 168 +++++++++++
 .../cpp/ignite/network/ssl/ssl_connection.h        | 148 ++++++++++
 .../socket_adapter/posix/client_socket_adapter.h   |   6 +-
 16 files changed, 587 insertions(+), 379 deletions(-)

diff --git a/modules/platforms/cpp/ignite/network/CMakeLists.txt 
b/modules/platforms/cpp/ignite/network/CMakeLists.txt
index 90356cf2865..202b54e37cb 100644
--- a/modules/platforms/cpp/ignite/network/CMakeLists.txt
+++ b/modules/platforms/cpp/ignite/network/CMakeLists.txt
@@ -34,6 +34,7 @@ set(SOURCES
     ssl/secure_data_filter.cpp
     ssl/secure_socket_client.cpp
     ssl/secure_utils.cpp
+    ssl/ssl_connection.cpp
     ssl/ssl_gateway.cpp
 )
 
diff --git 
a/modules/platforms/cpp/ignite/network/detail/linux/linux_async_client.cpp 
b/modules/platforms/cpp/ignite/network/detail/linux/linux_async_client.cpp
index 9faa3284852..a8d451001b4 100644
--- a/modules/platforms/cpp/ignite/network/detail/linux/linux_async_client.cpp
+++ b/modules/platforms/cpp/ignite/network/detail/linux/linux_async_client.cpp
@@ -89,7 +89,7 @@ bool linux_async_client::send_next_packet_locked() {
     auto &packet = m_send_packets.front();
     auto dataView = packet.get_bytes_view();
 
-    ssize_t ret = ::send(m_fd, dataView.data(), dataView.size(), MSG_NOSIGNAL);
+    ssize_t ret = detail::send(m_fd, dataView.data(), dataView.size());
     if (ret < 0)
         return false;
 
@@ -101,7 +101,7 @@ bool linux_async_client::send_next_packet_locked() {
 }
 
 bytes_view linux_async_client::receive() {
-    ssize_t res = recv(m_fd, m_recv_packet.data(), m_recv_packet.size(), 0);
+    ssize_t res = detail::recv(m_fd, m_recv_packet.data(), 
m_recv_packet.size());
     if (res < 0)
         return {};
 
diff --git 
a/modules/platforms/cpp/ignite/network/detail/linux/linux_async_worker_thread.cpp
 
b/modules/platforms/cpp/ignite/network/detail/linux/linux_async_worker_thread.cpp
index 789add23b26..d2b428d3af8 100644
--- 
a/modules/platforms/cpp/ignite/network/detail/linux/linux_async_worker_thread.cpp
+++ 
b/modules/platforms/cpp/ignite/network/detail/linux/linux_async_worker_thread.cpp
@@ -66,6 +66,7 @@ void linux_async_worker_thread::start(size_t limit, 
std::vector<tcp_range> addrs
     if (m_stop_event < 0) {
         std::string msg = get_last_system_error("Failed to create stop event 
instance", "");
         close(m_stop_event);
+        m_stop_event = SOCKET_ERROR;
         throw ignite_error(error::code::INTERNAL, msg);
     }
 
@@ -78,7 +79,9 @@ void linux_async_worker_thread::start(size_t limit, 
std::vector<tcp_range> addrs
     if (res < 0) {
         std::string msg = get_last_system_error("Failed to create stop event 
instance", "");
         close(m_stop_event);
+        m_stop_event = SOCKET_ERROR;
         close(m_epoll);
+        m_epoll = SOCKET_ERROR;
         throw ignite_error(error::code::INTERNAL, msg);
     }
 
@@ -114,7 +117,9 @@ void linux_async_worker_thread::stop() {
     m_thread.join();
 
     close(m_stop_event);
+    m_stop_event = SOCKET_ERROR;
     close(m_epoll);
+    m_epoll = SOCKET_ERROR;
 
     m_non_connected.clear();
     m_current_connection.reset();
diff --git a/modules/platforms/cpp/ignite/network/detail/linux/sockets.cpp 
b/modules/platforms/cpp/ignite/network/detail/linux/sockets.cpp
index 54881efefe2..39d48a422f3 100644
--- a/modules/platforms/cpp/ignite/network/detail/linux/sockets.cpp
+++ b/modules/platforms/cpp/ignite/network/detail/linux/sockets.cpp
@@ -16,9 +16,14 @@
  */
 
 #include "ignite/network/detail/linux/sockets.h"
+
+#include "ignite/common/detail/defer.h"
+#include "ignite/network/detail/utils.h"
 #include "ignite/network/socket_client.h"
+#include "ignite/common/ignite_error.h"
 
 #include <cerrno>
+#include <climits>
 #include <cstring>
 #include <sstream>
 
@@ -176,4 +181,23 @@ bool set_non_blocking_mode(int socket_fd, bool 
non_blocking) {
     return res != -1;
 }
 
+ssize_t send(int socket, const void* buf, size_t len) {
+    if (len > INT_MAX)
+        throw ignite_error("Socket send failed. Buffer size exceeds INT_MAX: " 
+ std::to_string(len));
+#ifdef __APPLE__
+    return ::send(socket, buf, static_cast<int>(len), 0); // SIGPIPE is 
already handled via setsockopt SO_NOSIGPIPE
+#else
+    return ::send(socket, buf, static_cast<int>(len), MSG_NOSIGNAL);
+#endif
+}
+
+ssize_t recv(int socket, void* buf, int len) {
+    return ::recv(socket, buf, len, 0);
+}
+
+void close(int socket) {
+    if (socket != SOCKET_ERROR)
+        ::close(socket);
+}
+
 } // namespace ignite::network::detail
diff --git a/modules/platforms/cpp/ignite/network/detail/linux/sockets.h 
b/modules/platforms/cpp/ignite/network/detail/linux/sockets.h
index 58df712ef18..fbff2b8c6fe 100644
--- a/modules/platforms/cpp/ignite/network/detail/linux/sockets.h
+++ b/modules/platforms/cpp/ignite/network/detail/linux/sockets.h
@@ -20,6 +20,11 @@
 #include <cstdint>
 #include <string>
 
+#include <netdb.h>
+#include <optional>
+#include <sys/socket.h>
+#include <unistd.h>
+
 #ifndef SOCKET_ERROR
 # define SOCKET_ERROR (-1)
 #endif // SOCKET_ERROR
@@ -69,4 +74,31 @@ int wait_on_socket(int socket, std::int32_t timeout, bool 
rd);
  */
 bool set_non_blocking_mode(int socket_fd, bool non_blocking);
 
+/**
+ * Send data through the socket.
+ *
+ * @param socket Socket to send into.
+ * @param buf Pointer to the data buffer.
+ * @param len Length of the buffer.
+ * @return Size of the sent data, -1 in case of error.
+ */
+ssize_t send(int socket, const void* buf, size_t len);
+
+/**
+ * Receive data from the socket.
+ *
+ * @param socket Socket to receive from.
+ * @param buf Buffer for received data.
+ * @param len Length of the buffer.
+ * @return Size of the received data, -1 in case of error.
+ */
+ssize_t recv(int socket, void* buf, int len);
+
+/**
+ * Closes socket.
+ *
+ * @param socket Socket to close.
+ */
+void close(int socket);
+
 } // namespace ignite::network::detail
diff --git 
a/modules/platforms/cpp/ignite/network/detail/linux/tcp_socket_client.h 
b/modules/platforms/cpp/ignite/network/detail/linux/tcp_socket_client.h
index 7870e46f019..b8465b24a88 100644
--- a/modules/platforms/cpp/ignite/network/detail/linux/tcp_socket_client.h
+++ b/modules/platforms/cpp/ignite/network/detail/linux/tcp_socket_client.h
@@ -164,7 +164,7 @@ public:
                 return res;
         }
 
-        return int(::send(m_socket_handle, reinterpret_cast<const char 
*>(data), static_cast<int>(size), 0));
+        return int(detail::send(m_socket_handle, reinterpret_cast<const char 
*>(data), static_cast<int>(size)));
     }
 
     /**
@@ -184,7 +184,7 @@ public:
                 return res;
         }
 
-        return int(::recv(m_socket_handle, reinterpret_cast<char *>(buffer), 
static_cast<int>(size), 0));
+        return int(detail::recv(m_socket_handle, reinterpret_cast<char 
*>(buffer), static_cast<int>(size)));
     }
 
     /**
diff --git a/modules/platforms/cpp/ignite/network/detail/win/sockets.cpp 
b/modules/platforms/cpp/ignite/network/detail/win/sockets.cpp
index cadb976cff5..91cc38c6fd9 100644
--- a/modules/platforms/cpp/ignite/network/detail/win/sockets.cpp
+++ b/modules/platforms/cpp/ignite/network/detail/win/sockets.cpp
@@ -30,6 +30,8 @@
 
 namespace ignite::network::detail {
 
+std::once_flag wsa_init_flag;
+
 std::string get_socket_error_message(HRESULT error) {
     std::stringstream res;
 
@@ -129,21 +131,13 @@ bool set_non_blocking_mode(SOCKET socket_handle, bool 
non_blocking) {
 }
 
 void init_wsa() {
-    static std::mutex init_mutex;
-    static bool network_inited = false;
-
-    if (!network_inited) {
-        std::lock_guard<std::mutex> lock(init_mutex);
-        if (!network_inited) {
-            WSADATA wsaData;
-
-            network_inited = WSAStartup(MAKEWORD(2, 2), &wsaData) == 0;
+    std::call_once(wsa_init_flag, [&] {
+        WSADATA wsa_data;
 
-            if (!network_inited)
-                throw ignite_error(
-                    error::code::CONNECTION, "Networking initialisation 
failed: " + get_last_socket_error_message());
-        }
-    }
+        if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0)
+            throw ignite_error(
+                error::code::CONNECTION, "Networking initialisation failed: " 
+ get_last_socket_error_message());
+    });
 }
 
 int wait_on_socket(SOCKET socket, std::int32_t timeout, bool rd) {
@@ -184,4 +178,41 @@ int wait_on_socket(SOCKET socket, std::int32_t timeout, 
bool rd) {
     return socket_client::wait_result::SUCCESS;
 }
 
+/**
+ * Send data through the socket.
+ *
+ * @param socket Socket to send into.
+ * @param buf Pointer to the data buffer.
+ * @param len Length of the buffer.
+ * @return Size of the sent data, -1 in case of error.
+ */
+int send(SOCKET socket, const void *buf, size_t len) {
+    if (len > INT_MAX)
+        throw ignite_error("Socket send failed. Buffer size exceeds INT_MAX: " 
+ std::to_string(len));
+
+    return ::send(socket, static_cast<const char*>(buf), 
static_cast<int>(len), 0);
+}
+
+/**
+ * Receive data from the socket.
+ *
+ * @param socket Socket to receive from.
+ * @param buf Buffer for received data.
+ * @param len Length of the buffer.
+ * @return Size of the received data, -1 in case of error.
+ */
+int recv(SOCKET socket, void* buf, int len) {
+    return ::recv(socket, static_cast<char*>(buf), len, 0);
+}
+
+/**
+ * Closes socket.
+ *
+ * @param socket Socket to close.
+ */
+void close(SOCKET socket) {
+    if (socket != SOCKET_ERROR)
+        ::closesocket(socket);
+}
+
 } // namespace ignite::network::detail
diff --git a/modules/platforms/cpp/ignite/network/detail/win/sockets.h 
b/modules/platforms/cpp/ignite/network/detail/win/sockets.h
index 0586cb3166b..74f1fd59ba1 100644
--- a/modules/platforms/cpp/ignite/network/detail/win/sockets.h
+++ b/modules/platforms/cpp/ignite/network/detail/win/sockets.h
@@ -81,4 +81,10 @@ int wait_on_socket(SOCKET socket, std::int32_t timeout, bool 
rd);
  */
 void init_wsa();
 
+int send(SOCKET socket, const void* buf, size_t len);
+
+int recv(SOCKET socket, void* buf, int len);
+
+void close(SOCKET socket);
+
 } // namespace ignite::network::detail
diff --git 
a/modules/platforms/cpp/ignite/network/detail/win/tcp_socket_client.h 
b/modules/platforms/cpp/ignite/network/detail/win/tcp_socket_client.h
index 8fd598b05b6..d2c46d2ab0f 100644
--- a/modules/platforms/cpp/ignite/network/detail/win/tcp_socket_client.h
+++ b/modules/platforms/cpp/ignite/network/detail/win/tcp_socket_client.h
@@ -161,7 +161,7 @@ public:
                 return res;
         }
 
-        return ::send(m_socket_handle, reinterpret_cast<const char *>(data), 
static_cast<int>(size), 0);
+        return detail::send(m_socket_handle, reinterpret_cast<const char 
*>(data), static_cast<int>(size));
     }
 
     /**
@@ -181,7 +181,7 @@ public:
                 return res;
         }
 
-        return ::recv(m_socket_handle, reinterpret_cast<char *>(buffer), 
static_cast<int>(size), 0);
+        return detail::recv(m_socket_handle, reinterpret_cast<char *>(buffer), 
static_cast<int>(size));
     }
 
     /**
diff --git a/modules/platforms/cpp/ignite/network/ssl/secure_data_filter.cpp 
b/modules/platforms/cpp/ignite/network/ssl/secure_data_filter.cpp
index 6cd1e39c262..582aab19e9c 100644
--- a/modules/platforms/cpp/ignite/network/ssl/secure_data_filter.cpp
+++ b/modules/platforms/cpp/ignite/network/ssl/secure_data_filter.cpp
@@ -16,11 +16,9 @@
  */
 
 #include <ignite/network/ssl/secure_data_filter.h>
-#include <ignite/network/ssl/ssl_gateway.h>
 #include <ignite/network/ssl/secure_utils.h>
 #include <ignite/network/network.h>
 
-#include <iostream>
 #include <sstream>
 #include <utility>
 
@@ -138,68 +136,24 @@ 
secure_data_filter::secure_connection_context::secure_connection_context(std::ui
     : m_id(id)
     , m_addr(std::move(addr))
     , m_filter(filter)
+    , m_ssl_conn(filter.m_ssl_context, nullptr)
 {
-    ssl_gateway &gateway = ssl_gateway::get_instance();
-
-    m_ssl = gateway.SSL_new_(static_cast<SSL_CTX*>(filter.m_ssl_context));
-    if (!m_ssl)
-        throw_last_secure_error("Can not create secure connection");
-
-    m_bio_in = gateway.BIO_new_(gateway.BIO_s_mem_());
-    if (!m_bio_in)
-        throw_last_secure_error("Can not create input BIO");
-
-    m_bio_out = gateway.BIO_new_(gateway.BIO_s_mem_());
-    if (!m_bio_out)
-        throw_last_secure_error("Can not create output BIO");
-
-    gateway.SSL_set_bio_(static_cast<SSL*>(m_ssl), 
static_cast<BIO*>(m_bio_in), static_cast<BIO*>(m_bio_out));
-    gateway.SSL_set_connect_state_(static_cast<SSL*>(m_ssl));
-}
-
-secure_data_filter::secure_connection_context::~secure_connection_context()
-{
-    ssl_gateway &gateway = ssl_gateway::get_instance();
-
-    if (m_ssl)
-        gateway.SSL_free_(static_cast<SSL*>(m_ssl));
-    else
-    {
-        if (m_bio_in)
-            gateway.BIO_free_all_(static_cast<BIO*>(m_bio_in));
-
-        if (m_bio_out)
-            gateway.BIO_free_all_(static_cast<BIO*>(m_bio_out));
-    }
 }
 
 bool secure_data_filter::secure_connection_context::do_connect()
 {
-    ssl_gateway &gateway = ssl_gateway::get_instance();
-
-    SSL* ssl0 = static_cast<SSL*>(m_ssl);
-    int res = gateway.SSL_connect_(ssl0);
-
-    if (res != SSL_OPERATION_SUCCESS)
-    {
-        int sslError = gateway.SSL_get_error_(ssl0, res);
-        if (is_actual_error(sslError))
-            throw_last_secure_error("Can not establish secure connection");
-    }
-
+    bool done = m_ssl_conn.do_handshake();
     send_pending_data();
-
-    return res == SSL_OPERATION_SUCCESS;
+    return done;
 }
 
 bool secure_data_filter::secure_connection_context::send_pending_data()
 {
-    auto data = get_pending_data(m_bio_out);
-
+    auto data = m_ssl_conn.drain_output();
     if (data.empty())
         return false;
 
-    return m_filter.send_internal(m_id, data);
+    return m_filter.send_internal(m_id, std::move(data));
 }
 
 bool secure_data_filter::secure_connection_context::send(const 
std::vector<std::byte> &data)
@@ -207,9 +161,7 @@ bool 
secure_data_filter::secure_connection_context::send(const std::vector<std::
     if (!m_connected)
         return false;
 
-    ssl_gateway &gateway = ssl_gateway::get_instance();
-
-    int res = gateway.SSL_write_(static_cast<SSL*>(m_ssl), data.data(), 
int(data.size()));
+    int res = m_ssl_conn.write(data.data(), static_cast<int>(data.size()));
     if (res <= 0)
         return false;
 
@@ -218,13 +170,9 @@ bool 
secure_data_filter::secure_connection_context::send(const std::vector<std::
 
 bool 
secure_data_filter::secure_connection_context::process_data(data_buffer_ref 
&data)
 {
-    ssl_gateway &gateway = ssl_gateway::get_instance();
     auto buf = data.get_bytes_view();
-    int res = gateway.BIO_write_(static_cast<BIO*>(m_bio_in), buf.data(), 
int(buf.size()));
-    if (res <= 0)
-        throw_last_secure_error("Failed to process SSL data");
-
-    data.skip(res);
+    m_ssl_conn.feed_input(buf.data(), static_cast<int>(buf.size()));
+    data.skip(buf.size());
 
     send_pending_data();
 
@@ -241,29 +189,9 @@ bool 
secure_data_filter::secure_connection_context::process_data(data_buffer_ref
     return true;
 }
 
-std::vector<std::byte> 
secure_data_filter::secure_connection_context::get_pending_data(void* bio)
-{
-    ssl_gateway &gateway = ssl_gateway::get_instance();
-
-    BIO *bio0 = static_cast<BIO*>(bio);
-    int available = gateway.BIO_pending_(bio0);
-    if (available <= 0)
-        return {};
-
-    std::vector<std::byte> buffer(available, {});
-    int res = gateway.BIO_read_(bio0, buffer.data(), int(buffer.size()));
-    if (res <= 0)
-        return {};
-
-    return buffer;
-}
-
 data_buffer_ref 
secure_data_filter::secure_connection_context::get_pending_decrypted_data()
 {
-    ssl_gateway &gateway = ssl_gateway::get_instance();
-
-    SSL *ssl0 = static_cast<SSL*>(m_ssl);
-    int res = gateway.SSL_read_(ssl0, m_recv_buffer.data(), 
int(m_recv_buffer.size()));
+    int res = m_ssl_conn.read(m_recv_buffer.data(), 
static_cast<int>(m_recv_buffer.size()));
     if (res <= 0)
         return {};
 
diff --git a/modules/platforms/cpp/ignite/network/ssl/secure_data_filter.h 
b/modules/platforms/cpp/ignite/network/ssl/secure_data_filter.h
index b908353744c..834380c9038 100644
--- a/modules/platforms/cpp/ignite/network/ssl/secure_data_filter.h
+++ b/modules/platforms/cpp/ignite/network/ssl/secure_data_filter.h
@@ -19,6 +19,7 @@
 
 #include <ignite/network/data_filter_adapter.h>
 #include <ignite/network/ssl/secure_configuration.h>
+#include <ignite/network/ssl/ssl_connection.h>
 
 #include <map>
 #include <memory>
@@ -88,7 +89,7 @@ private:
     {
     public:
         /**
-         * Default constructor.
+         * Constructor.
          *
          * @param id Connection ID.
          * @param addr Address.
@@ -96,15 +97,10 @@ private:
          */
         secure_connection_context(std::uint64_t id, end_point addr, 
secure_data_filter &filter);
 
-        /**
-         * Destructor.
-         */
-        ~secure_connection_context();
-
         /**
          * Start connection procedure including handshake.
          *
-         * @return @c true, if connection complete.
+         * @return @c true if connection is complete.
          */
         bool do_connect();
 
@@ -154,16 +150,10 @@ private:
 
         /**
          * Send pending data.
-         */
-        bool send_pending_data();
-
-        /**
-         * Get pending data.
          *
-         * @param bio BIO to get data from.
-         * @return Data buffer.
+         * @return @c true if data was sent.
          */
-        static std::vector<std::byte> get_pending_data(void* bio);
+        bool send_pending_data();
 
         /** Flag indicating that secure connection is established. */
         bool m_connected{false};
@@ -180,14 +170,8 @@ private:
         /** Receive buffer. */
         std::vector<std::byte> m_recv_buffer{DEFAULT_BUFFER_SIZE, {}};
 
-        /** SSL instance. */
-        void* m_ssl{nullptr};
-
-        /** Input BIO. */
-        void* m_bio_in{nullptr};
-
-        /** Output BIO. */
-        void* m_bio_out{nullptr};
+        /** TLS state machine (SSL instance + memory BIOs). */
+        ssl_connection m_ssl_conn;
     };
 
     /** Context map. */
diff --git a/modules/platforms/cpp/ignite/network/ssl/secure_socket_client.cpp 
b/modules/platforms/cpp/ignite/network/ssl/secure_socket_client.cpp
index fb26c31c8fb..92d59ac4e62 100644
--- a/modules/platforms/cpp/ignite/network/ssl/secure_socket_client.cpp
+++ b/modules/platforms/cpp/ignite/network/ssl/secure_socket_client.cpp
@@ -15,58 +15,24 @@
  * limitations under the License.
  */
 
+#include "ignite/common/detail/defer.h"
+#include "ignite/network/network.h"
 #include "ignite/network/detail/sockets.h"
-#include "ignite/network/detail/utils.h"
 #include "ignite/network/ssl/secure_socket_client.h"
 #include "ignite/network/ssl/secure_utils.h"
-#include "ignite/network/ssl/ssl_gateway.h"
-
-#include "ignite/common/detail/defer.h"
 
-#include <sstream>
-#include <cassert>
+namespace ignite::network {
 
-namespace {
-
-using namespace ignite::network;
-
-void free_bio(BIO* bio)
+int secure_socket_client::fill_bio_in(std::int32_t timeout)
 {
-    ssl_gateway &gateway = ssl_gateway::get_instance();
-
-    assert(gateway.is_loaded());
-
-    gateway.BIO_free_all_(bio);
-}
-
-SSL* ssl_from_bio_no_check(void* bio) {
-    ssl_gateway &gateway = ssl_gateway::get_instance();
-    SSL* ssl = nullptr;
-    gateway.BIO_get_ssl_(reinterpret_cast<BIO*>(bio), &ssl);
-    return ssl;
-}
-
-SSL* ssl_from_bio(void* bio, ignite::error::code on_fail_code, const 
std::string& on_fail_msg) {
-    SSL* ssl = ssl_from_bio_no_check(bio);
-    if (!ssl)
-        throw ignite::ignite_error(on_fail_code, on_fail_msg);
+    auto received = m_socket_client->receive(m_recv_packet.data(), 
m_recv_packet.size(), timeout);
+    if (received <= 0)
+        return received;
 
-    return ssl;
+    m_ssl_conn->feed_input(m_recv_packet.data(), received);
+    return received;
 }
 
-SSL* ssl_from_bio(void* bio, const std::string& on_fail_msg) {
-    SSL* ssl = ssl_from_bio_no_check(bio);
-    if (!ssl)
-        throw_last_secure_error(on_fail_msg);
-
-    return ssl;
-}
-
-} // anonymous namespace
-
-namespace ignite::network
-{
-
 secure_socket_client::~secure_socket_client()
 {
     close_internal();
@@ -77,9 +43,7 @@ secure_socket_client::~secure_socket_client()
 
 bool secure_socket_client::connect(const char* hostname, std::uint16_t port, 
std::int32_t timeout)
 {
-    ssl_gateway &gateway = ssl_gateway::get_instance();
-
-    assert(gateway.is_loaded());
+    ensure_ssl_loaded();
 
     close_internal();
 
@@ -91,46 +55,20 @@ bool secure_socket_client::connect(const char* hostname, 
std::uint16_t port, std
             throw_last_secure_error("Can not create SSL context", "Aborting 
connect");
     }
 
-    m_bio = make_ssl(m_context, hostname, port, m_blocking);
-
-    auto cleanup = ::ignite::detail::defer([&] { close_internal(); });
-
-    SSL* ssl = ssl_from_bio(m_bio, "Can not get SSL instance from BIO");
-
-    int res = gateway.SSL_set_tlsext_host_name_(ssl, hostname);
-
-    if (res != SSL_OPERATION_SUCCESS)
-        throw_last_secure_error("Can not set host name for secure connection");
-
-    gateway.SSL_set_connect_state_(ssl);
-
-    bool connected = complete_connect_internal(ssl, timeout);
-
-    if (!connected)
+    if (!m_socket_client->connect(hostname, port, timeout))
         return false;
 
-    // Verify a server certificate was presented during the negotiation
-    X509* cert = gateway.SSL_get_peer_certificate_(ssl);
-    if (cert)
-        gateway.X509_free_(cert);
-    else
-        throw_last_secure_error("Remote host did not provide certificate");
-
-    // Verify the result of chain verification.
-    // Verification performed according to RFC 4158.
-    res = gateway.SSL_get_verify_result_(ssl);
-    if (X509_V_OK != res)
-        throw_last_secure_error("Certificate chain verification failed");
+    auto ssl_cleanup = ::ignite::detail::defer([&] { close_internal(); });
 
-    res = wait_on_socket(ssl, timeout, false);
+    m_ssl_conn = std::make_unique<ssl_connection>(m_context, hostname);
 
-    if (res == wait_result::TIMEOUT)
+    bool connected = complete_connect_internal(timeout);
+    if (!connected)
         return false;
 
-    if (res != wait_result::SUCCESS)
-        throw_last_secure_error("Error while establishing secure connection");
+    m_ssl_conn->verify_peer();
 
-    cleanup.release();
+    ssl_cleanup.release();
 
     return true;
 }
@@ -142,182 +80,133 @@ void secure_socket_client::close()
 
 int secure_socket_client::send(const std::byte* data, std::size_t size, 
std::int32_t timeout)
 {
-    ssl_gateway &gateway = ssl_gateway::get_instance();
-
-    assert(gateway.is_loaded());
-
-    SSL* ssl = ssl_from_bio(m_bio, error::code::CONNECTION, "Trying to send 
data using closed connection");
-
-    int res = wait_on_socket(ssl, timeout, false);
-
-    if (res == wait_result::TIMEOUT)
-        return res;
+    if (!m_ssl_conn)
+        throw ignite_error(error::code::CONNECTION, "Trying to send data using 
closed connection");
 
-    do {
-        res = gateway.SSL_write_(ssl, data, static_cast<int>(size));
-
-        int waitRes = wait_on_socket_if_needed(res, ssl, timeout);
-        if (waitRes <= 0)
-            return waitRes;
-    } while (res <= 0);
-
-    return res;
+    int ssl_res;
+    do
+    {
+        ssl_res = m_ssl_conn->write(data, static_cast<int>(size));
+        if (ssl_res <= 0)
+        {
+            if (m_ssl_conn->is_fatal_error(ssl_res))
+                return ssl_res;
+
+            int flush_res = flush_bio_out(timeout);
+            if (flush_res < 0 || flush_res == wait_result::TIMEOUT)
+                return flush_res;
+
+            if (m_ssl_conn->wants_read_input())
+            {
+                int fill_res = fill_bio_in(timeout);
+                if (fill_res < 0 || fill_res == wait_result::TIMEOUT)
+                    return fill_res;
+            }
+        }
+    } while (ssl_res <= 0);
+
+    int flush_res = flush_bio_out(timeout);
+    if (flush_res < 0 || flush_res == wait_result::TIMEOUT)
+        return flush_res;
+
+    return ssl_res;
 }
 
 int secure_socket_client::receive(std::byte* buffer, std::size_t size, 
std::int32_t timeout)
 {
-    ssl_gateway &gateway = ssl_gateway::get_instance();
-
-    assert(gateway.is_loaded());
-
-    SSL* ssl = ssl_from_bio(m_bio, error::code::CONNECTION, "Trying to receive 
data using closed connection");
+    if (!m_ssl_conn)
+        throw ignite_error(error::code::CONNECTION, "Trying to receive data 
using closed connection");
 
-    int res = 0;
-    if (!m_blocking && gateway.SSL_pending_(ssl) == 0)
+    if (!m_socket_client->is_blocking() && m_ssl_conn->pending_decrypted() == 
0)
     {
-        res = wait_on_socket(ssl, timeout, true);
-
-        if (res < 0 || res == wait_result::TIMEOUT)
-            return res;
+        int fill_res = fill_bio_in(timeout);
+        if (fill_res < 0 || fill_res == wait_result::TIMEOUT)
+            return fill_res;
     }
 
-    do {
-        res = gateway.SSL_read_(ssl, buffer, static_cast<int>(size));
-
-        int waitRes = wait_on_socket_if_needed(res, ssl, timeout);
-        if (waitRes <= 0)
-            return waitRes;
-    } while (res <= 0);
-
-    return res;
+    int ssl_res;
+    do
+    {
+        ssl_res = m_ssl_conn->read(buffer, static_cast<int>(size));
+        if (ssl_res <= 0)
+        {
+            if (m_ssl_conn->is_fatal_error(ssl_res))
+                return ssl_res;
+
+            int fill_res = fill_bio_in(timeout);
+            if (fill_res < 0 || fill_res == wait_result::TIMEOUT)
+                return fill_res;
+
+            int flush_res = flush_bio_out(timeout);
+            if (flush_res < 0 || flush_res == wait_result::TIMEOUT)
+                return flush_res;
+        }
+    } while (ssl_res <= 0);
+
+    return ssl_res;
 }
 
-void* secure_socket_client::make_ssl(void* context, const char* hostname, 
std::uint16_t port, bool& blocking)
+void secure_socket_client::close_internal()
 {
-    ssl_gateway &gateway = ssl_gateway::get_instance();
-
-    assert(gateway.is_loaded());
-
-    BIO* bio = 
gateway.BIO_new_ssl_connect_(reinterpret_cast<SSL_CTX*>(context));
-    if (!bio)
-        throw_last_secure_error("Can not create SSL connection");
-
-    auto cleanup = ::ignite::detail::defer([&] { free_bio(bio); });
-
-    blocking = gateway.BIO_set_nbio_(bio, 1) != SSL_OPERATION_SUCCESS;
-
-    std::stringstream stream;
-    stream << hostname << ":" << port;
-
-    std::string address = stream.str();
-
-    long res = gateway.BIO_set_conn_hostname_(bio, address.c_str());
-    if (res != SSL_OPERATION_SUCCESS)
-        throw_last_secure_error("Can not set SSL connection hostname");
-
-    cleanup.release();
-
-    return bio;
+    m_ssl_conn.reset();
+    m_socket_client->close();
 }
 
-bool secure_socket_client::complete_connect_internal(void* m_ssl, int timeout)
+bool secure_socket_client::complete_connect_internal(std::int32_t timeout)
 {
-    ssl_gateway &gateway = ssl_gateway::get_instance();
-
-    assert(gateway.is_loaded());
-
-    SSL* ssl0 = reinterpret_cast<SSL*>(m_ssl);
-
     while (true)
     {
-        int res = gateway.SSL_connect_(ssl0);
-
-        if (res == SSL_OPERATION_SUCCESS)
-            break;
-
-        int sslError = gateway.SSL_get_error_(ssl0, res);
-
-        if (is_actual_error(sslError))
-            throw_last_secure_error("Can not establish secure connection");
+        bool done = m_ssl_conn->do_handshake();
 
-        int want = gateway.SSL_want_(ssl0);
+        int flush_res = flush_bio_out(timeout);
+        if (flush_res == wait_result::TIMEOUT)
+            return false;
+        if (flush_res < 0)
+            throw_last_secure_error("Error while establishing secure 
connection");
 
-        res = wait_on_socket(m_ssl, timeout, want == SSL_READING);
+        if (done)
+            break;
 
-        if (res == wait_result::TIMEOUT)
+        int fill_res = fill_bio_in(timeout);
+        if (fill_res == wait_result::TIMEOUT)
             return false;
-
-        if (res != wait_result::SUCCESS)
+        if (fill_res < 0)
             throw_last_secure_error("Error while establishing secure 
connection");
     }
 
-    if (std::string("TLSv1.3") == gateway.SSL_get_version_(ssl0))
+    if (std::string("TLSv1.3") == m_ssl_conn->version())
     {
         // Workaround. Need to get SSL into read state to avoid a deadlock.
         // See https://github.com/openssl/openssl/issues/7967 for details.
-        gateway.SSL_read_(ssl0, nullptr, 0);
-        int res = wait_on_socket(m_ssl, timeout, true);
+        m_ssl_conn->read(nullptr, 0);
 
-        if (res == wait_result::TIMEOUT)
+        if (wait_result::TIMEOUT == flush_bio_out(timeout))
+            return false;
+
+        if (wait_result::TIMEOUT == fill_bio_in(timeout))
             return false;
     }
 
     return true;
 }
 
-void secure_socket_client::close_internal()
-{
-    ssl_gateway &gateway = ssl_gateway::get_instance();
-
-    if (gateway.is_loaded() && m_bio)
-    {
-        gateway.BIO_free_all_(reinterpret_cast<BIO*>(m_bio));
-
-        m_bio = nullptr;
-    }
-}
-
-int secure_socket_client::wait_on_socket(void* ssl, std::int32_t timeout, bool 
rd)
+int secure_socket_client::flush_bio_out(std::int32_t timeout)
 {
-    ssl_gateway &gateway = ssl_gateway::get_instance();
-
-    assert(gateway.is_loaded());
+    auto data = m_ssl_conn->drain_output();
+    if (data.empty())
+        return wait_result::SUCCESS;
 
-    SSL* ssl0 = reinterpret_cast<SSL*>(ssl);
-    int fd = gateway.SSL_get_fd_(ssl0);
-
-    if (fd < 0)
+    std::size_t sent = 0;
+    while (sent < data.size())
     {
-        std::stringstream ss;
-        ss << "Can not get file descriptor from the SSL socket, fd=" << fd;
-
-        throw_last_secure_error(ss.str());
-    }
-
-    return detail::wait_on_socket(fd, timeout, rd);
-}
+        int n = m_socket_client->send(data.data() + sent, data.size() - sent, 
timeout);
+        if (n < 0 || n == wait_result::TIMEOUT)
+            return n;
 
-int secure_socket_client::wait_on_socket_if_needed(int res, void* ssl, int 
timeout)
-{
-    ssl_gateway &gateway = ssl_gateway::get_instance();
-
-    assert(gateway.is_loaded());
-
-    SSL* ssl0 = reinterpret_cast<SSL*>(ssl);
-
-    if (res <= 0)
-    {
-        int err = gateway.SSL_get_error_(ssl0, res);
-        if (is_actual_error(err))
-            return res;
-
-        int want = gateway.SSL_want_(ssl0);
-        int wait_res = wait_on_socket(ssl, timeout, want == SSL_READING);
-        if (wait_res < 0 || wait_res == wait_result::TIMEOUT)
-            return wait_res;
+        sent += static_cast<std::size_t>(n);
     }
 
     return wait_result::SUCCESS;
 }
 
-} // namespace ignite::network
+} // namespace ignite::network
\ No newline at end of file
diff --git a/modules/platforms/cpp/ignite/network/ssl/secure_socket_client.h 
b/modules/platforms/cpp/ignite/network/ssl/secure_socket_client.h
index 923deef2511..756aaa13766 100644
--- a/modules/platforms/cpp/ignite/network/ssl/secure_socket_client.h
+++ b/modules/platforms/cpp/ignite/network/ssl/secure_socket_client.h
@@ -17,10 +17,13 @@
 
 #pragma once
 
+#include <ignite/network/network.h>
 #include <ignite/network/socket_client.h>
 #include <ignite/network/ssl/secure_configuration.h>
+#include <ignite/network/ssl/ssl_connection.h>
 
 #include <cstdint>
+#include <memory>
 #include <string>
 #include <utility>
 
@@ -38,7 +41,11 @@ public:
      *
      * @param cfg Secure configuration.
      */
-    explicit secure_socket_client(secure_configuration  cfg) : 
m_cfg(std::move(cfg)) {}
+    explicit secure_socket_client(secure_configuration  cfg)
+        : m_cfg(std::move(cfg))
+        , m_socket_client(make_tcp_socket_client())
+        , m_recv_packet(BUFFER_SIZE)
+    {}
 
     /**
      * Destructor.
@@ -86,70 +93,55 @@ public:
      * @return @c true if the socket is blocking and false otherwise.
      */
     [[nodiscard]] bool is_blocking() const override {
-        return m_blocking;
+        return m_socket_client->is_blocking();
     }
 
 private:
+    static constexpr size_t BUFFER_SIZE = 0x10000;
+
     /**
-     * Close the connection.
-     * Internal call.
+     * Close the connection. Internal call.
      */
     void close_internal();
 
     /**
-     * Wait on the socket for any event for specified time.
-     * This function uses poll to achive timeout functionality
-     * for every separate socket operation.
+     * Drive the TLS handshake to completion, exchanging data with the peer
+     * over the socket.
      *
-     * @param ssl SSL instance.
      * @param timeout Timeout in seconds.
-     * @param rd Wait for read if @c true, or for write if @c false.
-     * @return -errno on error, WaitResult::TIMEOUT on timeout and
-     *     WaitResult::SUCCESS on success.
+     * @return @c true on success and @c false on timeout.
      */
-    static int wait_on_socket(void* ssl, std::int32_t timeout, bool rd);
+    bool complete_connect_internal(std::int32_t timeout);
 
     /**
-     * Wait on the socket if it's required by SSL.
+     * Drain all pending ciphertext from the SSL output BIO and send it to the 
peer.
      *
-     * @param res Operation result.
-     * @param ssl SSl instance.
      * @param timeout Timeout in seconds.
-     * @return
+     * @return WaitResult::SUCCESS on success, WaitResult::TIMEOUT on timeout, 
negative on error.
      */
-    static int wait_on_socket_if_needed(int res, void* ssl, int timeout);
+    int flush_bio_out(std::int32_t timeout);
 
     /**
-     * Make new SSL instance.
+     * Read data from the socket and feed it into the SSL input BIO.
      *
-     * @param context SSL context.
-     * @param hostname Host name or address.
-     * @param port TCP port.
-     * @param blocking Indicates if the resulted SSL is blocking or not.
-     * @return New SSL instance on success and null-pointer on fail.
-     */
-    static void* make_ssl(void* context, const char* hostname, std::uint16_t 
port, bool& blocking);
-
-    /**
-     * Complete async connect.
-     *
-     * @param ssl SSL instance.
      * @param timeout Timeout in seconds.
-     * @return @c true on success and @c false on timeout.
+     * @return Bytes fed on success, WaitResult::TIMEOUT on timeout, negative 
on error.
      */
-    static bool complete_connect_internal(void* ssl, int timeout);
+    int fill_bio_in(std::int32_t timeout);
 
     /** Secure configuration. */
     secure_configuration m_cfg;
 
-    /** SSL context. */
+    /** SSL context (SSL_CTX*). */
     void* m_context{nullptr};
 
-    /** OpenSSL BIO interface */
-    void* m_bio{nullptr};
+    /** TLS state machine (SSL instance + memory BIOs). */
+    std::unique_ptr<ssl_connection> m_ssl_conn;
+
+    std::unique_ptr<socket_client> m_socket_client;
 
-    /** Blocking flag. */
-    bool m_blocking{true};
+    /** Buffer for incoming data. */
+    std::vector<std::byte> m_recv_packet;
 };
 
 } // namespace ignite::network
diff --git a/modules/platforms/cpp/ignite/network/ssl/ssl_connection.cpp 
b/modules/platforms/cpp/ignite/network/ssl/ssl_connection.cpp
new file mode 100644
index 00000000000..aafadc6ef8b
--- /dev/null
+++ b/modules/platforms/cpp/ignite/network/ssl/ssl_connection.cpp
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ignite/network/ssl/ssl_connection.h>
+#include <ignite/network/ssl/ssl_gateway.h>
+#include <ignite/network/ssl/secure_utils.h>
+
+#include <ignite/common/detail/defer.h>
+
+namespace ignite::network
+{
+
+ssl_connection::ssl_connection(void* ctx, const char* hostname)
+{
+    ssl_gateway& gw = ssl_gateway::get_instance();
+
+    m_ssl = gw.SSL_new_(static_cast<SSL_CTX*>(ctx));
+    if (!m_ssl)
+        throw_last_secure_error("Can not create secure connection");
+
+    auto ssl_guard = detail::defer([&] {
+        gw.SSL_free_(static_cast<SSL*>(m_ssl));
+        m_ssl = nullptr;
+    });
+
+    m_bio_in = gw.BIO_new_(gw.BIO_s_mem_());
+    if (!m_bio_in)
+        throw_last_secure_error("Can not create input BIO");
+
+    auto bio_in_guard = detail::defer([&] {
+        gw.BIO_free_all_(static_cast<BIO*>(m_bio_in));
+        m_bio_in = nullptr;
+    });
+
+    m_bio_out = gw.BIO_new_(gw.BIO_s_mem_());
+    if (!m_bio_out)
+        throw_last_secure_error("Can not create output BIO");
+
+    // SSL now owns both BIOs; ssl_free will free them.
+    gw.SSL_set_bio_(static_cast<SSL*>(m_ssl), static_cast<BIO*>(m_bio_in), 
static_cast<BIO*>(m_bio_out));
+    bio_in_guard.release();
+
+    if (hostname)
+    {
+        int res = gw.SSL_set_tlsext_host_name_(static_cast<SSL*>(m_ssl), 
hostname);
+        if (res != SSL_OPERATION_SUCCESS)
+            throw_last_secure_error("Can not set host name for secure 
connection");
+    }
+
+    gw.SSL_set_connect_state_(static_cast<SSL*>(m_ssl));
+
+    ssl_guard.release();
+}
+
+ssl_connection::~ssl_connection()
+{
+    ssl_gateway& gw = ssl_gateway::get_instance();
+    if (gw.is_loaded() && m_ssl)
+        gw.SSL_free_(static_cast<SSL*>(m_ssl)); // also frees bio_in and 
bio_out
+}
+
+bool ssl_connection::do_handshake()
+{
+    ssl_gateway& gw = ssl_gateway::get_instance();
+    SSL* ssl = static_cast<SSL*>(m_ssl);
+
+    int res = gw.SSL_connect_(ssl);
+    if (res == SSL_OPERATION_SUCCESS)
+        return true;
+
+    if (is_actual_error(gw.SSL_get_error_(ssl, res)))
+        throw_last_secure_error("Can not establish secure connection");
+
+    return false;
+}
+
+int ssl_connection::write(const std::byte* data, int len)
+{
+    ssl_gateway& gw = ssl_gateway::get_instance();
+    return gw.SSL_write_(static_cast<SSL*>(m_ssl), data, len);
+}
+
+int ssl_connection::read(std::byte* buf, int len)
+{
+    ssl_gateway& gw = ssl_gateway::get_instance();
+    return gw.SSL_read_(static_cast<SSL*>(m_ssl), buf, len);
+}
+
+int ssl_connection::pending_decrypted() const
+{
+    ssl_gateway& gw = ssl_gateway::get_instance();
+    return gw.SSL_pending_(static_cast<const SSL*>(m_ssl));
+}
+
+void ssl_connection::feed_input(const void* data, int len)
+{
+    ssl_gateway& gw = ssl_gateway::get_instance();
+    if (gw.BIO_write_(static_cast<BIO*>(m_bio_in), data, len) <= 0)
+        throw_last_secure_error("Failed to process SSL data");
+}
+
+std::vector<std::byte> ssl_connection::drain_output()
+{
+    ssl_gateway& gw = ssl_gateway::get_instance();
+    BIO* bio_out = static_cast<BIO*>(m_bio_out);
+
+    int available = gw.BIO_pending_(bio_out);
+    if (available <= 0)
+        return {};
+
+    std::vector<std::byte> buf(available);
+    int res = gw.BIO_read_(bio_out, buf.data(), available);
+    if (res <= 0)
+        return {};
+
+    buf.resize(res);
+    return buf;
+}
+
+const char* ssl_connection::version() const
+{
+    ssl_gateway& gw = ssl_gateway::get_instance();
+    return gw.SSL_get_version_(static_cast<const SSL*>(m_ssl));
+}
+
+bool ssl_connection::is_fatal_error(int ssl_op_result) const
+{
+    ssl_gateway& gw = ssl_gateway::get_instance();
+    return is_actual_error(gw.SSL_get_error_(static_cast<SSL*>(m_ssl), 
ssl_op_result));
+}
+
+bool ssl_connection::wants_read_input() const
+{
+    ssl_gateway& gw = ssl_gateway::get_instance();
+    return gw.SSL_want_(static_cast<SSL*>(m_ssl)) == SSL_READING;
+}
+
+void ssl_connection::verify_peer() const
+{
+    ssl_gateway& gw = ssl_gateway::get_instance();
+    SSL* ssl = static_cast<SSL*>(m_ssl);
+
+    X509* cert = gw.SSL_get_peer_certificate_(ssl);
+    if (cert)
+        gw.X509_free_(cert);
+    else
+        throw_last_secure_error("Remote host did not provide certificate");
+
+    long res = gw.SSL_get_verify_result_(ssl);
+    if (X509_V_OK != res)
+        throw_last_secure_error("Certificate chain verification failed");
+}
+
+} // namespace ignite::network
diff --git a/modules/platforms/cpp/ignite/network/ssl/ssl_connection.h 
b/modules/platforms/cpp/ignite/network/ssl/ssl_connection.h
new file mode 100644
index 00000000000..ab8310a5572
--- /dev/null
+++ b/modules/platforms/cpp/ignite/network/ssl/ssl_connection.h
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstddef>
+#include <vector>
+
+namespace ignite::network
+{
+
+/**
+ * SSL/TLS connection state machine backed by a pair of memory BIOs.
+ *
+ * Owns an SSL instance and two memory BIOs:
+ *   - input BIO  (bio_in):  feed received ciphertext in via feed_input()
+ *   - output BIO (bio_out): drain ciphertext to send via drain_output()
+ *
+ * This class performs no network I/O. The caller is responsible for
+ * moving bytes between the BIOs and the actual transport.
+ */
+class ssl_connection
+{
+public:
+    /**
+     * Constructor. Creates the SSL instance and attaches memory BIOs.
+     *
+     * @param ctx SSL_CTX* cast to void*.
+     * @param hostname SNI hostname for TLS Server Name Indication. May be 
nullptr to skip SNI.
+     */
+    ssl_connection(void* ctx, const char* hostname);
+
+    /**
+     * Destructor. Frees the SSL instance (which also frees the attached BIOs).
+     */
+    ~ssl_connection();
+
+    // Non-copyable, non-movable.
+    ssl_connection(const ssl_connection&) = delete;
+    ssl_connection& operator=(const ssl_connection&) = delete;
+
+    /**
+     * Perform one TLS handshake step (SSL_connect).
+     *
+     * The caller must call @c drain_output() after each invocation to flush 
any
+     * generated handshake bytes to the peer, and @c feed_input() before 
retrying
+     * when this returns @c false.
+     *
+     * @return @c true if the handshake completed successfully.
+     * @throws ignite_error on a fatal SSL error.
+     */
+    bool do_handshake();
+
+    /**
+     * Encrypt plaintext. On success the resulting ciphertext is available via 
drain_output().
+     *
+     * @param data Plaintext bytes.
+     * @param len  Number of bytes to encrypt.
+     * @return Number of bytes consumed on success (> 0), or <= 0 on 
error/retry.
+     */
+    int write(const std::byte* data, int len);
+
+    /**
+     * Decrypt data from the input BIO into the caller-supplied buffer.
+     *
+     * @param buf Buffer to receive plaintext.
+     * @param len Buffer size in bytes.
+     * @return Number of plaintext bytes written to buf (> 0), or <= 0 on 
error/retry.
+     */
+    int read(std::byte* buf, int len);
+
+    /**
+     * Number of decrypted plaintext bytes currently buffered inside SSL
+     * and available for an immediate read() call.
+     */
+    [[nodiscard]] int pending_decrypted() const;
+
+    /**
+     * Feed ciphertext received from the network into the input BIO.
+     *
+     * @param data Ciphertext pointer.
+     * @param len  Number of bytes.
+     * @throws ignite_error on BIO write failure.
+     */
+    void feed_input(const void* data, int len);
+
+    /**
+     * Drain all pending ciphertext from the output BIO.
+     *
+     * @return Bytes to send to the network. Empty if nothing is pending.
+     */
+    [[nodiscard]] std::vector<std::byte> drain_output();
+
+    /**
+     * TLS protocol version string (e.g. "TLSv1.3").
+     */
+    [[nodiscard]] const char* version() const;
+
+    /**
+     * Returns @c true if the given SSL operation result represents a fatal
+     * error, @c false if it is a transient condition (@c WANT_READ, @c 
WANT_WRITE, etc.)
+     * that can be resolved by feeding more input or retrying.
+     *
+     * @param ssl_op_result Return value of @c write() or @c read().
+     */
+    [[nodiscard]] bool is_fatal_error(int ssl_op_result) const;
+
+    /**
+     * Returns @c true if SSL is requesting more ciphertext input before it
+     * can proceed (SSL_WANT_READ). Use after write() or read() returns <= 0.
+     */
+    [[nodiscard]] bool wants_read_input() const;
+
+    /**
+     * Verify the peer's certificate after the handshake completes.
+     * Checks that the remote host provided a certificate and that the
+     * certificate chain verification result is X509_V_OK.
+     *
+     * @throws ignite_error if verification fails.
+     */
+    void verify_peer() const;
+
+private:
+    /** SSL instance. */
+    void* m_ssl { nullptr };
+
+    /** Input memory BIO: ciphertext flows from network -> bio_in -> SSL. */
+    void* m_bio_in { nullptr };
+
+    /** Output memory BIO: ciphertext flows from SSL -> bio_out -> network. */
+    void* m_bio_out { nullptr };
+};
+
+} // namespace ignite::network
diff --git 
a/modules/platforms/cpp/tests/fake_server/socket_adapter/posix/client_socket_adapter.h
 
b/modules/platforms/cpp/tests/fake_server/socket_adapter/posix/client_socket_adapter.h
index 4d44b8165b9..e4773fb4a61 100644
--- 
a/modules/platforms/cpp/tests/fake_server/socket_adapter/posix/client_socket_adapter.h
+++ 
b/modules/platforms/cpp/tests/fake_server/socket_adapter/posix/client_socket_adapter.h
@@ -17,8 +17,8 @@
 
 #pragma once
 
-#include <sys/socket.h>
-#include <unistd.h>
+#include "ignite/network/detail/sockets.h"
+
 #include <vector>
 #include <cstddef>
 
@@ -55,7 +55,7 @@ public:
 
     [[nodiscard]] bool is_valid() const { return m_fd >= 0; }
 
-    void send_message(const std::vector<std::byte> &msg) const { ::send(m_fd, 
msg.data(), msg.size(), 0); }
+    void send_message(const std::vector<std::byte> &msg) const { 
network::detail::send(m_fd, msg.data(), msg.size()); }
 
     [[nodiscard]] ssize_t receive_next_packet(std::byte *buf, size_t buf_size) 
const {
         return ::recv(m_fd, buf, buf_size, 0);


Reply via email to