1 /*!
2 * @file
3 * @brief Interfaces for connection_handlers.
4 */
5
6 #pragma once
7
8 #include <arataga/acl_handler/sequence_number.hpp>
9
10 #include <arataga/utils/can_throw.hpp>
11 #include <arataga/utils/string_literal.hpp>
12
13 #include <arataga/config.hpp>
14
15 #include <arataga/logging/wrap_logging.hpp>
16
17 #include <arataga/nothrow_block/macros.hpp>
18
19 #include <so_5/all.hpp>
20
21 #include <asio/ip/tcp.hpp>
22 #include <asio/write.hpp>
23
24 #include <fmt/format.h>
25 #include <fmt/ostream.h>
26
27 #include <spdlog/spdlog.h>
28
29 #include <noexcept_ctcheck/pub.hpp>
30
31 #include <chrono>
32 #include <cstddef>
33 #include <functional>
34 #include <memory>
35 #include <string_view>
36
37 namespace arataga::acl_handler
38 {
39
40 //
41 // config_t
42 //
43 /*!
44 * @brief An interface of object for accessing the config.
45 */
46 class config_t
47 {
48 public:
49 virtual ~config_t();
50
51 [[nodiscard]]
52 virtual acl_protocol_t
53 acl_protocol() const noexcept = 0;
54
55 [[nodiscard]]
56 virtual const asio::ip::address &
57 out_addr() const noexcept = 0;
58
59 [[nodiscard]]
60 virtual std::size_t
61 io_chunk_size() const noexcept = 0;
62
63 [[nodiscard]]
64 virtual std::size_t
65 io_chunk_count() const noexcept = 0;
66
67 [[nodiscard]]
68 virtual std::chrono::milliseconds
69 protocol_detection_timeout() const noexcept = 0;
70
71 [[nodiscard]]
72 virtual std::chrono::milliseconds
73 socks_handshake_phase_timeout() const noexcept = 0;
74
75 [[nodiscard]]
76 virtual std::chrono::milliseconds
77 dns_resolving_timeout() const noexcept = 0;
78
79 [[nodiscard]]
80 virtual std::chrono::milliseconds
81 authentification_timeout() const noexcept = 0;
82
83 [[nodiscard]]
84 virtual std::chrono::milliseconds
85 connect_target_timeout() const noexcept = 0;
86
87 [[nodiscard]]
88 virtual std::chrono::milliseconds
89 socks_bind_timeout() const noexcept = 0;
90
91 [[nodiscard]]
92 virtual std::chrono::milliseconds
93 idle_connection_timeout() const noexcept = 0;
94
95 [[nodiscard]]
96 virtual std::chrono::milliseconds
97 http_headers_complete_timeout() const noexcept = 0;
98
99 [[nodiscard]]
100 virtual std::chrono::milliseconds
101 http_negative_response_timeout() const noexcept = 0;
102
103 [[nodiscard]]
104 virtual const http_message_value_limits_t &
105 http_message_limits() const noexcept = 0;
106 };
107
108 //
109 // remove_reason_t
110 //
111 //! Enumeration for connection-handler removal reason.
112 enum remove_reason_t
113 {
114 //! Normal completion of connection serving.
115 normal_completion,
116 //! I/O error detected.
117 io_error,
118 //! The current operation timed-out.
119 current_operation_timed_out,
120 //! Unsupported protocol detected.
121 unsupported_protocol,
122 //! Some protocol-related error. For example, an unsupported protocol
123 //! version detected.
124 protocol_error,
125 //! Some unexpected case that can't be correctly handled.
126 unexpected_and_unsupported_case,
127 //! There is no activity in the connection for too long time.
128 no_activity_for_too_long,
129 //! The current operation was cancelled from outside.
130 current_operation_canceled,
131 //! A uncaught exception fron connection-handled detected.
132 unhandled_exception,
133 //! The required IP-version can't be used.
134 //! For example, an attempt to connect IPv6 address from IPv4 address.
135 ip_version_mismatch,
136 //! The user has no required permissions.
137 access_denied,
138 //! The failure of target domain name resolution.
139 unresolved_target,
140 //! The connection to the target host was broken.
141 target_end_broken,
142 //! The connection from the user was broken.
143 user_end_broken,
144 //! HTTP-reposnse was received before the completion of outgoing
145 //! HTTP-request.
146 http_response_before_completion_of_http_request,
147 //! The connection from the user was closed by the user-end.
148 user_end_closed_by_client,
149 //! The user didn't send a new incoming HTTP-request.
150 http_no_incoming_request
151 };
152
153 [[nodiscard]]
154 inline constexpr arataga::utils::string_literal_t
to_string_literal(remove_reason_t reason)155 to_string_literal( remove_reason_t reason )
156 {
157 using namespace arataga::utils::string_literals;
158
159 auto result = "<unknown>"_static_str;
160 switch( reason )
161 {
162 case remove_reason_t::normal_completion:
163 result = "normal_completion"_static_str;
164 break;
165
166 case remove_reason_t::io_error:
167 result = "io_error"_static_str;
168 break;
169
170 case remove_reason_t::current_operation_timed_out:
171 result = "current_operation_timed_out"_static_str;
172 break;
173
174 case remove_reason_t::unsupported_protocol:
175 result = "unsupported_protocol"_static_str;
176 break;
177
178 case remove_reason_t::protocol_error:
179 result = "protocol_error"_static_str;
180 break;
181
182 case remove_reason_t::unexpected_and_unsupported_case:
183 result = "unexpected_and_unsupported_case"_static_str;
184 break;
185
186 case remove_reason_t::no_activity_for_too_long:
187 result = "no_activity_for_too_long"_static_str;
188 break;
189
190 case remove_reason_t::current_operation_canceled:
191 result = "current_operation_canceled"_static_str;
192 break;
193
194 case remove_reason_t::unhandled_exception:
195 result = "unhandled_exception"_static_str;
196 break;
197
198 case remove_reason_t::ip_version_mismatch:
199 result = "ip_version_mismatch"_static_str;
200 break;
201
202 case remove_reason_t::access_denied:
203 result = "access_denied"_static_str;
204 break;
205
206 case remove_reason_t::unresolved_target:
207 result = "unresolved_target"_static_str;
208 break;
209
210 case remove_reason_t::target_end_broken:
211 result = "target_end_broken"_static_str;
212 break;
213
214 case remove_reason_t::user_end_broken:
215 result = "user_end_broken"_static_str;
216 break;
217
218 case remove_reason_t::http_response_before_completion_of_http_request:
219 result = "http_response_before_completion_of_http_request"_static_str;
220 break;
221
222 case remove_reason_t::user_end_closed_by_client:
223 result = "user_end_closed_by_client"_static_str;
224 break;
225
226 case remove_reason_t::http_no_incoming_request:
227 result = "http_no_incoming_request"_static_str;
228 break;
229 }
230
231 return result;
232 }
233
234 inline std::ostream &
operator <<(std::ostream & to,remove_reason_t reason)235 operator<<( std::ostream & to, remove_reason_t reason )
236 {
237 return (to << to_string_literal(reason));
238 }
239
240 // The definition is going below.
241 class connection_handler_t;
242
243 //
244 // connection_handler_shptr_t
245 //
246 /*!
247 * @brief An alias for shared_ptr to connection_handler.
248 */
249 using connection_handler_shptr_t = std::shared_ptr< connection_handler_t >;
250
251 //
252 // traffic_limiter_t
253 //
254 /*!
255 * @brief An interface for object that limits the bandwidth.
256 *
257 * An implementation of that interface should clean all resources
258 * in its destructor.
259 */
260 class traffic_limiter_t
261 {
262 public:
263 enum class direction_t { from_user, from_target };
264
265 /*!
266 * @brief The result of asking a quote for reading incoming
267 * data on the current turn.
268 *
269 * If the direction can be read then m_capacity will contain
270 * an enabled amount of data to be read. After the completion
271 * of the read operation method release() should be called
272 * for reserved_capacity_t object.
273 */
274 struct reserved_capacity_t
275 {
276 std::size_t m_capacity;
277 sequence_number_t m_sequence_number;
278
279 //! The method for registering the result of I/O operation
280 //! in traffic_limiter.
281 /*!
282 * This method analyses the error code and, if that code is not 0,
283 * assumes that 0 bytes are read.
284 *
285 * @attention
286 * This method must be called after the completion of I/O operation.
287 * Otherwise the reserved capacity will remain reserved till the
288 * end of the current turn.
289 */
290 void
291 release(
292 traffic_limiter_t & limiter,
293 direction_t dir,
294 const asio::error_code & ec,
295 std::size_t bytes_transferred ) const noexcept;
296 };
297
298 traffic_limiter_t( const traffic_limiter_t & ) = delete;
299 traffic_limiter_t( traffic_limiter_t && ) = delete;
300
301 traffic_limiter_t();
302 virtual ~traffic_limiter_t();
303
304 // Can return 0.
305 // In that case attempts of reading data should be suspended
306 // until the next turn.
307 [[nodiscard]]
308 virtual reserved_capacity_t
309 reserve_read_portion(
310 direction_t dir,
311 std::size_t buffer_size ) noexcept = 0;
312
313 virtual void
314 release_reserved_capacity(
315 direction_t dir,
316 reserved_capacity_t reserved_capacity,
317 std::size_t actual_bytes ) noexcept = 0;
318 };
319
320 //
321 // traffic_limiter_unique_ptr_t
322 //
323 /*!
324 * @brief An alias for unique_ptr to traffic_limiter.
325 */
326 using traffic_limiter_unique_ptr_t =
327 std::unique_ptr< traffic_limiter_t >;
328
329 namespace dns_resolving
330 {
331
332 //! The result of successful DNS-resolving.
333 struct hostname_found_t
334 {
335 //! IP-address for the domain name.
336 asio::ip::address m_ip;
337 };
338
339 //! The result of failed DNS-resolving.
340 struct hostname_not_found_t
341 {
342 //! Textual description of the failure.
343 std::string m_error_desc;
344 };
345
346 //! Type for DNS-resolving result.
347 using hostname_result_t = std::variant<
348 hostname_found_t,
349 hostname_not_found_t
350 >;
351
352 //! Type of a functor that should be called after the completion
353 //! of DNS-resolving.
354 using hostname_result_handler_t =
355 std::function< void(const hostname_result_t &) >;
356
357 } /* namespace dns_resolving */
358
359 namespace authentification
360 {
361
362 //! Parameters for an authentification request.
363 struct request_params_t
364 {
365 asio::ip::address_v4 m_user_ip;
366 std::optional< std::string > m_username;
367 std::optional< std::string > m_password;
368 std::string m_target_host;
369 std::uint16_t m_target_port;
370 };
371
372 //! Enumeration of reasons for failed authentification.
373 enum class failure_reason_t
374 {
375 unknown_user,
376 target_blocked
377 };
378
379 [[nodiscard]]
380 inline constexpr arataga::utils::string_literal_t
to_string_literal(failure_reason_t reason)381 to_string_literal( failure_reason_t reason )
382 {
383 using namespace arataga::utils::string_literals;
384
385 switch( reason )
386 {
387 case failure_reason_t::unknown_user:
388 return "user unknown"_static_str;
389 case failure_reason_t::target_blocked:
390 return "target is blocked for user"_static_str;
391 }
392
393 return "<unknown>"_static_str;
394 }
395
396 //! Type of negative authentification result.
397 struct failure_t
398 {
399 failure_reason_t m_reason;
400 };
401
402 //! Type of positive authentification result.
403 struct success_t
404 {
405 //! Actual traffic limiter for the new connection.
406 traffic_limiter_unique_ptr_t m_traffic_limiter;
407 };
408
409 //! Type for authentification result.
410 using result_t = std::variant< failure_t, success_t >;
411
412 //! Type of a functor to be called after the completion of
413 //! user authentification.
414 using result_handler_t =
415 // The result is passed by value to enable to borrow
416 // moveable only values.
417 std::function< void(result_t) >;
418
419 } /* namespace dns_resolving */
420
421 //
422 // connection_type_t
423 //
424 /*!
425 * @brief Enumeration of various types of connections.
426 */
427 enum class connection_type_t
428 {
429 //! Type of the connection is not known yet.
430 /*!
431 * This type should be used for stats of total number of connections.
432 */
433 generic,
434 //! The connection uses SOCKS5 protocol.
435 socks5,
436 //! The connection uses HTTP protocol.
437 http
438 };
439
440 namespace details {
441
442 class delete_protector_maker_t;
443
444 } /* namespace details */
445
446 //
447 // delete_protector_t
448 //
449 /*!
450 * @brief A special marker that tells that connection_handler is
451 * protected from the deletion and it's safe to replace the current
452 * handler by a new one.
453 *
454 * An instance of delete_protector does nothing. But it presence
455 * tells that there is an instance of
456 * details::delete_protector_maker_t somewhere at the stack. And
457 * that delete_protector_maker_t defends the connection_handler
458 * from early deletion.
459 *
460 * The necessity of that class goes from the fact that connection_handler calls
461 * remove_connection_handler and replace_connection_handler from inside of own
462 * methods. As result of remove_connection_handler and
463 * replace_connection_handler the current handler can be deleted and this
464 * invalidates the `this` value. This can lead to use-after-free errors (for
465 * example if some non-static method will be acidentially called after the
466 * return from remove_connection_handler/replace_connection_handler).
467 *
468 * For protection from use-after-free error a scheme with additional
469 * connection_handler_shptr_t is used. This additional instance is created on
470 * the stack and only then methods like
471 * remove_connection_handler/replace_connection_handler are called. This
472 * additional instance protects the current handler from the deletion.
473 */
474 class delete_protector_t
475 {
476 friend class delete_protector_maker_t;
477
478 delete_protector_t() noexcept = default;
479
480 public:
481 ~delete_protector_t() noexcept = default;
482
483 delete_protector_t( const delete_protector_t & ) noexcept = default;
484 delete_protector_t( delete_protector_t && ) noexcept = default;
485
486 delete_protector_t &
487 operator=( const delete_protector_t & ) noexcept = default;
488 delete_protector_t &
489 operator=( delete_protector_t && ) noexcept = default;
490 };
491
492 namespace details {
493
494 class delete_protector_maker_t
495 {
496 public:
delete_protector_maker_t(connection_handler_shptr_t &)497 delete_protector_maker_t( connection_handler_shptr_t & ) {}
498
499 [[nodiscard]]
500 delete_protector_t
make() const501 make() const noexcept { return {}; }
502 };
503
504 } /* namespace details */
505
506 //
507 // handler_context_t
508 //
509 /*!
510 * @brief An interface of objects that holds a context in that
511 * user's connections are handled.
512 */
513 class handler_context_t
514 {
515 public:
516 virtual ~handler_context_t();
517
518 //! Type of connection ID inside that context.
519 using connection_id_t = std::uint_fast64_t;
520
521 virtual void
522 replace_connection_handler(
523 delete_protector_t,
524 connection_id_t id,
525 connection_handler_shptr_t handler ) = 0;
526
527 virtual void
528 remove_connection_handler(
529 delete_protector_t,
530 connection_id_t id,
531 remove_reason_t reason ) noexcept = 0;
532
533 // NOTE: this method should be called inside logging::wrap_logging!
534 virtual void
535 log_message_for_connection(
536 connection_id_t id,
537 ::arataga::logging::processed_log_level_t level,
538 std::string_view message ) = 0;
539
540 [[nodiscard]]
541 virtual const config_t &
542 config() const noexcept = 0;
543
544 virtual void
545 async_resolve_hostname(
546 connection_id_t id,
547 const std::string & hostname,
548 dns_resolving::hostname_result_handler_t result_handler ) = 0;
549
550 virtual void
551 async_authentificate(
552 connection_id_t id,
553 authentification::request_params_t request,
554 authentification::result_handler_t result_handler ) = 0;
555
556 virtual void
557 stats_inc_connection_count(
558 connection_type_t connection_type ) = 0;
559 };
560
561 //
562 // handler_context_holder_t
563 //
564 /*!
565 * @brief A special class that guarantees that handler_context
566 * will exist until someone holds a smart reference to it.
567 *
568 * There a danger in the use of Asio's async operations: completion
569 * handler for an operation can be called after the destruction of
570 * handler_context. In that case connection_handler will hold a
571 * dangled reference to handler_context. Any attempt to use
572 * that reference (a call to log_message_for_connection for example)
573 * will lead to undefined behaviour.
574 *
575 * To avoid that class handler_context_holder_t plays a role of
576 * a smart reference (or smart pointer) to handler_context.
577 * If a connection_handler holds an instance of
578 * handler_context_holder_t then it guarantees that handler_context
579 * will live even if handler_context itself has finished its work.
580 */
581 class handler_context_holder_t
582 {
583 //! A smart pointer to the object that holds handler_context.
584 so_5::agent_ref_t m_holder_agent;
585
586 //! A reference to the actual handler_context.
587 std::reference_wrapper< handler_context_t > m_context;
588
589 public:
handler_context_holder_t(so_5::agent_ref_t holder_agent,handler_context_t & context)590 handler_context_holder_t(
591 so_5::agent_ref_t holder_agent,
592 handler_context_t & context ) noexcept
593 : m_holder_agent{ std::move(holder_agent) }
594 , m_context{ context }
595 {}
596
597 [[nodiscard]]
598 handler_context_t &
ctx() const599 ctx() const noexcept { return m_context.get(); }
600 };
601
602 //
603 // connection_handler_t
604 //
605 /*!
606 * @brief A base type for connection_handler.
607 *
608 * This type not only defines an interface of connection_handler
609 * but also contains a basic functionality necessary for all
610 * connection_handler implementations.
611 */
612 class connection_handler_t
613 : public std::enable_shared_from_this< connection_handler_t >
614 {
615 public:
616 //! Handler status.
617 enum class status_t
618 {
619 //! Handler is active. It can handle the results of I/O operations.
620 active,
621 //! Handler is released. It was removed or replaced by another
622 //! handler. Because of that it can't handle the results
623 //! of I/O operations.
624 released
625 };
626
627 protected:
628 /*!
629 * @brief A special indicator that tells that exceptions can go out.
630 *
631 * This indicator tells a method/lambda that it's invoked inside
632 * try/catch block and throwing of an exception is permited.
633 *
634 * An instance is created inside wrap_action_and_handle_exceptions()
635 * and passed as a parameter to lambda-argument of
636 * wrap_action_and_handle_exceptions().
637 */
638 using can_throw_t = ::arataga::utils::can_throw_t;
639
640 //! Context for connection handler.
641 handler_context_holder_t m_ctx;
642
643 //! ID for the connection.
644 handler_context_t::connection_id_t m_id;
645
646 //! The connection with the client.
647 /*!
648 * That is the socked accepted by ACL.
649 */
650 asio::ip::tcp::socket m_connection;
651
652 //! Handler status.
653 status_t m_status;
654
655 /*!
656 * @name Methods inside those the handler can be removed/replaced.
657 * @{
658 */
659 virtual void
660 on_start_impl( delete_protector_t ) = 0;
661
662 virtual void
663 on_timer_impl( delete_protector_t ) = 0;
664 /*!
665 * @}
666 */
667
668 [[nodiscard]]
669 handler_context_t &
context()670 context() noexcept { return m_ctx.ctx(); }
671
672 /*!
673 * @brief Replace the handler to a new one.
674 */
675 template< typename New_Handler_Factory >
676 void
replace_handler(delete_protector_t delete_protector,can_throw_t can_throw,New_Handler_Factory && new_handler_factory)677 replace_handler(
678 delete_protector_t delete_protector,
679 can_throw_t can_throw,
680 New_Handler_Factory && new_handler_factory )
681 {
682 // If there will be an exception we'll have no choice except
683 // the removement of the old handler.
684 //
685 // The same picture is also for problems with the exceptions
686 // during the creation of a new handler. The old handler can
687 // be in invalid state when new_handler_factory throws. So we can
688 // only delete the old handler.
689 //
690 // Will catch only exceptions derived from std::exception.
691 // All other types of exception will crash the whole app.
692 [&]() noexcept {
693 NOEXCEPT_CTCHECK_STATIC_ASSERT_NOEXCEPT(
694 handler_context_holder_t{ m_ctx } );
695
696 // Make a copy of handler_context_holder because as
697 // the result of new_handler_factory invocation the m_ctx
698 // can become empty.
699 handler_context_holder_t ctx_holder = m_ctx;
700 try
701 {
702 connection_handler_shptr_t new_handler = new_handler_factory(
703 can_throw );
704
705 ctx_holder.ctx().replace_connection_handler(
706 delete_protector,
707 m_id,
708 std::move(new_handler) );
709 }
710 catch( const std::exception & x )
711 {
712 // Ignore exceptions that can be thrown during the logging.
713 ARATAGA_NOTHROW_BLOCK_BEGIN()
714 ARATAGA_NOTHROW_BLOCK_STAGE(log_exception)
715
716 ::arataga::logging::wrap_logging(
717 proxy_logging_mode,
718 spdlog::level::err,
719 [this, &ctx_holder, &x]( auto level )
720 {
721 ctx_holder.ctx().log_message_for_connection(
722 m_id,
723 level,
724 x.what() );
725 } );
726 ARATAGA_NOTHROW_BLOCK_END(LOG_THEN_IGNORE)
727
728 NOEXCEPT_CTCHECK_ENSURE_NOEXCEPT_STATEMENT(
729 ctx_holder.ctx().remove_connection_handler(
730 delete_protector,
731 m_id,
732 remove_reason_t::unexpected_and_unsupported_case )
733 );
734 }
735 }();
736 }
737
738 //! Remove the handler.
739 void
remove_handler(delete_protector_t delete_protector,remove_reason_t remove_reason)740 remove_handler(
741 delete_protector_t delete_protector,
742 remove_reason_t remove_reason )
743 {
744 context().remove_connection_handler(
745 delete_protector, m_id, remove_reason );
746 }
747
748 // NOTE: this method should be called from inside logging::wrap_logging.
749 void
log_message_for_connection(can_throw_t,::arataga::logging::processed_log_level_t level,std::string_view message)750 log_message_for_connection(
751 can_throw_t /*can_throw*/,
752 ::arataga::logging::processed_log_level_t level,
753 std::string_view message )
754 {
755 context().log_message_for_connection( m_id, level, message );
756 }
757
758 void
log_and_remove_connection_on_io_error(delete_protector_t delete_protector,can_throw_t can_throw,const asio::error_code & ec,std::string_view operation_description)759 log_and_remove_connection_on_io_error(
760 delete_protector_t delete_protector,
761 can_throw_t can_throw,
762 const asio::error_code & ec,
763 std::string_view operation_description )
764 {
765 // Should log the error except operation_aborted (this error is
766 // expected).
767 if( asio::error::operation_aborted != ec )
768 {
769 ::arataga::logging::wrap_logging(
770 proxy_logging_mode,
771 spdlog::level::warn,
772 [&]( auto level )
773 {
774 log_message_for_connection(
775 can_throw,
776 level,
777 fmt::format( "IO-error on {}: {}",
778 operation_description, ec.message() ) );
779 } );
780 }
781
782 remove_handler( delete_protector, remove_reason_t::io_error );
783 }
784
785 void
log_and_remove_connection(delete_protector_t delete_protector,can_throw_t can_throw,remove_reason_t reason,spdlog::level::level_enum level,std::string_view description)786 log_and_remove_connection(
787 delete_protector_t delete_protector,
788 can_throw_t can_throw,
789 remove_reason_t reason,
790 spdlog::level::level_enum level,
791 std::string_view description )
792 {
793 ::arataga::logging::wrap_logging(
794 proxy_logging_mode,
795 level,
796 [this, can_throw, description]( auto level )
797 {
798 log_message_for_connection(
799 can_throw,
800 level,
801 description );
802 } );
803
804 remove_handler( delete_protector, reason );
805 }
806
807 template< typename Action >
808 void
wrap_action_and_handle_exceptions(delete_protector_t delete_protector,Action && action)809 wrap_action_and_handle_exceptions(
810 delete_protector_t delete_protector,
811 Action && action )
812 {
813 try
814 {
815 ::arataga::utils::exception_handling_context_t ctx;
816
817 action( delete_protector, ctx.make_can_throw_marker() );
818 }
819 catch( const std::exception & x )
820 {
821 // We have to catch and suppress exceptions from here.
822 ARATAGA_NOTHROW_BLOCK_BEGIN()
823 ARATAGA_NOTHROW_BLOCK_STAGE(log_and_remove_connection)
824
825 ::arataga::utils::exception_handling_context_t ctx;
826
827 //FIXME: what if fmt::format throws?
828 log_and_remove_connection(
829 delete_protector,
830 ctx.make_can_throw_marker(),
831 remove_reason_t::unhandled_exception,
832 spdlog::level::err,
833 fmt::format( "exception caught: {}", x.what() )
834 );
835 ARATAGA_NOTHROW_BLOCK_END(LOG_THEN_IGNORE)
836 }
837 catch( ... )
838 {
839 // We have to catch and suppress exceptions from here.
840 ARATAGA_NOTHROW_BLOCK_BEGIN()
841 ARATAGA_NOTHROW_BLOCK_STAGE(
842 log_and_remove_connection_on_unknow_exception)
843
844 ::arataga::utils::exception_handling_context_t ctx;
845
846 log_and_remove_connection(
847 delete_protector,
848 ctx.make_can_throw_marker(),
849 remove_reason_t::unhandled_exception,
850 spdlog::level::err,
851 "unknown exception caught" );
852 ARATAGA_NOTHROW_BLOCK_END(LOG_THEN_IGNORE);
853 }
854 }
855
856 template< typename... Args >
857 friend struct completion_handler_maker_t;
858
859 /*!
860 * @brief A helper class for the creation of a callback
861 * for completion handler of an I/O operation.
862 *
863 * There is a tricky moment related to completion-handlers:
864 * they can be called after the removal of the coresponding
865 * connection_handler. In that case a completion-handler
866 * shouldn't do its work.
867 *
868 * The connection_handler's status should be checked at the
869 * beginning of completion-handler. The work should be done only
870 * if the connection_handler is still active (the status is
871 * status_t::active).
872 *
873 * Because there ara many different completion-handlers it's
874 * boring task to write that check in every of handlers. Another
875 * approach is used here: a programmer writes completion-handler
876 * in the form of a lambda. This lambda then wrapped into another
877 * lambda-function that is passed to Asio. This wrapper perform
878 * necessary status check and calls programmer's lambda if
879 * the status allows process the result of I/O operation.
880 *
881 * The class completion_handler_maker_t is a part of process
882 * of creation of the wrapped mentioned above.
883 *
884 * This is two-step process, because we have to create
885 * completion-handlers with different signatures.
886 *
887 * An instance of completion_handler_maker_t is created on the first step.
888 * The template parameters for it will be types of arguments for
889 * the completion-handler.
890 *
891 * Method make_handler is called on the second step. That method
892 * gets an actual completion-handler lambda as an argument and
893 * returns a proper wrapper.
894 *
895 * @attention
896 * This first parameter for user-provided completion-handler will be
897 * a value of delete_protector_t type. Then there will be a value
898 * of type can_throw_t. Then there will be parameters of types @a Args.
899 *
900 * @note
901 * Initialy a wrapper, created inside make_handler() method, doesn't
902 * catch exceptions. But then wrap_action_and_handle_exceptions() was
903 * used inside so now user-provided completion-handler is called
904 * from try...catch block.
905 */
906 template< typename... Args >
907 struct completion_handler_maker_t
908 {
909 connection_handler_shptr_t m_handler;
910
completion_handler_maker_tarataga::acl_handler::connection_handler_t::completion_handler_maker_t911 completion_handler_maker_t(
912 connection_handler_shptr_t handler )
913 : m_handler{ std::move(handler) }
914 {}
915
916 template< typename Completion >
917 [[nodiscard]]
918 auto
make_handlerarataga::acl_handler::connection_handler_t::completion_handler_maker_t919 make_handler( Completion && completion ) &&
920 {
921 return
922 [handler = std::move(m_handler),
923 completion_func = std::move(completion)]
924 ( Args ...args ) mutable
925 {
926 if( status_t::active == handler->m_status )
927 {
928 details::delete_protector_maker_t protector{ handler };
929 handler->wrap_action_and_handle_exceptions(
930 protector.make(),
931 [&](
932 delete_protector_t delete_protector,
933 can_throw_t can_throw )
934 {
935 completion_func(
936 delete_protector,
937 can_throw,
938 std::forward<Args>(args)... );
939 } );
940 }
941 };
942 }
943 };
944
945 //! Do the first step of completion-handler creation.
946 /*!
947 * Usage example:
948 * @code
949 * with<const asio::error_code &, std::size_t>().make_handler(
950 * [this]( delete_protector_t delete_protector,
951 * can_throw_t can_throw,
952 * const asio::error_code & ec,
953 * std::size_t bytes_transferred )
954 * {
955 * ... // Handler code.
956 * });
957 * @endcode
958 */
959 template< typename... Args >
960 completion_handler_maker_t< Args... >
with()961 with() noexcept
962 {
963 return { shared_from_this() };
964 }
965
966 template< typename Completion >
967 [[nodiscard]]
968 auto
make_read_write_completion_handler(arataga::utils::string_literal_t op_name,Completion && completion)969 make_read_write_completion_handler(
970 // Require string-literal because this value has to be valid
971 // until the end of completion-handler work.
972 arataga::utils::string_literal_t op_name,
973 Completion && completion )
974 {
975 return with<const asio::error_code &, std::size_t>()
976 .make_handler(
977 // There is no sense to call shared_from_this because
978 // it's already done by make_io_completion_handler.
979 [this, op_name, completion_func = std::move(completion)](
980 delete_protector_t delete_protector,
981 can_throw_t can_throw,
982 const asio::error_code & ec,
983 std::size_t bytes_transferred ) mutable
984 {
985 if( ec )
986 log_and_remove_connection_on_io_error(
987 delete_protector,
988 can_throw, ec, op_name );
989 else
990 completion_func(
991 delete_protector,
992 can_throw, bytes_transferred );
993 } );
994 }
995
996 template<
997 typename Buffer,
998 typename Completion >
999 void
read_some(can_throw_t,asio::ip::tcp::socket & connection,Buffer & buffer,Completion && completion)1000 read_some(
1001 can_throw_t /*can_throw*/,
1002 asio::ip::tcp::socket & connection,
1003 Buffer & buffer,
1004 Completion && completion )
1005 {
1006 using namespace arataga::utils::string_literals;
1007
1008 connection.async_read_some(
1009 buffer.asio_buffer(),
1010 make_read_write_completion_handler(
1011 "read"_static_str,
1012 [completion_func = std::move(completion), &buffer](
1013 delete_protector_t delete_protector,
1014 can_throw_t can_throw,
1015 std::size_t bytes_transferred )
1016 {
1017 buffer.increment_bytes_read( bytes_transferred );
1018 completion_func( delete_protector, can_throw );
1019 } )
1020 );
1021 }
1022
1023 template<
1024 typename Buffer,
1025 typename Completion >
1026 void
write_whole(can_throw_t,asio::ip::tcp::socket & connection,Buffer & buffer,Completion && completion)1027 write_whole(
1028 can_throw_t /*can_throw*/,
1029 asio::ip::tcp::socket & connection,
1030 Buffer & buffer,
1031 Completion && completion )
1032 {
1033 using namespace arataga::utils::string_literals;
1034
1035 asio::async_write(
1036 connection,
1037 buffer.asio_buffer(),
1038 make_read_write_completion_handler(
1039 "write"_static_str,
1040 [&buffer, completion_func = std::move(completion)](
1041 delete_protector_t delete_protector,
1042 can_throw_t can_throw,
1043 std::size_t bytes_transferred ) mutable
1044 {
1045 buffer.increment_bytes_written( bytes_transferred );
1046 completion_func( delete_protector, can_throw );
1047 } )
1048 );
1049 }
1050
1051 public:
1052 connection_handler_t(
1053 handler_context_holder_t ctx,
1054 handler_context_t::connection_id_t id,
1055 asio::ip::tcp::socket connection );
1056
1057 virtual ~connection_handler_t();
1058
1059 void
1060 on_start();
1061
1062 void
1063 on_timer();
1064
1065 [[nodiscard]]
1066 virtual arataga::utils::string_literal_t
1067 name() const noexcept = 0;
1068
1069 // The default implementation closes m_connection if it is not closed yet.
1070 // The status is changed to status_t::released.
1071 virtual void
1072 release() noexcept;
1073 };
1074
1075 } /* namespace arataga::acl_handler */
1076
1077