CppServer 1.0.6.0
C++ Server Library
Loading...
Searching...
No Matches
ssl_session.cpp
Go to the documentation of this file.
1
8
11
12namespace CppServer {
13namespace Asio {
14
15SSLSession::SSLSession(const std::shared_ptr<SSLServer>& server)
16 : _id(CppCommon::UUID::Sequential()),
17 _server(server),
18 _io_context(server->service()->GetAsioContext()),
19 _strand(*_io_context),
20 _strand_required(_server->_strand_required),
21 _stream(*_io_context, *server->context()),
22 _connected(false),
23 _handshaked(false),
24 _bytes_pending(0),
25 _bytes_sending(0),
26 _bytes_sent(0),
27 _bytes_received(0),
28 _receiving(false),
29 _sending(false),
30 _send_buffer_flush_offset(0)
31{
32}
33
35{
36 asio::socket_base::receive_buffer_size option;
37 _stream.next_layer().get_option(option);
38 return option.value();
39}
40
42{
43 asio::socket_base::send_buffer_size option;
44 _stream.next_layer().get_option(option);
45 return option.value();
46}
47
49{
50 asio::socket_base::receive_buffer_size option((int)size);
51 socket().set_option(option);
52}
53
55{
56 asio::socket_base::send_buffer_size option((int)size);
57 socket().set_option(option);
58}
59
60void SSLSession::Connect()
61{
62 // Apply the option: keep alive
63 if (_server->option_keep_alive())
64 socket().set_option(asio::ip::tcp::socket::keep_alive(true));
65 // Apply the option: no delay
66 if (_server->option_no_delay())
67 socket().set_option(asio::ip::tcp::no_delay(true));
68
69 // Prepare receive & send buffers
70 _receive_buffer.resize(option_receive_buffer_size());
71 _send_buffer_main.reserve(option_send_buffer_size());
72 _send_buffer_flush.reserve(option_send_buffer_size());
73
74 // Reset statistic
75 _bytes_pending = 0;
76 _bytes_sending = 0;
77 _bytes_sent = 0;
78 _bytes_received = 0;
79
80 // Call the session connecting handler
82
83 // Update the connected flag
84 _connected = true;
85
86 // Call the session connected handler
88
89 // Call the session connected handler in the server
90 auto connected_session(this->shared_from_this());
91 _server->onConnected(connected_session);
92
93 // Call the session handshaking handler
95
96 // Async SSL handshake with the handshake handler
97 auto self(this->shared_from_this());
98 auto async_handshake_handler = [this, self](std::error_code ec)
99 {
100 if (IsHandshaked())
101 return;
102
103 if (!ec)
104 {
105 // Update the handshaked flag
106 _handshaked = true;
107
108 // Try to receive something from the client
109 TryReceive();
110
111 // Call the session handshaked handler
112 onHandshaked();
113
114 // Call the session handshaked handler in the server
115 auto handshaked_session(this->shared_from_this());
116 _server->onHandshaked(handshaked_session);
117
118 // Call the empty send buffer handler
119 if (_send_buffer_main.empty())
120 onEmpty();
121 }
122 else
123 {
124 // Disconnect in case of the bad handshake
125 SendError(ec);
126 Disconnect(ec);
127 }
128 };
129 if (_strand_required)
130 _stream.async_handshake(asio::ssl::stream_base::server, bind_executor(_strand, async_handshake_handler));
131 else
132 _stream.async_handshake(asio::ssl::stream_base::server, async_handshake_handler);
133}
134
135void SSLSession::Disconnect(std::error_code ec)
136{
137 if (!IsConnected())
138 return;
139
140 // Call the session disconnecting handler
142
143 // Close the session socket
144 socket().close();
145
146 // Update the handshaked flag
147 _handshaked = false;
148
149 // Update the connected flag
150 _connected = false;
151
152 // Update sending/receiving flags
153 _receiving = false;
154 _sending = false;
155
156 // Clear send/receive buffers
157 ClearBuffers();
158
159 // Call the session disconnected handler
161
162 // Call the session disconnected handler in the server
163 auto disconnected_session(this->shared_from_this());
164 _server->onDisconnected(disconnected_session);
165
166 // Dispatch the unregister session handler
167 auto self(this->shared_from_this());
168 auto unregister_session_handler = [this, self]()
169 {
170 _server->UnregisterSession(id());
171 };
172 if (_server->_strand_required)
173 asio::dispatch(_server->_strand, unregister_session_handler);
174 else
175 asio::dispatch(*_server->_io_context, unregister_session_handler);
176}
177
178bool SSLSession::DisconnectAsync(bool dispatch)
179{
180 if (!IsConnected())
181 return false;
182
183 // Dispatch or post the disconnect handler
184 auto self(this->shared_from_this());
185 auto disconnect_handler = [this, self]()
186 {
187 if (!IsConnected())
188 return;
189
190 asio::error_code ec;
191
192 // Cancel the session socket
193 socket().cancel(ec);
194
195 // Async SSL shutdown with the shutdown handler
196 auto async_shutdown_handler = [this, self](std::error_code ec2) { Disconnect(ec2); };
197 if (_strand_required)
198 _stream.async_shutdown(bind_executor(_strand, async_shutdown_handler));
199 else
200 _stream.async_shutdown(async_shutdown_handler);
201 };
202 if (_strand_required)
203 {
204 if (dispatch)
205 asio::dispatch(_strand, disconnect_handler);
206 else
207 asio::post(_strand, disconnect_handler);
208 }
209 else
210 {
211 if (dispatch)
212 asio::dispatch(*_io_context, disconnect_handler);
213 else
214 asio::post(*_io_context, disconnect_handler);
215 }
216
217 return true;
218}
219
220size_t SSLSession::Send(const void* buffer, size_t size)
221{
222 if (!IsHandshaked())
223 return 0;
224
225 if (size == 0)
226 return 0;
227
228 assert((buffer != nullptr) && "Pointer to the buffer should not be null!");
229 if (buffer == nullptr)
230 return 0;
231
232 asio::error_code ec;
233
234 // Send data to the client
235 size_t sent = asio::write(_stream, asio::buffer(buffer, size), ec);
236 if (sent > 0)
237 {
238 // Update statistic
239 _bytes_sent += sent;
240 _server->_bytes_sent += sent;
241
242 // Call the buffer sent handler
243 onSent(sent, bytes_pending());
244 }
245
246 // Disconnect on error
247 if (ec)
248 {
249 SendError(ec);
250 Disconnect(ec);
251 }
252
253 return sent;
254}
255
256size_t SSLSession::Send(const void* buffer, size_t size, const CppCommon::Timespan& timeout)
257{
258 if (!IsHandshaked())
259 return 0;
260
261 if (size == 0)
262 return 0;
263
264 assert((buffer != nullptr) && "Pointer to the buffer should not be null!");
265 if (buffer == nullptr)
266 return 0;
267
268 int done = 0;
269 std::mutex mtx;
270 std::condition_variable cv;
271 asio::error_code error;
272 asio::system_timer timer(_stream.get_executor());
273
274 // Prepare done handler
275 auto async_done_handler = [&](asio::error_code ec)
276 {
277 std::unique_lock<std::mutex> lck(mtx);
278 if (done++ == 0)
279 {
280 error = ec;
281 socket().cancel();
282 timer.cancel();
283 }
284 cv.notify_one();
285 };
286
287 // Async wait for timeout
288 timer.expires_after(timeout.chrono());
289 timer.async_wait([&](const asio::error_code& ec) { async_done_handler(ec ? ec : asio::error::timed_out); });
290
291 // Async write some data to the client
292 size_t sent = 0;
293 _stream.async_write_some(asio::buffer(buffer, size), [&](std::error_code ec, size_t write) { async_done_handler(ec); sent = write; });
294
295 // Wait for complete or timeout
296 std::unique_lock<std::mutex> lck(mtx);
297 cv.wait(lck, [&]() { return done == 2; });
298
299 // Send data to the client
300 if (sent > 0)
301 {
302 // Update statistic
303 _bytes_sent += sent;
304 _server->_bytes_sent += sent;
305
306 // Call the buffer sent handler
307 onSent(sent, bytes_pending());
308 }
309
310 // Disconnect on error
311 if (error && (error != asio::error::timed_out))
312 {
313 SendError(error);
314 Disconnect(error);
315 }
316
317 return sent;
318}
319
320bool SSLSession::SendAsync(const void* buffer, size_t size)
321{
322 if (!IsHandshaked())
323 return false;
324
325 if (size == 0)
326 return true;
327
328 assert((buffer != nullptr) && "Pointer to the buffer should not be null!");
329 if (buffer == nullptr)
330 return false;
331
332 {
333 std::scoped_lock locker(_send_lock);
334
335 // Detect multiple send handlers
336 bool send_required = _send_buffer_main.empty() || _send_buffer_flush.empty();
337
338 // Check the send buffer limit
339 if (((_send_buffer_main.size() + size) > _send_buffer_limit) && (_send_buffer_limit > 0))
340 {
341 SendError(asio::error::no_buffer_space);
342 return false;
343 }
344
345 // Fill the main send buffer
346 const uint8_t* bytes = (const uint8_t*)buffer;
347 _send_buffer_main.insert(_send_buffer_main.end(), bytes, bytes + size);
348
349 // Update statistic
350 _bytes_pending = _send_buffer_main.size();
351
352 // Avoid multiple send handlers
353 if (!send_required)
354 return true;
355 }
356
357 // Dispatch the send handler
358 auto self(this->shared_from_this());
359 auto send_handler = [this, self]()
360 {
361 // Try to send the main buffer
362 TrySend();
363 };
364 if (_strand_required)
365 asio::dispatch(_strand, send_handler);
366 else
367 asio::dispatch(*_io_context, send_handler);
368
369 return true;
370}
371
372size_t SSLSession::Receive(void* buffer, size_t size)
373{
374 if (!IsHandshaked())
375 return 0;
376
377 if (size == 0)
378 return 0;
379
380 assert((buffer != nullptr) && "Pointer to the buffer should not be null!");
381 if (buffer == nullptr)
382 return 0;
383
384 asio::error_code ec;
385
386 // Receive data from the client
387 size_t received = _stream.read_some(asio::buffer(buffer, size), ec);
388 if (received > 0)
389 {
390 // Update statistic
391 _bytes_received += received;
392 _server->_bytes_received += received;
393
394 // Call the buffer received handler
395 onReceived(buffer, received);
396 }
397
398 // Disconnect on error
399 if (ec)
400 {
401 SendError(ec);
402 Disconnect(ec);
403 }
404
405 return received;
406}
407
408std::string SSLSession::Receive(size_t size)
409{
410 std::string text(size, 0);
411 text.resize(Receive(text.data(), text.size()));
412 return text;
413}
414
415size_t SSLSession::Receive(void* buffer, size_t size, const CppCommon::Timespan& timeout)
416{
417 if (!IsHandshaked())
418 return 0;
419
420 if (size == 0)
421 return 0;
422
423 assert((buffer != nullptr) && "Pointer to the buffer should not be null!");
424 if (buffer == nullptr)
425 return 0;
426
427 int done = 0;
428 std::mutex mtx;
429 std::condition_variable cv;
430 asio::error_code error;
431 asio::system_timer timer(_stream.get_executor());
432
433 // Prepare done handler
434 auto async_done_handler = [&](asio::error_code ec)
435 {
436 std::unique_lock<std::mutex> lck(mtx);
437 if (done++ == 0)
438 {
439 error = ec;
440 socket().cancel();
441 timer.cancel();
442 }
443 cv.notify_one();
444 };
445
446 // Async wait for timeout
447 timer.expires_after(timeout.chrono());
448 timer.async_wait([&](const asio::error_code& ec) { async_done_handler(ec ? ec : asio::error::timed_out); });
449
450 // Async read some data from the client
451 size_t received = 0;
452 _stream.async_read_some(asio::buffer(buffer, size), [&](std::error_code ec, size_t read) { async_done_handler(ec); received = read; });
453
454 // Wait for complete or timeout
455 std::unique_lock<std::mutex> lck(mtx);
456 cv.wait(lck, [&]() { return done == 2; });
457
458 // Received some data from the client
459 if (received > 0)
460 {
461 // Update statistic
462 _bytes_received += received;
463 _server->_bytes_received += received;
464
465 // Call the buffer received handler
466 onReceived(buffer, received);
467 }
468
469 // Disconnect on error
470 if (error && (error != asio::error::timed_out))
471 {
472 SendError(error);
473 Disconnect(error);
474 }
475
476 return received;
477}
478
479std::string SSLSession::Receive(size_t size, const CppCommon::Timespan& timeout)
480{
481 std::string text(size, 0);
482 text.resize(Receive(text.data(), text.size(), timeout));
483 return text;
484}
485
487{
488 // Try to receive data from the client
489 TryReceive();
490}
491
492void SSLSession::TryReceive()
493{
494 if (_receiving)
495 return;
496
497 if (!IsHandshaked())
498 return;
499
500 // Async receive with the receive handler
501 _receiving = true;
502 auto self(this->shared_from_this());
503 auto async_receive_handler = make_alloc_handler(_receive_storage, [this, self](std::error_code ec, size_t size)
504 {
505 _receiving = false;
506
507 if (!IsHandshaked())
508 return;
509
510 // Received some data from the client
511 if (size > 0)
512 {
513 // Update statistic
514 _bytes_received += size;
515 _server->_bytes_received += size;
516
517 // Call the buffer received handler
518 onReceived(_receive_buffer.data(), size);
519
520 // If the receive buffer is full increase its size
521 if (_receive_buffer.size() == size)
522 {
523 // Check the receive buffer limit
524 if (((2 * size) > _receive_buffer_limit) && (_receive_buffer_limit > 0))
525 {
526 SendError(asio::error::no_buffer_space);
527 Disconnect(asio::error::no_buffer_space);
528 return;
529 }
530
531 _receive_buffer.resize(2 * size);
532 }
533 }
534
535 // Try to receive again if the session is valid
536 if (!ec)
537 TryReceive();
538 else
539 {
540 SendError(ec);
541 Disconnect(ec);
542 }
543 });
544 if (_strand_required)
545 _stream.async_read_some(asio::buffer(_receive_buffer.data(), _receive_buffer.size()), bind_executor(_strand, async_receive_handler));
546 else
547 _stream.async_read_some(asio::buffer(_receive_buffer.data(), _receive_buffer.size()), async_receive_handler);
548}
549
550void SSLSession::TrySend()
551{
552 if (_sending)
553 return;
554
555 if (!IsHandshaked())
556 return;
557
558 // Swap send buffers
559 if (_send_buffer_flush.empty())
560 {
561 std::scoped_lock locker(_send_lock);
562
563 // Swap flush and main buffers
564 _send_buffer_flush.swap(_send_buffer_main);
565 _send_buffer_flush_offset = 0;
566
567 // Update statistic
568 _bytes_pending = 0;
569 _bytes_sending += _send_buffer_flush.size();
570 }
571
572 // Check if the flush buffer is empty
573 if (_send_buffer_flush.empty())
574 {
575 // Call the empty send buffer handler
576 onEmpty();
577 return;
578 }
579
580 // Async write with the write handler
581 _sending = true;
582 auto self(this->shared_from_this());
583 auto async_write_handler = make_alloc_handler(_send_storage, [this, self](std::error_code ec, size_t size)
584 {
585 _sending = false;
586
587 if (!IsHandshaked())
588 return;
589
590 // Send some data to the client
591 if (size > 0)
592 {
593 // Update statistic
594 _bytes_sending -= size;
595 _bytes_sent += size;
596 _server->_bytes_sent += size;
597
598 // Increase the flush buffer offset
599 _send_buffer_flush_offset += size;
600
601 // Successfully send the whole flush buffer
602 if (_send_buffer_flush_offset == _send_buffer_flush.size())
603 {
604 // Clear the flush buffer
605 _send_buffer_flush.clear();
606 _send_buffer_flush_offset = 0;
607 }
608
609 // Call the buffer sent handler
610 onSent(size, bytes_pending());
611 }
612
613 // Try to send again if the session is valid
614 if (!ec)
615 TrySend();
616 else
617 {
618 SendError(ec);
619 Disconnect(ec);
620 }
621 });
622 if (_strand_required)
623 _stream.async_write_some(asio::buffer(_send_buffer_flush.data() + _send_buffer_flush_offset, _send_buffer_flush.size() - _send_buffer_flush_offset), bind_executor(_strand, async_write_handler));
624 else
625 _stream.async_write_some(asio::buffer(_send_buffer_flush.data() + _send_buffer_flush_offset, _send_buffer_flush.size() - _send_buffer_flush_offset), async_write_handler);
626}
627
628void SSLSession::ClearBuffers()
629{
630 {
631 std::scoped_lock locker(_send_lock);
632
633 // Clear send buffers
634 _send_buffer_main.clear();
635 _send_buffer_flush.clear();
636 _send_buffer_flush_offset = 0;
637
638 // Update statistic
639 _bytes_pending = 0;
640 _bytes_sending = 0;
641 }
642}
643
644void SSLSession::ResetServer()
645{
646 // Reset cycle-reference to the server
647 _server.reset();
648}
649
650void SSLSession::SendError(std::error_code ec)
651{
652 // Skip Asio disconnect errors
653 if ((ec == asio::error::connection_aborted) ||
654 (ec == asio::error::connection_refused) ||
655 (ec == asio::error::connection_reset) ||
656 (ec == asio::error::eof) ||
657 (ec == asio::error::operation_aborted))
658 return;
659
660 // Skip OpenSSL annoying errors
661 if (ec == asio::ssl::error::stream_truncated)
662 return;
663 if (ec.category() == asio::error::get_ssl_category())
664 {
665 if ((ERR_GET_REASON(ec.value()) == SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC) ||
666 (ERR_GET_REASON(ec.value()) == SSL_R_PROTOCOL_IS_SHUTDOWN) ||
667 (ERR_GET_REASON(ec.value()) == SSL_R_WRONG_VERSION_NUMBER))
668 return;
669 }
670
671 onError(ec.value(), ec.category().name(), ec.message());
672}
673
674} // namespace Asio
675} // namespace CppServer
uint64_t bytes_pending() const noexcept
Get the number of bytes pending sent by the session.
Definition ssl_session.h:59
virtual void onSent(size_t sent, size_t pending)
Handle buffer sent notification.
virtual bool SendAsync(const void *buffer, size_t size)
Send data to the client (asynchronous).
asio::ssl::stream< asio::ip::tcp::socket >::next_layer_type & socket() noexcept
Get the session socket.
Definition ssl_session.h:56
void SetupReceiveBufferSize(size_t size)
Setup option: receive buffer size.
void SetupSendBufferSize(size_t size)
Setup option: send buffer size.
virtual void ReceiveAsync()
Receive data from the client (asynchronous).
virtual void onDisconnecting()
Handle session disconnecting notification.
virtual bool Disconnect()
Disconnect the session.
Definition ssl_session.h:83
virtual void onConnected()
Handle session connected notification.
virtual void onReceived(const void *buffer, size_t size)
Handle buffer received notification.
std::shared_ptr< SSLServer > & server() noexcept
Get the server.
Definition ssl_session.h:48
virtual void onConnecting()
Handle session connecting notification.
virtual void onHandshaking()
Handle session handshaking notification.
size_t option_send_buffer_size() const
Get the option: send buffer size.
virtual size_t Send(const void *buffer, size_t size)
Send data to the client (synchronous).
SSLSession(const std::shared_ptr< SSLServer > &server)
Initialize the session with a given server.
virtual void onEmpty()
Handle empty send buffer notification.
bool IsConnected() const noexcept
Is the session connected?
Definition ssl_session.h:75
virtual void onError(int error, const std::string &category, const std::string &message)
Handle error notification.
bool IsHandshaked() const noexcept
Is the session handshaked?
Definition ssl_session.h:77
virtual size_t Receive(void *buffer, size_t size)
Receive data from the client (synchronous).
virtual void onHandshaked()
Handle session handshaked notification.
virtual void onDisconnected()
Handle session disconnected notification.
size_t option_receive_buffer_size() const
Get the option: receive buffer size.
Asio definitions.
AllocateHandler< THandler > make_alloc_handler(HandlerStorage &storage, THandler handler)
Helper function to wrap a handler object to add custom allocation.
Definition memory.inl:39
C++ Server project definitions.
Definition asio.h:56
SSL server definition.
SSL session definition.