/proc/self/cwd/source/common/protobuf/deterministic_hash.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #if defined(ENVOY_ENABLE_FULL_PROTOS) |
2 | | #include "source/common/protobuf/deterministic_hash.h" |
3 | | |
4 | | #include "source/common/common/assert.h" |
5 | | #include "source/common/common/hash.h" |
6 | | |
7 | | namespace Envoy { |
8 | | namespace DeterministicProtoHash { |
9 | | namespace { |
10 | | |
11 | | // Get a scalar field from protobuf reflection field definition. The return |
12 | | // type must be specified by the caller. Every implementation is a specialization |
13 | | // because the reflection interface did separate named functions instead of a |
14 | | // template. |
15 | | template <typename T> |
16 | | T reflectionGet(const Protobuf::Reflection& reflection, const Protobuf::Message& message, |
17 | | const Protobuf::FieldDescriptor& field); |
18 | | |
19 | | template <> |
20 | | uint32_t reflectionGet(const Protobuf::Reflection& reflection, const Protobuf::Message& message, |
21 | 36.2k | const Protobuf::FieldDescriptor& field) { |
22 | 36.2k | return reflection.GetUInt32(message, &field); |
23 | 36.2k | } |
24 | | |
25 | | template <> |
26 | | int32_t reflectionGet(const Protobuf::Reflection& reflection, const Protobuf::Message& message, |
27 | 15.9k | const Protobuf::FieldDescriptor& field) { |
28 | 15.9k | return reflection.GetInt32(message, &field); |
29 | 15.9k | } |
30 | | |
31 | | template <> |
32 | | uint64_t reflectionGet(const Protobuf::Reflection& reflection, const Protobuf::Message& message, |
33 | 351 | const Protobuf::FieldDescriptor& field) { |
34 | 351 | return reflection.GetUInt64(message, &field); |
35 | 351 | } |
36 | | |
37 | | template <> |
38 | | int64_t reflectionGet(const Protobuf::Reflection& reflection, const Protobuf::Message& message, |
39 | 22.1k | const Protobuf::FieldDescriptor& field) { |
40 | 22.1k | return reflection.GetInt64(message, &field); |
41 | 22.1k | } |
42 | | |
43 | | template <> |
44 | | float reflectionGet(const Protobuf::Reflection& reflection, const Protobuf::Message& message, |
45 | 0 | const Protobuf::FieldDescriptor& field) { |
46 | 0 | return reflection.GetFloat(message, &field); |
47 | 0 | } |
48 | | |
49 | | template <> |
50 | | double reflectionGet(const Protobuf::Reflection& reflection, const Protobuf::Message& message, |
51 | 2.55k | const Protobuf::FieldDescriptor& field) { |
52 | 2.55k | return reflection.GetDouble(message, &field); |
53 | 2.55k | } |
54 | | |
55 | | template <> |
56 | | bool reflectionGet(const Protobuf::Reflection& reflection, const Protobuf::Message& message, |
57 | 45.4k | const Protobuf::FieldDescriptor& field) { |
58 | 45.4k | return reflection.GetBool(message, &field); |
59 | 45.4k | } |
60 | | |
61 | | // Takes a field of scalar type, and hashes it. In case the field is a repeated field, |
62 | | // the function hashes each of its elements. |
63 | | template <typename T, std::enable_if_t<std::is_scalar_v<T>, bool> = true> |
64 | | uint64_t hashScalarField(const Protobuf::Reflection& reflection, const Protobuf::Message& message, |
65 | 132k | const Protobuf::FieldDescriptor& field, uint64_t seed) { |
66 | 132k | if (field.is_repeated()) { |
67 | 20.7k | for (const T& scalar : reflection.GetRepeatedFieldRef<T>(message, &field)) { |
68 | 20.7k | seed = HashUtil::xxHash64Value(scalar, seed); |
69 | 20.7k | } |
70 | 122k | } else { |
71 | 122k | seed = HashUtil::xxHash64Value(reflectionGet<T>(reflection, message, field), seed); |
72 | 122k | } |
73 | 132k | return seed; |
74 | 132k | } deterministic_hash.cc:unsigned long Envoy::DeterministicProtoHash::(anonymous namespace)::hashScalarField<int, true>(google::protobuf::Reflection const&, google::protobuf::Message const&, google::protobuf::FieldDescriptor const&, unsigned long) Line | Count | Source | 65 | 16.9k | const Protobuf::FieldDescriptor& field, uint64_t seed) { | 66 | 16.9k | if (field.is_repeated()) { | 67 | 1.65k | for (const T& scalar : reflection.GetRepeatedFieldRef<T>(message, &field)) { | 68 | 1.65k | seed = HashUtil::xxHash64Value(scalar, seed); | 69 | 1.65k | } | 70 | 15.9k | } else { | 71 | 15.9k | seed = HashUtil::xxHash64Value(reflectionGet<T>(reflection, message, field), seed); | 72 | 15.9k | } | 73 | 16.9k | return seed; | 74 | 16.9k | } |
deterministic_hash.cc:unsigned long Envoy::DeterministicProtoHash::(anonymous namespace)::hashScalarField<unsigned int, true>(google::protobuf::Reflection const&, google::protobuf::Message const&, google::protobuf::FieldDescriptor const&, unsigned long) Line | Count | Source | 65 | 45.1k | const Protobuf::FieldDescriptor& field, uint64_t seed) { | 66 | 45.1k | if (field.is_repeated()) { | 67 | 19.0k | for (const T& scalar : reflection.GetRepeatedFieldRef<T>(message, &field)) { | 68 | 19.0k | seed = HashUtil::xxHash64Value(scalar, seed); | 69 | 19.0k | } | 70 | 36.2k | } else { | 71 | 36.2k | seed = HashUtil::xxHash64Value(reflectionGet<T>(reflection, message, field), seed); | 72 | 36.2k | } | 73 | 45.1k | return seed; | 74 | 45.1k | } |
deterministic_hash.cc:unsigned long Envoy::DeterministicProtoHash::(anonymous namespace)::hashScalarField<long, true>(google::protobuf::Reflection const&, google::protobuf::Message const&, google::protobuf::FieldDescriptor const&, unsigned long) Line | Count | Source | 65 | 22.1k | const Protobuf::FieldDescriptor& field, uint64_t seed) { | 66 | 22.1k | if (field.is_repeated()) { | 67 | 0 | for (const T& scalar : reflection.GetRepeatedFieldRef<T>(message, &field)) { | 68 | 0 | seed = HashUtil::xxHash64Value(scalar, seed); | 69 | 0 | } | 70 | 22.1k | } else { | 71 | 22.1k | seed = HashUtil::xxHash64Value(reflectionGet<T>(reflection, message, field), seed); | 72 | 22.1k | } | 73 | 22.1k | return seed; | 74 | 22.1k | } |
deterministic_hash.cc:unsigned long Envoy::DeterministicProtoHash::(anonymous namespace)::hashScalarField<unsigned long, true>(google::protobuf::Reflection const&, google::protobuf::Message const&, google::protobuf::FieldDescriptor const&, unsigned long) Line | Count | Source | 65 | 351 | const Protobuf::FieldDescriptor& field, uint64_t seed) { | 66 | 351 | if (field.is_repeated()) { | 67 | 0 | for (const T& scalar : reflection.GetRepeatedFieldRef<T>(message, &field)) { | 68 | 0 | seed = HashUtil::xxHash64Value(scalar, seed); | 69 | 0 | } | 70 | 351 | } else { | 71 | 351 | seed = HashUtil::xxHash64Value(reflectionGet<T>(reflection, message, field), seed); | 72 | 351 | } | 73 | 351 | return seed; | 74 | 351 | } |
deterministic_hash.cc:unsigned long Envoy::DeterministicProtoHash::(anonymous namespace)::hashScalarField<double, true>(google::protobuf::Reflection const&, google::protobuf::Message const&, google::protobuf::FieldDescriptor const&, unsigned long) Line | Count | Source | 65 | 2.55k | const Protobuf::FieldDescriptor& field, uint64_t seed) { | 66 | 2.55k | if (field.is_repeated()) { | 67 | 3 | for (const T& scalar : reflection.GetRepeatedFieldRef<T>(message, &field)) { | 68 | 3 | seed = HashUtil::xxHash64Value(scalar, seed); | 69 | 3 | } | 70 | 2.55k | } else { | 71 | 2.55k | seed = HashUtil::xxHash64Value(reflectionGet<T>(reflection, message, field), seed); | 72 | 2.55k | } | 73 | 2.55k | return seed; | 74 | 2.55k | } |
Unexecuted instantiation: deterministic_hash.cc:unsigned long Envoy::DeterministicProtoHash::(anonymous namespace)::hashScalarField<float, true>(google::protobuf::Reflection const&, google::protobuf::Message const&, google::protobuf::FieldDescriptor const&, unsigned long) deterministic_hash.cc:unsigned long Envoy::DeterministicProtoHash::(anonymous namespace)::hashScalarField<bool, true>(google::protobuf::Reflection const&, google::protobuf::Message const&, google::protobuf::FieldDescriptor const&, unsigned long) Line | Count | Source | 65 | 45.4k | const Protobuf::FieldDescriptor& field, uint64_t seed) { | 66 | 45.4k | if (field.is_repeated()) { | 67 | 0 | for (const T& scalar : reflection.GetRepeatedFieldRef<T>(message, &field)) { | 68 | 0 | seed = HashUtil::xxHash64Value(scalar, seed); | 69 | 0 | } | 70 | 45.4k | } else { | 71 | 45.4k | seed = HashUtil::xxHash64Value(reflectionGet<T>(reflection, message, field), seed); | 72 | 45.4k | } | 73 | 45.4k | return seed; | 74 | 45.4k | } |
|
75 | | |
76 | | uint64_t reflectionHashMessage(const Protobuf::Message& message, uint64_t seed = 0); |
77 | | uint64_t reflectionHashField(const Protobuf::Message& message, |
78 | | const Protobuf::FieldDescriptor& field, uint64_t seed); |
79 | | |
80 | | // To make a map serialize deterministically we need to ignore the order of |
81 | | // the map fields. To do that, we simply combine the hashes of each entry |
82 | | // using an unordered operator (addition), and then apply that combined hash to |
83 | | // the seed. |
84 | | uint64_t reflectionHashMapField(const Protobuf::Message& message, |
85 | 21.3k | const Protobuf::FieldDescriptor& field, uint64_t seed) { |
86 | 21.3k | const Protobuf::Reflection& reflection = *message.GetReflection(); |
87 | 21.3k | ASSERT(field.is_map()); |
88 | 21.3k | const auto& entries = reflection.GetRepeatedFieldRef<Protobuf::Message>(message, &field); |
89 | 21.3k | ASSERT(!entries.empty()); |
90 | 21.3k | const Protobuf::Descriptor& map_descriptor = *entries.begin()->GetDescriptor(); |
91 | 21.3k | const Protobuf::FieldDescriptor& key_field = *map_descriptor.map_key(); |
92 | 21.3k | const Protobuf::FieldDescriptor& value_field = *map_descriptor.map_value(); |
93 | 21.3k | uint64_t combined_hash = 0; |
94 | 54.4k | for (const Protobuf::Message& entry : entries) { |
95 | 54.4k | uint64_t entry_hash = reflectionHashField(entry, key_field, 0); |
96 | 54.4k | entry_hash = reflectionHashField(entry, value_field, entry_hash); |
97 | 54.4k | combined_hash += entry_hash; |
98 | 54.4k | } |
99 | 21.3k | return HashUtil::xxHash64Value(combined_hash, seed); |
100 | 21.3k | } |
101 | | |
102 | | uint64_t reflectionHashField(const Protobuf::Message& message, |
103 | 1.11M | const Protobuf::FieldDescriptor& field, uint64_t seed) { |
104 | 1.11M | using Protobuf::FieldDescriptor; |
105 | 1.11M | const Protobuf::Reflection& reflection = *message.GetReflection(); |
106 | 1.11M | seed = HashUtil::xxHash64Value(field.number(), seed); |
107 | 1.11M | switch (field.cpp_type()) { |
108 | 16.9k | case FieldDescriptor::CPPTYPE_INT32: |
109 | 16.9k | seed = hashScalarField<int32_t>(reflection, message, field, seed); |
110 | 16.9k | break; |
111 | 45.1k | case FieldDescriptor::CPPTYPE_UINT32: |
112 | 45.1k | seed = hashScalarField<uint32_t>(reflection, message, field, seed); |
113 | 45.1k | break; |
114 | 22.1k | case FieldDescriptor::CPPTYPE_INT64: |
115 | 22.1k | seed = hashScalarField<int64_t>(reflection, message, field, seed); |
116 | 22.1k | break; |
117 | 351 | case FieldDescriptor::CPPTYPE_UINT64: |
118 | 351 | seed = hashScalarField<uint64_t>(reflection, message, field, seed); |
119 | 351 | break; |
120 | 2.55k | case FieldDescriptor::CPPTYPE_DOUBLE: |
121 | 2.55k | seed = hashScalarField<double>(reflection, message, field, seed); |
122 | 2.55k | break; |
123 | 0 | case FieldDescriptor::CPPTYPE_FLOAT: |
124 | 0 | seed = hashScalarField<float>(reflection, message, field, seed); |
125 | 0 | break; |
126 | 45.4k | case FieldDescriptor::CPPTYPE_BOOL: |
127 | 45.4k | seed = hashScalarField<bool>(reflection, message, field, seed); |
128 | 45.4k | break; |
129 | 17.2k | case FieldDescriptor::CPPTYPE_ENUM: |
130 | 17.2k | if (field.is_repeated()) { |
131 | 714 | const int c = reflection.FieldSize(message, &field); |
132 | 2.51k | for (int i = 0; i < c; i++) { |
133 | 1.80k | seed = HashUtil::xxHash64Value(reflection.GetRepeatedEnumValue(message, &field, i), seed); |
134 | 1.80k | } |
135 | 16.5k | } else { |
136 | 16.5k | seed = HashUtil::xxHash64Value(reflection.GetEnumValue(message, &field), seed); |
137 | 16.5k | } |
138 | 17.2k | break; |
139 | 384k | case FieldDescriptor::CPPTYPE_STRING: |
140 | 384k | if (field.is_repeated()) { |
141 | 221k | for (const std::string& str : reflection.GetRepeatedFieldRef<std::string>(message, &field)) { |
142 | 221k | seed = HashUtil::xxHash64(str, seed); |
143 | 221k | } |
144 | 316k | } else { |
145 | | // Scratch may be used by GetStringReference if the field is not already a std::string. |
146 | 316k | std::string scratch; |
147 | 316k | seed = HashUtil::xxHash64(reflection.GetStringReference(message, &field, &scratch), seed); |
148 | 316k | } |
149 | 384k | break; |
150 | 584k | case FieldDescriptor::CPPTYPE_MESSAGE: |
151 | 584k | if (field.is_map()) { |
152 | 21.3k | seed = reflectionHashMapField(message, field, seed); |
153 | 562k | } else if (field.is_repeated()) { |
154 | 115k | for (const Protobuf::Message& submsg : |
155 | 218k | reflection.GetRepeatedFieldRef<Protobuf::Message>(message, &field)) { |
156 | 218k | seed = reflectionHashMessage(submsg, seed); |
157 | 218k | } |
158 | 447k | } else { |
159 | 447k | seed = reflectionHashMessage(reflection.GetMessage(message, &field), seed); |
160 | 447k | } |
161 | 584k | break; |
162 | 1.11M | } |
163 | 1.11M | return seed; |
164 | 1.11M | } |
165 | | |
166 | | // Converts from type urls OR descriptor full names to descriptor full names. |
167 | | // Type urls are as used in envoy yaml config, e.g. |
168 | | // "type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.UdpProxyConfig" |
169 | | // becomes |
170 | | // "envoy.extensions.filters.udp.udp_proxy.v3.UdpProxyConfig" |
171 | 88.8k | absl::string_view typeUrlToDescriptorFullName(absl::string_view url) { |
172 | 88.8k | const size_t pos = url.rfind('/'); |
173 | 88.8k | if (pos != absl::string_view::npos) { |
174 | 38.9k | return url.substr(pos + 1); |
175 | 38.9k | } |
176 | 49.9k | return url; |
177 | 88.8k | } |
178 | | |
179 | 88.8k | std::unique_ptr<Protobuf::Message> unpackAnyForReflection(const ProtobufWkt::Any& any) { |
180 | 88.8k | const Protobuf::Descriptor* descriptor = |
181 | 88.8k | Protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName( |
182 | 88.8k | typeUrlToDescriptorFullName(any.type_url())); |
183 | | // If the type name refers to an unknown type, we treat it the same as other |
184 | | // unknown fields - not including its contents in the hash. |
185 | 88.8k | if (descriptor == nullptr) { |
186 | 49.5k | return nullptr; |
187 | 49.5k | } |
188 | 39.3k | const Protobuf::Message* prototype = |
189 | 39.3k | Protobuf::MessageFactory::generated_factory()->GetPrototype(descriptor); |
190 | 39.3k | ASSERT(prototype != nullptr, "should be impossible since the descriptor is known"); |
191 | 39.3k | std::unique_ptr<Protobuf::Message> msg(prototype->New()); |
192 | 39.3k | any.UnpackTo(msg.get()); |
193 | 39.3k | return msg; |
194 | 39.3k | } |
195 | | |
196 | | // This is intentionally ignoring unknown fields. |
197 | 878k | uint64_t reflectionHashMessage(const Protobuf::Message& message, uint64_t seed) { |
198 | 878k | using Protobuf::FieldDescriptor; |
199 | 878k | const Protobuf::Reflection* reflection = message.GetReflection(); |
200 | 878k | const Protobuf::Descriptor* descriptor = message.GetDescriptor(); |
201 | 878k | seed = HashUtil::xxHash64(descriptor->full_name(), seed); |
202 | 878k | if (descriptor->well_known_type() == Protobuf::Descriptor::WELLKNOWNTYPE_ANY) { |
203 | 88.8k | const ProtobufWkt::Any* any = Protobuf::DynamicCastToGenerated<ProtobufWkt::Any>(&message); |
204 | 88.8k | ASSERT(any != nullptr, "casting to any should always work for WELLKNOWNTYPE_ANY"); |
205 | 88.8k | std::unique_ptr<Protobuf::Message> submsg = unpackAnyForReflection(*any); |
206 | 88.8k | if (submsg == nullptr) { |
207 | | // If we wanted to handle unknown types in Any, this is where we'd have to do it. |
208 | | // Since we don't know the type to introspect it, we hash just its type name. |
209 | 49.5k | return HashUtil::xxHash64(any->type_url(), seed); |
210 | 49.5k | } |
211 | 39.3k | return reflectionHashMessage(*submsg, seed); |
212 | 88.8k | } |
213 | 789k | std::vector<const FieldDescriptor*> fields; |
214 | | // ListFields returned the fields ordered by field number. |
215 | 789k | reflection->ListFields(message, &fields); |
216 | | // If we wanted to handle unknown fields, we'd need to also GetUnknownFields here. |
217 | 1.00M | for (const FieldDescriptor* field : fields) { |
218 | 1.00M | seed = reflectionHashField(message, *field, seed); |
219 | 1.00M | } |
220 | | // Hash one extra character to signify end of message, so that |
221 | | // msg{} field2=2 |
222 | | // hashes differently from |
223 | | // msg{field2=2} |
224 | 789k | return HashUtil::xxHash64("\x17", seed); |
225 | 878k | } |
226 | | } // namespace |
227 | | |
228 | 173k | uint64_t hash(const Protobuf::Message& message) { return reflectionHashMessage(message, 0); } |
229 | | |
230 | | } // namespace DeterministicProtoHash |
231 | | } // namespace Envoy |
232 | | #endif |