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