Skip to content

Commit 82d9a4b

Browse files
jckingcopybara-github
authored andcommitted
Add protocol buffer message encoding utility class
PiperOrigin-RevId: 550006265
1 parent d0131ea commit 82d9a4b

4 files changed

Lines changed: 217 additions & 6 deletions

File tree

internal/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ cc_test(
328328
deps = [
329329
":proto_wire",
330330
":testing",
331+
"@com_google_absl//absl/strings",
331332
"@com_google_absl//absl/strings:cord",
332333
],
333334
)

internal/proto_wire.cc

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@
1414

1515
#include "internal/proto_wire.h"
1616

17+
#include <limits>
1718
#include <string>
19+
#include <utility>
1820

1921
#include "absl/base/optimization.h"
22+
#include "absl/status/status.h"
23+
#include "absl/strings/str_cat.h"
2024

2125
namespace cel::internal {
2226

@@ -110,4 +114,36 @@ absl::StatusOr<absl::Cord> ProtoWireDecoder::ReadLengthDelimited() {
110114
return result;
111115
}
112116

117+
absl::Status ProtoWireEncoder::WriteTag(ProtoWireTag tag) {
118+
ABSL_DCHECK(!tag_.has_value());
119+
if (ABSL_PREDICT_FALSE(tag.field_number() == 0)) {
120+
// Cannot easily add test coverage as we assert during debug builds that
121+
// ProtoWireTag is valid upon construction.
122+
return absl::InvalidArgumentError(
123+
absl::StrCat("invalid field number encountered encoding ", message_));
124+
}
125+
if (ABSL_PREDICT_FALSE(!ProtoWireTypeIsValid(tag.type()))) {
126+
return absl::InvalidArgumentError(
127+
absl::StrCat("invalid wire type encountered encoding field ",
128+
tag.field_number(), " of ", message_));
129+
}
130+
VarintEncode(static_cast<uint32_t>(tag), data_);
131+
tag_.emplace(tag);
132+
return absl::OkStatus();
133+
}
134+
135+
absl::Status ProtoWireEncoder::WriteLengthDelimited(absl::Cord data) {
136+
ABSL_DCHECK(tag_.has_value() &&
137+
tag_->type() == ProtoWireType::kLengthDelimited);
138+
if (ABSL_PREDICT_FALSE(data.size() > std::numeric_limits<uint32_t>::max())) {
139+
return absl::InvalidArgumentError(
140+
absl::StrCat("out of range length encountered encoding field ",
141+
tag_->field_number(), " of ", message_));
142+
}
143+
VarintEncode(static_cast<uint32_t>(data.size()), data_);
144+
data_.Append(std::move(data));
145+
tag_.reset();
146+
return absl::OkStatus();
147+
}
148+
113149
} // namespace cel::internal

internal/proto_wire.h

Lines changed: 101 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,19 +88,27 @@ enum class ProtoWireType : uint32_t {
8888
kFixed32 = 5,
8989
};
9090

