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