Line data Source code
1 : #pragma once
2 :
3 : #include <numeric>
4 :
5 : #include "envoy/api/api.h"
6 : #include "envoy/common/exception.h"
7 : #include "envoy/protobuf/message_validator.h"
8 : #include "envoy/runtime/runtime.h"
9 : #include "envoy/type/v3/percent.pb.h"
10 :
11 : #include "source/common/common/hash.h"
12 : #include "source/common/common/stl_helpers.h"
13 : #include "source/common/common/utility.h"
14 : #include "source/common/protobuf/protobuf.h"
15 : #include "source/common/singleton/const_singleton.h"
16 :
17 : #include "absl/status/status.h"
18 : #include "absl/status/statusor.h"
19 : #include "absl/strings/str_join.h"
20 :
21 : // Obtain the value of a wrapped field (e.g. google.protobuf.UInt32Value) if set. Otherwise, return
22 : // the default value.
23 : #define PROTOBUF_GET_WRAPPED_OR_DEFAULT(message, field_name, default_value) \
24 5293 : ((message).has_##field_name() ? (message).field_name().value() : (default_value))
25 :
26 : // Obtain the value of a wrapped field (e.g. google.protobuf.UInt32Value) if set. Otherwise, throw
27 : // a EnvoyException.
28 :
29 : #define PROTOBUF_GET_WRAPPED_REQUIRED(message, field_name) \
30 304 : ([](const auto& msg) { \
31 304 : if (!msg.has_##field_name()) { \
32 0 : ::Envoy::ProtoExceptionUtil::throwMissingFieldException(#field_name, msg); \
33 0 : } \
34 304 : return msg.field_name().value(); \
35 304 : }((message)))
36 : // Obtain the milliseconds value of a google.protobuf.Duration field if set. Otherwise, return the
37 : // default value.
38 : #define PROTOBUF_GET_MS_OR_DEFAULT(message, field_name, default_value) \
39 2347 : ((message).has_##field_name() ? DurationUtil::durationToMilliseconds((message).field_name()) \
40 2347 : : (default_value))
41 :
42 : // Obtain the string value if the field is set. Otherwise, return the default value.
43 : #define PROTOBUF_GET_STRING_OR_DEFAULT(message, field_name, default_value) \
44 : (!(message).field_name().empty() ? (message).field_name() : (default_value))
45 :
46 : // Obtain the milliseconds value of a google.protobuf.Duration field if set. Otherwise, return
47 : // absl::nullopt.
48 : #define PROTOBUF_GET_OPTIONAL_MS(message, field_name) \
49 16 : ((message).has_##field_name() \
50 16 : ? absl::optional<std::chrono::milliseconds>( \
51 0 : DurationUtil::durationToMilliseconds((message).field_name())) \
52 16 : : absl::nullopt)
53 :
54 : // Obtain the milliseconds value of a google.protobuf.Duration field if set. Otherwise, throw an
55 : // EnvoyException.
56 : #define PROTOBUF_GET_MS_REQUIRED(message, field_name) \
57 355 : ([](const auto& msg) { \
58 355 : if (!msg.has_##field_name()) { \
59 3 : ::Envoy::ProtoExceptionUtil::throwMissingFieldException(#field_name, msg); \
60 3 : } \
61 355 : return DurationUtil::durationToMilliseconds(msg.field_name()); \
62 355 : }((message)))
63 :
64 : // Obtain the milliseconds value of a google.protobuf.Duration field if set. Otherwise, return the
65 : // default value.
66 : #define PROTOBUF_GET_SECONDS_OR_DEFAULT(message, field_name, default_value) \
67 : ((message).has_##field_name() ? DurationUtil::durationToSeconds((message).field_name()) \
68 : : (default_value))
69 :
70 : // Obtain the seconds value of a google.protobuf.Duration field if set. Otherwise, throw an
71 : // EnvoyException.
72 : #define PROTOBUF_GET_SECONDS_REQUIRED(message, field_name) \
73 : ([](const auto& msg) { \
74 : if (!msg.has_##field_name()) { \
75 : ::Envoy::ProtoExceptionUtil::throwMissingFieldException(#field_name, msg); \
76 : } \
77 : return DurationUtil::durationToSeconds(msg.field_name()); \
78 : }((message)))
79 :
80 : namespace Envoy {
81 : namespace ProtobufPercentHelper {
82 :
83 : // The following are helpers used in the PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT macro.
84 : // This avoids a giant macro mess when trying to do asserts, casts, etc.
85 : uint64_t checkAndReturnDefault(uint64_t default_value, uint64_t max_value);
86 : uint64_t convertPercent(double percent, uint64_t max_value);
87 :
88 : /**
89 : * Given a fractional percent chance of a given event occurring, evaluate to a yes/no decision
90 : * based on a provided random value.
91 : * @param percent the chance of a given event happening.
92 : * @param random_value supplies a numerical value to use to evaluate the event.
93 : * @return bool decision about whether the event should occur.
94 : */
95 : bool evaluateFractionalPercent(envoy::type::v3::FractionalPercent percent, uint64_t random_value);
96 :
97 : /**
98 : * Convert a fractional percent denominator enum into an integer.
99 : * @param denominator supplies denominator to convert.
100 : * @return the converted denominator.
101 : */
102 : uint64_t fractionalPercentDenominatorToInt(
103 : const envoy::type::v3::FractionalPercent::DenominatorType& denominator);
104 :
105 : } // namespace ProtobufPercentHelper
106 : } // namespace Envoy
107 :
108 : // Convert an envoy::type::v3::Percent to a double or a default.
109 : // @param message supplies the proto message containing the field.
110 : // @param field_name supplies the field name in the message.
111 : // @param default_value supplies the default if the field is not present.
112 : #define PROTOBUF_PERCENT_TO_DOUBLE_OR_DEFAULT(message, field_name, default_value) \
113 278 : ([](const auto& msg) -> double { \
114 276 : if (std::isnan(msg.field_name().value())) { \
115 0 : ::Envoy::ExceptionUtil::throwEnvoyException( \
116 0 : fmt::format("Value not in the range of 0..100 range.")); \
117 0 : } \
118 276 : return (msg).has_##field_name() ? (msg).field_name().value() : default_value; \
119 276 : }((message)))
120 : // Convert an envoy::type::v3::Percent to a rounded integer or a default.
121 : // @param message supplies the proto message containing the field.
122 : // @param field_name supplies the field name in the message.
123 : // @param max_value supplies the maximum allowed integral value (e.g., 100, 10000, etc.).
124 : // @param default_value supplies the default if the field is not present.
125 : //
126 : // TODO(anirudhmurali): Recommended to capture and validate NaN values in PGV
127 : // Issue: https://github.com/bufbuild/protoc-gen-validate/issues/85
128 : #define PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT(message, field_name, max_value, \
129 : default_value) \
130 690 : ([](const auto& msg) { \
131 690 : if (std::isnan(msg.field_name().value())) { \
132 34 : ::Envoy::ExceptionUtil::throwEnvoyException( \
133 34 : fmt::format("Value not in the range of 0..100 range.")); \
134 34 : } \
135 690 : return (msg).has_##field_name() \
136 690 : ? ProtobufPercentHelper::convertPercent((msg).field_name().value(), max_value) \
137 690 : : ProtobufPercentHelper::checkAndReturnDefault(default_value, max_value); \
138 690 : }((message)))
139 :
140 : namespace Envoy {
141 :
142 : class TypeUtil {
143 : public:
144 : static absl::string_view typeUrlToDescriptorFullName(absl::string_view type_url);
145 :
146 : static std::string descriptorFullNameToTypeUrl(absl::string_view type);
147 : };
148 :
149 : class RepeatedPtrUtil {
150 : public:
151 : static std::string join(const Protobuf::RepeatedPtrField<std::string>& source,
152 0 : const std::string& delimiter) {
153 0 : return absl::StrJoin(std::vector<std::string>(source.begin(), source.end()), delimiter);
154 0 : }
155 :
156 : template <class ProtoType>
157 : static std::string debugString(const Protobuf::RepeatedPtrField<ProtoType>& source) {
158 : return accumulateToString<ProtoType>(
159 : source, [](const Protobuf::Message& message) { return message.DebugString(); });
160 : }
161 :
162 : // Based on MessageUtil::hash() defined below.
163 : template <class ProtoType>
164 0 : static std::size_t hash(const Protobuf::RepeatedPtrField<ProtoType>& source) {
165 0 : std::string text;
166 0 : #if defined(ENVOY_ENABLE_FULL_PROTOS)
167 0 : {
168 0 : Protobuf::TextFormat::Printer printer;
169 0 : printer.SetExpandAny(true);
170 0 : printer.SetUseFieldNumber(true);
171 0 : printer.SetSingleLineMode(true);
172 0 : printer.SetHideUnknownFields(true);
173 0 : for (const auto& message : source) {
174 0 : std::string text_message;
175 0 : printer.PrintToString(message, &text_message);
176 0 : absl::StrAppend(&text, text_message);
177 0 : }
178 0 : }
179 : #else
180 : for (const auto& message : source) {
181 : absl::StrAppend(&text, message.SerializeAsString());
182 : }
183 : #endif
184 0 : return HashUtil::xxHash64(text);
185 0 : }
186 :
187 : /**
188 : * Converts a proto repeated field into a container of const Protobuf::Message unique_ptr's.
189 : *
190 : * @param repeated_field the proto repeated field to convert.
191 : * @return ReturnType the container of const Message pointers.
192 : */
193 : template <typename ProtoType, typename ReturnType>
194 : static ReturnType
195 0 : convertToConstMessagePtrContainer(const Protobuf::RepeatedPtrField<ProtoType>& repeated_field) {
196 0 : ReturnType ret_container;
197 0 : std::transform(repeated_field.begin(), repeated_field.end(), std::back_inserter(ret_container),
198 0 : [](const ProtoType& proto_message) -> std::unique_ptr<const Protobuf::Message> {
199 0 : Protobuf::Message* clone = proto_message.New();
200 0 : clone->CheckTypeAndMergeFrom(proto_message);
201 0 : return std::unique_ptr<const Protobuf::Message>(clone);
202 0 : });
203 0 : return ret_container;
204 0 : }
205 : };
206 :
207 : using ProtoValidationException = EnvoyException;
208 :
209 : /**
210 : * utility functions to call when throwing exceptions in header files
211 : */
212 : class ProtoExceptionUtil {
213 : public:
214 : static void throwMissingFieldException(const std::string& field_name,
215 : const Protobuf::Message& message);
216 : static void throwProtoValidationException(const std::string& validation_error,
217 : const Protobuf::Message& message);
218 : };
219 :
220 : class MessageUtil {
221 : public:
222 : // std::hash
223 818 : std::size_t operator()(const Protobuf::Message& message) const { return hash(message); }
224 :
225 : // std::equals_to
226 63 : bool operator()(const Protobuf::Message& lhs, const Protobuf::Message& rhs) const {
227 63 : return Protobuf::util::MessageDifferencer::Equivalent(lhs, rhs);
228 63 : }
229 :
230 : class FileExtensionValues {
231 : public:
232 : const std::string ProtoBinary = ".pb";
233 : const std::string ProtoBinaryLengthDelimited = ".pb_length_delimited";
234 : const std::string ProtoText = ".pb_text";
235 : const std::string Json = ".json";
236 : const std::string Yaml = ".yaml";
237 : const std::string Yml = ".yml";
238 : };
239 :
240 : using FileExtensions = ConstSingleton<FileExtensionValues>;
241 :
242 : /**
243 : * A hash function uses Protobuf::TextFormat to force deterministic serialization recursively
244 : * including known types in google.protobuf.Any. See
245 : * https://github.com/protocolbuffers/protobuf/issues/5731 for the context.
246 : * Using this function is discouraged, see discussion in
247 : * https://github.com/envoyproxy/envoy/issues/8301.
248 : */
249 : static std::size_t hash(const Protobuf::Message& message);
250 :
251 : #ifdef ENVOY_ENABLE_YAML
252 : static void loadFromJson(const std::string& json, Protobuf::Message& message,
253 : ProtobufMessage::ValidationVisitor& validation_visitor);
254 : /**
255 : * Return ok only when strict conversion(don't ignore unknown field) succeeds.
256 : * Return error status for strict conversion and set has_unknown_field to true if relaxed
257 : * conversion(ignore unknown field) succeeds.
258 : * Return error status for relaxed conversion and set has_unknown_field to false if relaxed
259 : * conversion(ignore unknown field) fails.
260 : */
261 : static absl::Status loadFromJsonNoThrow(const std::string& json, Protobuf::Message& message,
262 : bool& has_unknown_fileld);
263 : static void loadFromJson(const std::string& json, ProtobufWkt::Struct& message);
264 : static void loadFromYaml(const std::string& yaml, Protobuf::Message& message,
265 : ProtobufMessage::ValidationVisitor& validation_visitor);
266 : static void loadFromFile(const std::string& path, Protobuf::Message& message,
267 : ProtobufMessage::ValidationVisitor& validation_visitor, Api::Api& api);
268 : #endif
269 :
270 : /**
271 : * Checks for use of deprecated fields in message and all sub-messages.
272 : * @param message message to validate.
273 : * @param validation_visitor the validation visitor to use.
274 : * @param recurse_into_any whether to recurse into Any messages during unexpected checking.
275 : * @throw EnvoyException if deprecated fields are used and listed
276 : * in disallowed_features in runtime_features.h
277 : */
278 : static void checkForUnexpectedFields(const Protobuf::Message& message,
279 : ProtobufMessage::ValidationVisitor& validation_visitor,
280 : bool recurse_into_any = false);
281 :
282 : /**
283 : * Perform a PGV check on the entire message tree, recursing into Any messages as needed.
284 : */
285 : static void recursivePgvCheck(const Protobuf::Message& message);
286 :
287 : /**
288 : * Validate protoc-gen-validate constraints on a given protobuf as well as performing
289 : * unexpected field validation.
290 : * Note the corresponding `.pb.validate.h` for the message has to be included in the source file
291 : * of caller.
292 : * @param message message to validate.
293 : * @param validation_visitor the validation visitor to use.
294 : * @param recurse_into_any whether to recurse into Any messages during unexpected checking.
295 : * @throw EnvoyException if the message does not satisfy its type constraints.
296 : */
297 : template <class MessageType>
298 : static void validate(const MessageType& message,
299 : ProtobufMessage::ValidationVisitor& validation_visitor,
300 10725 : bool recurse_into_any = false) {
301 : // Log warnings or throw errors if deprecated fields or unknown fields are in use.
302 10725 : if (!validation_visitor.skipValidation()) {
303 10725 : checkForUnexpectedFields(message, validation_visitor, recurse_into_any);
304 10725 : }
305 :
306 : // TODO(mattklein123): This will recurse the message twice, once above and once for PGV. When
307 : // we move to always recursing, satisfying the TODO below, we should merge into a single
308 : // recursion for performance reasons.
309 10725 : if (recurse_into_any) {
310 0 : return recursivePgvCheck(message);
311 0 : }
312 :
313 : // TODO(mattklein123): Now that PGV is capable of doing recursive message checks on abstract
314 : // types, we can remove bottom up validation from the entire codebase and only validate
315 : // at top level ingestion (bootstrap, discovery response). This is a large change and will be
316 : // done as a separate PR. This change will also allow removing templating from most/all of
317 : // related functions.
318 10725 : std::string err;
319 10725 : if (!Validate(message, &err)) {
320 3925 : ProtoExceptionUtil::throwProtoValidationException(err, message);
321 3925 : }
322 10725 : }
323 :
324 : #ifdef ENVOY_ENABLE_YAML
325 : template <class MessageType>
326 : static void loadFromYamlAndValidate(const std::string& yaml, MessageType& message,
327 0 : ProtobufMessage::ValidationVisitor& validation_visitor) {
328 0 : loadFromYaml(yaml, message, validation_visitor);
329 0 : validate(message, validation_visitor);
330 0 : }
331 : #endif
332 :
333 : /**
334 : * Downcast and validate protoc-gen-validate constraints on a given protobuf.
335 : * Note the corresponding `.pb.validate.h` for the message has to be included in the source file
336 : * of caller.
337 : * @param message const Protobuf::Message& to downcast and validate.
338 : * @return const MessageType& the concrete message type downcasted to on success.
339 : * @throw EnvoyException if the message does not satisfy its type constraints.
340 : */
341 : template <class MessageType>
342 : static const MessageType&
343 : downcastAndValidate(const Protobuf::Message& config,
344 1871 : ProtobufMessage::ValidationVisitor& validation_visitor) {
345 1871 : const auto& typed_config = dynamic_cast<MessageType>(config);
346 1871 : validate(typed_config, validation_visitor);
347 1871 : return typed_config;
348 1871 : }
349 :
350 : /**
351 : * Convert from a typed message into a google.protobuf.Any. This should be used
352 : * instead of the inbuilt PackTo, as PackTo is not available with lite protos.
353 : *
354 : * @param any_message destination google.protobuf.Any.
355 : * @param message source to pack from.
356 : *
357 : * @throw EnvoyException if the message does not unpack.
358 : */
359 : static void packFrom(ProtobufWkt::Any& any_message, const Protobuf::Message& message);
360 :
361 : /**
362 : * Convert from google.protobuf.Any to a typed message. This should be used
363 : * instead of the inbuilt UnpackTo as it performs validation of results.
364 : *
365 : * @param any_message source google.protobuf.Any message.
366 : * @param message destination to unpack to.
367 : *
368 : * @throw EnvoyException if the message does not unpack.
369 : */
370 : static void unpackTo(const ProtobufWkt::Any& any_message, Protobuf::Message& message);
371 :
372 : /**
373 : * Convert from google.protobuf.Any to a typed message. This should be used
374 : * instead of the inbuilt UnpackTo as it performs validation of results.
375 : *
376 : * @param any_message source google.protobuf.Any message.
377 : * @param message destination to unpack to.
378 : *
379 : * @return absl::Status
380 : */
381 : static absl::Status unpackToNoThrow(const ProtobufWkt::Any& any_message,
382 : Protobuf::Message& message);
383 :
384 : /**
385 : * Convert from google.protobuf.Any to bytes as std::string
386 : * @param any source google.protobuf.Any message.
387 : *
388 : * @return std::string consists of bytes in the input message.
389 : */
390 8 : static std::string anyToBytes(const ProtobufWkt::Any& any) {
391 8 : if (any.Is<ProtobufWkt::StringValue>()) {
392 0 : ProtobufWkt::StringValue s;
393 0 : MessageUtil::unpackTo(any, s);
394 0 : return s.value();
395 0 : }
396 8 : if (any.Is<ProtobufWkt::BytesValue>()) {
397 0 : Protobuf::BytesValue b;
398 0 : MessageUtil::unpackTo(any, b);
399 0 : return b.value();
400 0 : }
401 8 : return any.value();
402 8 : };
403 :
404 : /**
405 : * Convert from google.protobuf.Any to a typed message.
406 : * @param message source google.protobuf.Any message.
407 : *
408 : * @return MessageType the typed message inside the Any.
409 : */
410 : template <class MessageType>
411 598 : static inline void anyConvert(const ProtobufWkt::Any& message, MessageType& typed_message) {
412 598 : unpackTo(message, typed_message);
413 598 : };
414 :
415 : template <class MessageType>
416 200 : static inline MessageType anyConvert(const ProtobufWkt::Any& message) {
417 200 : MessageType typed_message;
418 200 : anyConvert(message, typed_message);
419 200 : return typed_message;
420 200 : };
421 :
422 : /**
423 : * Convert and validate from google.protobuf.Any to a typed message.
424 : * @param message source google.protobuf.Any message.
425 : *
426 : * @return MessageType the typed message inside the Any.
427 : * @throw EnvoyException if the message does not satisfy its type constraints.
428 : */
429 : template <class MessageType>
430 : static inline void anyConvertAndValidate(const ProtobufWkt::Any& message,
431 : MessageType& typed_message,
432 398 : ProtobufMessage::ValidationVisitor& validation_visitor) {
433 398 : anyConvert<MessageType>(message, typed_message);
434 398 : validate(typed_message, validation_visitor);
435 398 : };
436 :
437 : template <class MessageType>
438 : static inline MessageType
439 : anyConvertAndValidate(const ProtobufWkt::Any& message,
440 0 : ProtobufMessage::ValidationVisitor& validation_visitor) {
441 0 : MessageType typed_message;
442 0 : anyConvertAndValidate<MessageType>(message, typed_message, validation_visitor);
443 0 : return typed_message;
444 0 : };
445 :
446 : /**
447 : * Obtain a string field from a protobuf message dynamically.
448 : *
449 : * @param message message to extract from.
450 : * @param field_name field name.
451 : *
452 : * @return std::string with field value.
453 : */
454 : static inline std::string getStringField(const Protobuf::Message& message,
455 354 : const std::string& field_name) {
456 354 : Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message);
457 354 : const Protobuf::Descriptor* descriptor = reflectable_message->GetDescriptor();
458 354 : const Protobuf::FieldDescriptor* name_field = descriptor->FindFieldByName(field_name);
459 354 : const Protobuf::Reflection* reflection = reflectable_message->GetReflection();
460 354 : return reflection->GetString(*reflectable_message, name_field);
461 0 : return name_field->name();
462 354 : }
463 :
464 : #ifdef ENVOY_ENABLE_YAML
465 : /**
466 : * Convert between two protobufs via a JSON round-trip. This is used to translate arbitrary
467 : * messages to/from google.protobuf.Struct.
468 : * TODO(htuch): Avoid round-tripping via JSON strings by doing whatever
469 : * Protobuf::util::MessageToJsonString does but generating a google.protobuf.Struct instead.
470 : * @param source message.
471 : * @param dest message.
472 : */
473 : static void jsonConvert(const Protobuf::Message& source, Protobuf::Message& dest);
474 : static void jsonConvert(const Protobuf::Message& source, ProtobufWkt::Struct& dest);
475 : static void jsonConvert(const ProtobufWkt::Struct& source,
476 : ProtobufMessage::ValidationVisitor& validation_visitor,
477 : Protobuf::Message& dest);
478 : // Convert a message to a ProtobufWkt::Value, return false upon failure.
479 : static bool jsonConvertValue(const Protobuf::Message& source, ProtobufWkt::Value& dest);
480 :
481 : /**
482 : * Extract YAML as string from a google.protobuf.Message.
483 : * @param message message of type type.googleapis.com/google.protobuf.Message.
484 : * @param block_print whether the returned JSON should be in block style rather than flow style.
485 : * @param always_print_primitive_fields whether to include primitive fields set to their default
486 : * values, e.g. an int32 set to 0 or a bool set to false.
487 : * @return std::string of formatted YAML object.
488 : */
489 : static std::string getYamlStringFromMessage(const Protobuf::Message& message,
490 : const bool block_print = true,
491 : const bool always_print_primitive_fields = false);
492 :
493 : /**
494 : * Extract JSON as string from a google.protobuf.Message. Returns an error if the message cannot
495 : * be represented as JSON, which can occur if it contains an Any proto with an unrecognized type
496 : * URL or invalid data, or if memory cannot be allocated.
497 : * @param message message of type type.googleapis.com/google.protobuf.Message.
498 : * @param pretty_print whether the returned JSON should be formatted.
499 : * @param always_print_primitive_fields whether to include primitive fields set to their default
500 : * values, e.g. an int32 set to 0 or a bool set to false.
501 : * @return ProtobufUtil::StatusOr<std::string> of formatted JSON object, or an error status if
502 : * conversion fails.
503 : */
504 : static absl::StatusOr<std::string>
505 : getJsonStringFromMessage(const Protobuf::Message& message, bool pretty_print = false,
506 : bool always_print_primitive_fields = false);
507 :
508 : /**
509 : * Extract JSON as string from a google.protobuf.Message, returning some error string if the
510 : * conversion to JSON fails.
511 : * @param message message of type type.googleapis.com/google.protobuf.Message.
512 : * @param pretty_print whether the returned JSON should be formatted.
513 : * @param always_print_primitive_fields whether to include primitive fields set to their default
514 : * values, e.g. an int32 set to 0 or a bool set to false.
515 : * @return std::string of formatted JSON object, or an error message if conversion fails.
516 : */
517 : static std::string getJsonStringFromMessageOrError(const Protobuf::Message& message,
518 : bool pretty_print = false,
519 : bool always_print_primitive_fields = false);
520 : #endif
521 :
522 : static std::string convertToStringForLogs(const Protobuf::Message& message,
523 : bool pretty_print = false,
524 : bool always_print_primitive_fields = false);
525 :
526 : /**
527 : * Utility method to create a Struct containing the passed in key/value strings.
528 : *
529 : * @param key the key to use to set the value
530 : * @param value the string value to associate with the key
531 : */
532 : static ProtobufWkt::Struct keyValueStruct(const std::string& key, const std::string& value);
533 :
534 : /**
535 : * Utility method to create a Struct containing the passed in key/value map.
536 : *
537 : * @param fields the key/value pairs to initialize the Struct proto
538 : */
539 : static ProtobufWkt::Struct keyValueStruct(const std::map<std::string, std::string>& fields);
540 :
541 : /**
542 : * Utility method to print a human readable string of the code passed in.
543 : *
544 : * @param code the protobuf error code
545 : */
546 : static std::string codeEnumToString(absl::StatusCode code);
547 :
548 : /**
549 : * Modifies a message such that all sensitive data (that is, fields annotated as
550 : * `udpa.annotations.sensitive`) is redacted for display. String-typed fields annotated as
551 : * `sensitive` will be replaced with the string "[redacted]", bytes-typed fields will be replaced
552 : * with the bytes `5B72656461637465645D` (the ASCII / UTF-8 encoding of the string "[redacted]"),
553 : * primitive-typed fields (including enums) will be cleared, and message-typed fields will be
554 : * traversed recursively to redact their contents.
555 : *
556 : * LIMITATION: This works properly for strongly-typed messages, as well as for messages packed in
557 : * a `ProtobufWkt::Any` with a `type_url` corresponding to a proto that was compiled into the
558 : * Envoy binary. However it does not work for messages encoded as `ProtobufWkt::Struct`, since
559 : * structs are missing the "sensitive" annotations that this function expects. Similarly, it fails
560 : * for messages encoded as `ProtobufWkt::Any` with a `type_url` that isn't registered with the
561 : * binary. If you're working with struct-typed messages, including those that might be hiding
562 : * within strongly-typed messages, please reify them to strongly-typed messages using
563 : * `MessageUtil::jsonConvert()` before calling `MessageUtil::redact()`.
564 : *
565 : * @param message message to redact.
566 : */
567 : static void redact(Protobuf::Message& message);
568 :
569 : /**
570 : * Reinterpret a Protobuf message as another Protobuf message by converting to wire format and
571 : * back. This only works for messages that can be effectively duck typed this way, e.g. with a
572 : * subtype relationship modulo field name.
573 : *
574 : * @param src source message.
575 : * @param dst destination message.
576 : * @throw EnvoyException if a conversion error occurs.
577 : */
578 : static void wireCast(const Protobuf::Message& src, Protobuf::Message& dst);
579 :
580 : /**
581 : * Sanitizes a string to contain only valid UTF-8. Invalid UTF-8 characters will be replaced. If
582 : * the input string is valid UTF-8, it will be returned unmodified.
583 : */
584 : static std::string sanitizeUtf8String(absl::string_view str);
585 : };
586 :
587 : class ValueUtil {
588 : public:
589 32 : static std::size_t hash(const ProtobufWkt::Value& value) { return MessageUtil::hash(value); }
590 :
591 : #ifdef ENVOY_ENABLE_YAML
592 : /**
593 : * Load YAML string into ProtobufWkt::Value.
594 : */
595 : static ProtobufWkt::Value loadFromYaml(const std::string& yaml);
596 : #endif
597 :
598 : /**
599 : * Compare two ProtobufWkt::Values for equality.
600 : * @param v1 message of type type.googleapis.com/google.protobuf.Value
601 : * @param v2 message of type type.googleapis.com/google.protobuf.Value
602 : * @return true if v1 and v2 are identical
603 : */
604 : static bool equal(const ProtobufWkt::Value& v1, const ProtobufWkt::Value& v2);
605 :
606 : /**
607 : * @return wrapped ProtobufWkt::NULL_VALUE.
608 : */
609 : static const ProtobufWkt::Value& nullValue();
610 :
611 : /**
612 : * Wrap std::string into ProtobufWkt::Value string value.
613 : * @param str string to be wrapped.
614 : * @return wrapped string.
615 : */
616 : static ProtobufWkt::Value stringValue(const std::string& str);
617 :
618 : /**
619 : * Wrap optional std::string into ProtobufWkt::Value string value.
620 : * If the argument contains a null optional, return ProtobufWkt::NULL_VALUE.
621 : * @param str string to be wrapped.
622 : * @return wrapped string.
623 : */
624 : static ProtobufWkt::Value optionalStringValue(const absl::optional<std::string>& str);
625 :
626 : /**
627 : * Wrap boolean into ProtobufWkt::Value boolean value.
628 : * @param str boolean to be wrapped.
629 : * @return wrapped boolean.
630 : */
631 : static ProtobufWkt::Value boolValue(bool b);
632 :
633 : /**
634 : * Wrap ProtobufWkt::Struct into ProtobufWkt::Value struct value.
635 : * @param obj struct to be wrapped.
636 : * @return wrapped struct.
637 : */
638 : static ProtobufWkt::Value structValue(const ProtobufWkt::Struct& obj);
639 :
640 : /**
641 : * Wrap number into ProtobufWkt::Value double value.
642 : * @param num number to be wrapped.
643 : * @return wrapped number.
644 : */
645 0 : template <typename T> static ProtobufWkt::Value numberValue(const T num) {
646 0 : ProtobufWkt::Value val;
647 0 : val.set_number_value(static_cast<double>(num));
648 0 : return val;
649 0 : }
650 :
651 : /**
652 : * Wrap a collection of ProtobufWkt::Values into ProtobufWkt::Value list value.
653 : * @param values collection of ProtobufWkt::Values to be wrapped.
654 : * @return wrapped list value.
655 : */
656 : static ProtobufWkt::Value listValue(const std::vector<ProtobufWkt::Value>& values);
657 : };
658 :
659 : /**
660 : * HashedValue is a wrapper around ProtobufWkt::Value that computes
661 : * and stores a hash code for the Value at construction.
662 : */
663 : class HashedValue {
664 : public:
665 32 : HashedValue(const ProtobufWkt::Value& value) : value_(value), hash_(ValueUtil::hash(value)){};
666 32 : HashedValue(const HashedValue& v) = default;
667 :
668 0 : const ProtobufWkt::Value& value() const { return value_; }
669 0 : std::size_t hash() const { return hash_; }
670 :
671 0 : bool operator==(const HashedValue& rhs) const {
672 0 : return hash_ == rhs.hash_ && ValueUtil::equal(value_, rhs.value_);
673 0 : }
674 :
675 0 : bool operator!=(const HashedValue& rhs) const { return !(*this == rhs); }
676 :
677 : private:
678 : const ProtobufWkt::Value value_;
679 : const std::size_t hash_;
680 : };
681 :
682 : class DurationUtil {
683 : public:
684 : /**
685 : * Same as DurationUtil::durationToMilliseconds but with extra validation logic.
686 : * Same as Protobuf::util::TimeUtil::DurationToSeconds but with extra validation logic.
687 : * Specifically, we ensure that the duration is positive.
688 : * @param duration protobuf.
689 : * @return duration in milliseconds.
690 : * @throw EnvoyException when duration is out-of-range.
691 : */
692 : static uint64_t durationToMilliseconds(const ProtobufWkt::Duration& duration);
693 :
694 : /**
695 : * Same as DurationUtil::durationToMilliseconds but does not throw an exception.
696 : * @param duration protobuf.
697 : * @return duration in milliseconds or an error status.
698 : */
699 : static absl::StatusOr<uint64_t>
700 : durationToMillisecondsNoThrow(const ProtobufWkt::Duration& duration);
701 :
702 : /**
703 : * Same as Protobuf::util::TimeUtil::DurationToSeconds but with extra validation logic.
704 : * Specifically, we ensure that the duration is positive.
705 : * @param duration protobuf.
706 : * @return duration in seconds.
707 : * @throw EnvoyException when duration is out-of-range.
708 : */
709 : static uint64_t durationToSeconds(const ProtobufWkt::Duration& duration);
710 : };
711 :
712 : class TimestampUtil {
713 : public:
714 : /**
715 : * Writes a time_point<system_clock> (SystemTime) to a protobuf Timestamp, by way of time_t.
716 : * @param system_clock_time the time to write
717 : * @param timestamp a pointer to the mutable protobuf member to be written into.
718 : */
719 : static void systemClockToTimestamp(const SystemTime system_clock_time,
720 : ProtobufWkt::Timestamp& timestamp);
721 : };
722 :
723 : class StructUtil {
724 : public:
725 : /**
726 : * Recursively updates in-place a protobuf structure with keys from another
727 : * object.
728 : *
729 : * The merging strategy is the following. If a key from \p other does not
730 : * exists, it's just copied into \p obj. If the key exists but has a
731 : * different type, it is replaced by the new value. Otherwise:
732 : * - for scalar values (null, string, number, boolean) are replaced with the new value
733 : * - for lists: new values are added to the current list
734 : * - for structures: recursively apply this scheme
735 : *
736 : * @param obj the object to update in-place
737 : * @param with the object to update \p obj with
738 : */
739 : static void update(ProtobufWkt::Struct& obj, const ProtobufWkt::Struct& with);
740 : };
741 :
742 : } // namespace Envoy
743 :
744 : namespace std {
745 : // Inject an implementation of std::hash for Envoy::HashedValue into the std namespace.
746 : template <> struct hash<Envoy::HashedValue> {
747 0 : std::size_t operator()(Envoy::HashedValue const& v) const { return v.hash(); }
748 : };
749 :
750 : } // namespace std
|