1
#pragma once
2

            
3
#include <cstdint>
4
#include <memory>
5
#include <string>
6

            
7
#include "envoy/common/optref.h"
8
#include "envoy/common/pure.h"
9
#include "envoy/config/common/matcher/v3/matcher.pb.h"
10
#include "envoy/config/core/v3/extension.pb.h"
11
#include "envoy/config/typed_config.h"
12
#include "envoy/protobuf/message_validator.h"
13

            
14
#include "source/common/common/non_copyable.h"
15

            
16
#include "absl/container/flat_hash_set.h"
17
#include "absl/functional/overload.h"
18
#include "absl/strings/string_view.h"
19
#include "absl/types/optional.h"
20
#include "absl/types/variant.h"
21
#include "xds/type/matcher/v3/matcher.pb.h"
22

            
23
namespace Envoy {
24

            
25
namespace Server {
26
namespace Configuration {
27
class ServerFactoryContext;
28
}
29
} // namespace Server
30

            
31
namespace Matcher {
32

            
33
// Abstract interface for custom matching data.
34
// Overrides this interface to provide custom matcher specific implementation.
35
class CustomMatchData {
36
public:
37
149
  virtual ~CustomMatchData() = default;
38
};
39

            
40
inline constexpr absl::string_view DefaultMatchingDataType = "string";
41

            
42
// This file describes a MatchTree<DataType>, which traverses a tree of matches until it
43
// either matches (resulting in either an action or a new tree to traverse) or doesn't match.
44
// The matching might stop early if either the data is not available at all yet, or if more data
45
// might result in a match.
46

            
47
// By returning a new tree when an OnMatch results in a new tree, matching can be resumed from
48
// this tree should more data be required to complete matching. This avoids having to start
49
// from the beginning every time. At some point we might support resuming for any node in the match
50
// tree: this requires some careful handling of tracking which on_no_match to use should we fail to
51
// match.
52
//
53
// All the matching is performed on strings: a DataInput<DataType> is used to extract a specific
54
// string from an instance of DataType, while an InputMatcher is used to determine whether the
55
// extracted string is a match.
56
//
57
// For example, DataType might be the type HttpDataInput, allowing
58
// for the use of HttpRequestHeaders : DataInput<HttpDataInput>, which is configured with the name
59
// of the header to extract from the request headers.
60
//
61
// In cases where the data to match on becomes available over time, this would be fed into the
62
// DataType over time, allowing matching to be re-attempted as more data is made available. As such
63
// whenever we extract data from a DataInput, we make note of whether the data might change and
64
// pause matching until we either match or have all the data. It would then fall on the caller to
65
// both provide more information to the DataType and to resume matching.
66
template <class DataType> class MatchTree;
67

            
68
template <class DataType> using MatchTreeSharedPtr = std::shared_ptr<MatchTree<DataType>>;
69
template <class DataType> using MatchTreePtr = std::unique_ptr<MatchTree<DataType>>;
70
template <class DataType> using MatchTreeFactoryCb = std::function<MatchTreePtr<DataType>()>;
71

            
72
/**
73
 * The result of a match.
74
 */
75
enum class MatchResult {
76
  // The match comparison was completed, and there was no match.
77
  NoMatch,
78
  // The match comparison was completed, and there was a match.
79
  Matched,
80
  // The match could not be completed, e.g. due to the required data
81
  // not being available.
82
  InsufficientData,
83
};
84

            
85
// Prints a human-readable string representing the MatchResult.
86
3
inline static std::string MatchResultToString(MatchResult match_result) {
87
3
  switch (match_result) {
88
1
  case MatchResult::Matched:
89
1
    return "match";
90
1
  case MatchResult::NoMatch:
91
1
    return "no match";
92
1
  case MatchResult::InsufficientData:
93
1
    return "insufficient data";
94
3
  }
95
  return "invalid enum value";
96
3
}
97

            
98
/**
99
 * Action provides the interface for actions to perform when a match occurs. It provides no
100
 * functions, as implementors are expected to downcast this to a more specific action.
101
 */
102
class Action {
103
public:
104
14915
  virtual ~Action() = default;
105

            
106
  /**
107
   * The underlying type of this action. This can be used to determine which
108
   * type this action is before attempting to cast it.
109
   */
110
  virtual absl::string_view typeUrl() const PURE;
111

            
112
  /**
113
   * Helper to convert this action to its underlying type.
114
   */
115
1073
  template <class T> const T& getTyped() const {
116
1073
    ASSERT(dynamic_cast<const T*>(this) != nullptr);
117
1073
    return static_cast<const T&>(*this);
118
1073
  }
119
};
120

            
121
using ActionConstSharedPtr = std::shared_ptr<const Action>;
122

            
123
template <class ActionFactoryContext> class ActionFactory : public Config::TypedFactory {
124
public:
125
  virtual ActionConstSharedPtr
126
  createAction(const Protobuf::Message& config, ActionFactoryContext& action_factory_context,
127
               ProtobufMessage::ValidationVisitor& validation_visitor) PURE;
128

            
129
6037
  std::string category() const override { return "envoy.matching.action"; }
130
};
131

            
132
// On match, we either return the action to perform or another match tree to match against.
133
template <class DataType> struct OnMatch {
134
  const ActionConstSharedPtr action_;
135
  const MatchTreeSharedPtr<DataType> matcher_;
136
  bool keep_matching_{};
137
};
138
template <class DataType> using OnMatchFactoryCb = std::function<OnMatch<DataType>()>;
139

            
140
template <class DataType> class OnMatchFactory {
141
public:
142
1085
  virtual ~OnMatchFactory() = default;
143

            
144
  // Instantiates a nested matcher sub-tree or an action.
145
  // Returns absl::nullopt if neither sub-tree or action is specified.
146
  virtual absl::optional<OnMatchFactoryCb<DataType>>
147
  createOnMatch(const xds::type::matcher::v3::Matcher::OnMatch&) PURE;
148
  // Instantiates a nested matcher sub-tree or an action.
149
  // Returns absl::nullopt if neither sub-tree or action is specified.
150
  virtual absl::optional<OnMatchFactoryCb<DataType>>
151
  createOnMatch(const envoy::config::common::matcher::v3::Matcher::OnMatch&) PURE;
152
};
153

            
154
// The result of a match. Use of ActionMatchResult over MatchResult indicates there is a configured
155
// action associated with the match. This is used to inject configuration into the matcher. In cases
156
// where there is no associated configuration (such as sub-matchers configured as part of a match
157
// tree), use MatchResult to convey the result without an associated action.
158
//
159
// There are three possible results:
160
// - The match could not be completed due to lack of data (isInsufficientData() will return true.)
161
// - The match was completed, no match found (isNoMatch() will return true.)
162
// - The match was completed, match found (isMatch() will return true, action() will return the
163
//   ActionConstSharedPtr.)
164
struct ActionMatchResult {
165
public:
166
1330
  ActionMatchResult(ActionConstSharedPtr cb) : result_(std::move(cb)) {}
167
2527
  static ActionMatchResult noMatch() { return ActionMatchResult(NoMatch{}); }
168
33
  static ActionMatchResult insufficientData() { return ActionMatchResult(InsufficientData{}); }
169
975
  bool isInsufficientData() const { return absl::holds_alternative<InsufficientData>(result_); }
170
574
  bool isComplete() const { return !isInsufficientData(); }
171
1525
  bool isNoMatch() const { return absl::holds_alternative<NoMatch>(result_); }
172
3411
  bool isMatch() const { return absl::holds_alternative<ActionConstSharedPtr>(result_); }
173
908
  const ActionConstSharedPtr& action() const {
174
908
    ASSERT(isMatch());
175
908
    return absl::get<ActionConstSharedPtr>(result_);
176
908
  }
177
  // Returns the action by move. The caller must ensure that the ActionMatchResult is not used after
178
  // this call.
179
556
  ActionConstSharedPtr actionByMove() {
180
556
    ASSERT(isMatch());
181
556
    return absl::get<ActionConstSharedPtr>(std::move(result_));
182
556
  }
183

            
184
private:
185
  struct InsufficientData {};
186
  struct NoMatch {};
187
  using Result = absl::variant<ActionConstSharedPtr, NoMatch, InsufficientData>;
188
  Result result_;
189
2527
  ActionMatchResult(NoMatch) : result_(NoMatch{}) {}
190
33
  ActionMatchResult(InsufficientData) : result_(InsufficientData{}) {}
191
};
192

            
193
// Callback to execute against skipped matches' actions.
194
using SkippedMatchCb = std::function<void(const ActionConstSharedPtr&)>;
195

            
196
/**
197
 * MatchTree provides the interface for performing matches against the data provided by DataType.
198
 */
199
template <class DataType> class MatchTree {
200
public:
201
1245
  virtual ~MatchTree() = default;
202

            
203
  // Attempts to match against the matching data (which should contain all the data requested via
204
  // matching requirements).
205
  // If the match couldn't be completed, ActionMatchResult::insufficientData() will be returned.
206
  // If a match result was determined, an action callback factory will be returned.
207
  // If it was determined to be no match, ActionMatchResult::noMatch() will be returned.
208
  //
209
  // Implementors should call handleRecursionAndSkips() to transform OnMatch values
210
  // into ActionMatchResult values, and handle noMatch and insufficientData results as appropriate
211
  // for the specific matcher type.
212
  virtual ActionMatchResult match(const DataType& matching_data,
213
                                  SkippedMatchCb skipped_match_cb = nullptr) PURE;
214

            
215
protected:
216
  // Internally handle recursion & keep_matching logic in matcher implementations.
217
  // This should be called against initial matching & on-no-match results.
218
  static inline ActionMatchResult
219
  handleRecursionAndSkips(const absl::optional<OnMatch<DataType>>& on_match, const DataType& data,
220
3908
                          SkippedMatchCb skipped_match_cb) {
221
3908
    if (!on_match.has_value()) {
222
2491
      return ActionMatchResult::noMatch();
223
2491
    }
224
1417
    if (on_match->matcher_) {
225
73
      ActionMatchResult nested_result = on_match->matcher_->match(data, skipped_match_cb);
226
      // Parent result's keep_matching skips the nested result.
227
73
      if (on_match->keep_matching_ && nested_result.isMatch()) {
228
6
        if (skipped_match_cb) {
229
6
          skipped_match_cb(nested_result.action());
230
6
        }
231
6
        return ActionMatchResult::noMatch();
232
6
      }
233
67
      return nested_result;
234
73
    }
235
1344
    if (on_match->action_ && on_match->keep_matching_) {
236
28
      if (skipped_match_cb) {
237
28
        skipped_match_cb(on_match->action_);
238
28
      }
239
28
      return ActionMatchResult::noMatch();
240
28
    }
241
1316
    return ActionMatchResult{on_match->action_};
242
1344
  }
243
};
244

            
245
template <class DataType> using MatchTreeSharedPtr = std::shared_ptr<MatchTree<DataType>>;
246

            
247
class DataInputGetResult;
248

            
249
// InputMatcher provides the interface for determining whether an input value matches.
250
class InputMatcher {
251
public:
252
1435
  virtual ~InputMatcher() = default;
253

            
254
  /**
255
   * Whether the provided input is a match.
256
   * @param input is the input result. Will be absl::monostate() if the
257
   * lookup failed.
258
   */
259
  virtual MatchResult match(const DataInputGetResult& input) PURE;
260

            
261
  /**
262
   * A set of data input types supported by InputMatcher.
263
   * String is default supported data input type because almost all the derived objects support
264
   * string only. The name of core types (e.g., std::string, int) is defined string constrant which
265
   * produces human-readable form (e.g., "string", "int").
266
   *
267
   * Override this function to provide matcher specific supported data input types.
268
   */
269
1283
  virtual bool supportsDataInputType(absl::string_view data_type) const {
270
1283
    return data_type == DefaultMatchingDataType;
271
1283
  }
272
};
273

            
274
using InputMatcherPtr = std::unique_ptr<InputMatcher>;
275
using InputMatcherFactoryCb = std::function<InputMatcherPtr()>;
276

            
277
/**
278
 * Factory for registering custom input matchers.
279
 */
280
class InputMatcherFactory : public Config::TypedFactory {
281
public:
282
  virtual InputMatcherFactoryCb
283
  createInputMatcherFactoryCb(const Protobuf::Message& config,
284
                              Server::Configuration::ServerFactoryContext& factory_context) PURE;
285

            
286
94
  std::string category() const override { return "envoy.matching.input_matchers"; }
287
};
288

            
289
enum class DataAvailability {
290
  // The data is not yet available.
291
  NotAvailable,
292
  // Some data is available, but more might arrive.
293
  MoreDataMightBeAvailable,
294
  // All the data is available.
295
  AllDataAvailable
296
};
297

            
298
// The result of retrieving data from a DataInput. As the data is generally made available
299
// over time (e.g. as more of the stream reaches the proxy), data might become increasingly
300
// available. This return type allows the DataInput to indicate this, as this might influence
301
// the match decision.
302
//
303
// Conceptually the data availability should start at being NotAvailable, transition to
304
// MoreDataMightBeAvailable (optional, this doesn't make sense for all data) and finally
305
// AllDataAvailable as the data becomes available.
306
//
307
// This is non-copyable because its lifetime has a definite upper bound by the backing
308
// string data and by the matching procedure.
309
class DataInputGetResult : public NonCopyable {
310
public:
311
4985
  DataAvailability availability() const { return data_availability_; }
312

            
313
  /**
314
   * @return the default "string" data or nil. Life time must be bound by "this".
315
   */
316
3743
  absl::optional<absl::string_view> stringData() const {
317
3743
    return absl::visit(
318
3743
        absl::Overload{
319
3743
            [](const std::string& arg) { return absl::make_optional<absl::string_view>(arg); },
320
3743
            [](const absl::string_view& arg) {
321
428
              return absl::make_optional<absl::string_view>(arg);
322
428
            },
323
3743
            [](const auto&) { return absl::optional<absl::string_view>(); }},
324
3743
        data_);
325
3743
  }
326

            
327
  /**
328
   * @return the default custom data of the expected type or nil. Life time must be bound by "this".
329
   */
330
154
  template <class T> OptRef<const T> customData() const {
331
154
    return absl::visit(absl::Overload{[](const std::shared_ptr<CustomMatchData>& arg) {
332
149
                                        const T* data = dynamic_cast<const T*>(arg.get());
333
149
                                        return makeOptRefFromPtr<const T>(data);
334
149
                                      },
335
154
                                      [](const auto&) { return OptRef<const T>(); }},
336
154
                       data_);
337
154
  }
338

            
339
  static DataInputGetResult
340
205
  NoData(DataAvailability data_availability = DataAvailability::AllDataAvailable) {
341
205
    return DataInputGetResult(absl::monostate(), data_availability);
342
205
  }
343

            
344
  /**
345
   * Returns a string view match result. The input must ensure the backing data stays alive for the
346
   *duration of matching. Use CreateString when a string must be constructed.
347
   **/
348
  static DataInputGetResult
349
  CreateStringView(absl::string_view data,
350
468
                   DataAvailability data_availability = DataAvailability::AllDataAvailable) {
351
468
    return DataInputGetResult(data, data_availability);
352
468
  }
353

            
354
  static DataInputGetResult
355
  CreateString(std::string&& data,
356
3183
               DataAvailability data_availability = DataAvailability::AllDataAvailable) {
357
3183
    return DataInputGetResult(std::move(data), data_availability);
358
3183
  }
359

            
360
  static DataInputGetResult
361
  CreateCustom(std::shared_ptr<CustomMatchData>&& data,
362
149
               DataAvailability data_availability = DataAvailability::AllDataAvailable) {
363
149
    return DataInputGetResult(std::move(data), data_availability);
364
149
  }
365

            
366
private:
367
  DataAvailability data_availability_;
368
  // The resulting data. This will be absl::monostate() if we don't have sufficient data available
369
  // (as per data_availability_) or because no value was extracted. For example, consider a
370
  // DataInput which attempts to look a key up in the map: if we don't have access to the map yet,
371
  // we return absl::monostate() with NotAvailable. If we have the entire map, but the key doesn't
372
  // exist in the map, we return absl::monostate() with AllDataAvailable.
373
  using MatchingDataType = absl::variant<absl::monostate, std::string, absl::string_view,
374
                                         std::shared_ptr<CustomMatchData>>;
375
  MatchingDataType data_;
376

            
377
  DataInputGetResult(MatchingDataType&& data, DataAvailability data_availability)
378
4005
      : data_availability_(data_availability), data_(std::move(data)) {}
379

            
380
public:
381
  // For pretty printing.
382
  friend std::ostream& operator<<(std::ostream& out, const DataInputGetResult& result) {
383
    out << "data input: ";
384
    absl::visit(absl::Overload{[&](const std::string& arg) { out << arg; },
385
                               [&](const absl::string_view& arg) { out << arg; },
386
                               [&](const std::shared_ptr<CustomMatchData>&) { out << "(custom)"; },
387
                               [&](const auto&) { out << "n/a"; }},
388
                result.data_);
389

            
390
    switch (result.data_availability_) {
391
    case DataAvailability::NotAvailable:
392
      out << " (not available)";
393
      break;
394
    case DataAvailability::MoreDataMightBeAvailable:
395
      out << " (more data available)";
396
      break;
397
    case DataAvailability::AllDataAvailable:;
398
    }
399
    return out;
400
  }
401
};
402

            
403
/**
404
 * Interface for types providing a way to extract a string from the DataType to perform matching
405
 * on.
406
 */
407
template <class DataType> class DataInput {
408
public:
409
2553
  virtual ~DataInput() = default;
410

            
411
  virtual DataInputGetResult get(const DataType& data) const PURE;
412

            
413
  /**
414
   * Input type of DataInput.
415
   * String is default data input type since nearly all the DataInput's derived objects' input type
416
   * is string. The name of core types (e.g., std::string, int) is defined string constrant which
417
   * produces human-readable form (e.g., "string", "int").
418
   *
419
   * Override this function to provide matcher specific data input type.
420
   */
421
1694
  virtual absl::string_view dataInputType() const { return DefaultMatchingDataType; }
422
};
423

            
424
template <class DataType> using DataInputPtr = std::unique_ptr<DataInput<DataType>>;
425
template <class DataType> using DataInputFactoryCb = std::function<DataInputPtr<DataType>()>;
426

            
427
/**
428
 * Factory for data inputs.
429
 */
430
template <class DataType> class DataInputFactory : public Config::TypedFactory {
431
public:
432
  /**
433
   * Creates a DataInput from the provided config.
434
   */
435
  virtual DataInputFactoryCb<DataType>
436
  createDataInputFactoryCb(const Protobuf::Message& config,
437
                           ProtobufMessage::ValidationVisitor& validation_visitor) PURE;
438

            
439
  /**
440
   * The category of this factory depends on the DataType, so we require a name() function to exist
441
   * that allows us to get a string representation of the data type for categorization.
442
   */
443
46680
  std::string category() const override {
444
    // Static assert to guide implementors to understand what is required.
445
46680
    static_assert(std::is_convertible<absl::string_view, decltype(DataType::name())>(),
446
46680
                  "DataType must implement valid name() function");
447
46680
    return fmt::format("envoy.matching.{}.input", DataType::name());
448
46680
  }
449
};
450

            
451
/**
452
 * Interface for types providing a way to use a string for matching without depending on protocol
453
 * data. As a result, these can be used for all protocols.
454
 */
455
class CommonProtocolInput {
456
public:
457
3
  virtual ~CommonProtocolInput() = default;
458
  virtual DataInputGetResult get() PURE;
459
};
460
using CommonProtocolInputPtr = std::unique_ptr<CommonProtocolInput>;
461
using CommonProtocolInputFactoryCb = std::function<CommonProtocolInputPtr()>;
462

            
463
/**
464
 * Factory for CommonProtocolInput.
465
 */
466
class CommonProtocolInputFactory : public Config::TypedFactory {
467
public:
468
  /**
469
   * Creates a CommonProtocolInput from the provided config.
470
   */
471
  virtual CommonProtocolInputFactoryCb
472
  createCommonProtocolInputFactoryCb(const Protobuf::Message& config,
473
                                     ProtobufMessage::ValidationVisitor& validation_visitor) PURE;
474

            
475
9
  std::string category() const override { return "envoy.matching.common_inputs"; }
476
};
477

            
478
/**
479
 * Factory for registering custom matchers.
480
 */
481
template <class DataType> class CustomMatcherFactory : public Config::TypedFactory {
482
public:
483
  virtual MatchTreeFactoryCb<DataType>
484
  createCustomMatcherFactoryCb(const Protobuf::Message& config,
485
                               Server::Configuration::ServerFactoryContext& factory_context,
486
                               DataInputFactoryCb<DataType> data_input,
487
                               absl::optional<OnMatchFactoryCb<DataType>> on_no_match,
488
                               OnMatchFactory<DataType>& on_match_factory) PURE;
489
102
  std::string category() const override {
490
    // Static assert to guide implementors to understand what is required.
491
102
    static_assert(std::is_convertible<absl::string_view, decltype(DataType::name())>(),
492
102
                  "DataType must implement valid name() function");
493
102
    return fmt::format("envoy.matching.{}.custom_matchers", DataType::name());
494
102
  }
495
};
496

            
497
} // namespace Matcher
498
} // namespace Envoy
499

            
500
// NOLINT(namespace-envoy)
501
namespace fmt {
502
// Allow fmtlib to use operator << defined in DataInputGetResult
503
template <> struct formatter<::Envoy::Matcher::DataInputGetResult> : ostream_formatter {};
504
} // namespace fmt