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 "gtest/gtest.h"
20 #include "pw_preprocessor/compiler.h"
21 #include "pw_protobuf/internal/codegen.h"
22 #include "pw_span/span.h"
23 #include "pw_status/status.h"
24 #include "pw_status/status_with_size.h"
25 #include "pw_stream/memory_stream.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,Read)324 TEST(CodegenMessage, Read) {
325 // clang-format off
326 constexpr uint8_t proto_data[] = {
327 // pigweed.magic_number
328 0x08, 0x49,
329 // pigweed.ziggy
330 0x10, 0xdd, 0x01,
331 // pigweed.cycles
332 0x19, 0xde, 0xad, 0xca, 0xfe, 0x10, 0x20, 0x30, 0x40,
333 // pigweed.ratio
334 0x25, 0x8f, 0xc2, 0xb5, 0xbf,
335 // pigweed.error_message
336 0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ',
337 't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r',
338 // pigweed.bin
339 0x40, 0x01,
340 // pigweed.bungle
341 0x70, 0x91, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01,
342 };
343 // clang-format on
344
345 stream::MemoryReader reader(as_bytes(span(proto_data)));
346 Pigweed::StreamDecoder pigweed(reader);
347
348 Pigweed::Message message{};
349 const auto status = pigweed.Read(message);
350 ASSERT_EQ(status, OkStatus());
351
352 constexpr std::string_view kExpectedErrorMessage{"not a typewriter"};
353
354 EXPECT_EQ(message.magic_number, 0x49u);
355 EXPECT_EQ(message.ziggy, -111);
356 EXPECT_EQ(message.cycles, 0x40302010fecaaddeu);
357 EXPECT_EQ(message.ratio, -1.42f);
358 EXPECT_EQ(message.error_message.size(), kExpectedErrorMessage.size());
359 EXPECT_EQ(std::memcmp(message.error_message.data(),
360 kExpectedErrorMessage.data(),
361 kExpectedErrorMessage.size()),
362 0);
363 EXPECT_EQ(message.bin, Pigweed::Protobuf::Binary::ZERO);
364 EXPECT_EQ(message.bungle, -111);
365 }
366
TEST(CodegenMessage,ReadNonPackedScalar)367 TEST(CodegenMessage, ReadNonPackedScalar) {
368 // clang-format off
369 constexpr uint8_t proto_data[] = {
370 // uint32s[], v={0, 16, 32, 48}
371 0x08, 0x00,
372 0x08, 0x10,
373 0x08, 0x20,
374 0x08, 0x30,
375 // fixed32s[]. v={0, 16, 32, 48}
376 0x35, 0x00, 0x00, 0x00, 0x00,
377 0x35, 0x10, 0x00, 0x00, 0x00,
378 0x35, 0x20, 0x00, 0x00, 0x00,
379 0x35, 0x30, 0x00, 0x00, 0x00,
380 };
381 // clang-format on
382
383 stream::MemoryReader reader(as_bytes(span(proto_data)));
384 RepeatedTest::StreamDecoder repeated_test(reader);
385
386 RepeatedTest::Message message{};
387 const auto status = repeated_test.Read(message);
388 ASSERT_EQ(status, OkStatus());
389
390 ASSERT_EQ(message.uint32s.size(), 4u);
391 for (int i = 0; i < 4; ++i) {
392 EXPECT_EQ(message.uint32s[i], i * 16u);
393 }
394
395 ASSERT_EQ(message.fixed32s.size(), 4u);
396 for (int i = 0; i < 4; ++i) {
397 EXPECT_EQ(message.fixed32s[i], i * 16u);
398 }
399 }
400
TEST(CodegenMessage,ReadPackedScalar)401 TEST(CodegenMessage, ReadPackedScalar) {
402 // clang-format off
403 constexpr uint8_t proto_data[] = {
404 // uint32s[], v={0, 16, 32, 48}
405 0x0a, 0x04,
406 0x00,
407 0x10,
408 0x20,
409 0x30,
410 // fixed32s[]. v={0, 16, 32, 48}
411 0x32, 0x10,
412 0x00, 0x00, 0x00, 0x00,
413 0x10, 0x00, 0x00, 0x00,
414 0x20, 0x00, 0x00, 0x00,
415 0x30, 0x00, 0x00, 0x00,
416 };
417 // clang-format on
418
419 stream::MemoryReader reader(as_bytes(span(proto_data)));
420 RepeatedTest::StreamDecoder repeated_test(reader);
421
422 RepeatedTest::Message message{};
423 const auto status = repeated_test.Read(message);
424 ASSERT_EQ(status, OkStatus());
425
426 ASSERT_EQ(message.uint32s.size(), 4u);
427 for (int i = 0; i < 4; ++i) {
428 EXPECT_EQ(message.uint32s[i], i * 16u);
429 }
430
431 ASSERT_EQ(message.fixed32s.size(), 4u);
432 for (int i = 0; i < 4; ++i) {
433 EXPECT_EQ(message.fixed32s[i], i * 16u);
434 }
435 }
436
TEST(CodegenMessage,ReadPackedScalarRepeated)437 TEST(CodegenMessage, ReadPackedScalarRepeated) {
438 // clang-format off
439 constexpr uint8_t proto_data[] = {
440 // uint32s[], v={0, 16, 32, 48}
441 0x0a, 0x04,
442 0x00,
443 0x10,
444 0x20,
445 0x30,
446 // uint32s[], v={64, 80, 96, 112}
447 0x0a, 0x04,
448 0x40,
449 0x50,
450 0x60,
451 0x70,
452 // fixed32s[]. v={0, 16, 32, 48}
453 0x32, 0x10,
454 0x00, 0x00, 0x00, 0x00,
455 0x10, 0x00, 0x00, 0x00,
456 0x20, 0x00, 0x00, 0x00,
457 0x30, 0x00, 0x00, 0x00,
458 // fixed32s[]. v={64, 80, 96, 112}
459 0x32, 0x10,
460 0x40, 0x00, 0x00, 0x00,
461 0x50, 0x00, 0x00, 0x00,
462 0x60, 0x00, 0x00, 0x00,
463 0x70, 0x00, 0x00, 0x00,
464 };
465 // clang-format on
466
467 stream::MemoryReader reader(as_bytes(span(proto_data)));
468 RepeatedTest::StreamDecoder repeated_test(reader);
469
470 RepeatedTest::Message message{};
471 const auto status = repeated_test.Read(message);
472 ASSERT_EQ(status, OkStatus());
473
474 ASSERT_EQ(message.uint32s.size(), 8u);
475 for (int i = 0; i < 8; ++i) {
476 EXPECT_EQ(message.uint32s[i], i * 16u);
477 }
478
479 ASSERT_EQ(message.fixed32s.size(), 8u);
480 for (int i = 0; i < 8; ++i) {
481 EXPECT_EQ(message.fixed32s[i], i * 16u);
482 }
483 }
484
TEST(CodegenMessage,ReadPackedScalarExhausted)485 TEST(CodegenMessage, ReadPackedScalarExhausted) {
486 // clang-format off
487 constexpr uint8_t proto_data[] = {
488 // uint32s[], v={0, 16, 32, 48, 64, 80, 96, 112, 128}
489 0x0a, 0x09,
490 0x00,
491 0x10,
492 0x20,
493 0x30,
494 0x40,
495 0x50,
496 0x60,
497 0x70,
498 0x80,
499 };
500 // clang-format on
501
502 stream::MemoryReader reader(as_bytes(span(proto_data)));
503 RepeatedTest::StreamDecoder repeated_test(reader);
504
505 // uint32s has max_size=8, so this will exhaust the vector.
506 RepeatedTest::Message message{};
507 const auto status = repeated_test.Read(message);
508 ASSERT_EQ(status, Status::ResourceExhausted());
509 }
510
TEST(CodegenMessage,ReadPackedScalarCallback)511 TEST(CodegenMessage, ReadPackedScalarCallback) {
512 // clang-format off
513 constexpr uint8_t proto_data[] = {
514 // sint32s[], v={-25, -1, 0, 1, 25}
515 0x12, 0x05,
516 0x31,
517 0x01,
518 0x00,
519 0x02,
520 0x32,
521 };
522 // clang-format on
523
524 stream::MemoryReader reader(as_bytes(span(proto_data)));
525 RepeatedTest::StreamDecoder repeated_test(reader);
526
527 // sint32s is a repeated field declared without max_count, so requirses a
528 // callback to be decoded.
529 RepeatedTest::Message message{};
530 message.sint32s.SetDecoder([](RepeatedTest::StreamDecoder& decoder) {
531 EXPECT_EQ(decoder.Field().value(), RepeatedTest::Fields::kSint32s);
532
533 pw::Vector<int32_t, 8> sint32s{};
534 const auto status = decoder.ReadSint32s(sint32s);
535 EXPECT_EQ(status, OkStatus());
536
537 EXPECT_EQ(sint32s.size(), 5u);
538 EXPECT_EQ(sint32s[0], -25);
539 EXPECT_EQ(sint32s[1], -1);
540 EXPECT_EQ(sint32s[2], 0);
541 EXPECT_EQ(sint32s[3], 1);
542 EXPECT_EQ(sint32s[4], 25);
543
544 return status;
545 });
546
547 const auto status = repeated_test.Read(message);
548 ASSERT_EQ(status, OkStatus());
549 }
550
TEST(CodegenMessage,ReadPackedScalarFixedLength)551 TEST(CodegenMessage, ReadPackedScalarFixedLength) {
552 // clang-format off
553 constexpr uint8_t proto_data[] = {
554 // uint64s[], v={1000, 2000, 3000, 4000}
555 0x42, 0x08, 0xe8, 0x07, 0xd0, 0x0f, 0xb8, 0x17, 0xa0, 0x1f,
556 // doubles[], v={3.14159, 2.71828}
557 0x22, 0x10,
558 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
559 0x90, 0xf7, 0xaa, 0x95, 0x09, 0xbf, 0x05, 0x40,
560 };
561 // clang-format on
562
563 stream::MemoryReader reader(as_bytes(span(proto_data)));
564 RepeatedTest::StreamDecoder repeated_test(reader);
565
566 RepeatedTest::Message message{};
567 const auto status = repeated_test.Read(message);
568 ASSERT_EQ(status, OkStatus());
569
570 EXPECT_EQ(message.uint64s[0], 1000u);
571 EXPECT_EQ(message.uint64s[1], 2000u);
572 EXPECT_EQ(message.uint64s[2], 3000u);
573 EXPECT_EQ(message.uint64s[3], 4000u);
574
575 EXPECT_EQ(message.doubles[0], 3.14159);
576 EXPECT_EQ(message.doubles[1], 2.71828);
577 }
578
TEST(CodegenMessage,ReadPackedScalarFixedLengthShort)579 TEST(CodegenMessage, ReadPackedScalarFixedLengthShort) {
580 // clang-format off
581 constexpr uint8_t proto_data[] = {
582 // uint64s[], v={1000, 2000}
583 0x42, 0x04, 0xe8, 0x07, 0xd0, 0x0f,
584 // doubles[], v={3.14159}
585 0x22, 0x08,
586 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
587 };
588 // clang-format on
589
590 stream::MemoryReader reader(as_bytes(span(proto_data)));
591 RepeatedTest::StreamDecoder repeated_test(reader);
592
593 RepeatedTest::Message message{};
594 const auto status = repeated_test.Read(message);
595 ASSERT_EQ(status, OkStatus());
596
597 EXPECT_EQ(message.uint64s[0], 1000u);
598 EXPECT_EQ(message.uint64s[1], 2000u);
599 EXPECT_EQ(message.uint64s[2], 0u);
600 EXPECT_EQ(message.uint64s[3], 0u);
601
602 EXPECT_EQ(message.doubles[0], 3.14159);
603 EXPECT_EQ(message.doubles[1], 0);
604 }
605
TEST(CodegenMessage,ReadPackedScalarVarintFixedLengthExhausted)606 TEST(CodegenMessage, ReadPackedScalarVarintFixedLengthExhausted) {
607 // clang-format off
608 constexpr uint8_t proto_data[] = {
609 // uint64s[], v={0, 1000, 2000, 3000, 4000}
610 0x42, 0x09, 0x08, 0xe8, 0x07, 0xd0, 0x0f, 0xb8, 0x17, 0xa0, 0x1f,
611 };
612 // clang-format on
613
614 stream::MemoryReader reader(as_bytes(span(proto_data)));
615 RepeatedTest::StreamDecoder repeated_test(reader);
616
617 RepeatedTest::Message message{};
618 const auto status = repeated_test.Read(message);
619 ASSERT_EQ(status, Status::ResourceExhausted());
620 }
621
TEST(CodegenMessage,ReadPackedScalarFixedLengthExhausted)622 TEST(CodegenMessage, ReadPackedScalarFixedLengthExhausted) {
623 // clang-format off
624 constexpr uint8_t proto_data[] = {
625 // doubles[], v={3.14159, 2.71828, 1.41429, 1.73205}
626 0x22, 0x20,
627 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
628 0x90, 0xf7, 0xaa, 0x95, 0x09, 0xbf, 0x05, 0x40,
629 0x1b, 0xf5, 0x10, 0x8d, 0xee, 0xa0, 0xf6, 0x3f,
630 0xbc, 0x96, 0x90, 0x0f, 0x7a, 0xb6, 0xfb, 0x3f,
631 };
632 // clang-format on
633
634 stream::MemoryReader reader(as_bytes(span(proto_data)));
635 RepeatedTest::StreamDecoder repeated_test(reader);
636
637 RepeatedTest::Message message{};
638 const auto status = repeated_test.Read(message);
639 ASSERT_EQ(status, Status::ResourceExhausted());
640 }
641
TEST(CodegenMessage,ReadPackedEnum)642 TEST(CodegenMessage, ReadPackedEnum) {
643 // clang-format off
644 constexpr uint8_t proto_data[] = {
645 // enums[], v={RED, GREEN, AMBER, RED}
646 0x4a, 0x04, 0x00, 0x02, 0x01, 0x00,
647 };
648 // clang-format on
649
650 stream::MemoryReader reader(as_bytes(span(proto_data)));
651 RepeatedTest::StreamDecoder repeated_test(reader);
652
653 RepeatedTest::Message message{};
654 const auto status = repeated_test.Read(message);
655 ASSERT_EQ(status, OkStatus());
656
657 ASSERT_EQ(message.enums.size(), 4u);
658 for (int i = 0; i < 4; ++i) {
659 EXPECT_TRUE(IsValidEnum(message.enums[i]));
660 }
661
662 EXPECT_EQ(message.enums[0], Enum::RED);
663 EXPECT_EQ(message.enums[1], Enum::GREEN);
664 EXPECT_EQ(message.enums[2], Enum::AMBER);
665 EXPECT_EQ(message.enums[3], Enum::RED);
666 }
667
TEST(CodegenMessage,ReadStringExhausted)668 TEST(CodegenMessage, ReadStringExhausted) {
669 // clang-format off
670 constexpr uint8_t proto_data[] = {
671 // pigweed.error_message
672 0x2a, 0xd3, 0x01, 'T', 'h', 'i', 's', ' ', 'l', 'a', 'b', 'e', 'l', ' ', 'i',
673 's', ' ', 't', 'h', 'e', ' ', 't', 'a', 'r', 'g', 'e', 't', ' ', 'o', 'f',
674 ' ', 'a', ' ', 'g', 'o', 't', 'o', ' ', 'f', 'r', 'o', 'm', ' ', 'o', 'u',
675 't', 's', 'i', 'd', 'e', ' ', 'o', 'f', ' ', 't', 'h', 'e', ' ', 'b', 'l',
676 'o', 'c', 'k', ' ', 'c', 'o', 'n', 't', 'a', 'i', 'n', 'i', 'n', 'g', ' ',
677 't', 'h', 'i', 's', ' ', 'l', 'a', 'b', 'e', 'l', ' ', 'A', 'N', 'D', ' ',
678 't', 'h', 'i', 's', ' ', 'b', 'l', 'o', 'c', 'k', ' ', 'h', 'a', 's', ' ',
679 'a', 'n', ' ', 'a', 'u', 't', 'o', 'm', 'a', 't', 'i', 'c', ' ', 'v', 'a',
680 'r', 'i', 'a', 'b', 'l', 'e', ' ', 'w', 'i', 't', 'h', ' ', 'a', 'n', ' ',
681 'i', 'n', 'i', 't', 'i', 'a', 'l', 'i', 'z', 'e', 'r', ' ', 'A', 'N', 'D',
682 ' ', 'y', 'o', 'u', 'r', ' ', 'w', 'i', 'n', 'd', 'o', 'w', ' ', 'w', 'a',
683 's', 'n', '\'', 't', ' ', 'w', 'i', 'd', 'e', ' ', 'e', 'n', 'o', 'u', 'g',
684 'h', ' ', 't', 'o', ' ', 'r', 'e', 'a', 'd', ' ', 't', 'h', 'i', 's', ' ',
685 'w', 'h', 'o', 'l', 'e', ' ', 'e', 'r', 'r', 'o', 'r', ' ', 'm', 'e', 's',
686 's', 'a', 'g', 'e'
687 };
688 // clang-format on
689
690 stream::MemoryReader reader(as_bytes(span(proto_data)));
691 Pigweed::StreamDecoder pigweed(reader);
692
693 Pigweed::Message message{};
694 const auto status = pigweed.Read(message);
695 ASSERT_EQ(status, Status::ResourceExhausted());
696 }
697
TEST(CodegenMessage,ReadStringCallback)698 TEST(CodegenMessage, ReadStringCallback) {
699 // clang-format off
700 constexpr uint8_t proto_data[] = {
701 // pigweed.description
702 0x62, 0x5c, 'a', 'n', ' ', 'o', 'p', 'e', 'n', ' ', 's', 'o', 'u', 'r', 'c',
703 'e', ' ', 'c', 'o', 'l', 'l', 'e', 'c', 't', 'i', 'o', 'n', ' ', 'o', 'f',
704 ' ', 'e', 'm', 'b', 'e', 'd', 'd', 'e', 'd', '-', 't', 'a', 'r', 'g', 'e',
705 't', 'e', 'd', ' ', 'l', 'i', 'b', 'r', 'a', 'r', 'i', 'e', 's', '-', 'o',
706 'r', ' ', 'a', 's', ' ', 'w', 'e', ' ', 'l', 'i', 'k', 'e', ' ', 't', 'o',
707 ' ', 'c', 'a', 'l', 'l', ' ', 't', 'h', 'e', 'm', ',', ' ', 'm', 'o', 'd',
708 'u', 'l', 'e', 's'
709 };
710 // clang-format on
711
712 stream::MemoryReader reader(as_bytes(span(proto_data)));
713 Pigweed::StreamDecoder pigweed(reader);
714
715 // pigweed.description has no max_size specified so a callback must be
716 // set to read the value if present.
717 Pigweed::Message message{};
718 message.description.SetDecoder([](Pigweed::StreamDecoder& decoder) {
719 EXPECT_EQ(decoder.Field().value(), Pigweed::Fields::kDescription);
720
721 constexpr std::string_view kExpectedDescription{
722 "an open source collection of embedded-targeted libraries-or as we "
723 "like to call them, modules"};
724
725 std::array<char, 128> description{};
726 const auto sws = decoder.ReadDescription(description);
727 EXPECT_EQ(sws.status(), OkStatus());
728 EXPECT_EQ(sws.size(), kExpectedDescription.size());
729 EXPECT_EQ(std::memcmp(description.data(),
730 kExpectedDescription.data(),
731 kExpectedDescription.size()),
732 0);
733
734 return sws.status();
735 });
736
737 const auto status = pigweed.Read(message);
738 ASSERT_EQ(status, OkStatus());
739 }
740
TEST(CodegenMessage,ReadMultipleString)741 TEST(CodegenMessage, ReadMultipleString) {
742 // clang-format off
743 constexpr uint8_t proto_data[] = {
744 // pigweed.error_message
745 0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ',
746 't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r',
747 // pigweed.error_message
748 0x02a, 0x07, 'o', 'n', ' ', 'f', 'i', 'r', 'e'
749 };
750 // clang-format on
751
752 stream::MemoryReader reader(as_bytes(span(proto_data)));
753 Pigweed::StreamDecoder pigweed(reader);
754
755 Pigweed::Message message{};
756 const auto status = pigweed.Read(message);
757 ASSERT_EQ(status, OkStatus());
758
759 constexpr std::string_view kExpectedErrorMessage{"on fire"};
760
761 EXPECT_EQ(message.error_message.size(), kExpectedErrorMessage.size());
762 EXPECT_EQ(std::memcmp(message.error_message.data(),
763 kExpectedErrorMessage.data(),
764 kExpectedErrorMessage.size()),
765 0);
766 }
767
TEST(CodegenMessage,ReadRepeatedStrings)768 TEST(CodegenMessage, ReadRepeatedStrings) {
769 // clang-format off
770 constexpr uint8_t proto_data[] = {
771 // repeated.strings
772 0x1a, 0x25, 'i', 'f', ' ', 'm', 'u', 's', 'i', 'c', ' ', 'b', 'e', ' ',
773 't', 'h', 'e', ' ', 'f', 'o', 'o', 'd', ' ', 'o', 'f', ' ',
774 'l', 'o', 'v', 'e', ',', ' ', 'p', 'l', 'a', 'y', ' ', 'o', 'n',
775 // repeated.strings
776 0x1a, 0x26, 'g', 'i', 'v', 'e', ' ', 'm', 'e', ' ', 'e', 'x', 'c', 'e',
777 's', 's', ' ', 'o', 'f', ' ', 'i', 't', ',', ' ', 't', 'h', 'a', 't', ',',
778 ' ', 's', 'u', 'r', 'f', 'e', 'i', 't', 'i', 'n', 'g',
779 // repeated.strings
780 0x1a, 0x23, 't', 'h', 'e', ' ', 'a', 'p', 'p', 'e', 't', 'i', 't', 'e', ' ',
781 'm', 'a', 'y', ' ', 's', 'i', 'c', 'k', 'e', 'n', ',', ' ', 'a', 'n', 'd',
782 ' ', 's', 'o', ' ', 'd', 'i', 'e',
783 };
784 // clang-format on
785
786 stream::MemoryReader reader(as_bytes(span(proto_data)));
787 RepeatedTest::StreamDecoder repeated_test(reader);
788
789 // Repeated strings require a callback to avoid forcing multi-dimensional
790 // arrays upon the caller.
791 RepeatedTest::Message message{};
792 int i = 0;
793 message.strings.SetDecoder([&i](RepeatedTest::StreamDecoder& decoder) {
794 EXPECT_EQ(decoder.Field().value(), RepeatedTest::Fields::kStrings);
795
796 constexpr std::string_view kExpectedStrings[] = {
797 {"if music be the food of love, play on"},
798 {"give me excess of it, that, surfeiting"},
799 {"the appetite may sicken, and so die"}};
800
801 std::array<char, 40> strings{};
802 const StatusWithSize sws = decoder.ReadStrings(strings);
803 EXPECT_EQ(sws.status(), OkStatus());
804 EXPECT_EQ(sws.size(), kExpectedStrings[i].size());
805 EXPECT_EQ(std::memcmp(strings.data(),
806 kExpectedStrings[i].data(),
807 kExpectedStrings[i].size()),
808 0);
809
810 ++i;
811 return sws.status();
812 });
813
814 const auto status = repeated_test.Read(message);
815 ASSERT_EQ(status, OkStatus());
816 }
817
TEST(CodegenMessage,ReadForcedCallback)818 TEST(CodegenMessage, ReadForcedCallback) {
819 // clang-format off
820 constexpr uint8_t proto_data[] = {
821 // pigweed.special_property
822 0x68, 0x2a,
823 };
824 // clang-format on
825
826 stream::MemoryReader reader(as_bytes(span(proto_data)));
827 Pigweed::StreamDecoder pigweed(reader);
828
829 // pigweed.special_property has use_callback=true to force the use of a
830 // callback even though it's a simple scalar.
831 Pigweed::Message message{};
832 message.special_property.SetDecoder([](Pigweed::StreamDecoder& decoder) {
833 EXPECT_EQ(decoder.Field().value(), Pigweed::Fields::kSpecialProperty);
834
835 pw::Result<uint32_t> result = decoder.ReadSpecialProperty();
836 EXPECT_EQ(result.status(), OkStatus());
837 EXPECT_EQ(result.value(), 42u);
838
839 return result.status();
840 });
841 const auto status = pigweed.Read(message);
842 ASSERT_EQ(status, OkStatus());
843 }
844
TEST(CodegenMessage,ReadMissingCallback)845 TEST(CodegenMessage, ReadMissingCallback) {
846 // clang-format off
847 constexpr uint8_t proto_data[] = {
848 // repeated.strings
849 0x1a, 0x25, 'i', 'f', ' ', 'm', 'u', 's', 'i', 'c', ' ', 'b', 'e', ' ',
850 't', 'h', 'e', ' ', 'f', 'o', 'o', 'd', ' ', 'o', 'f', ' ',
851 'l', 'o', 'v', 'e', ',', ' ', 'p', 'l', 'a', 'y', ' ', 'o', 'n',
852 // repeated.strings
853 0x1a, 0x26, 'g', 'i', 'v', 'e', ' ', 'm', 'e', ' ', 'e', 'x', 'c', 'e',
854 's', 's', ' ', 'o', 'f', ' ', 'i', 't', ',', ' ', 't', 'h', 'a', 't', ',',
855 ' ', 's', 'u', 'r', 'f', 'e', 'i', 't', 'i', 'n', 'g',
856 // repeated.strings
857 0x1a, 0x23, 't', 'h', 'e', ' ', 'a', 'p', 'p', 'e', 't', 'i', 't', 'e', ' ',
858 'm', 'a', 'y', ' ', 's', 'i', 'c', 'k', 'e', 'n', ',', ' ', 'a', 'n', 'd',
859 ' ', 's', 'o', ' ', 'd', 'i', 'e',
860 };
861 // clang-format on
862
863 stream::MemoryReader reader(as_bytes(span(proto_data)));
864 RepeatedTest::StreamDecoder repeated_test(reader);
865
866 // Failing to set a callback will give a DataLoss error if that field is
867 // present in the decoded data.
868 RepeatedTest::Message message{};
869 const auto status = repeated_test.Read(message);
870 ASSERT_EQ(status, Status::DataLoss());
871 }
872
TEST(CodegenMessage,ReadFixedLength)873 TEST(CodegenMessage, ReadFixedLength) {
874 // clang-format off
875 constexpr uint8_t proto_data[] = {
876 // pigweed.data
877 0x5a, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
878 };
879 // clang-format on
880
881 stream::MemoryReader reader(as_bytes(span(proto_data)));
882 Pigweed::StreamDecoder pigweed(reader);
883
884 Pigweed::Message message{};
885 const auto status = pigweed.Read(message);
886 ASSERT_EQ(status, OkStatus());
887
888 EXPECT_EQ(message.data[0], std::byte{0x01});
889 EXPECT_EQ(message.data[1], std::byte{0x02});
890 EXPECT_EQ(message.data[2], std::byte{0x03});
891 EXPECT_EQ(message.data[3], std::byte{0x04});
892 EXPECT_EQ(message.data[4], std::byte{0x05});
893 EXPECT_EQ(message.data[5], std::byte{0x06});
894 EXPECT_EQ(message.data[6], std::byte{0x07});
895 EXPECT_EQ(message.data[7], std::byte{0x08});
896 }
897
TEST(CodegenMessage,ReadFixedLengthShort)898 TEST(CodegenMessage, ReadFixedLengthShort) {
899 // clang-format off
900 constexpr uint8_t proto_data[] = {
901 // pigweed.data
902 0x5a, 0x04, 0x01, 0x02, 0x03, 0x04
903 };
904 // clang-format on
905
906 stream::MemoryReader reader(as_bytes(span(proto_data)));
907 Pigweed::StreamDecoder pigweed(reader);
908
909 Pigweed::Message message{};
910 const auto status = pigweed.Read(message);
911 ASSERT_EQ(status, OkStatus());
912
913 EXPECT_EQ(message.data[0], std::byte{0x01});
914 EXPECT_EQ(message.data[1], std::byte{0x02});
915 EXPECT_EQ(message.data[2], std::byte{0x03});
916 EXPECT_EQ(message.data[3], std::byte{0x04});
917 // Remaining bytes are whatever you initialized them to.
918 EXPECT_EQ(message.data[4], std::byte{0x00});
919 EXPECT_EQ(message.data[5], std::byte{0x00});
920 EXPECT_EQ(message.data[6], std::byte{0x00});
921 EXPECT_EQ(message.data[7], std::byte{0x00});
922 }
923
TEST(CodegenMessage,ReadFixedLengthExhausted)924 TEST(CodegenMessage, ReadFixedLengthExhausted) {
925 // clang-format off
926 constexpr uint8_t proto_data[] = {
927 // pigweed.data
928 0x5a, 0x0c, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
929 0x09, 0x0a, 0x0b, 0x0c
930 };
931 // clang-format on
932
933 stream::MemoryReader reader(as_bytes(span(proto_data)));
934 Pigweed::StreamDecoder pigweed(reader);
935
936 Pigweed::Message message{};
937 const auto status = pigweed.Read(message);
938 ASSERT_EQ(status, Status::ResourceExhausted());
939 }
940
TEST(CodegenMessage,ReadNested)941 TEST(CodegenMessage, ReadNested) {
942 // clang-format off
943 constexpr uint8_t proto_data[] = {
944 // pigweed.magic_number
945 0x08, 0x49,
946 // pigweed.pigweed
947 0x3a, 0x02,
948 // pigweed.pigweed.status
949 0x08, 0x02,
950 // pigweed.ziggy
951 0x10, 0xdd, 0x01,
952 };
953 // clang-format on
954
955 stream::MemoryReader reader(as_bytes(span(proto_data)));
956 Pigweed::StreamDecoder pigweed(reader);
957
958 Pigweed::Message message{};
959 const auto status = pigweed.Read(message);
960 ASSERT_EQ(status, OkStatus());
961
962 EXPECT_EQ(message.magic_number, 0x49u);
963 EXPECT_EQ(message.pigweed.status, Bool::FILE_NOT_FOUND);
964 EXPECT_EQ(message.ziggy, -111);
965 }
966
TEST(CodegenMessage,ReadNestedImported)967 TEST(CodegenMessage, ReadNestedImported) {
968 // clang-format off
969 constexpr uint8_t proto_data[] = {
970 // period.start
971 0x0a, 0x08,
972 // period.start.seconds v=1517949900
973 0x08, 0xcc, 0xa7, 0xe8, 0xd3, 0x05,
974 // period.start.nanoseconds v=0
975 0x10, 0x00,
976 // period.end
977 0x12, 0x08,
978 // period.end.seconds, v=1517950378
979 0x08, 0xaa, 0xab, 0xe8, 0xd3, 0x05,
980 // period.end.nanoseconds, v=0
981 0x10, 0x00,
982 };
983 // clang-format on
984
985 stream::MemoryReader reader(as_bytes(span(proto_data)));
986 Period::StreamDecoder period(reader);
987
988 // Messages imported from another file can be directly embedded in a message.
989 Period::Message message{};
990 const auto status = period.Read(message);
991 ASSERT_EQ(status, OkStatus());
992
993 EXPECT_EQ(message.start.seconds, 1517949900u);
994 EXPECT_EQ(message.start.nanoseconds, 0u);
995 EXPECT_EQ(message.end.seconds, 1517950378u);
996 EXPECT_EQ(message.end.nanoseconds, 0u);
997 }
998
TEST(CodegenMessage,ReadNestedRepeated)999 TEST(CodegenMessage, ReadNestedRepeated) {
1000 // clang-format off
1001 constexpr uint8_t proto_data[] = {
1002 // repeated.structs
1003 0x2a, 0x04,
1004 // repeated.structs.one v=16
1005 0x08, 0x10,
1006 // repeated.structs.two v=32
1007 0x10, 0x20,
1008 // repeated.structs
1009 0x2a, 0x04,
1010 // repeated.structs.one v=48
1011 0x08, 0x30,
1012 // repeated.structs.two v=64
1013 0x10, 0x40,
1014 };
1015 // clang-format on
1016
1017 stream::MemoryReader reader(as_bytes(span(proto_data)));
1018 RepeatedTest::StreamDecoder repeated_test(reader);
1019
1020 // Repeated nested messages require a callback since there would otherwise be
1021 // no way to set callbacks on the nested message.
1022 RepeatedTest::Message message{};
1023 int i = 0;
1024 message.structs.SetDecoder([&i](RepeatedTest::StreamDecoder& decoder) {
1025 EXPECT_EQ(decoder.Field().value(), RepeatedTest::Fields::kStructs);
1026
1027 Struct::Message structs_message{};
1028 auto structs_decoder = decoder.GetStructsDecoder();
1029 const auto status = structs_decoder.Read(structs_message);
1030 EXPECT_EQ(status, OkStatus());
1031
1032 EXPECT_LT(i, 2);
1033 EXPECT_EQ(structs_message.one, i * 32 + 16u);
1034 EXPECT_EQ(structs_message.two, i * 32 + 32u);
1035 ++i;
1036
1037 return status;
1038 });
1039
1040 const auto status = repeated_test.Read(message);
1041 ASSERT_EQ(status, OkStatus());
1042 }
1043
TEST(CodegenMessage,ReadNestedForcedCallback)1044 TEST(CodegenMessage, ReadNestedForcedCallback) {
1045 // clang-format off
1046 constexpr uint8_t proto_data[] = {
1047 // pigweed.device_info
1048 0x32, 0x0e,
1049 // pigweed.device_info.device_name
1050 0x0a, 0x05, 'p', 'i', 'x', 'e', 'l',
1051 // pigweed.device_info.device_id
1052 0x15, 0x08, 0x08, 0x08, 0x08,
1053 // pigweed.device_info.status
1054 0x18, 0x00,
1055 };
1056 // clang-format on
1057
1058 stream::MemoryReader reader(as_bytes(span(proto_data)));
1059 Pigweed::StreamDecoder pigweed(reader);
1060
1061 // pigweed.device_info has use_callback=true to force the use of a callback.
1062 Pigweed::Message message{};
1063 message.device_info.SetDecoder([](Pigweed::StreamDecoder& decoder) {
1064 EXPECT_EQ(decoder.Field().value(), Pigweed::Fields::kDeviceInfo);
1065
1066 DeviceInfo::Message device_info{};
1067 DeviceInfo::StreamDecoder device_info_decoder =
1068 decoder.GetDeviceInfoDecoder();
1069 const auto status = device_info_decoder.Read(device_info);
1070 EXPECT_EQ(status, OkStatus());
1071
1072 constexpr std::string_view kExpectedDeviceName{"pixel"};
1073
1074 EXPECT_EQ(device_info.device_name.size(), kExpectedDeviceName.size());
1075 EXPECT_EQ(std::memcmp(device_info.device_name.data(),
1076 kExpectedDeviceName.data(),
1077 kExpectedDeviceName.size()),
1078 0);
1079 EXPECT_EQ(device_info.device_id, 0x08080808u);
1080 EXPECT_EQ(device_info.status, DeviceInfo::DeviceStatus::OK);
1081
1082 return status;
1083 });
1084 const auto status = pigweed.Read(message);
1085 ASSERT_EQ(status, OkStatus());
1086 }
1087
TEST(CodegenMessage,ReadOptionalPresent)1088 TEST(CodegenMessage, ReadOptionalPresent) {
1089 // clang-format off
1090 constexpr uint8_t proto_data[] = {
1091 // optional.sometimes_present_fixed
1092 0x0d, 0x2a, 0x00, 0x00, 0x00,
1093 // optional.sometimes_present_varint
1094 0x10, 0x2a,
1095 // optional.explicitly_present_fixed
1096 0x1d, 0x45, 0x00, 0x00, 0x00,
1097 // optional.explicitly_present_varint
1098 0x20, 0x45,
1099 // optional.sometimes_empty_fixed
1100 0x2a, 0x04, 0x63, 0x00, 0x00, 0x00,
1101 // optional.sometimes_empty_varint
1102 0x32, 0x01, 0x63,
1103 };
1104 // clang-format on
1105
1106 stream::MemoryReader reader(as_bytes(span(proto_data)));
1107 OptionalTest::StreamDecoder optional_test(reader);
1108
1109 OptionalTest::Message message{};
1110 const auto status = optional_test.Read(message);
1111 ASSERT_EQ(status, OkStatus());
1112
1113 EXPECT_EQ(message.sometimes_present_fixed, 0x2a);
1114 EXPECT_EQ(message.sometimes_present_varint, 0x2a);
1115 EXPECT_TRUE(message.explicitly_present_fixed);
1116 EXPECT_EQ(*message.explicitly_present_fixed, 0x45);
1117 EXPECT_TRUE(message.explicitly_present_varint);
1118 EXPECT_EQ(*message.explicitly_present_varint, 0x45);
1119 EXPECT_FALSE(message.sometimes_empty_fixed.empty());
1120 EXPECT_EQ(message.sometimes_empty_fixed.size(), 1u);
1121 EXPECT_EQ(message.sometimes_empty_fixed[0], 0x63);
1122 EXPECT_FALSE(message.sometimes_empty_varint.empty());
1123 EXPECT_EQ(message.sometimes_empty_varint.size(), 1u);
1124 EXPECT_EQ(message.sometimes_empty_varint[0], 0x63);
1125 }
1126
TEST(CodegenMessage,ReadOptionalNotPresent)1127 TEST(CodegenMessage, ReadOptionalNotPresent) {
1128 constexpr std::array<std::byte, 0> proto_data{};
1129
1130 stream::MemoryReader reader(proto_data);
1131 OptionalTest::StreamDecoder optional_test(reader);
1132
1133 OptionalTest::Message message{};
1134 const auto status = optional_test.Read(message);
1135 ASSERT_EQ(status, OkStatus());
1136
1137 // Non-optional fields have their default value.
1138 EXPECT_EQ(message.sometimes_present_fixed, 0);
1139 EXPECT_EQ(message.sometimes_present_varint, 0);
1140 EXPECT_TRUE(message.sometimes_empty_fixed.empty());
1141 EXPECT_TRUE(message.sometimes_empty_varint.empty());
1142
1143 // Optional fields are explicitly not present.
1144 EXPECT_FALSE(message.explicitly_present_fixed);
1145 EXPECT_FALSE(message.explicitly_present_varint);
1146 }
1147
TEST(CodegenMessage,ReadOptionalPresentDefaults)1148 TEST(CodegenMessage, ReadOptionalPresentDefaults) {
1149 // clang-format off
1150 constexpr uint8_t proto_data[] = {
1151 // optional.sometimes_present_fixed
1152 0x0d, 0x00, 0x00, 0x00, 0x00,
1153 // optional.sometimes_present_varint
1154 0x10, 0x00,
1155 // optional.explicitly_present_fixed
1156 0x1d, 0x00, 0x00, 0x00, 0x00,
1157 // optional.explicitly_present_varint
1158 0x20, 0x00,
1159 // optional.sometimes_empty_fixed
1160 0x2a, 0x04, 0x00, 0x00, 0x00, 0x00,
1161 // optional.sometimes_empty_varint
1162 0x32, 0x01, 0x00,
1163 };
1164 // clang-format on
1165
1166 stream::MemoryReader reader(as_bytes(span(proto_data)));
1167 OptionalTest::StreamDecoder optional_test(reader);
1168
1169 OptionalTest::Message message{};
1170 const auto status = optional_test.Read(message);
1171 ASSERT_EQ(status, OkStatus());
1172
1173 // Non-optional fields have their default value and aren't meaningfully
1174 // different from missing.
1175 EXPECT_EQ(message.sometimes_present_fixed, 0x00);
1176 EXPECT_EQ(message.sometimes_present_varint, 0x00);
1177
1178 // Optional fields are explicitly present with a default value.
1179 EXPECT_TRUE(message.explicitly_present_fixed);
1180 EXPECT_EQ(*message.explicitly_present_fixed, 0x00);
1181 EXPECT_TRUE(message.explicitly_present_varint);
1182 EXPECT_EQ(*message.explicitly_present_varint, 0x00);
1183
1184 // Repeated fields with a default value are meaningfully non-empty.
1185 EXPECT_FALSE(message.sometimes_empty_fixed.empty());
1186 EXPECT_EQ(message.sometimes_empty_fixed.size(), 1u);
1187 EXPECT_EQ(message.sometimes_empty_fixed[0], 0x00);
1188 EXPECT_FALSE(message.sometimes_empty_varint.empty());
1189 EXPECT_EQ(message.sometimes_empty_varint.size(), 1u);
1190 EXPECT_EQ(message.sometimes_empty_varint[0], 0x00);
1191 }
1192
TEST(CodegenMessage,ReadImportedOptions)1193 TEST(CodegenMessage, ReadImportedOptions) {
1194 // clang-format off
1195 constexpr uint8_t proto_data[] = {
1196 // notice
1197 0x0a, 0x0f,
1198 // notice.message
1199 0x0a, 0x0d, 'P', 'r', 'e', 's', 's', ' ', 'a', 'n', 'y', ' ', 'k', 'e', 'y'
1200 };
1201 // clang-format on
1202
1203 stream::MemoryReader reader(as_bytes(span(proto_data)));
1204 TestMessage::StreamDecoder test_message(reader);
1205
1206 // The options file for the imported proto is applied, making the string
1207 // field a vector rather than requiring a callback.
1208 TestMessage::Message message{};
1209 const auto status = test_message.Read(message);
1210 ASSERT_EQ(status, OkStatus());
1211
1212 constexpr std::string_view kExpectedMessage{"Press any key"};
1213
1214 EXPECT_EQ(message.notice.message.size(), kExpectedMessage.size());
1215 EXPECT_EQ(std::memcmp(message.notice.message.data(),
1216 kExpectedMessage.data(),
1217 kExpectedMessage.size()),
1218 0);
1219 }
1220
TEST(CodegenMessage,ReadImportedFromDepsOptions)1221 TEST(CodegenMessage, ReadImportedFromDepsOptions) {
1222 // clang-format off
1223 constexpr uint8_t proto_data[] = {
1224 // debug
1225 0x12, 0x0f,
1226 // debug.message
1227 0x0a, 0x0d, 'P', 'r', 'e', 's', 's', ' ', 'a', 'n', 'y', ' ', 'k', 'e', 'y'
1228 };
1229 // clang-format on
1230
1231 stream::MemoryReader reader(as_bytes(span(proto_data)));
1232 TestMessage::StreamDecoder test_message(reader);
1233
1234 // The options file for the imported proto is applied, making the string
1235 // field a vector rather than requiring a callback.
1236 TestMessage::Message message{};
1237 const auto status = test_message.Read(message);
1238 ASSERT_EQ(status, OkStatus());
1239
1240 constexpr std::string_view kExpectedMessage{"Press any key"};
1241
1242 EXPECT_EQ(message.debug.message.size(), kExpectedMessage.size());
1243 EXPECT_EQ(std::memcmp(message.debug.message.data(),
1244 kExpectedMessage.data(),
1245 kExpectedMessage.size()),
1246 0);
1247 }
1248
1249 class BreakableDecoder : public KeyValuePair::StreamDecoder {
1250 public:
BreakableDecoder(stream::Reader & reader)1251 constexpr BreakableDecoder(stream::Reader& reader) : StreamDecoder(reader) {}
1252
Read(KeyValuePair::Message & message,span<const internal::MessageField> table)1253 Status Read(KeyValuePair::Message& message,
1254 span<const internal::MessageField> table) {
1255 return ::pw::protobuf::StreamDecoder::Read(
1256 as_writable_bytes(span(&message, 1)), table);
1257 }
1258 };
1259
TEST(CodegenMessage,DISABLED_ReadDoesNotOverrun)1260 TEST(CodegenMessage, DISABLED_ReadDoesNotOverrun) {
1261 // Deliberately construct a message table that attempts to violate the bounds
1262 // of the structure. We're not testing that a developer can't do this, rather
1263 // that the protobuf decoder can't be exploited in this way.
1264 constexpr internal::MessageField kMessageFields[] = {
1265 {1,
1266 WireType::kDelimited,
1267 sizeof(std::byte),
1268 static_cast<internal::VarintType>(0),
1269 false,
1270 false,
1271 false,
1272 false,
1273 false,
1274 0,
1275 sizeof(KeyValuePair::Message) * 2,
1276 {}},
1277 };
1278
1279 // clang-format off
1280 constexpr uint8_t proto_data[] = {
1281 // id=1, len=9,
1282 0x0a, 0x08, 'd', 'o', 'n', 't', 'e', 'a', 't', 'm', 'e',
1283 };
1284 // clang-format on
1285
1286 stream::MemoryReader reader(as_bytes(span(proto_data)));
1287 BreakableDecoder decoder(reader);
1288
1289 KeyValuePair::Message message{};
1290 // ASSERT_CRASH
1291 std::ignore = decoder.Read(message, kMessageFields);
1292 }
1293
TEST(CodegenMessage,Write)1294 TEST(CodegenMessage, Write) {
1295 constexpr uint8_t pigweed_data[] = {
1296 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80};
1297
1298 Pigweed::Message message{};
1299 message.magic_number = 0x49u;
1300 message.ziggy = -111;
1301 message.cycles = 0x40302010fecaaddeu;
1302 message.ratio = -1.42f;
1303 message.error_message = "not a typewriter";
1304 message.pigweed.status = Bool::FILE_NOT_FOUND;
1305 message.bin = Pigweed::Protobuf::Binary::ZERO;
1306 message.bungle = -111;
1307 message.proto.bin = Proto::Binary::OFF;
1308 message.proto.pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO;
1309 message.proto.pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ZERO;
1310 message.proto.meta.file_name = "/etc/passwd";
1311 message.proto.meta.status = Pigweed::Protobuf::Compiler::Status::FUBAR;
1312 message.proto.meta.protobuf_bin = Pigweed::Protobuf::Binary::ONE;
1313 message.proto.meta.pigweed_bin = Pigweed::Pigweed::Binary::ONE;
1314 std::memcpy(message.data.data(), pigweed_data, sizeof(pigweed_data));
1315
1316 std::byte encode_buffer[Pigweed::kMaxEncodedSizeBytes];
1317 std::byte temp_buffer[Pigweed::kScratchBufferSizeBytes];
1318
1319 stream::MemoryWriter writer(encode_buffer);
1320 Pigweed::StreamEncoder pigweed(writer, temp_buffer);
1321
1322 const auto status = pigweed.Write(message);
1323 ASSERT_EQ(status, OkStatus());
1324
1325 // clang-format off
1326 constexpr uint8_t expected_proto[] = {
1327 // pigweed.magic_number
1328 0x08, 0x49,
1329 // pigweed.ziggy
1330 0x10, 0xdd, 0x01,
1331 // pigweed.cycles
1332 0x19, 0xde, 0xad, 0xca, 0xfe, 0x10, 0x20, 0x30, 0x40,
1333 // pigweed.ratio
1334 0x25, 0x8f, 0xc2, 0xb5, 0xbf,
1335 // pigweed.error_message
1336 0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ',
1337 't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r',
1338 // pigweed.pigweed
1339 0x3a, 0x02,
1340 // pigweed.pigweed.status
1341 0x08, 0x02,
1342 // pigweed.bin
1343 0x40, 0x01,
1344 // pigweed.proto
1345 0x4a, 0x15,
1346 // pigweed.proto.pigweed_protobuf_bin
1347 0x20, 0x01,
1348 // pigweed.proto.meta
1349 0x2a, 0x11,
1350 // pigweed.proto.meta.file_name
1351 0x0a, 0x0b, '/', 'e', 't', 'c', '/', 'p', 'a', 's', 's', 'w', 'd',
1352 // pigweed.proto.meta.status
1353 0x10, 0x02,
1354 // pigweed.proto.meta.pigweed_bin
1355 0x20, 0x01,
1356 // pigweed.bytes
1357 0x5a, 0x08, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80,
1358 // pigweed.bungle
1359 0x70, 0x91, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01,
1360 };
1361 // clang-format on
1362
1363 ConstByteSpan result = writer.WrittenData();
1364 EXPECT_EQ(result.size(), sizeof(expected_proto));
1365 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1366 0);
1367 }
1368
TEST(CodegenMessage,WriteDefaults)1369 TEST(CodegenMessage, WriteDefaults) {
1370 Pigweed::Message message{};
1371
1372 std::byte encode_buffer[Pigweed::kMaxEncodedSizeBytes];
1373 std::byte temp_buffer[Pigweed::kScratchBufferSizeBytes];
1374
1375 stream::MemoryWriter writer(encode_buffer);
1376 Pigweed::StreamEncoder pigweed(writer, temp_buffer);
1377
1378 const auto status = pigweed.Write(message);
1379 ASSERT_EQ(status, OkStatus());
1380
1381 // Since all fields are at their default, the output should be zero sized.
1382 ConstByteSpan result = writer.WrittenData();
1383 EXPECT_EQ(result.size(), 0u);
1384 }
1385
TEST(CodegenMessage,WritePackedScalar)1386 TEST(CodegenMessage, WritePackedScalar) {
1387 RepeatedTest::Message message{};
1388 for (int i = 0; i < 4; ++i) {
1389 message.uint32s.push_back(i * 16u);
1390 message.fixed32s.push_back(i * 16u);
1391 }
1392
1393 std::byte encode_buffer[RepeatedTest::kMaxEncodedSizeBytes];
1394
1395 stream::MemoryWriter writer(encode_buffer);
1396 RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());
1397
1398 const auto status = repeated_test.Write(message);
1399 ASSERT_EQ(status, OkStatus());
1400
1401 // clang-format off
1402 constexpr uint8_t expected_proto[] = {
1403 // uint32s[], v={0, 16, 32, 48}
1404 0x0a, 0x04,
1405 0x00,
1406 0x10,
1407 0x20,
1408 0x30,
1409 // fixed32s[]. v={0, 16, 32, 48}
1410 0x32, 0x10,
1411 0x00, 0x00, 0x00, 0x00,
1412 0x10, 0x00, 0x00, 0x00,
1413 0x20, 0x00, 0x00, 0x00,
1414 0x30, 0x00, 0x00, 0x00,
1415 };
1416 // clang-format on
1417
1418 ConstByteSpan result = writer.WrittenData();
1419 EXPECT_EQ(result.size(), sizeof(expected_proto));
1420 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1421 0);
1422 }
1423
TEST(CodegenMessage,WritePackedScalarFixedLength)1424 TEST(CodegenMessage, WritePackedScalarFixedLength) {
1425 RepeatedTest::Message message{};
1426 for (int i = 0; i < 4; ++i) {
1427 message.uint64s[i] = (i + 1) * 1000u;
1428 }
1429 message.doubles[0] = 3.14159;
1430 message.doubles[1] = 2.71828;
1431
1432 std::byte encode_buffer[RepeatedTest::kMaxEncodedSizeBytes];
1433
1434 stream::MemoryWriter writer(encode_buffer);
1435 RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());
1436
1437 const auto status = repeated_test.Write(message);
1438 ASSERT_EQ(status, OkStatus());
1439
1440 // clang-format off
1441 constexpr uint8_t expected_proto[] = {
1442 // doubles[], v={3.14159, 2.71828}
1443 0x22, 0x10,
1444 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
1445 0x90, 0xf7, 0xaa, 0x95, 0x09, 0xbf, 0x05, 0x40,
1446 // uint64s[], v={1000, 2000, 3000, 4000}
1447 0x42, 0x08, 0xe8, 0x07, 0xd0, 0x0f, 0xb8, 0x17, 0xa0, 0x1f,
1448 };
1449 // clang-format on
1450
1451 ConstByteSpan result = writer.WrittenData();
1452 EXPECT_EQ(result.size(), sizeof(expected_proto));
1453 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1454 0);
1455 }
1456
TEST(CodegenMessage,WritePackedScalarCallback)1457 TEST(CodegenMessage, WritePackedScalarCallback) {
1458 RepeatedTest::Message message{};
1459 message.sint32s.SetEncoder([](RepeatedTest::StreamEncoder& encoder) {
1460 constexpr int32_t sint32s[] = {-25, -1, 0, 1, 25};
1461 return encoder.WriteSint32s(sint32s);
1462 });
1463
1464 std::byte encode_buffer[RepeatedTest::kMaxEncodedSizeBytes +
1465 varint::kMaxVarint32SizeBytes * 5];
1466
1467 stream::MemoryWriter writer(encode_buffer);
1468 RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());
1469
1470 const auto status = repeated_test.Write(message);
1471 ASSERT_EQ(status, OkStatus());
1472
1473 // clang-format off
1474 constexpr uint8_t expected_proto[] = {
1475 // sint32s[], v={-25, -1, 0, 1, 25}
1476 0x12, 0x05,
1477 0x31,
1478 0x01,
1479 0x00,
1480 0x02,
1481 0x32,
1482 };
1483 // clang-format on
1484
1485 ConstByteSpan result = writer.WrittenData();
1486 EXPECT_EQ(result.size(), sizeof(expected_proto));
1487 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1488 0);
1489 }
1490
TEST(CodegenMessage,WritePackedEnum)1491 TEST(CodegenMessage, WritePackedEnum) {
1492 RepeatedTest::Message message{};
1493 message.enums.push_back(Enum::RED);
1494 message.enums.push_back(Enum::GREEN);
1495 message.enums.push_back(Enum::AMBER);
1496 message.enums.push_back(Enum::RED);
1497
1498 std::byte encode_buffer[RepeatedTest::kMaxEncodedSizeBytes];
1499
1500 stream::MemoryWriter writer(encode_buffer);
1501 RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());
1502
1503 const auto status = repeated_test.Write(message);
1504 ASSERT_EQ(status, OkStatus());
1505
1506 // clang-format off
1507 constexpr uint8_t expected_proto[] = {
1508 // enums[], v={RED, GREEN, AMBER, RED}
1509 0x4a, 0x04, 0x00, 0x02, 0x01, 0x00,
1510 };
1511 // clang-format on
1512
1513 ConstByteSpan result = writer.WrittenData();
1514 EXPECT_EQ(result.size(), sizeof(expected_proto));
1515 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1516 0);
1517 }
1518
TEST(CodegenMessage,WriteStringCallback)1519 TEST(CodegenMessage, WriteStringCallback) {
1520 Pigweed::Message message{};
1521 // pigweed.description has no max_size specified so a callback must be
1522 // set to write the value.
1523 message.description.SetEncoder([](Pigweed::StreamEncoder& encoder) {
1524 return encoder.WriteDescription(
1525 "an open source collection of embedded-targeted "
1526 "libraries-or as we like to call them, modules");
1527 });
1528
1529 std::byte encode_buffer[Pigweed::kMaxEncodedSizeBytes + 92];
1530 std::byte temp_buffer[Pigweed::kScratchBufferSizeBytes];
1531
1532 stream::MemoryWriter writer(encode_buffer);
1533 Pigweed::StreamEncoder pigweed(writer, temp_buffer);
1534
1535 const auto status = pigweed.Write(message);
1536 ASSERT_EQ(status, OkStatus());
1537
1538 // clang-format off
1539 constexpr uint8_t expected_proto[] = {
1540 // pigweed.description
1541 0x62, 0x5c, 'a', 'n', ' ', 'o', 'p', 'e', 'n', ' ', 's', 'o', 'u', 'r', 'c',
1542 'e', ' ', 'c', 'o', 'l', 'l', 'e', 'c', 't', 'i', 'o', 'n', ' ', 'o', 'f',
1543 ' ', 'e', 'm', 'b', 'e', 'd', 'd', 'e', 'd', '-', 't', 'a', 'r', 'g', 'e',
1544 't', 'e', 'd', ' ', 'l', 'i', 'b', 'r', 'a', 'r', 'i', 'e', 's', '-', 'o',
1545 'r', ' ', 'a', 's', ' ', 'w', 'e', ' ', 'l', 'i', 'k', 'e', ' ', 't', 'o',
1546 ' ', 'c', 'a', 'l', 'l', ' ', 't', 'h', 'e', 'm', ',', ' ', 'm', 'o', 'd',
1547 'u', 'l', 'e', 's',
1548 };
1549 // clang-format on
1550
1551 ConstByteSpan result = writer.WrittenData();
1552 EXPECT_EQ(result.size(), sizeof(expected_proto));
1553 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1554 0);
1555 }
1556
TEST(CodegenMessage,WriteForcedCallback)1557 TEST(CodegenMessage, WriteForcedCallback) {
1558 Pigweed::Message message{};
1559 // pigweed.special_property has use_callback=true to force the use of a
1560 // callback even though it's a simple scalar.
1561 message.special_property.SetEncoder([](Pigweed::StreamEncoder& encoder) {
1562 return encoder.WriteSpecialProperty(42u);
1563 });
1564
1565 std::byte encode_buffer[Pigweed::kMaxEncodedSizeBytes];
1566 std::byte temp_buffer[Pigweed::kScratchBufferSizeBytes];
1567
1568 stream::MemoryWriter writer(encode_buffer);
1569 Pigweed::StreamEncoder pigweed(writer, temp_buffer);
1570
1571 const auto status = pigweed.Write(message);
1572 ASSERT_EQ(status, OkStatus());
1573
1574 // clang-format off
1575 constexpr uint8_t expected_proto[] = {
1576 // pigweed.special_property
1577 0x68, 0x2a,
1578 };
1579 // clang-format on
1580
1581 ConstByteSpan result = writer.WrittenData();
1582 EXPECT_EQ(result.size(), sizeof(expected_proto));
1583 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1584 0);
1585 }
1586
TEST(CodegenMessage,WriteNestedImported)1587 TEST(CodegenMessage, WriteNestedImported) {
1588 Period::Message message{};
1589 message.start.seconds = 1517949900u;
1590 message.end.seconds = 1517950378u;
1591
1592 std::byte encode_buffer[Period::kMaxEncodedSizeBytes];
1593 std::byte temp_buffer[Period::kScratchBufferSizeBytes];
1594
1595 stream::MemoryWriter writer(encode_buffer);
1596 Period::StreamEncoder period(writer, temp_buffer);
1597
1598 const auto status = period.Write(message);
1599 ASSERT_EQ(status, OkStatus());
1600
1601 // clang-format off
1602 constexpr uint8_t expected_proto[] = {
1603 // period.start
1604 0x0a, 0x06,
1605 // period.start.seconds v=1517949900
1606 0x08, 0xcc, 0xa7, 0xe8, 0xd3, 0x05,
1607 // period.end
1608 0x12, 0x06,
1609 // period.end.seconds, v=1517950378
1610 0x08, 0xaa, 0xab, 0xe8, 0xd3, 0x05,
1611 };
1612 // clang-format on
1613
1614 ConstByteSpan result = writer.WrittenData();
1615 EXPECT_EQ(result.size(), sizeof(expected_proto));
1616 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1617 0);
1618 }
1619
TEST(CodegenMessage,WriteNestedRepeated)1620 TEST(CodegenMessage, WriteNestedRepeated) {
1621 RepeatedTest::Message message{};
1622 // Repeated nested messages require a callback since there would otherwise be
1623 // no way to set callbacks on the nested message.
1624 message.structs.SetEncoder([](RepeatedTest::StreamEncoder& encoder) {
1625 for (int i = 0; i < 2; ++i) {
1626 Struct::Message struct_message{};
1627 struct_message.one = i * 32 + 16u;
1628 struct_message.two = i * 32 + 32u;
1629
1630 const auto status = encoder.GetStructsEncoder().Write(struct_message);
1631 EXPECT_EQ(status, OkStatus());
1632 }
1633 return OkStatus();
1634 });
1635
1636 std::byte encode_buffer[RepeatedTest::kMaxEncodedSizeBytes +
1637 Struct::kMaxEncodedSizeBytes * 2];
1638 std::byte temp_buffer[RepeatedTest::kScratchBufferSizeBytes +
1639 Struct::kMaxEncodedSizeBytes];
1640
1641 stream::MemoryWriter writer(encode_buffer);
1642 RepeatedTest::StreamEncoder repeated_test(writer, temp_buffer);
1643
1644 const auto status = repeated_test.Write(message);
1645 ASSERT_EQ(status, OkStatus());
1646
1647 // clang-format off
1648 constexpr uint8_t expected_proto[] = {
1649 // repeated.structs
1650 0x2a, 0x04,
1651 // repeated.structs.one v=16
1652 0x08, 0x10,
1653 // repeated.structs.two v=32
1654 0x10, 0x20,
1655 // repeated.structs
1656 0x2a, 0x04,
1657 // repeated.structs.one v=48
1658 0x08, 0x30,
1659 // repeated.structs.two v=64
1660 0x10, 0x40,
1661 };
1662 // clang-format on
1663
1664 ConstByteSpan result = writer.WrittenData();
1665 EXPECT_EQ(result.size(), sizeof(expected_proto));
1666 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1667 0);
1668 }
1669
TEST(CodegenMessage,WriteNestedForcedCallback)1670 TEST(CodegenMessage, WriteNestedForcedCallback) {
1671 Pigweed::Message message{};
1672 // pigweed.device_info has use_callback=true to force the use of a callback.
1673 message.device_info.SetEncoder([](Pigweed::StreamEncoder& encoder) {
1674 DeviceInfo::Message device_info{};
1675 device_info.device_name = "pixel";
1676 device_info.device_id = 0x08080808u;
1677 device_info.status = DeviceInfo::DeviceStatus::OK;
1678
1679 // Use the callback to set nested callbacks.
1680 device_info.attributes.SetEncoder(
1681 [](DeviceInfo::StreamEncoder& device_info_encoder) {
1682 KeyValuePair::Message attribute{};
1683
1684 attribute.key = "version";
1685 attribute.value = "5.3.1";
1686 PW_TRY(device_info_encoder.GetAttributesEncoder().Write(attribute));
1687
1688 attribute.key = "chip";
1689 attribute.value = "left-soc";
1690 PW_TRY(device_info_encoder.GetAttributesEncoder().Write(attribute));
1691
1692 return OkStatus();
1693 });
1694
1695 return encoder.GetDeviceInfoEncoder().Write(device_info);
1696 });
1697
1698 std::byte encode_buffer[Pigweed::kMaxEncodedSizeBytes +
1699 DeviceInfo::kMaxEncodedSizeBytes];
1700 std::byte temp_buffer[Pigweed::kScratchBufferSizeBytes +
1701 DeviceInfo::kMaxEncodedSizeBytes];
1702
1703 stream::MemoryWriter writer(encode_buffer);
1704 Pigweed::StreamEncoder pigweed(writer, temp_buffer);
1705
1706 const auto status = pigweed.Write(message);
1707 ASSERT_EQ(status, OkStatus());
1708
1709 // clang-format off
1710 constexpr uint8_t expected_proto[] = {
1711 // pigweed.device_info
1712 0x32, 0x30,
1713 // pigweed.device_info.device_name
1714 0x0a, 0x05, 'p', 'i', 'x', 'e', 'l',
1715 // pigweed.device_info.device_id
1716 0x15, 0x08, 0x08, 0x08, 0x08,
1717 // pigweed.device_info.attributes[0]
1718 0x22, 0x10,
1719 // pigweed.device_info.attributes[0].key
1720 0x0a, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
1721 // pigweed.device_info.attributes[0].value
1722 0x12, 0x05, '5', '.', '3', '.', '1',
1723 // pigweed.device_info.attributes[1]
1724 0x22, 0x10,
1725 // pigweed.device_info.attributes[1].key
1726 0x0a, 0x04, 'c', 'h', 'i', 'p',
1727 // pigweed.device_info.attributes[1].value
1728 0x12, 0x08, 'l', 'e', 'f', 't', '-', 's', 'o', 'c',
1729 };
1730 // clang-format on
1731
1732 ConstByteSpan result = writer.WrittenData();
1733 EXPECT_EQ(result.size(), sizeof(expected_proto));
1734 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1735 0);
1736 }
1737
TEST(CodegenMessage,EnumAliases)1738 TEST(CodegenMessage, EnumAliases) {
1739 // Unprefixed enum.
1740 EXPECT_EQ(Bool::kTrue, Bool::TRUE);
1741 EXPECT_EQ(Bool::kFalse, Bool::FALSE);
1742 EXPECT_EQ(Bool::kFileNotFound, Bool::FILE_NOT_FOUND);
1743
1744 // Prefixed enum has the prefix removed.
1745 EXPECT_EQ(Error::kNone, Error::ERROR_NONE);
1746 EXPECT_EQ(Error::kNotFound, Error::ERROR_NOT_FOUND);
1747 EXPECT_EQ(Error::kUnknown, Error::ERROR_UNKNOWN);
1748
1749 // Single-value enum.
1750 EXPECT_EQ(AlwaysBlue::kBlue, AlwaysBlue::BLUE);
1751 }
1752
TEST(CodegenMessage,WriteOptionalPresent)1753 TEST(CodegenMessage, WriteOptionalPresent) {
1754 OptionalTest::Message message{};
1755 message.sometimes_present_fixed = 0x2a;
1756 message.sometimes_present_varint = 0x2a;
1757 message.explicitly_present_fixed = 0x45;
1758 message.explicitly_present_varint = 0x45;
1759 message.sometimes_empty_fixed.push_back(0x63);
1760 message.sometimes_empty_varint.push_back(0x63);
1761
1762 std::byte encode_buffer[512];
1763
1764 stream::MemoryWriter writer(encode_buffer);
1765 OptionalTest::StreamEncoder optional_test(writer, ByteSpan());
1766
1767 const auto status = optional_test.Write(message);
1768 ASSERT_EQ(status, OkStatus());
1769
1770 // clang-format off
1771 constexpr uint8_t expected_proto[] = {
1772 // optional.sometimes_present_fixed
1773 0x0d, 0x2a, 0x00, 0x00, 0x00,
1774 // optional.sometimes_present_varint
1775 0x10, 0x2a,
1776 // optional.explicitly_present_fixed
1777 0x1d, 0x45, 0x00, 0x00, 0x00,
1778 // optional.explicitly_present_varint
1779 0x20, 0x45,
1780 // optional.sometimes_empty_fixed
1781 0x2a, 0x04, 0x63, 0x00, 0x00, 0x00,
1782 // optional.sometimes_empty_varint
1783 0x32, 0x01, 0x63,
1784 };
1785 // clang-format on
1786
1787 ConstByteSpan result = writer.WrittenData();
1788 EXPECT_EQ(result.size(), sizeof(expected_proto));
1789 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1790 0);
1791 }
1792
TEST(CodegenMessage,WriteOptionalNotPresent)1793 TEST(CodegenMessage, WriteOptionalNotPresent) {
1794 OptionalTest::Message message{};
1795
1796 std::byte encode_buffer[512];
1797
1798 stream::MemoryWriter writer(encode_buffer);
1799 OptionalTest::StreamEncoder optional_test(writer, ByteSpan());
1800
1801 const auto status = optional_test.Write(message);
1802 ASSERT_EQ(status, OkStatus());
1803
1804 // The expected proto is empty; no bytes should be written.
1805
1806 ConstByteSpan result = writer.WrittenData();
1807 EXPECT_TRUE(result.empty());
1808 }
1809
TEST(CodegenMessage,WriteOptionalPresentDefaults)1810 TEST(CodegenMessage, WriteOptionalPresentDefaults) {
1811 OptionalTest::Message message{};
1812 // Non-optional fields with a default value are not explicitly encoded, so
1813 // aren't meaningfully different from one that's just ommitted.
1814 message.sometimes_present_fixed = 0x00;
1815 message.sometimes_present_varint = 0x00;
1816 // Optional fields, even with a default value, are explicitly encoded.
1817 message.explicitly_present_fixed = 0x00;
1818 message.explicitly_present_varint = 0x00;
1819 // Repeated fields with a default value are meaningfully non-empty.
1820 message.sometimes_empty_fixed.push_back(0x00);
1821 message.sometimes_empty_varint.push_back(0x00);
1822
1823 std::byte encode_buffer[512];
1824
1825 stream::MemoryWriter writer(encode_buffer);
1826 OptionalTest::StreamEncoder optional_test(writer, ByteSpan());
1827
1828 const auto status = optional_test.Write(message);
1829 ASSERT_EQ(status, OkStatus());
1830
1831 // clang-format off
1832 constexpr uint8_t expected_proto[] = {
1833 // optional.explicitly_present_fixed
1834 0x1d, 0x00, 0x00, 0x00, 0x00,
1835 // optional.explicitly_present_varint
1836 0x20, 0x00,
1837 // optional.sometimes_empty_fixed
1838 0x2a, 0x04, 0x00, 0x00, 0x00, 0x00,
1839 // optional.sometimes_empty_varint
1840 0x32, 0x01, 0x00,
1841 };
1842 // clang-format on
1843
1844 ConstByteSpan result = writer.WrittenData();
1845 EXPECT_EQ(result.size(), sizeof(expected_proto));
1846 EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
1847 0);
1848 }
1849
1850 class BreakableEncoder : public KeyValuePair::MemoryEncoder {
1851 public:
BreakableEncoder(ByteSpan buffer)1852 constexpr BreakableEncoder(ByteSpan buffer)
1853 : KeyValuePair::MemoryEncoder(buffer) {}
1854
Write(const KeyValuePair::Message & message,span<const internal::MessageField> table)1855 Status Write(const KeyValuePair::Message& message,
1856 span<const internal::MessageField> table) {
1857 return ::pw::protobuf::StreamEncoder::Write(as_bytes(span(&message, 1)),
1858 table);
1859 }
1860 };
1861
TEST(CodegenMessage,DISABLED_WriteDoesNotOverrun)1862 TEST(CodegenMessage, DISABLED_WriteDoesNotOverrun) {
1863 // Deliberately construct a message table that attempts to violate the bounds
1864 // of the structure. We're not testing that a developer can't do this, rather
1865 // that the protobuf encoder can't be exploited in this way.
1866 constexpr internal::MessageField kMessageFields[] = {
1867 {1,
1868 WireType::kDelimited,
1869 sizeof(std::byte),
1870 static_cast<internal::VarintType>(0),
1871 false,
1872 false,
1873 false,
1874 false,
1875 false,
1876 0,
1877 sizeof(KeyValuePair::Message) * 2,
1878 {}},
1879 };
1880
1881 std::byte encode_buffer[64];
1882
1883 BreakableEncoder encoder(encode_buffer);
1884 KeyValuePair::Message message{};
1885 // ASSERT_CRASH
1886 std::ignore = encoder.Write(message, kMessageFields);
1887 }
1888
1889 // The following tests cover using the codegen struct Message and callbacks in
1890 // different ways.
1891
1892 // Check that the callback function object is large enough to implement a
1893 // "call a function on this" lambda.
1894 class StringChecker {
1895 public:
1896 StringChecker() = default;
1897 ~StringChecker() = default;
1898
Check(RepeatedTest::StreamDecoder & repeated_test)1899 Status Check(RepeatedTest::StreamDecoder& repeated_test) {
1900 RepeatedTest::Message message{};
1901 message.strings.SetDecoder([this](RepeatedTest::StreamDecoder& decoder) {
1902 return this->CheckOne(decoder);
1903 });
1904 return repeated_test.Read(message);
1905 }
1906
1907 private:
CheckOne(RepeatedTest::StreamDecoder & decoder)1908 Status CheckOne(RepeatedTest::StreamDecoder& decoder) {
1909 EXPECT_EQ(decoder.Field().value(), RepeatedTest::Fields::kStrings);
1910
1911 std::array<char, 40> strings{};
1912 const StatusWithSize sws = decoder.ReadStrings(strings);
1913 EXPECT_EQ(sws.status(), OkStatus());
1914 EXPECT_EQ(sws.size(), kExpectedStrings[i_].size());
1915 EXPECT_EQ(std::memcmp(strings.data(),
1916 kExpectedStrings[i_].data(),
1917 kExpectedStrings[i_].size()),
1918 0);
1919
1920 ++i_;
1921 return sws.status();
1922 }
1923
1924 int i_ = 0;
1925 constexpr static std::string_view kExpectedStrings[] = {
1926 {"if music be the food of love, play on"},
1927 {"give me excess of it, that, surfeiting"},
1928 {"the appetite may sicken, and so die"}};
1929 };
1930
TEST(CodegenMessage,CallbackInClass)1931 TEST(CodegenMessage, CallbackInClass) {
1932 // clang-format off
1933 constexpr uint8_t proto_data[] = {
1934 // repeated.strings
1935 0x1a, 0x25, 'i', 'f', ' ', 'm', 'u', 's', 'i', 'c', ' ', 'b', 'e', ' ',
1936 't', 'h', 'e', ' ', 'f', 'o', 'o', 'd', ' ', 'o', 'f', ' ',
1937 'l', 'o', 'v', 'e', ',', ' ', 'p', 'l', 'a', 'y', ' ', 'o', 'n',
1938 // repeated.strings
1939 0x1a, 0x26, 'g', 'i', 'v', 'e', ' ', 'm', 'e', ' ', 'e', 'x', 'c', 'e',
1940 's', 's', ' ', 'o', 'f', ' ', 'i', 't', ',', ' ', 't', 'h', 'a', 't', ',',
1941 ' ', 's', 'u', 'r', 'f', 'e', 'i', 't', 'i', 'n', 'g',
1942 // repeated.strings
1943 0x1a, 0x23, 't', 'h', 'e', ' ', 'a', 'p', 'p', 'e', 't', 'i', 't', 'e', ' ',
1944 'm', 'a', 'y', ' ', 's', 'i', 'c', 'k', 'e', 'n', ',', ' ', 'a', 'n', 'd',
1945 ' ', 's', 'o', ' ', 'd', 'i', 'e',
1946 };
1947 // clang-format on
1948
1949 stream::MemoryReader reader(as_bytes(span(proto_data)));
1950 RepeatedTest::StreamDecoder repeated_test(reader);
1951
1952 StringChecker checker{};
1953 const auto status = checker.Check(repeated_test);
1954 ASSERT_EQ(status, OkStatus());
1955 }
1956
1957 // Check that we can create a custom subclass of the message struct that sets
1958 // its own callbacks to member functions that populate fields added in the
1959 // subclass.
1960 struct CustomMessage : RepeatedTest::Message {
CustomMessagepw::protobuf::__anon0bc28c7c0111::CustomMessage1961 CustomMessage() : RepeatedTest::Message() {
1962 strings.SetDecoder([this](RepeatedTest::StreamDecoder& decoder) {
1963 return this->ParseStrings(decoder);
1964 });
1965 }
1966
1967 pw::Vector<std::array<char, 40>, 8> all_strings{};
1968
1969 private:
ParseStringspw::protobuf::__anon0bc28c7c0111::CustomMessage1970 Status ParseStrings(RepeatedTest::StreamDecoder& decoder) {
1971 PW_ASSERT(decoder.Field().value() == RepeatedTest::Fields::kStrings);
1972
1973 std::array<char, 40> one_strings{};
1974 const auto sws = decoder.ReadStrings(one_strings);
1975 if (!sws.ok()) {
1976 return sws.status();
1977 }
1978
1979 one_strings[sws.size()] = '\0';
1980 all_strings.push_back(one_strings);
1981
1982 return OkStatus();
1983 }
1984 };
1985
TEST(CodegenMessage,CallbackInSubclass)1986 TEST(CodegenMessage, CallbackInSubclass) {
1987 // clang-format off
1988 constexpr uint8_t proto_data[] = {
1989 // repeated.strings
1990 0x1a, 0x25, 'i', 'f', ' ', 'm', 'u', 's', 'i', 'c', ' ', 'b', 'e', ' ',
1991 't', 'h', 'e', ' ', 'f', 'o', 'o', 'd', ' ', 'o', 'f', ' ',
1992 'l', 'o', 'v', 'e', ',', ' ', 'p', 'l', 'a', 'y', ' ', 'o', 'n',
1993 // repeated.strings
1994 0x1a, 0x26, 'g', 'i', 'v', 'e', ' ', 'm', 'e', ' ', 'e', 'x', 'c', 'e',
1995 's', 's', ' ', 'o', 'f', ' ', 'i', 't', ',', ' ', 't', 'h', 'a', 't', ',',
1996 ' ', 's', 'u', 'r', 'f', 'e', 'i', 't', 'i', 'n', 'g',
1997 // repeated.strings
1998 0x1a, 0x23, 't', 'h', 'e', ' ', 'a', 'p', 'p', 'e', 't', 'i', 't', 'e', ' ',
1999 'm', 'a', 'y', ' ', 's', 'i', 'c', 'k', 'e', 'n', ',', ' ', 'a', 'n', 'd',
2000 ' ', 's', 'o', ' ', 'd', 'i', 'e',
2001 };
2002 // clang-format on
2003
2004 stream::MemoryReader reader(as_bytes(span(proto_data)));
2005 RepeatedTest::StreamDecoder repeated_test(reader);
2006
2007 CustomMessage message{};
2008 const auto status = repeated_test.Read(message);
2009 ASSERT_EQ(status, OkStatus());
2010
2011 constexpr static std::string_view kExpectedStrings[] = {
2012 {"if music be the food of love, play on"},
2013 {"give me excess of it, that, surfeiting"},
2014 {"the appetite may sicken, and so die"}};
2015
2016 EXPECT_EQ(message.all_strings.size(), 3u);
2017 for (int i = 0; i < 3; ++i) {
2018 EXPECT_EQ(std::memcmp(message.all_strings[i].data(),
2019 kExpectedStrings[i].data(),
2020 kExpectedStrings[i].size()),
2021 0);
2022 EXPECT_EQ(message.all_strings[i].data()[kExpectedStrings[i].size()], '\0');
2023 }
2024 }
2025
2026 } // namespace
2027 } // namespace pw::protobuf
2028