91+
inline constexpr uint32_t kProtoWireTypeMask = uint32_t{0x7};
92+
inline constexpr int kFieldNumberShift = 3;
93+
9194
class ProtoWireTag final {
9295
public:
96+
static constexpr uint32_t kTypeMask = uint32_t{0x7};
97+
static constexpr int kFieldNumberShift = 3;
98+
9399
constexpr explicit ProtoWireTag(uint32_t tag) : tag_(tag) {}
94100

95101
constexpr ProtoWireTag(uint32_t field_number, ProtoWireType type)
96-
: ProtoWireTag((field_number << 3) | static_cast<uint32_t>(type)) {
97-
ABSL_ASSERT(((field_number << 3) >> 3) == field_number);
102+
: ProtoWireTag((field_number << kFieldNumberShift) |
103+
static_cast<uint32_t>(type)) {
104+
ABSL_ASSERT(((field_number << kFieldNumberShift) >> kFieldNumberShift) ==
105+
field_number);
98106
}
99107

100-
constexpr uint32_t field_number() const { return tag_ >> 3; }
108+
constexpr uint32_t field_number() const { return tag_ >> kFieldNumberShift; }
101109

102110
constexpr ProtoWireType type() const {
103-
return static_cast<ProtoWireType>(tag_ & uint32_t{0x7});
111+
return static_cast<ProtoWireType>(tag_ & kTypeMask);
104112
}
105113

106114
// NOLINTNEXTLINE(google-explicit-constructor)
@@ -110,6 +118,14 @@ class ProtoWireTag final {
110118
uint32_t tag_;
111119
};
112120

121+
inline constexpr bool ProtoWireTypeIsValid(ProtoWireType type) {
122+
// Ensure `type` is only [0-5]. The bitmask for `type` is 0x7 which allows 6
123+
// to exist, but that is not used and invalid. We detect that here.
124+
return (static_cast<uint32_t>(type) & uint32_t{0x7}) ==
125+
static_cast<uint32_t>(type) &&
126+
static_cast<uint32_t>(type) != uint32_t{0x6};
127+
}
128+
113129
// Creates the "tag" of a record, see
114130
// https://protobuf.dev/programming-guides/encoding/#structure.
115131
inline constexpr uint32_t MakeProtoWireTag(uint32_t field_number,
@@ -168,6 +184,30 @@ inline void VarintEncode(bool value, absl::Cord& buffer) {
168184
buffer.Append(absl::string_view(&scratch, 1));
169185
}
170186

187+
inline void Fixed32EncodeUnsafe(uint64_t value, char* buffer) {
188+
buffer[0] = static_cast<char>(static_cast<uint8_t>(value));
189+
buffer[1] = static_cast<char>(static_cast<uint8_t>(value >> 8));
190+
buffer[2] = static_cast<char>(static_cast<uint8_t>(value >> 16));
191+
buffer[3] = static_cast<char>(static_cast<uint8_t>(value >> 24));
192+
}
193+
194+
// Encodes `value` as a fixed-size number, see
195+
// https://protobuf.dev/programming-guides/encoding/#non-varint-numbers.
196+
inline void Fixed32Encode(uint32_t value, absl::Cord& buffer) {
197+
// `absl::Cord::GetAppendBuffer` will allocate a block regardless of whether
198+
// `buffer` has enough inline storage space left. To take advantage of inline
199+
// storage space, we need to just do a plain append.
200+
char scratch[4];
201+
Fixed32EncodeUnsafe(value, scratch);
202+
buffer.Append(absl::string_view(scratch, ABSL_ARRAYSIZE(scratch)));
203+
}
204+
205+
// Encodes `value` as a fixed-size number, see
206+
// https://protobuf.dev/programming-guides/encoding/#non-varint-numbers.
207+
inline void Fixed32Encode(float value, absl::Cord& buffer) {
208+
Fixed32Encode(absl::bit_cast<uint32_t>(value), buffer);
209+
}
210+
171211
inline void Fixed64EncodeUnsafe(uint64_t value, char* buffer) {
172212
buffer[0] = static_cast<char>(static_cast<uint8_t>(value));
173213
buffer[1] = static_cast<char>(static_cast<uint8_t>(value >> 8));
@@ -308,11 +348,12 @@ Fixed32Decode(const absl::Cord& data) {
308348
}
309349

310350
inline absl::optional<ProtoWireTag> DecodeProtoWireTag(uint32_t value) {
311-
if (ABSL_PREDICT_FALSE((value >> 3) == 0)) {
351+
if (ABSL_PREDICT_FALSE((value >> ProtoWireTag::kFieldNumberShift) == 0)) {
312352
// Field number is 0.
313353
return absl::nullopt;
314354
}
315-
if (ABSL_PREDICT_FALSE((value & uint32_t{0x7}) == uint32_t{0x6})) {
355+
if (ABSL_PREDICT_FALSE(!ProtoWireTypeIsValid(
356+
static_cast<ProtoWireType>(value & ProtoWireTag::kTypeMask)))) {
316357
// Wire type is 6, only 0-5 are used.
317358
return absl::nullopt;
318359
}
@@ -409,6 +450,60 @@ class ProtoWireDecoder {
409450
absl::optional<ProtoWireTag> tag_;
410451
};
411452

453+
class ProtoWireEncoder final {
454+
public:
455+
explicit ProtoWireEncoder(absl::string_view message
456+
ABSL_ATTRIBUTE_LIFETIME_BOUND,
457+
absl::Cord& data ABSL_ATTRIBUTE_LIFETIME_BOUND)
458+
: message_(message), data_(data), original_data_size_(data_.size()) {}
459+
460+
bool empty() const { return size() == 0; }
461+
462+
size_t size() const { return data_.size() - original_data_size_; }
463+
464+
absl::Status WriteTag(ProtoWireTag tag);
465+
466+
template <typename T>
467+
std::enable_if_t<std::is_integral_v<T>, absl::Status> WriteVarint(T value) {
468+
ABSL_DCHECK(tag_.has_value() && tag_->type() == ProtoWireType::kVarint);
469+
VarintEncode(value, data_);
470+
tag_.reset();
471+
return absl::OkStatus();
472+
}
473+
474+
template <typename T>
475+
std::enable_if_t<sizeof(T) == 4 &&
476+
(std::is_integral_v<T> || std::is_floating_point_v<T>),
477+
absl::Status>
478+
WriteFixed32(T value) {
479+
ABSL_DCHECK(tag_.has_value() && tag_->type() == ProtoWireType::kFixed32);
480+
Fixed32Encode(value, data_);
481+
tag_.reset();
482+
return absl::OkStatus();
483+
}
484+
485+
template <typename T>
486+
std::enable_if_t<sizeof(T) == 8 &&
487+
(std::is_integral_v<T> || std::is_floating_point_v<T>),
488+
absl::Status>
489+
WriteFixed64(T value) {
490+
ABSL_DCHECK(tag_.has_value() && tag_->type() == ProtoWireType::kFixed64);
491+
Fixed64Encode(value, data_);
492+
tag_.reset();
493+
return absl::OkStatus();
494+
}
495+
496+
absl::Status WriteLengthDelimited(absl::Cord data);
497+
498+
void EnsureFullyEncoded() { ABSL_DCHECK(!tag_.has_value()); }
499+
500+
private:
501+
absl::string_view message_;
502+
absl::Cord& data_;
503+
const size_t original_data_size_;
504+
absl::optional<ProtoWireTag> tag_;
505+
};
506+
412507
} // namespace cel::internal
413508

414509
#endif // THIRD_PARTY_CEL_CPP_INTERNAL_PROTO_WIRE_H_

internal/proto_wire_test.cc

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <limits>
1818

1919
#include "absl/strings/cord.h"
20+
#include "absl/strings/string_view.h"
2021
#include "internal/testing.h"
2122

2223
namespace cel::internal {
@@ -112,6 +113,13 @@ absl::Cord Fixed64Encode(T value) {
112113
return cord;
113114
}
114115

116+
template <typename T>
117+
absl::Cord Fixed32Encode(T value) {
118+
absl::Cord cord;
119+
internal::Fixed32Encode(value, cord);
120+
return cord;
121+
}
122+
115123
} // namespace
116124

117125
TEST(Fixed64, Encode) {
@@ -122,6 +130,10 @@ TEST(Fixed64, Decode) {
122130
EXPECT_THAT(Fixed64Decode<double>(Fixed64Encode(0.0)), Optional(Eq(0.0)));
123131
}
124132

133+
TEST(Fixed32, Encode) {
134+
EXPECT_EQ(Fixed32Encode(0.0f), Fixed32Encode(uint32_t{0}));
135+
}
136+
125137
TEST(Fixed32, Decode) {
126138
EXPECT_THAT(Fixed32Decode<float>(
127139
absl::Cord(absl::string_view("\x00\x00\x00\x00", 4))),
@@ -206,6 +218,73 @@ TEST(SkipLengthValue, Decoder) {
206218
}
207219
}
208220

221+
TEST(ProtoWireEncoder, BadTag) {
222+
absl::Cord data;
223+
ProtoWireEncoder encoder("foo.Bar", data);
224+
EXPECT_TRUE(encoder.empty());
225+
EXPECT_EQ(encoder.size(), 0);
226+
EXPECT_OK(encoder.WriteTag(ProtoWireTag(1, ProtoWireType::kVarint)));
227+
EXPECT_OK(encoder.WriteVarint(1));
228+
encoder.EnsureFullyEncoded();
229+
EXPECT_FALSE(encoder.empty());
230+
EXPECT_EQ(encoder.size(), 2);
231+
EXPECT_EQ(data, "\x08\x01");
232+
}
233+
234+
TEST(ProtoWireEncoder, Varint) {
235+
absl::Cord data;
236+
ProtoWireEncoder encoder("foo.Bar", data);
237+
EXPECT_TRUE(encoder.empty());
238+
EXPECT_EQ(encoder.size(), 0);
239+
EXPECT_OK(encoder.WriteTag(ProtoWireTag(1, ProtoWireType::kVarint)));
240+
EXPECT_OK(encoder.WriteVarint(1));
241+
encoder.EnsureFullyEncoded();
242+
EXPECT_FALSE(encoder.empty());
243+
EXPECT_EQ(encoder.size(), 2);
244+
EXPECT_EQ(data, "\x08\x01");
245+
}
246+
247+
TEST(ProtoWireEncoder, Fixed32) {
248+
absl::Cord data;
249+
ProtoWireEncoder encoder("foo.Bar", data);
250+
EXPECT_TRUE(encoder.empty());
251+
EXPECT_EQ(encoder.size(), 0);
252+
EXPECT_OK(encoder.WriteTag(ProtoWireTag(1, ProtoWireType::kFixed32)));
253+
EXPECT_OK(encoder.WriteFixed32(0.0f));
254+
encoder.EnsureFullyEncoded();
255+
EXPECT_FALSE(encoder.empty());
256+
EXPECT_EQ(encoder.size(), 5);
257+
EXPECT_EQ(data, absl::string_view("\x0d\x00\x00\x00\x00", 5));
258+
}
259+
260+
TEST(ProtoWireEncoder, Fixed64) {
261+
absl::Cord data;
262+
ProtoWireEncoder encoder("foo.Bar", data);
263+
EXPECT_TRUE(encoder.empty());
264+
EXPECT_EQ(encoder.size(), 0);
265+
EXPECT_OK(encoder.WriteTag(ProtoWireTag(1, ProtoWireType::kFixed64)));
266+
EXPECT_OK(encoder.WriteFixed64(0.0));
267+
encoder.EnsureFullyEncoded();
268+
EXPECT_FALSE(encoder.empty());
269+
EXPECT_EQ(encoder.size(), 9);
270+
EXPECT_EQ(data, absl::string_view("\x09\x00\x00\x00\x00\x00\x00\x00\x00", 9));
271+
}
272+
273+
TEST(ProtoWireEncoder, LengthDelimited) {
274+
absl::Cord data;
275+
ProtoWireEncoder encoder("foo.Bar", data);
276+
EXPECT_TRUE(encoder.empty());
277+
EXPECT_EQ(encoder.size(), 0);
278+
EXPECT_OK(encoder.WriteTag(ProtoWireTag(1, ProtoWireType::kLengthDelimited)));
279+
EXPECT_OK(encoder.WriteLengthDelimited(absl::Cord("foo")));
280+
encoder.EnsureFullyEncoded();
281+
EXPECT_FALSE(encoder.empty());
282+
EXPECT_EQ(encoder.size(), 5);
283+
EXPECT_EQ(data,
284+
"\x0a\x03"
285+
"foo");
286+
}
287+
209288
} // namespace
210289

211290
} // namespace cel::internal

0 commit comments

Comments
 (0)