| /* |
| * 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 "perfetto/ext/base/utils.h" |
| #include "perfetto/protozero/message.h" |
| #include "perfetto/protozero/proto_utils.h" |
| #include "perfetto/protozero/scattered_heap_buffer.h" |
| #include "perfetto/protozero/static_buffer.h" |
| #include "test/gtest_and_gmock.h" |
| |
| #include "src/protozero/test/example_proto/test_messages.pb.h" |
| #include "src/protozero/test/example_proto/test_messages.pbzero.h" |
| |
| // Generated by the protozero plugin. |
| namespace pbtest = protozero::test::protos::pbzero; |
| |
| // Generated by the official protobuf compiler. |
| namespace pbgold = protozero::test::protos; |
| |
| 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, SkipVeryLargeFields) { |
| const size_t kPayloadSize = 257 * 1024 * 1024; |
| const uint64_t data_size = 4096 + kPayloadSize; |
| std::unique_ptr<uint8_t, perfetto::base::FreeDeleter> data( |
| static_cast<uint8_t*>(malloc(data_size))); |
| StaticBuffered<Message> message(data.get(), data_size); |
| |
| // Append a valid field. |
| message->AppendVarInt(/*field_id=*/1, 11); |
| |
| // Append a very large field that will be skipped. |
| uint8_t raw[10]; |
| uint8_t* wptr = raw; |
| wptr = WriteVarInt(MakeTagLengthDelimited(2), wptr); |
| wptr = WriteVarInt(kPayloadSize, wptr); |
| message->AppendRawProtoBytes(raw, static_cast<size_t>(wptr - raw)); |
| const uint8_t padding[1024 * 128]{}; |
| for (size_t i = 0; i < kPayloadSize / sizeof(padding); i++) |
| message->AppendRawProtoBytes(padding, sizeof(padding)); |
| |
| // Append another valid field. |
| message->AppendVarInt(/*field_id=*/3, 13); |
| |
| ProtoDecoder decoder(data.get(), message.Finalize()); |
| Field field = decoder.ReadField(); |
| ASSERT_EQ(1u, field.id()); |
| ASSERT_EQ(11, field.as_int32()); |
| |
| field = decoder.ReadField(); |
| ASSERT_EQ(3u, field.id()); |
| ASSERT_EQ(13, field.as_int32()); |
| |
| field = decoder.ReadField(); |
| ASSERT_FALSE(field.valid()); |
| } |
| |
| TEST(ProtoDecoderTest, SingleRepeatedField) { |
| HeapBuffered<Message> message; |
| message->AppendVarInt(/*field_id=*/2, 10); |
| auto data = message.SerializeAsArray(); |
| TypedProtoDecoder<2, true> tpd(data.data(), data.size()); |
| auto it = tpd.GetRepeated<int32_t>(/*field_id=*/2); |
| EXPECT_TRUE(it); |
| EXPECT_EQ(it.field().as_int32(), 10); |
| EXPECT_EQ(*it, 10); |
| EXPECT_FALSE(++it); |
| } |
| |
| TEST(ProtoDecoderTest, RepeatedVariableLengthField) { |
| HeapBuffered<Message> message; |
| |
| static constexpr char kTestString[] = "test"; |
| static constexpr char kTestString2[] = "honk honk"; |
| message->AppendString(1, kTestString); |
| message->AppendString(1, kTestString2); |
| std::vector<uint8_t> proto = message.SerializeAsArray(); |
| TypedProtoDecoder<32, false> decoder(proto.data(), proto.size()); |
| |
| auto it = decoder.GetRepeated<ConstChars>(1); |
| ASSERT_EQ(it->type(), ProtoWireType::kLengthDelimited); |
| ASSERT_EQ(it->size(), sizeof(kTestString) - 1); |
| ASSERT_EQ(it->as_std_string(), std::string(kTestString)); |
| ASSERT_EQ((*it).ToStdString(), std::string(kTestString)); |
| ++it; |
| ASSERT_EQ(it->type(), ProtoWireType::kLengthDelimited); |
| ASSERT_EQ(it->size(), sizeof(kTestString2) - 1); |
| ASSERT_EQ(it->as_std_string(), std::string(kTestString2)); |
| ASSERT_EQ((*it).ToStdString(), std::string(kTestString2)); |
| } |
| |
| TEST(ProtoDecoderTest, SingleRepeatedFieldWithExpansion) { |
| HeapBuffered<Message> message; |
| for (int i = 0; i < 2000; i++) { |
| message->AppendVarInt(/*field_id=*/2, i); |
| } |
| auto data = message.SerializeAsArray(); |
| TypedProtoDecoder<2, true> tpd(data.data(), data.size()); |
| auto it = tpd.GetRepeated<int32_t>(/*field_id=*/2); |
| for (int i = 0; i < 2000; i++) { |
| EXPECT_TRUE(it); |
| EXPECT_EQ(*it, i); |
| ++it; |
| } |
| EXPECT_FALSE(it); |
| } |
| |
| TEST(ProtoDecoderTest, NoRepeatedField) { |
| uint8_t buf[] = {0x01}; |
| TypedProtoDecoder<2, true> tpd(buf, 1); |
| auto it = tpd.GetRepeated<int32_t>(/*field_id=*/1); |
| EXPECT_FALSE(it); |
| EXPECT_FALSE(tpd.Get(2).valid()); |
| } |
| |
| TEST(ProtoDecoderTest, RepeatedFields) { |
| HeapBuffered<Message> message; |
| |
| message->AppendVarInt(1, 10); |
| message->AppendVarInt(2, 20); |
| message->AppendVarInt(3, 30); |
| |
| message->AppendVarInt(1, 11); |
| message->AppendVarInt(2, 21); |
| message->AppendVarInt(2, 22); |
| |
| // When iterating with the simple decoder we should just see fields in parsing |
| // order. |
| auto data = message.SerializeAsArray(); |
| ProtoDecoder decoder(data.data(), data.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(data.data(), data.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<int32_t>(1); |
| EXPECT_EQ(*it, 10); |
| EXPECT_EQ(*++it, 11); |
| EXPECT_FALSE(++it); |
| |
| it = tpd.GetRepeated<int32_t>(2); |
| EXPECT_EQ(*it++, 20); |
| EXPECT_EQ(*it++, 21); |
| EXPECT_EQ(*it++, 22); |
| EXPECT_FALSE(it); |
| |
| it = tpd.GetRepeated<int32_t>(3); |
| EXPECT_EQ(*it, 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, false> 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)); |
| } |
| |
| TEST(ProtoDecoderTest, PackedRepeatedVarint) { |
| std::vector<int32_t> values = {42, 255, 0, -1}; |
| |
| // serialize using protobuf library |
| pbgold::PackedRepeatedFields msg; |
| for (auto v : values) |
| msg.add_field_int32(v); |
| std::string serialized = msg.SerializeAsString(); |
| |
| // decode using TypedProtoDecoder directly |
| { |
| constexpr int kFieldId = |
| pbtest::PackedRepeatedFields::kFieldInt32FieldNumber; |
| TypedProtoDecoder<kFieldId, false> decoder( |
| reinterpret_cast<const uint8_t*>(serialized.data()), serialized.size()); |
| ASSERT_TRUE(decoder.at<kFieldId>().valid()); |
| bool parse_error = false; |
| auto packed_it = |
| decoder.GetPackedRepeated<proto_utils::ProtoWireType::kVarInt, int32_t>( |
| kFieldId, &parse_error); |
| |
| std::vector<int32_t> decoded_values; |
| for (; packed_it; ++packed_it) { |
| auto v = *packed_it; |
| decoded_values.push_back(v); |
| } |
| ASSERT_EQ(values, decoded_values); |
| ASSERT_FALSE(parse_error); |
| } |
| |
| // decode using plugin-generated accessor |
| { |
| auto decoder = pbtest::PackedRepeatedFields::Decoder(serialized); |
| ASSERT_TRUE(decoder.has_field_int32()); |
| |
| bool parse_error = false; |
| std::vector<int32_t> decoded_values; |
| for (auto packed_it = decoder.field_int32(&parse_error); packed_it; |
| ++packed_it) { |
| auto v = *packed_it; |
| decoded_values.push_back(v); |
| } |
| ASSERT_EQ(values, decoded_values); |
| ASSERT_FALSE(parse_error); |
| } |
| |
| // unset field case |
| pbgold::PackedRepeatedFields empty_msg; |
| std::string empty_serialized = empty_msg.SerializeAsString(); |
| auto decoder = pbtest::PackedRepeatedFields::Decoder(empty_serialized); |
| ASSERT_FALSE(decoder.has_field_int32()); |
| bool parse_error = false; |
| auto packed_it = decoder.field_int32(&parse_error); |
| ASSERT_FALSE(bool(packed_it)); |
| ASSERT_FALSE(parse_error); |
| } |
| |
| TEST(ProtoDecoderTest, PackedRepeatedFixed32) { |
| std::vector<uint32_t> values = {42, 255, 0, 1}; |
| |
| // serialize using protobuf library |
| pbgold::PackedRepeatedFields msg; |
| for (auto v : values) |
| msg.add_field_fixed32(v); |
| std::string serialized = msg.SerializeAsString(); |
| |
| // decode using TypedProtoDecoder directly |
| { |
| constexpr int kFieldId = |
| pbtest::PackedRepeatedFields::kFieldFixed32FieldNumber; |
| TypedProtoDecoder<kFieldId, false> decoder( |
| reinterpret_cast<const uint8_t*>(serialized.data()), serialized.size()); |
| bool parse_error = false; |
| auto packed_it = |
| decoder |
| .GetPackedRepeated<proto_utils::ProtoWireType::kFixed32, uint32_t>( |
| kFieldId, &parse_error); |
| |
| std::vector<uint32_t> decoded_values; |
| for (; packed_it; ++packed_it) { |
| auto v = *packed_it; |
| decoded_values.push_back(v); |
| } |
| ASSERT_EQ(values, decoded_values); |
| ASSERT_FALSE(parse_error); |
| } |
| |
| // decode using plugin-generated accessor |
| { |
| auto decoder = pbtest::PackedRepeatedFields::Decoder(serialized); |
| ASSERT_TRUE(decoder.has_field_fixed32()); |
| |
| bool parse_error = false; |
| std::vector<uint32_t> decoded_values; |
| for (auto packed_it = decoder.field_fixed32(&parse_error); packed_it; |
| packed_it++) { |
| auto v = *packed_it; |
| decoded_values.push_back(v); |
| } |
| ASSERT_EQ(values, decoded_values); |
| ASSERT_FALSE(parse_error); |
| } |
| |
| // unset field case |
| pbgold::PackedRepeatedFields empty_msg; |
| std::string empty_serialized = empty_msg.SerializeAsString(); |
| auto decoder = pbtest::PackedRepeatedFields::Decoder(empty_serialized); |
| ASSERT_FALSE(decoder.has_field_fixed32()); |
| bool parse_error = false; |
| auto packed_it = decoder.field_fixed32(&parse_error); |
| ASSERT_FALSE(bool(packed_it)); |
| ASSERT_FALSE(parse_error); |
| } |
| |
| TEST(ProtoDecoderTest, PackedRepeatedFixed64) { |
| std::vector<int64_t> values = {42, 255, 0, -1}; |
| |
| // serialize using protobuf library |
| pbgold::PackedRepeatedFields msg; |
| for (auto v : values) |
| msg.add_field_sfixed64(v); |
| std::string serialized = msg.SerializeAsString(); |
| |
| // decode using TypedProtoDecoder directly |
| { |
| constexpr int kFieldId = |
| pbtest::PackedRepeatedFields::kFieldSfixed64FieldNumber; |
| TypedProtoDecoder<kFieldId, false> decoder( |
| reinterpret_cast<const uint8_t*>(serialized.data()), serialized.size()); |
| bool parse_error = false; |
| auto packed_it = |
| decoder |
| .GetPackedRepeated<proto_utils::ProtoWireType::kFixed64, int64_t>( |
| kFieldId, &parse_error); |
| |
| std::vector<int64_t> decoded_values; |
| for (; packed_it; ++packed_it) { |
| auto v = *packed_it; |
| decoded_values.push_back(v); |
| } |
| ASSERT_EQ(values, decoded_values); |
| ASSERT_FALSE(parse_error); |
| } |
| |
| // decode using plugin-generated accessor |
| { |
| auto decoder = pbtest::PackedRepeatedFields::Decoder(serialized); |
| ASSERT_TRUE(decoder.has_field_sfixed64()); |
| |
| bool parse_error = false; |
| std::vector<int64_t> decoded_values; |
| for (auto packed_it = decoder.field_sfixed64(&parse_error); packed_it; |
| packed_it++) { |
| auto v = *packed_it; |
| decoded_values.push_back(v); |
| } |
| ASSERT_EQ(values, decoded_values); |
| ASSERT_FALSE(parse_error); |
| } |
| |
| // unset field case |
| pbgold::PackedRepeatedFields empty_msg; |
| std::string empty_serialized = empty_msg.SerializeAsString(); |
| auto decoder = pbtest::PackedRepeatedFields::Decoder(empty_serialized); |
| ASSERT_FALSE(decoder.has_field_sfixed64()); |
| bool parse_error = false; |
| auto packed_it = decoder.field_sfixed64(&parse_error); |
| ASSERT_FALSE(bool(packed_it)); |
| ASSERT_FALSE(parse_error); |
| } |
| |
| TEST(ProtoDecoderTest, ZeroLengthPackedRepeatedField) { |
| HeapBuffered<pbtest::PackedRepeatedFields> msg; |
| PackedVarInt buf; |
| msg->set_field_int32(buf); |
| std::string serialized = msg.SerializeAsString(); |
| |
| // Encoded as 2 bytes: tag/field, and a length of zero. |
| EXPECT_EQ(2u, serialized.size()); |
| |
| // Appears empty when decoded. |
| auto decoder = pbtest::PackedRepeatedFields::Decoder(serialized); |
| ASSERT_TRUE(decoder.has_field_int32()); |
| bool parse_error = false; |
| auto packed_it = decoder.field_int32(&parse_error); |
| ASSERT_FALSE(bool(packed_it)); |
| ASSERT_FALSE(parse_error); |
| } |
| |
| TEST(ProtoDecoderTest, MalformedPackedFixedBuffer) { |
| // Encode a fixed32 field where the length is not a multiple of 4 bytes. |
| HeapBuffered<pbtest::PackedRepeatedFields> msg; |
| PackedFixedSizeInt<uint32_t> buf; |
| buf.Append(1); |
| buf.Append(2); |
| buf.Append(3); |
| const uint8_t* data = buf.data(); |
| size_t size = buf.size(); |
| size_t invalid_size = size - 2; |
| constexpr int kFieldId = |
| pbtest::PackedRepeatedFields::kFieldFixed32FieldNumber; |
| msg->AppendBytes(kFieldId, data, invalid_size); |
| std::string serialized = msg.SerializeAsString(); |
| |
| // Iterator indicates parse error. |
| auto decoder = pbtest::PackedRepeatedFields::Decoder(serialized); |
| ASSERT_TRUE(decoder.has_field_fixed32()); |
| bool parse_error = false; |
| for (auto packed_it = decoder.field_fixed32(&parse_error); packed_it; |
| packed_it++) { |
| } |
| ASSERT_TRUE(parse_error); |
| } |
| |
| TEST(ProtoDecoderTest, MalformedPackedVarIntBuffer) { |
| // Encode a varint field with the last varint chopped off partway. |
| HeapBuffered<pbtest::PackedRepeatedFields> msg; |
| PackedVarInt buf; |
| buf.Append(1024); |
| buf.Append(2048); |
| buf.Append(4096); |
| const uint8_t* data = buf.data(); |
| size_t size = buf.size(); |
| size_t invalid_size = size - 1; |
| constexpr int kFieldId = pbtest::PackedRepeatedFields::kFieldInt32FieldNumber; |
| msg->AppendBytes(kFieldId, data, invalid_size); |
| std::string serialized = msg.SerializeAsString(); |
| |
| // Iterator indicates parse error. |
| auto decoder = pbtest::PackedRepeatedFields::Decoder(serialized); |
| ASSERT_TRUE(decoder.has_field_int32()); |
| bool parse_error = false; |
| for (auto packed_it = decoder.field_int32(&parse_error); packed_it; |
| packed_it++) { |
| } |
| ASSERT_TRUE(parse_error); |
| } |
| |
| // Tests that big field ids (> 0xffff) are just skipped but don't fail parsing. |
| // This is a regression test for b/145339282 (DataSourceConfig.for_testing |
| // having a very large ID == 268435455 until Android R). |
| TEST(ProtoDecoderTest, SkipBigFieldIds) { |
| HeapBuffered<Message> message; |
| message->AppendVarInt(/*field_id=*/1, 11); |
| message->AppendVarInt(/*field_id=*/1000000, 0); // Will be skipped |
| message->AppendVarInt(/*field_id=*/65535, 99); |
| message->AppendVarInt(/*field_id=*/268435455, 0); // Will be skipped |
| message->AppendVarInt(/*field_id=*/2, 12); |
| message->AppendVarInt(/*field_id=*/2000000, 0); // Will be skipped |
| auto data = message.SerializeAsArray(); |
| |
| // Check the iterator-based ProtoDecoder. |
| { |
| ProtoDecoder decoder(data.data(), data.size()); |
| Field field = decoder.ReadField(); |
| ASSERT_TRUE(field.valid()); |
| ASSERT_EQ(field.id(), 1u); |
| ASSERT_EQ(field.as_int32(), 11); |
| |
| field = decoder.ReadField(); |
| ASSERT_TRUE(field.valid()); |
| ASSERT_EQ(field.id(), 65535u); |
| ASSERT_EQ(field.as_int32(), 99); |
| |
| field = decoder.ReadField(); |
| ASSERT_TRUE(field.valid()); |
| ASSERT_EQ(field.id(), 2u); |
| ASSERT_EQ(field.as_int32(), 12); |
| |
| field = decoder.ReadField(); |
| ASSERT_FALSE(field.valid()); |
| } |
| |
| // Test the one-shot-read TypedProtoDecoder. |
| // Note: field 65535 will be also skipped because this TypedProtoDecoder has |
| // a cap on MAX_FIELD_ID = 3. |
| { |
| TypedProtoDecoder<3, true> tpd(data.data(), data.size()); |
| EXPECT_EQ(tpd.Get(1).as_int32(), 11); |
| EXPECT_EQ(tpd.Get(2).as_int32(), 12); |
| } |
| } |
| |
| // Edge case for SkipBigFieldIds, the message contains only one field with a |
| // very big id. Test that we skip it and return an invalid field, instead of |
| // geetting stuck in some loop. |
| TEST(ProtoDecoderTest, OneBigFieldIdOnly) { |
| HeapBuffered<Message> message; |
| message->AppendVarInt(/*field_id=*/268435455, 0); |
| auto data = message.SerializeAsArray(); |
| |
| // Check the iterator-based ProtoDecoder. |
| ProtoDecoder decoder(data.data(), data.size()); |
| Field field = decoder.ReadField(); |
| ASSERT_FALSE(field.valid()); |
| } |
| |
| } // namespace |
| } // namespace protozero |