Network System 0.1.1
High-performance modular networking library for scalable client-server applications
Loading...
Searching...
No Matches
secure_tcp_socket.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2024, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
6
7#include <atomic>
8#include <span>
9#include <type_traits>
10
12{
13
14 secure_tcp_socket::secure_tcp_socket(asio::ip::tcp::socket socket,
15 asio::ssl::context& ssl_context)
16 : ssl_stream_(std::move(socket), ssl_context)
17 {
18 }
19
21 asio::ssl::stream_base::handshake_type type,
22 std::function<void(std::error_code)> handler) -> void
23 {
24 auto self = shared_from_this();
25 ssl_stream_.async_handshake(
26 type,
27 [handler = std::move(handler), self](std::error_code ec)
28 {
29 if constexpr (std::is_invocable_v<decltype(handler), std::error_code>)
30 {
31 if (handler)
32 {
33 handler(ec);
34 }
35 }
36 });
37 }
38
40 std::function<void(const std::vector<uint8_t>&)> callback) -> void
41 {
42 auto new_cb = std::make_shared<receive_callback_t>(std::move(callback));
43 std::lock_guard<std::mutex> lock(callback_mutex_);
44 std::atomic_store(&receive_callback_, new_cb);
45 }
46
48 std::function<void(std::span<const uint8_t>)> callback) -> void
49 {
50 auto new_cb = std::make_shared<receive_callback_view_t>(std::move(callback));
51 std::lock_guard<std::mutex> lock(callback_mutex_);
52 std::atomic_store(&receive_callback_view_, new_cb);
53 }
54
56 std::function<void(std::error_code)> callback) -> void
57 {
58 auto new_cb = std::make_shared<error_callback_t>(std::move(callback));
59 std::lock_guard<std::mutex> lock(callback_mutex_);
60 std::atomic_store(&error_callback_, new_cb);
61 }
62
64 {
65 // Set reading flag and kick off the initial read loop
66 is_reading_.store(true);
67 do_read();
68 }
69
71 {
72 // Stop further read operations
73 is_reading_.store(false);
74 }
75
77 {
78 // Atomically mark socket as closed before actual close
79 // This prevents data races with concurrent async operations
80 is_closed_.store(true);
81 is_reading_.store(false);
82
83 std::error_code ec;
84 ssl_stream_.lowest_layer().close(ec);
85 // Ignore close errors - socket may already be closed
86 }
87
88 auto secure_tcp_socket::is_closed() const -> bool
89 {
90 return is_closed_.load();
91 }
92
94 {
95 // Check if reading has been stopped before initiating new async operation
96 if (!is_reading_.load())
97 {
98 return;
99 }
100
101 // Check if socket has been closed or is no longer open before starting async operation
102 // This prevents data races and UBSAN errors from accessing null descriptor_state
103 // Both checks are needed: is_closed_ for explicit close() calls, is_open() for ASIO state
104 if (is_closed_.load() || !ssl_stream_.lowest_layer().is_open())
105 {
106 is_reading_.store(false);
107 return;
108 }
109
110 auto self = shared_from_this();
111 ssl_stream_.async_read_some(
112 asio::buffer(read_buffer_),
113 [this, self](std::error_code ec, std::size_t length)
114 {
115 // Check if reading has been stopped or socket closed at callback time
116 // This prevents accessing invalid socket state after close()
117 if (!is_reading_.load() || is_closed_.load())
118 {
119 return;
120 }
121
122 if (ec)
123 {
124 // On error, invoke the error callback
125 // Lock-free callback access via atomic_load
126 auto error_cb = std::atomic_load(&error_callback_);
127 if (error_cb && *error_cb)
128 {
129 (*error_cb)(ec);
130 }
131 return;
132 }
133
134 // On success, if length > 0, dispatch to the appropriate callback
135 if (length > 0)
136 {
137 // Lock-free callback access via atomic_load
138 // Prefer view callback (zero-copy) over vector callback
139 auto view_cb = std::atomic_load(&receive_callback_view_);
140 if (view_cb && *view_cb)
141 {
142 // Zero-copy path: create span view directly into read_buffer_
143 // No std::vector allocation or copy required
144 std::span<const uint8_t> data_view(read_buffer_.data(), length);
145 (*view_cb)(data_view);
146 }
147 else
148 {
149 // Legacy path: allocate and copy into vector for compatibility
150 auto recv_cb = std::atomic_load(&receive_callback_);
151 if (recv_cb && *recv_cb)
152 {
153 std::vector<uint8_t> chunk(read_buffer_.begin(),
154 read_buffer_.begin() + length);
155 (*recv_cb)(chunk);
156 }
157 }
158 }
159
160 // Continue reading only if still active and socket is not closed
161 // Use atomic is_closed_ flag to prevent data race with close()
162 if (is_reading_.load() && !is_closed_.load())
163 {
164 do_read();
165 }
166 });
167 }
168
170 std::vector<uint8_t>&& data,
171 std::function<void(std::error_code, std::size_t)> handler) -> void
172 {
173 // Check if socket has been closed before starting async operation
174 // Use atomic is_closed_ flag to prevent data race with close()
175 if (is_closed_.load())
176 {
177 if (handler)
178 {
179 handler(asio::error::not_connected, 0);
180 }
181 return;
182 }
183
184 auto self = shared_from_this();
185 // Move data into shared_ptr for lifetime management
186 auto buffer = std::make_shared<std::vector<uint8_t>>(std::move(data));
187 asio::async_write(
188 ssl_stream_, asio::buffer(*buffer),
189 [handler = std::move(handler), self, buffer](std::error_code ec, std::size_t bytes_transferred)
190 {
191 if constexpr (std::is_invocable_v<decltype(handler), std::error_code, std::size_t>)
192 {
193 if (handler)
194 {
195 handler(ec, bytes_transferred);
196 }
197 }
198 });
199 }
200
201} // namespace kcenon::network::internal
auto set_receive_callback(std::function< void(const std::vector< uint8_t > &)> callback) -> void
Sets a callback to receive inbound data chunks.
secure_tcp_socket(asio::ip::tcp::socket socket, asio::ssl::context &ssl_context)
Constructs a secure_tcp_socket by taking ownership of a moved socket and SSL context.
auto async_handshake(asio::ssl::stream_base::handshake_type type, std::function< void(std::error_code)> handler) -> void
Performs asynchronous SSL handshake.
auto close() -> void
Safely closes the socket and stops all async operations.
auto start_read() -> void
Begins the continuous asynchronous read loop.
auto async_send(std::vector< uint8_t > &&data, std::function< void(std::error_code, std::size_t)> handler) -> void
Initiates an asynchronous write of the given data buffer with encryption.
auto do_read() -> void
Internal function to handle the read logic with async_read_some().
auto is_closed() const -> bool
Checks if the socket has been closed.
auto set_receive_callback_view(std::function< void(std::span< const uint8_t >)> callback) -> void
Sets a zero-copy callback to receive inbound data as a view.
auto set_error_callback(std::function< void(std::error_code)> callback) -> void
Sets a callback to handle socket errors (e.g., read/write failures).
auto stop_read() -> void
Stops the read loop to prevent further async operations.