Coverage Report

Created: 2024-09-19 09:45

/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