1
#pragma once
2

            
3
#include <numeric>
4
#include <string>
5

            
6
#include "envoy/api/api.h"
7
#include "envoy/common/exception.h"
8
#include "envoy/protobuf/message_validator.h"
9
#include "envoy/runtime/runtime.h"
10
#include "envoy/type/v3/percent.pb.h"
11

            
12
#include "source/common/common/hash.h"
13
#include "source/common/common/stl_helpers.h"
14
#include "source/common/common/utility.h"
15
#include "source/common/protobuf/protobuf.h"
16
#include "source/common/singleton/const_singleton.h"
17

            
18
#include "absl/status/status.h"
19
#include "absl/status/statusor.h"
20
#include "absl/strings/str_join.h"
21
#include "absl/strings/string_view.h"
22

            
23
// Obtain the value of a wrapped field (e.g. google.protobuf.UInt32Value) if set. Otherwise, return
24
// the default value.
25
#define PROTOBUF_GET_WRAPPED_OR_DEFAULT(message, field_name, default_value)                        \
26
692054
  ((message).has_##field_name() ? (message).field_name().value() : (default_value))
27

            
28
// Obtain the value of a wrapped field (e.g. google.protobuf.UInt32Value) if set. Otherwise, return
29
// absl::nullopt.
30
#define PROTOBUF_GET_OPTIONAL_WRAPPED(message, field_name)                                         \
31
23811
  ((message).has_##field_name() ? absl::make_optional((message).field_name().value())              \
32
23811
                                : absl::nullopt)
33

            
34
// Obtain the value of a wrapped field (e.g. google.protobuf.UInt32Value) if set. Otherwise, throw
35
// a EnvoyException.
36

            
37
#define PROTOBUF_GET_WRAPPED_REQUIRED(message, field_name)                                         \
38
932
  ([](const auto& msg) {                                                                           \
39
932
    if (!msg.has_##field_name()) {                                                                 \
40
4
      ::Envoy::ProtoExceptionUtil::throwMissingFieldException(#field_name, msg);                   \
41
4
    }                                                                                              \
42
932
    return msg.field_name().value();                                                               \
43
932
  }((message)))
44
// Obtain the milliseconds value of a google.protobuf.Duration field if set. Otherwise, return the
45
// default value.
46
#define PROTOBUF_GET_MS_OR_DEFAULT(message, field_name, default_value)                             \
47
334790
  ((message).has_##field_name() ? DurationUtil::durationToMilliseconds((message).field_name())     \
48
334790
                                : (default_value))
49

            
50
// Obtain the string value if the field is set. Otherwise, return the default value.
51
#define PROTOBUF_GET_STRING_OR_DEFAULT(message, field_name, default_value)                         \
52
1153
  (!(message).field_name().empty() ? (message).field_name() : (default_value))
53

            
54
// Obtain the milliseconds value of a google.protobuf.Duration field if set. Otherwise, return
55
// absl::nullopt.
56
#define PROTOBUF_GET_OPTIONAL_MS(message, field_name)                                              \
57
30043
  ((message).has_##field_name()                                                                    \
58
30043
       ? absl::optional<std::chrono::milliseconds>(                                                \
59
246
             DurationUtil::durationToMilliseconds((message).field_name()))                         \
60
30043
       : absl::nullopt)
61

            
62
// Obtain the milliseconds value of a google.protobuf.Duration field if set. Otherwise, throw an
63
// EnvoyException.
64
#define PROTOBUF_GET_MS_REQUIRED(message, field_name)                                              \
65
1559
  ([](const auto& msg) {                                                                           \
66
1559
    if (!msg.has_##field_name()) {                                                                 \
67
      ::Envoy::ProtoExceptionUtil::throwMissingFieldException(#field_name, msg);                   \
68
    }                                                                                              \
69
1559
    return DurationUtil::durationToMilliseconds(msg.field_name());                                 \
70
1559
  }((message)))
71

            
72
// Obtain the seconds value of a google.protobuf.Duration field if set. Otherwise, return the
73
// default value.
74
#define PROTOBUF_GET_SECONDS_OR_DEFAULT(message, field_name, default_value)                        \
75
718
  ((message).has_##field_name() ? DurationUtil::durationToSeconds((message).field_name())          \
76
718
                                : (default_value))
77

            
78
// Obtain the seconds value of a google.protobuf.Duration field if set. Otherwise, throw an
79
// EnvoyException.
80
#define PROTOBUF_GET_SECONDS_REQUIRED(message, field_name)                                         \
81
  ([](const auto& msg) {                                                                           \
82
    if (!msg.has_##field_name()) {                                                                 \
83
      ::Envoy::ProtoExceptionUtil::throwMissingFieldException(#field_name, msg);                   \
84
    }                                                                                              \
85
    return DurationUtil::durationToSeconds(msg.field_name());                                      \
86
  }((message)))
87

            
88
namespace Envoy {
89
namespace ProtobufPercentHelper {
90

            
91
// The following are helpers used in the PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT macro.
92
// This avoids a giant macro mess when trying to do asserts, casts, etc.
93
uint64_t checkAndReturnDefault(uint64_t default_value, uint64_t max_value);
94
uint64_t convertPercent(double percent, uint64_t max_value);
95

            
96
/**
97
 * Given a fractional percent chance of a given event occurring, evaluate to a yes/no decision
98
 * based on a provided random value.
99
 * @param percent the chance of a given event happening.
100
 * @param random_value supplies a numerical value to use to evaluate the event.
101
 * @return bool decision about whether the event should occur.
102
 */
103
bool evaluateFractionalPercent(envoy::type::v3::FractionalPercent percent, uint64_t random_value);
104

            
105
/**
106
 * Convert a fractional percent denominator enum into an integer.
107
 * @param denominator supplies denominator to convert.
108
 * @return the converted denominator.
109
 */
110
uint64_t fractionalPercentDenominatorToInt(
111
    const envoy::type::v3::FractionalPercent::DenominatorType& denominator);
112

            
113
} // namespace ProtobufPercentHelper
114
} // namespace Envoy
115

            
116
// Convert an envoy::type::v3::Percent to a double or a default.
117
// @param message supplies the proto message containing the field.
118
// @param field_name supplies the field name in the message.
119
// @param default_value supplies the default if the field is not present.
120
#define PROTOBUF_PERCENT_TO_DOUBLE_OR_DEFAULT(message, field_name, default_value)                  \
121
21738
  ([](const auto& msg) -> double {                                                                 \
122
21738
    if (std::isnan(msg.field_name().value())) {                                                    \
123
      ::Envoy::ExceptionUtil::throwEnvoyException(                                                 \
124
          fmt::format("Value not in the range of 0..100 range."));                                 \
125
    }                                                                                              \
126
21738
    return (msg).has_##field_name() ? (msg).field_name().value() : default_value;                  \
127
21738
  }((message)))
128
// Convert an envoy::type::v3::Percent to a rounded integer or a default.
129
// @param message supplies the proto message containing the field.
130
// @param field_name supplies the field name in the message.
131
// @param max_value supplies the maximum allowed integral value (e.g., 100, 10000, etc.).
132
// @param default_value supplies the default if the field is not present.
133
//
134
// TODO(anirudhmurali): Recommended to capture and validate NaN values in PGV
135
// Issue: https://github.com/bufbuild/protoc-gen-validate/issues/85
136
#define PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT(message, field_name, max_value,             \
137
                                                       default_value)                              \
138
34091
  ([](const auto& msg) {                                                                           \
139
34091
    if (std::isnan(msg.field_name().value())) {                                                    \
140
      ::Envoy::ExceptionUtil::throwEnvoyException(                                                 \
141
          fmt::format("Value not in the range of 0..100 range."));                                 \
142
    }                                                                                              \
143
34091
    return (msg).has_##field_name()                                                                \
144
34091
               ? ProtobufPercentHelper::convertPercent((msg).field_name().value(), max_value)      \
145
34091
               : ProtobufPercentHelper::checkAndReturnDefault(default_value, max_value);           \
146
34091
  }((message)))
147

            
148
namespace Envoy {
149

            
150
class TypeUtil {
151
public:
152
  static absl::string_view typeUrlToDescriptorFullName(absl::string_view type_url);
153

            
154
  static std::string descriptorFullNameToTypeUrl(absl::string_view type);
155
};
156

            
157
class RepeatedPtrUtil {
158
public:
159
  static std::string join(const Protobuf::RepeatedPtrField<std::string>& source,
160
28392
                          const std::string& delimiter) {
161
28392
    return absl::StrJoin(std::vector<std::string>(source.begin(), source.end()), delimiter);
162
28392
  }
163

            
164
  template <class ProtoType>
165
3
  static std::string debugString(const Protobuf::RepeatedPtrField<ProtoType>& source) {
166
3
    return accumulateToString<ProtoType>(
167
3
        source, [](const Protobuf::Message& message) { return message.DebugString(); });
168
3
  }
169

            
170
  // Based on MessageUtil::hash() defined below.
171
  template <class ProtoType>
172
86
  static std::size_t hash(const Protobuf::RepeatedPtrField<ProtoType>& source) {
173
86
    std::string text;
174
86
#if defined(ENVOY_ENABLE_FULL_PROTOS)
175
86
    {
176
86
      Protobuf::TextFormat::Printer printer;
177
86
      printer.SetExpandAny(true);
178
86
      printer.SetUseFieldNumber(true);
179
86
      printer.SetSingleLineMode(true);
180
86
      printer.SetHideUnknownFields(true);
181
86
      for (const auto& message : source) {
182
8
        std::string text_message;
183
8
        printer.PrintToString(message, &text_message);
184
8
        absl::StrAppend(&text, text_message);
185
8
      }
186
86
    }
187
#else
188
    for (const auto& message : source) {
189
      absl::StrAppend(&text, message.SerializeAsString());
190
    }
191
#endif
192
86
    return HashUtil::xxHash64(text);
193
86
  }
194

            
195
  /**
196
   * Converts a proto repeated field into a container of const Protobuf::Message unique_ptr's.
197
   *
198
   * @param repeated_field the proto repeated field to convert.
199
   * @return ReturnType the container of const Message pointers.
200
   */
201
  template <typename ProtoType, typename ReturnType>
202
  static ReturnType
203
160
  convertToConstMessagePtrContainer(const Protobuf::RepeatedPtrField<ProtoType>& repeated_field) {
204
160
    ReturnType ret_container;
205
160
    std::transform(repeated_field.begin(), repeated_field.end(), std::back_inserter(ret_container),
206
164
                   [](const ProtoType& proto_message) -> std::unique_ptr<const Protobuf::Message> {
207
112
                     Protobuf::Message* clone = proto_message.New();
208
112
                     clone->CheckTypeAndMergeFrom(proto_message);
209
112
                     return std::unique_ptr<const Protobuf::Message>(clone);
210
112
                   });
211
160
    return ret_container;
212
160
  }
213
};
214

            
215
using ProtoValidationException = EnvoyException;
216

            
217
/**
218
 * utility functions to call when throwing exceptions in header files
219
 */
220
class ProtoExceptionUtil {
221
public:
222
  static void throwMissingFieldException(const std::string& field_name,
223
                                         const Protobuf::Message& message);
224
  static void throwProtoValidationException(const std::string& validation_error,
225
                                            const Protobuf::Message& message);
226
};
227

            
228
class MessageUtil {
229
public:
230
  // std::hash
231
494
  std::size_t operator()(const Protobuf::Message& message) const { return hash(message); }
232

            
233
  // std::equals_to
234
7573
  bool operator()(const Protobuf::Message& lhs, const Protobuf::Message& rhs) const {
235
7573
    return Protobuf::util::MessageDifferencer::Equals(lhs, rhs);
236
7573
  }
237

            
238
  class FileExtensionValues {
239
  public:
240
    const std::string ProtoBinary = ".pb";
241
    const std::string ProtoBinaryLengthDelimited = ".pb_length_delimited";
242
    const std::string ProtoText = ".pb_text";
243
    const std::string Json = ".json";
244
    const std::string Yaml = ".yaml";
245
    const std::string Yml = ".yml";
246
  };
247

            
248
  using FileExtensions = ConstSingleton<FileExtensionValues>;
249

            
250
  /**
251
   * A hash function uses Protobuf::TextFormat to force deterministic serialization recursively
252
   * including known types in google.protobuf.Any. See
253
   * https://github.com/protocolbuffers/protobuf/issues/5731 for the context.
254
   * Using this function is discouraged, see discussion in
255
   * https://github.com/envoyproxy/envoy/issues/8301.
256
   */
257
  static std::size_t hash(const Protobuf::Message& message);
258

            
259
#ifdef ENVOY_ENABLE_YAML
260
  static void loadFromJson(absl::string_view json, Protobuf::Message& message,
261
                           ProtobufMessage::ValidationVisitor& validation_visitor);
262
  /**
263
   * Return ok only when strict conversion(don't ignore unknown field) succeeds.
264
   * Return error status for strict conversion and set has_unknown_field to true if relaxed
265
   * conversion(ignore unknown field) succeeds.
266
   * Return error status for relaxed conversion and set has_unknown_field to false if relaxed
267
   * conversion(ignore unknown field) fails.
268
   */
269
  static absl::Status loadFromJsonNoThrow(absl::string_view json, Protobuf::Message& message,
270
                                          bool& has_unknown_field);
271
  static absl::Status loadFromJsonNoThrow(absl::string_view json, Protobuf::Struct& message);
272
  static void loadFromJson(absl::string_view json, Protobuf::Struct& message);
273
  static void loadFromYaml(const std::string& yaml, Protobuf::Message& message,
274
                           ProtobufMessage::ValidationVisitor& validation_visitor);
275
#endif
276

            
277
  // This function attempts to load Envoy configuration from the specified file
278
  // based on the file type.
279
  // It handles .pb .pb_text .json .yaml and .yml files which are well
280
  // structured based on the file type.
281
  // It has somewhat inconsistent handling of invalid file contents,
282
  // occasionally failing over to try another type of parsing, or silently
283
  // failing instead of throwing an exception.
284
  static absl::Status loadFromFile(const std::string& path, Protobuf::Message& message,
285
                                   ProtobufMessage::ValidationVisitor& validation_visitor,
286
                                   Api::Api& api);
287

            
288
  /**
289
   * Checks for use of deprecated fields in message and all sub-messages.
290
   * @param message message to validate.
291
   * @param validation_visitor the validation visitor to use.
292
   * @param recurse_into_any whether to recurse into Any messages during unexpected checking.
293
   * @throw EnvoyException if deprecated fields are used and listed
294
   *    in disallowed_features in runtime_features.h
295
   */
296
  static void checkForUnexpectedFields(const Protobuf::Message& message,
297
                                       ProtobufMessage::ValidationVisitor& validation_visitor,
298
                                       bool recurse_into_any = false);
299

            
300
  /**
301
   * Validates that duration fields in the config are valid.
302
   * @param message message to validate.
303
   * @param recurse_into_any whether to recurse into Any messages during unexpected checking.
304
   * @throw EnvoyException if a duration field is invalid.
305
   */
306
  static void validateDurationFields(const Protobuf::Message& message,
307
                                     bool recurse_into_any = false);
308

            
309
  /**
310
   * Perform a PGV check on the entire message tree, recursing into Any messages as needed.
311
   */
312
  static void recursivePgvCheck(const Protobuf::Message& message);
313

            
314
  /**
315
   * Validate protoc-gen-validate constraints on a given protobuf as well as performing
316
   * unexpected field validation.
317
   * Note the corresponding `.pb.validate.h` for the message has to be included in the source file
318
   * of caller.
319
   * @param message message to validate.
320
   * @param validation_visitor the validation visitor to use.
321
   * @param recurse_into_any whether to recurse into Any messages during unexpected checking.
322
   * @throw EnvoyException if the message does not satisfy its type constraints.
323
   */
324
  template <class MessageType>
325
  static void validate(const MessageType& message,
326
                       ProtobufMessage::ValidationVisitor& validation_visitor,
327
147501
                       bool recurse_into_any = false) {
328
    // TODO(adisuissa): There are multiple recursive traversals done by the
329
    // calls in this function. This can be refactored into a single recursive
330
    // traversal that invokes the various validators.
331

            
332
    // Log warnings or throw errors if deprecated fields or unknown fields are in use.
333
147501
    if (!validation_visitor.skipValidation()) {
334
147403
      checkForUnexpectedFields(message, validation_visitor, recurse_into_any);
335
147403
    }
336

            
337
    // Throw an exception if the config has an invalid Duration field. This is needed
338
    // because Envoy validates the duration in a strict way that is not supported by PGV.
339
147501
    validateDurationFields(message, recurse_into_any);
340

            
341
    // TODO(mattklein123): This will recurse the message twice, once above and once for PGV. When
342
    // we move to always recursing, satisfying the TODO below, we should merge into a single
343
    // recursion for performance reasons.
344
147501
    if (recurse_into_any) {
345
7
      return recursivePgvCheck(message);
346
7
    }
347

            
348
    // TODO(mattklein123): Now that PGV is capable of doing recursive message checks on abstract
349
    // types, we can remove bottom up validation from the entire codebase and only validate
350
    // at top level ingestion (bootstrap, discovery response). This is a large change and will be
351
    // done as a separate PR. This change will also allow removing templating from most/all of
352
    // related functions.
353
147494
    std::string err;
354
147494
    if (!Validate(message, &err)) {
355
196
      ProtoExceptionUtil::throwProtoValidationException(err, message);
356
196
    }
357
147494
  }
358

            
359
#ifdef ENVOY_ENABLE_YAML
360
  template <class MessageType>
361
  static void loadFromYamlAndValidate(const std::string& yaml, MessageType& message,
362
1454
                                      ProtobufMessage::ValidationVisitor& validation_visitor) {
363
1454
    loadFromYaml(yaml, message, validation_visitor);
364
1454
    validate(message, validation_visitor);
365
1454
  }
366
#endif
367

            
368
  /**
369
   * Downcast and validate protoc-gen-validate constraints on a given protobuf.
370
   * Note the corresponding `.pb.validate.h` for the message has to be included in the source file
371
   * of caller.
372
   * @param message const Protobuf::Message& to downcast and validate.
373
   * @return const MessageType& the concrete message type downcasted to on success.
374
   * @throw EnvoyException if the message does not satisfy its type constraints.
375
   */
376
  template <class MessageType>
377
  static const MessageType&
378
  downcastAndValidate(const Protobuf::Message& config,
379
118858
                      ProtobufMessage::ValidationVisitor& validation_visitor) {
380
118858
    const auto& typed_config = dynamic_cast<MessageType>(config);
381
118858
    validate(typed_config, validation_visitor);
382
118858
    return typed_config;
383
118858
  }
384

            
385
  /**
386
   * Utility method to swap between protobuf bytes type using absl::Cord instead of std::string.
387
   * Noop for now.
388
   */
389
687
  static const std::string& bytesToString(const std::string& bytes) { return bytes; }
390

            
391
  /**
392
   * Convert from a typed message into a google.protobuf.Any. This should be used
393
   * instead of the inbuilt PackTo, as PackTo is not available with lite protos.
394
   *
395
   * @param any_message destination google.protobuf.Any.
396
   * @param message source to pack from.
397
   *
398
   * @throw EnvoyException if the message does not unpack.
399
   */
400
  static void packFrom(Protobuf::Any& any_message, const Protobuf::Message& message);
401

            
402
  /**
403
   * Convert from google.protobuf.Any to a typed message. This should be used
404
   * instead of the inbuilt UnpackTo as it performs validation of results.
405
   *
406
   * @param any_message source google.protobuf.Any message.
407
   * @param message destination to unpack to.
408
   *
409
   * @return absl::Status
410
   */
411
  static absl::Status unpackTo(const Protobuf::Any& any_message, Protobuf::Message& message);
412

            
413
  /**
414
   * Convert from google.protobuf.Any to bytes as std::string
415
   *
416
   * NOTE: prefer to use knownAnyToBytes() instead of this function, which has additional support
417
   * for google.protobuf.Struct. This is kept temporarily for backward compatibility.
418
   *
419
   * @param any source google.protobuf.Any message.
420
   *
421
   * @return std::string consists of bytes in the input message or error status.
422
   */
423
1238
  static absl::StatusOr<std::string> anyToBytes(const Protobuf::Any& any) {
424
1238
    if (any.Is<Protobuf::StringValue>()) {
425
640
      Protobuf::StringValue s;
426
640
      RETURN_IF_NOT_OK(MessageUtil::unpackTo(any, s));
427
639
      return s.value();
428
640
    }
429
598
    if (any.Is<Protobuf::BytesValue>()) {
430
3
      Protobuf::BytesValue b;
431
3
      RETURN_IF_NOT_OK(MessageUtil::unpackTo(any, b));
432
3
      return bytesToString(b.value());
433
3
    }
434
595
    return bytesToString(any.value());
435
598
  };
436

            
437
#ifdef ENVOY_ENABLE_YAML
438

            
439
  /**
440
   * Convert from google.protobuf.Any to bytes as std::string, with additional support for
441
   * google.protobuf.Struct which is serialized to JSON.
442
   * @param any source google.protobuf.Any message.
443
   *
444
   * @return std::string consists of bytes (StringValue/BytesValue), JSON (Struct), or raw bytes.
445
   */
446
204
  static absl::StatusOr<std::string> knownAnyToBytes(const Protobuf::Any& any) {
447
204
    if (any.Is<Protobuf::StringValue>()) {
448
131
      Protobuf::StringValue s;
449
131
      RETURN_IF_NOT_OK(MessageUtil::unpackTo(any, s));
450
129
      return s.value();
451
131
    }
452
73
    if (any.Is<Protobuf::BytesValue>()) {
453
9
      Protobuf::BytesValue b;
454
9
      RETURN_IF_NOT_OK(MessageUtil::unpackTo(any, b));
455
9
      return bytesToString(b.value());
456
9
    }
457
64
    if (any.Is<Protobuf::Struct>()) {
458
6
      Protobuf::Struct s;
459
6
      RETURN_IF_NOT_OK(MessageUtil::unpackTo(any, s));
460
6
      return getJsonStringFromMessage(s);
461
6
    }
462
58
    return bytesToString(any.value());
463
64
  };
464

            
465
#endif
466

            
467
  /**
468
   * Convert from google.protobuf.Any to a typed message.
469
   * @param message source google.protobuf.Any message.
470
   *
471
   * @return MessageType the typed message inside the Any.
472
   */
473
  template <class MessageType>
474
59870
  static inline void anyConvert(const Protobuf::Any& message, MessageType& typed_message) {
475
59870
    THROW_IF_NOT_OK(unpackTo(message, typed_message));
476
59870
  };
477

            
478
44482
  template <class MessageType> static inline MessageType anyConvert(const Protobuf::Any& message) {
479
44482
    MessageType typed_message;
480
44482
    anyConvert(message, typed_message);
481
44482
    return typed_message;
482
44482
  };
483

            
484
  /**
485
   * Convert and validate from google.protobuf.Any to a typed message.
486
   * @param message source google.protobuf.Any message.
487
   *
488
   * @return MessageType the typed message inside the Any.
489
   * @throw EnvoyException if the message does not satisfy its type constraints.
490
   */
491
  template <class MessageType>
492
  static inline void anyConvertAndValidate(const Protobuf::Any& message, MessageType& typed_message,
493
15388
                                           ProtobufMessage::ValidationVisitor& validation_visitor) {
494
15388
    anyConvert<MessageType>(message, typed_message);
495
15388
    validate(typed_message, validation_visitor);
496
15388
  };
497

            
498
  template <class MessageType>
499
  static inline MessageType
500
  anyConvertAndValidate(const Protobuf::Any& message,
501
193
                        ProtobufMessage::ValidationVisitor& validation_visitor) {
502
193
    MessageType typed_message;
503
193
    anyConvertAndValidate<MessageType>(message, typed_message, validation_visitor);
504
193
    return typed_message;
505
193
  };
506

            
507
  /**
508
   * Obtain a string field from a protobuf message dynamically.
509
   *
510
   * @param message message to extract from.
511
   * @param field_name field name.
512
   *
513
   * @return std::string with field value.
514
   */
515
  static inline std::string getStringField(const Protobuf::Message& message,
516
13605
                                           const std::string& field_name) {
517
13605
    Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message);
518
13605
    const Protobuf::Descriptor* descriptor = reflectable_message->GetDescriptor();
519
13605
    const Protobuf::FieldDescriptor* name_field = descriptor->FindFieldByName(field_name);
520
13605
    const Protobuf::Reflection* reflection = reflectable_message->GetReflection();
521
13605
    return reflection->GetString(*reflectable_message, name_field);
522
13605
  }
523

            
524
#ifdef ENVOY_ENABLE_YAML
525
  /**
526
   * Convert between two protobufs via a JSON round-trip. This is used to translate arbitrary
527
   * messages to/from google.protobuf.Struct.
528
   * TODO(htuch): Avoid round-tripping via JSON strings by doing whatever
529
   * Protobuf::util::MessageToJsonString does but generating a google.protobuf.Struct instead.
530
   * @param source message.
531
   * @param dest message.
532
   */
533
  static void jsonConvert(const Protobuf::Message& source, Protobuf::Message& dest);
534
  static void jsonConvert(const Protobuf::Message& source, Protobuf::Struct& dest);
535
  static void jsonConvert(const Protobuf::Struct& source,
536
                          ProtobufMessage::ValidationVisitor& validation_visitor,
537
                          Protobuf::Message& dest);
538
  // Convert a message to a Protobuf::Value, return false upon failure.
539
  static bool jsonConvertValue(const Protobuf::Message& source, Protobuf::Value& dest);
540

            
541
  /**
542
   * Extract YAML as string from a google.protobuf.Message.
543
   * @param message message of type type.googleapis.com/google.protobuf.Message.
544
   * @param block_print whether the returned JSON should be in block style rather than flow style.
545
   * @param always_print_primitive_fields whether to include primitive fields set to their default
546
   * values, e.g. an int32 set to 0 or a bool set to false.
547
   * @return std::string of formatted YAML object.
548
   */
549
  static std::string getYamlStringFromMessage(const Protobuf::Message& message,
550
                                              const bool block_print = true,
551
                                              const bool always_print_primitive_fields = false);
552

            
553
  /**
554
   * Extract JSON as string from a google.protobuf.Message. Returns an error if the message cannot
555
   * be represented as JSON, which can occur if it contains an Any proto with an unrecognized type
556
   * URL or invalid data, or if memory cannot be allocated.
557
   * @param message message of type type.googleapis.com/google.protobuf.Message.
558
   * @param pretty_print whether the returned JSON should be formatted.
559
   * @param always_print_primitive_fields whether to include primitive fields set to their default
560
   * values, e.g. an int32 set to 0 or a bool set to false.
561
   * @return ProtobufUtil::StatusOr<std::string> of formatted JSON object, or an error status if
562
   * conversion fails.
563
   */
564
  static absl::StatusOr<std::string>
565
  getJsonStringFromMessage(const Protobuf::Message& message, bool pretty_print = false,
566
                           bool always_print_primitive_fields = false);
567

            
568
  /**
569
   * Extract JSON as string from a google.protobuf.Message, returning some error string if the
570
   * conversion to JSON fails.
571
   * @param message message of type type.googleapis.com/google.protobuf.Message.
572
   * @param pretty_print whether the returned JSON should be formatted.
573
   * @param always_print_primitive_fields whether to include primitive fields set to their default
574
   * values, e.g. an int32 set to 0 or a bool set to false.
575
   * @return std::string of formatted JSON object, or an error message if conversion fails.
576
   */
577
  static std::string getJsonStringFromMessageOrError(const Protobuf::Message& message,
578
                                                     bool pretty_print = false,
579
                                                     bool always_print_primitive_fields = false);
580
#endif
581

            
582
  static std::string convertToStringForLogs(const Protobuf::Message& message,
583
                                            bool pretty_print = false,
584
                                            bool always_print_primitive_fields = false);
585

            
586
  /**
587
   * Utility method to create a Struct containing the passed in key/value strings.
588
   *
589
   * @param key the key to use to set the value
590
   * @param value the string value to associate with the key
591
   */
592
  static Protobuf::Struct keyValueStruct(const std::string& key, const std::string& value);
593

            
594
  /**
595
   * Utility method to create a Struct containing the passed in key/value map.
596
   *
597
   * @param fields the key/value pairs to initialize the Struct proto
598
   */
599
  static Protobuf::Struct keyValueStruct(const std::map<std::string, std::string>& fields);
600

            
601
  /**
602
   * Utility method to print a human readable string of the code passed in.
603
   *
604
   * @param code the protobuf error code
605
   */
606
  static std::string codeEnumToString(absl::StatusCode code);
607

            
608
  /**
609
   * Modifies a message such that all sensitive data (that is, fields annotated as
610
   * `udpa.annotations.sensitive`) is redacted for display. String-typed fields annotated as
611
   * `sensitive` will be replaced with the string "[redacted]", bytes-typed fields will be replaced
612
   * with the bytes `5B72656461637465645D` (the ASCII / UTF-8 encoding of the string "[redacted]"),
613
   * primitive-typed fields (including enums) will be cleared, and message-typed fields will be
614
   * traversed recursively to redact their contents.
615
   *
616
   * LIMITATION: This works properly for strongly-typed messages, as well as for messages packed in
617
   * a `Protobuf::Any` with a `type_url` corresponding to a proto that was compiled into the
618
   * Envoy binary. However it does not work for messages encoded as `Protobuf::Struct`, since
619
   * structs are missing the "sensitive" annotations that this function expects. Similarly, it fails
620
   * for messages encoded as `Protobuf::Any` with a `type_url` that isn't registered with the
621
   * binary. If you're working with struct-typed messages, including those that might be hiding
622
   * within strongly-typed messages, please reify them to strongly-typed messages using
623
   * `MessageUtil::jsonConvert()` before calling `MessageUtil::redact()`.
624
   *
625
   * @param message message to redact.
626
   */
627
  static void redact(Protobuf::Message& message);
628

            
629
  /**
630
   * Sanitizes a string to contain only valid UTF-8. Invalid UTF-8 characters will be replaced. If
631
   * the input string is valid UTF-8, it will be returned unmodified.
632
   */
633
  static std::string sanitizeUtf8String(absl::string_view str);
634

            
635
  /**
636
   * Return text proto representation of the `message`.
637
   * @param message proto to print.
638
   * @return text representation of the proto `message`.
639
   */
640
  static std::string toTextProto(const Protobuf::Message& message);
641
};
642

            
643
class ValueUtil {
644
public:
645
1575
  static std::size_t hash(const Protobuf::Value& value) { return MessageUtil::hash(value); }
646

            
647
#ifdef ENVOY_ENABLE_YAML
648
  /**
649
   * Load YAML string into Protobuf::Value.
650
   */
651
  static Protobuf::Value loadFromYaml(const std::string& yaml);
652
#endif
653

            
654
  /**
655
   * Compare two Protobuf::Values for equality.
656
   * @param v1 message of type type.googleapis.com/google.protobuf.Value
657
   * @param v2 message of type type.googleapis.com/google.protobuf.Value
658
   * @return true if v1 and v2 are identical
659
   */
660
  static bool equal(const Protobuf::Value& v1, const Protobuf::Value& v2);
661

            
662
  /**
663
   * @return wrapped Protobuf::NULL_VALUE.
664
   */
665
  static const Protobuf::Value& nullValue();
666

            
667
  /**
668
   * Wrap absl::string_view into Protobuf::Value string value.
669
   * @param str string to be wrapped.
670
   * @return wrapped string.
671
   */
672
  static Protobuf::Value stringValue(absl::string_view str);
673

            
674
  /**
675
   * Wrap optional std::string into Protobuf::Value string value.
676
   * If the argument contains a null optional, return Protobuf::NULL_VALUE.
677
   * @param str string to be wrapped.
678
   * @return wrapped string.
679
   */
680
  static Protobuf::Value optionalStringValue(const absl::optional<std::string>& str);
681

            
682
  /**
683
   * Wrap boolean into Protobuf::Value boolean value.
684
   * @param str boolean to be wrapped.
685
   * @return wrapped boolean.
686
   */
687
  static Protobuf::Value boolValue(bool b);
688

            
689
  /**
690
   * Wrap Protobuf::Struct into Protobuf::Value struct value.
691
   * @param obj struct to be wrapped.
692
   * @return wrapped struct.
693
   */
694
  static Protobuf::Value structValue(const Protobuf::Struct& obj);
695

            
696
  /**
697
   * Wrap number into Protobuf::Value double value.
698
   * @param num number to be wrapped.
699
   * @return wrapped number.
700
   */
701
1148
  template <typename T> static Protobuf::Value numberValue(const T num) {
702
1148
    Protobuf::Value val;
703
1148
    val.set_number_value(static_cast<double>(num));
704
1148
    return val;
705
1148
  }
706

            
707
  /**
708
   * Wrap a collection of Protobuf::Values into Protobuf::Value list value.
709
   * @param values collection of Protobuf::Values to be wrapped.
710
   * @return wrapped list value.
711
   */
712
  static Protobuf::Value listValue(const std::vector<Protobuf::Value>& values);
713
};
714

            
715
/**
716
 * HashedValue is a wrapper around Protobuf::Value that computes
717
 * and stores a hash code for the Value at construction.
718
 */
719
class HashedValue {
720
public:
721
1574
  HashedValue(const Protobuf::Value& value) : value_(value), hash_(ValueUtil::hash(value)) {};
722
1650
  HashedValue(const HashedValue& v) = default;
723

            
724
93
  const Protobuf::Value& value() const { return value_; }
725
2203
  std::size_t hash() const { return hash_; }
726

            
727
2303
  bool operator==(const HashedValue& rhs) const {
728
2303
    return hash_ == rhs.hash_ && ValueUtil::equal(value_, rhs.value_);
729
2303
  }
730

            
731
1
  bool operator!=(const HashedValue& rhs) const { return !(*this == rhs); }
732

            
733
private:
734
  const Protobuf::Value value_;
735
  const std::size_t hash_;
736
};
737

            
738
class DurationUtil {
739
public:
740
  /**
741
   * Same as DurationUtil::durationToMilliseconds but with extra validation logic.
742
   * Same as Protobuf::util::TimeUtil::DurationToSeconds but with extra validation logic.
743
   * Specifically, we ensure that the duration is positive.
744
   * @param duration protobuf.
745
   * @return duration in milliseconds.
746
   * @throw EnvoyException when duration is out-of-range.
747
   */
748
  static uint64_t durationToMilliseconds(const Protobuf::Duration& duration);
749

            
750
  /**
751
   * Same as DurationUtil::durationToMilliseconds but does not throw an exception.
752
   * @param duration protobuf.
753
   * @return duration in milliseconds or an error status.
754
   */
755
  static absl::StatusOr<uint64_t> durationToMillisecondsNoThrow(const Protobuf::Duration& duration);
756

            
757
  /**
758
   * Same as Protobuf::util::TimeUtil::DurationToSeconds but with extra validation logic.
759
   * Specifically, we ensure that the duration is positive.
760
   * @param duration protobuf.
761
   * @return duration in seconds.
762
   * @throw EnvoyException when duration is out-of-range.
763
   */
764
  static uint64_t durationToSeconds(const Protobuf::Duration& duration);
765
};
766

            
767
class TimestampUtil {
768
public:
769
  /**
770
   * Writes a time_point<system_clock> (SystemTime) to a protobuf Timestamp, by way of time_t.
771
   * @param system_clock_time the time to write
772
   * @param timestamp a pointer to the mutable protobuf member to be written into.
773
   */
774
  static void systemClockToTimestamp(const SystemTime system_clock_time,
775
                                     Protobuf::Timestamp& timestamp);
776
};
777

            
778
class StructUtil {
779
public:
780
  /**
781
   * Recursively updates in-place a protobuf structure with keys from another
782
   * object.
783
   *
784
   * The merging strategy is the following. If a key from \p other does not
785
   * exists, it's just copied into \p obj. If the key exists but has a
786
   * different type, it is replaced by the new value. Otherwise:
787
   * - for scalar values (null, string, number, boolean) are replaced with the new value
788
   * - for lists: new values are added to the current list
789
   * - for structures: recursively apply this scheme
790
   *
791
   * @param obj the object to update in-place
792
   * @param with the object to update \p obj with
793
   */
794
  static void update(Protobuf::Struct& obj, const Protobuf::Struct& with);
795
};
796

            
797
} // namespace Envoy
798

            
799
// Specialize std::hash on Envoy::HashedValue.
800
template <> struct std::hash<Envoy::HashedValue> {
801
2203
  std::size_t operator()(Envoy::HashedValue const& v) const { return v.hash(); }
802
};