Network System 0.1.1
High-performance modular networking library for scalable client-server applications
Loading...
Searching...
No Matches
secure_messaging_udp_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
8
9#include <chrono>
10#include <condition_variable>
11#include <string_view>
12
14{
15
17 std::string_view client_id, bool verify_cert)
18 : client_id_(client_id)
19 , verify_cert_(verify_cert)
20{
21}
22
30
31// =====================================================================
32// Lifecycle Management
33// =====================================================================
34
35auto secure_messaging_udp_client::start_client(std::string_view host, unsigned short port)
36 -> VoidResult
37{
38 if (lifecycle_.is_running())
39 {
40 return error_void(
42 "Client is already running",
43 "secure_messaging_udp_client::start_client");
44 }
45
46 if (host.empty())
47 {
48 return error_void(
50 "Host cannot be empty",
51 "secure_messaging_udp_client::start_client");
52 }
53
54 if (!lifecycle_.try_start())
55 {
56 return error_void(
58 "Client is already starting",
59 "secure_messaging_udp_client::start_client");
60 }
61
62 is_connected_.store(false, std::memory_order_release);
63
64 // Call implementation
65 auto result = do_start_impl(host, port);
66 if (result.is_err())
67 {
68 lifecycle_.mark_stopped();
69 }
70
71 return result;
72}
73
75{
76 if (!lifecycle_.is_running())
77 {
78 return ok();
79 }
80
81 if (!lifecycle_.prepare_stop())
82 {
83 return ok(); // Already stopping or not running
84 }
85
86 is_connected_.store(false, std::memory_order_release);
87
88 // Call implementation
89 auto result = do_stop_impl();
90
91 // Signal stop completion
92 lifecycle_.mark_stopped();
93
94 // Invoke disconnected callback
95 invoke_disconnected_callback();
96
97 return result;
98}
99
101{
102 lifecycle_.wait_for_stop();
103}
104
105auto secure_messaging_udp_client::is_running() const noexcept -> bool
106{
107 return lifecycle_.is_running();
108}
109
110auto secure_messaging_udp_client::is_connected() const noexcept -> bool
111{
112 return is_connected_.load(std::memory_order_acquire);
113}
114
115auto secure_messaging_udp_client::client_id() const -> const std::string&
116{
117 return client_id_;
118}
119
120// =====================================================================
121// Data Transfer
122// =====================================================================
123
124auto secure_messaging_udp_client::send_packet(std::vector<uint8_t>&& data) -> VoidResult
125{
126 if (!is_connected_.load(std::memory_order_acquire))
127 {
128 return error_void(
130 "Not connected",
131 "secure_messaging_udp_client::send_packet");
132 }
133
134 if (data.empty())
135 {
136 return error_void(
138 "Data cannot be empty",
139 "secure_messaging_udp_client::send_packet");
140 }
141
142 return do_send_impl(std::move(data));
143}
144
146 std::vector<uint8_t>&& data,
147 std::function<void(std::error_code, std::size_t)> handler) -> VoidResult
148{
149 if (!is_connected())
150 {
152 "Client not connected",
153 "secure_messaging_udp_client::send_packet_with_handler");
154 }
155
156 if (!socket_)
157 {
159 "Socket not available",
160 "secure_messaging_udp_client::send_packet_with_handler");
161 }
162
163 socket_->async_send(std::move(data), std::move(handler));
164 return ok();
165}
166
167// =====================================================================
168// Callback Setters
169// =====================================================================
170
172 udp_receive_callback_t callback) -> void
173{
174 std::lock_guard<std::mutex> lock(udp_callback_mutex_);
175 udp_receive_callback_ = std::move(callback);
176}
177
179{
180 callbacks_.set<to_index(callback_index::receive)>(std::move(callback));
181}
182
184{
185 callbacks_.set<to_index(callback_index::connected)>(std::move(callback));
186}
187
189{
190 callbacks_.set<to_index(callback_index::disconnected)>(std::move(callback));
191}
192
194{
195 callbacks_.set<to_index(callback_index::error)>(std::move(callback));
196}
197
198// =====================================================================
199// Internal Callback Helpers
200// =====================================================================
201
203{
204 is_connected_.store(connected, std::memory_order_release);
205}
206
207auto secure_messaging_udp_client::invoke_receive_callback(const std::vector<uint8_t>& data) -> void
208{
209 callbacks_.invoke<to_index(callback_index::receive)>(data);
210}
211
213{
214 callbacks_.invoke<to_index(callback_index::connected)>();
215}
216
218{
219 callbacks_.invoke<to_index(callback_index::disconnected)>();
220}
221
223{
224 callbacks_.invoke<to_index(callback_index::error)>(ec);
225}
226
227// =====================================================================
228// Internal Implementation Methods
229// =====================================================================
230
232{
233 // Create DTLS client context
234 ssl_ctx_ = SSL_CTX_new(DTLS_client_method());
235 if (!ssl_ctx_)
236 {
238 "Failed to create DTLS context",
239 "secure_messaging_udp_client::init_ssl_context");
240 }
241
242 // Set DTLS options
243 SSL_CTX_set_options(ssl_ctx_, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
244
245 // Set verification mode
246 if (verify_cert_)
247 {
248 SSL_CTX_set_verify(ssl_ctx_, SSL_VERIFY_PEER, nullptr);
249 // Load default CA certificates
250 SSL_CTX_set_default_verify_paths(ssl_ctx_);
251 }
252 else
253 {
254 SSL_CTX_set_verify(ssl_ctx_, SSL_VERIFY_NONE, nullptr);
255 }
256
257 return ok();
258}
259
261 std::string_view host, unsigned short port) -> VoidResult
262{
263 // Initialize SSL context
264 auto ssl_result = init_ssl_context();
265 if (!ssl_result.is_ok())
266 {
267 return ssl_result;
268 }
269
270 // Create io_context
271 io_context_ = std::make_unique<asio::io_context>();
272
273 // Resolve the target endpoint
274 asio::ip::udp::resolver resolver(*io_context_);
275 std::error_code resolve_ec;
276 auto endpoints = resolver.resolve(
277 asio::ip::udp::v4(),
278 std::string(host),
279 std::to_string(port),
280 resolve_ec);
281
282 if (resolve_ec || endpoints.empty())
283 {
284 SSL_CTX_free(ssl_ctx_);
285 ssl_ctx_ = nullptr;
287 "Failed to resolve host: " + std::string(host),
288 "secure_messaging_udp_client::do_start_impl");
289 }
290
291 {
292 std::lock_guard<std::mutex> lock(endpoint_mutex_);
293 target_endpoint_ = *endpoints.begin();
294 }
295
296 // Create UDP socket
297 asio::ip::udp::socket udp_socket(*io_context_, asio::ip::udp::v4());
298
299 // Create DTLS socket
300 socket_ = std::make_shared<internal::dtls_socket>(
301 std::move(udp_socket), ssl_ctx_);
302
303 // Set peer endpoint
304 {
305 std::lock_guard<std::mutex> lock(endpoint_mutex_);
306 socket_->set_peer_endpoint(target_endpoint_);
307 }
308
309 // Set callbacks - use UDP-specific callback for receive
310 auto self = shared_from_this();
311 socket_->set_receive_callback(
312 [this, self](const std::vector<uint8_t>& data, const asio::ip::udp::endpoint& sender)
313 {
314 udp_receive_callback_t callback;
315 {
316 std::lock_guard<std::mutex> lock(udp_callback_mutex_);
317 callback = udp_receive_callback_;
318 }
319 if (callback)
320 {
321 callback(data, sender);
322 }
323 });
324
325 socket_->set_error_callback(
326 [this, self](std::error_code ec)
327 {
328 invoke_error_callback(ec);
329 });
330
331 // Get thread pool from network context
333 if (!thread_pool_) {
334 // Fallback: create a temporary thread pool if network_context is not initialized
335 thread_pool_ = std::make_shared<integration::basic_thread_pool>(std::thread::hardware_concurrency());
336 }
337
338 // Start io_context in background
339 io_context_future_ = thread_pool_->submit(
340 [this]()
341 {
342 auto work_guard = asio::make_work_guard(*io_context_);
343 io_context_->run();
344 });
345
346 // Perform handshake
347 auto handshake_result = do_handshake();
348 if (!handshake_result.is_ok())
349 {
350 return handshake_result;
351 }
352
353 // Mark as connected
354 set_connected(true);
355
356 // Notify connected
357 invoke_connected_callback();
358
359 return ok();
360}
361
363{
364 std::mutex handshake_mutex;
365 std::condition_variable handshake_cv;
366 bool handshake_done = false;
367 std::error_code handshake_error;
368
369 socket_->async_handshake(
371 [&](std::error_code ec)
372 {
373 {
374 std::lock_guard<std::mutex> lock(handshake_mutex);
375 handshake_done = true;
376 handshake_error = ec;
377 }
378 handshake_cv.notify_one();
379 });
380
381 // Wait for handshake with timeout
382 {
383 std::unique_lock<std::mutex> lock(handshake_mutex);
384 if (!handshake_cv.wait_for(lock, std::chrono::seconds(30),
385 [&] { return handshake_done; }))
386 {
388 "DTLS handshake timeout",
389 "secure_messaging_udp_client::do_handshake");
390 }
391 }
392
393 if (handshake_error)
394 {
396 "DTLS handshake failed: " + handshake_error.message(),
397 "secure_messaging_udp_client::do_handshake");
398 }
399
400 return ok();
401}
402
404{
405 // Stop socket
406 if (socket_)
407 {
408 socket_->stop_receive();
409 socket_.reset();
410 }
411
412 // Stop io_context
413 if (io_context_)
414 {
415 io_context_->stop();
416 }
417
418 // Wait for io_context thread
419 if (io_context_future_.valid())
420 {
421 io_context_future_.wait();
422 }
423
424 // Cleanup SSL context
425 if (ssl_ctx_)
426 {
427 SSL_CTX_free(ssl_ctx_);
428 ssl_ctx_ = nullptr;
429 }
430
431 return ok();
432}
433
434auto secure_messaging_udp_client::do_send_impl(std::vector<uint8_t>&& data) -> VoidResult
435{
436 if (!socket_)
437 {
439 "Socket not available",
440 "secure_messaging_udp_client::do_send_impl");
441 }
442
443 socket_->async_send(std::move(data), nullptr);
444 return ok();
445}
446
447} // 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.
auto invoke_disconnected_callback() -> void
Invokes the disconnected callback.
auto set_error_callback(error_callback_t callback) -> void
Sets the callback for errors.
auto set_udp_receive_callback(udp_receive_callback_t callback) -> void
Sets a UDP-specific callback to handle received decrypted datagrams.
auto send_packet_with_handler(std::vector< uint8_t > &&data, std::function< void(std::error_code, std::size_t)> handler) -> VoidResult
Sends an encrypted datagram with a completion handler.
auto set_disconnected_callback(disconnected_callback_t callback) -> void
Sets the callback for disconnection.
auto do_start_impl(std::string_view host, unsigned short port) -> VoidResult
DTLS-specific implementation of client start.
auto init_ssl_context() -> VoidResult
Initializes the SSL context for DTLS.
auto do_send_impl(std::vector< uint8_t > &&data) -> VoidResult
DTLS-specific implementation of data send.
auto start_client(std::string_view host, unsigned short port) -> VoidResult
Starts the client and connects to the specified host and port.
std::function< void(const std::vector< uint8_t > &)> receive_callback_t
Callback type for received data.
~secure_messaging_udp_client() noexcept
Destructor. Automatically calls stop_client() if still running.
auto invoke_connected_callback() -> void
Invokes the connected callback.
auto do_stop_impl() -> VoidResult
DTLS-specific implementation of client stop.
std::function< void()> connected_callback_t
Callback type for connection established.
secure_messaging_udp_client(std::string_view client_id, bool verify_cert=true)
Constructs a secure_messaging_udp_client with an identifier.
auto stop_client() -> VoidResult
Stops the client and disconnects.
auto invoke_error_callback(std::error_code ec) -> void
Invokes the error callback.
auto invoke_receive_callback(const std::vector< uint8_t > &data) -> void
Invokes the receive callback.
std::function< void()> disconnected_callback_t
Callback type for disconnection.
auto client_id() const -> const std::string &
Returns the client identifier.
auto is_connected() const noexcept -> bool
Checks if the client is connected to the server.
auto send_packet(std::vector< uint8_t > &&data) -> VoidResult
Sends an encrypted datagram to the server.
auto is_running() const noexcept -> bool
Checks if the client is currently running.
auto set_receive_callback(receive_callback_t callback) -> void
Sets the callback for received data.
auto set_connected(bool connected) -> void
Sets the connected state.
auto wait_for_stop() -> void
Blocks until stop_client() is called.
std::function< void(const std::vector< uint8_t > &, const asio::ip::udp::endpoint &)> udp_receive_callback_t
UDP-specific receive callback type with sender endpoint.
std::function< void(std::error_code)> error_callback_t
Callback type for errors.
auto do_handshake() -> VoidResult
Performs the DTLS handshake.
auto set_connected_callback(connected_callback_t callback) -> void
Sets the callback for connection established.
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.
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.
DTLS client class for encrypted UDP communication.