// Copyright 2022 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 #include #include #include "gtest/gtest.h" #include "pw_preprocessor/compiler.h" #include "pw_status/status.h" #include "pw_status/status_with_size.h" #include "pw_stream/memory_stream.h" #include "pw_string/vector.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/repeated.pwpb.h" namespace pw::protobuf { namespace { using namespace ::pw::protobuf::test; 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); } 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(std::as_bytes(std::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(std::as_bytes(std::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(std::as_bytes(std::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(std::as_bytes(std::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(std::as_bytes(std::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(std::as_bytes(std::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::SINT32S); pw::Vector 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(std::as_bytes(std::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(std::as_bytes(std::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(std::as_bytes(std::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(std::as_bytes(std::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(std::as_bytes(std::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(std::as_bytes(std::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(std::as_bytes(std::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::DESCRIPTION); constexpr std::string_view kExpectedDescription{ "an open source collection of embedded-targeted libraries-or as we " "like to call them, modules"}; std::array 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, 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(std::as_bytes(std::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::STRINGS); 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 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(std::as_bytes(std::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::SPECIAL_PROPERTY); pw::Result 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(std::as_bytes(std::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(std::as_bytes(std::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(std::as_bytes(std::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(std::as_bytes(std::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(std::as_bytes(std::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(std::as_bytes(std::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(std::as_bytes(std::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::STRUCTS); 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(std::as_bytes(std::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::DEVICE_INFO); 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, 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; pw::string::Copy("not a typewriter", message.error_message).IgnoreError(); 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; pw::string::Copy("/etc/passwd", message.proto.meta.file_name).IgnoreError(); 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[512]; std::byte temp_buffer[512]; 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, 0x1b, // pigweed.proto.bin 0x10, 0x00, // pigweed.proto.pigweed_pigweed_bin 0x18, 0x00, // pigweed.proto.pigweed_protobuf_bin 0x20, 0x01, // pigweed.proto.meta 0x2a, 0x13, // 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.protobuf_bin 0x18, 0x00, // 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[512]; std::byte temp_buffer[512]; 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 (default) 0x08, 0x00, // pigweed.ziggy (default) 0x10, 0x00, // pigweed.cycles (default) 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pigweed.ratio (default) 0x25, 0x00, 0x00, 0x00, 0x00, // pigweed.pigweed (default) 0x3a, 0x02, // pigweed.pigweed.status (default) 0x08, 0x00, // pigweed.bin (default) 0x40, 0x00, // pigweed.proto (default) 0x4a, 0x0e, // pigweed.proto.bin (default) 0x10, 0x00, // pigweed.proto.pigweed_pigweed_bin (default) 0x18, 0x00, // pigweed.proto.pigweed_protobuf_bin (default) 0x20, 0x00, // pigweed.proto.meta (default) 0x2a, 0x06, // pigweed.proto.meta.status (default) 0x10, 0x00, // pigweed.proto.meta.protobuf_bin (default) 0x18, 0x00, // pigweed.proto.meta.pigweed_bin (default) 0x20, 0x00, // pigweed.bytes (default) 0x5a, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pigweed.bungle (default) 0x70, 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, 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[64]; 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, // doubles[], v={0, 0} (default) 0x22, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // fixed32s[]. v={0, 16, 32, 48} 0x32, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, // uint64s[]. v={0, 0, 0, 0} (default) 0x42, 0x04, 0x00, 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[64]; 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[64]; 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, // doubles[], v={0, 0} (default) 0x22, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // uint64s[]. v={0, 0, 0, 0} (default) 0x42, 0x04, 0x00, 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, 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[64]; 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={0, 0} (default) 0x22, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // uint64s[]. v={0, 0, 0, 0} (default) 0x42, 0x04, 0x00, 0x00, 0x00, 0x00, // 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[512]; std::byte temp_buffer[512]; 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 (default) 0x08, 0x00, // pigweed.ziggy (default) 0x10, 0x00, // pigweed.cycles (default) 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pigweed.ratio (default) 0x25, 0x00, 0x00, 0x00, 0x00, // pigweed.pigweed (default) 0x3a, 0x02, // pigweed.pigweed.status (default) 0x08, 0x00, // pigweed.bin (default) 0x40, 0x00, // pigweed.proto (default) 0x4a, 0x0e, // pigweed.proto.bin (default) 0x10, 0x00, // pigweed.proto.pigweed_pigweed_bin (default) 0x18, 0x00, // pigweed.proto.pigweed_protobuf_bin (default) 0x20, 0x00, // pigweed.proto.meta (default) 0x2a, 0x06, // pigweed.proto.meta.status (default) 0x10, 0x00, // pigweed.proto.meta.protobuf_bin (default) 0x18, 0x00, // pigweed.proto.meta.pigweed_bin (default) 0x20, 0x00, // pigweed.bytes (default) 0x5a, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 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', // pigweed.bungle (default) 0x70, 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, 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[512]; std::byte temp_buffer[512]; 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 (default) 0x08, 0x00, // pigweed.ziggy (default) 0x10, 0x00, // pigweed.cycles (default) 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pigweed.ratio (default) 0x25, 0x00, 0x00, 0x00, 0x00, // pigweed.pigweed (default) 0x3a, 0x02, // pigweed.pigweed.status (default) 0x08, 0x00, // pigweed.bin (default) 0x40, 0x00, // pigweed.proto (default) 0x4a, 0x0e, // pigweed.proto.bin (default) 0x10, 0x00, // pigweed.proto.pigweed_pigweed_bin (default) 0x18, 0x00, // pigweed.proto.pigweed_protobuf_bin (default) 0x20, 0x00, // pigweed.proto.meta (default) 0x2a, 0x06, // pigweed.proto.meta.status (default) 0x10, 0x00, // pigweed.proto.meta.protobuf_bin (default) 0x18, 0x00, // pigweed.proto.meta.pigweed_bin (default) 0x20, 0x00, // pigweed.bytes (default) 0x5a, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pigweed.special_property 0x68, 0x2a, // pigweed.bungle (default) 0x70, 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, WriteNestedImported) { Period::Message message{}; message.start.seconds = 1517949900u; message.end.seconds = 1517950378u; std::byte encode_buffer[32]; std::byte temp_buffer[32]; 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, 0x08, // period.start.seconds v=1517949900 0x08, 0xcc, 0xa7, 0xe8, 0xd3, 0x05, // period.start.nanoseconds v=0 (default) 0x10, 0x00, // period.end 0x12, 0x08, // period.end.seconds, v=1517950378 0x08, 0xaa, 0xab, 0xe8, 0xd3, 0x05, // period.end.nanoseconds, v=0 (default) 0x10, 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, 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[64]; std::byte temp_buffer[64]; 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[] = { // doubles[], v={0, 0} (default) 0x22, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 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, // uint64s[]. v={0, 0, 0, 0} (default) 0x42, 0x04, 0x00, 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, 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{}; pw::string::Copy("pixel", device_info.device_name).IgnoreError(); 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{}; pw::string::Copy("version", attribute.key).IgnoreError(); pw::string::Copy("5.3.1", attribute.value).IgnoreError(); PW_TRY(device_info_encoder.GetAttributesEncoder().Write(attribute)); pw::string::Copy("chip", attribute.key).IgnoreError(); pw::string::Copy("left-soc", attribute.value).IgnoreError(); PW_TRY(device_info_encoder.GetAttributesEncoder().Write(attribute)); return OkStatus(); }); return encoder.GetDeviceInfoEncoder().Write(device_info); }); std::byte encode_buffer[512]; std::byte temp_buffer[512]; 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 (default) 0x08, 0x00, // pigweed.ziggy (default) 0x10, 0x00, // pigweed.cycles (default) 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pigweed.ratio (default) 0x25, 0x00, 0x00, 0x00, 0x00, // pigweed.device_info 0x32, 0x32, // 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, // pigweed.proto.nested_pigweed.device_info.attributes[0] 0x22, 0x10, // pigweed.proto.nested_pigweed.device_info.attributes[0].key 0x0a, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n', // pigweed.proto.nested_pigweed.device_info.attributes[0].value 0x12, 0x05, '5', '.', '3', '.', '1', // pigweed.proto.nested_pigweed.device_info.attributes[1] 0x22, 0x10, // pigweed.proto.nested_pigweed.device_info.attributes[1].key 0x0a, 0x04, 'c', 'h', 'i', 'p', // pigweed.proto.nested_pigweed.device_info.attributes[1].value 0x12, 0x08, 'l', 'e', 'f', 't', '-', 's', 'o', 'c', // pigweed.pigweed (default) 0x3a, 0x02, // pigweed.pigweed.status (default) 0x08, 0x00, // pigweed.bin (default) 0x40, 0x00, // pigweed.proto (default) 0x4a, 0x0e, // pigweed.proto.bin (default) 0x10, 0x00, // pigweed.proto.pigweed_pigweed_bin (default) 0x18, 0x00, // pigweed.proto.pigweed_protobuf_bin (default) 0x20, 0x00, // pigweed.proto.meta (default) 0x2a, 0x06, // pigweed.proto.meta.status (default) 0x10, 0x00, // pigweed.proto.meta.protobuf_bin (default) 0x18, 0x00, // pigweed.proto.meta.pigweed_bin (default) 0x20, 0x00, // pigweed.bytes (default) 0x5a, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pigweed.bungle (default) 0x70, 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); } // 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::STRINGS); std::array 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(std::as_bytes(std::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, 8> all_strings{}; private: Status ParseStrings(RepeatedTest::StreamDecoder& decoder) { PW_ASSERT(decoder.Field().value() == RepeatedTest::Fields::STRINGS); std::array 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(std::as_bytes(std::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