Network System 0.1.1
High-performance modular networking library for scalable client-server applications
Loading...
Searching...
No Matches
secure_messaging_client.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
9
10#include <chrono>
11#include <string_view>
12
13namespace kcenon::network::core {
14
15using tcp = asio::ip::tcp;
16
18 bool verify_cert)
19 : client_id_(client_id),
20 verify_cert_(verify_cert) {
21 // Initialize SSL context with TLS 1.3 support (TICKET-009: TLS 1.3 only by default)
23 std::make_unique<asio::ssl::context>(asio::ssl::context::tls_client);
24
25 // Set SSL context options - disable all legacy protocols
26 ssl_context_->set_options(
27 asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 |
28 asio::ssl::context::no_sslv3 | asio::ssl::context::no_tlsv1 |
29 asio::ssl::context::no_tlsv1_1);
30
31 // Enforce TLS 1.3 minimum version using native OpenSSL handle
32 // This prevents protocol downgrade attacks (CVSS 7.5)
33 SSL_CTX *native_ctx = ssl_context_->native_handle();
34 if (native_ctx) {
35 // Set minimum protocol version to TLS 1.3 (security hardening)
36 SSL_CTX_set_min_proto_version(native_ctx, TLS1_3_VERSION);
37 SSL_CTX_set_max_proto_version(native_ctx, TLS1_3_VERSION);
38
39 // Set strong cipher suites for TLS 1.3
40 SSL_CTX_set_ciphersuites(native_ctx,
41 "TLS_AES_256_GCM_SHA384:"
42 "TLS_CHACHA20_POLY1305_SHA256:"
43 "TLS_AES_128_GCM_SHA256");
44 }
45
46 if (verify_cert_) {
47 // Set verification mode to verify peer certificate
48 ssl_context_->set_verify_mode(asio::ssl::verify_peer);
49
50 // Load default certificate paths for verification
51 ssl_context_->set_default_verify_paths();
52
53 NETWORK_LOG_INFO("[secure_messaging_client] SSL context initialized with "
54 "TLS 1.3 only and certificate verification");
55 } else {
56 // No certificate verification
57 ssl_context_->set_verify_mode(asio::ssl::verify_none);
58
59 NETWORK_LOG_WARN("[secure_messaging_client] SSL context initialized with "
60 "TLS 1.3 only WITHOUT certificate verification");
61 }
62}
63
69
70// =====================================================================
71// Lifecycle Management
72// =====================================================================
73
74auto secure_messaging_client::start_client(std::string_view host, unsigned short port)
75 -> VoidResult {
76 if (lifecycle_.is_running()) {
77 return error_void(
79 "Client is already running",
80 "secure_messaging_client::start_client");
81 }
82
83 if (host.empty()) {
84 return error_void(
86 "Host cannot be empty",
87 "secure_messaging_client::start_client");
88 }
89
90 if (!lifecycle_.try_start()) {
91 return error_void(
93 "Client is already starting",
94 "secure_messaging_client::start_client");
95 }
96
97 is_connected_.store(false, std::memory_order_release);
98
99 // Call implementation
100 auto result = do_start_impl(host, port);
101 if (result.is_err()) {
102 lifecycle_.mark_stopped();
103 }
104
105 return result;
106}
107
109 if (!lifecycle_.is_running()) {
110 return ok();
111 }
112
113 if (!lifecycle_.prepare_stop()) {
114 return ok(); // Already stopping or not running
115 }
116
117 is_connected_.store(false, std::memory_order_release);
118
119 // Call implementation
120 auto result = do_stop_impl();
121
122 // Signal stop completion
123 lifecycle_.mark_stopped();
124
125 // Invoke disconnected callback
126 invoke_disconnected_callback();
127
128 return result;
129}
130
132 lifecycle_.wait_for_stop();
133}
134
135auto secure_messaging_client::is_running() const noexcept -> bool {
136 return lifecycle_.is_running();
137}
138
139auto secure_messaging_client::is_connected() const noexcept -> bool {
140 return is_connected_.load(std::memory_order_acquire);
141}
142
143auto secure_messaging_client::client_id() const -> const std::string& {
144 return client_id_;
145}
146
147// =====================================================================
148// Data Transfer
149// =====================================================================
150
151auto secure_messaging_client::send_packet(std::vector<uint8_t>&& data) -> VoidResult {
152 if (!is_connected_.load(std::memory_order_acquire)) {
153 return error_void(
155 "Not connected",
156 "secure_messaging_client::send_packet");
157 }
158
159 if (data.empty()) {
160 return error_void(
162 "Data cannot be empty",
163 "secure_messaging_client::send_packet");
164 }
165
166 return do_send_impl(std::move(data));
167}
168
169// =====================================================================
170// Callback Setters
171// =====================================================================
172
174 callbacks_.set<to_index(callback_index::receive)>(std::move(callback));
175}
176
178 callbacks_.set<to_index(callback_index::connected)>(std::move(callback));
179}
180
182 callbacks_.set<to_index(callback_index::disconnected)>(std::move(callback));
183}
184
186 callbacks_.set<to_index(callback_index::error)>(std::move(callback));
187}
188
189// =====================================================================
190// Internal Callback Helpers
191// =====================================================================
192
194 is_connected_.store(connected, std::memory_order_release);
195}
196
197auto secure_messaging_client::invoke_receive_callback(const std::vector<uint8_t>& data) -> void {
198 callbacks_.invoke<to_index(callback_index::receive)>(data);
199}
200
202 callbacks_.invoke<to_index(callback_index::connected)>();
203}
204
206 callbacks_.invoke<to_index(callback_index::disconnected)>();
207}
208
209auto secure_messaging_client::invoke_error_callback(std::error_code ec) -> void {
210 callbacks_.invoke<to_index(callback_index::error)>(ec);
211}
212
213// =====================================================================
214// Internal Implementation Methods
215// =====================================================================
216
217auto secure_messaging_client::do_start_impl(std::string_view host,
218 unsigned short port) -> VoidResult {
219 try {
220 // Create io_context
221 io_context_ = std::make_unique<asio::io_context>();
222
223 // Create work guard to keep io_context running
224 work_guard_ = std::make_unique<
225 asio::executor_work_guard<asio::io_context::executor_type>>(
226 asio::make_work_guard(*io_context_));
227
228 // Resolve the server address
229 tcp::resolver resolver(*io_context_);
230 auto endpoints = resolver.resolve(std::string(host), std::to_string(port));
231
232 // Create TCP socket first (not SSL socket yet)
233 tcp::socket tcp_sock(*io_context_);
234
235 // Connect to server (plain TCP)
236 std::error_code connect_ec;
237 asio::connect(tcp_sock, endpoints, connect_ec);
238
239 if (connect_ec) {
241 "Failed to connect to server: " + connect_ec.message(),
242 "secure_messaging_client::do_start_impl",
243 "Host: " + std::string(host) +
244 ", Port: " + std::to_string(port));
245 }
246
247 // Now create secure socket with connected TCP socket
248 socket_ = std::make_shared<internal::secure_tcp_socket>(std::move(tcp_sock),
249 *ssl_context_);
250
251 // Set up callbacks
252 auto self = shared_from_this();
253 socket_->set_receive_callback(
254 [this, self](const std::vector<uint8_t> &data) { on_receive(data); });
255 socket_->set_error_callback(
256 [this, self](std::error_code ec) { on_error(ec); });
257
258 // Perform SSL handshake (synchronously for now)
259 std::promise<std::error_code> handshake_promise;
260 auto handshake_future = handshake_promise.get_future();
261
262 socket_->async_handshake(asio::ssl::stream_base::client,
263 [&handshake_promise](std::error_code ec) {
264 handshake_promise.set_value(ec);
265 });
266
267 // Get thread pool from network context
269 if (!thread_pool_) {
270 // Fallback: create a temporary thread pool if network_context is not
271 // initialized
273 "[secure_messaging_client::" + client_id_ +
274 "] network_context not initialized, creating temporary thread pool");
275 thread_pool_ = std::make_shared<integration::basic_thread_pool>(
276 std::thread::hardware_concurrency());
277 }
278
279 // Start io_context on thread pool for handshake to complete
280 io_context_future_ = thread_pool_->submit([this]() {
281 try {
282 NETWORK_LOG_DEBUG("[secure_messaging_client::" + client_id_ +
283 "] io_context started");
284 io_context_->run();
285 NETWORK_LOG_DEBUG("[secure_messaging_client::" + client_id_ +
286 "] io_context stopped");
287 } catch (const std::exception &e) {
289 "[secure_messaging_client::" + client_id_ +
290 "] Exception in io_context: " + std::string(e.what()));
291 }
292 });
293
294 // Wait for handshake to complete (with timeout)
295 auto status = handshake_future.wait_for(std::chrono::seconds(10));
296
297 if (status == std::future_status::timeout) {
299 "SSL handshake timeout",
300 "secure_messaging_client::do_start_impl",
301 "Host: " + std::string(host) +
302 ", Port: " + std::to_string(port));
303 }
304
305 auto handshake_ec = handshake_future.get();
306 if (handshake_ec) {
308 "SSL handshake failed: " + handshake_ec.message(),
309 "secure_messaging_client::do_start_impl",
310 "Host: " + std::string(host) +
311 ", Port: " + std::to_string(port));
312 }
313
314 // Begin reading after successful handshake
315 socket_->start_read();
316
317 // Mark as connected
318 set_connected(true);
319
320 NETWORK_LOG_INFO("[secure_messaging_client] Connected to " +
321 std::string(host) + ":" + std::to_string(port) +
322 " (TLS/SSL secured)");
323
324 // Invoke connected callback
325 invoke_connected_callback();
326
327 return ok();
328 } catch (const std::system_error &e) {
330 "Failed to connect: " + std::string(e.what()),
331 "secure_messaging_client::do_start_impl",
332 "Host: " + std::string(host) +
333 ", Port: " + std::to_string(port));
334 } catch (const std::exception &e) {
336 "Failed to connect: " + std::string(e.what()),
337 "secure_messaging_client::do_start_impl",
338 "Host: " + std::string(host) +
339 ", Port: " + std::to_string(port));
340 }
341}
342
344 try {
345 // Close socket safely using atomic close() method
346 // This prevents data races between close and async read operations
347 if (socket_) {
348 socket_->close();
349 }
350
351 // Release work guard
352 if (work_guard_) {
353 work_guard_.reset();
354 }
355
356 // Stop io_context
357 if (io_context_) {
358 io_context_->stop();
359 }
360
361 // Wait for io_context task to complete
362 if (io_context_future_.valid()) {
363 try {
364 io_context_future_.wait();
365 } catch (const std::exception &e) {
366 NETWORK_LOG_ERROR("[secure_messaging_client::" + client_id_ +
367 "] Exception while waiting for io_context: " +
368 std::string(e.what()));
369 }
370 }
371
372 // Release resources
373 socket_.reset();
374 thread_pool_.reset();
375 io_context_.reset();
376
377 NETWORK_LOG_INFO("[secure_messaging_client] Disconnected.");
378 return ok();
379 } catch (const std::exception &e) {
381 "Failed to stop client: " + std::string(e.what()),
382 "secure_messaging_client::do_stop_impl",
383 "Client ID: " + client_id_);
384 }
385}
386
387auto secure_messaging_client::do_send_impl(std::vector<uint8_t> &&data)
388 -> VoidResult {
389 if (!socket_) {
390 return error_void(
391 error_codes::network_system::send_failed, "Socket is not available",
392 "secure_messaging_client::do_send_impl", "Client ID: " + client_id_);
393 }
394
395 // Send data directly over the secure connection
396 socket_->async_send(
397 std::move(data), [this](std::error_code ec, std::size_t bytes_transferred) {
398 if (ec) {
399 NETWORK_LOG_ERROR("[secure_messaging_client] Send error: " +
400 ec.message());
401 } else {
402 NETWORK_LOG_DEBUG("[secure_messaging_client] Sent " +
403 std::to_string(bytes_transferred) + " bytes");
404 }
405 });
406
407 return ok();
408}
409
410// =====================================================================
411// Internal Socket Handlers
412// =====================================================================
413
414auto secure_messaging_client::on_receive(const std::vector<uint8_t> &data)
415 -> void {
416 if (!is_connected()) {
417 return;
418 }
419
420 NETWORK_LOG_DEBUG("[secure_messaging_client] Received " +
421 std::to_string(data.size()) + " bytes.");
422
423 // Invoke receive callback
424 invoke_receive_callback(data);
425}
426
427auto secure_messaging_client::on_error(std::error_code ec) -> void {
428 NETWORK_LOG_ERROR("[secure_messaging_client] Socket error: " + ec.message());
429
430 // Invoke error callback
431 invoke_error_callback(ec);
432
433 // Invoke disconnected callback if was connected
434 if (is_connected()) {
435 invoke_disconnected_callback();
436 }
437
438 // Mark connection as lost
439 set_connected(false);
440}
441
442} // namespace kcenon::network::core
std::shared_ptr< kcenon::network::integration::thread_pool_interface > get_thread_pool()
Get current thread pool.
static network_context & instance()
Get the singleton instance.
std::function< void()> connected_callback_t
Callback type for connection established.
~secure_messaging_client() noexcept
Destructor. Calls stop_client() if still connected.
auto on_receive(const std::vector< uint8_t > &data) -> void
Callback for when encrypted data arrives from the server.
auto send_packet(std::vector< uint8_t > &&data) -> VoidResult
Sends data to the connected server.
auto invoke_disconnected_callback() -> void
Invokes the disconnected callback.
auto client_id() const -> const std::string &
Returns the client identifier.
auto invoke_connected_callback() -> void
Invokes the connected callback.
auto set_receive_callback(receive_callback_t callback) -> void
Sets the callback for received data.
auto set_connected_callback(connected_callback_t callback) -> void
Sets the callback for connection established.
auto on_error(std::error_code ec) -> void
Callback for handling socket errors.
auto stop_client() -> VoidResult
Stops the client and disconnects from the server.
auto do_stop_impl() -> VoidResult
Secure TCP-specific implementation of client stop.
std::function< void(std::error_code)> error_callback_t
Callback type for errors.
auto do_send_impl(std::vector< uint8_t > &&data) -> VoidResult
Secure TCP-specific implementation of data send.
auto invoke_receive_callback(const std::vector< uint8_t > &data) -> void
Invokes the receive callback.
auto wait_for_stop() -> void
Blocks until stop_client() is called.
secure_messaging_client(std::string_view client_id, bool verify_cert=true)
Constructs a secure messaging client.
auto is_running() const noexcept -> bool
Checks if the client is currently running.
auto set_error_callback(error_callback_t callback) -> void
Sets the callback for errors.
std::function< void(const std::vector< uint8_t > &)> receive_callback_t
Callback type for received data.
auto start_client(std::string_view host, unsigned short port) -> VoidResult
Starts the client and connects to the specified host and port.
auto set_connected(bool connected) -> void
Sets the connected state.
std::function< void()> disconnected_callback_t
Callback type for disconnection.
std::unique_ptr< asio::ssl::context > ssl_context_
auto is_connected() const noexcept -> bool
Checks if the client is connected to the server.
auto invoke_error_callback(std::error_code ec) -> void
Invokes the error callback.
auto do_start_impl(std::string_view host, unsigned short port) -> VoidResult
Secure TCP-specific implementation of client start.
auto set_disconnected_callback(disconnected_callback_t callback) -> void
Sets the callback for disconnection.
virtual std::future< void > submit(std::function< void()> task)=0
Submit a task to the thread pool.
auto is_running() const -> bool
Checks if the component is currently running.
struct ssl_ctx_st SSL_CTX
Definition crypto.h:20
Logger system integration interface for network_system.
#define NETWORK_LOG_WARN(msg)
#define NETWORK_LOG_INFO(msg)
#define NETWORK_LOG_ERROR(msg)
#define NETWORK_LOG_DEBUG(msg)
constexpr auto to_index(E e) noexcept -> std::size_t
Helper to convert enum to std::size_t for callback_manager access.
VoidResult error_void(int code, const std::string &message, const std::string &source="network_system", const std::string &details="")
VoidResult ok()
Global context for shared network system resources.
Secure TCP client class with TLS/SSL support.