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
|