CppServer  1.0.4.0
C++ Server Library
ssl_client.cpp
Go to the documentation of this file.
1 
10 
11 namespace CppServer {
12 namespace Asio {
13 
14 SSLClient::SSLClient(const std::shared_ptr<Service>& service, const std::shared_ptr<SSLContext>& context, const std::string& address, int port)
15  : _id(CppCommon::UUID::Sequential()),
16  _service(service),
17  _io_service(_service->GetAsioService()),
18  _strand(*_io_service),
19  _strand_required(_service->IsStrandRequired()),
20  _address(address),
21  _port(port),
22  _context(context),
23  _stream(*_io_service, *_context),
24  _resolving(false),
25  _connecting(false),
26  _connected(false),
27  _handshaking(false),
28  _handshaked(false),
29  _bytes_pending(0),
30  _bytes_sending(0),
31  _bytes_sent(0),
32  _bytes_received(0),
33  _receiving(false),
34  _sending(false),
35  _send_buffer_flush_offset(0),
36  _option_keep_alive(false),
37  _option_no_delay(false)
38 {
39  assert((service != nullptr) && "Asio service is invalid!");
40  if (service == nullptr)
41  throw CppCommon::ArgumentException("Asio service is invalid!");
42 
43  assert((context != nullptr) && "SSL context is invalid!");
44  if (context == nullptr)
45  throw CppCommon::ArgumentException("SSL context is invalid!");
46 }
47 
48 SSLClient::SSLClient(const std::shared_ptr<Service>& service, const std::shared_ptr<SSLContext>& context, const std::string& address, const std::string& scheme)
49  : _id(CppCommon::UUID::Sequential()),
50  _service(service),
51  _io_service(_service->GetAsioService()),
52  _strand(*_io_service),
53  _strand_required(_service->IsStrandRequired()),
54  _address(address),
55  _scheme(scheme),
56  _port(0),
57  _context(context),
58  _stream(*_io_service, *_context),
59  _resolving(false),
60  _connecting(false),
61  _connected(false),
62  _handshaking(false),
63  _handshaked(false),
64  _bytes_pending(0),
65  _bytes_sending(0),
66  _bytes_sent(0),
67  _bytes_received(0),
68  _receiving(false),
69  _sending(false),
70  _send_buffer_flush_offset(0),
71  _option_keep_alive(false),
72  _option_no_delay(false)
73 {
74  assert((service != nullptr) && "Asio service is invalid!");
75  if (service == nullptr)
76  throw CppCommon::ArgumentException("Asio service is invalid!");
77 
78  assert((context != nullptr) && "SSL context is invalid!");
79  if (context == nullptr)
80  throw CppCommon::ArgumentException("SSL context is invalid!");
81 }
82 
83 SSLClient::SSLClient(const std::shared_ptr<Service>& service, const std::shared_ptr<SSLContext>& context, const asio::ip::tcp::endpoint& endpoint)
84  : _id(CppCommon::UUID::Sequential()),
85  _service(service),
86  _io_service(_service->GetAsioService()),
87  _strand(*_io_service),
88  _strand_required(_service->IsStrandRequired()),
89  _address(endpoint.address().to_string()),
90  _port(endpoint.port()),
91  _context(context),
92  _endpoint(endpoint),
93  _stream(*_io_service, *_context),
94  _resolving(false),
95  _connecting(false),
96  _connected(false),
97  _handshaking(false),
98  _handshaked(false),
99  _bytes_pending(0),
100  _bytes_sending(0),
101  _bytes_sent(0),
102  _bytes_received(0),
103  _receiving(false),
104  _sending(false),
105  _send_buffer_flush_offset(0),
106  _option_keep_alive(false),
107  _option_no_delay(false)
108 {
109  assert((service != nullptr) && "Asio service is invalid!");
110  if (service == nullptr)
111  throw CppCommon::ArgumentException("Asio service is invalid!");
112 
113  assert((context != nullptr) && "SSL context is invalid!");
114  if (context == nullptr)
115  throw CppCommon::ArgumentException("SSL context is invalid!");
116 }
117 
119 {
120 }
121 
123 {
124  asio::socket_base::receive_buffer_size option;
125  _stream.next_layer().get_option(option);
126  return option.value();
127 }
128 
130 {
131  asio::socket_base::send_buffer_size option;
132  _stream.next_layer().get_option(option);
133  return option.value();
134 }
135 
137 {
138  asio::socket_base::receive_buffer_size option((int)size);
139  socket().set_option(option);
140 }
141 
143 {
144  asio::socket_base::send_buffer_size option((int)size);
145  socket().set_option(option);
146 }
147 
149 {
150  if (IsConnected() || IsHandshaked() || _resolving || _connecting || _handshaking)
151  return false;
152 
153  // Create a new SSL stream
154  _stream = asio::ssl::stream<asio::ip::tcp::socket>(*_io_service, *_context);
155 
156  asio::error_code ec;
157 
158  // Create the server endpoint
159  _endpoint = asio::ip::tcp::endpoint(asio::ip::make_address(_address), (unsigned short)_port);
160 
161  // Connect to the server
162  socket().connect(_endpoint, ec);
163 
164  // Disconnect on error
165  if (ec)
166  {
167  SendError(ec);
168 
169  // Call the client disconnected handler
170  onDisconnected();
171 
172  return false;
173  }
174 
175  // Apply the option: keep alive
176  if (option_keep_alive())
177  socket().set_option(asio::ip::tcp::socket::keep_alive(true));
178  // Apply the option: no delay
179  if (option_no_delay())
180  socket().set_option(asio::ip::tcp::no_delay(true));
181 
182  // Prepare receive & send buffers
183  _receive_buffer.resize(option_receive_buffer_size());
184  _send_buffer_main.reserve(option_send_buffer_size());
185  _send_buffer_flush.reserve(option_send_buffer_size());
186 
187  // Reset statistic
188  _bytes_pending = 0;
189  _bytes_sending = 0;
190  _bytes_sent = 0;
191  _bytes_received = 0;
192 
193  // Update the connected flag
194  _connected = true;
195 
196  // Call the client connected handler
197  onConnected();
198 
199  // SSL handshake
200  _stream.handshake(asio::ssl::stream_base::client, ec);
201 
202  // Disconnect on error
203  if (ec)
204  {
205  // Disconnect in case of the bad handshake
206  SendError(ec);
207  Disconnect();
208  return false;
209  }
210 
211  // Update the handshaked flag
212  _handshaked = true;
213 
214  // Call the client handshaked handler
215  onHandshaked();
216 
217  // Call the empty send buffer handler
218  if (_send_buffer_main.empty())
219  onEmpty();
220 
221  return true;
222 }
223 
224 bool SSLClient::Connect(const std::shared_ptr<TCPResolver>& resolver)
225 {
226  if (IsConnected() || IsHandshaked() || _resolving || _connecting || _handshaking)
227  return false;
228 
229  // Create a new SSL stream
230  _stream = asio::ssl::stream<asio::ip::tcp::socket>(*_io_service, *_context);
231 
232  asio::error_code ec;
233 
234  // Resolve the server endpoint
235  asio::ip::tcp::resolver::query query(_address, (_scheme.empty() ? std::to_string(_port) : _scheme));
236  auto endpoints = resolver->resolver().resolve(query, ec);
237 
238  // Disconnect on error
239  if (ec)
240  {
241  SendError(ec);
242 
243  // Call the client disconnected handler
244  onDisconnected();
245 
246  return false;
247  }
248 
249  // Connect to the server
250  _endpoint = asio::connect(socket(), endpoints, ec);
251 
252  // Disconnect on error
253  if (ec)
254  {
255  SendError(ec);
256 
257  // Call the client disconnected handler
258  onDisconnected();
259 
260  return false;
261  }
262 
263  // Apply the option: keep alive
264  if (option_keep_alive())
265  socket().set_option(asio::ip::tcp::socket::keep_alive(true));
266  // Apply the option: no delay
267  if (option_no_delay())
268  socket().set_option(asio::ip::tcp::no_delay(true));
269 
270  // Prepare receive & send buffers
271  _receive_buffer.resize(option_receive_buffer_size());
272  _send_buffer_main.reserve(option_send_buffer_size());
273  _send_buffer_flush.reserve(option_send_buffer_size());
274 
275  // Reset statistic
276  _bytes_pending = 0;
277  _bytes_sending = 0;
278  _bytes_sent = 0;
279  _bytes_received = 0;
280 
281  // Update the connected flag
282  _connected = true;
283 
284  // Call the client connected handler
285  onConnected();
286 
287  // SSL handshake
288  _stream.handshake(asio::ssl::stream_base::client, ec);
289 
290  // Disconnect on error
291  if (ec)
292  {
293  // Disconnect in case of the bad handshake
294  SendError(ec);
295  Disconnect();
296  return false;
297  }
298 
299  // Update the handshaked flag
300  _handshaked = true;
301 
302  // Call the client handshaked handler
303  onHandshaked();
304 
305  // Call the empty send buffer handler
306  if (_send_buffer_main.empty())
307  onEmpty();
308 
309  return true;
310 }
311 
312 bool SSLClient::DisconnectInternal()
313 {
314  if (!IsConnected() || _resolving || _connecting || _handshaking)
315  return false;
316 
317  auto self(this->shared_from_this());
318 
319  // Close the client socket
320  socket().close();
321 
322  // Update the handshaked flag
323  _handshaking = false;
324  _handshaked = false;
325 
326  // Update the connected flag
327  _resolving = false;
328  _connecting = false;
329  _connected = false;
330 
331  // Update sending/receiving flags
332  _receiving = false;
333  _sending = false;
334 
335  // Clear send/receive buffers
336  ClearBuffers();
337 
338  // Call the client disconnected handler
339  onDisconnected();
340 
341  return true;
342 }
343 
345 {
346  if (!Disconnect())
347  return false;
348 
349  return Connect();
350 }
351 
353 {
354  if (IsConnected() || IsHandshaked() || _resolving || _connecting || _handshaking)
355  return false;
356 
357  // Post the connect handler
358  auto self(this->shared_from_this());
359  auto connect_handler = make_alloc_handler(_connect_storage, [this, self]()
360  {
361  if (IsConnected() || IsHandshaked() || _resolving || _connecting || _handshaking)
362  return;
363 
364  _connecting = true;
365 
366  // Create a new SSL stream
367  _stream = asio::ssl::stream<asio::ip::tcp::socket>(*_io_service, *_context);
368 
369  // Async connect with the connect handler
370  auto async_connect_handler = make_alloc_handler(_connect_storage, [this, self](std::error_code ec1)
371  {
372  _connecting = false;
373 
374  if (IsConnected() || IsHandshaked() || _resolving || _connecting || _handshaking)
375  return;
376 
377  if (!ec1)
378  {
379  // Apply the option: keep alive
380  if (option_keep_alive())
381  socket().set_option(asio::ip::tcp::socket::keep_alive(true));
382  // Apply the option: no delay
383  if (option_no_delay())
384  socket().set_option(asio::ip::tcp::no_delay(true));
385 
386  // Prepare receive & send buffers
387  _receive_buffer.resize(option_receive_buffer_size());
388  _send_buffer_main.reserve(option_send_buffer_size());
389  _send_buffer_flush.reserve(option_send_buffer_size());
390 
391  // Reset statistic
392  _bytes_pending = 0;
393  _bytes_sending = 0;
394  _bytes_sent = 0;
395  _bytes_received = 0;
396 
397  // Update the connected flag
398  _connected = true;
399 
400  // Call the client connected handler
401  onConnected();
402 
403  // Async SSL handshake with the handshake handler
404  _handshaking = true;
405  auto async_handshake_handler = make_alloc_handler(_connect_storage, [this, self](std::error_code ec2)
406  {
407  _handshaking = false;
408 
409  if (IsHandshaked())
410  return;
411 
412  if (!ec2)
413  {
414  // Update the handshaked flag
415  _handshaked = true;
416 
417  // Try to receive something from the server
418  TryReceive();
419 
420  // Call the client handshaked handler
421  onHandshaked();
422 
423  // Call the empty send buffer handler
424  if (_send_buffer_main.empty())
425  onEmpty();
426  }
427  else
428  {
429  // Disconnect in case of the bad handshake
430  SendError(ec2);
431  DisconnectInternalAsync(true);
432  }
433  });
434  if (_strand_required)
435  _stream.async_handshake(asio::ssl::stream_base::client, bind_executor(_strand, async_handshake_handler));
436  else
437  _stream.async_handshake(asio::ssl::stream_base::client, async_handshake_handler);
438  }
439  else
440  {
441  SendError(ec1);
442 
443  // Call the client disconnected handler
444  onDisconnected();
445  }
446  });
447 
448  // Create the server endpoint
449  _endpoint = asio::ip::tcp::endpoint(asio::ip::make_address(_address), (unsigned short)_port);
450 
451  if (_strand_required)
452  socket().async_connect(_endpoint, bind_executor(_strand, async_connect_handler));
453  else
454  socket().async_connect(_endpoint, async_connect_handler);
455  });
456  if (_strand_required)
457  _strand.post(connect_handler);
458  else
459  _io_service->post(connect_handler);
460 
461  return true;
462 }
463 
464 bool SSLClient::ConnectAsync(const std::shared_ptr<TCPResolver>& resolver)
465 {
466  if (IsConnected() || IsHandshaked() || _resolving || _connecting || _handshaking)
467  return false;
468 
469  // Post the connect handler
470  auto self(this->shared_from_this());
471  auto connect_handler = make_alloc_handler(_connect_storage, [this, self, resolver]()
472  {
473  if (IsConnected() || IsHandshaked() || _resolving || _connecting || _handshaking)
474  return;
475 
476  _resolving = true;
477 
478  // Create a new SSL stream
479  _stream = asio::ssl::stream<asio::ip::tcp::socket>(*_io_service, *_context);
480 
481  // Async resolve with the resolve handler
482  auto async_resolve_handler = make_alloc_handler(_connect_storage, [this, self](std::error_code ec1, asio::ip::tcp::resolver::results_type endpoints)
483  {
484  _resolving = false;
485 
486  if (IsConnected() || IsHandshaked() || _resolving || _connecting || _handshaking)
487  return;
488 
489  if (!ec1)
490  {
491  // Async connect with the connect handler
492  _connecting = true;
493  auto async_connect_handler = make_alloc_handler(_connect_storage, [this, self](std::error_code ec2, const asio::ip::tcp::endpoint& endpoint)
494  {
495  _connecting = false;
496 
497  if (IsConnected() || IsHandshaked() || _resolving || _connecting || _handshaking)
498  return;
499 
500  if (!ec2)
501  {
502  // Connect to the server
503  _endpoint = endpoint;
504 
505  // Apply the option: keep alive
506  if (option_keep_alive())
507  socket().set_option(asio::ip::tcp::socket::keep_alive(true));
508  // Apply the option: no delay
509  if (option_no_delay())
510  socket().set_option(asio::ip::tcp::no_delay(true));
511 
512  // Prepare receive & send buffers
513  _receive_buffer.resize(option_receive_buffer_size());
514  _send_buffer_main.reserve(option_send_buffer_size());
515  _send_buffer_flush.reserve(option_send_buffer_size());
516 
517  // Reset statistic
518  _bytes_pending = 0;
519  _bytes_sending = 0;
520  _bytes_sent = 0;
521  _bytes_received = 0;
522 
523  // Update the connected flag
524  _connected = true;
525 
526  // Call the client connected handler
527  onConnected();
528 
529  // Async SSL handshake with the handshake handler
530  _handshaking = true;
531  auto async_handshake_handler = make_alloc_handler(_connect_storage, [this, self](std::error_code ec3)
532  {
533  _handshaking = false;
534 
535  if (IsHandshaked())
536  return;
537 
538  if (!ec3)
539  {
540  // Update the handshaked flag
541  _handshaked = true;
542 
543  // Try to receive something from the server
544  TryReceive();
545 
546  // Call the client handshaked handler
547  onHandshaked();
548 
549  // Call the empty send buffer handler
550  if (_send_buffer_main.empty())
551  onEmpty();
552  }
553  else
554  {
555  // Disconnect in case of the bad handshake
556  SendError(ec3);
557  DisconnectInternalAsync(true);
558  }
559  });
560  if (_strand_required)
561  _stream.async_handshake(asio::ssl::stream_base::client, bind_executor(_strand, async_handshake_handler));
562  else
563  _stream.async_handshake(asio::ssl::stream_base::client, async_handshake_handler);
564  }
565  else
566  {
567  SendError(ec2);
568 
569  // Call the client disconnected handler
570  onDisconnected();
571  }
572  });
573  if (_strand_required)
574  asio::async_connect(socket(), endpoints, bind_executor(_strand, async_connect_handler));
575  else
576  asio::async_connect(socket(), endpoints, async_connect_handler);
577  }
578  else
579  {
580  SendError(ec1);
581 
582  // Call the client disconnected handler
583  onDisconnected();
584  }
585  });
586 
587  // Resolve the server endpoint
588  asio::ip::tcp::resolver::query query(_address, (_scheme.empty() ? std::to_string(_port) : _scheme));
589  if (_strand_required)
590  resolver->resolver().async_resolve(query, bind_executor(_strand, async_resolve_handler));
591  else
592  resolver->resolver().async_resolve(query, async_resolve_handler);
593  });
594  if (_strand_required)
595  _strand.post(connect_handler);
596  else
597  _io_service->post(connect_handler);
598 
599  return true;
600 }
601 
602 bool SSLClient::DisconnectInternalAsync(bool dispatch)
603 {
604  if (!IsConnected() || _resolving || _connecting || _handshaking)
605  return false;
606 
607  // Dispatch or post the disconnect handler
608  auto self(this->shared_from_this());
609  auto disconnect_handler = make_alloc_handler(_connect_storage, [this, self]()
610  {
611  if (!IsConnected() || _resolving || _connecting || _handshaking)
612  return;
613 
614  asio::error_code ec;
615 
616  // Cancel the client socket
617  socket().cancel(ec);
618 
619  // Async SSL shutdown with the shutdown handler
620  auto async_shutdown_handler = make_alloc_handler(_connect_storage, [this, self](std::error_code ec2) { DisconnectInternal(); });
621  if (_strand_required)
622  _stream.async_shutdown(bind_executor(_strand, async_shutdown_handler));
623  else
624  _stream.async_shutdown(async_shutdown_handler);
625  });
626  if (_strand_required)
627  {
628  if (dispatch)
629  _strand.dispatch(disconnect_handler);
630  else
631  _strand.post(disconnect_handler);
632  }
633  else
634  {
635  if (dispatch)
636  _io_service->dispatch(disconnect_handler);
637  else
638  _io_service->post(disconnect_handler);
639  }
640 
641  return true;
642 }
643 
645 {
646  if (!DisconnectAsync())
647  return false;
648 
649  while (IsConnected())
650  CppCommon::Thread::Yield();
651 
652  return ConnectAsync();
653 }
654 
655 size_t SSLClient::Send(const void* buffer, size_t size)
656 {
657  if (!IsHandshaked())
658  return 0;
659 
660  if (size == 0)
661  return 0;
662 
663  assert((buffer != nullptr) && "Pointer to the buffer should not be null!");
664  if (buffer == nullptr)
665  return 0;
666 
667  asio::error_code ec;
668 
669  // Send data to the server
670  size_t sent = asio::write(_stream, asio::buffer(buffer, size), ec);
671  if (sent > 0)
672  {
673  // Update statistic
674  _bytes_sent += sent;
675 
676  // Call the buffer sent handler
677  onSent(sent, bytes_pending());
678  }
679 
680  // Disconnect on error
681  if (ec)
682  {
683  SendError(ec);
684  Disconnect();
685  }
686 
687  return sent;
688 }
689 
690 size_t SSLClient::Send(const void* buffer, size_t size, const CppCommon::Timespan& timeout)
691 {
692  if (!IsHandshaked())
693  return 0;
694 
695  if (size == 0)
696  return 0;
697 
698  assert((buffer != nullptr) && "Pointer to the buffer should not be null!");
699  if (buffer == nullptr)
700  return 0;
701 
702  int done = 0;
703  std::mutex mtx;
704  std::condition_variable cv;
705  asio::error_code error;
706  asio::system_timer timer(_stream.get_executor());
707 
708  // Prepare done handler
709  auto async_done_handler = [&](asio::error_code ec)
710  {
711  std::unique_lock<std::mutex> lck(mtx);
712  if (done++ == 0)
713  {
714  error = ec;
715  socket().cancel();
716  timer.cancel();
717  }
718  cv.notify_one();
719  };
720 
721  // Async wait for timeout
722  timer.expires_from_now(timeout.chrono());
723  timer.async_wait([&](const asio::error_code& ec) { async_done_handler(ec ? ec : asio::error::timed_out); });
724 
725  // Async write some data to the server
726  size_t sent = 0;
727  _stream.async_write_some(asio::buffer(buffer, size), [&](std::error_code ec, size_t write) { async_done_handler(ec); sent = write; });
728 
729  // Wait for complete or timeout
730  std::unique_lock<std::mutex> lck(mtx);
731  cv.wait(lck, [&]() { return done == 2; });
732 
733  // Send data to the server
734  if (sent > 0)
735  {
736  // Update statistic
737  _bytes_sent += sent;
738 
739  // Call the buffer sent handler
740  onSent(sent, bytes_pending());
741  }
742 
743  // Disconnect on error
744  if (error && (error != asio::error::timed_out))
745  {
746  SendError(error);
747  Disconnect();
748  }
749 
750  return sent;
751 }
752 
753 bool SSLClient::SendAsync(const void* buffer, size_t size)
754 {
755  if (!IsHandshaked())
756  return false;
757 
758  if (size == 0)
759  return true;
760 
761  assert((buffer != nullptr) && "Pointer to the buffer should not be null!");
762  if (buffer == nullptr)
763  return false;
764 
765  {
766  std::scoped_lock locker(_send_lock);
767 
768  // Detect multiple send handlers
769  bool send_required = _send_buffer_main.empty() || _send_buffer_flush.empty();
770 
771  // Check the send buffer limit
772  if (((_send_buffer_main.size() + size) > _send_buffer_limit) && (_send_buffer_limit > 0))
773  {
774  SendError(asio::error::no_buffer_space);
775  return false;
776  }
777 
778  // Fill the main send buffer
779  const uint8_t* bytes = (const uint8_t*)buffer;
780  _send_buffer_main.insert(_send_buffer_main.end(), bytes, bytes + size);
781 
782  // Update statistic
783  _bytes_pending = _send_buffer_main.size();
784 
785  // Avoid multiple send handlers
786  if (!send_required)
787  return true;
788  }
789 
790  // Dispatch the send handler
791  auto self(this->shared_from_this());
792  auto send_handler = [this, self]()
793  {
794  // Try to send the main buffer
795  TrySend();
796  };
797  if (_strand_required)
798  _strand.dispatch(send_handler);
799  else
800  _io_service->dispatch(send_handler);
801 
802  return true;
803 }
804 
805 size_t SSLClient::Receive(void* buffer, size_t size)
806 {
807  if (!IsHandshaked())
808  return 0;
809 
810  if (size == 0)
811  return 0;
812 
813  assert((buffer != nullptr) && "Pointer to the buffer should not be null!");
814  if (buffer == nullptr)
815  return 0;
816 
817  asio::error_code ec;
818 
819  // Receive data from the server
820  size_t received = _stream.read_some(asio::buffer(buffer, size), ec);
821  if (received > 0)
822  {
823  // Update statistic
824  _bytes_received += received;
825 
826  // Call the buffer received handler
827  onReceived(buffer, received);
828  }
829 
830  // Disconnect on error
831  if (ec)
832  {
833  SendError(ec);
834  Disconnect();
835  }
836 
837  return received;
838 }
839 
840 std::string SSLClient::Receive(size_t size)
841 {
842  std::string text(size, 0);
843  text.resize(Receive(text.data(), text.size()));
844  return text;
845 }
846 
847 size_t SSLClient::Receive(void* buffer, size_t size, const CppCommon::Timespan& timeout)
848 {
849  if (!IsHandshaked())
850  return 0;
851 
852  if (size == 0)
853  return 0;
854 
855  assert((buffer != nullptr) && "Pointer to the buffer should not be null!");
856  if (buffer == nullptr)
857  return 0;
858 
859  int done = 0;
860  std::mutex mtx;
861  std::condition_variable cv;
862  asio::error_code error;
863  asio::system_timer timer(_stream.get_executor());
864 
865  // Prepare done handler
866  auto async_done_handler = [&](asio::error_code ec)
867  {
868  std::unique_lock<std::mutex> lck(mtx);
869  if (done++ == 0)
870  {
871  error = ec;
872  socket().cancel();
873  timer.cancel();
874  }
875  cv.notify_one();
876  };
877 
878  // Async wait for timeout
879  timer.expires_from_now(timeout.chrono());
880  timer.async_wait([&](const asio::error_code& ec) { async_done_handler(ec ? ec : asio::error::timed_out); });
881 
882  // Async read some data from the server
883  size_t received = 0;
884  _stream.async_read_some(asio::buffer(buffer, size), [&](std::error_code ec, size_t read) { async_done_handler(ec); received = read; });
885 
886  // Wait for complete or timeout
887  std::unique_lock<std::mutex> lck(mtx);
888  cv.wait(lck, [&]() { return done == 2; });
889 
890  // Received some data from the server
891  if (received > 0)
892  {
893  // Update statistic
894  _bytes_received += received;
895 
896  // Call the buffer received handler
897  onReceived(buffer, received);
898  }
899 
900  // Disconnect on error
901  if (error && (error != asio::error::timed_out))
902  {
903  SendError(error);
904  Disconnect();
905  }
906 
907  return received;
908 }
909 
910 std::string SSLClient::Receive(size_t size, const CppCommon::Timespan& timeout)
911 {
912  std::string text(size, 0);
913  text.resize(Receive(text.data(), text.size(), timeout));
914  return text;
915 }
916 
918 {
919  // Try to receive data from the server
920  TryReceive();
921 }
922 
923 void SSLClient::TryReceive()
924 {
925  if (_receiving)
926  return;
927 
928  if (!IsHandshaked())
929  return;
930 
931  // Async receive with the receive handler
932  _receiving = true;
933  auto self(this->shared_from_this());
934  auto async_receive_handler = make_alloc_handler(_receive_storage, [this, self](std::error_code ec, size_t size)
935  {
936  _receiving = false;
937 
938  if (!IsHandshaked())
939  return;
940 
941  // Received some data from the server
942  if (size > 0)
943  {
944  // Update statistic
945  _bytes_received += size;
946 
947  // Call the buffer received handler
948  onReceived(_receive_buffer.data(), size);
949 
950  // If the receive buffer is full increase its size
951  if (_receive_buffer.size() == size)
952  {
953  // Check the receive buffer limit
954  if (((2 * size) > _receive_buffer_limit) && (_receive_buffer_limit > 0))
955  {
956  SendError(asio::error::no_buffer_space);
957  DisconnectInternalAsync(true);
958  return;
959  }
960 
961  _receive_buffer.resize(2 * size);
962  }
963  }
964 
965  // Try to receive again if the session is valid
966  if (!ec)
967  TryReceive();
968  else
969  {
970  SendError(ec);
971  DisconnectInternalAsync(true);
972  }
973  });
974  if (_strand_required)
975  _stream.async_read_some(asio::buffer(_receive_buffer.data(), _receive_buffer.size()), bind_executor(_strand, async_receive_handler));
976  else
977  _stream.async_read_some(asio::buffer(_receive_buffer.data(), _receive_buffer.size()), async_receive_handler);
978 }
979 
980 void SSLClient::TrySend()
981 {
982  if (_sending)
983  return;
984 
985  if (!IsHandshaked())
986  return;
987 
988  // Swap send buffers
989  if (_send_buffer_flush.empty())
990  {
991  std::scoped_lock locker(_send_lock);
992 
993  // Swap flush and main buffers
994  _send_buffer_flush.swap(_send_buffer_main);
995  _send_buffer_flush_offset = 0;
996 
997  // Update statistic
998  _bytes_pending = 0;
999  _bytes_sending += _send_buffer_flush.size();
1000  }
1001 
1002  // Check if the flush buffer is empty
1003  if (_send_buffer_flush.empty())
1004  {
1005  // Call the empty send buffer handler
1006  onEmpty();
1007  return;
1008  }
1009 
1010  // Async write with the write handler
1011  _sending = true;
1012  auto self(this->shared_from_this());
1013  auto async_write_handler = make_alloc_handler(_send_storage, [this, self](std::error_code ec, size_t size)
1014  {
1015  _sending = false;
1016 
1017  if (!IsHandshaked())
1018  return;
1019 
1020  // Send some data to the server
1021  if (size > 0)
1022  {
1023  // Update statistic
1024  _bytes_sending -= size;
1025  _bytes_sent += size;
1026 
1027  // Increase the flush buffer offset
1028  _send_buffer_flush_offset += size;
1029 
1030  // Successfully send the whole flush buffer
1031  if (_send_buffer_flush_offset == _send_buffer_flush.size())
1032  {
1033  // Clear the flush buffer
1034  _send_buffer_flush.clear();
1035  _send_buffer_flush_offset = 0;
1036  }
1037 
1038  // Call the buffer sent handler
1039  onSent(size, bytes_pending());
1040  }
1041 
1042  // Try to send again if the session is valid
1043  if (!ec)
1044  TrySend();
1045  else
1046  {
1047  SendError(ec);
1048  DisconnectInternalAsync(true);
1049  }
1050  });
1051  if (_strand_required)
1052  _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));
1053  else
1054  _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);
1055 }
1056 
1057 void SSLClient::ClearBuffers()
1058 {
1059  {
1060  std::scoped_lock locker(_send_lock);
1061 
1062  // Clear send buffers
1063  _send_buffer_main.clear();
1064  _send_buffer_flush.clear();
1065  _send_buffer_flush_offset = 0;
1066 
1067  // Update statistic
1068  _bytes_pending = 0;
1069  _bytes_sending = 0;
1070  }
1071 }
1072 
1073 void SSLClient::SendError(std::error_code ec)
1074 {
1075  // Skip Asio disconnect errors
1076  if ((ec == asio::error::connection_aborted) ||
1077  (ec == asio::error::connection_refused) ||
1078  (ec == asio::error::connection_reset) ||
1079  (ec == asio::error::eof) ||
1080  (ec == asio::error::operation_aborted))
1081  return;
1082 
1083  // Skip OpenSSL annoying errors
1084  if (ec == asio::ssl::error::stream_truncated)
1085  return;
1086  if (ec.category() == asio::error::get_ssl_category())
1087  {
1088  if ((ERR_GET_REASON(ec.value()) == SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC) ||
1089  (ERR_GET_REASON(ec.value()) == SSL_R_PROTOCOL_IS_SHUTDOWN) ||
1090  (ERR_GET_REASON(ec.value()) == SSL_R_WRONG_VERSION_NUMBER))
1091  return;
1092  }
1093 
1094  onError(ec.value(), ec.category().name(), ec.message());
1095 }
1096 
1097 } // namespace Asio
1098 } // namespace CppServer
bool option_keep_alive() const noexcept
Get the option: keep alive.
Definition: ssl_client.h:96
virtual size_t Send(const void *buffer, size_t size)
Send data to the server (synchronous)
Definition: ssl_client.cpp:655
virtual void onError(int error, const std::string &category, const std::string &message)
Handle error notification.
Definition: ssl_client.h:332
SSLClient(const std::shared_ptr< Service > &service, const std::shared_ptr< SSLContext > &context, const std::string &address, int port)
Initialize SSL client with a given Asio service, SSL context, server address and port number.
Definition: ssl_client.cpp:14
virtual void onSent(size_t sent, size_t pending)
Handle buffer sent notification.
Definition: ssl_client.h:315
virtual void ReceiveAsync()
Receive data from the server (asynchronous)
Definition: ssl_client.cpp:917
bool IsHandshaked() const noexcept
Is the session handshaked?
Definition: ssl_client.h:111
virtual void onHandshaked()
Handle session handshaked notification.
Definition: ssl_client.h:291
std::shared_ptr< SSLContext > & context() noexcept
Get the client SSL context.
Definition: ssl_client.h:73
bool IsConnected() const noexcept
Is the client connected?
Definition: ssl_client.h:109
virtual void onEmpty()
Handle empty send buffer notification.
Definition: ssl_client.h:324
std::shared_ptr< Service > & service() noexcept
Get the Asio service.
Definition: ssl_client.h:67
virtual bool SendAsync(const void *buffer, size_t size)
Send data to the server (asynchronous)
Definition: ssl_client.cpp:753
virtual bool ConnectAsync()
Connect the client (asynchronous)
Definition: ssl_client.cpp:352
virtual bool DisconnectAsync()
Disconnect the client (asynchronous)
Definition: ssl_client.h:156
virtual void onDisconnected()
Handle client disconnected notification.
Definition: ssl_client.h:293
virtual size_t Receive(void *buffer, size_t size)
Receive data from the server (synchronous)
Definition: ssl_client.cpp:805
void SetupSendBufferSize(size_t size)
Setup option: send buffer size.
Definition: ssl_client.cpp:142
virtual bool ReconnectAsync()
Reconnect the client (asynchronous)
Definition: ssl_client.cpp:644
virtual bool Disconnect()
Disconnect the client (synchronous)
Definition: ssl_client.h:134
bool option_no_delay() const noexcept
Get the option: no delay.
Definition: ssl_client.h:98
void SetupReceiveBufferSize(size_t size)
Setup option: receive buffer size.
Definition: ssl_client.cpp:136
asio::ip::tcp::endpoint & endpoint() noexcept
Get the client endpoint.
Definition: ssl_client.h:75
virtual bool Reconnect()
Reconnect the client (synchronous)
Definition: ssl_client.cpp:344
virtual void onReceived(const void *buffer, size_t size)
Handle buffer received notification.
Definition: ssl_client.h:303
size_t option_receive_buffer_size() const
Get the option: receive buffer size.
Definition: ssl_client.cpp:122
virtual bool Connect()
Connect the client (synchronous)
Definition: ssl_client.cpp:148
virtual void onConnected()
Handle client connected notification.
Definition: ssl_client.h:289
asio::ssl::stream< asio::ip::tcp::socket >::next_layer_type & socket() noexcept
Get the client socket.
Definition: ssl_client.h:79
size_t option_send_buffer_size() const
Get the option: send buffer size.
Definition: ssl_client.cpp:129
uint64_t bytes_pending() const noexcept
Get the number of bytes pending sent by the client.
Definition: ssl_client.h:89
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 client definition.