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