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