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);