| // 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 <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" |
| #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/optional.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); |
| } |
| |
| 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::BITS; |
| std::ignore = IntegerMetadata::Fields::SIGNED_; |
| std::ignore = IntegerMetadata::Fields::NULL_; |
| |
| // 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::DESCRIPTION; |
| std::ignore = Function::Fields::DOMAIN_FIELD; |
| std::ignore = Function::Fields::CODOMAIN_FIELD; |
| |
| // Check for expected values of `enum class Function::Message_::Fields`: |
| std::ignore = Function::Message_::Fields::CONTENT; |
| |
| // 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::SINT32S); |
| |
| 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::DESCRIPTION); |
| |
| 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::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<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::SPECIAL_PROPERTY); |
| |
| 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::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(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::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, 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 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 MessageField kMessageFields[] = { |
| {1, |
| WireType::kDelimited, |
| sizeof(std::byte), |
| static_cast<VarintType>(0), |
| 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; |
| 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[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{}; |
| 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[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()); |
| |
| // clang-format off |
| constexpr uint8_t expected_proto[] = { |
| }; |
| // 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, 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 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 MessageField kMessageFields[] = { |
| {1, |
| WireType::kDelimited, |
| sizeof(std::byte), |
| static_cast<VarintType>(0), |
| 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::STRINGS); |
| |
| 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::STRINGS); |
| |
| 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 |