• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #include "pw_protobuf/encoder.h"
16 
17 #include <span>
18 
19 #include "gtest/gtest.h"
20 #include "pw_bytes/span.h"
21 #include "pw_stream/memory_stream.h"
22 
23 namespace pw::protobuf {
24 namespace {
25 
26 using stream::MemoryWriter;
27 
28 // The tests in this file use the following proto message schemas.
29 //
30 //   message TestProto {
31 //     uint32 magic_number = 1;
32 //     sint32 ziggy = 2;
33 //     fixed64 cycles = 3;
34 //     float ratio = 4;
35 //     string error_message = 5;
36 //     NestedProto nested = 6;
37 //   }
38 //
39 //   message NestedProto {
40 //     string hello = 1;
41 //     uint32 id = 2;
42 //     repeated DoubleNestedProto pair = 3;
43 //   }
44 //
45 //   message DoubleNestedProto {
46 //     string key = 1;
47 //     string value = 2;
48 //   }
49 //
50 
51 constexpr uint32_t kTestProtoMagicNumberField = 1;
52 constexpr uint32_t kTestProtoZiggyField = 2;
53 constexpr uint32_t kTestProtoCyclesField = 3;
54 constexpr uint32_t kTestProtoRatioField = 4;
55 constexpr uint32_t kTestProtoErrorMessageField = 5;
56 constexpr uint32_t kTestProtoNestedField = 6;
57 constexpr uint32_t kTestProtoPayloadFromStreamField = 7;
58 
59 constexpr uint32_t kNestedProtoHelloField = 1;
60 constexpr uint32_t kNestedProtoIdField = 2;
61 constexpr uint32_t kNestedProtoPairField = 3;
62 
63 constexpr uint32_t kDoubleNestedProtoKeyField = 1;
64 constexpr uint32_t kDoubleNestedProtoValueField = 2;
65 
TEST(StreamEncoder,EncodePrimitives)66 TEST(StreamEncoder, EncodePrimitives) {
67   // TestProto tp;
68   // tp.magic_number = 42;
69   // tp.ziggy = -13;
70   // tp.cycles = 0xdeadbeef8badf00d;
71   // tp.ratio = 1.618034;
72   // tp.error_message = "broken ��";
73   // tp.payload_from_stream = "byreader"
74 
75   // Hand-encoded version of the above.
76   // clang-format off
77   constexpr uint8_t encoded_proto[] = {
78     // magic_number [varint k=1]
79     0x08, 0x2a,
80     // ziggy [varint k=2]
81     0x10, 0x19,
82     // cycles [fixed64 k=3]
83     0x19, 0x0d, 0xf0, 0xad, 0x8b, 0xef, 0xbe, 0xad, 0xde,
84     // ratio [fixed32 k=4]
85     0x25, 0xbd, 0x1b, 0xcf, 0x3f,
86     // error_message [delimited k=5],
87     0x2a, 0x0b, 'b', 'r', 'o', 'k', 'e', 'n', ' ',
88     // poop!
89     0xf0, 0x9f, 0x92, 0xa9,
90     // payload_from_stream [delimited k=7]
91     0x3a, 0x08, 'b', 'y', 'r', 'e', 'a', 'd', 'e', 'r',
92   };
93   // clang-format on
94   std::byte encode_buffer[64];
95   std::byte dest_buffer[64];
96   // This writer isn't necessary, it's just the most testable way to exercise
97   // a stream interface. Use a MemoryEncoder when encoding a proto directly to
98   // an in-memory buffer.
99   MemoryWriter writer(dest_buffer);
100   StreamEncoder encoder(writer, encode_buffer);
101 
102   EXPECT_EQ(encoder.WriteUint32(kTestProtoMagicNumberField, 42), OkStatus());
103   EXPECT_EQ(writer.bytes_written(), 2u);
104   EXPECT_EQ(encoder.WriteSint32(kTestProtoZiggyField, -13), OkStatus());
105   EXPECT_EQ(encoder.WriteFixed64(kTestProtoCyclesField, 0xdeadbeef8badf00d),
106             OkStatus());
107   EXPECT_EQ(encoder.WriteFloat(kTestProtoRatioField, 1.618034), OkStatus());
108   EXPECT_EQ(encoder.WriteString(kTestProtoErrorMessageField, "broken ��"),
109             OkStatus());
110 
111   const std::string_view kReaderMessage = "byreader";
112   stream::MemoryReader msg_reader(std::as_bytes(std::span(kReaderMessage)));
113   std::byte stream_pipe_buffer[1];
114   EXPECT_EQ(encoder.WriteStringFromStream(kTestProtoPayloadFromStreamField,
115                                           msg_reader,
116                                           kReaderMessage.size(),
117                                           stream_pipe_buffer),
118             OkStatus());
119 
120   ASSERT_EQ(encoder.status(), OkStatus());
121   ConstByteSpan result = writer.WrittenData();
122   EXPECT_EQ(result.size(), sizeof(encoded_proto));
123   EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
124             0);
125 }
126 
TEST(StreamEncoder,EncodeInsufficientSpace)127 TEST(StreamEncoder, EncodeInsufficientSpace) {
128   std::byte encode_buffer[12];
129   MemoryEncoder encoder(encode_buffer);
130 
131   // 2 bytes.
132   EXPECT_EQ(encoder.WriteUint32(kTestProtoMagicNumberField, 42), OkStatus());
133   // 2 bytes.
134   EXPECT_EQ(encoder.WriteSint32(kTestProtoZiggyField, -13), OkStatus());
135   // 9 bytes; not enough space! The encoder will start writing the field but
136   // should rollback when it realizes it doesn't have enough space.
137   EXPECT_EQ(encoder.WriteFixed64(kTestProtoCyclesField, 0xdeadbeef8badf00d),
138             Status::ResourceExhausted());
139   // Any further write operations should fail.
140   EXPECT_EQ(encoder.WriteFloat(kTestProtoRatioField, 1.618034),
141             Status::ResourceExhausted());
142 
143   ASSERT_EQ(encoder.status(), Status::ResourceExhausted());
144 }
145 
TEST(StreamEncoder,EncodeInvalidArguments)146 TEST(StreamEncoder, EncodeInvalidArguments) {
147   std::byte encode_buffer[12];
148   MemoryEncoder encoder(encode_buffer);
149 
150   EXPECT_EQ(encoder.WriteUint32(kTestProtoMagicNumberField, 42), OkStatus());
151   // Invalid proto field numbers.
152   EXPECT_EQ(encoder.WriteUint32(0, 1337), Status::InvalidArgument());
153 
154   // TODO(amontanez): Does it make sense to support this?
155   // encoder.Clear();
156 
157   EXPECT_EQ(encoder.WriteString(1u << 31, "ha"), Status::InvalidArgument());
158 
159   // TODO(amontanez): Does it make sense to support this?
160   // encoder.Clear();
161 
162   EXPECT_EQ(encoder.WriteBool(19091, false), Status::InvalidArgument());
163   ASSERT_EQ(encoder.status(), Status::InvalidArgument());
164 }
165 
TEST(StreamEncoder,Nested)166 TEST(StreamEncoder, Nested) {
167   // This is the largest complete submessage in this test.
168   constexpr size_t kLargestSubmessageSize = 0x30;
169   constexpr size_t kScratchBufferSize =
170       MaxScratchBufferSize(kLargestSubmessageSize, 2);
171   std::byte encode_buffer[kScratchBufferSize];
172   std::byte dest_buffer[128];
173   MemoryWriter writer(dest_buffer);
174   StreamEncoder encoder(writer, encode_buffer);
175 
176   // TestProto test_proto;
177   // test_proto.magic_number = 42;
178   EXPECT_EQ(encoder.WriteUint32(kTestProtoMagicNumberField, 42), OkStatus());
179 
180   {
181     // NestedProto& nested_proto = test_proto.nested;
182     StreamEncoder nested_proto =
183         encoder.GetNestedEncoder(kTestProtoNestedField);
184     // nested_proto.hello = "world";
185     EXPECT_EQ(nested_proto.WriteString(kNestedProtoHelloField, "world"),
186               OkStatus());
187 
188     {
189       // DoubleNestedProto& double_nested_proto = nested_proto.append_pair();
190       StreamEncoder double_nested_proto =
191           nested_proto.GetNestedEncoder(kNestedProtoPairField);
192       // double_nested_proto.key = "version";
193       EXPECT_EQ(double_nested_proto.WriteString(kDoubleNestedProtoKeyField,
194                                                 "version"),
195                 OkStatus());
196       // double_nested_proto.value = "2.9.1";
197       EXPECT_EQ(double_nested_proto.WriteString(kDoubleNestedProtoValueField,
198                                                 "2.9.1"),
199                 OkStatus());
200     }  // end DoubleNestedProto
201 
202     // nested_proto.id = 999;
203     EXPECT_EQ(nested_proto.WriteUint32(kNestedProtoIdField, 999), OkStatus());
204 
205     {
206       // DoubleNestedProto& double_nested_proto = nested_proto.append_pair();
207       StreamEncoder double_nested_proto =
208           nested_proto.GetNestedEncoder(kNestedProtoPairField);
209       // double_nested_proto.key = "device";
210       EXPECT_EQ(
211           double_nested_proto.WriteString(kDoubleNestedProtoKeyField, "device"),
212           OkStatus());
213       // double_nested_proto.value = "left-soc";
214       EXPECT_EQ(double_nested_proto.WriteString(kDoubleNestedProtoValueField,
215                                                 "left-soc"),
216                 OkStatus());
217       // Rely on destructor for finalization.
218     }  // end DoubleNestedProto
219   }    // end NestedProto
220 
221   // test_proto.ziggy = -13;
222   EXPECT_EQ(encoder.WriteSint32(kTestProtoZiggyField, -13), OkStatus());
223 
224   // clang-format off
225   constexpr uint8_t encoded_proto[] = {
226     // magic_number
227     0x08, 0x2a,
228     // nested header (key, size)
229     0x32, 0x30,
230     // nested.hello
231     0x0a, 0x05, 'w', 'o', 'r', 'l', 'd',
232     // nested.pair[0] header (key, size)
233     0x1a, 0x10,
234     // nested.pair[0].key
235     0x0a, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
236     // nested.pair[0].value
237     0x12, 0x05, '2', '.', '9', '.', '1',
238     // nested.id
239     0x10, 0xe7, 0x07,
240     // nested.pair[1] header (key, size)
241     0x1a, 0x12,
242     // nested.pair[1].key
243     0x0a, 0x06, 'd', 'e', 'v', 'i', 'c', 'e',
244     // nested.pair[1].value
245     0x12, 0x08, 'l', 'e', 'f', 't', '-', 's', 'o', 'c',
246     // ziggy
247     0x10, 0x19
248   };
249   // clang-format on
250 
251   ASSERT_EQ(encoder.status(), OkStatus());
252   ConstByteSpan result = ConstByteSpan(writer.data(), writer.bytes_written());
253   EXPECT_EQ(result.size(), sizeof(encoded_proto));
254   EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
255             0);
256 }
257 
TEST(StreamEncoder,RepeatedField)258 TEST(StreamEncoder, RepeatedField) {
259   std::byte encode_buffer[32];
260   MemoryEncoder encoder(encode_buffer);
261 
262   // repeated uint32 values = 1;
263   constexpr uint32_t values[] = {0, 50, 100, 150, 200};
264   for (int i = 0; i < 5; ++i) {
265     encoder.WriteUint32(1, values[i])
266         .IgnoreError();  // TODO(pwbug/387): Handle Status properly
267   }
268 
269   constexpr uint8_t encoded_proto[] = {
270       0x08, 0x00, 0x08, 0x32, 0x08, 0x64, 0x08, 0x96, 0x01, 0x08, 0xc8, 0x01};
271 
272   ASSERT_EQ(encoder.status(), OkStatus());
273   ConstByteSpan result(encoder);
274   EXPECT_EQ(result.size(), sizeof(encoded_proto));
275   EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
276             0);
277 }
278 
TEST(StreamEncoder,PackedVarint)279 TEST(StreamEncoder, PackedVarint) {
280   std::byte encode_buffer[32];
281   MemoryEncoder encoder(encode_buffer);
282 
283   // repeated uint32 values = 1;
284   constexpr uint32_t values[] = {0, 50, 100, 150, 200};
285   encoder.WritePackedUint32(1, values)
286       .IgnoreError();  // TODO(pwbug/387): Handle Status properly
287 
288   constexpr uint8_t encoded_proto[] = {
289       0x0a, 0x07, 0x00, 0x32, 0x64, 0x96, 0x01, 0xc8, 0x01};
290   //  key   size  v[0]  v[1]  v[2]  v[3]        v[4]
291 
292   ASSERT_EQ(encoder.status(), OkStatus());
293   ConstByteSpan result(encoder);
294   EXPECT_EQ(result.size(), sizeof(encoded_proto));
295   EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
296             0);
297 }
298 
TEST(StreamEncoder,PackedVarintInsufficientSpace)299 TEST(StreamEncoder, PackedVarintInsufficientSpace) {
300   std::byte encode_buffer[8];
301   MemoryEncoder encoder(encode_buffer);
302 
303   constexpr uint32_t values[] = {0, 50, 100, 150, 200};
304   encoder.WritePackedUint32(1, values)
305       .IgnoreError();  // TODO(pwbug/387): Handle Status properly
306 
307   EXPECT_EQ(encoder.status(), Status::ResourceExhausted());
308 }
309 
TEST(StreamEncoder,PackedVarintVector)310 TEST(StreamEncoder, PackedVarintVector) {
311   std::byte encode_buffer[32];
312   MemoryEncoder encoder(encode_buffer);
313 
314   // repeated uint32 values = 1;
315   const pw::Vector<uint32_t, 5> values = {0, 50, 100, 150, 200};
316   encoder.WriteRepeatedUint32(1, values)
317       .IgnoreError();  // TODO(pwbug/387): Handle Status properly
318 
319   constexpr uint8_t encoded_proto[] = {
320       0x0a, 0x07, 0x00, 0x32, 0x64, 0x96, 0x01, 0xc8, 0x01};
321   //  key   size  v[0]  v[1]  v[2]  v[3]        v[4]
322 
323   ASSERT_EQ(encoder.status(), OkStatus());
324   ConstByteSpan result(encoder);
325   EXPECT_EQ(result.size(), sizeof(encoded_proto));
326   EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
327             0);
328 }
329 
TEST(StreamEncoder,PackedVarintVectorInsufficientSpace)330 TEST(StreamEncoder, PackedVarintVectorInsufficientSpace) {
331   std::byte encode_buffer[8];
332   MemoryEncoder encoder(encode_buffer);
333 
334   const pw::Vector<uint32_t, 5> values = {0, 50, 100, 150, 200};
335   encoder.WriteRepeatedUint32(1, values)
336       .IgnoreError();  // TODO(pwbug/387): Handle Status properly
337 
338   EXPECT_EQ(encoder.status(), Status::ResourceExhausted());
339 }
340 
TEST(StreamEncoder,PackedBool)341 TEST(StreamEncoder, PackedBool) {
342   std::byte encode_buffer[32];
343   MemoryEncoder encoder(encode_buffer);
344 
345   // repeated bool values = 1;
346   constexpr bool values[] = {true, false, true, true, false};
347   encoder.WritePackedBool(1, values)
348       .IgnoreError();  // TODO(pwbug/387): Handle Status properly
349 
350   constexpr uint8_t encoded_proto[] = {
351       0x0a, 0x05, 0x01, 0x00, 0x01, 0x01, 0x00};
352   //  key   size  v[0]  v[1]  v[2]  v[3]  v[4]
353 
354   ASSERT_EQ(encoder.status(), OkStatus());
355   ConstByteSpan result(encoder);
356   EXPECT_EQ(result.size(), sizeof(encoded_proto));
357   EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
358             0);
359 }
360 
TEST(StreamEncoder,PackedFixed)361 TEST(StreamEncoder, PackedFixed) {
362   std::byte encode_buffer[32];
363   MemoryEncoder encoder(encode_buffer);
364 
365   // repeated fixed32 values = 1;
366   constexpr uint32_t values[] = {0, 50, 100, 150, 200};
367   encoder.WritePackedFixed32(1, values)
368       .IgnoreError();  // TODO(pwbug/387): Handle Status properly
369 
370   // repeated fixed64 values64 = 2;
371   constexpr uint64_t values64[] = {0x0102030405060708};
372   encoder.WritePackedFixed64(2, values64)
373       .IgnoreError();  // TODO(pwbug/387): Handle Status properly
374 
375   constexpr uint8_t encoded_proto[] = {
376       0x0a, 0x14, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x64,
377       0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00,
378       0x12, 0x08, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01};
379 
380   ASSERT_EQ(encoder.status(), OkStatus());
381   ConstByteSpan result(encoder);
382   EXPECT_EQ(result.size(), sizeof(encoded_proto));
383   EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
384             0);
385 }
386 
TEST(StreamEncoder,PackedFixedVector)387 TEST(StreamEncoder, PackedFixedVector) {
388   std::byte encode_buffer[32];
389   MemoryEncoder encoder(encode_buffer);
390 
391   // repeated fixed32 values = 1;
392   const pw::Vector<uint32_t, 5> values = {0, 50, 100, 150, 200};
393   encoder.WriteRepeatedFixed32(1, values)
394       .IgnoreError();  // TODO(pwbug/387): Handle Status properly
395 
396   // repeated fixed64 values64 = 2;
397   const pw::Vector<uint64_t, 1> values64 = {0x0102030405060708};
398   encoder.WriteRepeatedFixed64(2, values64)
399       .IgnoreError();  // TODO(pwbug/387): Handle Status properly
400 
401   constexpr uint8_t encoded_proto[] = {
402       0x0a, 0x14, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x64,
403       0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00,
404       0x12, 0x08, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01};
405 
406   ASSERT_EQ(encoder.status(), OkStatus());
407   ConstByteSpan result(encoder);
408   EXPECT_EQ(result.size(), sizeof(encoded_proto));
409   EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
410             0);
411 }
412 
TEST(StreamEncoder,PackedZigzag)413 TEST(StreamEncoder, PackedZigzag) {
414   std::byte encode_buffer[32];
415   MemoryEncoder encoder(encode_buffer);
416 
417   // repeated sint32 values = 1;
418   constexpr int32_t values[] = {-100, -25, -1, 0, 1, 25, 100};
419   encoder.WritePackedSint32(1, values)
420       .IgnoreError();  // TODO(pwbug/387): Handle Status properly
421 
422   constexpr uint8_t encoded_proto[] = {
423       0x0a, 0x09, 0xc7, 0x01, 0x31, 0x01, 0x00, 0x02, 0x32, 0xc8, 0x01};
424 
425   ASSERT_EQ(encoder.status(), OkStatus());
426   ConstByteSpan result(encoder);
427   EXPECT_EQ(result.size(), sizeof(encoded_proto));
428   EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
429             0);
430 }
431 
TEST(StreamEncoder,PackedZigzagVector)432 TEST(StreamEncoder, PackedZigzagVector) {
433   std::byte encode_buffer[32];
434   MemoryEncoder encoder(encode_buffer);
435 
436   // repeated sint32 values = 1;
437   const pw::Vector<int32_t, 7> values = {-100, -25, -1, 0, 1, 25, 100};
438   encoder.WriteRepeatedSint32(1, values)
439       .IgnoreError();  // TODO(pwbug/387): Handle Status properly
440 
441   constexpr uint8_t encoded_proto[] = {
442       0x0a, 0x09, 0xc7, 0x01, 0x31, 0x01, 0x00, 0x02, 0x32, 0xc8, 0x01};
443 
444   ASSERT_EQ(encoder.status(), OkStatus());
445   ConstByteSpan result(encoder);
446   EXPECT_EQ(result.size(), sizeof(encoded_proto));
447   EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
448             0);
449 }
450 
TEST(StreamEncoder,ParentUnavailable)451 TEST(StreamEncoder, ParentUnavailable) {
452   std::byte encode_buffer[32];
453   MemoryEncoder parent(encode_buffer);
454   {
455     StreamEncoder child = parent.GetNestedEncoder(kTestProtoNestedField);
456     ASSERT_EQ(child.status(), OkStatus());
457   }
458   ASSERT_EQ(parent.status(), OkStatus());
459 }
460 
TEST(StreamEncoder,NestedEncoderRequiresBuffer)461 TEST(StreamEncoder, NestedEncoderRequiresBuffer) {
462   MemoryEncoder parent((ByteSpan()));
463   {
464     StreamEncoder child = parent.GetNestedEncoder(kTestProtoNestedField);
465 
466     ASSERT_EQ(child.status(), Status::ResourceExhausted());
467   }
468   ASSERT_EQ(parent.status(), Status::ResourceExhausted());
469 }
470 
TEST(StreamEncoder,WriteTooBig)471 TEST(StreamEncoder, WriteTooBig) {
472   constexpr size_t kTempBufferSize = 32;
473   constexpr size_t kWriteSize = 2;
474   std::byte encode_buffer[32];
475   MemoryEncoder encoder(encode_buffer);
476   // Each write is 2 bytes. Ensure we can write 16 times.
477   for (size_t i = 0; i < kTempBufferSize; i += kWriteSize) {
478     ASSERT_EQ(encoder.WriteUint32(1, 12), OkStatus());
479   }
480   ASSERT_EQ(encoder.size(), kTempBufferSize);
481   ASSERT_EQ(encoder.WriteUint32(1, 12), Status::ResourceExhausted());
482 }
483 
TEST(StreamEncoder,EmptyChildWrites)484 TEST(StreamEncoder, EmptyChildWrites) {
485   std::byte encode_buffer[32];
486   MemoryEncoder parent(encode_buffer);
487   { StreamEncoder child = parent.GetNestedEncoder(kTestProtoNestedField); }
488   ASSERT_EQ(parent.status(), OkStatus());
489   const size_t kExpectedSize =
490       varint::EncodedSize(
491           FieldKey(kTestProtoNestedField, WireType::kDelimited)) +
492       varint::EncodedSize(0);
493   ASSERT_EQ(parent.size(), kExpectedSize);
494 }
495 
TEST(StreamEncoder,NestedStatusPropagates)496 TEST(StreamEncoder, NestedStatusPropagates) {
497   std::byte encode_buffer[32];
498   MemoryEncoder parent(encode_buffer);
499   {
500     StreamEncoder child = parent.GetNestedEncoder(kTestProtoNestedField);
501     ASSERT_EQ(child.WriteUint32(0, 0), Status::InvalidArgument());
502   }
503   ASSERT_EQ(parent.status(), Status::InvalidArgument());
504 }
505 
506 }  // namespace
507 }  // namespace pw::protobuf
508