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