LCOV - code coverage report
Current view: top level - envoy/matcher - matcher.h (source / functions) Hit Total Coverage
Test: coverage.dat Lines: 20 45 44.4 %
Date: 2024-01-05 06:35:25 Functions: 18 41 43.9 %

          Line data    Source code
       1             : #pragma once
       2             : 
       3             : #include <cstdint>
       4             : #include <memory>
       5             : #include <string>
       6             : 
       7             : #include "envoy/common/pure.h"
       8             : #include "envoy/config/common/matcher/v3/matcher.pb.h"
       9             : #include "envoy/config/core/v3/extension.pb.h"
      10             : #include "envoy/config/typed_config.h"
      11             : #include "envoy/protobuf/message_validator.h"
      12             : 
      13             : #include "absl/container/flat_hash_set.h"
      14             : #include "absl/strings/string_view.h"
      15             : #include "absl/types/optional.h"
      16             : #include "xds/type/matcher/v3/matcher.pb.h"
      17             : 
      18             : namespace Envoy {
      19             : 
      20             : namespace Server {
      21             : namespace Configuration {
      22             : class ServerFactoryContext;
      23             : }
      24             : } // namespace Server
      25             : 
      26             : namespace Matcher {
      27             : 
      28             : // Abstract interface for custom matching data.
      29             : // Overrides this interface to provide custom matcher specific implementation.
      30             : class CustomMatchData {
      31             : public:
      32           0 :   virtual ~CustomMatchData() = default;
      33             : };
      34             : 
      35             : using MatchingDataType =
      36             :     absl::variant<absl::monostate, std::string, std::shared_ptr<CustomMatchData>>;
      37             : 
      38             : inline constexpr absl::string_view DefaultMatchingDataType = "string";
      39             : 
      40             : // This file describes a MatchTree<DataType>, which traverses a tree of matches until it
      41             : // either matches (resulting in either an action or a new tree to traverse) or doesn't match.
      42             : // The matching might stop early if either the data is not available at all yet, or if more data
      43             : // might result in a match.
      44             : 
      45             : // By returning a new tree when an OnMatch results in a new tree, matching can be resumed from
      46             : // this tree should more data be required to complete matching. This avoids having to start
      47             : // from the beginning every time. At some point we might support resuming for any node in the match
      48             : // tree: this requires some careful handling of tracking which on_no_match to use should we fail to
      49             : // match.
      50             : //
      51             : // All the matching is performed on strings: a DataInput<DataType> is used to extract a specific
      52             : // string from an instance of DataType, while an InputMatcher is used to determine whether the
      53             : // extracted string is a match.
      54             : //
      55             : // For example, DataType might be the type HttpDataInput, allowing
      56             : // for the use of HttpRequestHeaders : DataInput<HttpDataInput>, which is configured with the name
      57             : // of the header to extract from the request headers.
      58             : //
      59             : // In cases where the data to match on becomes available over time, this would be fed into the
      60             : // DataType over time, allowing matching to be re-attempted as more data is made available. As such
      61             : // whenever we extract data from a DataInput, we make note of whether the data might change and
      62             : // pause matching until we either match or have all the data. It would then fall on the caller to
      63             : // both provide more information to the DataType and to resume matching.
      64             : template <class DataType> class MatchTree;
      65             : 
      66             : template <class DataType> using MatchTreeSharedPtr = std::shared_ptr<MatchTree<DataType>>;
      67             : template <class DataType> using MatchTreePtr = std::unique_ptr<MatchTree<DataType>>;
      68             : template <class DataType> using MatchTreeFactoryCb = std::function<MatchTreePtr<DataType>()>;
      69             : 
      70             : /**
      71             :  * Action provides the interface for actions to perform when a match occurs. It provides no
      72             :  * functions, as implementors are expected to downcast this to a more specific action.
      73             :  */
      74             : class Action {
      75             : public:
      76           2 :   virtual ~Action() = default;
      77             : 
      78             :   /**
      79             :    * The underlying type of this action. This can be used to determine which
      80             :    * type this action is before attempting to cast it.
      81             :    */
      82             :   virtual absl::string_view typeUrl() const PURE;
      83             : 
      84             :   /**
      85             :    * Helper to convert this action to its underlying type.
      86             :    */
      87           2 :   template <class T> const T& getTyped() const {
      88           2 :     ASSERT(dynamic_cast<const T*>(this) != nullptr);
      89           2 :     return static_cast<const T&>(*this);
      90           2 :   }
      91             : };
      92             : 
      93             : using ActionPtr = std::unique_ptr<Action>;
      94             : using ActionFactoryCb = std::function<ActionPtr()>;
      95             : 
      96             : template <class ActionFactoryContext> class ActionFactory : public Config::TypedFactory {
      97             : public:
      98             :   virtual ActionFactoryCb
      99             :   createActionFactoryCb(const Protobuf::Message& config,
     100             :                         ActionFactoryContext& action_factory_context,
     101             :                         ProtobufMessage::ValidationVisitor& validation_visitor) PURE;
     102             : 
     103         253 :   std::string category() const override { return "envoy.matching.action"; }
     104             : };
     105             : 
     106             : // On match, we either return the action to perform or another match tree to match against.
     107             : template <class DataType> struct OnMatch {
     108             :   const ActionFactoryCb action_cb_;
     109             :   const MatchTreeSharedPtr<DataType> matcher_;
     110             : };
     111             : template <class DataType> using OnMatchFactoryCb = std::function<OnMatch<DataType>()>;
     112             : 
     113             : template <class DataType> class OnMatchFactory {
     114             : public:
     115           3 :   virtual ~OnMatchFactory() = default;
     116             : 
     117             :   // Instantiates a nested matcher sub-tree or an action.
     118             :   // Returns absl::nullopt if neither sub-tree or action is specified.
     119             :   virtual absl::optional<OnMatchFactoryCb<DataType>>
     120             :   createOnMatch(const xds::type::matcher::v3::Matcher::OnMatch&) PURE;
     121             :   // Instantiates a nested matcher sub-tree or an action.
     122             :   // Returns absl::nullopt if neither sub-tree or action is specified.
     123             :   virtual absl::optional<OnMatchFactoryCb<DataType>>
     124             :   createOnMatch(const envoy::config::common::matcher::v3::Matcher::OnMatch&) PURE;
     125             : };
     126             : 
     127             : /**
     128             :  * State enum for the result of an attempted match.
     129             :  */
     130             : enum class MatchState {
     131             :   /**
     132             :    * The match could not be completed, e.g. due to the required data not being available.
     133             :    */
     134             :   UnableToMatch,
     135             :   /**
     136             :    * The match was completed.
     137             :    */
     138             :   MatchComplete,
     139             : };
     140             : 
     141             : /**
     142             :  * MatchTree provides the interface for performing matches against the data provided by DataType.
     143             :  */
     144             : template <class DataType> class MatchTree {
     145             : public:
     146           2 :   virtual ~MatchTree() = default;
     147             : 
     148             :   // The result of a match. There are three possible results:
     149             :   // - The match could not be completed (match_state_ == MatchState::UnableToMatch)
     150             :   // - The match was completed, no match found (match_state_ == MatchState::MatchComplete, on_match_
     151             :   // = {})
     152             :   // - The match was complete, match found (match_state_ == MatchState::MatchComplete, on_match_ =
     153             :   // something).
     154             :   struct MatchResult {
     155             :     const MatchState match_state_;
     156             :     const absl::optional<OnMatch<DataType>> on_match_;
     157             :   };
     158             : 
     159             :   // Attempts to match against the matching data (which should contain all the data requested via
     160             :   // matching requirements). If the match couldn't be completed, {false, {}} will be returned.
     161             :   // If a match result was determined, {true, action} will be returned. If a match result was
     162             :   // determined to be no match, {true, {}} will be returned.
     163             :   virtual MatchResult match(const DataType& matching_data) PURE;
     164             : };
     165             : 
     166             : template <class DataType> using MatchTreeSharedPtr = std::shared_ptr<MatchTree<DataType>>;
     167             : 
     168             : // InputMatcher provides the interface for determining whether an input value matches.
     169             : class InputMatcher {
     170             : public:
     171           0 :   virtual ~InputMatcher() = default;
     172             : 
     173             :   /**
     174             :    * Whether the provided input is a match.
     175             :    * @param Matcher::MatchingDataType the value to match on. Will be absl::monostate() if the
     176             :    * lookup failed.
     177             :    */
     178             :   virtual bool match(const Matcher::MatchingDataType& input) PURE;
     179             : 
     180             :   /**
     181             :    * A set of data input types supported by InputMatcher.
     182             :    * String is default supported data input type because almost all the derived objects support
     183             :    * string only. The name of core types (e.g., std::string, int) is defined string constrant which
     184             :    * produces human-readable form (e.g., "string", "int").
     185             :    *
     186             :    * Override this function to provide matcher specific supported data input types.
     187             :    */
     188           0 :   virtual absl::flat_hash_set<std::string> supportedDataInputTypes() const {
     189           0 :     return absl::flat_hash_set<std::string>{std::string(DefaultMatchingDataType)};
     190           0 :   }
     191             : };
     192             : 
     193             : using InputMatcherPtr = std::unique_ptr<InputMatcher>;
     194             : using InputMatcherFactoryCb = std::function<InputMatcherPtr()>;
     195             : 
     196             : /**
     197             :  * Factory for registering custom input matchers.
     198             :  */
     199             : class InputMatcherFactory : public Config::TypedFactory {
     200             : public:
     201             :   virtual InputMatcherFactoryCb
     202             :   createInputMatcherFactoryCb(const Protobuf::Message& config,
     203             :                               Server::Configuration::ServerFactoryContext& factory_context) PURE;
     204             : 
     205          18 :   std::string category() const override { return "envoy.matching.input_matchers"; }
     206             : };
     207             : 
     208             : // The result of retrieving data from a DataInput. As the data is generally made available
     209             : // over time (e.g. as more of the stream reaches the proxy), data might become increasingly
     210             : // available. This return type allows the DataInput to indicate this, as this might influence
     211             : // the match decision.
     212             : //
     213             : // Conceptually the data availability should start at being NotAvailable, transition to
     214             : // MoreDataMightBeAvailable (optional, this doesn't make sense for all data) and finally
     215             : // AllDataAvailable as the data becomes available.
     216             : struct DataInputGetResult {
     217             :   enum class DataAvailability {
     218             :     // The data is not yet available.
     219             :     NotAvailable,
     220             :     // Some data is available, but more might arrive.
     221             :     MoreDataMightBeAvailable,
     222             :     // All the data is available.
     223             :     AllDataAvailable
     224             :   };
     225             : 
     226             :   DataAvailability data_availability_;
     227             :   // The resulting data. This will be absl::monostate() if we don't have sufficient data available
     228             :   // (as per data_availability_) or because no value was extracted. For example, consider a
     229             :   // DataInput which attempts to look a key up in the map: if we don't have access to the map yet,
     230             :   // we return absl::monostate() with NotAvailable. If we have the entire map, but the key doesn't
     231             :   // exist in the map, we return absl::monostate() with AllDataAvailable.
     232             :   MatchingDataType data_;
     233             : 
     234             :   // For pretty printing.
     235           0 :   friend std::ostream& operator<<(std::ostream& out, const DataInputGetResult& result) {
     236           0 :     out << "data input: "
     237           0 :         << (absl::holds_alternative<std::string>(result.data_)
     238           0 :                 ? absl::get<std::string>(result.data_)
     239           0 :                 : "n/a");
     240           0 : 
     241           0 :     switch (result.data_availability_) {
     242           0 :     case DataInputGetResult::DataAvailability::NotAvailable:
     243           0 :       out << " (not available)";
     244           0 :       break;
     245           0 :     case DataInputGetResult::DataAvailability::MoreDataMightBeAvailable:
     246           0 :       out << " (more data available)";
     247           0 :       break;
     248           0 :     case DataInputGetResult::DataAvailability::AllDataAvailable:;
     249           0 :     }
     250           0 :     return out;
     251           0 :   }
     252             : };
     253             : 
     254             : /**
     255             :  * Interface for types providing a way to extract a string from the DataType to perform matching
     256             :  * on.
     257             :  */
     258             : template <class DataType> class DataInput {
     259             : public:
     260           0 :   virtual ~DataInput() = default;
     261             : 
     262             :   virtual DataInputGetResult get(const DataType& data) const PURE;
     263             : 
     264             :   /**
     265             :    * Input type of DataInput.
     266             :    * String is default data input type since nearly all the DataInput's derived objects' input type
     267             :    * is string. The name of core types (e.g., std::string, int) is defined string constrant which
     268             :    * produces human-readable form (e.g., "string", "int").
     269             :    *
     270             :    * Override this function to provide matcher specific data input type.
     271             :    */
     272           0 :   virtual absl::string_view dataInputType() const { return DefaultMatchingDataType; }
     273             : };
     274             : 
     275             : template <class DataType> using DataInputPtr = std::unique_ptr<DataInput<DataType>>;
     276             : template <class DataType> using DataInputFactoryCb = std::function<DataInputPtr<DataType>()>;
     277             : 
     278             : /**
     279             :  * Factory for data inputs.
     280             :  */
     281             : template <class DataType> class DataInputFactory : public Config::TypedFactory {
     282             : public:
     283             :   /**
     284             :    * Creates a DataInput from the provided config.
     285             :    */
     286             :   virtual DataInputFactoryCb<DataType>
     287             :   createDataInputFactoryCb(const Protobuf::Message& config,
     288             :                            ProtobufMessage::ValidationVisitor& validation_visitor) PURE;
     289             : 
     290             :   /**
     291             :    * The category of this factory depends on the DataType, so we require a name() function to exist
     292             :    * that allows us to get a string representation of the data type for categorization.
     293             :    */
     294         415 :   std::string category() const override {
     295             :     // Static assert to guide implementors to understand what is required.
     296         415 :     static_assert(std::is_convertible<absl::string_view, decltype(DataType::name())>(),
     297         415 :                   "DataType must implement valid name() function");
     298         415 :     return fmt::format("envoy.matching.{}.input", DataType::name());
     299         415 :   }
     300             : };
     301             : 
     302             : /**
     303             :  * Interface for types providing a way to use a string for matching without depending on protocol
     304             :  * data. As a result, these can be used for all protocols.
     305             :  */
     306             : class CommonProtocolInput {
     307             : public:
     308           0 :   virtual ~CommonProtocolInput() = default;
     309             :   virtual MatchingDataType get() PURE;
     310             : };
     311             : using CommonProtocolInputPtr = std::unique_ptr<CommonProtocolInput>;
     312             : using CommonProtocolInputFactoryCb = std::function<CommonProtocolInputPtr()>;
     313             : 
     314             : /**
     315             :  * Factory for CommonProtocolInput.
     316             :  */
     317             : class CommonProtocolInputFactory : public Config::TypedFactory {
     318             : public:
     319             :   /**
     320             :    * Creates a CommonProtocolInput from the provided config.
     321             :    */
     322             :   virtual CommonProtocolInputFactoryCb
     323             :   createCommonProtocolInputFactoryCb(const Protobuf::Message& config,
     324             :                                      ProtobufMessage::ValidationVisitor& validation_visitor) PURE;
     325             : 
     326           8 :   std::string category() const override { return "envoy.matching.common_inputs"; }
     327             : };
     328             : 
     329             : /**
     330             :  * Factory for registering custom matchers.
     331             :  */
     332             : template <class DataType> class CustomMatcherFactory : public Config::TypedFactory {
     333             : public:
     334             :   virtual MatchTreeFactoryCb<DataType>
     335             :   createCustomMatcherFactoryCb(const Protobuf::Message& config,
     336             :                                Server::Configuration::ServerFactoryContext& factory_context,
     337             :                                DataInputFactoryCb<DataType> data_input,
     338             :                                absl::optional<OnMatchFactoryCb<DataType>> on_no_match,
     339             :                                OnMatchFactory<DataType>& on_match_factory) PURE;
     340          10 :   std::string category() const override {
     341             :     // Static assert to guide implementors to understand what is required.
     342          10 :     static_assert(std::is_convertible<absl::string_view, decltype(DataType::name())>(),
     343          10 :                   "DataType must implement valid name() function");
     344          10 :     return fmt::format("envoy.matching.{}.custom_matchers", DataType::name());
     345          10 :   }
     346             : };
     347             : 
     348             : } // namespace Matcher
     349             : } // namespace Envoy
     350             : 
     351             : // NOLINT(namespace-envoy)
     352             : namespace fmt {
     353             : // Allow fmtlib to use operator << defined in DataInputGetResult
     354             : template <> struct formatter<::Envoy::Matcher::DataInputGetResult> : ostream_formatter {};
     355             : } // namespace fmt

Generated by: LCOV version 1.15