CppServer 1.0.5.0
C++ Server Library
Loading...
Searching...
No Matches
ssl_client.cpp
Go to the documentation of this file.
1
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_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
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_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
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_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
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
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
224bool 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
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
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
312bool 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
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
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
464bool 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
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
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
602bool 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
655size_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
678 }
679
680 // Disconnect on error
681 if (ec)
682 {
683 SendError(ec);
684 Disconnect();
685 }
686
687 return sent;
688}
689
690size_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
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
753bool 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
805size_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
828 }
829
830 // Disconnect on error
831 if (ec)
832 {
833 SendError(ec);
834 Disconnect();
835 }
836
837 return received;
838}
839
840std::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
847size_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
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
910std::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
923void 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
980void 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
1057void 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
1073void 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
Asio allocate handler wrapper.
Definition memory.h:133
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: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.
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)
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
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
virtual bool SendAsync(const void *buffer, size_t size)
Send data to the server (asynchronous)
virtual bool ConnectAsync()
Connect the client (asynchronous)
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)
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
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:303
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:289
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
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.