blob: 9bcdac4580dd9805a27bf831aa0605cdc5c3d4ef [file] [log] [blame]
/*
* Copyright (C) 2018 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/proto_decoder.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "perfetto/ext/base/utils.h"
#include "perfetto/protozero/message.h"
#include "perfetto/protozero/proto_utils.h"
#include "perfetto/protozero/scattered_heap_buffer.h"
namespace protozero {
namespace {
using ::testing::_;
using ::testing::InSequence;
using ::testing::Invoke;
using namespace proto_utils;
TEST(ProtoDecoderTest, ReadString) {
HeapBuffered<Message> message;
static constexpr char kTestString[] = "test";
message->AppendString(1, kTestString);
std::vector<uint8_t> proto = message.SerializeAsArray();
TypedProtoDecoder<32, false> decoder(proto.data(), proto.size());
const auto& field = decoder.Get(1);
ASSERT_EQ(field.type(), ProtoWireType::kLengthDelimited);
ASSERT_EQ(field.size(), sizeof(kTestString) - 1);
for (size_t i = 0; i < sizeof(kTestString) - 1; i++) {
ASSERT_EQ(field.data()[i], kTestString[i]);
}
}
TEST(ProtoDecoderTest, VeryLargeField) {
const uint64_t size = 512 * 1024 * 1024 + 6;
std::unique_ptr<uint8_t, perfetto::base::FreeDeleter> data(
static_cast<uint8_t*>(malloc(size)));
data.get()[0] = static_cast<unsigned char>('\x0A');
data.get()[1] = static_cast<unsigned char>('\x80');
data.get()[2] = static_cast<unsigned char>('\x80');
data.get()[3] = static_cast<unsigned char>('\x80');
data.get()[4] = static_cast<unsigned char>('\x80');
data.get()[5] = static_cast<unsigned char>('\x02');
ProtoDecoder decoder(data.get(), size);
Field field = decoder.ReadField();
ASSERT_EQ(1u, field.id());
ASSERT_EQ(nullptr, field.data());
ASSERT_EQ(0u, field.size());
ASSERT_EQ(0u, decoder.bytes_left());
}
TEST(ProtoDecoderTest, SingleRepeatedField) {
Message message;
ScatteredHeapBuffer delegate(512, 512);
ScatteredStreamWriter writer(&delegate);
delegate.set_writer(&writer);
message.Reset(&writer);
message.AppendVarInt(/*field_id=*/2, 10);
delegate.AdjustUsedSizeOfCurrentSlice();
auto used_range = delegate.slices()[0].GetUsedRange();
TypedProtoDecoder<2, true> tpd(used_range.begin, used_range.size());
auto it = tpd.GetRepeated(/*field_id=*/2);
EXPECT_TRUE(it);
EXPECT_EQ(it->as_int32(), 10);
EXPECT_FALSE(++it);
}
TEST(ProtoDecoderTest, SingleRepeatedFieldWithExpansion) {
Message message;
ScatteredHeapBuffer delegate(512, 512);
ScatteredStreamWriter writer(&delegate);
delegate.set_writer(&writer);
message.Reset(&writer);
for (int i = 0; i < 2000; i++) {
message.AppendVarInt(/*field_id=*/2, i);
}
std::vector<uint8_t> data = delegate.StitchSlices();
TypedProtoDecoder<2, true> tpd(data.data(), data.size());
auto it = tpd.GetRepeated(/*field_id=*/2);
for (int i = 0; i < 2000; i++) {
EXPECT_TRUE(it);
EXPECT_EQ(it->as_int32(), i);
++it;
}
EXPECT_FALSE(it);
}
TEST(ProtoDecoderTest, NoRepeatedField) {
uint8_t buf[] = {0x01};
TypedProtoDecoder<2, true> tpd(buf, 1);
auto it = tpd.GetRepeated(/*field_id=*/1);
EXPECT_FALSE(it);
EXPECT_FALSE(tpd.Get(2).valid());
}
TEST(ProtoDecoderTest, RepeatedFields) {
Message message;
ScatteredHeapBuffer delegate(512, 512);
ScatteredStreamWriter writer(&delegate);
delegate.set_writer(&writer);
message.Reset(&writer);
message.AppendVarInt(1, 10);
message.AppendVarInt(2, 20);
message.AppendVarInt(3, 30);
message.AppendVarInt(1, 11);
message.AppendVarInt(2, 21);
message.AppendVarInt(2, 22);
delegate.AdjustUsedSizeOfCurrentSlice();
auto used_range = delegate.slices()[0].GetUsedRange();
// When iterating with the simple decoder we should just see fields in parsing
// order.
ProtoDecoder decoder(used_range.begin, used_range.size());
std::string fields_seen;
for (auto fld = decoder.ReadField(); fld.valid(); fld = decoder.ReadField()) {
fields_seen +=
std::to_string(fld.id()) + ":" + std::to_string(fld.as_int32()) + ";";
}
EXPECT_EQ(fields_seen, "1:10;2:20;3:30;1:11;2:21;2:22;");
TypedProtoDecoder<4, true> tpd(used_range.begin, used_range.size());
// When parsing with the one-shot decoder and querying the single field id, we
// should see the last value for each of them, not the first one. This is the
// current behavior of Google protobuf's parser.
EXPECT_EQ(tpd.Get(1).as_int32(), 11);
EXPECT_EQ(tpd.Get(2).as_int32(), 22);
EXPECT_EQ(tpd.Get(3).as_int32(), 30);
// But when iterating we should see values in the original order.
auto it = tpd.GetRepeated(1);
EXPECT_EQ(it->as_int32(), 10);
EXPECT_EQ((++it)->as_int32(), 11);
EXPECT_FALSE(++it);
it = tpd.GetRepeated(2);
EXPECT_EQ((it++)->as_int32(), 20);
EXPECT_EQ((it++)->as_int32(), 21);
EXPECT_EQ((it++)->as_int32(), 22);
EXPECT_FALSE(it);
it = tpd.GetRepeated(3);
EXPECT_EQ(it->as_int32(), 30);
EXPECT_FALSE(++it);
}
TEST(ProtoDecoderTest, FixedData) {
struct FieldExpectation {
const char* encoded;
size_t encoded_size;
uint32_t id;
ProtoWireType type;
uint64_t int_value;
};
const FieldExpectation kFieldExpectations[] = {
{"\x08\x00", 2, 1, ProtoWireType::kVarInt, 0},
{"\x08\x01", 2, 1, ProtoWireType::kVarInt, 1},
{"\x08\x42", 2, 1, ProtoWireType::kVarInt, 0x42},
{"\xF8\x07\x42", 3, 127, ProtoWireType::kVarInt, 0x42},
{"\xB8\x3E\xFF\xFF\xFF\xFF\x0F", 7, 999, ProtoWireType::kVarInt,
0xFFFFFFFF},
{"\x7D\x42\x00\x00\x00", 5, 15, ProtoWireType::kFixed32, 0x42},
{"\xBD\x3E\x78\x56\x34\x12", 6, 999, ProtoWireType::kFixed32, 0x12345678},
{"\x79\x42\x00\x00\x00\x00\x00\x00\x00", 9, 15, ProtoWireType::kFixed64,
0x42},
{"\xB9\x3E\x08\x07\x06\x05\x04\x03\x02\x01", 10, 999,
ProtoWireType::kFixed64, 0x0102030405060708},
{"\x0A\x00", 2, 1, ProtoWireType::kLengthDelimited, 0},
{"\x0A\x04|abc", 6, 1, ProtoWireType::kLengthDelimited, 4},
{"\xBA\x3E\x04|abc", 7, 999, ProtoWireType::kLengthDelimited, 4},
{"\xBA\x3E\x83\x01|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzab"
"cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstu"
"vwx",
135, 999, ProtoWireType::kLengthDelimited, 131},
};
for (size_t i = 0; i < perfetto::base::ArraySize(kFieldExpectations); ++i) {
const FieldExpectation& exp = kFieldExpectations[i];
TypedProtoDecoder<999, 0> decoder(
reinterpret_cast<const uint8_t*>(exp.encoded), exp.encoded_size);
auto& field = decoder.Get(exp.id);
ASSERT_EQ(exp.type, field.type());
if (field.type() == ProtoWireType::kLengthDelimited) {
ASSERT_EQ(exp.int_value, field.size());
} else {
ASSERT_EQ(int64_t(exp.int_value), field.as_int64());
// Proto encodes booleans as varints of 0 or 1.
if (exp.int_value == 0 || exp.int_value == 1) {
ASSERT_EQ(int64_t(exp.int_value), field.as_bool());
}
}
}
// Test float and doubles decoding.
const char buf[] = "\x0d\x00\x00\xa0\x3f\x11\x00\x00\x00\x00\x00\x42\x8f\xc0";
TypedProtoDecoder<2, 0> decoder(reinterpret_cast<const uint8_t*>(buf),
sizeof(buf));
EXPECT_FLOAT_EQ(decoder.Get(1).as_float(), 1.25f);
EXPECT_DOUBLE_EQ(decoder.Get(2).as_double(), -1000.25);
}
TEST(ProtoDecoderTest, FindField) {
uint8_t buf[] = {0x08, 0x00}; // field_id 1, varint value 0.
ProtoDecoder pd(buf, 2);
auto field = pd.FindField(1);
ASSERT_TRUE(field);
EXPECT_EQ(field.as_int64(), 0);
auto field2 = pd.FindField(2);
EXPECT_FALSE(field2);
}
TEST(ProtoDecoderTest, MoveTypedDecoder) {
HeapBuffered<Message> message;
message->AppendVarInt(/*field_id=*/1, 10);
std::vector<uint8_t> proto = message.SerializeAsArray();
// Construct a decoder that uses inline storage (i.e., the fields are stored
// within the object itself).
using Decoder = TypedProtoDecoder<32, false>;
std::unique_ptr<Decoder> decoder(new Decoder(proto.data(), proto.size()));
ASSERT_GE(reinterpret_cast<uintptr_t>(&decoder->at<1>()),
reinterpret_cast<uintptr_t>(decoder.get()));
ASSERT_LT(reinterpret_cast<uintptr_t>(&decoder->at<1>()),
reinterpret_cast<uintptr_t>(decoder.get()) + sizeof(Decoder));
// Move the decoder into another object and deallocate the original object.
Decoder decoder2(std::move(*decoder));
decoder.reset();
// Check that the contents got moved correctly.
EXPECT_EQ(decoder2.Get(1).as_int32(), 10);
ASSERT_GE(reinterpret_cast<uintptr_t>(&decoder2.at<1>()),
reinterpret_cast<uintptr_t>(&decoder2));
ASSERT_LT(reinterpret_cast<uintptr_t>(&decoder2.at<1>()),
reinterpret_cast<uintptr_t>(&decoder2) + sizeof(Decoder));
}
} // namespace
} // namespace protozero