blob: 7f757f361cf9b6c5d5c010b34b8e8a0016fced8b [file] [edit]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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
*
* http://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 "perfetto/protozero/message.h"
#include <limits>
#include <memory>
#include <random>
#include <string>
#include <utility>
#include <vector>
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/protozero/message_handle.h"
#include "perfetto/protozero/proto_utils.h"
#include "perfetto/protozero/root_message.h"
#include "src/base/test/utils.h"
#include "src/protozero/test/example_proto/test_messages.pbzero.h"
#include "src/protozero/test/fake_scattered_buffer.h"
#include "test/gtest_and_gmock.h"
namespace protozero {
namespace {
constexpr size_t kChunkSize = 16;
constexpr uint8_t kTestBytes[] = {0, 0, 0, 0, 0x42, 1, 0x42, 0xff, 0x42, 0};
constexpr const char kStartWatermark[] = {'a', 'b', 'c', 'd',
'1', '2', '3', '\0'};
constexpr const char kEndWatermark[] = {'9', '8', '7', '6',
'z', 'w', 'y', '\0'};
class FakeRootMessage : public RootMessage<Message> {};
class FakeChildMessage : public Message {};
uint32_t SimpleHash(const std::string& str) {
uint32_t hash = 5381;
for (char c : str)
hash = 33 * hash + static_cast<uint32_t>(c);
return hash;
}
class MessageTest : public ::testing::Test {
public:
void SetUp() override { SetChunkSize(kChunkSize); }
void TearDown() override {
// Check that none of the messages created by the text fixtures below did
// under/overflow their heap boundaries.
for (std::unique_ptr<uint8_t[]>& mem : messages_) {
EXPECT_STREQ(kStartWatermark, reinterpret_cast<char*>(mem.get()));
EXPECT_STREQ(kEndWatermark,
reinterpret_cast<char*>(mem.get() + sizeof(kStartWatermark) +
sizeof(FakeRootMessage)));
FakeRootMessage* msg = reinterpret_cast<FakeRootMessage*>(
mem.get() + sizeof(kStartWatermark));
msg->~FakeRootMessage();
mem.reset();
}
messages_.clear();
stream_writer_.reset();
buffer_.reset();
}
void ResetMessage(FakeRootMessage* msg) { msg->Reset(stream_writer_.get()); }
void SetChunkSize(size_t size) {
buffer_.reset(new FakeScatteredBuffer(size));
stream_writer_.reset(new ScatteredStreamWriter(buffer_.get()));
chunk_size_ = size;
readback_pos_ = 0;
}
FakeRootMessage* NewMessage() {
std::unique_ptr<uint8_t[]> mem(
new uint8_t[sizeof(kStartWatermark) + sizeof(FakeRootMessage) +
sizeof(kEndWatermark)]);
uint8_t* msg_start = mem.get() + sizeof(kStartWatermark);
memcpy(mem.get(), kStartWatermark, sizeof(kStartWatermark));
memset(msg_start, 0, sizeof(FakeRootMessage));
memcpy(msg_start + sizeof(FakeRootMessage), kEndWatermark,
sizeof(kEndWatermark));
messages_.push_back(std::move(mem));
FakeRootMessage* msg = new (msg_start) FakeRootMessage();
msg->Reset(stream_writer_.get());
return msg;
}
FakeRootMessage* NewMessageWithSizeField() {
FakeRootMessage* msg = NewMessage();
uint8_t* size_field =
stream_writer_->ReserveBytes(proto_utils::kMessageLengthFieldSize);
memset(size_field, 0u, proto_utils::kMessageLengthFieldSize);
msg->set_size_field(size_field);
return msg;
}
size_t GetNumSerializedBytes() {
if (buffer_->chunks().empty())
return 0;
return buffer_->chunks().size() * chunk_size_ -
stream_writer_->bytes_available();
}
std::string GetNextSerializedBytes(size_t num_bytes) {
size_t old_readback_pos = readback_pos_;
readback_pos_ += num_bytes;
return buffer_->GetBytesAsString(old_readback_pos, num_bytes);
}
static void BuildNestedMessages(Message* msg,
uint32_t max_depth,
bool empty = false,
uint32_t depth = 0) {
if (!empty) {
for (uint32_t i = 1; i <= 128; ++i)
msg->AppendBytes(i, kTestBytes, sizeof(kTestBytes));
}
if (depth < max_depth) {
auto* nested_msg =
msg->BeginNestedMessage<FakeChildMessage>(1 + depth * 10);
BuildNestedMessages(nested_msg, max_depth, empty, depth + 1);
}
if (!empty) {
for (uint32_t i = 129; i <= 256; ++i)
msg->AppendVarInt(i, 42);
}
if ((depth & 2) == 0)
msg->Finalize();
}
protected:
std::unique_ptr<FakeScatteredBuffer> buffer_;
std::unique_ptr<ScatteredStreamWriter> stream_writer_;
std::vector<std::unique_ptr<uint8_t[]>> messages_;
size_t chunk_size_{};
size_t readback_pos_{};
};
TEST_F(MessageTest, ZeroLengthArraysAndStrings) {
Message* msg = NewMessage();
msg->AppendBytes(1 /* field_id */, nullptr, 0);
msg->AppendString(2 /* field_id */, "");
EXPECT_EQ(4u, msg->Finalize());
EXPECT_EQ(4u, GetNumSerializedBytes());
// These lines match the serialization of the Append* calls above.
ASSERT_EQ("0A00", GetNextSerializedBytes(2));
ASSERT_EQ("1200", GetNextSerializedBytes(2));
}
TEST_F(MessageTest, BasicTypesNoNesting) {
Message* msg = NewMessage();
msg->AppendVarInt(1 /* field_id */, 0);
msg->AppendVarInt(2 /* field_id */, std::numeric_limits<uint32_t>::max());
msg->AppendVarInt(3 /* field_id */, 42);
msg->AppendVarInt(4 /* field_id */, std::numeric_limits<uint64_t>::max());
msg->AppendFixed(5 /* field_id */, 3.1415f /* float */);
msg->AppendFixed(6 /* field_id */, 3.14159265358979323846 /* double */);
msg->AppendBytes(7 /* field_id */, kTestBytes, sizeof(kTestBytes));
// Field ids > 16 are expected to be varint encoded (preamble > 1 byte)
msg->AppendString(257 /* field_id */, "0123456789abcdefABCDEF");
msg->AppendSignedVarInt(3 /* field_id */, -21);
EXPECT_EQ(74u, msg->Finalize());
EXPECT_EQ(74u, GetNumSerializedBytes());
// These lines match the serialization of the Append* calls above.
ASSERT_EQ("0800", GetNextSerializedBytes(2));
ASSERT_EQ("10FFFFFFFF0F", GetNextSerializedBytes(6));
ASSERT_EQ("182A", GetNextSerializedBytes(2));
ASSERT_EQ("20FFFFFFFFFFFFFFFFFF01", GetNextSerializedBytes(11));
ASSERT_EQ("2D560E4940", GetNextSerializedBytes(5));
ASSERT_EQ("31182D4454FB210940", GetNextSerializedBytes(9));
ASSERT_EQ("3A0A00000000420142FF4200", GetNextSerializedBytes(12));
ASSERT_EQ("8A101630313233343536373839616263646566414243444546",
GetNextSerializedBytes(25));
ASSERT_EQ("1829", GetNextSerializedBytes(2));
}
TEST_F(MessageTest, NestedMessagesSimple) {
Message* root_msg = NewMessage();
root_msg->AppendVarInt(1 /* field_id */, 1);
FakeChildMessage* nested_msg =
root_msg->BeginNestedMessage<FakeChildMessage>(128 /* field_id */);
ASSERT_EQ(0u, reinterpret_cast<uintptr_t>(nested_msg) % sizeof(void*));
nested_msg->AppendVarInt(2 /* field_id */, 2);
nested_msg =
root_msg->BeginNestedMessage<FakeChildMessage>(129 /* field_id */);
nested_msg->AppendVarInt(4 /* field_id */, 2);
root_msg->AppendVarInt(5 /* field_id */, 3);
// The expected size of the root message is supposed to be 14 bytes:
// 2 bytes for the varint field (id: 1) (1 for preamble and one for payload)
// 3 bytes for the preamble of the 1st nested message (2 for id, 1 for size)
// 2 bytes for the varint field (id: 2) of the 1st nested message
// 3 bytes for the premable of the 2nd nested message
// 2 bytes for the varint field (id: 4) of the 2nd nested message.
// 2 bytes for the last varint (id : 5) field of the root message.
// Test also that finalization is idempontent and Finalize() can be safely
// called more than once without side effects.
for (int i = 0; i < 3; ++i) {
EXPECT_EQ(14u, root_msg->Finalize());
EXPECT_EQ(14u, GetNumSerializedBytes());
}
ASSERT_EQ("0801", GetNextSerializedBytes(2));
ASSERT_EQ("820802", GetNextSerializedBytes(3));
ASSERT_EQ("1002", GetNextSerializedBytes(2));
ASSERT_EQ("8A0802", GetNextSerializedBytes(3));
ASSERT_EQ("2002", GetNextSerializedBytes(2));
ASSERT_EQ("2803", GetNextSerializedBytes(2));
}
// Tests using a AppendScatteredBytes to append raw bytes to
// a message using multiple individual buffers.
TEST_F(MessageTest, AppendScatteredBytes) {
Message* root_msg = NewMessage();
uint8_t buffer[42];
memset(buffer, 0x42, sizeof(buffer));
ContiguousMemoryRange ranges[] = {{buffer, buffer + sizeof(buffer)},
{buffer, buffer + sizeof(buffer)}};
root_msg->AppendScatteredBytes(1 /* field_id */, ranges, 2);
EXPECT_EQ(86u, root_msg->Finalize());
EXPECT_EQ(86u, GetNumSerializedBytes());
// field_id
EXPECT_EQ("0A", GetNextSerializedBytes(1));
// field length
EXPECT_EQ("54", GetNextSerializedBytes(1));
// start of contents
EXPECT_EQ("42424242", GetNextSerializedBytes(4));
}
TEST_F(MessageTest, AppendRawProtoBytesFinalizesNestedMessage) {
Message* root_msg = NewMessage();
uint8_t buffer[42];
memset(buffer, 0x42, sizeof(buffer));
FakeChildMessage* nested_msg =
root_msg->BeginNestedMessage<FakeChildMessage>(9001 /* field_id */);
nested_msg->AppendVarInt(4 /* field_id */, 2);
uint8_t* nested_msg_size_field = nested_msg->size_field();
EXPECT_FALSE(nested_msg->is_finalized());
EXPECT_EQ(0u, *nested_msg_size_field);
root_msg->AppendRawProtoBytes(buffer, sizeof(buffer));
// Nested message should have been finalized as a side effect of appending
// raw bytes.
EXPECT_EQ(0x2u, *nested_msg_size_field);
}
TEST_F(MessageTest, AppendScatteredBytesFinalizesNestedMessage) {
Message* root_msg = NewMessage();
uint8_t buffer[42];
memset(buffer, 0x42, sizeof(buffer));
FakeChildMessage* nested_msg =
root_msg->BeginNestedMessage<FakeChildMessage>(9001 /* field_id */);
nested_msg->AppendVarInt(4 /* field_id */, 2);
uint8_t* nested_msg_size_field = nested_msg->size_field();
EXPECT_FALSE(nested_msg->is_finalized());
EXPECT_EQ(0u, *nested_msg_size_field);
ContiguousMemoryRange ranges[] = {{buffer, buffer + sizeof(buffer)}};
root_msg->AppendScatteredBytes(1 /* field_id */, ranges, 1);
// Nested message should have been finalized as a side effect of appending
// scattered bytes.
EXPECT_EQ(0x2u, *nested_msg_size_field);
}
TEST_F(MessageTest, StressTest) {
std::vector<Message*> nested_msgs;
Message* root_msg = NewMessage();
BuildNestedMessages(root_msg, /*max_depth=*/10);
root_msg->Finalize();
// The main point of this test is to stress the code paths and test for
// unexpected crashes of the production code. The actual serialization is
// already covered in the other text fixtures. Keeping just a final smoke test
// here on the full buffer hash.
std::string full_buf = GetNextSerializedBytes(GetNumSerializedBytes());
size_t buf_hash = SimpleHash(full_buf);
EXPECT_EQ(0xf9e32b65, buf_hash);
}
TEST_F(MessageTest, DeeplyNested) {
Message* root_msg = NewMessage();
BuildNestedMessages(root_msg, /*max_depth=*/1000);
root_msg->Finalize();
std::string full_buf = GetNextSerializedBytes(GetNumSerializedBytes());
size_t buf_hash = SimpleHash(full_buf);
EXPECT_EQ(0xc0fde419, buf_hash);
}
TEST_F(MessageTest, DeeplyNestedEmptyMessages) {
// Stress test writing deeply nested empty messages, many of which will be
// packed into the protobuf length field.
// Use a larger chunk size for this test so there is more opportunity to pack
// messages.
SetChunkSize(4096u);
Message* root_msg = NewMessage();
BuildNestedMessages(root_msg, /*max_depth=*/1000, /*empty=*/true);
root_msg->Finalize();
std::string full_buf = GetNextSerializedBytes(GetNumSerializedBytes());
size_t buf_hash = SimpleHash(full_buf);
EXPECT_EQ(0x9371fe8eu, buf_hash);
}
TEST_F(MessageTest, DestructInvalidMessageHandle) {
FakeRootMessage* msg = NewMessage();
EXPECT_DCHECK_DEATH({
MessageHandle<FakeRootMessage> handle(msg);
ResetMessage(msg);
});
}
TEST_F(MessageTest, MessageHandle) {
FakeRootMessage* msg3 = NewMessageWithSizeField();
FakeRootMessage* msg2 = NewMessageWithSizeField();
FakeRootMessage* msg1 = NewMessageWithSizeField();
FakeRootMessage* ignored_msg = NewMessage();
uint8_t* msg1_size = msg1->size_field();
uint8_t* msg2_size = msg2->size_field();
uint8_t* msg3_size = msg3->size_field();
// Test that the handle going out of scope causes the finalization of the
// target message and triggers the optional callback.
{
MessageHandle<FakeRootMessage> handle1(msg1);
handle1->AppendBytes(1 /* field_id */, kTestBytes, 1 /* size */);
ASSERT_EQ(0u, msg1_size[0]);
}
ASSERT_EQ(0x3u, msg1_size[0]);
// Test that the handle can be late initialized.
MessageHandle<FakeRootMessage> handle2(ignored_msg);
handle2 = MessageHandle<FakeRootMessage>(msg2);
handle2->AppendBytes(1 /* field_id */, kTestBytes, 2 /* size */);
ASSERT_EQ(0u, msg2_size[0]); // |msg2| should not be finalized yet.
// Test that std::move works and does NOT cause finalization of the moved
// message.
MessageHandle<FakeRootMessage> handle_swp(ignored_msg);
handle_swp = std::move(handle2);
ASSERT_EQ(0u, msg2_size[0]); // msg2 should be NOT finalized yet.
handle_swp->AppendBytes(2 /* field_id */, kTestBytes, 3 /* size */);
MessageHandle<FakeRootMessage> handle3(msg3);
handle3->AppendBytes(1 /* field_id */, kTestBytes, 4 /* size */);
ASSERT_EQ(0u, msg3_size[0]); // msg2 should be NOT finalized yet.
// Both |handle3| and |handle_swp| point to a valid message (respectively,
// |msg3| and |msg2|). Now move |handle3| into |handle_swp|.
handle_swp = std::move(handle3);
ASSERT_EQ(0x89u, msg2_size[0]); // |msg2| should be finalized at this point.
// At this point writing into handle_swp should actually write into |msg3|.
ASSERT_EQ(msg3, &*handle_swp);
handle_swp->AppendBytes(2 /* field_id */, kTestBytes, 8 /* size */);
MessageHandle<FakeRootMessage> another_handle(ignored_msg);
handle_swp = std::move(another_handle);
ASSERT_EQ(0x90u, msg3_size[0]); // |msg3| should be finalized at this point.
#if PERFETTO_DCHECK_IS_ON()
// In developer builds w/ PERFETTO_DCHECK on a finalized message should
// invalidate the handle, in order to early catch bugs in the client code.
FakeRootMessage* msg4 = NewMessage();
MessageHandle<FakeRootMessage> handle4(msg4);
ASSERT_EQ(msg4, &*handle4);
msg4->Finalize();
ASSERT_THAT(handle4.get(), testing::IsNull());
#endif
// Test also the behavior of handle with non-root (nested) messages.
uint8_t* size_msg_2;
{
auto* nested_msg_1 = NewMessage()->BeginNestedMessage<FakeChildMessage>(3);
MessageHandle<FakeChildMessage> child_handle_1(nested_msg_1);
uint8_t* size_msg_1 = nested_msg_1->size_field();
memset(size_msg_1, 0, proto_utils::kMessageLengthFieldSize);
child_handle_1->AppendVarInt(1, 0x11);
auto* nested_msg_2 = NewMessage()->BeginNestedMessage<FakeChildMessage>(2);
size_msg_2 = nested_msg_2->size_field();
memset(size_msg_2, 0, proto_utils::kMessageLengthFieldSize);
MessageHandle<FakeChildMessage> child_handle_2(nested_msg_2);
child_handle_2->AppendVarInt(2, 0xFF);
// |nested_msg_1| should not be finalized yet.
ASSERT_EQ(0u, size_msg_1[0]);
// This move should cause |nested_msg_1| to be finalized, but not
// |nested_msg_2|, which will be finalized only after the current scope.
child_handle_1 = std::move(child_handle_2);
ASSERT_EQ(0x82u, size_msg_1[0]);
ASSERT_EQ(0u, size_msg_2[0]);
}
ASSERT_EQ(0x3u, size_msg_2[0]);
}
TEST_F(MessageTest, MoveMessageHandle) {
FakeRootMessage* msg = NewMessageWithSizeField();
uint8_t* msg_size = msg->size_field();
// Test that the handle going out of scope causes the finalization of the
// target message.
{
MessageHandle<FakeRootMessage> handle1(msg);
MessageHandle<FakeRootMessage> handle2{};
handle1->AppendBytes(1 /* field_id */, kTestBytes, 1 /* size */);
handle2 = std::move(handle1);
ASSERT_EQ(0u, msg_size[0]);
}
ASSERT_EQ(0x3u, msg_size[0]);
}
TEST_F(MessageTest, FinalizeWithCompaction) {
FakeRootMessage* msg = NewMessageWithSizeField();
msg->AppendBytes(1 /* field_id */, kTestBytes, 5 /* size */);
uint32_t size = msg->Finalize();
EXPECT_EQ(7u, size);
EXPECT_EQ(8u, GetNumSerializedBytes());
}
TEST_F(MessageTest, FinalizeWithoutCompaction) {
FakeRootMessage* msg = NewMessageWithSizeField();
// This message doesn't fit into a single chunk, so it won't be compacted.
msg->AppendBytes(1 /* field_id */, kTestBytes, sizeof(kTestBytes) /* size */);
msg->AppendBytes(1 /* field_id */, kTestBytes, sizeof(kTestBytes) /* size */);
uint32_t size = msg->Finalize();
EXPECT_EQ(24u, size);
EXPECT_EQ(28u, GetNumSerializedBytes());
}
TEST_F(MessageTest, ProtoGroupGeneratedMessages) {
RootMessage<protozero::test::protos::pbzero::EveryField> root;
root.Reset(stream_writer_.get(), Message::Encoding::kProtoGroup);
auto* outer = root.add_field_nested();
auto* inner = outer->add_field_nested();
auto* leaf = inner->add_field_nested();
leaf->set_field_int32(150);
root.set_field_int32(42);
EXPECT_EQ(11u, root.Finalize());
EXPECT_EQ(11u, root.Finalize());
EXPECT_EQ("737373089601040404082A", GetNextSerializedBytes(11));
}
TEST_F(MessageTest, ProtoGroupEmptyAndReset) {
auto* root = NewMessage();
root->Reset(stream_writer_.get(), Message::Encoding::kProtoGroup);
EXPECT_EQ(0u, root->Finalize());
EXPECT_EQ(0u, root->Finalize());
EXPECT_EQ(0u, GetNumSerializedBytes());
root->Reset(stream_writer_.get(), Message::Encoding::kProtoGroup);
root->BeginNestedMessage<Message>(1);
EXPECT_EQ(2u, root->Finalize());
root->Reset(stream_writer_.get());
root->BeginNestedMessage<Message>(1);
EXPECT_EQ(2u, root->Finalize());
EXPECT_EQ("0B040A00", GetNextSerializedBytes(4));
}
TEST_F(MessageTest, LengthDelimitedRootWithErasedType) {
Message* root = NewMessage();
root->BeginNestedMessage<Message>(1)->AppendVarInt(1, 1);
EXPECT_EQ(root->Finalize(), 4u);
EXPECT_EQ(root->Finalize(), 4u);
EXPECT_EQ("0A020801", GetNextSerializedBytes(4));
}
TEST_F(MessageTest, ProtoGroupHighFieldIdAndSiblings) {
auto* root = NewMessage();
root->Reset(stream_writer_.get(), Message::Encoding::kProtoGroup);
auto* child = root->BeginNestedMessage<Message>(100);
child->AppendVarInt(1, 0);
// The child's size excludes its closing byte. The parent writes that byte.
EXPECT_EQ(2u, child->Finalize());
EXPECT_EQ(2u, child->Finalize());
root->BeginNestedMessage<Message>(1)->AppendVarInt(2, 150);
root->AppendVarInt(2, 42);
EXPECT_EQ(12u, root->Finalize());
EXPECT_EQ("A3060800040B10960104102A", GetNextSerializedBytes(12));
}
TEST_F(MessageTest, ProtoGroupCloseAtChunkBoundary) {
SetChunkSize(4);
auto* root = NewMessage();
root->Reset(stream_writer_.get(), Message::Encoding::kProtoGroup);
auto* child = root->BeginNestedMessage<Message>(1);
child->AppendVarInt(2, 150);
ASSERT_EQ(4u, GetNumSerializedBytes());
EXPECT_EQ(nullptr, child->size_field());
// The closing byte goes into the next chunk. The first chunk is unchanged.
const auto published = buffer_->GetChunkAsString(0);
EXPECT_EQ(5u, root->Finalize());
EXPECT_EQ(published, buffer_->GetChunkAsString(0));
EXPECT_EQ("0B10960104", GetNextSerializedBytes(5));
}
TEST_F(MessageTest, ProtoGroupBytesAcrossChunks) {
SetChunkSize(4);
auto* root = NewMessage();
root->Reset(stream_writer_.get(), Message::Encoding::kProtoGroup);
auto* child = root->BeginNestedMessage<Message>(1);
child->AppendBytes(2, kTestBytes, sizeof(kTestBytes));
EXPECT_EQ(14u, root->Finalize());
EXPECT_EQ("0B120A00000000420142FF420004", GetNextSerializedBytes(14));
}
// Roots and nested messages share one Finalize(). A root gets no closing byte,
// even through a plain Message*, because it has no parent.
TEST_F(MessageTest, ProtoGroupRootThroughMessagePointerHasNoClosingByte) {
auto* root = NewMessage();
root->Reset(stream_writer_.get(), Message::Encoding::kProtoGroup);
root->BeginNestedMessage<Message>(1)->AppendVarInt(2, 7);
Message* as_plain_message = root;
EXPECT_EQ(4u, as_plain_message->Finalize());
EXPECT_EQ(4u, GetNumSerializedBytes());
EXPECT_EQ("0B100704", GetNextSerializedBytes(4));
}
// A child finalized early, even twice, gets exactly one closing byte.
// The parent writes it right after the child, before its own next field.
TEST_F(MessageTest, ProtoGroupEarlyChildFinalizeClosesOnce) {
auto* root = NewMessage();
root->Reset(stream_writer_.get(), Message::Encoding::kProtoGroup);
auto* child = root->BeginNestedMessage<Message>(1);
child->AppendVarInt(2, 7);
EXPECT_EQ(2u, child->Finalize());
EXPECT_EQ(2u, child->Finalize());
// The parent has not written the closing byte yet.
EXPECT_EQ(3u, GetNumSerializedBytes());
root->AppendVarInt(3, 1);
EXPECT_EQ(6u, root->Finalize());
EXPECT_EQ(6u, root->Finalize());
EXPECT_EQ("0B1007041801", GetNextSerializedBytes(6));
}
// A handle on a nested message finalizes the child.
// The closing byte still comes from the parent, once.
TEST_F(MessageTest, ProtoGroupNestedHandleClosesInParent) {
auto* root = NewMessage();
root->Reset(stream_writer_.get(), Message::Encoding::kProtoGroup);
{
MessageHandle<Message> handle(root->BeginNestedMessage<Message>(1));
handle->AppendVarInt(2, 7);
}
EXPECT_EQ(3u, GetNumSerializedBytes());
EXPECT_EQ(4u, root->Finalize());
EXPECT_EQ("0B100704", GetNextSerializedBytes(4));
}
// Deep nesting closes innermost first, one byte per level.
TEST_F(MessageTest, ProtoGroupDeepNesting) {
constexpr size_t kDepth = 100;
SetChunkSize(4096u);
auto* root = NewMessage();
root->Reset(stream_writer_.get(), Message::Encoding::kProtoGroup);
Message* msg = root;
for (size_t i = 0; i < kDepth; ++i)
msg = msg->BeginNestedMessage<Message>(1);
EXPECT_EQ(2 * kDepth, root->Finalize());
std::string expected;
for (size_t i = 0; i < kDepth; ++i)
expected += "0B";
for (size_t i = 0; i < kDepth; ++i)
expected += "04";
EXPECT_EQ(expected, GetNextSerializedBytes(2 * kDepth));
}
// The encoding does not leak into a later length-delimited Reset(), and a
// length-delimited child still gets its length field.
TEST_F(MessageTest, LengthDelimitedAfterProtoGroupReset) {
auto* root = NewMessage();
root->Reset(stream_writer_.get(), Message::Encoding::kProtoGroup);
root->BeginNestedMessage<Message>(1)->AppendVarInt(2, 7);
EXPECT_EQ(4u, root->Finalize());
root->Reset(stream_writer_.get());
EXPECT_EQ(Message::Encoding::kLengthDelimited, root->encoding());
root->BeginNestedMessage<Message>(1)->AppendVarInt(2, 7);
EXPECT_EQ(4u, root->Finalize());
EXPECT_EQ("0B100704", GetNextSerializedBytes(4));
EXPECT_EQ("0A021007", GetNextSerializedBytes(4));
}
// A random message tree: varint fields and nested messages.
struct TreeNode {
struct Item {
uint32_t field_id = 0;
uint64_t value = 0; // Varint fields only.
std::unique_ptr<TreeNode> nested; // Set for a nested message.
};
std::vector<Item> items;
};
std::unique_ptr<TreeNode> RandomTree(std::minstd_rand* rnd, int depth) {
auto node = std::make_unique<TreeNode>();
const uint32_t num_items = (*rnd)() % 4;
for (uint32_t i = 0; i < num_items; ++i) {
TreeNode::Item item;
item.field_id = 1 + (*rnd)() % 300; // One- and two-byte tags.
if (depth > 0 && (*rnd)() % 2)
item.nested = RandomTree(rnd, depth - 1);
else
item.value = (*rnd)();
node->items.push_back(std::move(item));
}
return node;
}
// The reference proto group encoding: a nested message is its start tag, its
// content and a closing byte. The root has no framing.
void EncodeReference(const TreeNode& node, std::vector<uint8_t>* out) {
uint8_t varint[proto_utils::kMaxSimpleFieldEncodedSize];
for (const TreeNode::Item& item : node.items) {
if (item.nested) {
uint8_t* end = proto_utils::WriteVarInt(
proto_utils::MakeTagStartGroup(item.field_id), varint);
out->insert(out->end(), varint, end);
EncodeReference(*item.nested, out);
out->push_back(proto_utils::kProtoGroupEndByte);
} else {
uint8_t* end = proto_utils::WriteVarInt(
proto_utils::MakeTagVarInt(item.field_id), varint);
end = proto_utils::WriteVarInt(item.value, end);
out->insert(out->end(), varint, end);
}
}
}
// Writes |node| through protozero. Each nested message is finalized early at
// random, the way a nested MessageHandle finalizes it.
void WriteTree(const TreeNode& node, Message* msg, std::minstd_rand* rnd) {
for (const TreeNode::Item& item : node.items) {
if (item.nested) {
Message* child = msg->BeginNestedMessage<Message>(item.field_id);
WriteTree(*item.nested, child, rnd);
if ((*rnd)() % 2)
child->Finalize();
} else {
msg->AppendVarInt(item.field_id, item.value);
}
}
}
// The closing byte lands in the right place for any tree, any early
// finalization and any chunk size, including closes at a chunk boundary.
TEST_F(MessageTest, ProtoGroupRandomTreesMatchReferenceEncoding) {
std::minstd_rand rnd(0x2545f491);
for (int i = 0; i < 500; ++i) {
SCOPED_TRACE(i);
SetChunkSize(4 + static_cast<size_t>(i % 29));
const std::unique_ptr<TreeNode> tree = RandomTree(&rnd, /*depth=*/5);
std::vector<uint8_t> expected;
EncodeReference(*tree, &expected);
auto* root = NewMessage();
root->Reset(stream_writer_.get(), Message::Encoding::kProtoGroup);
WriteTree(*tree, root, &rnd);
ASSERT_EQ(expected.size(), root->Finalize());
ASSERT_EQ(expected.size(), GetNumSerializedBytes());
const char* expected_data = reinterpret_cast<const char*>(expected.data());
const std::string expected_hex = perfetto::base::ToUpper(
perfetto::base::ToHex(expected_data, expected.size()));
ASSERT_EQ(expected_hex, GetNextSerializedBytes(expected.size()));
}
}
} // namespace
} // namespace protozero