blob: b8b38959925857aa6d0b06154239420f16f08511 [file] [log] [blame]
// 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