1 // Copyright 2023 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 <array>
16 #include <string_view>
17 #include <tuple>
18
19 #include "pw_preprocessor/compiler.h"
20 #include "pw_protobuf/internal/codegen.h"
21 #include "pw_span/span.h"
22 #include "pw_status/status.h"
23 #include "pw_status/status_with_size.h"
24 #include "pw_stream/memory_stream.h"
25 #include "pw_unit_test/framework.h"
26
27 // These header files contain the code generated by the pw_protobuf plugin.
28 // They are re-generated every time the tests are built and are used by the
29 // tests to ensure that the interface remains consistent.
30 //
31 // The purpose of the tests in this file is primarily to verify that the
32 // generated C++ interface is valid rather than the correctness of the
33 // low-level encoder.
34 #include "pw_protobuf_test_protos/full_test.pwpb.h"
35 #include "pw_protobuf_test_protos/importer.pwpb.h"
36 #include "pw_protobuf_test_protos/optional.pwpb.h"
37 #include "pw_protobuf_test_protos/repeated.pwpb.h"
38
39 namespace pw::protobuf {
40 namespace {
41
42 using namespace ::pw::protobuf::test::pwpb;
43
44 PW_MODIFY_DIAGNOSTICS_PUSH();
45 PW_MODIFY_DIAGNOSTIC(ignored, "-Wmissing-field-initializers");
46
TEST(CodegenMessage,Equality)47 TEST(CodegenMessage, Equality) {
48 const Pigweed::Message one{
49 .magic_number = 0x49u,
50 .ziggy = -111,
51 .cycles = 0x40302010fecaaddeu,
52 .ratio = -1.42f,
53 .error_message = "not a typewriter",
54 .pigweed = {.status = Bool::FILE_NOT_FOUND},
55 .bin = Pigweed::Protobuf::Binary::ZERO,
56 .proto = {.bin = Proto::Binary::OFF,
57 .pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO,
58 .pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ZERO,
59 .meta =
60 {
61 .file_name = "/etc/passwd",
62 .status = Pigweed::Protobuf::Compiler::Status::FUBAR,
63 .protobuf_bin = Pigweed::Protobuf::Binary::ONE,
64 .pigweed_bin = Pigweed::Pigweed::Binary::ONE,
65 }},
66 .data = {std::byte{0x10},
67 std::byte{0x20},
68 std::byte{0x30},
69 std::byte{0x40},
70 std::byte{0x50},
71 std::byte{0x60},
72 std::byte{0x70},
73 std::byte{0x80}},
74 .bungle = -111,
75 };
76
77 const Pigweed::Message two{
78 .magic_number = 0x49u,
79 .ziggy = -111,
80 .cycles = 0x40302010fecaaddeu,
81 .ratio = -1.42f,
82 .error_message = "not a typewriter",
83 .pigweed = {.status = Bool::FILE_NOT_FOUND},
84 .bin = Pigweed::Protobuf::Binary::ZERO,
85 .proto = {.bin = Proto::Binary::OFF,
86 .pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO,
87 .pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ZERO,
88 .meta =
89 {
90 .file_name = "/etc/passwd",
91 .status = Pigweed::Protobuf::Compiler::Status::FUBAR,
92 .protobuf_bin = Pigweed::Protobuf::Binary::ONE,
93 .pigweed_bin = Pigweed::Pigweed::Binary::ONE,
94 }},
95 .data = {std::byte{0x10},
96 std::byte{0x20},
97 std::byte{0x30},
98 std::byte{0x40},
99 std::byte{0x50},
100 std::byte{0x60},
101 std::byte{0x70},
102 std::byte{0x80}},
103 .bungle = -111,
104 };
105
106 EXPECT_TRUE(one == two);
107 }
108
TEST(CodegenMessage,CopyEquality)109 TEST(CodegenMessage, CopyEquality) {
110 Pigweed::Message one{
111 .magic_number = 0x49u,
112 .ziggy = -111,
113 .cycles = 0x40302010fecaaddeu,
114 .ratio = -1.42f,
115 .error_message = "not a typewriter",
116 .pigweed = {.status = Bool::FILE_NOT_FOUND},
117 .bin = Pigweed::Protobuf::Binary::ZERO,
118 .proto = {.bin = Proto::Binary::OFF,
119 .pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO,
120 .pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ZERO,
121 .meta =
122 {
123 .file_name = "/etc/passwd",
124 .status = Pigweed::Protobuf::Compiler::Status::FUBAR,
125 .protobuf_bin = Pigweed::Protobuf::Binary::ONE,
126 .pigweed_bin = Pigweed::Pigweed::Binary::ONE,
127 }},
128 .data = {std::byte{0x10},
129 std::byte{0x20},
130 std::byte{0x30},
131 std::byte{0x40},
132 std::byte{0x50},
133 std::byte{0x60},
134 std::byte{0x70},
135 std::byte{0x80}},
136 .bungle = -111,
137 };
138 Pigweed::Message two = one;
139
140 EXPECT_TRUE(one == two);
141 }
142
TEST(CodegenMessage,EmptyEquality)143 TEST(CodegenMessage, EmptyEquality) {
144 const Pigweed::Message one{};
145 const Pigweed::Message two{};
146
147 EXPECT_TRUE(one == two);
148 }
149
TEST(CodegenMessage,Inequality)150 TEST(CodegenMessage, Inequality) {
151 const Pigweed::Message one{
152 .magic_number = 0x49u,
153 .ziggy = -111,
154 .cycles = 0x40302010fecaaddeu,
155 .ratio = -1.42f,
156 .error_message = "not a typewriter",
157 .pigweed = {.status = Bool::FILE_NOT_FOUND},
158 .bin = Pigweed::Protobuf::Binary::ZERO,
159 .proto = {.bin = Proto::Binary::OFF,
160 .pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO,
161 .pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ZERO,
162 .meta =
163 {
164 .file_name = "/etc/passwd",
165 .status = Pigweed::Protobuf::Compiler::Status::FUBAR,
166 .protobuf_bin = Pigweed::Protobuf::Binary::ONE,
167 .pigweed_bin = Pigweed::Pigweed::Binary::ONE,
168 }},
169 .data = {std::byte{0x10},
170 std::byte{0x20},
171 std::byte{0x30},
172 std::byte{0x40},
173 std::byte{0x50},
174 std::byte{0x60},
175 std::byte{0x70},
176 std::byte{0x80}},
177 .bungle = -111,
178 };
179
180 const Pigweed::Message two{
181 .magic_number = 0x43u,
182 .ziggy = 128,
183 .ratio = -1.42f,
184 .error_message = "not a typewriter",
185 .pigweed = {.status = Bool::TRUE},
186 .bin = Pigweed::Protobuf::Binary::ZERO,
187 .proto = {.bin = Proto::Binary::OFF,
188 .pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO,
189 .pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ONE,
190 .meta =
191 {
192 .file_name = "/etc/passwd",
193 .status = Pigweed::Protobuf::Compiler::Status::FUBAR,
194 .protobuf_bin = Pigweed::Protobuf::Binary::ONE,
195 .pigweed_bin = Pigweed::Pigweed::Binary::ONE,
196 }},
197 .data = {std::byte{0x20},
198 std::byte{0x30},
199 std::byte{0x40},
200 std::byte{0x50},
201 std::byte{0x60},
202 std::byte{0x70},
203 std::byte{0x80},
204 std::byte{0x90}},
205 };
206
207 EXPECT_FALSE(one == two);
208 }
209
TEST(CodegenMessage,TriviallyComparable)210 TEST(CodegenMessage, TriviallyComparable) {
211 static_assert(IsTriviallyComparable<IntegerMetadata::Message>());
212 static_assert(IsTriviallyComparable<KeyValuePair::Message>());
213 static_assert(!IsTriviallyComparable<Pigweed::Message>());
214 }
215
TEST(CodegenMessage,ConstCopyable)216 TEST(CodegenMessage, ConstCopyable) {
217 const Pigweed::Message one{
218 .magic_number = 0x49u,
219 .ziggy = -111,
220 .cycles = 0x40302010fecaaddeu,
221 .ratio = -1.42f,
222 .error_message = "not a typewriter",
223 .pigweed = {.status = Bool::FILE_NOT_FOUND},
224 .bin = Pigweed::Protobuf::Binary::ZERO,
225 .proto = {.bin = Proto::Binary::OFF,
226 .pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO,
227 .pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ZERO,
228 .meta =
229 {
230 .file_name = "/etc/passwd",
231 .status = Pigweed::Protobuf::Compiler::Status::FUBAR,
232 .protobuf_bin = Pigweed::Protobuf::Binary::ONE,
233 .pigweed_bin = Pigweed::Pigweed::Binary::ONE,
234 }},
235 .data = {std::byte{0x10},
236 std::byte{0x20},
237 std::byte{0x30},
238 std::byte{0x40},
239 std::byte{0x50},
240 std::byte{0x60},
241 std::byte{0x70},
242 std::byte{0x80}},
243 .bungle = -111,
244 };
245 Pigweed::Message two = one;
246
247 EXPECT_TRUE(one == two);
248 }
249
TEST(CodegenMessage,FixReservedIdentifiers)250 TEST(CodegenMessage, FixReservedIdentifiers) {
251 // This test checks that the code was generated as expected, so it will simply
252 // fail to compile if its expectations are not met.
253
254 // Make sure that the `signed` field was renamed to `signed_`.
255 std::ignore = IntegerMetadata::Message{
256 .bits = 32,
257 .signed_ = true,
258 .null = false,
259 };
260
261 // Make sure that the internal enum describing the struct's fields was
262 // generated as expected:
263 // - `BITS` doesn't need an underscore.
264 // - `SIGNED_` has an underscore to match the corresponding `signed_` field.
265 // - `NULL_` has an underscore to avoid a collision with `NULL` (even though
266 // the field `null` doesn't have or need an underscore).
267 std::ignore = IntegerMetadata::Fields::kBits;
268 std::ignore = IntegerMetadata::Fields::kSigned;
269 std::ignore = IntegerMetadata::Fields::kNull;
270
271 // Make sure that the `ReservedWord` enum values were renamed as expected.
272 // Specifically, only enum-value names that are reserved in UPPER_SNAKE_CASE
273 // should be modified. Names that are only reserved in lower_snake_case should
274 // be left alone since they'll never appear in that form in the generated
275 // code.
276 std::ignore = ReservedWord::NULL_; // Add underscore since NULL is a macro.
277 std::ignore = ReservedWord::kNull; // No underscore necessary.
278 std::ignore = ReservedWord::INT; // No underscore necessary.
279 std::ignore = ReservedWord::kInt; // No underscore necessary.
280 std::ignore = ReservedWord::RETURN; // No underscore necessary.
281 std::ignore = ReservedWord::kReturn; // No underscore necessary.
282 std::ignore = ReservedWord::BREAK; // No underscore necessary.
283 std::ignore = ReservedWord::kBreak; // No underscore necessary.
284 std::ignore = ReservedWord::FOR; // No underscore necessary.
285 std::ignore = ReservedWord::kFor; // No underscore necessary.
286 std::ignore = ReservedWord::DO; // No underscore necessary.
287 std::ignore = ReservedWord::kDo; // No underscore necessary.
288
289 // Instantiate an extremely degenerately named set of nested types in order to
290 // make sure that name conflicts with the codegen internals are properly
291 // prevented.
292 std::ignore = Function::Message{
293 .description =
294 Function::Message_::Message{
295 .content = "multiplication (mod 5)",
296 },
297 .domain_field = Function::Fields_::INTEGERS_MOD_5,
298 .codomain_field = Function::Fields_::INTEGERS_MOD_5,
299 };
300
301 // Check for expected values of `enum class Function::Fields`:
302 std::ignore = Function::Fields::kDescription;
303 std::ignore = Function::Fields::kDomainField;
304 std::ignore = Function::Fields::kCodomainField;
305
306 // Check for expected values of `enum class Function::Message_::Fields`:
307 std::ignore = Function::Message_::Fields::kContent;
308
309 // Check for expected values of `enum class Function::Fields_`:
310 std::ignore = Function::Fields_::NONE;
311 std::ignore = Function::Fields_::kNone;
312 std::ignore = Function::Fields_::COMPLEX_NUMBERS;
313 std::ignore = Function::Fields_::kComplexNumbers;
314 std::ignore = Function::Fields_::INTEGERS_MOD_5;
315 std::ignore = Function::Fields_::kIntegersMod5;
316 std::ignore = Function::Fields_::MEROMORPHIC_FUNCTIONS_ON_COMPLEX_PLANE;
317 std::ignore = Function::Fields_::kMeromorphicFunctionsOnComplexPlane;
318 std::ignore = Function::Fields_::OTHER;
319 std::ignore = Function::Fields_::kOther;
320 }
321
322 PW_MODIFY_DIAGNOSTICS_POP();
323
TEST(CodegenMessage,SetEncoder)324 TEST(CodegenMessage, SetEncoder) {
325 Pigweed::Message msg{};
326
327 EXPECT_FALSE(msg.id);
328 msg.id.SetEncoder(
329 [](Pigweed::StreamEncoder&) -> Status { return OkStatus(); });
330 EXPECT_TRUE(msg.id);
331 }
332
TEST(CodegenMessage,SetDecoder)333 TEST(CodegenMessage, SetDecoder) {
334 Pigweed::Message msg{};
335
336 EXPECT_FALSE(msg.id);
337 msg.id.SetDecoder(
338 [](Pigweed::StreamDecoder&) -> Status { return OkStatus(); });
339 EXPECT_TRUE(msg.id);
340 }
341
TEST(CodegenMessage,SetEncoderAndDecoder)342 TEST(CodegenMessage, SetEncoderAndDecoder) {
343 Pigweed::Message msg{};
344
345 EXPECT_FALSE(msg.id);
346 msg.id.SetEncoder(
347 [](Pigweed::StreamEncoder&) -> Status { return OkStatus(); });
348 msg.id.SetDecoder(
349 [](Pigweed::StreamDecoder&) -> Status { return OkStatus(); });
350 EXPECT_TRUE(msg.id);
351 }
352
TEST(CodegenMessage,Read)353 TEST(CodegenMessage, Read) {
354 // clang-format off
355 constexpr uint8_t proto_data[] = {
356 // pigweed.magic_number
357 0x08, 0x49,
358 // pigweed.ziggy
359 0x10, 0xdd, 0x01,
360 // pigweed.cycles
361 0x19, 0xde, 0xad, 0xca, 0xfe, 0x10, 0x20, 0x30, 0x40,
362 // pigweed.ratio
363 0x25, 0x8f, 0xc2, 0xb5, 0xbf,
364 // pigweed.error_message
365 0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ',
366 't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r',
367 // pigweed.bin
368 0x40, 0x01,
369 // pigweed.bungle
370 0x70, 0x91, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01,
371 };
372 // clang-format on
373
374 stream::MemoryReader reader(as_bytes(span(proto_data)));
375 Pigweed::StreamDecoder pigweed(reader);
376
377 Pigweed::Message message{};
378 const auto status = pigweed.Read(message);
379 ASSERT_EQ(status, OkStatus());
380
381 constexpr std::string_view kExpectedErrorMessage{"not a typewriter"};
382
383 EXPECT_EQ(message.magic_number, 0x49u);
384 EXPECT_EQ(message.ziggy, -111);
385 EXPECT_EQ(message.cycles, 0x40302010fecaaddeu);
386 EXPECT_EQ(message.ratio, -1.42f);
387 EXPECT_EQ(message.error_message.size(), kExpectedErrorMessage.size());
388 EXPECT_EQ(std::memcmp(message.error_message.data(),
389 kExpectedErrorMessage.data(),
390 kExpectedErrorMessage.size()),
391 0);
392 EXPECT_EQ(message.bin, Pigweed::Protobuf::Binary::ZERO);
393 EXPECT_EQ(message.bungle, -111);
394 }
395
TEST(CodegenMessage,ReadNonPackedScalar)396 TEST(CodegenMessage, ReadNonPackedScalar) {
397 // clang-format off
398 constexpr uint8_t proto_data[] = {
399 // uint32s[], v={0, 16, 32, 48}
400 0x08, 0x00,
401 0x08, 0x10,
402 0x08, 0x20,
403 0x08, 0x30,
404 // fixed32s[]. v={0, 16, 32, 48}
405 0x35, 0x00, 0x00, 0x00, 0x00,
406 0x35, 0x10, 0x00, 0x00, 0x00,
407 0x35, 0x20, 0x00, 0x00, 0x00,
408 0x35, 0x30, 0x00, 0x00, 0x00,
409 };
410 // clang-format on
411
412 stream::MemoryReader reader(as_bytes(span(proto_data)));
413 RepeatedTest::StreamDecoder repeated_test(reader);
414
415 RepeatedTest::Message message{};
416 const auto status = repeated_test.Read(message);
417 ASSERT_EQ(status, OkStatus());
418
419 ASSERT_EQ(message.uint32s.size(), 4u);
420 for (unsigned short i = 0; i < 4; ++i) {
421 EXPECT_EQ(message.uint32s[i], i * 16u);
422 }
423
424 ASSERT_EQ(message.fixed32s.size(), 4u);
425 for (unsigned short i = 0; i < 4; ++i) {
426 EXPECT_EQ(message.fixed32s[i], i * 16u);
427 }
428 }
429
TEST(CodegenMessage,ReadPackedScalar)430 TEST(CodegenMessage, ReadPackedScalar) {
431 // clang-format off
432 constexpr uint8_t proto_data[] = {
433 // uint32s[], v={0, 16, 32, 48}
434 0x0a, 0x04,
435 0x00,
436 0x10,
437 0x20,
438 0x30,
439 // fixed32s[]. v={0, 16, 32, 48}
440 0x32, 0x10,
441 0x00, 0x00, 0x00, 0x00,
442 0x10, 0x00, 0x00, 0x00,
443 0x20, 0x00, 0x00, 0x00,
444 0x30, 0x00, 0x00, 0x00,
445 };
446 // clang-format on
447
448 stream::MemoryReader reader(as_bytes(span(proto_data)));
449 RepeatedTest::StreamDecoder repeated_test(reader);
450
451 RepeatedTest::Message message{};
452 const auto status = repeated_test.Read(message);
453 ASSERT_EQ(status, OkStatus());
454
455 ASSERT_EQ(message.uint32s.size(), 4u);
456 for (unsigned short i = 0; i < 4; ++i) {
457 EXPECT_EQ(message.uint32s[i], i * 16u);
458 }
459
460 ASSERT_EQ(message.fixed32s.size(), 4u);
461 for (unsigned short i = 0; i < 4; ++i) {
462 EXPECT_EQ(message.fixed32s[i], i * 16u);
463 }
464 }
465
TEST(CodegenMessage,ReadPackedScalarRepeated)466 TEST(CodegenMessage, ReadPackedScalarRepeated) {
467 // clang-format off
468 constexpr uint8_t proto_data[] = {
469 // uint32s[], v={0, 16, 32, 48}
470 0x0a, 0x04,
471 0x00,
472 0x10,
473 0x20,
474 0x30,
475 // uint32s[], v={64, 80, 96, 112}
476 0x0a, 0x04,
477 0x40,
478 0x50,
479 0x60,
480 0x70,
481 // fixed32s[]. v={0, 16, 32, 48}
482 0x32, 0x10,
483 0x00, 0x00, 0x00, 0x00,
484 0x10, 0x00, 0x00, 0x00,
485 0x20, 0x00, 0x00, 0x00,
486 0x30, 0x00, 0x00, 0x00,
487 // fixed32s[]. v={64, 80, 96, 112}
488 0x32, 0x10,
489 0x40, 0x00, 0x00, 0x00,
490 0x50, 0x00, 0x00, 0x00,
491 0x60, 0x00, 0x00, 0x00,
492 0x70, 0x00, 0x00, 0x00,
493 };
494 // clang-format on
495
496 stream::MemoryReader reader(as_bytes(span(proto_data)));
497 RepeatedTest::StreamDecoder repeated_test(reader);
498
499 RepeatedTest::Message message{};
500 const auto status = repeated_test.Read(message);
501 ASSERT_EQ(status, OkStatus());
502
503 ASSERT_EQ(message.uint32s.size(), 8u);
504 for (unsigned short i = 0; i < 8; ++i) {
505 EXPECT_EQ(message.uint32s[i], i * 16u);
506 }
507
508 ASSERT_EQ(message.fixed32s.size(), 8u);
509 for (unsigned short i = 0; i < 8; ++i) {
510 EXPECT_EQ(message.fixed32s[i], i * 16u);
511 }
512 }
513
TEST(CodegenMessage,ReadPackedScalarExhausted)514 TEST(CodegenMessage, ReadPackedScalarExhausted) {
515 // clang-format off
516 constexpr uint8_t proto_data[] = {
517 // uint32s[], v={0, 16, 32, 48, 64, 80, 96, 112, 128}
518 0x0a, 0x09,
519 0x00,
520 0x10,
521 0x20,
522 0x30,
523 0x40,
524 0x50,
525 0x60,
526 0x70,
527 0x80,
528 };
529 // clang-format on
530
531 stream::MemoryReader reader(as_bytes(span(proto_data)));
532 RepeatedTest::StreamDecoder repeated_test(reader);
533
534 // uint32s has max_size=8, so this will exhaust the vector.
535 RepeatedTest::Message message{};
536 const auto status = repeated_test.Read(message);
537 ASSERT_EQ(status, Status::ResourceExhausted());
538 }
539
TEST(CodegenMessage,ReadPackedScalarCallback)540 TEST(CodegenMessage, ReadPackedScalarCallback) {
541 // clang-format off
542 constexpr uint8_t proto_data[] = {
543 // sint32s[], v={-25, -1, 0, 1, 25}
544 0x12, 0x05,
545 0x31,
546 0x01,
547 0x00,
548 0x02,
549 0x32,
550 };
551 // clang-format on
552
553 stream::MemoryReader reader(as_bytes(span(proto_data)));
554 RepeatedTest::StreamDecoder repeated_test(reader);
555
556 // sint32s is a repeated field declared without max_count, so requirses a
557 // callback to be decoded.
558 RepeatedTest::Message message{};
559 message.sint32s.SetDecoder([](RepeatedTest::StreamDecoder& decoder) {
560 EXPECT_EQ(decoder.Field().value(), RepeatedTest::Fields::kSint32s);
561
562 pw::Vector<int32_t, 8> sint32s{};
563 const auto status = decoder.ReadSint32s(sint32s);
564 EXPECT_EQ(status, OkStatus());
565
566 EXPECT_EQ(sint32s.size(), 5u);
567 EXPECT_EQ(sint32s[0], -25);
568 EXPECT_EQ(sint32s[1], -1);
569 EXPECT_EQ(sint32s[2], 0);
570 EXPECT_EQ(sint32s[3], 1);
571 EXPECT_EQ(sint32s[4], 25);
572
573 return status;
574 });
575
576 const auto status = repeated_test.Read(message);
577 ASSERT_EQ(status, OkStatus());
578 }
579
TEST(CodegenMessage,ReadPackedScalarFixedLength)580 TEST(CodegenMessage, ReadPackedScalarFixedLength) {
581 // clang-format off
582 constexpr uint8_t proto_data[] = {
583 // uint64s[], v={1000, 2000, 3000, 4000}
584 0x42, 0x08, 0xe8, 0x07, 0xd0, 0x0f, 0xb8, 0x17, 0xa0, 0x1f,
585 // doubles[], v={3.14159, 2.71828}
586 0x22, 0x10,
587 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
588 0x90, 0xf7, 0xaa, 0x95, 0x09, 0xbf, 0x05, 0x40,
589 };
590 // clang-format on
591
592 stream::MemoryReader reader(as_bytes(span(proto_data)));
593 RepeatedTest::StreamDecoder repeated_test(reader);
594
595 RepeatedTest::Message message{};
596 const auto status = repeated_test.Read(message);
597 ASSERT_EQ(status, OkStatus());
598
599 EXPECT_EQ(message.uint64s[0], 1000u);
600 EXPECT_EQ(message.uint64s[1], 2000u);
601 EXPECT_EQ(message.uint64s[2], 3000u);
602 EXPECT_EQ(message.uint64s[3], 4000u);
603
604 EXPECT_EQ(message.doubles[0], 3.14159);
605 EXPECT_EQ(message.doubles[1], 2.71828);
606 }
607
TEST(CodegenMessage,ReadPackedScalarFixedLengthShort)608 TEST(CodegenMessage, ReadPackedScalarFixedLengthShort) {
609 // clang-format off
610 constexpr uint8_t proto_data[] = {
611 // uint64s[], v={1000, 2000}
612 0x42, 0x04, 0xe8, 0x07, 0xd0, 0x0f,
613 // doubles[], v={3.14159}
614 0x22, 0x08,
615 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
616 };
617 // clang-format on
618
619 stream::MemoryReader reader(as_bytes(span(proto_data)));
620 RepeatedTest::StreamDecoder repeated_test(reader);
621
622 RepeatedTest::Message message{};
623 const auto status = repeated_test.Read(message);
624 ASSERT_EQ(status, OkStatus());
625
626 EXPECT_EQ(message.uint64s[0], 1000u);
627 EXPECT_EQ(message.uint64s[1], 2000u);
628 EXPECT_EQ(message.uint64s[2], 0u);
629 EXPECT_EQ(message.uint64s[3], 0u);
630
631 EXPECT_EQ(message.doubles[0], 3.14159);
632 EXPECT_EQ(message.doubles[1], 0);
633 }
634
TEST(CodegenMessage,ReadPackedScalarVarintFixedLengthExhausted)635 TEST(CodegenMessage, ReadPackedScalarVarintFixedLengthExhausted) {
636 // clang-format off
637 constexpr uint8_t proto_data[] = {
638 // uint64s[], v={0, 1000, 2000, 3000, 4000}
639 0x42, 0x09, 0x08, 0xe8, 0x07, 0xd0, 0x0f, 0xb8, 0x17, 0xa0, 0x1f,
640 };
641 // clang-format on
642
643 stream::MemoryReader reader(as_bytes(span(proto_data)));
644 RepeatedTest::StreamDecoder repeated_test(reader);
645
646 RepeatedTest::Message message{};
647 const auto status = repeated_test.Read(message);
648 ASSERT_EQ(status, Status::ResourceExhausted());
649 }
650
TEST(CodegenMessage,ReadPackedScalarFixedLengthExhausted)651 TEST(CodegenMessage, ReadPackedScalarFixedLengthExhausted) {
652 // clang-format off
653 constexpr uint8_t proto_data[] = {
654 // doubles[], v={3.14159, 2.71828, 1.41429, 1.73205}
655 0x22, 0x20,
656 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
657 0x90, 0xf7, 0xaa, 0x95, 0x09, 0xbf, 0x05, 0x40,
658 0x1b, 0xf5, 0x10, 0x8d, 0xee, 0xa0, 0xf6, 0x3f,
659 0xbc, 0x96, 0x90, 0x0f, 0x7a, 0xb6, 0xfb, 0x3f,
660 };
661 // clang-format on
662
663 stream::MemoryReader reader(as_bytes(span(proto_data)));
664 RepeatedTest::StreamDecoder repeated_test(reader);
665
666 RepeatedTest::Message message{};
667 const auto status = repeated_test.Read(message);
668 ASSERT_EQ(status, Status::ResourceExhausted());
669 }
670
TEST(CodegenMessage,ReadPackedEnum)671 TEST(CodegenMessage, ReadPackedEnum) {
672 // clang-format off
673 constexpr uint8_t proto_data[] = {
674 // enums[], v={RED, GREEN, AMBER, RED}
675 0x4a, 0x04, 0x00, 0x02, 0x01, 0x00,
676 };
677 // clang-format on
678
679 stream::MemoryReader reader(as_bytes(span(proto_data)));
680 RepeatedTest::StreamDecoder repeated_test(reader);
681
682 RepeatedTest::Message message{};
683 const auto status = repeated_test.Read(message);
684 ASSERT_EQ(status, OkStatus());
685
686 ASSERT_EQ(message.enums.size(), 4u);
687 for (unsigned short i = 0; i < 4; ++i) {
688 EXPECT_TRUE(IsValidEnum(message.enums[i]));
689 }
690
691 EXPECT_EQ(message.enums[0], Enum::RED);
692 EXPECT_EQ(message.enums[1], Enum::GREEN);
693 EXPECT_EQ(message.enums[2], Enum::AMBER);
694 EXPECT_EQ(message.enums[3], Enum::RED);
695 }
696
TEST(CodegenMessage,ReadStringExhausted)697 TEST(CodegenMessage, ReadStringExhausted) {
698 // clang-format off
699 constexpr uint8_t proto_data[] = {
700 // pigweed.error_message
701 0x2a, 0xd3, 0x01, 'T', 'h', 'i', 's', ' ', 'l', 'a', 'b', 'e', 'l', ' ', 'i',
702 's', ' ', 't', 'h', 'e', ' ', 't', 'a', 'r', 'g', 'e', 't', ' ', 'o', 'f',
703 ' ', 'a', ' ', 'g', 'o', 't', 'o', ' ', 'f', 'r', 'o', 'm', ' ', 'o', 'u',
704 't', 's', 'i', 'd', 'e', ' ', 'o', 'f', ' ', 't', 'h', 'e', ' ', 'b', 'l',
705 'o', 'c', 'k', ' ', 'c', 'o', 'n', 't', 'a', 'i', 'n', 'i', 'n', 'g', ' ',
706 't', 'h', 'i', 's', ' ', 'l', 'a', 'b', 'e', 'l', ' ', 'A', 'N', 'D', ' ',
707 't', 'h', 'i', 's', ' ', 'b', 'l', 'o', 'c', 'k', ' ', 'h', 'a', 's', ' ',
708 'a', 'n', ' ', 'a', 'u', 't', 'o', 'm', 'a', 't', 'i', 'c', ' ', 'v', 'a',
709 'r', 'i', 'a', 'b', 'l', 'e', ' ', 'w', 'i', 't', 'h', ' ', 'a', 'n', ' ',
710 'i', 'n', 'i', 't', 'i', 'a', 'l', 'i', 'z', 'e', 'r', ' ', 'A', 'N', 'D',
711 ' ', 'y', 'o', 'u', 'r', ' ', 'w', 'i', 'n', 'd', 'o', 'w', ' ', 'w', 'a',
712 's', 'n', '\'', 't', ' ', 'w', 'i', 'd', 'e', ' ', 'e', 'n', 'o', 'u', 'g',
713 'h', ' ', 't', 'o', ' ', 'r', 'e', 'a', 'd', ' ', 't', 'h', 'i', 's', ' ',
714 'w', 'h', 'o', 'l', 'e', ' ', 'e', 'r', 'r', 'o', 'r', ' ', 'm', 'e', 's',
715 's', 'a', 'g', 'e'
716 };
717 // clang-format on
718
719 stream::MemoryReader reader(as_bytes(span(proto_data)));
720 Pigweed::StreamDecoder pigweed(reader);
721
722 Pigweed::Message message{};
723 const auto status = pigweed.Read(message);
724 ASSERT_EQ(status, Status::ResourceExhausted());
725 }
726
TEST(CodegenMessage,ReadStringCallback)727 TEST(CodegenMessage, ReadStringCallback) {
728 // clang-format off
729 constexpr uint8_t proto_data[] = {
730 // pigweed.description
731 0x62, 0x5c, 'a', 'n', ' ', 'o', 'p', 'e', 'n', ' ', 's', 'o', 'u', 'r', 'c',
732 'e', ' ', 'c', 'o', 'l', 'l', 'e', 'c', 't', 'i', 'o', 'n', ' ', 'o', 'f',
733 ' ', 'e', 'm', 'b', 'e', 'd', 'd', 'e', 'd', '-', 't', 'a', 'r', 'g', 'e',
734 't', 'e', 'd', ' ', 'l', 'i', 'b', 'r', 'a', 'r', 'i', 'e', 's', '-', 'o',
735 'r', ' ', 'a', 's', ' ', 'w', 'e', ' ', 'l', 'i', 'k', 'e', ' ', 't', 'o',
736 ' ', 'c', 'a', 'l', 'l', ' ', 't', 'h', 'e', 'm', ',', ' ', 'm', 'o', 'd',
737 'u', 'l', 'e', 's'
738 };
739 // clang-format on
740
741 stream::MemoryReader reader(as_bytes(span(proto_data)));
742 Pigweed::StreamDecoder pigweed(reader);
743
744 // pigweed.description has no max_size specified so a callback must be
745 // set to read the value if present.
746 Pigweed::Message message{};
747 message.description.SetDecoder([](Pigweed::StreamDecoder& decoder) {
748 EXPECT_EQ(decoder.Field().value(), Pigweed::Fields::kDescription);
749
750 constexpr std::string_view kExpectedDescription{
751 "an open source collection of embedded-targeted libraries-or as we "
752 "like to call them, modules"};
753
754 std::array<char, 128> description{};
755 const auto sws = decoder.ReadDescription(description);
756 EXPECT_EQ(sws.status(), OkStatus());
757 EXPECT_EQ(sws.size(), kExpectedDescription.size());
758 EXPECT_EQ(std::memcmp(description.data(),
759 kExpectedDescription.data(),
760 kExpectedDescription.size()),
761 0);
762
763 return sws.status();
764 });
765
766 const auto status = pigweed.Read(message);
767 ASSERT_EQ(status, OkStatus());
768 }
769
TEST(CodegenMessage,ReadMultipleString)770 TEST(CodegenMessage, ReadMultipleString) {
771 // clang-format off
772 constexpr uint8_t proto_data[] = {
773 // pigweed.error_message
774 0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ',
775 't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r',
776 // pigweed.error_message
777 0x02a, 0x07, 'o', 'n', ' ', 'f', 'i', 'r', 'e'
778 };
779 // clang-format on
780
781 stream::MemoryReader reader(as_bytes(span(proto_data)));
782 Pigweed::StreamDecoder pigweed(reader);
783
784 Pigweed::Message message{};
785 const auto status = pigweed.Read(message);
786 ASSERT_EQ(status, OkStatus());
787
788 constexpr std::string_view kExpectedErrorMessage{"on fire"};
789
790 EXPECT_EQ(message.error_message.size(), kExpectedErrorMessage.size());
791 EXPECT_EQ(std::memcmp(message.error_message.data(),
792 kExpectedErrorMessage.data(),
793 kExpectedErrorMessage.size()),
794 0);
795 }
796
TEST(CodegenMessage,ReadRepeatedStrings)797 TEST(CodegenMessage, ReadRepeatedStrings) {
798 // clang-format off
799 constexpr uint8_t proto_data[] = {
800 // repeated.strings
801 0x1a, 0x25, 'i', 'f', ' ', 'm', 'u', 's', 'i', 'c', ' ', 'b', 'e', ' ',
802 't', 'h', 'e', ' ', 'f', 'o', 'o', 'd', ' ', 'o', 'f', ' ',
803 'l', 'o', 'v', 'e', ',', ' ', 'p', 'l', 'a', 'y', ' ', 'o', 'n',
804 // repeated.strings
805 0x1a, 0x26, 'g', 'i', 'v', 'e', ' ', 'm', 'e', ' ', 'e', 'x', 'c', 'e',
806 's', 's', ' ', 'o', 'f', ' ', 'i', 't', ',', ' ', 't', 'h', 'a', 't', ',',
807 ' ', 's', 'u', 'r', 'f', 'e', 'i', 't', 'i', 'n', 'g',
808 // repeated.strings
809 0x1a, 0x23, 't', 'h', 'e', ' ', 'a', 'p', 'p', 'e', 't', 'i', 't', 'e', ' ',
810 'm', 'a', 'y', ' ', 's', 'i', 'c', 'k', 'e', 'n', ',', ' ', 'a', 'n', 'd',
811 ' ', 's', 'o', ' ', 'd', 'i', 'e',
812 };
813 // clang-format on
814
815 stream::MemoryReader reader(as_bytes(span(proto_data)));
816 RepeatedTest::StreamDecoder repeated_test(reader);
817
818 // Repeated strings require a callback to avoid forcing multi-dimensional
819 // arrays upon the caller.
820 RepeatedTest::Message message{};
821 int i = 0;
822 message.strings.SetDecoder([&i](RepeatedTest::StreamDecoder& decoder) {
823 EXPECT_EQ(decoder.Field().value(), RepeatedTest::Fields::kStrings);
824
825 constexpr std::string_view kExpectedStrings[] = {
826 {"if music be the food of love, play on"},
827 {"give me excess of it, that, surfeiting"},
828 {"the appetite may sicken, and so die"}};
829
830 std::array<char, 40> strings{};
831 const StatusWithSize sws = decoder.ReadStrings(strings);
832 EXPECT_EQ(sws.status(), OkStatus());
833 EXPECT_EQ(sws.size(), kExpectedStrings[i].size());
834 EXPECT_EQ(std::memcmp(strings.data(),
835 kExpectedStrings[i].data(),
836 kExpectedStrings[i].size()),
837 0);
838
839 ++i;
840 return sws.status();
841 });
842
843 const auto status = repeated_test.Read(message);
844 ASSERT_EQ(status, OkStatus());
845 }
846
TEST(CodegenMessage,ReadForcedCallback)847 TEST(CodegenMessage, ReadForcedCallback) {
848 // clang-format off
849 constexpr uint8_t proto_data[] = {
850 // pigweed.special_property
851 0x68, 0x2a,
852 };
853 // clang-format on
854
855 stream::MemoryReader reader(as_bytes(span(proto_data)));
856 Pigweed::StreamDecoder pigweed(reader);
857
858 // pigweed.special_property has use_callback=true to force the use of a
859 // callback even though it's a simple scalar.
860 Pigweed::Message message{};
861 message.special_property.SetDecoder([](Pigweed::StreamDecoder& decoder) {
862 EXPECT_EQ(decoder.Field().value(), Pigweed::Fields::kSpecialProperty);
863
864 pw::Result<uint32_t> result = decoder.ReadSpecialProperty();
865 EXPECT_EQ(result.status(), OkStatus());
866 EXPECT_EQ(result.value(), 42u);
867
868 return result.status();
869 });
870 const auto status = pigweed.Read(message);
871 ASSERT_EQ(status, OkStatus());
872 }
873
TEST(CodegenMessage,ReadMissingCallback)874 TEST(CodegenMessage, ReadMissingCallback) {
875 // clang-format off
876 constexpr uint8_t proto_data[] = {
877 // repeated.strings
878 0x1a, 0x25, 'i', 'f', ' ', 'm', 'u', 's', 'i', 'c', ' ', 'b', 'e', ' ',
879 't', 'h', 'e', ' ', 'f', 'o', 'o', 'd', ' ', 'o', 'f', ' ',
880 'l', 'o', 'v', 'e', ',', ' ', 'p', 'l', 'a', 'y', ' ', 'o', 'n',
881 // repeated.strings
882 0x1a, 0x26, 'g', 'i', 'v', 'e', ' ', 'm', 'e', ' ', 'e', 'x', 'c', 'e',
883 's', 's', ' ', 'o', 'f', ' ', 'i', 't', ',', ' ', 't', 'h', 'a', 't', ',',
884 ' ', 's', 'u', 'r', 'f', 'e', 'i', 't', 'i', 'n', 'g',
885 // repeated.strings
886 0x1a, 0x23, 't', 'h', 'e', ' ', 'a', 'p', 'p', 'e', 't', 'i', 't', 'e', ' ',
887 'm', 'a', 'y', ' ', 's', 'i', 'c', 'k', 'e', 'n', ',', ' ', 'a', 'n', 'd',
888 ' ', 's', 'o', ' ', 'd', 'i', 'e',
889 };
890 // clang-format on
891
892 stream::MemoryReader reader(as_bytes(span(proto_data)));
893 RepeatedTest::StreamDecoder repeated_test(reader);
894
895 // Failing to set a callback will give a DataLoss error if that field is
896 // present in the decoded data.
897 RepeatedTest::Message message{};
898 const auto status = repeated_test.Read(message);
899 ASSERT_EQ(status, Status::DataLoss());
900 }
901
TEST(CodegenMessage,ReadFixedLength)902 TEST(CodegenMessage, ReadFixedLength) {
903 // clang-format off
904 constexpr uint8_t proto_data[] = {
905 // pigweed.data
906 0x5a, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
907 };
908 // clang-format on
909
910 stream::MemoryReader reader(as_bytes(span(proto_data)));
911 Pigweed::StreamDecoder pigweed(reader);
912
913 Pigweed::Message message{};
914 const auto status = pigweed.Read(message);
915 ASSERT_EQ(status, OkStatus());
916
917 EXPECT_EQ(message.data[0], std::byte{0x01});
918 EXPECT_EQ(message.data[1], std::byte{0x02});
919 EXPECT_EQ(message.data[2], std::byte{0x03});
920 EXPECT_EQ(message.data[3], std::byte{0x04});
921 EXPECT_EQ(message.data[4], std::byte{0x05});
922 EXPECT_EQ(message.data[5], std::byte{0x06});
923 EXPECT_EQ(message.data[6], std::byte{0x07});
924 EXPECT_EQ(message.data[7], std::byte{0x08});
925 }
926
TEST(CodegenMessage,ReadFixedLengthShort)927 TEST(CodegenMessage, ReadFixedLengthShort) {
928 // clang-format off
929 constexpr uint8_t proto_data[] = {
930 // pigweed.data
931 0x5a, 0x04, 0x01, 0x02, 0x03, 0x04
932 };
933 // clang-format on
934
935 stream::MemoryReader reader(as_bytes(span(proto_data)));
936 Pigweed::StreamDecoder pigweed(reader);
937
938 Pigweed::Message message{};
939 const auto status = pigweed.Read(message);
940 ASSERT_EQ(status, OkStatus());
941
942 EXPECT_EQ(message.data[0], std::byte{0x01});
943 EXPECT_EQ(message.data[1], std::byte{0x02});
944 EXPECT_EQ(message.data[2], std::byte{0x03});
945 EXPECT_EQ(message.data[3], std::byte{0x04});
946 // Remaining bytes are whatever you initialized them to.
947 EXPECT_EQ(message.data[4], std::byte{0x00});
948 EXPECT_EQ(message.data[5], std::byte{0x00});
949 EXPECT_EQ(message.data[6], std::byte{0x00});
950 EXPECT_EQ(message.data[7], std::byte{0x00});
951 }
952
TEST(CodegenMessage,ReadFixedLengthExhausted)953 TEST(CodegenMessage, ReadFixedLengthExhausted) {
954 // clang-format off
955 constexpr uint8_t proto_data[] = {
956 // pigweed.data
957 0x5a, 0x0c, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
958 0x09, 0x0a, 0x0b, 0x0c
959 };
960 // clang-format on
961
962 stream::MemoryReader reader(as_bytes(span(proto_data)));
963 Pigweed::StreamDecoder pigweed(reader);
964
965 Pigweed::Message message{};
966 const auto status = pigweed.Read(message);
967 ASSERT_EQ(status, Status::ResourceExhausted());
968 }
969
TEST(CodegenMessage,ReadNested)970 TEST(CodegenMessage, ReadNested) {
971 // clang-format off
972 constexpr uint8_t proto_data[] = {
973 // pigweed.magic_number
974 0x08, 0x49,
975 // pigweed.pigweed
976 0x3a, 0x02,
977 // pigweed.pigweed.status
978 0x08, 0x02,
979 // pigweed.ziggy
980 0x10, 0xdd, 0x01,
981 };
982 // clang-format on
983
984 stream::MemoryReader reader(as_bytes(span(proto_data)));
985 Pigweed::StreamDecoder pigweed(reader);
986
987 Pigweed::Message message{};
988 const auto status = pigweed.Read(message);
989 ASSERT_EQ(status, OkStatus());
990
991 EXPECT_EQ(message.magic_number, 0x49u);
992 EXPECT_EQ(message.pigweed.status, Bool::FILE_NOT_FOUND);
993 EXPECT_EQ(message.ziggy, -111);
994 }
995
TEST(CodegenMessage,ReadNestedImported)996 TEST(CodegenMessage, ReadNestedImported) {
997 // clang-format off
998 constexpr uint8_t proto_data[] = {
999 // period.start
1000 0x0a, 0x08,
1001 // period.start.seconds v=1517949900
1002 0x08, 0xcc, 0xa7, 0xe8, 0xd3, 0x05,
1003 // period.start.nanoseconds v=0
1004 0x10, 0x00,
1005 // period.end
1006 0x12, 0x08,
1007 // period.end.seconds, v=1517950378
1008 0x08, 0xaa, 0xab, 0xe8, 0xd3, 0x05,
1009 // period.end.nanoseconds, v=0
1010 0x10, 0x00,
1011 };
1012 // clang-format on
1013
1014 stream::MemoryReader reader(as_bytes(span(proto_data)));
1015 Period::StreamDecoder period(reader);
1016
1017 // Messages imported from another file can be directly embedded in a message.
1018 Period::Message message{};
1019 const auto status = period.Read(message);
1020 ASSERT_EQ(status, OkStatus());
1021
1022 EXPECT_EQ(message.start.seconds, 1517949900u);
1023 EXPECT_EQ(message.start.nanoseconds, 0u);
1024 EXPECT_EQ(message.end.seconds, 1517950378u);
1025 EXPECT_EQ(message.end.nanoseconds, 0u);
1026 }
1027
TEST(CodegenMessage,ReadNestedRepeated)1028 TEST(CodegenMessage, ReadNestedRepeated) {
1029 // clang-format off
1030 constexpr uint8_t proto_data[] = {
1031 // repeated.structs
1032 0x2a, 0x04,
1033 // repeated.structs.one v=16
1034 0x08, 0x10,
1035 // repeated.structs.two v=32
1036 0x10, 0x20,
1037 // repeated.structs
1038 0x2a, 0x04,
1039 // repeated.structs.one v=48
1040 0x08, 0x30,
1041 // repeated.structs.two v=64
1042 0x10, 0x40,
1043 };
1044 // clang-format on
1045
1046 stream::MemoryReader reader(as_bytes(span(proto_data)));
1047 RepeatedTest::StreamDecoder repeated_test(reader);
1048
1049 // Repeated nested messages require a callback since there would otherwise be
1050 // no way to set callbacks on the nested message.
1051 RepeatedTest::Message message{};
1052 unsigned i = 0;
1053 message.structs.SetDecoder([&i](RepeatedTest::StreamDecoder& decoder) {
1054 EXPECT_EQ(decoder.Field().value(), RepeatedTest::Fields::kStructs);
1055
1056 Struct::Message structs_message{};
1057 auto structs_decoder = decoder.GetStructsDecoder();
1058 const auto status = structs_decoder.Read(structs_message);
1059 EXPECT_EQ(status, OkStatus());
1060
1061 EXPECT_LT(i, 2u);
1062 EXPECT_EQ(structs_message.one, i * 32 + 16u);
1063 EXPECT_EQ(structs_message.two, i * 32 + 32u);
1064 ++i;
1065
1066 return status;
1067 });
1068
1069 const auto status = repeated_test.Read(message);
1070 ASSERT_EQ(status, OkStatus());
1071 }
1072
TEST(CodegenMessage,ReadNestedForcedCallback)1073 TEST(CodegenMessage, ReadNestedForcedCallback) {
1074 // clang-format off
1075 constexpr uint8_t proto_data[] = {
1076 // pigweed.device_info
1077 0x32, 0x0e,
1078 // pigweed.device_info.device_name
1079 0x0a, 0x05, 'p', 'i', 'x', 'e', 'l',
1080 // pigweed.device_info.device_id
1081 0x15, 0x08, 0x08, 0x08, 0x08,
1082 // pigweed.device_info.status
1083 0x18, 0x00,
1084 };
1085 // clang-format on
1086
1087 stream::MemoryReader reader(as_bytes(span(proto_data)));
1088 Pigweed::StreamDecoder pigweed(reader);
1089
1090 // pigweed.device_info has use_callback=true to force the use of a callback.
1091 Pigweed::Message message{};
1092 message.device_info.SetDecoder([](Pigweed::StreamDecoder& decoder) {
1093 EXPECT_EQ(decoder.Field().value(), Pigweed::Fields::kDeviceInfo);
1094
1095 DeviceInfo::Message device_info{};
1096 DeviceInfo::StreamDecoder device_info_decoder =
1097 decoder.GetDeviceInfoDecoder();
1098 const auto status = device_info_decoder.Read(device_info);
1099 EXPECT_EQ(status, OkStatus());
1100
1101 constexpr std::string_view kExpectedDeviceName{"pixel"};
1102
1103 EXPECT_EQ(device_info.device_name.size(), kExpectedDeviceName.size());
1104 EXPECT_EQ(std::memcmp(device_info.device_name.data(),
1105 kExpectedDeviceName.data(),
1106 kExpectedDeviceName.size()),
1107 0);
1108 EXPECT_EQ(device_info.device_id, 0x08080808u);
1109 EXPECT_EQ(device_info.status, DeviceInfo::DeviceStatus::OK);
1110
1111 return status;
1112 });
1113 const auto status = pigweed.Read(message);
1114 ASSERT_EQ(status, OkStatus());
1115 }
1116
TEST(CodegenMessage,ReadOptionalPresent)1117 TEST(CodegenMessage, ReadOptionalPresent) {
1118 // clang-format off
1119 constexpr uint8_t proto_data[] = {
1120 // optional.sometimes_present_fixed
1121 0x0d, 0x2a, 0x00, 0x00, 0x00,
1122 // optional.sometimes_present_varint
1123 0x10, 0x2a,
1124 // optional.explicitly_present_fixed
1125 0x1d, 0x45, 0x00, 0x00, 0x00,
1126 // optional.explicitly_present_varint
1127 0x20, 0x45,
1128 // optional.sometimes_empty_fixed
1129 0x2a, 0x04, 0x63, 0x00, 0x00, 0x00,
1130 // optional.sometimes_empty_varint
1131 0x32, 0x01, 0x63,
1132 };
1133 // clang-format on
1134
1135 stream::MemoryReader reader(as_bytes(span(proto_data)));
1136 OptionalTest::StreamDecoder optional_test(reader);
1137
1138 OptionalTest::Message message{};
1139 const auto status = optional_test.Read(message);
1140 ASSERT_EQ(status, OkStatus());
1141
1142 EXPECT_EQ(message.sometimes_present_fixed, 0x2a);
1143 EXPECT_EQ(message.sometimes_present_varint, 0x2a);
1144 EXPECT_TRUE(message.explicitly_present_fixed);
1145 EXPECT_EQ(*message.explicitly_present_fixed, 0x45);
1146 EXPECT_TRUE(message.explicitly_present_varint);
1147 EXPECT_EQ(*message.explicitly_present_varint, 0x45);
1148 EXPECT_FALSE(message.sometimes_empty_fixed.empty());
1149 EXPECT_EQ(message.sometimes_empty_fixed.size(), 1u);
1150 EXPECT_EQ(message.sometimes_empty_fixed[0], 0x63);
1151 EXPECT_FALSE(message.sometimes_empty_varint.empty());
1152 EXPECT_EQ(message.sometimes_empty_varint.size(), 1u);
1153 EXPECT_EQ(message.sometimes_empty_varint[0], 0x63);
1154 }
1155
TEST(CodegenMessage,ReadOptionalNotPresent)1156 TEST(CodegenMessage, ReadOptionalNotPresent) {
1157 constexpr std::array<std::byte, 0> proto_data{};
1158
1159 stream::MemoryReader reader(proto_data);
1160 OptionalTest::StreamDecoder optional_test(reader);
1161
1162 OptionalTest::Message message{};
1163 const auto status = optional_test.Read(message);
1164 ASSERT_EQ(status, OkStatus());
1165
1166 // Non-optional fields have their default value.
1167 EXPECT_EQ(message.sometimes_present_fixed, 0);
1168 EXPECT_EQ(message.sometimes_present_varint, 0);
1169 EXPECT_TRUE(message.sometimes_empty_fixed.empty());
1170 EXPECT_TRUE(message.sometimes_empty_varint.empty());
1171
1172 // Optional fields are explicitly not present.
1173 EXPECT_FALSE(message.explicitly_present_fixed);
1174 EXPECT_FALSE(message.explicitly_present_varint);
1175 }
1176
TEST(CodegenMessage,ReadOptionalPresentDefaults)1177 TEST(CodegenMessage, ReadOptionalPresentDefaults) {
1178 // clang-format off
1179 constexpr uint8_t proto_data[] = {
1180 // optional.sometimes_present_fixed
1181 0x0d, 0x00, 0x00, 0x00, 0x00,
1182 // optional.sometimes_present_varint
1183 0x10, 0x00,
1184 // optional.explicitly_present_fixed
1185 0x1d, 0x00, 0x00, 0x00, 0x00,
1186 // optional.explicitly_present_varint
1187 0x20, 0x00,
1188 // optional.sometimes_empty_fixed
1189 0x2a, 0x04, 0x00, 0x00, 0x00, 0x00,
1190 // optional.sometimes_empty_varint
1191 0x32, 0x01, 0x00,
1192 };
1193 // clang-format on
1194
1195 stream::MemoryReader reader(as_bytes(span(proto_data)));
1196 OptionalTest::StreamDecoder optional_test(reader);
1197
1198 OptionalTest::Message message{};
1199 const auto status = optional_test.Read(message);
1200 ASSERT_EQ(status, OkStatus());
1201
1202 // Non-optional fields have their default value and aren't meaningfully
1203 // different from missing.
1204 EXPECT_EQ(message.sometimes_present_fixed, 0x00);
1205 EXPECT_EQ(message.sometimes_present_varint, 0x00);
1206
1207 // Optional fields are explicitly present with a default value.
1208 EXPECT_TRUE(message.explicitly_present_fixed);
1209 EXPECT_EQ(*message.explicitly_present_fixed, 0x00);
1210 EXPECT_TRUE(message.explicitly_present_varint);
1211 EXPECT_EQ(*message.explicitly_present_varint, 0x00);
1212
1213 // Repeated fields with a default value are meaningfully non-empty.
1214 EXPECT_FALSE(message.sometimes_empty_fixed.empty());
1215 EXPECT_EQ(message.sometimes_empty_fixed.size(), 1u);
1216 EXPECT_EQ(message.sometimes_empty_fixed[0], 0x00);
1217 EXPECT_FALSE(message.sometimes_empty_varint.empty());
1218 EXPECT_EQ(message.sometimes_empty_varint.size(), 1u);
1219 EXPECT_EQ(message.sometimes_empty_varint[0], 0x00);
1220 }
1221
TEST(CodegenMessage,ReadImportedOptions)1222 TEST(CodegenMessage, ReadImportedOptions) {
1223 // clang-format off
1224 constexpr uint8_t proto_data[] = {
1225 // notice
1226 0x0a, 0x0f,
1227 // notice.message
1228 0x0a, 0x0d, 'P', 'r', 'e', 's', 's', ' ', 'a', 'n', 'y', ' ', 'k', 'e', 'y'
1229 };
1230 // clang-format on
1231
1232 stream::MemoryReader reader(as_bytes(span(proto_data)));
1233 TestMessage::StreamDecoder test_message(reader);
1234
1235 // The options file for the imported proto is applied, making the string
1236 // field a vector rather than requiring a callback.
1237 TestMessage::Message message{};
1238 const auto status = test_message.Read(message);
1239 ASSERT_EQ(status, OkStatus());
1240
1241 constexpr std::string_view kExpectedMessage{"Press any key"};
1242
1243 EXPECT_EQ(message.notice.message.size(), kExpectedMessage.size());
1244 EXPECT_EQ(std::memcmp(message.notice.message.data(),
1245 kExpectedMessage.data(),
1246 kExpectedMessage.size()),
1247 0);
1248 }
1249
TEST(CodegenMessage,ReadImportedFromDepsOptions)1250 TEST(CodegenMessage, ReadImportedFromDepsOptions) {
1251 // clang-format off
1252 constexpr uint8_t proto_data[] = {
1253 // debug
1254 0x12, 0x0f,
1255 // debug.message
1256 0x0a, 0x0d, 'P', 'r', 'e', 's', 's', ' ', 'a', 'n', 'y', ' ', 'k', 'e', 'y'
1257 };
1258 // clang-format on
1259
1260 stream::MemoryReader reader(as_bytes(span(proto_data)));
1261 TestMessage::StreamDecoder test_message(reader);
1262
1263 // The options file for the imported proto is applied, making the string
1264 // fields a vector rather than requiring a callback. This will not compile if
1265 // the .pwpb_options files aren't applied correctly.
1266 TestMessage::Message message{};
1267 const auto status = test_message.Read(message);
1268 ASSERT_EQ(status, OkStatus());
1269
1270 constexpr std::string_view kExpectedMessage{"Press any key"};
1271
1272 EXPECT_EQ(message.debug.message.size(), kExpectedMessage.size());
1273 EXPECT_EQ(std::memcmp(message.debug.message.data(),
1274 kExpectedMessage.data(),
1275 kExpectedMessage.size()),
1276 0);
1277 EXPECT_EQ(message.prefix_debug.message.size(), 0U);
1278 }
1279
1280 class BreakableDecoder : public KeyValuePair::StreamDecoder {
1281 public:
BreakableDecoder(stream::Reader & reader)1282 constexpr BreakableDecoder(stream::Reader& reader) : StreamDecoder(reader) {}
1283
Read(KeyValuePair::Message & message,span<const internal::MessageField> table)1284 Status Read(KeyValuePair::Message& message,
1285 span<const internal::MessageField> table) {
1286 return ::pw::protobuf::StreamDecoder::Read(
1287 as_writable_bytes(span(&message, 1)), table);
1288 }
1289 };
1290
TEST(CodegenMessage,DISABLED_ReadDoesNotOverrun)1291 TEST(CodegenMessage, DISABLED_ReadDoesNotOverrun) {
1292 // Deliberately construct a message table that attempts to violate the bounds
1293 // of the structure. We're not testing that a developer can't do this, rather
1294 // that the protobuf decoder can't be exploited in this way.
1295 constexpr internal::MessageField kMessageFields[] = {
1296 {1,
1297 WireType::kDelimited,
1298 sizeof(std::byte),
1299 static_cast<internal::VarintType>(0),
1300 false,
1301 false,
1302 false,
1303 false,
1304 internal::CallbackType::kNone,
1305 0,
1306 sizeof(KeyValuePair::Message) * 2,
1307 {}},
1308 };
1309
1310 // clang-format off
1311 constexpr uint8_t proto_data[] = {
1312 // id=1, len=9,
1313 0x0a, 0x08, 'd', 'o', 'n', 't', 'e', 'a', 't', 'm', 'e',
1314 };
1315 // clang-format on
1316
1317 stream::MemoryReader reader(as_bytes(span(proto_data)));
1318 BreakableDecoder decoder(reader);
1319
1320 KeyValuePair::Message message{};
1321 // ASSERT_CRASH
1322 std::ignore = decoder.Read(message, kMessageFields);
1323 }
1324
TEST(CodegenMessage,Write)1325 TEST(CodegenMessage, Write) {
1326 constexpr uint8_t pigweed_data[] = {
1327 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80};
1328
1329 Pigweed::Message message{};
1330 message.magic_number = 0x49u;
1331 message.ziggy = -111;
1332 message.cycles = 0x40302010fecaaddeu;
1333 message.ratio = -1.42f;
1334 message.error_message = "not a typewriter";
1335 message.pigweed.status = Bool::FILE_NOT_FOUND;
1336 message.bin = Pigweed::Protobuf::Binary::ZERO;
1337 message.bungle = -111;
1338 message.proto.bin = Proto::Binary::OFF;
1339 message.proto.pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO;
1340 message.proto.pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ZERO;
1341 message.proto.meta.file_name = "/etc/passwd";
1342 message.proto.meta.status = Pigweed::Protobuf::Compiler::Status::FUBAR;
1343 message.proto.meta.protobuf_bin = Pigweed::Protobuf::Binary::ONE;
1344 message.proto.meta.pigweed_bin = Pigweed::Pigweed::Binary::ONE;
1345 std::memcpy(message.data.data(), pigweed_data, sizeof(pigweed_data));
1346
1347 std::byte encode_buffer[Pigweed::kMaxEncodedSizeBytes];
1348 std::byte temp_buffer[Pigweed::kScratchBufferSizeBytes];
1349
1350 stream::MemoryWriter writer(encode_buffer);
1351 Pigweed::StreamEncoder pigweed(writer, temp_buffer);
1352
1353 const auto status = pigweed.Write(message);
1354 ASSERT_EQ(status, OkStatus());
1355
1356 // clang-format off
1357 constexpr uint8_t expected_proto[] = {
1358 // pigweed.magic_number
1359 0x08, 0x49,
1360 // pigweed.ziggy
1361 0x10, 0xdd, 0x01,
1362 // pigweed.cycles
1363 0x19, 0xde, 0xad, 0xca, 0xfe, 0x10, 0x20, 0x30, 0x40,
1364 // pigweed.ratio
1365 0x25, 0x8f, 0xc2, 0xb5, 0xbf,
1366 // pigweed.error_message
1367 0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ',
1368 't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r',
1369 // pigweed.pigweed
1370 0x3a, 0x02,
1371 // pigweed.pigweed.status
1372 0x08, 0x02,
1373 // pigweed.bin
1374 0x40, 0x01,
1375 // pigweed.proto
1376 0x4a, 0x15,
1377 // pigweed.proto.pigweed_protobuf_bin
1378 0x20, 0x01,
1379 // pigweed.proto.meta
1380 0x2a, 0x11,
1381 // pigweed.proto.meta.file_name
1382 0x0a, 0x0b, '/', 'e', 't', 'c', '/', 'p', 'a', 's', 's', 'w', 'd',
1383 // pigweed.proto.meta.status
1384 0x10, 0x02,
1385 // pigweed.proto.meta.pigweed_bin
1386 0x20, 0x01,
1387 // pigweed.bytes
1388 0x5a, 0x08, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80,
1389 // pigweed.bungle
1390 0x70, 0x91, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01,
1391 };
1392 // clang-format on
1393
1394 ConstByteSpan result = writer.WrittenData();
1395 EXPECT_EQ(result.size(), sizeof(expected_proto));
1396 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1397 0);
1398 }
1399
TEST(CodegenMessage,WriteDefaults)1400 TEST(CodegenMessage, WriteDefaults) {
1401 Pigweed::Message message{};
1402
1403 std::byte encode_buffer[Pigweed::kMaxEncodedSizeBytes];
1404 std::byte temp_buffer[Pigweed::kScratchBufferSizeBytes];
1405
1406 stream::MemoryWriter writer(encode_buffer);
1407 Pigweed::StreamEncoder pigweed(writer, temp_buffer);
1408
1409 const auto status = pigweed.Write(message);
1410 ASSERT_EQ(status, OkStatus());
1411
1412 // Since all fields are at their default, the output should be zero sized.
1413 ConstByteSpan result = writer.WrittenData();
1414 EXPECT_EQ(result.size(), 0u);
1415 }
1416
TEST(CodegenMessage,WritePackedScalar)1417 TEST(CodegenMessage, WritePackedScalar) {
1418 RepeatedTest::Message message{};
1419 for (unsigned i = 0; i < 4; ++i) {
1420 message.uint32s.push_back(i * 16u);
1421 message.fixed32s.push_back(i * 16u);
1422 }
1423
1424 std::byte encode_buffer[RepeatedTest::kMaxEncodedSizeBytes];
1425
1426 stream::MemoryWriter writer(encode_buffer);
1427 RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());
1428
1429 const auto status = repeated_test.Write(message);
1430 ASSERT_EQ(status, OkStatus());
1431
1432 // clang-format off
1433 constexpr uint8_t expected_proto[] = {
1434 // uint32s[], v={0, 16, 32, 48}
1435 0x0a, 0x04,
1436 0x00,
1437 0x10,
1438 0x20,
1439 0x30,
1440 // fixed32s[]. v={0, 16, 32, 48}
1441 0x32, 0x10,
1442 0x00, 0x00, 0x00, 0x00,
1443 0x10, 0x00, 0x00, 0x00,
1444 0x20, 0x00, 0x00, 0x00,
1445 0x30, 0x00, 0x00, 0x00,
1446 };
1447 // clang-format on
1448
1449 ConstByteSpan result = writer.WrittenData();
1450 EXPECT_EQ(result.size(), sizeof(expected_proto));
1451 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1452 0);
1453 }
1454
TEST(CodegenMessage,WritePackedScalarFixedLength)1455 TEST(CodegenMessage, WritePackedScalarFixedLength) {
1456 RepeatedTest::Message message{};
1457 for (unsigned i = 0; i < 4; ++i) {
1458 message.uint64s[i] = (i + 1) * 1000u;
1459 }
1460 message.doubles[0] = 3.14159;
1461 message.doubles[1] = 2.71828;
1462
1463 std::byte encode_buffer[RepeatedTest::kMaxEncodedSizeBytes];
1464
1465 stream::MemoryWriter writer(encode_buffer);
1466 RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());
1467
1468 const auto status = repeated_test.Write(message);
1469 ASSERT_EQ(status, OkStatus());
1470
1471 // clang-format off
1472 constexpr uint8_t expected_proto[] = {
1473 // doubles[], v={3.14159, 2.71828}
1474 0x22, 0x10,
1475 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
1476 0x90, 0xf7, 0xaa, 0x95, 0x09, 0xbf, 0x05, 0x40,
1477 // uint64s[], v={1000, 2000, 3000, 4000}
1478 0x42, 0x08, 0xe8, 0x07, 0xd0, 0x0f, 0xb8, 0x17, 0xa0, 0x1f,
1479 };
1480 // clang-format on
1481
1482 ConstByteSpan result = writer.WrittenData();
1483 EXPECT_EQ(result.size(), sizeof(expected_proto));
1484 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1485 0);
1486 }
1487
TEST(CodegenMessage,WritePackedScalarCallback)1488 TEST(CodegenMessage, WritePackedScalarCallback) {
1489 RepeatedTest::Message message{};
1490 message.sint32s.SetEncoder([](RepeatedTest::StreamEncoder& encoder) {
1491 constexpr int32_t sint32s[] = {-25, -1, 0, 1, 25};
1492 return encoder.WriteSint32s(sint32s);
1493 });
1494
1495 std::byte encode_buffer[RepeatedTest::kMaxEncodedSizeBytes +
1496 varint::kMaxVarint32SizeBytes * 5];
1497
1498 stream::MemoryWriter writer(encode_buffer);
1499 RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());
1500
1501 const auto status = repeated_test.Write(message);
1502 ASSERT_EQ(status, OkStatus());
1503
1504 // clang-format off
1505 constexpr uint8_t expected_proto[] = {
1506 // sint32s[], v={-25, -1, 0, 1, 25}
1507 0x12, 0x05,
1508 0x31,
1509 0x01,
1510 0x00,
1511 0x02,
1512 0x32,
1513 };
1514 // clang-format on
1515
1516 ConstByteSpan result = writer.WrittenData();
1517 EXPECT_EQ(result.size(), sizeof(expected_proto));
1518 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1519 0);
1520 }
1521
TEST(CodegenMessage,WritePackedEnum)1522 TEST(CodegenMessage, WritePackedEnum) {
1523 RepeatedTest::Message message{};
1524 message.enums.push_back(Enum::RED);
1525 message.enums.push_back(Enum::GREEN);
1526 message.enums.push_back(Enum::AMBER);
1527 message.enums.push_back(Enum::RED);
1528
1529 std::byte encode_buffer[RepeatedTest::kMaxEncodedSizeBytes];
1530
1531 stream::MemoryWriter writer(encode_buffer);
1532 RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());
1533
1534 const auto status = repeated_test.Write(message);
1535 ASSERT_EQ(status, OkStatus());
1536
1537 // clang-format off
1538 constexpr uint8_t expected_proto[] = {
1539 // enums[], v={RED, GREEN, AMBER, RED}
1540 0x4a, 0x04, 0x00, 0x02, 0x01, 0x00,
1541 };
1542 // clang-format on
1543
1544 ConstByteSpan result = writer.WrittenData();
1545 EXPECT_EQ(result.size(), sizeof(expected_proto));
1546 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1547 0);
1548 }
1549
TEST(CodegenMessage,WriteStringCallback)1550 TEST(CodegenMessage, WriteStringCallback) {
1551 Pigweed::Message message{};
1552 // pigweed.description has no max_size specified so a callback must be
1553 // set to write the value.
1554 message.description.SetEncoder([](Pigweed::StreamEncoder& encoder) {
1555 return encoder.WriteDescription(
1556 "an open source collection of embedded-targeted "
1557 "libraries-or as we like to call them, modules");
1558 });
1559
1560 std::byte encode_buffer[Pigweed::kMaxEncodedSizeBytes + 92];
1561 std::byte temp_buffer[Pigweed::kScratchBufferSizeBytes];
1562
1563 stream::MemoryWriter writer(encode_buffer);
1564 Pigweed::StreamEncoder pigweed(writer, temp_buffer);
1565
1566 const auto status = pigweed.Write(message);
1567 ASSERT_EQ(status, OkStatus());
1568
1569 // clang-format off
1570 constexpr uint8_t expected_proto[] = {
1571 // pigweed.description
1572 0x62, 0x5c, 'a', 'n', ' ', 'o', 'p', 'e', 'n', ' ', 's', 'o', 'u', 'r', 'c',
1573 'e', ' ', 'c', 'o', 'l', 'l', 'e', 'c', 't', 'i', 'o', 'n', ' ', 'o', 'f',
1574 ' ', 'e', 'm', 'b', 'e', 'd', 'd', 'e', 'd', '-', 't', 'a', 'r', 'g', 'e',
1575 't', 'e', 'd', ' ', 'l', 'i', 'b', 'r', 'a', 'r', 'i', 'e', 's', '-', 'o',
1576 'r', ' ', 'a', 's', ' ', 'w', 'e', ' ', 'l', 'i', 'k', 'e', ' ', 't', 'o',
1577 ' ', 'c', 'a', 'l', 'l', ' ', 't', 'h', 'e', 'm', ',', ' ', 'm', 'o', 'd',
1578 'u', 'l', 'e', 's',
1579 };
1580 // clang-format on
1581
1582 ConstByteSpan result = writer.WrittenData();
1583 EXPECT_EQ(result.size(), sizeof(expected_proto));
1584 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1585 0);
1586 }
1587
TEST(CodegenMessage,WriteForcedCallback)1588 TEST(CodegenMessage, WriteForcedCallback) {
1589 Pigweed::Message message{};
1590 // pigweed.special_property has use_callback=true to force the use of a
1591 // callback even though it's a simple scalar.
1592 message.special_property.SetEncoder([](Pigweed::StreamEncoder& encoder) {
1593 return encoder.WriteSpecialProperty(42u);
1594 });
1595
1596 std::byte encode_buffer[Pigweed::kMaxEncodedSizeBytes];
1597 std::byte temp_buffer[Pigweed::kScratchBufferSizeBytes];
1598
1599 stream::MemoryWriter writer(encode_buffer);
1600 Pigweed::StreamEncoder pigweed(writer, temp_buffer);
1601
1602 const auto status = pigweed.Write(message);
1603 ASSERT_EQ(status, OkStatus());
1604
1605 // clang-format off
1606 constexpr uint8_t expected_proto[] = {
1607 // pigweed.special_property
1608 0x68, 0x2a,
1609 };
1610 // clang-format on
1611
1612 ConstByteSpan result = writer.WrittenData();
1613 EXPECT_EQ(result.size(), sizeof(expected_proto));
1614 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1615 0);
1616 }
1617
TEST(CodegenMessage,WriteNestedImported)1618 TEST(CodegenMessage, WriteNestedImported) {
1619 Period::Message message{};
1620 message.start.seconds = 1517949900u;
1621 message.end.seconds = 1517950378u;
1622
1623 std::byte encode_buffer[Period::kMaxEncodedSizeBytes];
1624 std::byte temp_buffer[Period::kScratchBufferSizeBytes];
1625
1626 stream::MemoryWriter writer(encode_buffer);
1627 Period::StreamEncoder period(writer, temp_buffer);
1628
1629 const auto status = period.Write(message);
1630 ASSERT_EQ(status, OkStatus());
1631
1632 // clang-format off
1633 constexpr uint8_t expected_proto[] = {
1634 // period.start
1635 0x0a, 0x06,
1636 // period.start.seconds v=1517949900
1637 0x08, 0xcc, 0xa7, 0xe8, 0xd3, 0x05,
1638 // period.end
1639 0x12, 0x06,
1640 // period.end.seconds, v=1517950378
1641 0x08, 0xaa, 0xab, 0xe8, 0xd3, 0x05,
1642 };
1643 // clang-format on
1644
1645 ConstByteSpan result = writer.WrittenData();
1646 EXPECT_EQ(result.size(), sizeof(expected_proto));
1647 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1648 0);
1649 }
1650
TEST(CodegenMessage,WriteNestedRepeated)1651 TEST(CodegenMessage, WriteNestedRepeated) {
1652 RepeatedTest::Message message{};
1653 // Repeated nested messages require a callback since there would otherwise be
1654 // no way to set callbacks on the nested message.
1655 message.structs.SetEncoder([](RepeatedTest::StreamEncoder& encoder) {
1656 for (uint32_t i = 0; i < 2; ++i) {
1657 Struct::Message struct_message{};
1658 struct_message.one = i * 32 + 16u;
1659 struct_message.two = i * 32 + 32u;
1660
1661 const auto status = encoder.GetStructsEncoder().Write(struct_message);
1662 EXPECT_EQ(status, OkStatus());
1663 }
1664 return OkStatus();
1665 });
1666
1667 std::byte encode_buffer[RepeatedTest::kMaxEncodedSizeBytes +
1668 Struct::kMaxEncodedSizeBytes * 2];
1669 std::byte temp_buffer[RepeatedTest::kScratchBufferSizeBytes +
1670 Struct::kMaxEncodedSizeBytes];
1671
1672 stream::MemoryWriter writer(encode_buffer);
1673 RepeatedTest::StreamEncoder repeated_test(writer, temp_buffer);
1674
1675 const auto status = repeated_test.Write(message);
1676 ASSERT_EQ(status, OkStatus());
1677
1678 // clang-format off
1679 constexpr uint8_t expected_proto[] = {
1680 // repeated.structs
1681 0x2a, 0x04,
1682 // repeated.structs.one v=16
1683 0x08, 0x10,
1684 // repeated.structs.two v=32
1685 0x10, 0x20,
1686 // repeated.structs
1687 0x2a, 0x04,
1688 // repeated.structs.one v=48
1689 0x08, 0x30,
1690 // repeated.structs.two v=64
1691 0x10, 0x40,
1692 };
1693 // clang-format on
1694
1695 ConstByteSpan result = writer.WrittenData();
1696 EXPECT_EQ(result.size(), sizeof(expected_proto));
1697 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1698 0);
1699 }
1700
TEST(CodegenMessage,WriteNestedForcedCallback)1701 TEST(CodegenMessage, WriteNestedForcedCallback) {
1702 Pigweed::Message message{};
1703 // pigweed.device_info has use_callback=true to force the use of a callback.
1704 message.device_info.SetEncoder([](Pigweed::StreamEncoder& encoder) {
1705 DeviceInfo::Message device_info{};
1706 device_info.device_name = "pixel";
1707 device_info.device_id = 0x08080808u;
1708 device_info.status = DeviceInfo::DeviceStatus::OK;
1709
1710 // Use the callback to set nested callbacks.
1711 device_info.attributes.SetEncoder(
1712 [](DeviceInfo::StreamEncoder& device_info_encoder) {
1713 KeyValuePair::Message attribute{};
1714
1715 attribute.key = "version";
1716 attribute.value = "5.3.1";
1717 PW_TRY(device_info_encoder.GetAttributesEncoder().Write(attribute));
1718
1719 attribute.key = "chip";
1720 attribute.value = "left-soc";
1721 PW_TRY(device_info_encoder.GetAttributesEncoder().Write(attribute));
1722
1723 return OkStatus();
1724 });
1725
1726 return encoder.GetDeviceInfoEncoder().Write(device_info);
1727 });
1728
1729 std::byte encode_buffer[Pigweed::kMaxEncodedSizeBytes +
1730 DeviceInfo::kMaxEncodedSizeBytes];
1731 std::byte temp_buffer[Pigweed::kScratchBufferSizeBytes +
1732 DeviceInfo::kMaxEncodedSizeBytes];
1733
1734 stream::MemoryWriter writer(encode_buffer);
1735 Pigweed::StreamEncoder pigweed(writer, temp_buffer);
1736
1737 const auto status = pigweed.Write(message);
1738 ASSERT_EQ(status, OkStatus());
1739
1740 // clang-format off
1741 constexpr uint8_t expected_proto[] = {
1742 // pigweed.device_info
1743 0x32, 0x30,
1744 // pigweed.device_info.device_name
1745 0x0a, 0x05, 'p', 'i', 'x', 'e', 'l',
1746 // pigweed.device_info.device_id
1747 0x15, 0x08, 0x08, 0x08, 0x08,
1748 // pigweed.device_info.attributes[0]
1749 0x22, 0x10,
1750 // pigweed.device_info.attributes[0].key
1751 0x0a, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
1752 // pigweed.device_info.attributes[0].value
1753 0x12, 0x05, '5', '.', '3', '.', '1',
1754 // pigweed.device_info.attributes[1]
1755 0x22, 0x10,
1756 // pigweed.device_info.attributes[1].key
1757 0x0a, 0x04, 'c', 'h', 'i', 'p',
1758 // pigweed.device_info.attributes[1].value
1759 0x12, 0x08, 'l', 'e', 'f', 't', '-', 's', 'o', 'c',
1760 };
1761 // clang-format on
1762
1763 ConstByteSpan result = writer.WrittenData();
1764 EXPECT_EQ(result.size(), sizeof(expected_proto));
1765 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1766 0);
1767 }
1768
TEST(CodegenMessage,EnumAliases)1769 TEST(CodegenMessage, EnumAliases) {
1770 // Unprefixed enum.
1771 EXPECT_EQ(Bool::kTrue, Bool::TRUE);
1772 EXPECT_EQ(Bool::kFalse, Bool::FALSE);
1773 EXPECT_EQ(Bool::kFileNotFound, Bool::FILE_NOT_FOUND);
1774
1775 // Prefixed enum has the prefix removed.
1776 EXPECT_EQ(Error::kNone, Error::ERROR_NONE);
1777 EXPECT_EQ(Error::kNotFound, Error::ERROR_NOT_FOUND);
1778 EXPECT_EQ(Error::kUnknown, Error::ERROR_UNKNOWN);
1779
1780 // Single-value enum.
1781 EXPECT_EQ(AlwaysBlue::kBlue, AlwaysBlue::BLUE);
1782 }
1783
TEST(CodegenMessage,WriteOptionalPresent)1784 TEST(CodegenMessage, WriteOptionalPresent) {
1785 OptionalTest::Message message{};
1786 message.sometimes_present_fixed = 0x2a;
1787 message.sometimes_present_varint = 0x2a;
1788 message.explicitly_present_fixed = 0x45;
1789 message.explicitly_present_varint = 0x45;
1790 message.sometimes_empty_fixed.push_back(0x63);
1791 message.sometimes_empty_varint.push_back(0x63);
1792
1793 std::byte encode_buffer[512];
1794
1795 stream::MemoryWriter writer(encode_buffer);
1796 OptionalTest::StreamEncoder optional_test(writer, ByteSpan());
1797
1798 const auto status = optional_test.Write(message);
1799 ASSERT_EQ(status, OkStatus());
1800
1801 // clang-format off
1802 constexpr uint8_t expected_proto[] = {
1803 // optional.sometimes_present_fixed
1804 0x0d, 0x2a, 0x00, 0x00, 0x00,
1805 // optional.sometimes_present_varint
1806 0x10, 0x2a,
1807 // optional.explicitly_present_fixed
1808 0x1d, 0x45, 0x00, 0x00, 0x00,
1809 // optional.explicitly_present_varint
1810 0x20, 0x45,
1811 // optional.sometimes_empty_fixed
1812 0x2a, 0x04, 0x63, 0x00, 0x00, 0x00,
1813 // optional.sometimes_empty_varint
1814 0x32, 0x01, 0x63,
1815 };
1816 // clang-format on
1817
1818 ConstByteSpan result = writer.WrittenData();
1819 EXPECT_EQ(result.size(), sizeof(expected_proto));
1820 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1821 0);
1822 }
1823
TEST(CodegenMessage,WriteOptionalNotPresent)1824 TEST(CodegenMessage, WriteOptionalNotPresent) {
1825 OptionalTest::Message message{};
1826
1827 std::byte encode_buffer[512];
1828
1829 stream::MemoryWriter writer(encode_buffer);
1830 OptionalTest::StreamEncoder optional_test(writer, ByteSpan());
1831
1832 const auto status = optional_test.Write(message);
1833 ASSERT_EQ(status, OkStatus());
1834
1835 // The expected proto is empty; no bytes should be written.
1836
1837 ConstByteSpan result = writer.WrittenData();
1838 EXPECT_TRUE(result.empty());
1839 }
1840
TEST(CodegenMessage,WriteOptionalPresentDefaults)1841 TEST(CodegenMessage, WriteOptionalPresentDefaults) {
1842 OptionalTest::Message message{};
1843 // Non-optional fields with a default value are not explicitly encoded, so
1844 // aren't meaningfully different from one that's just ommitted.
1845 message.sometimes_present_fixed = 0x00;
1846 message.sometimes_present_varint = 0x00;
1847 // Optional fields, even with a default value, are explicitly encoded.
1848 message.explicitly_present_fixed = 0x00;
1849 message.explicitly_present_varint = 0x00;
1850 // Repeated fields with a default value are meaningfully non-empty.
1851 message.sometimes_empty_fixed.push_back(0x00);
1852 message.sometimes_empty_varint.push_back(0x00);
1853
1854 std::byte encode_buffer[512];
1855
1856 stream::MemoryWriter writer(encode_buffer);
1857 OptionalTest::StreamEncoder optional_test(writer, ByteSpan());
1858
1859 const auto status = optional_test.Write(message);
1860 ASSERT_EQ(status, OkStatus());
1861
1862 // clang-format off
1863 constexpr uint8_t expected_proto[] = {
1864 // optional.explicitly_present_fixed
1865 0x1d, 0x00, 0x00, 0x00, 0x00,
1866 // optional.explicitly_present_varint
1867 0x20, 0x00,
1868 // optional.sometimes_empty_fixed
1869 0x2a, 0x04, 0x00, 0x00, 0x00, 0x00,
1870 // optional.sometimes_empty_varint
1871 0x32, 0x01, 0x00,
1872 };
1873 // clang-format on
1874
1875 ConstByteSpan result = writer.WrittenData();
1876 EXPECT_EQ(result.size(), sizeof(expected_proto));
1877 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1878 0);
1879 }
1880
1881 class BreakableEncoder : public KeyValuePair::MemoryEncoder {
1882 public:
BreakableEncoder(ByteSpan buffer)1883 constexpr BreakableEncoder(ByteSpan buffer)
1884 : KeyValuePair::MemoryEncoder(buffer) {}
1885
Write(const KeyValuePair::Message & message,span<const internal::MessageField> table)1886 Status Write(const KeyValuePair::Message& message,
1887 span<const internal::MessageField> table) {
1888 return ::pw::protobuf::StreamEncoder::Write(as_bytes(span(&message, 1)),
1889 table);
1890 }
1891 };
1892
TEST(CodegenMessage,DISABLED_WriteDoesNotOverrun)1893 TEST(CodegenMessage, DISABLED_WriteDoesNotOverrun) {
1894 // Deliberately construct a message table that attempts to violate the bounds
1895 // of the structure. We're not testing that a developer can't do this, rather
1896 // that the protobuf encoder can't be exploited in this way.
1897 constexpr internal::MessageField kMessageFields[] = {
1898 {1,
1899 WireType::kDelimited,
1900 sizeof(std::byte),
1901 static_cast<internal::VarintType>(0),
1902 false,
1903 false,
1904 false,
1905 false,
1906 internal::CallbackType::kNone,
1907 0,
1908 sizeof(KeyValuePair::Message) * 2,
1909 {}},
1910 };
1911
1912 std::byte encode_buffer[64];
1913
1914 BreakableEncoder encoder(encode_buffer);
1915 KeyValuePair::Message message{};
1916 // ASSERT_CRASH
1917 std::ignore = encoder.Write(message, kMessageFields);
1918 }
1919
1920 // The following tests cover using the codegen struct Message and callbacks in
1921 // different ways.
1922
1923 // Check that the callback function object is large enough to implement a
1924 // "call a function on this" lambda.
1925 class StringChecker {
1926 public:
1927 StringChecker() = default;
1928 ~StringChecker() = default;
1929
Check(RepeatedTest::StreamDecoder & repeated_test)1930 Status Check(RepeatedTest::StreamDecoder& repeated_test) {
1931 RepeatedTest::Message message{};
1932 message.strings.SetDecoder([this](RepeatedTest::StreamDecoder& decoder) {
1933 return this->CheckOne(decoder);
1934 });
1935 return repeated_test.Read(message);
1936 }
1937
1938 private:
CheckOne(RepeatedTest::StreamDecoder & decoder)1939 Status CheckOne(RepeatedTest::StreamDecoder& decoder) {
1940 EXPECT_EQ(decoder.Field().value(), RepeatedTest::Fields::kStrings);
1941
1942 std::array<char, 40> strings{};
1943 const StatusWithSize sws = decoder.ReadStrings(strings);
1944 EXPECT_EQ(sws.status(), OkStatus());
1945 EXPECT_EQ(sws.size(), kExpectedStrings[i_].size());
1946 EXPECT_EQ(std::memcmp(strings.data(),
1947 kExpectedStrings[i_].data(),
1948 kExpectedStrings[i_].size()),
1949 0);
1950
1951 ++i_;
1952 return sws.status();
1953 }
1954
1955 int i_ = 0;
1956 constexpr static std::string_view kExpectedStrings[] = {
1957 {"if music be the food of love, play on"},
1958 {"give me excess of it, that, surfeiting"},
1959 {"the appetite may sicken, and so die"}};
1960 };
1961
TEST(CodegenMessage,CallbackInClass)1962 TEST(CodegenMessage, CallbackInClass) {
1963 // clang-format off
1964 constexpr uint8_t proto_data[] = {
1965 // repeated.strings
1966 0x1a, 0x25, 'i', 'f', ' ', 'm', 'u', 's', 'i', 'c', ' ', 'b', 'e', ' ',
1967 't', 'h', 'e', ' ', 'f', 'o', 'o', 'd', ' ', 'o', 'f', ' ',
1968 'l', 'o', 'v', 'e', ',', ' ', 'p', 'l', 'a', 'y', ' ', 'o', 'n',
1969 // repeated.strings
1970 0x1a, 0x26, 'g', 'i', 'v', 'e', ' ', 'm', 'e', ' ', 'e', 'x', 'c', 'e',
1971 's', 's', ' ', 'o', 'f', ' ', 'i', 't', ',', ' ', 't', 'h', 'a', 't', ',',
1972 ' ', 's', 'u', 'r', 'f', 'e', 'i', 't', 'i', 'n', 'g',
1973 // repeated.strings
1974 0x1a, 0x23, 't', 'h', 'e', ' ', 'a', 'p', 'p', 'e', 't', 'i', 't', 'e', ' ',
1975 'm', 'a', 'y', ' ', 's', 'i', 'c', 'k', 'e', 'n', ',', ' ', 'a', 'n', 'd',
1976 ' ', 's', 'o', ' ', 'd', 'i', 'e',
1977 };
1978 // clang-format on
1979
1980 stream::MemoryReader reader(as_bytes(span(proto_data)));
1981 RepeatedTest::StreamDecoder repeated_test(reader);
1982
1983 StringChecker checker{};
1984 const auto status = checker.Check(repeated_test);
1985 ASSERT_EQ(status, OkStatus());
1986 }
1987
1988 // Check that we can create a custom subclass of the message struct that sets
1989 // its own callbacks to member functions that populate fields added in the
1990 // subclass.
1991 struct CustomMessage : RepeatedTest::Message {
CustomMessagepw::protobuf::__anon3b63cbad0111::CustomMessage1992 CustomMessage() : RepeatedTest::Message() {
1993 strings.SetDecoder([this](RepeatedTest::StreamDecoder& decoder) {
1994 return this->ParseStrings(decoder);
1995 });
1996 }
1997
1998 pw::Vector<std::array<char, 40>, 8> all_strings{};
1999
2000 private:
ParseStringspw::protobuf::__anon3b63cbad0111::CustomMessage2001 Status ParseStrings(RepeatedTest::StreamDecoder& decoder) {
2002 PW_ASSERT(decoder.Field().value() == RepeatedTest::Fields::kStrings);
2003
2004 std::array<char, 40> one_strings{};
2005 const auto sws = decoder.ReadStrings(one_strings);
2006 if (!sws.ok()) {
2007 return sws.status();
2008 }
2009
2010 one_strings[sws.size()] = '\0';
2011 all_strings.push_back(one_strings);
2012
2013 return OkStatus();
2014 }
2015 };
2016
TEST(CodegenMessage,CallbackInSubclass)2017 TEST(CodegenMessage, CallbackInSubclass) {
2018 // clang-format off
2019 constexpr uint8_t proto_data[] = {
2020 // repeated.strings
2021 0x1a, 0x25, 'i', 'f', ' ', 'm', 'u', 's', 'i', 'c', ' ', 'b', 'e', ' ',
2022 't', 'h', 'e', ' ', 'f', 'o', 'o', 'd', ' ', 'o', 'f', ' ',
2023 'l', 'o', 'v', 'e', ',', ' ', 'p', 'l', 'a', 'y', ' ', 'o', 'n',
2024 // repeated.strings
2025 0x1a, 0x26, 'g', 'i', 'v', 'e', ' ', 'm', 'e', ' ', 'e', 'x', 'c', 'e',
2026 's', 's', ' ', 'o', 'f', ' ', 'i', 't', ',', ' ', 't', 'h', 'a', 't', ',',
2027 ' ', 's', 'u', 'r', 'f', 'e', 'i', 't', 'i', 'n', 'g',
2028 // repeated.strings
2029 0x1a, 0x23, 't', 'h', 'e', ' ', 'a', 'p', 'p', 'e', 't', 'i', 't', 'e', ' ',
2030 'm', 'a', 'y', ' ', 's', 'i', 'c', 'k', 'e', 'n', ',', ' ', 'a', 'n', 'd',
2031 ' ', 's', 'o', ' ', 'd', 'i', 'e',
2032 };
2033 // clang-format on
2034
2035 stream::MemoryReader reader(as_bytes(span(proto_data)));
2036 RepeatedTest::StreamDecoder repeated_test(reader);
2037
2038 CustomMessage message{};
2039 const auto status = repeated_test.Read(message);
2040 ASSERT_EQ(status, OkStatus());
2041
2042 constexpr static std::string_view kExpectedStrings[] = {
2043 {"if music be the food of love, play on"},
2044 {"give me excess of it, that, surfeiting"},
2045 {"the appetite may sicken, and so die"}};
2046
2047 EXPECT_EQ(message.all_strings.size(), 3u);
2048 for (unsigned short i = 0; i < 3; ++i) {
2049 EXPECT_EQ(std::memcmp(message.all_strings[i].data(),
2050 kExpectedStrings[i].data(),
2051 kExpectedStrings[i].size()),
2052 0);
2053 EXPECT_EQ(message.all_strings[i].data()[kExpectedStrings[i].size()], '\0');
2054 }
2055 }
2056
TEST(CodegenMessage,MaxSize)2057 TEST(CodegenMessage, MaxSize) {
2058 // Verify constants generated from max_size options in full_test.pwpb_options
2059 static_assert(Pigweed::kErrorMessageMaxSize == 64);
2060 static_assert(Pigweed::kDataMaxSize == 8);
2061
2062 Pigweed::Message size_message;
2063 EXPECT_EQ(size_message.error_message.max_size(),
2064 Pigweed::kErrorMessageMaxSize);
2065 EXPECT_EQ(size_message.data.max_size(), Pigweed::kDataMaxSize);
2066
2067 // Verify constants generated from max_count options in repeated.pwpb_options
2068 static_assert(RepeatedTest::kUint32sMaxSize == 8);
2069 static_assert(RepeatedTest::kFixed32sMaxSize == 8);
2070 static_assert(RepeatedTest::kDoublesMaxSize == 2);
2071 static_assert(RepeatedTest::kUint64sMaxSize == 4);
2072 static_assert(RepeatedTest::kEnumsMaxSize == 4);
2073
2074 RepeatedTest::Message count_message;
2075 EXPECT_EQ(count_message.uint32s.max_size(), RepeatedTest::kUint32sMaxSize);
2076 EXPECT_EQ(count_message.fixed32s.max_size(), RepeatedTest::kFixed32sMaxSize);
2077 EXPECT_EQ(count_message.doubles.max_size(), RepeatedTest::kDoublesMaxSize);
2078 EXPECT_EQ(count_message.uint64s.max_size(), RepeatedTest::kUint64sMaxSize);
2079 EXPECT_EQ(count_message.enums.max_size(), RepeatedTest::kEnumsMaxSize);
2080 }
2081
TEST(CodegenMessage,OneOf_Encode)2082 TEST(CodegenMessage, OneOf_Encode) {
2083 OneOfTest::Message message;
2084
2085 int invocations = 0;
2086 message.type.SetEncoder([&invocations](OneOfTest::StreamEncoder& encoder) {
2087 invocations++;
2088 return encoder.WriteAnInt(32);
2089 });
2090
2091 // clang-format off
2092 constexpr uint8_t expected_proto[] = {
2093 // type.an_int
2094 0x08, 0x20,
2095 };
2096 // clang-format on
2097
2098 std::array<std::byte, 8> buffer;
2099 OneOfTest::MemoryEncoder oneof_test(buffer);
2100
2101 EXPECT_EQ(oneof_test.Write(message), OkStatus());
2102 EXPECT_EQ(invocations, 1);
2103
2104 EXPECT_EQ(oneof_test.size(), sizeof(expected_proto));
2105 EXPECT_EQ(
2106 std::memcmp(oneof_test.data(), expected_proto, sizeof(expected_proto)),
2107 0);
2108 }
2109
TEST(CodegenMessage,OneOf_Encode_MultipleTimes)2110 TEST(CodegenMessage, OneOf_Encode_MultipleTimes) {
2111 OneOfTest::Message message;
2112
2113 int invocations = 0;
2114 message.type.SetEncoder([&invocations](OneOfTest::StreamEncoder& encoder) {
2115 invocations++;
2116 return encoder.WriteAString("oneof");
2117 });
2118
2119 // clang-format off
2120 constexpr uint8_t expected_proto[] = {
2121 // type.a_string
2122 0x12, 0x05, 'o', 'n', 'e', 'o', 'f'
2123 };
2124 // clang-format on
2125
2126 // Write the same message struct to two different buffers. Even though its
2127 // internal state is modified during the write, it should be logically const
2128 // with both writes successfully producing the same output.
2129
2130 std::array<std::byte, 8> buffer_1;
2131 std::array<std::byte, 8> buffer_2;
2132 OneOfTest::MemoryEncoder oneof_test_1(buffer_1);
2133 OneOfTest::MemoryEncoder oneof_test_2(buffer_2);
2134
2135 EXPECT_EQ(oneof_test_1.Write(message), OkStatus());
2136 EXPECT_EQ(invocations, 1);
2137 EXPECT_EQ(oneof_test_1.size(), sizeof(expected_proto));
2138 EXPECT_EQ(
2139 std::memcmp(oneof_test_1.data(), expected_proto, sizeof(expected_proto)),
2140 0);
2141
2142 EXPECT_EQ(oneof_test_2.Write(message), OkStatus());
2143 EXPECT_EQ(invocations, 2);
2144 EXPECT_EQ(oneof_test_2.size(), sizeof(expected_proto));
2145 EXPECT_EQ(
2146 std::memcmp(oneof_test_2.data(), expected_proto, sizeof(expected_proto)),
2147 0);
2148 }
2149
TEST(CodegenMessage,OneOf_Encode_UnsetEncoderIsIgnored)2150 TEST(CodegenMessage, OneOf_Encode_UnsetEncoderIsIgnored) {
2151 OneOfTest::Message message;
2152 std::array<std::byte, 8> buffer;
2153 OneOfTest::MemoryEncoder oneof_test(buffer);
2154 EXPECT_EQ(oneof_test.Write(message), OkStatus());
2155 }
2156
TEST(CodegenMessage,OneOf_Decode)2157 TEST(CodegenMessage, OneOf_Decode) {
2158 // clang-format off
2159 constexpr uint8_t proto_data[] = {
2160 // type.a_message
2161 0x1a, 0x02, 0x08, 0x01,
2162 };
2163 // clang-format on
2164
2165 stream::MemoryReader reader(as_bytes(span(proto_data)));
2166 OneOfTest::StreamDecoder stream_decoder(reader);
2167
2168 struct {
2169 OneOfTest::Fields field;
2170 OneOfTest::AMessage::Message submessage;
2171 int invocations = 0;
2172 } result;
2173
2174 OneOfTest::Message message;
2175 message.type.SetDecoder(
2176 [&result](OneOfTest::Fields field, OneOfTest::StreamDecoder& decoder) {
2177 result.field = field;
2178 result.invocations++;
2179 if (field == OneOfTest::Fields::kAMessage) {
2180 return decoder.GetAMessageDecoder().Read(result.submessage);
2181 }
2182 return Status::InvalidArgument();
2183 });
2184
2185 EXPECT_EQ(stream_decoder.Read(message), OkStatus());
2186 EXPECT_EQ(result.field, OneOfTest::Fields::kAMessage);
2187 EXPECT_EQ(result.invocations, 1);
2188 EXPECT_EQ(result.submessage.a_bool, true);
2189 }
2190
TEST(CodegenMessage,OneOf_Decode_MultipleOneOfFieldsFails)2191 TEST(CodegenMessage, OneOf_Decode_MultipleOneOfFieldsFails) {
2192 // clang-format off
2193 constexpr uint8_t proto_data[] = {
2194 // type.an_int
2195 0x08, 0x20,
2196 // type.a_message
2197 0x1a, 0x02, 0x08, 0x01,
2198 };
2199 // clang-format on
2200
2201 stream::MemoryReader reader(as_bytes(span(proto_data)));
2202 OneOfTest::StreamDecoder stream_decoder(reader);
2203
2204 OneOfTest::Message message;
2205 message.type.SetDecoder(
2206 [](OneOfTest::Fields, OneOfTest::StreamDecoder&) { return OkStatus(); });
2207
2208 EXPECT_EQ(stream_decoder.Read(message), Status::DataLoss());
2209 }
2210
TEST(CodegenMessage,OneOf_Decode_UnsetDecoderIsIgnored)2211 TEST(CodegenMessage, OneOf_Decode_UnsetDecoderIsIgnored) {
2212 // clang-format off
2213 constexpr uint8_t proto_data[] = {
2214 // type.an_int
2215 0x08, 0x20,
2216 };
2217 // clang-format on
2218
2219 stream::MemoryReader reader(as_bytes(span(proto_data)));
2220 OneOfTest::StreamDecoder stream_decoder(reader);
2221 OneOfTest::Message message;
2222 EXPECT_EQ(stream_decoder.Read(message), OkStatus());
2223 }
2224
2225 } // namespace
2226 } // namespace pw::protobuf
2227