CppServer  1.0.4.0
C++ Server Library
ssl_session.cpp
Go to the documentation of this file.
1 
10 #include "server/asio/ssl_server.h"
11 
12 namespace CppServer {
13 namespace Asio {
14 
15 SSLSession::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 
60 void 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
84  onConnected();
85 
86  // Call the session connected handler in the server
87  auto connected_session(this->shared_from_this());
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
109  auto handshaked_session(this->shared_from_this());
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 
129 void 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
151  onDisconnected();
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 
169 bool 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 
211 size_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
234  onSent(sent, bytes_pending());
235  }
236 
237  // Disconnect on error
238  if (ec)
239  {
240  SendError(ec);
241  Disconnect(ec);
242  }
243 
244  return sent;
245 }
246 
247 size_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
298  onSent(sent, bytes_pending());
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 
311 bool 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 
363 size_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
386  onReceived(buffer, received);
387  }
388 
389  // Disconnect on error
390  if (ec)
391  {
392  SendError(ec);
393  Disconnect(ec);
394  }
395 
396  return received;
397 }
398 
399 std::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 
406 size_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
457  onReceived(buffer, received);
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 
470 std::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 
483 void 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 
541 void 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 
619 void 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 
635 void SSLSession::ResetServer()
636 {
637  // Reset cycle-reference to the server
638  _server.reset();
639 }
640 
641 void 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
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.
Definition: ssl_session.h:221
virtual bool SendAsync(const void *buffer, size_t size)
Send data to the client (asynchronous)
void SetupReceiveBufferSize(size_t size)
Setup option: receive buffer size.
Definition: ssl_session.cpp:48
void SetupSendBufferSize(size_t size)
Setup option: send buffer size.
Definition: ssl_session.cpp:54
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.
Definition: ssl_session.h:195
virtual void onReceived(const void *buffer, size_t size)
Handle buffer received notification.
Definition: ssl_session.h:209
asio::ssl::stream< asio::ip::tcp::socket >::next_layer_type & socket() noexcept
Get the session socket.
Definition: ssl_session.h:56
size_t option_send_buffer_size() const
Get the option: send buffer size.
Definition: ssl_session.cpp:41
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.
Definition: ssl_session.cpp:15
virtual void onEmpty()
Handle empty send buffer notification.
Definition: ssl_session.h:230
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.
Definition: ssl_session.h:238
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.
Definition: ssl_session.h:197
virtual void onDisconnected()
Handle session disconnected notification.
Definition: ssl_session.h:199
size_t option_receive_buffer_size() const
Get the option: receive buffer size.
Definition: ssl_session.cpp:34
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.