aboutsummaryrefslogtreecommitdiff
path: root/pw_protobuf/codegen_message_test.cc
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2023-08-14 15:38:30 -0700
committerXin Li <delphij@google.com>2023-08-14 15:38:30 -0700
commitbddf63953e111d742b591c1c0c7c34bcda8a51c7 (patch)
tree3a93128bff4b737b24b0c9581922c0b20410f0f4 /pw_protobuf/codegen_message_test.cc
parentee890da55c82b95deca3518d5f3777e3d8ca9f0e (diff)
parentfbb9890f8922aa55fde183655a0017e69127ea4b (diff)
downloadpigweed-bddf63953e111d742b591c1c0c7c34bcda8a51c7.tar.gz
Merge Android U (ab/10368041)tmp_amf_298295554
Bug: 291102124 Merged-In: I10c41adb8fe3e126cfa4ff2f49b15863fff379de Change-Id: I66f7a6cccaafc173d3924dae62a736c6c53520c7
Diffstat (limited to 'pw_protobuf/codegen_message_test.cc')
-rw-r--r--pw_protobuf/codegen_message_test.cc2027
1 files changed, 2027 insertions, 0 deletions
diff --git a/pw_protobuf/codegen_message_test.cc b/pw_protobuf/codegen_message_test.cc
new file mode 100644
index 000000000..f049715ea
--- /dev/null
+++ b/pw_protobuf/codegen_message_test.cc
@@ -0,0 +1,2027 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <array>
+#include <string_view>
+#include <tuple>
+
+#include "gtest/gtest.h"
+#include "pw_preprocessor/compiler.h"
+#include "pw_protobuf/internal/codegen.h"
+#include "pw_span/span.h"
+#include "pw_status/status.h"
+#include "pw_status/status_with_size.h"
+#include "pw_stream/memory_stream.h"
+
+// These header files contain the code generated by the pw_protobuf plugin.
+// They are re-generated every time the tests are built and are used by the
+// tests to ensure that the interface remains consistent.
+//
+// The purpose of the tests in this file is primarily to verify that the
+// generated C++ interface is valid rather than the correctness of the
+// low-level encoder.
+#include "pw_protobuf_test_protos/full_test.pwpb.h"
+#include "pw_protobuf_test_protos/importer.pwpb.h"
+#include "pw_protobuf_test_protos/optional.pwpb.h"
+#include "pw_protobuf_test_protos/repeated.pwpb.h"
+
+namespace pw::protobuf {
+namespace {
+
+using namespace ::pw::protobuf::test::pwpb;
+
+PW_MODIFY_DIAGNOSTICS_PUSH();
+PW_MODIFY_DIAGNOSTIC(ignored, "-Wmissing-field-initializers");
+
+TEST(CodegenMessage, Equality) {
+ const Pigweed::Message one{
+ .magic_number = 0x49u,
+ .ziggy = -111,
+ .cycles = 0x40302010fecaaddeu,
+ .ratio = -1.42f,
+ .error_message = "not a typewriter",
+ .pigweed = {.status = Bool::FILE_NOT_FOUND},
+ .bin = Pigweed::Protobuf::Binary::ZERO,
+ .proto = {.bin = Proto::Binary::OFF,
+ .pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO,
+ .pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ZERO,
+ .meta =
+ {
+ .file_name = "/etc/passwd",
+ .status = Pigweed::Protobuf::Compiler::Status::FUBAR,
+ .protobuf_bin = Pigweed::Protobuf::Binary::ONE,
+ .pigweed_bin = Pigweed::Pigweed::Binary::ONE,
+ }},
+ .data = {std::byte{0x10},
+ std::byte{0x20},
+ std::byte{0x30},
+ std::byte{0x40},
+ std::byte{0x50},
+ std::byte{0x60},
+ std::byte{0x70},
+ std::byte{0x80}},
+ .bungle = -111,
+ };
+
+ const Pigweed::Message two{
+ .magic_number = 0x49u,
+ .ziggy = -111,
+ .cycles = 0x40302010fecaaddeu,
+ .ratio = -1.42f,
+ .error_message = "not a typewriter",
+ .pigweed = {.status = Bool::FILE_NOT_FOUND},
+ .bin = Pigweed::Protobuf::Binary::ZERO,
+ .proto = {.bin = Proto::Binary::OFF,
+ .pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO,
+ .pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ZERO,
+ .meta =
+ {
+ .file_name = "/etc/passwd",
+ .status = Pigweed::Protobuf::Compiler::Status::FUBAR,
+ .protobuf_bin = Pigweed::Protobuf::Binary::ONE,
+ .pigweed_bin = Pigweed::Pigweed::Binary::ONE,
+ }},
+ .data = {std::byte{0x10},
+ std::byte{0x20},
+ std::byte{0x30},
+ std::byte{0x40},
+ std::byte{0x50},
+ std::byte{0x60},
+ std::byte{0x70},
+ std::byte{0x80}},
+ .bungle = -111,
+ };
+
+ EXPECT_TRUE(one == two);
+}
+
+TEST(CodegenMessage, CopyEquality) {
+ Pigweed::Message one{
+ .magic_number = 0x49u,
+ .ziggy = -111,
+ .cycles = 0x40302010fecaaddeu,
+ .ratio = -1.42f,
+ .error_message = "not a typewriter",
+ .pigweed = {.status = Bool::FILE_NOT_FOUND},
+ .bin = Pigweed::Protobuf::Binary::ZERO,
+ .proto = {.bin = Proto::Binary::OFF,
+ .pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO,
+ .pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ZERO,
+ .meta =
+ {
+ .file_name = "/etc/passwd",
+ .status = Pigweed::Protobuf::Compiler::Status::FUBAR,
+ .protobuf_bin = Pigweed::Protobuf::Binary::ONE,
+ .pigweed_bin = Pigweed::Pigweed::Binary::ONE,
+ }},
+ .data = {std::byte{0x10},
+ std::byte{0x20},
+ std::byte{0x30},
+ std::byte{0x40},
+ std::byte{0x50},
+ std::byte{0x60},
+ std::byte{0x70},
+ std::byte{0x80}},
+ .bungle = -111,
+ };
+ Pigweed::Message two = one;
+
+ EXPECT_TRUE(one == two);
+}
+
+TEST(CodegenMessage, EmptyEquality) {
+ const Pigweed::Message one{};
+ const Pigweed::Message two{};
+
+ EXPECT_TRUE(one == two);
+}
+
+TEST(CodegenMessage, Inequality) {
+ const Pigweed::Message one{
+ .magic_number = 0x49u,
+ .ziggy = -111,
+ .cycles = 0x40302010fecaaddeu,
+ .ratio = -1.42f,
+ .error_message = "not a typewriter",
+ .pigweed = {.status = Bool::FILE_NOT_FOUND},
+ .bin = Pigweed::Protobuf::Binary::ZERO,
+ .proto = {.bin = Proto::Binary::OFF,
+ .pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO,
+ .pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ZERO,
+ .meta =
+ {
+ .file_name = "/etc/passwd",
+ .status = Pigweed::Protobuf::Compiler::Status::FUBAR,
+ .protobuf_bin = Pigweed::Protobuf::Binary::ONE,
+ .pigweed_bin = Pigweed::Pigweed::Binary::ONE,
+ }},
+ .data = {std::byte{0x10},
+ std::byte{0x20},
+ std::byte{0x30},
+ std::byte{0x40},
+ std::byte{0x50},
+ std::byte{0x60},
+ std::byte{0x70},
+ std::byte{0x80}},
+ .bungle = -111,
+ };
+
+ const Pigweed::Message two{
+ .magic_number = 0x43u,
+ .ziggy = 128,
+ .ratio = -1.42f,
+ .error_message = "not a typewriter",
+ .pigweed = {.status = Bool::TRUE},
+ .bin = Pigweed::Protobuf::Binary::ZERO,
+ .proto = {.bin = Proto::Binary::OFF,
+ .pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO,
+ .pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ONE,
+ .meta =
+ {
+ .file_name = "/etc/passwd",
+ .status = Pigweed::Protobuf::Compiler::Status::FUBAR,
+ .protobuf_bin = Pigweed::Protobuf::Binary::ONE,
+ .pigweed_bin = Pigweed::Pigweed::Binary::ONE,
+ }},
+ .data = {std::byte{0x20},
+ std::byte{0x30},
+ std::byte{0x40},
+ std::byte{0x50},
+ std::byte{0x60},
+ std::byte{0x70},
+ std::byte{0x80},
+ std::byte{0x90}},
+ };
+
+ EXPECT_FALSE(one == two);
+}
+
+TEST(CodegenMessage, TriviallyComparable) {
+ static_assert(IsTriviallyComparable<IntegerMetadata::Message>());
+ static_assert(IsTriviallyComparable<KeyValuePair::Message>());
+ static_assert(!IsTriviallyComparable<Pigweed::Message>());
+}
+
+TEST(CodegenMessage, ConstCopyable) {
+ const Pigweed::Message one{
+ .magic_number = 0x49u,
+ .ziggy = -111,
+ .cycles = 0x40302010fecaaddeu,
+ .ratio = -1.42f,
+ .error_message = "not a typewriter",
+ .pigweed = {.status = Bool::FILE_NOT_FOUND},
+ .bin = Pigweed::Protobuf::Binary::ZERO,
+ .proto = {.bin = Proto::Binary::OFF,
+ .pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO,
+ .pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ZERO,
+ .meta =
+ {
+ .file_name = "/etc/passwd",
+ .status = Pigweed::Protobuf::Compiler::Status::FUBAR,
+ .protobuf_bin = Pigweed::Protobuf::Binary::ONE,
+ .pigweed_bin = Pigweed::Pigweed::Binary::ONE,
+ }},
+ .data = {std::byte{0x10},
+ std::byte{0x20},
+ std::byte{0x30},
+ std::byte{0x40},
+ std::byte{0x50},
+ std::byte{0x60},
+ std::byte{0x70},
+ std::byte{0x80}},
+ .bungle = -111,
+ };
+ Pigweed::Message two = one;
+
+ EXPECT_TRUE(one == two);
+}
+
+TEST(CodegenMessage, FixReservedIdentifiers) {
+ // This test checks that the code was generated as expected, so it will simply
+ // fail to compile if its expectations are not met.
+
+ // Make sure that the `signed` field was renamed to `signed_`.
+ std::ignore = IntegerMetadata::Message{
+ .bits = 32,
+ .signed_ = true,
+ .null = false,
+ };
+
+ // Make sure that the internal enum describing the struct's fields was
+ // generated as expected:
+ // - `BITS` doesn't need an underscore.
+ // - `SIGNED_` has an underscore to match the corresponding `signed_` field.
+ // - `NULL_` has an underscore to avoid a collision with `NULL` (even though
+ // the field `null` doesn't have or need an underscore).
+ std::ignore = IntegerMetadata::Fields::kBits;
+ std::ignore = IntegerMetadata::Fields::kSigned;
+ std::ignore = IntegerMetadata::Fields::kNull;
+
+ // Make sure that the `ReservedWord` enum values were renamed as expected.
+ // Specifically, only enum-value names that are reserved in UPPER_SNAKE_CASE
+ // should be modified. Names that are only reserved in lower_snake_case should
+ // be left alone since they'll never appear in that form in the generated
+ // code.
+ std::ignore = ReservedWord::NULL_; // Add underscore since NULL is a macro.
+ std::ignore = ReservedWord::kNull; // No underscore necessary.
+ std::ignore = ReservedWord::INT; // No underscore necessary.
+ std::ignore = ReservedWord::kInt; // No underscore necessary.
+ std::ignore = ReservedWord::RETURN; // No underscore necessary.
+ std::ignore = ReservedWord::kReturn; // No underscore necessary.
+ std::ignore = ReservedWord::BREAK; // No underscore necessary.
+ std::ignore = ReservedWord::kBreak; // No underscore necessary.
+ std::ignore = ReservedWord::FOR; // No underscore necessary.
+ std::ignore = ReservedWord::kFor; // No underscore necessary.
+ std::ignore = ReservedWord::DO; // No underscore necessary.
+ std::ignore = ReservedWord::kDo; // No underscore necessary.
+
+ // Instantiate an extremely degenerately named set of nested types in order to
+ // make sure that name conflicts with the codegen internals are properly
+ // prevented.
+ std::ignore = Function::Message{
+ .description =
+ Function::Message_::Message{
+ .content = "multiplication (mod 5)",
+ },
+ .domain_field = Function::Fields_::INTEGERS_MOD_5,
+ .codomain_field = Function::Fields_::INTEGERS_MOD_5,
+ };
+
+ // Check for expected values of `enum class Function::Fields`:
+ std::ignore = Function::Fields::kDescription;
+ std::ignore = Function::Fields::kDomainField;
+ std::ignore = Function::Fields::kCodomainField;
+
+ // Check for expected values of `enum class Function::Message_::Fields`:
+ std::ignore = Function::Message_::Fields::kContent;
+
+ // Check for expected values of `enum class Function::Fields_`:
+ std::ignore = Function::Fields_::NONE;
+ std::ignore = Function::Fields_::kNone;
+ std::ignore = Function::Fields_::COMPLEX_NUMBERS;
+ std::ignore = Function::Fields_::kComplexNumbers;
+ std::ignore = Function::Fields_::INTEGERS_MOD_5;
+ std::ignore = Function::Fields_::kIntegersMod5;
+ std::ignore = Function::Fields_::MEROMORPHIC_FUNCTIONS_ON_COMPLEX_PLANE;
+ std::ignore = Function::Fields_::kMeromorphicFunctionsOnComplexPlane;
+ std::ignore = Function::Fields_::OTHER;
+ std::ignore = Function::Fields_::kOther;
+}
+
+PW_MODIFY_DIAGNOSTICS_POP();
+
+TEST(CodegenMessage, Read) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // pigweed.magic_number
+ 0x08, 0x49,
+ // pigweed.ziggy
+ 0x10, 0xdd, 0x01,
+ // pigweed.cycles
+ 0x19, 0xde, 0xad, 0xca, 0xfe, 0x10, 0x20, 0x30, 0x40,
+ // pigweed.ratio
+ 0x25, 0x8f, 0xc2, 0xb5, 0xbf,
+ // pigweed.error_message
+ 0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ',
+ 't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r',
+ // pigweed.bin
+ 0x40, 0x01,
+ // pigweed.bungle
+ 0x70, 0x91, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01,
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ Pigweed::StreamDecoder pigweed(reader);
+
+ Pigweed::Message message{};
+ const auto status = pigweed.Read(message);
+ ASSERT_EQ(status, OkStatus());
+
+ constexpr std::string_view kExpectedErrorMessage{"not a typewriter"};
+
+ EXPECT_EQ(message.magic_number, 0x49u);
+ EXPECT_EQ(message.ziggy, -111);
+ EXPECT_EQ(message.cycles, 0x40302010fecaaddeu);
+ EXPECT_EQ(message.ratio, -1.42f);
+ EXPECT_EQ(message.error_message.size(), kExpectedErrorMessage.size());
+ EXPECT_EQ(std::memcmp(message.error_message.data(),
+ kExpectedErrorMessage.data(),
+ kExpectedErrorMessage.size()),
+ 0);
+ EXPECT_EQ(message.bin, Pigweed::Protobuf::Binary::ZERO);
+ EXPECT_EQ(message.bungle, -111);
+}
+
+TEST(CodegenMessage, ReadNonPackedScalar) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // uint32s[], v={0, 16, 32, 48}
+ 0x08, 0x00,
+ 0x08, 0x10,
+ 0x08, 0x20,
+ 0x08, 0x30,
+ // fixed32s[]. v={0, 16, 32, 48}
+ 0x35, 0x00, 0x00, 0x00, 0x00,
+ 0x35, 0x10, 0x00, 0x00, 0x00,
+ 0x35, 0x20, 0x00, 0x00, 0x00,
+ 0x35, 0x30, 0x00, 0x00, 0x00,
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ RepeatedTest::StreamDecoder repeated_test(reader);
+
+ RepeatedTest::Message message{};
+ const auto status = repeated_test.Read(message);
+ ASSERT_EQ(status, OkStatus());
+
+ ASSERT_EQ(message.uint32s.size(), 4u);
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_EQ(message.uint32s[i], i * 16u);
+ }
+
+ ASSERT_EQ(message.fixed32s.size(), 4u);
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_EQ(message.fixed32s[i], i * 16u);
+ }
+}
+
+TEST(CodegenMessage, ReadPackedScalar) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // uint32s[], v={0, 16, 32, 48}
+ 0x0a, 0x04,
+ 0x00,
+ 0x10,
+ 0x20,
+ 0x30,
+ // fixed32s[]. v={0, 16, 32, 48}
+ 0x32, 0x10,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00,
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ RepeatedTest::StreamDecoder repeated_test(reader);
+
+ RepeatedTest::Message message{};
+ const auto status = repeated_test.Read(message);
+ ASSERT_EQ(status, OkStatus());
+
+ ASSERT_EQ(message.uint32s.size(), 4u);
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_EQ(message.uint32s[i], i * 16u);
+ }
+
+ ASSERT_EQ(message.fixed32s.size(), 4u);
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_EQ(message.fixed32s[i], i * 16u);
+ }
+}
+
+TEST(CodegenMessage, ReadPackedScalarRepeated) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // uint32s[], v={0, 16, 32, 48}
+ 0x0a, 0x04,
+ 0x00,
+ 0x10,
+ 0x20,
+ 0x30,
+ // uint32s[], v={64, 80, 96, 112}
+ 0x0a, 0x04,
+ 0x40,
+ 0x50,
+ 0x60,
+ 0x70,
+ // fixed32s[]. v={0, 16, 32, 48}
+ 0x32, 0x10,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00,
+ // fixed32s[]. v={64, 80, 96, 112}
+ 0x32, 0x10,
+ 0x40, 0x00, 0x00, 0x00,
+ 0x50, 0x00, 0x00, 0x00,
+ 0x60, 0x00, 0x00, 0x00,
+ 0x70, 0x00, 0x00, 0x00,
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ RepeatedTest::StreamDecoder repeated_test(reader);
+
+ RepeatedTest::Message message{};
+ const auto status = repeated_test.Read(message);
+ ASSERT_EQ(status, OkStatus());
+
+ ASSERT_EQ(message.uint32s.size(), 8u);
+ for (int i = 0; i < 8; ++i) {
+ EXPECT_EQ(message.uint32s[i], i * 16u);
+ }
+
+ ASSERT_EQ(message.fixed32s.size(), 8u);
+ for (int i = 0; i < 8; ++i) {
+ EXPECT_EQ(message.fixed32s[i], i * 16u);
+ }
+}
+
+TEST(CodegenMessage, ReadPackedScalarExhausted) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // uint32s[], v={0, 16, 32, 48, 64, 80, 96, 112, 128}
+ 0x0a, 0x09,
+ 0x00,
+ 0x10,
+ 0x20,
+ 0x30,
+ 0x40,
+ 0x50,
+ 0x60,
+ 0x70,
+ 0x80,
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ RepeatedTest::StreamDecoder repeated_test(reader);
+
+ // uint32s has max_size=8, so this will exhaust the vector.
+ RepeatedTest::Message message{};
+ const auto status = repeated_test.Read(message);
+ ASSERT_EQ(status, Status::ResourceExhausted());
+}
+
+TEST(CodegenMessage, ReadPackedScalarCallback) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // sint32s[], v={-25, -1, 0, 1, 25}
+ 0x12, 0x05,
+ 0x31,
+ 0x01,
+ 0x00,
+ 0x02,
+ 0x32,
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ RepeatedTest::StreamDecoder repeated_test(reader);
+
+ // sint32s is a repeated field declared without max_count, so requirses a
+ // callback to be decoded.
+ RepeatedTest::Message message{};
+ message.sint32s.SetDecoder([](RepeatedTest::StreamDecoder& decoder) {
+ EXPECT_EQ(decoder.Field().value(), RepeatedTest::Fields::kSint32s);
+
+ pw::Vector<int32_t, 8> sint32s{};
+ const auto status = decoder.ReadSint32s(sint32s);
+ EXPECT_EQ(status, OkStatus());
+
+ EXPECT_EQ(sint32s.size(), 5u);
+ EXPECT_EQ(sint32s[0], -25);
+ EXPECT_EQ(sint32s[1], -1);
+ EXPECT_EQ(sint32s[2], 0);
+ EXPECT_EQ(sint32s[3], 1);
+ EXPECT_EQ(sint32s[4], 25);
+
+ return status;
+ });
+
+ const auto status = repeated_test.Read(message);
+ ASSERT_EQ(status, OkStatus());
+}
+
+TEST(CodegenMessage, ReadPackedScalarFixedLength) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // uint64s[], v={1000, 2000, 3000, 4000}
+ 0x42, 0x08, 0xe8, 0x07, 0xd0, 0x0f, 0xb8, 0x17, 0xa0, 0x1f,
+ // doubles[], v={3.14159, 2.71828}
+ 0x22, 0x10,
+ 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
+ 0x90, 0xf7, 0xaa, 0x95, 0x09, 0xbf, 0x05, 0x40,
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ RepeatedTest::StreamDecoder repeated_test(reader);
+
+ RepeatedTest::Message message{};
+ const auto status = repeated_test.Read(message);
+ ASSERT_EQ(status, OkStatus());
+
+ EXPECT_EQ(message.uint64s[0], 1000u);
+ EXPECT_EQ(message.uint64s[1], 2000u);
+ EXPECT_EQ(message.uint64s[2], 3000u);
+ EXPECT_EQ(message.uint64s[3], 4000u);
+
+ EXPECT_EQ(message.doubles[0], 3.14159);
+ EXPECT_EQ(message.doubles[1], 2.71828);
+}
+
+TEST(CodegenMessage, ReadPackedScalarFixedLengthShort) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // uint64s[], v={1000, 2000}
+ 0x42, 0x04, 0xe8, 0x07, 0xd0, 0x0f,
+ // doubles[], v={3.14159}
+ 0x22, 0x08,
+ 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ RepeatedTest::StreamDecoder repeated_test(reader);
+
+ RepeatedTest::Message message{};
+ const auto status = repeated_test.Read(message);
+ ASSERT_EQ(status, OkStatus());
+
+ EXPECT_EQ(message.uint64s[0], 1000u);
+ EXPECT_EQ(message.uint64s[1], 2000u);
+ EXPECT_EQ(message.uint64s[2], 0u);
+ EXPECT_EQ(message.uint64s[3], 0u);
+
+ EXPECT_EQ(message.doubles[0], 3.14159);
+ EXPECT_EQ(message.doubles[1], 0);
+}
+
+TEST(CodegenMessage, ReadPackedScalarVarintFixedLengthExhausted) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // uint64s[], v={0, 1000, 2000, 3000, 4000}
+ 0x42, 0x09, 0x08, 0xe8, 0x07, 0xd0, 0x0f, 0xb8, 0x17, 0xa0, 0x1f,
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ RepeatedTest::StreamDecoder repeated_test(reader);
+
+ RepeatedTest::Message message{};
+ const auto status = repeated_test.Read(message);
+ ASSERT_EQ(status, Status::ResourceExhausted());
+}
+
+TEST(CodegenMessage, ReadPackedScalarFixedLengthExhausted) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // doubles[], v={3.14159, 2.71828, 1.41429, 1.73205}
+ 0x22, 0x20,
+ 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
+ 0x90, 0xf7, 0xaa, 0x95, 0x09, 0xbf, 0x05, 0x40,
+ 0x1b, 0xf5, 0x10, 0x8d, 0xee, 0xa0, 0xf6, 0x3f,
+ 0xbc, 0x96, 0x90, 0x0f, 0x7a, 0xb6, 0xfb, 0x3f,
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ RepeatedTest::StreamDecoder repeated_test(reader);
+
+ RepeatedTest::Message message{};
+ const auto status = repeated_test.Read(message);
+ ASSERT_EQ(status, Status::ResourceExhausted());
+}
+
+TEST(CodegenMessage, ReadPackedEnum) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // enums[], v={RED, GREEN, AMBER, RED}
+ 0x4a, 0x04, 0x00, 0x02, 0x01, 0x00,
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ RepeatedTest::StreamDecoder repeated_test(reader);
+
+ RepeatedTest::Message message{};
+ const auto status = repeated_test.Read(message);
+ ASSERT_EQ(status, OkStatus());
+
+ ASSERT_EQ(message.enums.size(), 4u);
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_TRUE(IsValidEnum(message.enums[i]));
+ }
+
+ EXPECT_EQ(message.enums[0], Enum::RED);
+ EXPECT_EQ(message.enums[1], Enum::GREEN);
+ EXPECT_EQ(message.enums[2], Enum::AMBER);
+ EXPECT_EQ(message.enums[3], Enum::RED);
+}
+
+TEST(CodegenMessage, ReadStringExhausted) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // pigweed.error_message
+ 0x2a, 0xd3, 0x01, 'T', 'h', 'i', 's', ' ', 'l', 'a', 'b', 'e', 'l', ' ', 'i',
+ 's', ' ', 't', 'h', 'e', ' ', 't', 'a', 'r', 'g', 'e', 't', ' ', 'o', 'f',
+ ' ', 'a', ' ', 'g', 'o', 't', 'o', ' ', 'f', 'r', 'o', 'm', ' ', 'o', 'u',
+ 't', 's', 'i', 'd', 'e', ' ', 'o', 'f', ' ', 't', 'h', 'e', ' ', 'b', 'l',
+ 'o', 'c', 'k', ' ', 'c', 'o', 'n', 't', 'a', 'i', 'n', 'i', 'n', 'g', ' ',
+ 't', 'h', 'i', 's', ' ', 'l', 'a', 'b', 'e', 'l', ' ', 'A', 'N', 'D', ' ',
+ 't', 'h', 'i', 's', ' ', 'b', 'l', 'o', 'c', 'k', ' ', 'h', 'a', 's', ' ',
+ 'a', 'n', ' ', 'a', 'u', 't', 'o', 'm', 'a', 't', 'i', 'c', ' ', 'v', 'a',
+ 'r', 'i', 'a', 'b', 'l', 'e', ' ', 'w', 'i', 't', 'h', ' ', 'a', 'n', ' ',
+ 'i', 'n', 'i', 't', 'i', 'a', 'l', 'i', 'z', 'e', 'r', ' ', 'A', 'N', 'D',
+ ' ', 'y', 'o', 'u', 'r', ' ', 'w', 'i', 'n', 'd', 'o', 'w', ' ', 'w', 'a',
+ 's', 'n', '\'', 't', ' ', 'w', 'i', 'd', 'e', ' ', 'e', 'n', 'o', 'u', 'g',
+ 'h', ' ', 't', 'o', ' ', 'r', 'e', 'a', 'd', ' ', 't', 'h', 'i', 's', ' ',
+ 'w', 'h', 'o', 'l', 'e', ' ', 'e', 'r', 'r', 'o', 'r', ' ', 'm', 'e', 's',
+ 's', 'a', 'g', 'e'
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ Pigweed::StreamDecoder pigweed(reader);
+
+ Pigweed::Message message{};
+ const auto status = pigweed.Read(message);
+ ASSERT_EQ(status, Status::ResourceExhausted());
+}
+
+TEST(CodegenMessage, ReadStringCallback) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // pigweed.description
+ 0x62, 0x5c, 'a', 'n', ' ', 'o', 'p', 'e', 'n', ' ', 's', 'o', 'u', 'r', 'c',
+ 'e', ' ', 'c', 'o', 'l', 'l', 'e', 'c', 't', 'i', 'o', 'n', ' ', 'o', 'f',
+ ' ', 'e', 'm', 'b', 'e', 'd', 'd', 'e', 'd', '-', 't', 'a', 'r', 'g', 'e',
+ 't', 'e', 'd', ' ', 'l', 'i', 'b', 'r', 'a', 'r', 'i', 'e', 's', '-', 'o',
+ 'r', ' ', 'a', 's', ' ', 'w', 'e', ' ', 'l', 'i', 'k', 'e', ' ', 't', 'o',
+ ' ', 'c', 'a', 'l', 'l', ' ', 't', 'h', 'e', 'm', ',', ' ', 'm', 'o', 'd',
+ 'u', 'l', 'e', 's'
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ Pigweed::StreamDecoder pigweed(reader);
+
+ // pigweed.description has no max_size specified so a callback must be
+ // set to read the value if present.
+ Pigweed::Message message{};
+ message.description.SetDecoder([](Pigweed::StreamDecoder& decoder) {
+ EXPECT_EQ(decoder.Field().value(), Pigweed::Fields::kDescription);
+
+ constexpr std::string_view kExpectedDescription{
+ "an open source collection of embedded-targeted libraries-or as we "
+ "like to call them, modules"};
+
+ std::array<char, 128> description{};
+ const auto sws = decoder.ReadDescription(description);
+ EXPECT_EQ(sws.status(), OkStatus());
+ EXPECT_EQ(sws.size(), kExpectedDescription.size());
+ EXPECT_EQ(std::memcmp(description.data(),
+ kExpectedDescription.data(),
+ kExpectedDescription.size()),
+ 0);
+
+ return sws.status();
+ });
+
+ const auto status = pigweed.Read(message);
+ ASSERT_EQ(status, OkStatus());
+}
+
+TEST(CodegenMessage, ReadMultipleString) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // pigweed.error_message
+ 0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ',
+ 't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r',
+ // pigweed.error_message
+ 0x02a, 0x07, 'o', 'n', ' ', 'f', 'i', 'r', 'e'
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ Pigweed::StreamDecoder pigweed(reader);
+
+ Pigweed::Message message{};
+ const auto status = pigweed.Read(message);
+ ASSERT_EQ(status, OkStatus());
+
+ constexpr std::string_view kExpectedErrorMessage{"on fire"};
+
+ EXPECT_EQ(message.error_message.size(), kExpectedErrorMessage.size());
+ EXPECT_EQ(std::memcmp(message.error_message.data(),
+ kExpectedErrorMessage.data(),
+ kExpectedErrorMessage.size()),
+ 0);
+}
+
+TEST(CodegenMessage, ReadRepeatedStrings) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // repeated.strings
+ 0x1a, 0x25, 'i', 'f', ' ', 'm', 'u', 's', 'i', 'c', ' ', 'b', 'e', ' ',
+ 't', 'h', 'e', ' ', 'f', 'o', 'o', 'd', ' ', 'o', 'f', ' ',
+ 'l', 'o', 'v', 'e', ',', ' ', 'p', 'l', 'a', 'y', ' ', 'o', 'n',
+ // repeated.strings
+ 0x1a, 0x26, 'g', 'i', 'v', 'e', ' ', 'm', 'e', ' ', 'e', 'x', 'c', 'e',
+ 's', 's', ' ', 'o', 'f', ' ', 'i', 't', ',', ' ', 't', 'h', 'a', 't', ',',
+ ' ', 's', 'u', 'r', 'f', 'e', 'i', 't', 'i', 'n', 'g',
+ // repeated.strings
+ 0x1a, 0x23, 't', 'h', 'e', ' ', 'a', 'p', 'p', 'e', 't', 'i', 't', 'e', ' ',
+ 'm', 'a', 'y', ' ', 's', 'i', 'c', 'k', 'e', 'n', ',', ' ', 'a', 'n', 'd',
+ ' ', 's', 'o', ' ', 'd', 'i', 'e',
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ RepeatedTest::StreamDecoder repeated_test(reader);
+
+ // Repeated strings require a callback to avoid forcing multi-dimensional
+ // arrays upon the caller.
+ RepeatedTest::Message message{};
+ int i = 0;
+ message.strings.SetDecoder([&i](RepeatedTest::StreamDecoder& decoder) {
+ EXPECT_EQ(decoder.Field().value(), RepeatedTest::Fields::kStrings);
+
+ constexpr std::string_view kExpectedStrings[] = {
+ {"if music be the food of love, play on"},
+ {"give me excess of it, that, surfeiting"},
+ {"the appetite may sicken, and so die"}};
+
+ std::array<char, 40> strings{};
+ const StatusWithSize sws = decoder.ReadStrings(strings);
+ EXPECT_EQ(sws.status(), OkStatus());
+ EXPECT_EQ(sws.size(), kExpectedStrings[i].size());
+ EXPECT_EQ(std::memcmp(strings.data(),
+ kExpectedStrings[i].data(),
+ kExpectedStrings[i].size()),
+ 0);
+
+ ++i;
+ return sws.status();
+ });
+
+ const auto status = repeated_test.Read(message);
+ ASSERT_EQ(status, OkStatus());
+}
+
+TEST(CodegenMessage, ReadForcedCallback) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // pigweed.special_property
+ 0x68, 0x2a,
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ Pigweed::StreamDecoder pigweed(reader);
+
+ // pigweed.special_property has use_callback=true to force the use of a
+ // callback even though it's a simple scalar.
+ Pigweed::Message message{};
+ message.special_property.SetDecoder([](Pigweed::StreamDecoder& decoder) {
+ EXPECT_EQ(decoder.Field().value(), Pigweed::Fields::kSpecialProperty);
+
+ pw::Result<uint32_t> result = decoder.ReadSpecialProperty();
+ EXPECT_EQ(result.status(), OkStatus());
+ EXPECT_EQ(result.value(), 42u);
+
+ return result.status();
+ });
+ const auto status = pigweed.Read(message);
+ ASSERT_EQ(status, OkStatus());
+}
+
+TEST(CodegenMessage, ReadMissingCallback) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // repeated.strings
+ 0x1a, 0x25, 'i', 'f', ' ', 'm', 'u', 's', 'i', 'c', ' ', 'b', 'e', ' ',
+ 't', 'h', 'e', ' ', 'f', 'o', 'o', 'd', ' ', 'o', 'f', ' ',
+ 'l', 'o', 'v', 'e', ',', ' ', 'p', 'l', 'a', 'y', ' ', 'o', 'n',
+ // repeated.strings
+ 0x1a, 0x26, 'g', 'i', 'v', 'e', ' ', 'm', 'e', ' ', 'e', 'x', 'c', 'e',
+ 's', 's', ' ', 'o', 'f', ' ', 'i', 't', ',', ' ', 't', 'h', 'a', 't', ',',
+ ' ', 's', 'u', 'r', 'f', 'e', 'i', 't', 'i', 'n', 'g',
+ // repeated.strings
+ 0x1a, 0x23, 't', 'h', 'e', ' ', 'a', 'p', 'p', 'e', 't', 'i', 't', 'e', ' ',
+ 'm', 'a', 'y', ' ', 's', 'i', 'c', 'k', 'e', 'n', ',', ' ', 'a', 'n', 'd',
+ ' ', 's', 'o', ' ', 'd', 'i', 'e',
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ RepeatedTest::StreamDecoder repeated_test(reader);
+
+ // Failing to set a callback will give a DataLoss error if that field is
+ // present in the decoded data.
+ RepeatedTest::Message message{};
+ const auto status = repeated_test.Read(message);
+ ASSERT_EQ(status, Status::DataLoss());
+}
+
+TEST(CodegenMessage, ReadFixedLength) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // pigweed.data
+ 0x5a, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ Pigweed::StreamDecoder pigweed(reader);
+
+ Pigweed::Message message{};
+ const auto status = pigweed.Read(message);
+ ASSERT_EQ(status, OkStatus());
+
+ EXPECT_EQ(message.data[0], std::byte{0x01});
+ EXPECT_EQ(message.data[1], std::byte{0x02});
+ EXPECT_EQ(message.data[2], std::byte{0x03});
+ EXPECT_EQ(message.data[3], std::byte{0x04});
+ EXPECT_EQ(message.data[4], std::byte{0x05});
+ EXPECT_EQ(message.data[5], std::byte{0x06});
+ EXPECT_EQ(message.data[6], std::byte{0x07});
+ EXPECT_EQ(message.data[7], std::byte{0x08});
+}
+
+TEST(CodegenMessage, ReadFixedLengthShort) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // pigweed.data
+ 0x5a, 0x04, 0x01, 0x02, 0x03, 0x04
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ Pigweed::StreamDecoder pigweed(reader);
+
+ Pigweed::Message message{};
+ const auto status = pigweed.Read(message);
+ ASSERT_EQ(status, OkStatus());
+
+ EXPECT_EQ(message.data[0], std::byte{0x01});
+ EXPECT_EQ(message.data[1], std::byte{0x02});
+ EXPECT_EQ(message.data[2], std::byte{0x03});
+ EXPECT_EQ(message.data[3], std::byte{0x04});
+ // Remaining bytes are whatever you initialized them to.
+ EXPECT_EQ(message.data[4], std::byte{0x00});
+ EXPECT_EQ(message.data[5], std::byte{0x00});
+ EXPECT_EQ(message.data[6], std::byte{0x00});
+ EXPECT_EQ(message.data[7], std::byte{0x00});
+}
+
+TEST(CodegenMessage, ReadFixedLengthExhausted) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // pigweed.data
+ 0x5a, 0x0c, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0a, 0x0b, 0x0c
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ Pigweed::StreamDecoder pigweed(reader);
+
+ Pigweed::Message message{};
+ const auto status = pigweed.Read(message);
+ ASSERT_EQ(status, Status::ResourceExhausted());
+}
+
+TEST(CodegenMessage, ReadNested) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // pigweed.magic_number
+ 0x08, 0x49,
+ // pigweed.pigweed
+ 0x3a, 0x02,
+ // pigweed.pigweed.status
+ 0x08, 0x02,
+ // pigweed.ziggy
+ 0x10, 0xdd, 0x01,
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ Pigweed::StreamDecoder pigweed(reader);
+
+ Pigweed::Message message{};
+ const auto status = pigweed.Read(message);
+ ASSERT_EQ(status, OkStatus());
+
+ EXPECT_EQ(message.magic_number, 0x49u);
+ EXPECT_EQ(message.pigweed.status, Bool::FILE_NOT_FOUND);
+ EXPECT_EQ(message.ziggy, -111);
+}
+
+TEST(CodegenMessage, ReadNestedImported) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // period.start
+ 0x0a, 0x08,
+ // period.start.seconds v=1517949900
+ 0x08, 0xcc, 0xa7, 0xe8, 0xd3, 0x05,
+ // period.start.nanoseconds v=0
+ 0x10, 0x00,
+ // period.end
+ 0x12, 0x08,
+ // period.end.seconds, v=1517950378
+ 0x08, 0xaa, 0xab, 0xe8, 0xd3, 0x05,
+ // period.end.nanoseconds, v=0
+ 0x10, 0x00,
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ Period::StreamDecoder period(reader);
+
+ // Messages imported from another file can be directly embedded in a message.
+ Period::Message message{};
+ const auto status = period.Read(message);
+ ASSERT_EQ(status, OkStatus());
+
+ EXPECT_EQ(message.start.seconds, 1517949900u);
+ EXPECT_EQ(message.start.nanoseconds, 0u);
+ EXPECT_EQ(message.end.seconds, 1517950378u);
+ EXPECT_EQ(message.end.nanoseconds, 0u);
+}
+
+TEST(CodegenMessage, ReadNestedRepeated) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // repeated.structs
+ 0x2a, 0x04,
+ // repeated.structs.one v=16
+ 0x08, 0x10,
+ // repeated.structs.two v=32
+ 0x10, 0x20,
+ // repeated.structs
+ 0x2a, 0x04,
+ // repeated.structs.one v=48
+ 0x08, 0x30,
+ // repeated.structs.two v=64
+ 0x10, 0x40,
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ RepeatedTest::StreamDecoder repeated_test(reader);
+
+ // Repeated nested messages require a callback since there would otherwise be
+ // no way to set callbacks on the nested message.
+ RepeatedTest::Message message{};
+ int i = 0;
+ message.structs.SetDecoder([&i](RepeatedTest::StreamDecoder& decoder) {
+ EXPECT_EQ(decoder.Field().value(), RepeatedTest::Fields::kStructs);
+
+ Struct::Message structs_message{};
+ auto structs_decoder = decoder.GetStructsDecoder();
+ const auto status = structs_decoder.Read(structs_message);
+ EXPECT_EQ(status, OkStatus());
+
+ EXPECT_LT(i, 2);
+ EXPECT_EQ(structs_message.one, i * 32 + 16u);
+ EXPECT_EQ(structs_message.two, i * 32 + 32u);
+ ++i;
+
+ return status;
+ });
+
+ const auto status = repeated_test.Read(message);
+ ASSERT_EQ(status, OkStatus());
+}
+
+TEST(CodegenMessage, ReadNestedForcedCallback) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // pigweed.device_info
+ 0x32, 0x0e,
+ // pigweed.device_info.device_name
+ 0x0a, 0x05, 'p', 'i', 'x', 'e', 'l',
+ // pigweed.device_info.device_id
+ 0x15, 0x08, 0x08, 0x08, 0x08,
+ // pigweed.device_info.status
+ 0x18, 0x00,
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ Pigweed::StreamDecoder pigweed(reader);
+
+ // pigweed.device_info has use_callback=true to force the use of a callback.
+ Pigweed::Message message{};
+ message.device_info.SetDecoder([](Pigweed::StreamDecoder& decoder) {
+ EXPECT_EQ(decoder.Field().value(), Pigweed::Fields::kDeviceInfo);
+
+ DeviceInfo::Message device_info{};
+ DeviceInfo::StreamDecoder device_info_decoder =
+ decoder.GetDeviceInfoDecoder();
+ const auto status = device_info_decoder.Read(device_info);
+ EXPECT_EQ(status, OkStatus());
+
+ constexpr std::string_view kExpectedDeviceName{"pixel"};
+
+ EXPECT_EQ(device_info.device_name.size(), kExpectedDeviceName.size());
+ EXPECT_EQ(std::memcmp(device_info.device_name.data(),
+ kExpectedDeviceName.data(),
+ kExpectedDeviceName.size()),
+ 0);
+ EXPECT_EQ(device_info.device_id, 0x08080808u);
+ EXPECT_EQ(device_info.status, DeviceInfo::DeviceStatus::OK);
+
+ return status;
+ });
+ const auto status = pigweed.Read(message);
+ ASSERT_EQ(status, OkStatus());
+}
+
+TEST(CodegenMessage, ReadOptionalPresent) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // optional.sometimes_present_fixed
+ 0x0d, 0x2a, 0x00, 0x00, 0x00,
+ // optional.sometimes_present_varint
+ 0x10, 0x2a,
+ // optional.explicitly_present_fixed
+ 0x1d, 0x45, 0x00, 0x00, 0x00,
+ // optional.explicitly_present_varint
+ 0x20, 0x45,
+ // optional.sometimes_empty_fixed
+ 0x2a, 0x04, 0x63, 0x00, 0x00, 0x00,
+ // optional.sometimes_empty_varint
+ 0x32, 0x01, 0x63,
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ OptionalTest::StreamDecoder optional_test(reader);
+
+ OptionalTest::Message message{};
+ const auto status = optional_test.Read(message);
+ ASSERT_EQ(status, OkStatus());
+
+ EXPECT_EQ(message.sometimes_present_fixed, 0x2a);
+ EXPECT_EQ(message.sometimes_present_varint, 0x2a);
+ EXPECT_TRUE(message.explicitly_present_fixed);
+ EXPECT_EQ(*message.explicitly_present_fixed, 0x45);
+ EXPECT_TRUE(message.explicitly_present_varint);
+ EXPECT_EQ(*message.explicitly_present_varint, 0x45);
+ EXPECT_FALSE(message.sometimes_empty_fixed.empty());
+ EXPECT_EQ(message.sometimes_empty_fixed.size(), 1u);
+ EXPECT_EQ(message.sometimes_empty_fixed[0], 0x63);
+ EXPECT_FALSE(message.sometimes_empty_varint.empty());
+ EXPECT_EQ(message.sometimes_empty_varint.size(), 1u);
+ EXPECT_EQ(message.sometimes_empty_varint[0], 0x63);
+}
+
+TEST(CodegenMessage, ReadOptionalNotPresent) {
+ constexpr std::array<std::byte, 0> proto_data{};
+
+ stream::MemoryReader reader(proto_data);
+ OptionalTest::StreamDecoder optional_test(reader);
+
+ OptionalTest::Message message{};
+ const auto status = optional_test.Read(message);
+ ASSERT_EQ(status, OkStatus());
+
+ // Non-optional fields have their default value.
+ EXPECT_EQ(message.sometimes_present_fixed, 0);
+ EXPECT_EQ(message.sometimes_present_varint, 0);
+ EXPECT_TRUE(message.sometimes_empty_fixed.empty());
+ EXPECT_TRUE(message.sometimes_empty_varint.empty());
+
+ // Optional fields are explicitly not present.
+ EXPECT_FALSE(message.explicitly_present_fixed);
+ EXPECT_FALSE(message.explicitly_present_varint);
+}
+
+TEST(CodegenMessage, ReadOptionalPresentDefaults) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // optional.sometimes_present_fixed
+ 0x0d, 0x00, 0x00, 0x00, 0x00,
+ // optional.sometimes_present_varint
+ 0x10, 0x00,
+ // optional.explicitly_present_fixed
+ 0x1d, 0x00, 0x00, 0x00, 0x00,
+ // optional.explicitly_present_varint
+ 0x20, 0x00,
+ // optional.sometimes_empty_fixed
+ 0x2a, 0x04, 0x00, 0x00, 0x00, 0x00,
+ // optional.sometimes_empty_varint
+ 0x32, 0x01, 0x00,
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ OptionalTest::StreamDecoder optional_test(reader);
+
+ OptionalTest::Message message{};
+ const auto status = optional_test.Read(message);
+ ASSERT_EQ(status, OkStatus());
+
+ // Non-optional fields have their default value and aren't meaningfully
+ // different from missing.
+ EXPECT_EQ(message.sometimes_present_fixed, 0x00);
+ EXPECT_EQ(message.sometimes_present_varint, 0x00);
+
+ // Optional fields are explicitly present with a default value.
+ EXPECT_TRUE(message.explicitly_present_fixed);
+ EXPECT_EQ(*message.explicitly_present_fixed, 0x00);
+ EXPECT_TRUE(message.explicitly_present_varint);
+ EXPECT_EQ(*message.explicitly_present_varint, 0x00);
+
+ // Repeated fields with a default value are meaningfully non-empty.
+ EXPECT_FALSE(message.sometimes_empty_fixed.empty());
+ EXPECT_EQ(message.sometimes_empty_fixed.size(), 1u);
+ EXPECT_EQ(message.sometimes_empty_fixed[0], 0x00);
+ EXPECT_FALSE(message.sometimes_empty_varint.empty());
+ EXPECT_EQ(message.sometimes_empty_varint.size(), 1u);
+ EXPECT_EQ(message.sometimes_empty_varint[0], 0x00);
+}
+
+TEST(CodegenMessage, ReadImportedOptions) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // notice
+ 0x0a, 0x0f,
+ // notice.message
+ 0x0a, 0x0d, 'P', 'r', 'e', 's', 's', ' ', 'a', 'n', 'y', ' ', 'k', 'e', 'y'
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ TestMessage::StreamDecoder test_message(reader);
+
+ // The options file for the imported proto is applied, making the string
+ // field a vector rather than requiring a callback.
+ TestMessage::Message message{};
+ const auto status = test_message.Read(message);
+ ASSERT_EQ(status, OkStatus());
+
+ constexpr std::string_view kExpectedMessage{"Press any key"};
+
+ EXPECT_EQ(message.notice.message.size(), kExpectedMessage.size());
+ EXPECT_EQ(std::memcmp(message.notice.message.data(),
+ kExpectedMessage.data(),
+ kExpectedMessage.size()),
+ 0);
+}
+
+TEST(CodegenMessage, ReadImportedFromDepsOptions) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // debug
+ 0x12, 0x0f,
+ // debug.message
+ 0x0a, 0x0d, 'P', 'r', 'e', 's', 's', ' ', 'a', 'n', 'y', ' ', 'k', 'e', 'y'
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ TestMessage::StreamDecoder test_message(reader);
+
+ // The options file for the imported proto is applied, making the string
+ // field a vector rather than requiring a callback.
+ TestMessage::Message message{};
+ const auto status = test_message.Read(message);
+ ASSERT_EQ(status, OkStatus());
+
+ constexpr std::string_view kExpectedMessage{"Press any key"};
+
+ EXPECT_EQ(message.debug.message.size(), kExpectedMessage.size());
+ EXPECT_EQ(std::memcmp(message.debug.message.data(),
+ kExpectedMessage.data(),
+ kExpectedMessage.size()),
+ 0);
+}
+
+class BreakableDecoder : public KeyValuePair::StreamDecoder {
+ public:
+ constexpr BreakableDecoder(stream::Reader& reader) : StreamDecoder(reader) {}
+
+ Status Read(KeyValuePair::Message& message,
+ span<const internal::MessageField> table) {
+ return ::pw::protobuf::StreamDecoder::Read(
+ as_writable_bytes(span(&message, 1)), table);
+ }
+};
+
+TEST(CodegenMessage, DISABLED_ReadDoesNotOverrun) {
+ // Deliberately construct a message table that attempts to violate the bounds
+ // of the structure. We're not testing that a developer can't do this, rather
+ // that the protobuf decoder can't be exploited in this way.
+ constexpr internal::MessageField kMessageFields[] = {
+ {1,
+ WireType::kDelimited,
+ sizeof(std::byte),
+ static_cast<internal::VarintType>(0),
+ false,
+ false,
+ false,
+ false,
+ false,
+ 0,
+ sizeof(KeyValuePair::Message) * 2,
+ {}},
+ };
+
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // id=1, len=9,
+ 0x0a, 0x08, 'd', 'o', 'n', 't', 'e', 'a', 't', 'm', 'e',
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ BreakableDecoder decoder(reader);
+
+ KeyValuePair::Message message{};
+ // ASSERT_CRASH
+ std::ignore = decoder.Read(message, kMessageFields);
+}
+
+TEST(CodegenMessage, Write) {
+ constexpr uint8_t pigweed_data[] = {
+ 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80};
+
+ Pigweed::Message message{};
+ message.magic_number = 0x49u;
+ message.ziggy = -111;
+ message.cycles = 0x40302010fecaaddeu;
+ message.ratio = -1.42f;
+ message.error_message = "not a typewriter";
+ message.pigweed.status = Bool::FILE_NOT_FOUND;
+ message.bin = Pigweed::Protobuf::Binary::ZERO;
+ message.bungle = -111;
+ message.proto.bin = Proto::Binary::OFF;
+ message.proto.pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO;
+ message.proto.pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ZERO;
+ message.proto.meta.file_name = "/etc/passwd";
+ message.proto.meta.status = Pigweed::Protobuf::Compiler::Status::FUBAR;
+ message.proto.meta.protobuf_bin = Pigweed::Protobuf::Binary::ONE;
+ message.proto.meta.pigweed_bin = Pigweed::Pigweed::Binary::ONE;
+ std::memcpy(message.data.data(), pigweed_data, sizeof(pigweed_data));
+
+ std::byte encode_buffer[Pigweed::kMaxEncodedSizeBytes];
+ std::byte temp_buffer[Pigweed::kScratchBufferSizeBytes];
+
+ stream::MemoryWriter writer(encode_buffer);
+ Pigweed::StreamEncoder pigweed(writer, temp_buffer);
+
+ const auto status = pigweed.Write(message);
+ ASSERT_EQ(status, OkStatus());
+
+ // clang-format off
+ constexpr uint8_t expected_proto[] = {
+ // pigweed.magic_number
+ 0x08, 0x49,
+ // pigweed.ziggy
+ 0x10, 0xdd, 0x01,
+ // pigweed.cycles
+ 0x19, 0xde, 0xad, 0xca, 0xfe, 0x10, 0x20, 0x30, 0x40,
+ // pigweed.ratio
+ 0x25, 0x8f, 0xc2, 0xb5, 0xbf,
+ // pigweed.error_message
+ 0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ',
+ 't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r',
+ // pigweed.pigweed
+ 0x3a, 0x02,
+ // pigweed.pigweed.status
+ 0x08, 0x02,
+ // pigweed.bin
+ 0x40, 0x01,
+ // pigweed.proto
+ 0x4a, 0x15,
+ // pigweed.proto.pigweed_protobuf_bin
+ 0x20, 0x01,
+ // pigweed.proto.meta
+ 0x2a, 0x11,
+ // pigweed.proto.meta.file_name
+ 0x0a, 0x0b, '/', 'e', 't', 'c', '/', 'p', 'a', 's', 's', 'w', 'd',
+ // pigweed.proto.meta.status
+ 0x10, 0x02,
+ // pigweed.proto.meta.pigweed_bin
+ 0x20, 0x01,
+ // pigweed.bytes
+ 0x5a, 0x08, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80,
+ // pigweed.bungle
+ 0x70, 0x91, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01,
+ };
+ // clang-format on
+
+ ConstByteSpan result = writer.WrittenData();
+ EXPECT_EQ(result.size(), sizeof(expected_proto));
+ EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
+ 0);
+}
+
+TEST(CodegenMessage, WriteDefaults) {
+ Pigweed::Message message{};
+
+ std::byte encode_buffer[Pigweed::kMaxEncodedSizeBytes];
+ std::byte temp_buffer[Pigweed::kScratchBufferSizeBytes];
+
+ stream::MemoryWriter writer(encode_buffer);
+ Pigweed::StreamEncoder pigweed(writer, temp_buffer);
+
+ const auto status = pigweed.Write(message);
+ ASSERT_EQ(status, OkStatus());
+
+ // Since all fields are at their default, the output should be zero sized.
+ ConstByteSpan result = writer.WrittenData();
+ EXPECT_EQ(result.size(), 0u);
+}
+
+TEST(CodegenMessage, WritePackedScalar) {
+ RepeatedTest::Message message{};
+ for (int i = 0; i < 4; ++i) {
+ message.uint32s.push_back(i * 16u);
+ message.fixed32s.push_back(i * 16u);
+ }
+
+ std::byte encode_buffer[RepeatedTest::kMaxEncodedSizeBytes];
+
+ stream::MemoryWriter writer(encode_buffer);
+ RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());
+
+ const auto status = repeated_test.Write(message);
+ ASSERT_EQ(status, OkStatus());
+
+ // clang-format off
+ constexpr uint8_t expected_proto[] = {
+ // uint32s[], v={0, 16, 32, 48}
+ 0x0a, 0x04,
+ 0x00,
+ 0x10,
+ 0x20,
+ 0x30,
+ // fixed32s[]. v={0, 16, 32, 48}
+ 0x32, 0x10,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00,
+ };
+ // clang-format on
+
+ ConstByteSpan result = writer.WrittenData();
+ EXPECT_EQ(result.size(), sizeof(expected_proto));
+ EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
+ 0);
+}
+
+TEST(CodegenMessage, WritePackedScalarFixedLength) {
+ RepeatedTest::Message message{};
+ for (int i = 0; i < 4; ++i) {
+ message.uint64s[i] = (i + 1) * 1000u;
+ }
+ message.doubles[0] = 3.14159;
+ message.doubles[1] = 2.71828;
+
+ std::byte encode_buffer[RepeatedTest::kMaxEncodedSizeBytes];
+
+ stream::MemoryWriter writer(encode_buffer);
+ RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());
+
+ const auto status = repeated_test.Write(message);
+ ASSERT_EQ(status, OkStatus());
+
+ // clang-format off
+ constexpr uint8_t expected_proto[] = {
+ // doubles[], v={3.14159, 2.71828}
+ 0x22, 0x10,
+ 0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
+ 0x90, 0xf7, 0xaa, 0x95, 0x09, 0xbf, 0x05, 0x40,
+ // uint64s[], v={1000, 2000, 3000, 4000}
+ 0x42, 0x08, 0xe8, 0x07, 0xd0, 0x0f, 0xb8, 0x17, 0xa0, 0x1f,
+ };
+ // clang-format on
+
+ ConstByteSpan result = writer.WrittenData();
+ EXPECT_EQ(result.size(), sizeof(expected_proto));
+ EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
+ 0);
+}
+
+TEST(CodegenMessage, WritePackedScalarCallback) {
+ RepeatedTest::Message message{};
+ message.sint32s.SetEncoder([](RepeatedTest::StreamEncoder& encoder) {
+ constexpr int32_t sint32s[] = {-25, -1, 0, 1, 25};
+ return encoder.WriteSint32s(sint32s);
+ });
+
+ std::byte encode_buffer[RepeatedTest::kMaxEncodedSizeBytes +
+ varint::kMaxVarint32SizeBytes * 5];
+
+ stream::MemoryWriter writer(encode_buffer);
+ RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());
+
+ const auto status = repeated_test.Write(message);
+ ASSERT_EQ(status, OkStatus());
+
+ // clang-format off
+ constexpr uint8_t expected_proto[] = {
+ // sint32s[], v={-25, -1, 0, 1, 25}
+ 0x12, 0x05,
+ 0x31,
+ 0x01,
+ 0x00,
+ 0x02,
+ 0x32,
+ };
+ // clang-format on
+
+ ConstByteSpan result = writer.WrittenData();
+ EXPECT_EQ(result.size(), sizeof(expected_proto));
+ EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
+ 0);
+}
+
+TEST(CodegenMessage, WritePackedEnum) {
+ RepeatedTest::Message message{};
+ message.enums.push_back(Enum::RED);
+ message.enums.push_back(Enum::GREEN);
+ message.enums.push_back(Enum::AMBER);
+ message.enums.push_back(Enum::RED);
+
+ std::byte encode_buffer[RepeatedTest::kMaxEncodedSizeBytes];
+
+ stream::MemoryWriter writer(encode_buffer);
+ RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());
+
+ const auto status = repeated_test.Write(message);
+ ASSERT_EQ(status, OkStatus());
+
+ // clang-format off
+ constexpr uint8_t expected_proto[] = {
+ // enums[], v={RED, GREEN, AMBER, RED}
+ 0x4a, 0x04, 0x00, 0x02, 0x01, 0x00,
+ };
+ // clang-format on
+
+ ConstByteSpan result = writer.WrittenData();
+ EXPECT_EQ(result.size(), sizeof(expected_proto));
+ EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
+ 0);
+}
+
+TEST(CodegenMessage, WriteStringCallback) {
+ Pigweed::Message message{};
+ // pigweed.description has no max_size specified so a callback must be
+ // set to write the value.
+ message.description.SetEncoder([](Pigweed::StreamEncoder& encoder) {
+ return encoder.WriteDescription(
+ "an open source collection of embedded-targeted "
+ "libraries-or as we like to call them, modules");
+ });
+
+ std::byte encode_buffer[Pigweed::kMaxEncodedSizeBytes + 92];
+ std::byte temp_buffer[Pigweed::kScratchBufferSizeBytes];
+
+ stream::MemoryWriter writer(encode_buffer);
+ Pigweed::StreamEncoder pigweed(writer, temp_buffer);
+
+ const auto status = pigweed.Write(message);
+ ASSERT_EQ(status, OkStatus());
+
+ // clang-format off
+ constexpr uint8_t expected_proto[] = {
+ // pigweed.description
+ 0x62, 0x5c, 'a', 'n', ' ', 'o', 'p', 'e', 'n', ' ', 's', 'o', 'u', 'r', 'c',
+ 'e', ' ', 'c', 'o', 'l', 'l', 'e', 'c', 't', 'i', 'o', 'n', ' ', 'o', 'f',
+ ' ', 'e', 'm', 'b', 'e', 'd', 'd', 'e', 'd', '-', 't', 'a', 'r', 'g', 'e',
+ 't', 'e', 'd', ' ', 'l', 'i', 'b', 'r', 'a', 'r', 'i', 'e', 's', '-', 'o',
+ 'r', ' ', 'a', 's', ' ', 'w', 'e', ' ', 'l', 'i', 'k', 'e', ' ', 't', 'o',
+ ' ', 'c', 'a', 'l', 'l', ' ', 't', 'h', 'e', 'm', ',', ' ', 'm', 'o', 'd',
+ 'u', 'l', 'e', 's',
+ };
+ // clang-format on
+
+ ConstByteSpan result = writer.WrittenData();
+ EXPECT_EQ(result.size(), sizeof(expected_proto));
+ EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
+ 0);
+}
+
+TEST(CodegenMessage, WriteForcedCallback) {
+ Pigweed::Message message{};
+ // pigweed.special_property has use_callback=true to force the use of a
+ // callback even though it's a simple scalar.
+ message.special_property.SetEncoder([](Pigweed::StreamEncoder& encoder) {
+ return encoder.WriteSpecialProperty(42u);
+ });
+
+ std::byte encode_buffer[Pigweed::kMaxEncodedSizeBytes];
+ std::byte temp_buffer[Pigweed::kScratchBufferSizeBytes];
+
+ stream::MemoryWriter writer(encode_buffer);
+ Pigweed::StreamEncoder pigweed(writer, temp_buffer);
+
+ const auto status = pigweed.Write(message);
+ ASSERT_EQ(status, OkStatus());
+
+ // clang-format off
+ constexpr uint8_t expected_proto[] = {
+ // pigweed.special_property
+ 0x68, 0x2a,
+ };
+ // clang-format on
+
+ ConstByteSpan result = writer.WrittenData();
+ EXPECT_EQ(result.size(), sizeof(expected_proto));
+ EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
+ 0);
+}
+
+TEST(CodegenMessage, WriteNestedImported) {
+ Period::Message message{};
+ message.start.seconds = 1517949900u;
+ message.end.seconds = 1517950378u;
+
+ std::byte encode_buffer[Period::kMaxEncodedSizeBytes];
+ std::byte temp_buffer[Period::kScratchBufferSizeBytes];
+
+ stream::MemoryWriter writer(encode_buffer);
+ Period::StreamEncoder period(writer, temp_buffer);
+
+ const auto status = period.Write(message);
+ ASSERT_EQ(status, OkStatus());
+
+ // clang-format off
+ constexpr uint8_t expected_proto[] = {
+ // period.start
+ 0x0a, 0x06,
+ // period.start.seconds v=1517949900
+ 0x08, 0xcc, 0xa7, 0xe8, 0xd3, 0x05,
+ // period.end
+ 0x12, 0x06,
+ // period.end.seconds, v=1517950378
+ 0x08, 0xaa, 0xab, 0xe8, 0xd3, 0x05,
+ };
+ // clang-format on
+
+ ConstByteSpan result = writer.WrittenData();
+ EXPECT_EQ(result.size(), sizeof(expected_proto));
+ EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
+ 0);
+}
+
+TEST(CodegenMessage, WriteNestedRepeated) {
+ RepeatedTest::Message message{};
+ // Repeated nested messages require a callback since there would otherwise be
+ // no way to set callbacks on the nested message.
+ message.structs.SetEncoder([](RepeatedTest::StreamEncoder& encoder) {
+ for (int i = 0; i < 2; ++i) {
+ Struct::Message struct_message{};
+ struct_message.one = i * 32 + 16u;
+ struct_message.two = i * 32 + 32u;
+
+ const auto status = encoder.GetStructsEncoder().Write(struct_message);
+ EXPECT_EQ(status, OkStatus());
+ }
+ return OkStatus();
+ });
+
+ std::byte encode_buffer[RepeatedTest::kMaxEncodedSizeBytes +
+ Struct::kMaxEncodedSizeBytes * 2];
+ std::byte temp_buffer[RepeatedTest::kScratchBufferSizeBytes +
+ Struct::kMaxEncodedSizeBytes];
+
+ stream::MemoryWriter writer(encode_buffer);
+ RepeatedTest::StreamEncoder repeated_test(writer, temp_buffer);
+
+ const auto status = repeated_test.Write(message);
+ ASSERT_EQ(status, OkStatus());
+
+ // clang-format off
+ constexpr uint8_t expected_proto[] = {
+ // repeated.structs
+ 0x2a, 0x04,
+ // repeated.structs.one v=16
+ 0x08, 0x10,
+ // repeated.structs.two v=32
+ 0x10, 0x20,
+ // repeated.structs
+ 0x2a, 0x04,
+ // repeated.structs.one v=48
+ 0x08, 0x30,
+ // repeated.structs.two v=64
+ 0x10, 0x40,
+ };
+ // clang-format on
+
+ ConstByteSpan result = writer.WrittenData();
+ EXPECT_EQ(result.size(), sizeof(expected_proto));
+ EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
+ 0);
+}
+
+TEST(CodegenMessage, WriteNestedForcedCallback) {
+ Pigweed::Message message{};
+ // pigweed.device_info has use_callback=true to force the use of a callback.
+ message.device_info.SetEncoder([](Pigweed::StreamEncoder& encoder) {
+ DeviceInfo::Message device_info{};
+ device_info.device_name = "pixel";
+ device_info.device_id = 0x08080808u;
+ device_info.status = DeviceInfo::DeviceStatus::OK;
+
+ // Use the callback to set nested callbacks.
+ device_info.attributes.SetEncoder(
+ [](DeviceInfo::StreamEncoder& device_info_encoder) {
+ KeyValuePair::Message attribute{};
+
+ attribute.key = "version";
+ attribute.value = "5.3.1";
+ PW_TRY(device_info_encoder.GetAttributesEncoder().Write(attribute));
+
+ attribute.key = "chip";
+ attribute.value = "left-soc";
+ PW_TRY(device_info_encoder.GetAttributesEncoder().Write(attribute));
+
+ return OkStatus();
+ });
+
+ return encoder.GetDeviceInfoEncoder().Write(device_info);
+ });
+
+ std::byte encode_buffer[Pigweed::kMaxEncodedSizeBytes +
+ DeviceInfo::kMaxEncodedSizeBytes];
+ std::byte temp_buffer[Pigweed::kScratchBufferSizeBytes +
+ DeviceInfo::kMaxEncodedSizeBytes];
+
+ stream::MemoryWriter writer(encode_buffer);
+ Pigweed::StreamEncoder pigweed(writer, temp_buffer);
+
+ const auto status = pigweed.Write(message);
+ ASSERT_EQ(status, OkStatus());
+
+ // clang-format off
+ constexpr uint8_t expected_proto[] = {
+ // pigweed.device_info
+ 0x32, 0x30,
+ // pigweed.device_info.device_name
+ 0x0a, 0x05, 'p', 'i', 'x', 'e', 'l',
+ // pigweed.device_info.device_id
+ 0x15, 0x08, 0x08, 0x08, 0x08,
+ // pigweed.device_info.attributes[0]
+ 0x22, 0x10,
+ // pigweed.device_info.attributes[0].key
+ 0x0a, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
+ // pigweed.device_info.attributes[0].value
+ 0x12, 0x05, '5', '.', '3', '.', '1',
+ // pigweed.device_info.attributes[1]
+ 0x22, 0x10,
+ // pigweed.device_info.attributes[1].key
+ 0x0a, 0x04, 'c', 'h', 'i', 'p',
+ // pigweed.device_info.attributes[1].value
+ 0x12, 0x08, 'l', 'e', 'f', 't', '-', 's', 'o', 'c',
+ };
+ // clang-format on
+
+ ConstByteSpan result = writer.WrittenData();
+ EXPECT_EQ(result.size(), sizeof(expected_proto));
+ EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
+ 0);
+}
+
+TEST(CodegenMessage, EnumAliases) {
+ // Unprefixed enum.
+ EXPECT_EQ(Bool::kTrue, Bool::TRUE);
+ EXPECT_EQ(Bool::kFalse, Bool::FALSE);
+ EXPECT_EQ(Bool::kFileNotFound, Bool::FILE_NOT_FOUND);
+
+ // Prefixed enum has the prefix removed.
+ EXPECT_EQ(Error::kNone, Error::ERROR_NONE);
+ EXPECT_EQ(Error::kNotFound, Error::ERROR_NOT_FOUND);
+ EXPECT_EQ(Error::kUnknown, Error::ERROR_UNKNOWN);
+
+ // Single-value enum.
+ EXPECT_EQ(AlwaysBlue::kBlue, AlwaysBlue::BLUE);
+}
+
+TEST(CodegenMessage, WriteOptionalPresent) {
+ OptionalTest::Message message{};
+ message.sometimes_present_fixed = 0x2a;
+ message.sometimes_present_varint = 0x2a;
+ message.explicitly_present_fixed = 0x45;
+ message.explicitly_present_varint = 0x45;
+ message.sometimes_empty_fixed.push_back(0x63);
+ message.sometimes_empty_varint.push_back(0x63);
+
+ std::byte encode_buffer[512];
+
+ stream::MemoryWriter writer(encode_buffer);
+ OptionalTest::StreamEncoder optional_test(writer, ByteSpan());
+
+ const auto status = optional_test.Write(message);
+ ASSERT_EQ(status, OkStatus());
+
+ // clang-format off
+ constexpr uint8_t expected_proto[] = {
+ // optional.sometimes_present_fixed
+ 0x0d, 0x2a, 0x00, 0x00, 0x00,
+ // optional.sometimes_present_varint
+ 0x10, 0x2a,
+ // optional.explicitly_present_fixed
+ 0x1d, 0x45, 0x00, 0x00, 0x00,
+ // optional.explicitly_present_varint
+ 0x20, 0x45,
+ // optional.sometimes_empty_fixed
+ 0x2a, 0x04, 0x63, 0x00, 0x00, 0x00,
+ // optional.sometimes_empty_varint
+ 0x32, 0x01, 0x63,
+ };
+ // clang-format on
+
+ ConstByteSpan result = writer.WrittenData();
+ EXPECT_EQ(result.size(), sizeof(expected_proto));
+ EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
+ 0);
+}
+
+TEST(CodegenMessage, WriteOptionalNotPresent) {
+ OptionalTest::Message message{};
+
+ std::byte encode_buffer[512];
+
+ stream::MemoryWriter writer(encode_buffer);
+ OptionalTest::StreamEncoder optional_test(writer, ByteSpan());
+
+ const auto status = optional_test.Write(message);
+ ASSERT_EQ(status, OkStatus());
+
+ // The expected proto is empty; no bytes should be written.
+
+ ConstByteSpan result = writer.WrittenData();
+ EXPECT_TRUE(result.empty());
+}
+
+TEST(CodegenMessage, WriteOptionalPresentDefaults) {
+ OptionalTest::Message message{};
+ // Non-optional fields with a default value are not explicitly encoded, so
+ // aren't meaningfully different from one that's just ommitted.
+ message.sometimes_present_fixed = 0x00;
+ message.sometimes_present_varint = 0x00;
+ // Optional fields, even with a default value, are explicitly encoded.
+ message.explicitly_present_fixed = 0x00;
+ message.explicitly_present_varint = 0x00;
+ // Repeated fields with a default value are meaningfully non-empty.
+ message.sometimes_empty_fixed.push_back(0x00);
+ message.sometimes_empty_varint.push_back(0x00);
+
+ std::byte encode_buffer[512];
+
+ stream::MemoryWriter writer(encode_buffer);
+ OptionalTest::StreamEncoder optional_test(writer, ByteSpan());
+
+ const auto status = optional_test.Write(message);
+ ASSERT_EQ(status, OkStatus());
+
+ // clang-format off
+ constexpr uint8_t expected_proto[] = {
+ // optional.explicitly_present_fixed
+ 0x1d, 0x00, 0x00, 0x00, 0x00,
+ // optional.explicitly_present_varint
+ 0x20, 0x00,
+ // optional.sometimes_empty_fixed
+ 0x2a, 0x04, 0x00, 0x00, 0x00, 0x00,
+ // optional.sometimes_empty_varint
+ 0x32, 0x01, 0x00,
+ };
+ // clang-format on
+
+ ConstByteSpan result = writer.WrittenData();
+ EXPECT_EQ(result.size(), sizeof(expected_proto));
+ EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
+ 0);
+}
+
+class BreakableEncoder : public KeyValuePair::MemoryEncoder {
+ public:
+ constexpr BreakableEncoder(ByteSpan buffer)
+ : KeyValuePair::MemoryEncoder(buffer) {}
+
+ Status Write(const KeyValuePair::Message& message,
+ span<const internal::MessageField> table) {
+ return ::pw::protobuf::StreamEncoder::Write(as_bytes(span(&message, 1)),
+ table);
+ }
+};
+
+TEST(CodegenMessage, DISABLED_WriteDoesNotOverrun) {
+ // Deliberately construct a message table that attempts to violate the bounds
+ // of the structure. We're not testing that a developer can't do this, rather
+ // that the protobuf encoder can't be exploited in this way.
+ constexpr internal::MessageField kMessageFields[] = {
+ {1,
+ WireType::kDelimited,
+ sizeof(std::byte),
+ static_cast<internal::VarintType>(0),
+ false,
+ false,
+ false,
+ false,
+ false,
+ 0,
+ sizeof(KeyValuePair::Message) * 2,
+ {}},
+ };
+
+ std::byte encode_buffer[64];
+
+ BreakableEncoder encoder(encode_buffer);
+ KeyValuePair::Message message{};
+ // ASSERT_CRASH
+ std::ignore = encoder.Write(message, kMessageFields);
+}
+
+// The following tests cover using the codegen struct Message and callbacks in
+// different ways.
+
+// Check that the callback function object is large enough to implement a
+// "call a function on this" lambda.
+class StringChecker {
+ public:
+ StringChecker() = default;
+ ~StringChecker() = default;
+
+ Status Check(RepeatedTest::StreamDecoder& repeated_test) {
+ RepeatedTest::Message message{};
+ message.strings.SetDecoder([this](RepeatedTest::StreamDecoder& decoder) {
+ return this->CheckOne(decoder);
+ });
+ return repeated_test.Read(message);
+ }
+
+ private:
+ Status CheckOne(RepeatedTest::StreamDecoder& decoder) {
+ EXPECT_EQ(decoder.Field().value(), RepeatedTest::Fields::kStrings);
+
+ std::array<char, 40> strings{};
+ const StatusWithSize sws = decoder.ReadStrings(strings);
+ EXPECT_EQ(sws.status(), OkStatus());
+ EXPECT_EQ(sws.size(), kExpectedStrings[i_].size());
+ EXPECT_EQ(std::memcmp(strings.data(),
+ kExpectedStrings[i_].data(),
+ kExpectedStrings[i_].size()),
+ 0);
+
+ ++i_;
+ return sws.status();
+ }
+
+ int i_ = 0;
+ constexpr static std::string_view kExpectedStrings[] = {
+ {"if music be the food of love, play on"},
+ {"give me excess of it, that, surfeiting"},
+ {"the appetite may sicken, and so die"}};
+};
+
+TEST(CodegenMessage, CallbackInClass) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // repeated.strings
+ 0x1a, 0x25, 'i', 'f', ' ', 'm', 'u', 's', 'i', 'c', ' ', 'b', 'e', ' ',
+ 't', 'h', 'e', ' ', 'f', 'o', 'o', 'd', ' ', 'o', 'f', ' ',
+ 'l', 'o', 'v', 'e', ',', ' ', 'p', 'l', 'a', 'y', ' ', 'o', 'n',
+ // repeated.strings
+ 0x1a, 0x26, 'g', 'i', 'v', 'e', ' ', 'm', 'e', ' ', 'e', 'x', 'c', 'e',
+ 's', 's', ' ', 'o', 'f', ' ', 'i', 't', ',', ' ', 't', 'h', 'a', 't', ',',
+ ' ', 's', 'u', 'r', 'f', 'e', 'i', 't', 'i', 'n', 'g',
+ // repeated.strings
+ 0x1a, 0x23, 't', 'h', 'e', ' ', 'a', 'p', 'p', 'e', 't', 'i', 't', 'e', ' ',
+ 'm', 'a', 'y', ' ', 's', 'i', 'c', 'k', 'e', 'n', ',', ' ', 'a', 'n', 'd',
+ ' ', 's', 'o', ' ', 'd', 'i', 'e',
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ RepeatedTest::StreamDecoder repeated_test(reader);
+
+ StringChecker checker{};
+ const auto status = checker.Check(repeated_test);
+ ASSERT_EQ(status, OkStatus());
+}
+
+// Check that we can create a custom subclass of the message struct that sets
+// its own callbacks to member functions that populate fields added in the
+// subclass.
+struct CustomMessage : RepeatedTest::Message {
+ CustomMessage() : RepeatedTest::Message() {
+ strings.SetDecoder([this](RepeatedTest::StreamDecoder& decoder) {
+ return this->ParseStrings(decoder);
+ });
+ }
+
+ pw::Vector<std::array<char, 40>, 8> all_strings{};
+
+ private:
+ Status ParseStrings(RepeatedTest::StreamDecoder& decoder) {
+ PW_ASSERT(decoder.Field().value() == RepeatedTest::Fields::kStrings);
+
+ std::array<char, 40> one_strings{};
+ const auto sws = decoder.ReadStrings(one_strings);
+ if (!sws.ok()) {
+ return sws.status();
+ }
+
+ one_strings[sws.size()] = '\0';
+ all_strings.push_back(one_strings);
+
+ return OkStatus();
+ }
+};
+
+TEST(CodegenMessage, CallbackInSubclass) {
+ // clang-format off
+ constexpr uint8_t proto_data[] = {
+ // repeated.strings
+ 0x1a, 0x25, 'i', 'f', ' ', 'm', 'u', 's', 'i', 'c', ' ', 'b', 'e', ' ',
+ 't', 'h', 'e', ' ', 'f', 'o', 'o', 'd', ' ', 'o', 'f', ' ',
+ 'l', 'o', 'v', 'e', ',', ' ', 'p', 'l', 'a', 'y', ' ', 'o', 'n',
+ // repeated.strings
+ 0x1a, 0x26, 'g', 'i', 'v', 'e', ' ', 'm', 'e', ' ', 'e', 'x', 'c', 'e',
+ 's', 's', ' ', 'o', 'f', ' ', 'i', 't', ',', ' ', 't', 'h', 'a', 't', ',',
+ ' ', 's', 'u', 'r', 'f', 'e', 'i', 't', 'i', 'n', 'g',
+ // repeated.strings
+ 0x1a, 0x23, 't', 'h', 'e', ' ', 'a', 'p', 'p', 'e', 't', 'i', 't', 'e', ' ',
+ 'm', 'a', 'y', ' ', 's', 'i', 'c', 'k', 'e', 'n', ',', ' ', 'a', 'n', 'd',
+ ' ', 's', 'o', ' ', 'd', 'i', 'e',
+ };
+ // clang-format on
+
+ stream::MemoryReader reader(as_bytes(span(proto_data)));
+ RepeatedTest::StreamDecoder repeated_test(reader);
+
+ CustomMessage message{};
+ const auto status = repeated_test.Read(message);
+ ASSERT_EQ(status, OkStatus());
+
+ constexpr static std::string_view kExpectedStrings[] = {
+ {"if music be the food of love, play on"},
+ {"give me excess of it, that, surfeiting"},
+ {"the appetite may sicken, and so die"}};
+
+ EXPECT_EQ(message.all_strings.size(), 3u);
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_EQ(std::memcmp(message.all_strings[i].data(),
+ kExpectedStrings[i].data(),
+ kExpectedStrings[i].size()),
+ 0);
+ EXPECT_EQ(message.all_strings[i].data()[kExpectedStrings[i].size()], '\0');
+ }
+}
+
+} // namespace
+} // namespace pw::protobuf