Pivot source files into top-level src,include and unify test target
See discussion in go/perfetto-build-files .
This is to disambiguate things like
#include "base/logging.h"
when in the chrome tree.
Also this CL unifies the test targets into two monolithic targets:
perfetto_tests and perfetto_benchmarks. This is to avoid ending
up with confusing binary names in the chrome tree (e.g.,
ipc_unittests)
Bug: 68710794
Change-Id: I1768e15b661406052b2be060d7aab0f1e7443a98
diff --git a/src/protozero/BUILD.gn b/src/protozero/BUILD.gn
new file mode 100644
index 0000000..885d934
--- /dev/null
+++ b/src/protozero/BUILD.gn
@@ -0,0 +1,77 @@
+# 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.
+
+import("../../gn/perfetto.gni")
+import("../../gn/proto_library.gni")
+import("protozero_library.gni")
+
+source_set("protozero") {
+ public_configs = [ "../../gn:default_config" ]
+ public_deps = [
+ "../../include/perfetto/protozero",
+ ]
+ deps = [
+ "../../gn:default_deps",
+ "../../gn:gtest_prod_config",
+ "../base",
+ ]
+ sources = [
+ "proto_utils.cc",
+ "protozero_message.cc",
+ "protozero_message_handle.cc",
+ "scattered_stream_writer.cc",
+ ]
+}
+
+executable("protozero_unittests") {
+ testonly = true
+ deps = [
+ ":protozero",
+ ":testing_messages_lite",
+ ":testing_messages_zero",
+ "../../gn:default_deps",
+ "../../gn:gtest_deps",
+ "../base",
+ ]
+ sources = [
+ "proto_utils_unittest.cc",
+ "protozero_message_unittest.cc",
+ "scattered_stream_writer_unittest.cc",
+ "test/fake_scattered_buffer.cc",
+ "test/fake_scattered_buffer.h",
+ "test/protozero_conformance_unittest.cc",
+ ]
+}
+
+# Generates both xxx.pbzero.h and xxx.pb.h (official proto).
+
+testing_proto_sources = [
+ "test/example_proto/library.proto",
+ "test/example_proto/library_internals/galaxies.proto",
+ "test/example_proto/test_messages.proto",
+ "test/example_proto/upper_import.proto",
+]
+
+protozero_library("testing_messages_zero") {
+ sources = testing_proto_sources
+ proto_in_dir = perfetto_root_path
+ proto_out_dir = "protos_zero"
+ generator_plugin_options = "wrapper_namespace=pbzero"
+}
+
+proto_library("testing_messages_lite") {
+ sources = testing_proto_sources
+ proto_in_dir = perfetto_root_path
+ proto_out_dir = "protos_lite"
+}
diff --git a/src/protozero/README.md b/src/protozero/README.md
new file mode 100644
index 0000000..113961d
--- /dev/null
+++ b/src/protozero/README.md
@@ -0,0 +1,12 @@
+ProtoZero
+---------
+
+ProtoZero is a zero-copy zero-malloc append-only protobuf library.
+It's designed to be fast and efficient at the cost of a reduced API
+surface for generated stubs. The main limitations consist of:
+- Append-only interface: no readbacks are possible from the stubs.
+- No runtime checks for duplicated or missing mandatory fields.
+- Mandatory ordering when writing of nested messages: once a nested message is
+ started it must be completed before adding any fields to its parent.
+
+See also: [Design doc](https://goo.gl/EKvEfa]).
diff --git a/src/protozero/proto_utils.cc b/src/protozero/proto_utils.cc
new file mode 100644
index 0000000..8d1254a
--- /dev/null
+++ b/src/protozero/proto_utils.cc
@@ -0,0 +1,108 @@
+/*
+ * 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/proto_utils.h"
+
+#include <string.h>
+
+#include <limits>
+
+#include "perfetto/base/logging.h"
+
+#define PERFETTO_CHECK_PTR_LE(a, b) \
+ PERFETTO_CHECK(reinterpret_cast<uintptr_t>(a) <= \
+ reinterpret_cast<uintptr_t>(b))
+
+namespace protozero {
+namespace proto_utils {
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define BYTE_SWAP_TO_LE32(x) (x)
+#define BYTE_SWAP_TO_LE64(x) (x)
+#else
+#error Unimplemented for big endian archs.
+#endif
+
+const uint8_t* ParseVarInt(const uint8_t* start,
+ const uint8_t* end,
+ uint64_t* value) {
+ const uint8_t* pos = start;
+ uint64_t shift = 0;
+ *value = 0;
+ do {
+ PERFETTO_CHECK_PTR_LE(pos, end - 1);
+ PERFETTO_DCHECK(shift < 64ull);
+ *value |= static_cast<uint64_t>(*pos & 0x7f) << shift;
+ shift += 7;
+ } while (*pos++ & 0x80);
+ return pos;
+}
+
+const uint8_t* ParseField(const uint8_t* start,
+ const uint8_t* end,
+ uint32_t* field_id,
+ FieldType* field_type,
+ uint64_t* field_intvalue) {
+ // The first byte of a proto field is structured as follows:
+ // The least 3 significant bits determine the field type.
+ // The most 5 significant bits determine the field id. If MSB == 1, the
+ // field id continues on the next bytes following the VarInt encoding.
+ const uint8_t kFieldTypeNumBits = 3;
+ const uint8_t kFieldTypeMask = (1 << kFieldTypeNumBits) - 1; // 0000 0111;
+
+ const uint8_t* pos = start;
+ PERFETTO_CHECK_PTR_LE(pos, end - 1);
+ *field_type = static_cast<FieldType>(*pos & kFieldTypeMask);
+
+ uint64_t raw_field_id;
+ pos = ParseVarInt(pos, end, &raw_field_id);
+ raw_field_id >>= kFieldTypeNumBits;
+
+ PERFETTO_DCHECK(raw_field_id <= std::numeric_limits<uint32_t>::max());
+ *field_id = static_cast<uint32_t>(raw_field_id);
+
+ switch (*field_type) {
+ case kFieldTypeFixed64: {
+ PERFETTO_CHECK_PTR_LE(pos + sizeof(uint64_t), end);
+ memcpy(field_intvalue, pos, sizeof(uint64_t));
+ *field_intvalue = BYTE_SWAP_TO_LE64(*field_intvalue);
+ pos += sizeof(uint64_t);
+ break;
+ }
+ case kFieldTypeFixed32: {
+ PERFETTO_CHECK_PTR_LE(pos + sizeof(uint32_t), end);
+ uint32_t tmp;
+ memcpy(&tmp, pos, sizeof(uint32_t));
+ *field_intvalue = BYTE_SWAP_TO_LE32(tmp);
+ pos += sizeof(uint32_t);
+ break;
+ }
+ case kFieldTypeVarInt: {
+ pos = ParseVarInt(pos, end, field_intvalue);
+ break;
+ }
+ case kFieldTypeLengthDelimited: {
+ pos = ParseVarInt(pos, end, field_intvalue);
+ pos += *field_intvalue;
+ PERFETTO_CHECK_PTR_LE(pos, end);
+ break;
+ }
+ }
+ return pos;
+}
+
+} // namespace proto_utils
+} // namespace protozero
diff --git a/src/protozero/proto_utils_unittest.cc b/src/protozero/proto_utils_unittest.cc
new file mode 100644
index 0000000..2ad6677
--- /dev/null
+++ b/src/protozero/proto_utils_unittest.cc
@@ -0,0 +1,206 @@
+/*
+ * 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/proto_utils.h"
+
+#include <limits>
+
+#include "gtest/gtest.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/base/utils.h"
+
+namespace protozero {
+namespace proto_utils {
+namespace {
+
+using ::perfetto::base::ArraySize;
+
+struct VarIntExpectation {
+ const char* encoded;
+ size_t encoded_size;
+ uint64_t int_value;
+};
+
+const VarIntExpectation kVarIntExpectations[] = {
+ {"\x00", 1, 0},
+ {"\x01", 1, 0x1},
+ {"\x7f", 1, 0x7F},
+ {"\xFF\x01", 2, 0xFF},
+ {"\xFF\x7F", 2, 0x3FFF},
+ {"\x80\x80\x01", 3, 0x4000},
+ {"\xFF\xFF\x7F", 3, 0x1FFFFF},
+ {"\x80\x80\x80\x01", 4, 0x200000},
+ {"\xFF\xFF\xFF\x7F", 4, 0xFFFFFFF},
+ {"\x80\x80\x80\x80\x01", 5, 0x10000000},
+ {"\xFF\xFF\xFF\xFF\x0F", 5, 0xFFFFFFFF},
+ {"\x80\x80\x80\x80\x10", 5, 0x100000000},
+ {"\xFF\xFF\xFF\xFF\x7F", 5, 0x7FFFFFFFF},
+ {"\x80\x80\x80\x80\x80\x01", 6, 0x800000000},
+ {"\xFF\xFF\xFF\xFF\xFF\x7F", 6, 0x3FFFFFFFFFF},
+ {"\x80\x80\x80\x80\x80\x80\x01", 7, 0x40000000000},
+ {"\xFF\xFF\xFF\xFF\xFF\xFF\x7F", 7, 0x1FFFFFFFFFFFF},
+ {"\x80\x80\x80\x80\x80\x80\x80\x01", 8, 0x2000000000000},
+ {"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F", 8, 0xFFFFFFFFFFFFFF},
+ {"\x80\x80\x80\x80\x80\x80\x80\x80\x01", 9, 0x100000000000000},
+ {"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F", 9, 0x7FFFFFFFFFFFFFFF},
+ {"\x80\x80\x80\x80\x80\x80\x80\x80\x80\x01", 10, 0x8000000000000000},
+ {"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01", 10, 0xFFFFFFFFFFFFFFFF},
+};
+
+TEST(ProtoUtilsTest, FieldPreambleEncoding) {
+ // According to C++ standard, right shift of negative value has
+ // implementation-defined resulting value.
+ if ((static_cast<int32_t>(0x80000000u) >> 31) != -1)
+ FAIL() << "Platform has unsupported negative number format or arithmetic";
+
+ EXPECT_EQ(0x08u, MakeTagVarInt(1));
+ EXPECT_EQ(0x09u, MakeTagFixed<uint64_t>(1));
+ EXPECT_EQ(0x0Au, MakeTagLengthDelimited(1));
+ EXPECT_EQ(0x0Du, MakeTagFixed<uint32_t>(1));
+
+ EXPECT_EQ(0x03F8u, MakeTagVarInt(0x7F));
+ EXPECT_EQ(0x03F9u, MakeTagFixed<int64_t>(0x7F));
+ EXPECT_EQ(0x03FAu, MakeTagLengthDelimited(0x7F));
+ EXPECT_EQ(0x03FDu, MakeTagFixed<int32_t>(0x7F));
+
+ EXPECT_EQ(0x0400u, MakeTagVarInt(0x80));
+ EXPECT_EQ(0x0401u, MakeTagFixed<double>(0x80));
+ EXPECT_EQ(0x0402u, MakeTagLengthDelimited(0x80));
+ EXPECT_EQ(0x0405u, MakeTagFixed<float>(0x80));
+
+ EXPECT_EQ(0x01FFF8u, MakeTagVarInt(0x3fff));
+ EXPECT_EQ(0x01FFF9u, MakeTagFixed<int64_t>(0x3fff));
+ EXPECT_EQ(0x01FFFAu, MakeTagLengthDelimited(0x3fff));
+ EXPECT_EQ(0x01FFFDu, MakeTagFixed<int32_t>(0x3fff));
+
+ EXPECT_EQ(0x020000u, MakeTagVarInt(0x4000));
+ EXPECT_EQ(0x020001u, MakeTagFixed<int64_t>(0x4000));
+ EXPECT_EQ(0x020002u, MakeTagLengthDelimited(0x4000));
+ EXPECT_EQ(0x020005u, MakeTagFixed<int32_t>(0x4000));
+}
+
+TEST(ProtoUtilsTest, ZigZagEncoding) {
+ EXPECT_EQ(0u, ZigZagEncode(0));
+ EXPECT_EQ(1u, ZigZagEncode(-1));
+ EXPECT_EQ(2u, ZigZagEncode(1));
+ EXPECT_EQ(3u, ZigZagEncode(-2));
+ EXPECT_EQ(4294967293u, ZigZagEncode(-2147483647));
+ EXPECT_EQ(4294967294u, ZigZagEncode(2147483647));
+ EXPECT_EQ(std::numeric_limits<uint32_t>::max(),
+ ZigZagEncode(std::numeric_limits<int32_t>::min()));
+ EXPECT_EQ(std::numeric_limits<uint64_t>::max(),
+ ZigZagEncode(std::numeric_limits<int64_t>::min()));
+}
+
+TEST(ProtoUtilsTest, VarIntEncoding) {
+ for (size_t i = 0; i < ArraySize(kVarIntExpectations); ++i) {
+ const VarIntExpectation& exp = kVarIntExpectations[i];
+ uint8_t buf[32];
+ uint8_t* res = WriteVarInt<uint64_t>(exp.int_value, buf);
+ ASSERT_EQ(exp.encoded_size, static_cast<size_t>(res - buf));
+ ASSERT_EQ(0, memcmp(buf, exp.encoded, exp.encoded_size));
+
+ if (exp.int_value <= std::numeric_limits<uint32_t>::max()) {
+ uint8_t* res_32 =
+ WriteVarInt<uint32_t>(static_cast<uint32_t>(exp.int_value), buf);
+ ASSERT_EQ(exp.encoded_size, static_cast<size_t>(res_32 - buf));
+ ASSERT_EQ(0, memcmp(buf, exp.encoded, exp.encoded_size));
+ }
+ }
+}
+
+TEST(ProtoUtilsTest, RedundantVarIntEncoding) {
+ uint8_t buf[kMessageLengthFieldSize];
+
+ WriteRedundantVarInt(0, buf);
+ EXPECT_EQ(0, memcmp("\x80\x80\x80\x00", buf, sizeof(buf)));
+
+ WriteRedundantVarInt(1, buf);
+ EXPECT_EQ(0, memcmp("\x81\x80\x80\x00", buf, sizeof(buf)));
+
+ WriteRedundantVarInt(0x80, buf);
+ EXPECT_EQ(0, memcmp("\x80\x81\x80\x00", buf, sizeof(buf)));
+
+ WriteRedundantVarInt(0x332211, buf);
+ EXPECT_EQ(0, memcmp("\x91\xC4\xCC\x01", buf, sizeof(buf)));
+
+ // Largest allowed length.
+ WriteRedundantVarInt(0x0FFFFFFF, buf);
+ EXPECT_EQ(0, memcmp("\xFF\xFF\xFF\x7F", buf, sizeof(buf)));
+}
+
+TEST(ProtoUtilsTest, VarIntDecoding) {
+ for (size_t i = 0; i < ArraySize(kVarIntExpectations); ++i) {
+ const VarIntExpectation& exp = kVarIntExpectations[i];
+ uint64_t value = std::numeric_limits<uint64_t>::max();
+ const uint8_t* res = ParseVarInt(
+ reinterpret_cast<const uint8_t*>(exp.encoded),
+ reinterpret_cast<const uint8_t*>(exp.encoded + exp.encoded_size),
+ &value);
+ ASSERT_EQ(reinterpret_cast<const void*>(exp.encoded + exp.encoded_size),
+ reinterpret_cast<const void*>(res));
+ ASSERT_EQ(exp.int_value, value);
+ }
+}
+
+TEST(ProtoUtilsTest, FieldDecoding) {
+ struct FieldExpectation {
+ const char* encoded;
+ size_t encoded_size;
+ uint32_t id;
+ FieldType type;
+ uint64_t int_value;
+ };
+
+ const FieldExpectation kFieldExpectations[] = {
+ {"\x08\x00", 2, 1, kFieldTypeVarInt, 0},
+ {"\x08\x42", 2, 1, kFieldTypeVarInt, 0x42},
+ {"\xF8\x07\x42", 3, 127, kFieldTypeVarInt, 0x42},
+ {"\x90\x4D\xFF\xFF\xFF\xFF\x0F", 7, 1234, kFieldTypeVarInt, 0xFFFFFFFF},
+ {"\x7D\x42\x00\x00\x00", 5, 15, kFieldTypeFixed32, 0x42},
+ {"\x95\x4D\x78\x56\x34\x12", 6, 1234, kFieldTypeFixed32, 0x12345678},
+ {"\x79\x42\x00\x00\x00\x00\x00\x00\x00", 9, 15, kFieldTypeFixed64, 0x42},
+ {"\x91\x4D\x08\x07\x06\x05\x04\x03\x02\x01", 10, 1234, kFieldTypeFixed64,
+ 0x0102030405060708},
+ {"\x0A\x00", 2, 1, kFieldTypeLengthDelimited, 0},
+ {"\x0A\x04|abc", 6, 1, kFieldTypeLengthDelimited, 4},
+ {"\x92\x4D\x04|abc", 7, 1234, kFieldTypeLengthDelimited, 4},
+ {"\x92\x4D\x83\x01|abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzab"
+ "cdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstu"
+ "vwx",
+ 135, 1234, kFieldTypeLengthDelimited, 131},
+ };
+
+ for (size_t i = 0; i < ArraySize(kFieldExpectations); ++i) {
+ const FieldExpectation& exp = kFieldExpectations[i];
+ FieldType field_type = kFieldTypeVarInt;
+ uint32_t field_id = std::numeric_limits<uint32_t>::max();
+ uint64_t field_intvalue = std::numeric_limits<uint64_t>::max();
+ const uint8_t* res = ParseField(
+ reinterpret_cast<const uint8_t*>(exp.encoded),
+ reinterpret_cast<const uint8_t*>(exp.encoded + exp.encoded_size),
+ &field_id, &field_type, &field_intvalue);
+ ASSERT_EQ(reinterpret_cast<const void*>(exp.encoded + exp.encoded_size),
+ reinterpret_cast<const void*>(res));
+ ASSERT_EQ(exp.id, field_id);
+ ASSERT_EQ(exp.type, field_type);
+ ASSERT_EQ(exp.int_value, field_intvalue);
+ }
+}
+
+} // namespace
+} // namespace proto_utils
+} // namespace protozero
diff --git a/src/protozero/protoc_plugin/BUILD.gn b/src/protozero/protoc_plugin/BUILD.gn
new file mode 100644
index 0000000..ca56cea
--- /dev/null
+++ b/src/protozero/protoc_plugin/BUILD.gn
@@ -0,0 +1,31 @@
+# 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.
+
+if (current_toolchain == host_toolchain) {
+ executable("protoc_plugin") {
+ sources = [
+ "protozero_generator.cc",
+ "protozero_generator.h",
+ "protozero_plugin.cc",
+ ]
+ deps = [
+ "../../../gn:default_deps",
+ "../../../gn:protoc_lib_deps",
+ ]
+ if (is_clang) {
+ # Internal protobuf headers hit this.
+ cflags = [ "-Wno-unreachable-code" ]
+ }
+ }
+} # host_toolchain
diff --git a/src/protozero/protoc_plugin/protozero_generator.cc b/src/protozero/protoc_plugin/protozero_generator.cc
new file mode 100644
index 0000000..9a3ead9
--- /dev/null
+++ b/src/protozero/protoc_plugin/protozero_generator.cc
@@ -0,0 +1,651 @@
+/*
+ * 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 "src/protozero/protoc_plugin/protozero_generator.h"
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+
+#include "google/protobuf/descriptor.h"
+#include "google/protobuf/io/printer.h"
+#include "google/protobuf/io/zero_copy_stream.h"
+#include "google/protobuf/stubs/strutil.h"
+
+namespace protozero {
+
+using google::protobuf::Descriptor; // Message descriptor.
+using google::protobuf::EnumDescriptor;
+using google::protobuf::EnumValueDescriptor;
+using google::protobuf::FieldDescriptor;
+using google::protobuf::FileDescriptor;
+using google::protobuf::compiler::GeneratorContext;
+using google::protobuf::io::Printer;
+using google::protobuf::io::ZeroCopyOutputStream;
+
+using google::protobuf::Split;
+using google::protobuf::StripPrefixString;
+using google::protobuf::StripString;
+using google::protobuf::StripSuffixString;
+using google::protobuf::UpperString;
+
+namespace {
+
+inline std::string ProtoStubName(const FileDescriptor* proto) {
+ return StripSuffixString(proto->name(), ".proto") + ".pbzero";
+}
+
+class GeneratorJob {
+ public:
+ GeneratorJob(const FileDescriptor* file,
+ Printer* stub_h_printer,
+ Printer* stub_cc_printer)
+ : source_(file), stub_h_(stub_h_printer), stub_cc_(stub_cc_printer) {}
+
+ bool GenerateStubs() {
+ Preprocess();
+ GeneratePrologue();
+ for (const EnumDescriptor* enumeration : enums_)
+ GenerateEnumDescriptor(enumeration);
+ for (const Descriptor* message : messages_)
+ GenerateMessageDescriptor(message);
+ GenerateEpilogue();
+ return error_.empty();
+ }
+
+ void SetOption(const std::string& name, const std::string& value) {
+ if (name == "wrapper_namespace") {
+ wrapper_namespace_ = value;
+ } else {
+ Abort(std::string() + "Unknown plugin option '" + name + "'.");
+ }
+ }
+
+ // If generator fails to produce stubs for a particular proto definitions
+ // it finishes with undefined output and writes the first error occured.
+ const std::string& GetFirstError() const { return error_; }
+
+ private:
+ // Only the first error will be recorded.
+ void Abort(const std::string& reason) {
+ if (error_.empty())
+ error_ = reason;
+ }
+
+ // Get full name (including outer descriptors) of proto descriptor.
+ template <class T>
+ inline std::string GetDescriptorName(const T* descriptor) {
+ if (!package_.empty()) {
+ return StripPrefixString(descriptor->full_name(), package_ + ".");
+ } else {
+ return descriptor->full_name();
+ }
+ }
+
+ // Get C++ class name corresponding to proto descriptor.
+ // Nested names are splitted by underscores. Underscores in type names aren't
+ // prohibited but not recommended in order to avoid name collisions.
+ template <class T>
+ inline std::string GetCppClassName(const T* descriptor, bool full = false) {
+ std::string name = GetDescriptorName(descriptor);
+ StripString(&name, ".", '_');
+ if (full)
+ name = full_namespace_prefix_ + name;
+ return name;
+ }
+
+ inline std::string GetFieldNumberConstant(const FieldDescriptor* field) {
+ std::string name = field->camelcase_name();
+ if (!name.empty()) {
+ name.at(0) = static_cast<char>(toupper(name.at(0)));
+ name = "k" + name + "FieldNumber";
+ } else {
+ // Protoc allows fields like 'bool _ = 1'.
+ Abort("Empty field name in camel case notation.");
+ }
+ return name;
+ }
+
+ // Small enums can be written faster without involving VarInt encoder.
+ inline bool IsTinyEnumField(const FieldDescriptor* field) {
+ if (field->type() != FieldDescriptor::TYPE_ENUM)
+ return false;
+ const EnumDescriptor* enumeration = field->enum_type();
+
+ for (int i = 0; i < enumeration->value_count(); ++i) {
+ int32_t value = enumeration->value(i)->number();
+ if (value < 0 || value > 0x7F)
+ return false;
+ }
+ return true;
+ }
+
+ void CollectDescriptors() {
+ // Collect message descriptors in DFS order.
+ std::vector<const Descriptor*> stack;
+ for (int i = 0; i < source_->message_type_count(); ++i)
+ stack.push_back(source_->message_type(i));
+
+ while (!stack.empty()) {
+ const Descriptor* message = stack.back();
+ stack.pop_back();
+ messages_.push_back(message);
+ for (int i = 0; i < message->nested_type_count(); ++i) {
+ stack.push_back(message->nested_type(i));
+ }
+ }
+
+ // Collect enums.
+ for (int i = 0; i < source_->enum_type_count(); ++i)
+ enums_.push_back(source_->enum_type(i));
+
+ for (const Descriptor* message : messages_) {
+ for (int i = 0; i < message->enum_type_count(); ++i) {
+ enums_.push_back(message->enum_type(i));
+ }
+ }
+ }
+
+ void CollectDependencies() {
+ // Public import basically means that callers only need to import this
+ // proto in order to use the stuff publicly imported by this proto.
+ for (int i = 0; i < source_->public_dependency_count(); ++i)
+ public_imports_.insert(source_->public_dependency(i));
+
+ if (source_->weak_dependency_count() > 0)
+ Abort("Weak imports are not supported.");
+
+ // Sanity check. Collect public imports (of collected imports) in DFS order.
+ // Visibilty for current proto:
+ // - all imports listed in current proto,
+ // - public imports of everything imported (recursive).
+ std::vector<const FileDescriptor*> stack;
+ for (int i = 0; i < source_->dependency_count(); ++i) {
+ const FileDescriptor* import = source_->dependency(i);
+ stack.push_back(import);
+ if (public_imports_.count(import) == 0) {
+ private_imports_.insert(import);
+ }
+ }
+
+ while (!stack.empty()) {
+ const FileDescriptor* import = stack.back();
+ stack.pop_back();
+ // Having imports under different packages leads to unnecessary
+ // complexity with namespaces.
+ if (import->package() != package_)
+ Abort("Imported proto must be in the same package.");
+
+ for (int i = 0; i < import->public_dependency_count(); ++i) {
+ stack.push_back(import->public_dependency(i));
+ }
+ }
+
+ // Collect descriptors of messages and enums used in current proto.
+ // It will be used to generate necessary forward declarations and performed
+ // sanity check guarantees that everything lays in the same namespace.
+ for (const Descriptor* message : messages_) {
+ for (int i = 0; i < message->field_count(); ++i) {
+ const FieldDescriptor* field = message->field(i);
+
+ if (field->type() == FieldDescriptor::TYPE_MESSAGE) {
+ if (public_imports_.count(field->message_type()->file()) == 0) {
+ // Avoid multiple forward declarations since
+ // public imports have been already included.
+ referenced_messages_.insert(field->message_type());
+ }
+ } else if (field->type() == FieldDescriptor::TYPE_ENUM) {
+ if (public_imports_.count(field->enum_type()->file()) == 0) {
+ referenced_enums_.insert(field->enum_type());
+ }
+ }
+ }
+ }
+ }
+
+ void Preprocess() {
+ // Package name maps to a series of namespaces.
+ package_ = source_->package();
+ namespaces_ = Split(package_, ".");
+ if (!wrapper_namespace_.empty())
+ namespaces_.push_back(wrapper_namespace_);
+
+ full_namespace_prefix_ = "::";
+ for (const std::string& ns : namespaces_)
+ full_namespace_prefix_ += ns + "::";
+
+ CollectDescriptors();
+ CollectDependencies();
+ }
+
+ // Print top header, namespaces and forward declarations.
+ void GeneratePrologue() {
+ std::string greeting =
+ "// Autogenerated by the ProtoZero compiler plugin. DO NOT EDIT.\n";
+ std::string guard = package_ + "_" + source_->name() + "_H_";
+ UpperString(&guard);
+ StripString(&guard, ".-/\\", '_');
+
+ stub_h_->Print(
+ "$greeting$\n"
+ "#ifndef $guard$\n"
+ "#define $guard$\n\n"
+ "#include <stddef.h>\n"
+ "#include <stdint.h>\n\n"
+ "#include \"perfetto/protozero/proto_field_descriptor.h\"\n"
+ "#include \"perfetto/protozero/protozero_message.h\"\n",
+ "greeting", greeting, "guard", guard);
+ stub_cc_->Print(
+ "$greeting$\n"
+ "#include \"$name$.h\"\n",
+ "greeting", greeting, "name", ProtoStubName(source_));
+
+ // Print includes for public imports.
+ for (const FileDescriptor* dependency : public_imports_) {
+ // Dependency name could contain slashes but importing from upper-level
+ // directories is not possible anyway since build system processes each
+ // proto file individually. Hence proto lookup path is always equal to the
+ // directory where particular proto file is located and protoc does not
+ // allow reference to upper directory (aka ..) in import path.
+ //
+ // Laconically said:
+ // - source_->name() may never have slashes,
+ // - dependency->name() may have slashes but always refers to inner path.
+ stub_h_->Print("#include \"$name$.h\"\n", "name",
+ ProtoStubName(dependency));
+ }
+ stub_h_->Print("\n");
+
+ // Print includes for private imports to .cc file.
+ for (const FileDescriptor* dependency : private_imports_) {
+ stub_cc_->Print("#include \"$name$.h\"\n", "name",
+ ProtoStubName(dependency));
+ }
+ stub_cc_->Print("\n");
+
+ if (messages_.size() > 0) {
+ stub_cc_->Print(
+ "namespace {\n"
+ " static const ::protozero::ProtoFieldDescriptor "
+ "kInvalidField = {\"\", "
+ "::protozero::ProtoFieldDescriptor::Type::TYPE_INVALID, "
+ "0, false};\n"
+ "}\n\n");
+ }
+
+ // Print namespaces.
+ for (const std::string& ns : namespaces_) {
+ stub_h_->Print("namespace $ns$ {\n", "ns", ns);
+ stub_cc_->Print("namespace $ns$ {\n", "ns", ns);
+ }
+ stub_h_->Print("\n");
+ stub_cc_->Print("\n");
+
+ // Print forward declarations.
+ for (const Descriptor* message : referenced_messages_) {
+ stub_h_->Print("class $class$;\n", "class", GetCppClassName(message));
+ }
+ for (const EnumDescriptor* enumeration : referenced_enums_) {
+ stub_h_->Print("enum $class$ : int32_t;\n", "class",
+ GetCppClassName(enumeration));
+ }
+ stub_h_->Print("\n");
+ }
+
+ void GenerateEnumDescriptor(const EnumDescriptor* enumeration) {
+ stub_h_->Print("enum $class$ : int32_t {\n", "class",
+ GetCppClassName(enumeration));
+ stub_h_->Indent();
+
+ std::string value_name_prefix;
+ if (enumeration->containing_type() != nullptr)
+ value_name_prefix = GetCppClassName(enumeration) + "_";
+
+ for (int i = 0; i < enumeration->value_count(); ++i) {
+ const EnumValueDescriptor* value = enumeration->value(i);
+ stub_h_->Print("$name$ = $number$,\n", "name",
+ value_name_prefix + value->name(), "number",
+ std::to_string(value->number()));
+ }
+
+ stub_h_->Outdent();
+ stub_h_->Print("};\n\n");
+ }
+
+ void GenerateSimpleFieldDescriptor(const FieldDescriptor* field) {
+ std::map<std::string, std::string> setter;
+ setter["id"] = std::to_string(field->number());
+ setter["name"] = field->name();
+ setter["action"] = field->is_repeated() ? "add" : "set";
+
+ std::string appender;
+ std::string cpp_type;
+
+ switch (field->type()) {
+ case FieldDescriptor::TYPE_BOOL: {
+ appender = "AppendTinyVarInt";
+ cpp_type = "bool";
+ break;
+ }
+ case FieldDescriptor::TYPE_INT32: {
+ appender = "AppendVarInt";
+ cpp_type = "int32_t";
+ break;
+ }
+ case FieldDescriptor::TYPE_INT64: {
+ appender = "AppendVarInt";
+ cpp_type = "int64_t";
+ break;
+ }
+ case FieldDescriptor::TYPE_UINT32: {
+ appender = "AppendVarInt";
+ cpp_type = "uint32_t";
+ break;
+ }
+ case FieldDescriptor::TYPE_UINT64: {
+ appender = "AppendVarInt";
+ cpp_type = "uint64_t";
+ break;
+ }
+ case FieldDescriptor::TYPE_SINT32: {
+ appender = "AppendSignedVarInt";
+ cpp_type = "int32_t";
+ break;
+ }
+ case FieldDescriptor::TYPE_SINT64: {
+ appender = "AppendSignedVarInt";
+ cpp_type = "int64_t";
+ break;
+ }
+ case FieldDescriptor::TYPE_FIXED32: {
+ appender = "AppendFixed";
+ cpp_type = "uint32_t";
+ break;
+ }
+ case FieldDescriptor::TYPE_FIXED64: {
+ appender = "AppendFixed";
+ cpp_type = "uint64_t";
+ break;
+ }
+ case FieldDescriptor::TYPE_SFIXED32: {
+ appender = "AppendFixed";
+ cpp_type = "int32_t";
+ break;
+ }
+ case FieldDescriptor::TYPE_SFIXED64: {
+ appender = "AppendFixed";
+ cpp_type = "int64_t";
+ break;
+ }
+ case FieldDescriptor::TYPE_FLOAT: {
+ appender = "AppendFixed";
+ cpp_type = "float";
+ break;
+ }
+ case FieldDescriptor::TYPE_DOUBLE: {
+ appender = "AppendFixed";
+ cpp_type = "double";
+ break;
+ }
+ case FieldDescriptor::TYPE_ENUM: {
+ appender = IsTinyEnumField(field) ? "AppendTinyVarInt" : "AppendVarInt";
+ cpp_type = GetCppClassName(field->enum_type(), true);
+ break;
+ }
+ case FieldDescriptor::TYPE_STRING: {
+ appender = "AppendString";
+ cpp_type = "const char*";
+ break;
+ }
+ case FieldDescriptor::TYPE_BYTES: {
+ stub_h_->Print(
+ setter,
+ "void $action$_$name$(const uint8_t* data, size_t size) {\n"
+ " AppendBytes($id$, data, size);\n"
+ "}\n");
+ return;
+ }
+ case FieldDescriptor::TYPE_GROUP:
+ case FieldDescriptor::TYPE_MESSAGE: {
+ Abort("Unsupported field type.");
+ return;
+ }
+ }
+ setter["appender"] = appender;
+ setter["cpp_type"] = cpp_type;
+ stub_h_->Print(setter,
+ "void $action$_$name$($cpp_type$ value) {\n"
+ " $appender$($id$, value);\n"
+ "}\n");
+
+ // For strings also generate a variant for non-null terminated strings.
+ if (field->type() == FieldDescriptor::TYPE_STRING) {
+ stub_h_->Print(setter,
+ "// Doesn't check for null terminator.\n"
+ "// Expects |value| to be at least |size| long.\n"
+ "void $action$_$name$($cpp_type$ value, size_t size) {\n"
+ " AppendBytes($id$, value, size);\n"
+ "}\n");
+ }
+ }
+
+ void GenerateNestedMessageFieldDescriptor(const FieldDescriptor* field) {
+ std::string action = field->is_repeated() ? "add" : "set";
+ std::string inner_class = GetCppClassName(field->message_type());
+ std::string outer_class = GetCppClassName(field->containing_type());
+
+ stub_h_->Print("$inner_class$* $action$_$name$();\n", "name", field->name(),
+ "action", action, "inner_class", inner_class);
+ stub_cc_->Print(
+ "$inner_class$* $outer_class$::$action$_$name$() {\n"
+ " return BeginNestedMessage<$inner_class$>($id$);\n"
+ "}\n\n",
+ "id", std::to_string(field->number()), "name", field->name(), "action",
+ action, "inner_class", inner_class, "outer_class", outer_class);
+ }
+
+ void GenerateReflectionForMessageFields(const Descriptor* message) {
+ const bool has_fields = (message->field_count() > 0);
+
+ // Field number constants.
+ if (has_fields) {
+ stub_h_->Print("enum : int32_t {\n");
+ stub_h_->Indent();
+
+ for (int i = 0; i < message->field_count(); ++i) {
+ const FieldDescriptor* field = message->field(i);
+ stub_h_->Print("$name$ = $id$,\n", "name",
+ GetFieldNumberConstant(field), "id",
+ std::to_string(field->number()));
+ }
+ stub_h_->Outdent();
+ stub_h_->Print("};\n");
+ }
+
+ // Fields reflection table.
+ stub_h_->Print(
+ "static const ::protozero::ProtoFieldDescriptor* "
+ "GetFieldDescriptor(uint32_t field_id);\n");
+
+ std::string class_name = GetCppClassName(message);
+ if (has_fields) {
+ stub_cc_->Print(
+ "static const ::protozero::ProtoFieldDescriptor "
+ "kFields_$class$[] = {\n",
+ "class", class_name);
+ stub_cc_->Indent();
+ for (int i = 0; i < message->field_count(); ++i) {
+ const FieldDescriptor* field = message->field(i);
+ std::string type_const =
+ std::string("TYPE_") + FieldDescriptor::TypeName(field->type());
+ UpperString(&type_const);
+ stub_cc_->Print(
+ "{\"$name$\", "
+ "::protozero::ProtoFieldDescriptor::Type::$type$, "
+ "$number$, $is_repeated$},\n",
+ "name", field->name(), "type", type_const, "number",
+ std::to_string(field->number()), "is_repeated",
+ std::to_string(field->is_repeated()));
+ }
+ stub_cc_->Outdent();
+ stub_cc_->Print("};\n\n");
+ }
+
+ // Fields reflection getter.
+ stub_cc_->Print(
+ "const ::protozero::ProtoFieldDescriptor* "
+ "$class$::GetFieldDescriptor(uint32_t field_id) {\n",
+ "class", class_name);
+ stub_cc_->Indent();
+ if (has_fields) {
+ stub_cc_->Print("switch (field_id) {\n");
+ stub_cc_->Indent();
+ for (int i = 0; i < message->field_count(); ++i) {
+ stub_cc_->Print(
+ "case $field$:\n"
+ " return &kFields_$class$[$id$];\n",
+ "class", class_name, "field",
+ GetFieldNumberConstant(message->field(i)), "id", std::to_string(i));
+ }
+ stub_cc_->Print(
+ "default:\n"
+ " return &kInvalidField;\n");
+ stub_cc_->Outdent();
+ stub_cc_->Print("}\n");
+ } else {
+ stub_cc_->Print("return &kInvalidField;\n");
+ }
+ stub_cc_->Outdent();
+ stub_cc_->Print("}\n\n");
+ }
+
+ void GenerateMessageDescriptor(const Descriptor* message) {
+ stub_h_->Print(
+ "class $name$ : public ::protozero::ProtoZeroMessage {\n"
+ " public:\n",
+ "name", GetCppClassName(message));
+ stub_h_->Indent();
+
+ GenerateReflectionForMessageFields(message);
+
+ // Using statements for nested messages.
+ for (int i = 0; i < message->nested_type_count(); ++i) {
+ const Descriptor* nested_message = message->nested_type(i);
+ stub_h_->Print("using $local_name$ = $global_name$;\n", "local_name",
+ nested_message->name(), "global_name",
+ GetCppClassName(nested_message, true));
+ }
+
+ // Using statements for nested enums.
+ for (int i = 0; i < message->enum_type_count(); ++i) {
+ const EnumDescriptor* nested_enum = message->enum_type(i);
+ stub_h_->Print("using $local_name$ = $global_name$;\n", "local_name",
+ nested_enum->name(), "global_name",
+ GetCppClassName(nested_enum, true));
+ }
+
+ // Values of nested enums.
+ for (int i = 0; i < message->enum_type_count(); ++i) {
+ const EnumDescriptor* nested_enum = message->enum_type(i);
+ std::string value_name_prefix = GetCppClassName(nested_enum) + "_";
+
+ for (int j = 0; j < nested_enum->value_count(); ++j) {
+ const EnumValueDescriptor* value = nested_enum->value(j);
+ stub_h_->Print("static const $class$ $name$ = $full_name$;\n", "class",
+ nested_enum->name(), "name", value->name(), "full_name",
+ value_name_prefix + value->name());
+ }
+ }
+
+ // Field descriptors.
+ for (int i = 0; i < message->field_count(); ++i) {
+ const FieldDescriptor* field = message->field(i);
+ if (field->is_packed()) {
+ Abort("Packed repeated fields are not supported.");
+ return;
+ }
+ if (field->type() != FieldDescriptor::TYPE_MESSAGE) {
+ GenerateSimpleFieldDescriptor(field);
+ } else {
+ GenerateNestedMessageFieldDescriptor(field);
+ }
+ }
+
+ stub_h_->Outdent();
+ stub_h_->Print("};\n\n");
+ }
+
+ void GenerateEpilogue() {
+ for (unsigned i = 0; i < namespaces_.size(); ++i) {
+ stub_h_->Print("} // Namespace.\n");
+ stub_cc_->Print("} // Namespace.\n");
+ }
+ stub_h_->Print("#endif // Include guard.\n");
+ }
+
+ const FileDescriptor* const source_;
+ Printer* const stub_h_;
+ Printer* const stub_cc_;
+ std::string error_;
+
+ std::string package_;
+ std::string wrapper_namespace_;
+ std::vector<std::string> namespaces_;
+ std::string full_namespace_prefix_;
+ std::vector<const Descriptor*> messages_;
+ std::vector<const EnumDescriptor*> enums_;
+
+ std::set<const FileDescriptor*> public_imports_;
+ std::set<const FileDescriptor*> private_imports_;
+ std::set<const Descriptor*> referenced_messages_;
+ std::set<const EnumDescriptor*> referenced_enums_;
+};
+
+} // namespace
+
+ProtoZeroGenerator::ProtoZeroGenerator() {}
+
+ProtoZeroGenerator::~ProtoZeroGenerator() {}
+
+bool ProtoZeroGenerator::Generate(const FileDescriptor* file,
+ const std::string& options,
+ GeneratorContext* context,
+ std::string* error) const {
+ const std::unique_ptr<ZeroCopyOutputStream> stub_h_file_stream(
+ context->Open(ProtoStubName(file) + ".h"));
+ const std::unique_ptr<ZeroCopyOutputStream> stub_cc_file_stream(
+ context->Open(ProtoStubName(file) + ".cc"));
+
+ // Variables are delimited by $.
+ Printer stub_h_printer(stub_h_file_stream.get(), '$');
+ Printer stub_cc_printer(stub_cc_file_stream.get(), '$');
+ GeneratorJob job(file, &stub_h_printer, &stub_cc_printer);
+
+ // Parse additional options.
+ for (const std::string& option : Split(options, ",")) {
+ std::vector<std::string> option_pair = Split(option, "=");
+ job.SetOption(option_pair[0], option_pair[1]);
+ }
+
+ if (!job.GenerateStubs()) {
+ *error = job.GetFirstError();
+ return false;
+ }
+ return true;
+}
+
+} // namespace protozero
diff --git a/src/protozero/protoc_plugin/protozero_generator.h b/src/protozero/protoc_plugin/protozero_generator.h
new file mode 100644
index 0000000..a9f7a70
--- /dev/null
+++ b/src/protozero/protoc_plugin/protozero_generator.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_PROTOZERO_PROTOC_PLUGIN_PROTOZERO_GENERATOR_H_
+#define SRC_PROTOZERO_PROTOC_PLUGIN_PROTOZERO_GENERATOR_H_
+
+#include <string>
+
+#include "google/protobuf/compiler/code_generator.h"
+
+namespace protozero {
+
+class ProtoZeroGenerator : public ::google::protobuf::compiler::CodeGenerator {
+ public:
+ explicit ProtoZeroGenerator();
+ ~ProtoZeroGenerator() override;
+
+ // CodeGenerator implementation
+ bool Generate(const google::protobuf::FileDescriptor* file,
+ const std::string& options,
+ google::protobuf::compiler::GeneratorContext* context,
+ std::string* error) const override;
+};
+
+} // namespace protozero
+
+#endif // SRC_PROTOZERO_PROTOC_PLUGIN_PROTOZERO_GENERATOR_H_
diff --git a/src/protozero/protoc_plugin/protozero_plugin.cc b/src/protozero/protoc_plugin/protozero_plugin.cc
new file mode 100644
index 0000000..7d9d6a3
--- /dev/null
+++ b/src/protozero/protoc_plugin/protozero_plugin.cc
@@ -0,0 +1,23 @@
+/*
+ * 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 "google/protobuf/compiler/plugin.h"
+#include "src/protozero/protoc_plugin/protozero_generator.h"
+
+int main(int argc, char* argv[]) {
+ ::protozero::ProtoZeroGenerator generator;
+ return google::protobuf::compiler::PluginMain(argc, argv, &generator);
+}
diff --git a/src/protozero/protozero_library.gni b/src/protozero/protozero_library.gni
new file mode 100644
index 0000000..ffaedca
--- /dev/null
+++ b/src/protozero/protozero_library.gni
@@ -0,0 +1,55 @@
+# 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.
+
+import("//build_overrides/build.gni")
+
+if (!build_with_chromium) {
+ import("//build/proto_library.gni")
+} else {
+ import("//third_party/protobuf/proto_library.gni")
+}
+
+# Equivalent to proto_library (generation of .h/.cc from .proto files) but
+# enables also generation using the protozero plugin.
+# The generated files will have the .pbzero.{cc,h} suffix, as opposed to the
+# .pb.{cc,h} of the official proto library.
+template("protozero_library") {
+ proto_library(target_name) {
+ perfetto_root_path = invoker.perfetto_root_path
+
+ generate_cc = false
+ generator_plugin_label = perfetto_root_path + "src/protozero/protoc_plugin"
+ generator_plugin_suffix = ".pbzero"
+
+ if (defined(invoker.deps)) {
+ deps = invoker.deps
+ } else {
+ deps = []
+ }
+
+ deps += [ perfetto_root_path + "src/protozero" ]
+
+ forward_variables_from(invoker,
+ [
+ "defines",
+ "generator_plugin_options",
+ "include_dirs",
+ "proto_in_dir",
+ "proto_out_dir",
+ "sources",
+ "testonly",
+ "visibility",
+ ])
+ }
+}
diff --git a/src/protozero/protozero_message.cc b/src/protozero/protozero_message.cc
new file mode 100644
index 0000000..195b5c2
--- /dev/null
+++ b/src/protozero/protozero_message.cc
@@ -0,0 +1,142 @@
+/*
+ * 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/protozero_message.h"
+
+#include <type_traits>
+
+#include "perfetto/base/logging.h"
+
+#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
+// The memcpy() for float and double below needs to be adjusted if we want to
+// support big endian CPUs. There doesn't seem to be a compelling need today.
+#error Unimplemented for big endian archs.
+#endif
+
+namespace protozero {
+
+// static
+constexpr uint32_t ProtoZeroMessage::kMaxNestingDepth;
+
+// Do NOT put any code in the constructor or use default initialization.
+// Use the Reset() method below instead. See the header for the reason why.
+
+// This method is called to initialize both root and nested messages.
+void ProtoZeroMessage::Reset(ScatteredStreamWriter* stream_writer) {
+// Older versions of libstdcxx don't have is_trivially_constructible.
+#if !defined(__GLIBCXX__) || __GLIBCXX__ >= 20170516
+ static_assert(std::is_trivially_constructible<ProtoZeroMessage>::value,
+ "ProtoZeroMessage must be trivially constructible");
+#endif
+
+ static_assert(std::is_trivially_destructible<ProtoZeroMessage>::value,
+ "ProtoZeroMessage must be trivially destructible");
+
+ static_assert(
+ sizeof(ProtoZeroMessage::nested_messages_arena_) >=
+ kMaxNestingDepth * (sizeof(ProtoZeroMessage) -
+ sizeof(ProtoZeroMessage::nested_messages_arena_)),
+ "ProtoZeroMessage::nested_messages_arena_ is too small");
+
+ stream_writer_ = stream_writer;
+ size_ = 0;
+ size_field_.reset();
+ nested_message_ = nullptr;
+ nesting_depth_ = 0;
+#if PROTOZERO_ENABLE_HANDLE_DEBUGGING()
+ sealed_ = false;
+ handle_ = nullptr;
+#endif
+}
+
+void ProtoZeroMessage::AppendString(uint32_t field_id, const char* str) {
+ AppendBytes(field_id, str, strlen(str));
+}
+
+void ProtoZeroMessage::AppendBytes(uint32_t field_id,
+ const void* src,
+ size_t size) {
+ if (nested_message_)
+ EndNestedMessage();
+
+ PERFETTO_DCHECK(size < proto_utils::kMaxMessageLength);
+ // Write the proto preamble (field id, type and length of the field).
+ uint8_t buffer[proto_utils::kMaxSimpleFieldEncodedSize];
+ uint8_t* pos = buffer;
+ pos = proto_utils::WriteVarInt(proto_utils::MakeTagLengthDelimited(field_id),
+ pos);
+ pos = proto_utils::WriteVarInt(static_cast<uint32_t>(size), pos);
+ WriteToStream(buffer, pos);
+
+ const uint8_t* src_u8 = reinterpret_cast<const uint8_t*>(src);
+ WriteToStream(src_u8, src_u8 + size);
+}
+
+size_t ProtoZeroMessage::Finalize() {
+ if (nested_message_)
+ EndNestedMessage();
+
+ // Write the length of the nested message a posteriori, using a leading-zero
+ // redundant varint encoding.
+ if (size_field_.is_valid()) {
+#if PROTOZERO_ENABLE_HANDLE_DEBUGGING()
+ PERFETTO_DCHECK(!sealed_);
+#endif
+ PERFETTO_DCHECK(size_ < proto_utils::kMaxMessageLength);
+ PERFETTO_DCHECK(proto_utils::kMessageLengthFieldSize == size_field_.size());
+ proto_utils::WriteRedundantVarInt(static_cast<uint32_t>(size_),
+ size_field_.begin);
+ size_field_.reset();
+ }
+
+#if PROTOZERO_ENABLE_HANDLE_DEBUGGING()
+ sealed_ = true;
+ if (handle_)
+ handle_->reset_message();
+#endif
+
+ return size_;
+}
+
+void ProtoZeroMessage::BeginNestedMessageInternal(uint32_t field_id,
+ ProtoZeroMessage* message) {
+ if (nested_message_)
+ EndNestedMessage();
+
+ // Write the proto preamble for the nested message.
+ uint8_t data[proto_utils::kMaxTagEncodedSize];
+ uint8_t* data_end = proto_utils::WriteVarInt(
+ proto_utils::MakeTagLengthDelimited(field_id), data);
+ WriteToStream(data, data_end);
+
+ message->Reset(stream_writer_);
+ PERFETTO_CHECK(nesting_depth_ < kMaxNestingDepth);
+ message->nesting_depth_ = nesting_depth_ + 1;
+
+ // The length of the nested message cannot be known upfront. So right now
+ // just reserve the bytes to encode the size after the nested message is done.
+ message->set_size_field(
+ stream_writer_->ReserveBytes(proto_utils::kMessageLengthFieldSize));
+ size_ += proto_utils::kMessageLengthFieldSize;
+ nested_message_ = message;
+}
+
+void ProtoZeroMessage::EndNestedMessage() {
+ size_ += nested_message_->Finalize();
+ nested_message_ = nullptr;
+}
+
+} // namespace protozero
diff --git a/src/protozero/protozero_message_handle.cc b/src/protozero/protozero_message_handle.cc
new file mode 100644
index 0000000..3e2b4e7
--- /dev/null
+++ b/src/protozero/protozero_message_handle.cc
@@ -0,0 +1,78 @@
+/*
+ * 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/protozero_message_handle.h"
+
+#include <utility>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/protozero/protozero_message.h"
+
+namespace protozero {
+
+ProtoZeroMessageHandleBase::ProtoZeroMessageHandleBase(
+ ProtoZeroMessage* message)
+ : message_(message) {
+#if PROTOZERO_ENABLE_HANDLE_DEBUGGING()
+ if (message_)
+ message_->set_handle(this);
+#endif
+}
+
+ProtoZeroMessageHandleBase::~ProtoZeroMessageHandleBase() {
+ Finalize();
+}
+
+ProtoZeroMessageHandleBase::ProtoZeroMessageHandleBase(
+ ProtoZeroMessageHandleBase&& other) noexcept {
+ Move(std::move(other));
+}
+
+ProtoZeroMessageHandleBase& ProtoZeroMessageHandleBase::operator=(
+ ProtoZeroMessageHandleBase&& other) {
+ // If the current handle was pointing to a message and is being reset to a new
+ // one, finalize the old message.
+ Finalize();
+ Move(std::move(other));
+ return *this;
+}
+
+void ProtoZeroMessageHandleBase::Finalize() {
+ if (!message_)
+ return;
+ const size_t size = message_->Finalize();
+ if (on_finalize_) {
+ on_finalize_(size);
+ on_finalize_ = nullptr;
+ }
+}
+
+void ProtoZeroMessageHandleBase::Move(ProtoZeroMessageHandleBase&& other) {
+ // In theory other->message_ could be nullptr, if |other| is a handle that has
+ // been std::move-d (and hence empty). There isn't a legitimate use case for
+ // doing so, though. Therefore this case is deliberately ignored (if hit, it
+ // will manifest as a segfault when dereferencing |message_| below) to avoid a
+ // useless null-check.
+ message_ = other.message_;
+ other.message_ = nullptr;
+ on_finalize_ = std::move(other.on_finalize_);
+ other.on_finalize_ = nullptr;
+#if PROTOZERO_ENABLE_HANDLE_DEBUGGING()
+ message_->set_handle(this);
+#endif
+}
+
+} // namespace protozero
diff --git a/src/protozero/protozero_message_unittest.cc b/src/protozero/protozero_message_unittest.cc
new file mode 100644
index 0000000..076612b
--- /dev/null
+++ b/src/protozero/protozero_message_unittest.cc
@@ -0,0 +1,370 @@
+/*
+ * 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/protozero_message.h"
+
+#include <limits>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "perfetto/base/logging.h"
+#include "src/protozero/test/fake_scattered_buffer.h"
+
+namespace protozero {
+
+namespace {
+
+const size_t kChunkSize = 16;
+const uint8_t kTestBytes[] = {0, 0, 0, 0, 0x42, 1, 0x42, 0xff, 0x42, 0};
+const char kStartWatermark[] = {'a', 'b', 'c', 'd', '1', '2', '3', '\0'};
+const char kEndWatermark[] = {'9', '8', '7', '6', 'z', 'w', 'y', '\0'};
+
+class FakeRootMessage : public ProtoZeroMessage {};
+class FakeChildMessage : public ProtoZeroMessage {};
+
+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 ProtoZeroMessageTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ buffer_.reset(new FakeScatteredBuffer(kChunkSize));
+ stream_writer_.reset(new ScatteredStreamWriter(buffer_.get()));
+ readback_pos_ = 0;
+ }
+
+ 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(ProtoZeroMessage)));
+ mem.reset();
+ }
+ messages_.clear();
+ stream_writer_.reset();
+ buffer_.reset();
+ }
+
+ 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 = reinterpret_cast<FakeRootMessage*>(msg_start);
+ msg->Reset(stream_writer_.get());
+ return msg;
+ }
+
+ size_t GetNumSerializedBytes() {
+ if (buffer_->chunks().empty())
+ return 0;
+ return buffer_->chunks().size() * kChunkSize -
+ 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(ProtoZeroMessage* msg, uint32_t depth = 0) {
+ for (uint32_t i = 1; i <= 128; ++i)
+ msg->AppendBytes(i, kTestBytes, sizeof(kTestBytes));
+
+ if (depth < ProtoZeroMessage::kMaxNestingDepth) {
+ auto* nested_msg =
+ msg->BeginNestedMessage<FakeChildMessage>(1 + depth * 10);
+ BuildNestedMessages(nested_msg, depth + 1);
+ }
+
+ for (uint32_t i = 129; i <= 256; ++i)
+ msg->AppendVarInt(i, 42);
+
+ if ((depth & 2) == 0)
+ msg->Finalize();
+ }
+
+ private:
+ std::unique_ptr<FakeScatteredBuffer> buffer_;
+ std::unique_ptr<ScatteredStreamWriter> stream_writer_;
+ std::vector<std::unique_ptr<uint8_t[]>> messages_;
+ size_t readback_pos_;
+};
+
+TEST_F(ProtoZeroMessageTest, BasicTypesNoNesting) {
+ ProtoZeroMessage* 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(ProtoZeroMessageTest, NestedMessagesSimple) {
+ ProtoZeroMessage* 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 20 bytes:
+ // 2 bytes for the varint field (id: 1) (1 for preamble and one for payload)
+ // 6 bytes for the preamble of the 1st nested message (2 for id, 4 for size)
+ // 2 bytes for the varint field (id: 2) of the 1st nested message
+ // 6 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(20u, root_msg->Finalize());
+ EXPECT_EQ(20u, GetNumSerializedBytes());
+ }
+
+ ASSERT_EQ("0801", GetNextSerializedBytes(2));
+
+ ASSERT_EQ("820882808000", GetNextSerializedBytes(6));
+ ASSERT_EQ("1002", GetNextSerializedBytes(2));
+
+ ASSERT_EQ("8A0882808000", GetNextSerializedBytes(6));
+ ASSERT_EQ("2002", GetNextSerializedBytes(2));
+
+ ASSERT_EQ("2803", GetNextSerializedBytes(2));
+}
+
+// Checks that the size field of root and nested messages is properly written
+// on finalization.
+TEST_F(ProtoZeroMessageTest, BackfillSizeOnFinalization) {
+ ProtoZeroMessage* root_msg = NewMessage();
+ uint8_t root_msg_size[proto_utils::kMessageLengthFieldSize] = {};
+ root_msg->set_size_field(
+ {&root_msg_size[0],
+ &root_msg_size[proto_utils::kMessageLengthFieldSize]});
+ root_msg->AppendVarInt(1, 0x42);
+
+ FakeChildMessage* nested_msg_1 =
+ root_msg->BeginNestedMessage<FakeChildMessage>(2);
+ nested_msg_1->AppendVarInt(3, 0x43);
+
+ FakeChildMessage* nested_msg_2 =
+ nested_msg_1->BeginNestedMessage<FakeChildMessage>(4);
+ uint8_t buf200[200];
+ memset(buf200, 0x42, sizeof(buf200));
+ nested_msg_2->AppendBytes(5, buf200, sizeof(buf200));
+
+ // The value returned by Finalize() should be == the full size of |root_msg|.
+ EXPECT_EQ(217u, root_msg->Finalize());
+ EXPECT_EQ(217u, GetNumSerializedBytes());
+
+ // However the size written in the size field should take into account the
+ // inc_size_already_written() call and be equal to 118 - 6 = 112, encoded
+ // in a rendundant varint encoding of kMessageLengthFieldSize bytes.
+ EXPECT_STREQ("\xD9\x81\x80\x00", reinterpret_cast<char*>(root_msg_size));
+
+ // Skip 2 bytes for the 0x42 varint + 1 byte for the |nested_msg_1| preamble.
+ GetNextSerializedBytes(3);
+
+ // Check that the size of |nested_msg_1| was backfilled. Its size is:
+ // 203 bytes for |nest_mesg_2| (see below) + 5 bytes for its preamble +
+ // 2 bytes for the 0x43 varint = 210 bytes.
+ EXPECT_EQ("D2818000", GetNextSerializedBytes(4));
+
+ // Skip 2 bytes for the 0x43 varint + 1 byte for the |nested_msg_2| preamble.
+ GetNextSerializedBytes(3);
+
+ // Check that the size of |nested_msg_2| was backfilled. Its size is:
+ // 200 bytes (for |buf200|) + 3 bytes for its preamble = 203 bytes.
+ EXPECT_EQ("CB818000", GetNextSerializedBytes(4));
+}
+
+TEST_F(ProtoZeroMessageTest, StressTest) {
+ std::vector<ProtoZeroMessage*> nested_msgs;
+
+ ProtoZeroMessage* root_msg = NewMessage();
+ BuildNestedMessages(root_msg);
+ 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(0xfd19cc0a, buf_hash);
+}
+
+TEST_F(ProtoZeroMessageTest, MessageHandle) {
+ FakeRootMessage* msg1 = NewMessage();
+ FakeRootMessage* msg2 = NewMessage();
+ FakeRootMessage* msg3 = NewMessage();
+ FakeRootMessage* ignored_msg = NewMessage();
+ uint8_t msg1_size[proto_utils::kMessageLengthFieldSize] = {};
+ uint8_t msg2_size[proto_utils::kMessageLengthFieldSize] = {};
+ uint8_t msg3_size[proto_utils::kMessageLengthFieldSize] = {};
+ msg1->set_size_field(
+ {&msg1_size[0], &msg1_size[proto_utils::kMessageLengthFieldSize]});
+ msg2->set_size_field(
+ {&msg2_size[0], &msg2_size[proto_utils::kMessageLengthFieldSize]});
+ msg3->set_size_field(
+ {&msg3_size[0], &msg3_size[proto_utils::kMessageLengthFieldSize]});
+
+ // Test that the handle going out of scope causes the finalization of the
+ // target message and triggers the optional callback.
+ size_t callback_arg = 0;
+ {
+ ProtoZeroMessageHandle<FakeRootMessage> handle1(msg1);
+ handle1.set_on_finalize([&callback_arg](size_t sz) { callback_arg = sz; });
+ handle1->AppendBytes(1 /* field_id */, kTestBytes, 1 /* size */);
+ ASSERT_EQ(0u, msg1_size[0]);
+ }
+ ASSERT_EQ(0x83u, msg1_size[0]);
+ ASSERT_EQ(3u, callback_arg);
+
+ // Test that the handle can be late initialized.
+ ProtoZeroMessageHandle<FakeRootMessage> handle2(ignored_msg);
+ handle2 = ProtoZeroMessageHandle<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.
+ ProtoZeroMessageHandle<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 */);
+
+ ProtoZeroMessageHandle<FakeRootMessage> handle3(msg3);
+ handle3->AppendBytes(1 /* field_id */, kTestBytes, 4 /* size */);
+ ASSERT_EQ(0u, msg3_size[0]); // msg2 should be NOT finalized yet.
+ callback_arg = 0;
+ handle3.set_on_finalize([&callback_arg](size_t sz) { callback_arg = sz; });
+
+ // 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 */);
+ ProtoZeroMessageHandle<FakeRootMessage> another_handle(ignored_msg);
+ ASSERT_EQ(0u, callback_arg);
+ handle_swp = std::move(another_handle);
+ ASSERT_EQ(0x90u, msg3_size[0]); // |msg3| should be finalized at this point.
+ ASSERT_EQ(0x10u, callback_arg);
+
+#if PROTOZERO_ENABLE_HANDLE_DEBUGGING()
+ // 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();
+ ProtoZeroMessageHandle<FakeRootMessage> handle4(msg4);
+ ASSERT_EQ(msg4, &*handle4);
+ msg4->Finalize();
+ ASSERT_EQ(nullptr, &*handle4);
+#endif
+
+ // Test also the behavior of handle with non-root (nested) messages.
+
+ ContiguousMemoryRange size_msg_2;
+ {
+ auto* nested_msg_1 = NewMessage()->BeginNestedMessage<FakeChildMessage>(3);
+ ProtoZeroMessageHandle<FakeChildMessage> child_handle_1(nested_msg_1);
+ ContiguousMemoryRange size_msg_1 = nested_msg_1->size_field();
+ memset(size_msg_1.begin, 0, size_msg_1.size());
+ 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.begin, 0, size_msg_2.size());
+ ProtoZeroMessageHandle<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.begin[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.begin[0]);
+ ASSERT_EQ(0u, size_msg_2.begin[0]);
+ }
+ ASSERT_EQ(0x83u, size_msg_2.begin[0]);
+}
+
+TEST_F(ProtoZeroMessageTest, MoveMessageHandle) {
+ FakeRootMessage* msg = NewMessage();
+ uint8_t msg_size[proto_utils::kMessageLengthFieldSize] = {};
+ msg->set_size_field(
+ {&msg_size[0], &msg_size[proto_utils::kMessageLengthFieldSize]});
+
+ // Test that the handle going out of scope causes the finalization of the
+ // target message.
+ {
+ ProtoZeroMessageHandle<FakeRootMessage> handle1(msg);
+ ProtoZeroMessageHandle<FakeRootMessage> handle2{};
+ handle1->AppendBytes(1 /* field_id */, kTestBytes, 1 /* size */);
+ handle2 = std::move(handle1);
+ ASSERT_EQ(0u, msg_size[0]);
+ }
+ ASSERT_EQ(0x83u, msg_size[0]);
+}
+
+} // namespace
+} // namespace protozero
diff --git a/src/protozero/scattered_stream_writer.cc b/src/protozero/scattered_stream_writer.cc
new file mode 100644
index 0000000..23fb12c
--- /dev/null
+++ b/src/protozero/scattered_stream_writer.cc
@@ -0,0 +1,74 @@
+/*
+ * 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/scattered_stream_writer.h"
+
+#include <algorithm>
+
+#include "perfetto/base/logging.h"
+
+namespace protozero {
+
+ScatteredStreamWriter::Delegate::~Delegate() {}
+
+ScatteredStreamWriter::ScatteredStreamWriter(Delegate* delegate)
+ : delegate_(delegate),
+ cur_range_({nullptr, nullptr}),
+ write_ptr_(nullptr) {}
+
+ScatteredStreamWriter::~ScatteredStreamWriter() {}
+
+void ScatteredStreamWriter::Reset(ContiguousMemoryRange range) {
+ cur_range_ = range;
+ write_ptr_ = range.begin;
+ PERFETTO_DCHECK(write_ptr_ < cur_range_.end);
+}
+
+void ScatteredStreamWriter::Extend() {
+ Reset(delegate_->GetNewBuffer());
+}
+
+void ScatteredStreamWriter::WriteBytesSlowPath(const uint8_t* src,
+ size_t size) {
+ size_t bytes_left = size;
+ while (bytes_left > 0) {
+ if (write_ptr_ >= cur_range_.end)
+ Extend();
+ const size_t burst_size = std::min(bytes_available(), bytes_left);
+ WriteBytesUnsafe(src, burst_size);
+ bytes_left -= burst_size;
+ src += burst_size;
+ }
+}
+
+// TODO(primiano): perf optimization: I suspect that at the end this will always
+// be called with |size| == 4, in which case we might just hardcode it.
+ContiguousMemoryRange ScatteredStreamWriter::ReserveBytes(size_t size) {
+ if (write_ptr_ + size > cur_range_.end) {
+ // Assume the reservations are always < Delegate::GetNewBuffer().size(),
+ // so that one single call to Extend() will definitely give enough headroom.
+ Extend();
+ PERFETTO_DCHECK(write_ptr_ + size <= cur_range_.end);
+ }
+ uint8_t* begin = write_ptr_;
+ write_ptr_ += size;
+#ifndef NDEBUG
+ memset(begin, '\xFF', size);
+#endif
+ return {begin, begin + size};
+}
+
+} // namespace protozero
diff --git a/src/protozero/scattered_stream_writer_unittest.cc b/src/protozero/scattered_stream_writer_unittest.cc
new file mode 100644
index 0000000..3dc38fb
--- /dev/null
+++ b/src/protozero/scattered_stream_writer_unittest.cc
@@ -0,0 +1,111 @@
+/*
+ * 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/scattered_stream_writer.h"
+
+#include <string.h>
+
+#include <memory>
+
+#include "gtest/gtest.h"
+#include "perfetto/base/logging.h"
+#include "src/protozero/test/fake_scattered_buffer.h"
+
+namespace protozero {
+namespace {
+
+const size_t kChunkSize = 8;
+
+TEST(ScatteredStreamWriterTest, ScatteredWrites) {
+ FakeScatteredBuffer delegate(kChunkSize);
+ ScatteredStreamWriter ssw(&delegate);
+
+ const uint8_t kOneByteBuf[] = {0x40};
+ const uint8_t kThreeByteBuf[] = {0x50, 0x51, 0x52};
+ const uint8_t kFourByteBuf[] = {0x60, 0x61, 0x62, 0x63};
+ uint8_t kTwentyByteBuf[20];
+ for (uint8_t i = 0; i < sizeof(kTwentyByteBuf); ++i)
+ kTwentyByteBuf[i] = 0xA0 + i;
+
+ // Writing up to the chunk size should cause only the initial extension.
+ for (uint8_t i = 0; i < kChunkSize; ++i) {
+ ssw.WriteByte(i);
+ EXPECT_EQ(kChunkSize - i - 1, ssw.bytes_available());
+ }
+ EXPECT_EQ(1u, delegate.chunks().size());
+ EXPECT_EQ(0u, ssw.bytes_available());
+
+ // This extra write will cause the first extension.
+ ssw.WriteBytes(kOneByteBuf, sizeof(kOneByteBuf));
+ EXPECT_EQ(2u, delegate.chunks().size());
+ EXPECT_EQ(7u, ssw.bytes_available());
+
+ // This starts at offset 1, to make sure we don't hardcode any assumption
+ // about alignment.
+ ContiguousMemoryRange reserved_range_1 = ssw.ReserveBytes(4);
+ EXPECT_EQ(2u, delegate.chunks().size());
+ EXPECT_EQ(3u, ssw.bytes_available());
+
+ ssw.WriteByte(0xFF);
+ ssw.WriteBytes(kThreeByteBuf, sizeof(kThreeByteBuf));
+ EXPECT_EQ(3u, delegate.chunks().size());
+ EXPECT_EQ(7u, ssw.bytes_available());
+
+ ContiguousMemoryRange reserved_range_2 = ssw.ReserveBytes(4);
+ ssw.WriteBytes(kTwentyByteBuf, sizeof(kTwentyByteBuf));
+ EXPECT_EQ(6u, delegate.chunks().size());
+ EXPECT_EQ(7u, ssw.bytes_available());
+
+ // Writing reserved bytes should not change the bytes_available().
+ memcpy(reserved_range_1.begin, kFourByteBuf, sizeof(kFourByteBuf));
+ memcpy(reserved_range_2.begin, kFourByteBuf, sizeof(kFourByteBuf));
+ EXPECT_EQ(6u, delegate.chunks().size());
+ EXPECT_EQ(7u, ssw.bytes_available());
+
+ // Check that reserving more bytes than what left creates a brand new chunk
+ // even if the previous one is not exhausted
+ for (uint8_t i = 0; i < 5; ++i)
+ ssw.WriteByte(0xFF);
+ memcpy(ssw.ReserveBytes(4).begin, kFourByteBuf, sizeof(kFourByteBuf));
+ memcpy(ssw.ReserveBytesUnsafe(3), kThreeByteBuf, sizeof(kThreeByteBuf));
+ memcpy(ssw.ReserveBytes(3).begin, kThreeByteBuf, sizeof(kThreeByteBuf));
+ memcpy(ssw.ReserveBytesUnsafe(1), kOneByteBuf, sizeof(kOneByteBuf));
+ memcpy(ssw.ReserveBytes(1).begin, kOneByteBuf, sizeof(kOneByteBuf));
+
+ EXPECT_EQ(8u, delegate.chunks().size());
+ EXPECT_EQ(3u, ssw.bytes_available());
+
+ EXPECT_EQ("0001020304050607", delegate.GetChunkAsString(0));
+ EXPECT_EQ("4060616263FF5051", delegate.GetChunkAsString(1));
+ EXPECT_EQ("5260616263A0A1A2", delegate.GetChunkAsString(2));
+ EXPECT_EQ("A3A4A5A6A7A8A9AA", delegate.GetChunkAsString(3));
+ EXPECT_EQ("ABACADAEAFB0B1B2", delegate.GetChunkAsString(4));
+ EXPECT_EQ("B3FFFFFFFFFF0000", delegate.GetChunkAsString(5));
+ EXPECT_EQ("6061626350515200", delegate.GetChunkAsString(6));
+ EXPECT_EQ("5051524040000000", delegate.GetChunkAsString(7));
+
+ // Finally reset the writer to a new buffer.
+ uint8_t other_buffer[8] = {0};
+ ssw.Reset({other_buffer, other_buffer + sizeof(other_buffer)});
+ EXPECT_EQ(other_buffer, ssw.write_ptr());
+ ssw.WriteByte(1);
+ ssw.WriteBytes(kThreeByteBuf, sizeof(kThreeByteBuf));
+ EXPECT_EQ(1u, other_buffer[0]);
+ EXPECT_EQ(0x52u, other_buffer[3]);
+}
+
+} // namespace
+} // namespace protozero
diff --git a/src/protozero/test/example_proto/library.proto b/src/protozero/test/example_proto/library.proto
new file mode 100644
index 0000000..4ff8fee
--- /dev/null
+++ b/src/protozero/test/example_proto/library.proto
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+
+package foo.bar;
+
+import public "src/protozero/test/example_proto/library_internals/galaxies.proto";
+
+message TransgalacticMessage {
+ optional Galaxy origin_galaxy = 1;
+ optional string origin_planet = 2;
+ optional Galaxy destination_galaxy = 3;
+ optional string destination_planet = 4;
+ optional bytes proto_message = 5;
+}
diff --git a/src/protozero/test/example_proto/library_internals/galaxies.proto b/src/protozero/test/example_proto/library_internals/galaxies.proto
new file mode 100644
index 0000000..0b1c1c5
--- /dev/null
+++ b/src/protozero/test/example_proto/library_internals/galaxies.proto
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+
+package foo.bar;
+
+import public "src/protozero/test/example_proto/upper_import.proto";
+
+enum Galaxy {
+ MILKY_WAY = 1;
+ ANDROMEDA = 2;
+ SUNFLOWER = 3;
+}
diff --git a/src/protozero/test/example_proto/test_messages.proto b/src/protozero/test/example_proto/test_messages.proto
new file mode 100644
index 0000000..077b088
--- /dev/null
+++ b/src/protozero/test/example_proto/test_messages.proto
@@ -0,0 +1,100 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+
+package foo.bar;
+
+import "src/protozero/test/example_proto/library.proto";
+
+// This file contains comprehensive set of supported message structures and
+// data types. Unit tests depends on the plugin-processed version of this file.
+
+// Tests importing message definition from another proto file.
+message TransgalacticParcel {
+ optional TransgalacticMessage message = 1;
+ optional string tracking_code = 2;
+}
+
+enum SmallEnum {
+ TO_BE = 1;
+ NOT_TO_BE = 0;
+}
+
+enum SignedEnum {
+ POSITIVE = 1;
+ NEUTRAL = 0;
+ NEGATIVE = -1;
+}
+
+enum BigEnum {
+ BEGIN = 10;
+ END = 100500;
+}
+
+message EveryField {
+ optional int32 field_int32 = 1;
+ optional int64 field_int64 = 2;
+ optional uint32 field_uint32 = 3;
+ optional uint64 field_uint64 = 4;
+ optional sint32 field_sint32 = 5;
+ optional sint64 field_sint64 = 6;
+ optional fixed32 field_fixed32 = 7;
+ optional fixed64 field_fixed64 = 8;
+ optional sfixed32 field_sfixed32 = 9;
+ optional sfixed64 field_sfixed64 = 10;
+ optional float field_float = 11;
+ optional double field_double = 12;
+ optional bool field_bool = 13;
+
+ optional SmallEnum small_enum = 51;
+ optional SignedEnum signed_enum = 52;
+ optional BigEnum big_enum = 53;
+
+ optional string field_string = 500;
+ optional bytes field_bytes = 505;
+
+ enum NestedEnum {
+ PING = 1;
+ PONG = 2;
+ }
+ optional NestedEnum nested_enum = 600;
+
+ repeated int32 repeated_int32 = 999;
+}
+
+message NestedA {
+ message NestedB {
+ message NestedC { optional int32 value_c = 1; }
+ optional NestedC value_b = 1;
+ }
+ repeated NestedB repeated_a = 2;
+ optional NestedB.NestedC super_nested = 3;
+}
+
+message CamelCaseFields {
+ // To check that any reasonable name converts to camel case correctly.
+ optional bool foo_bar_baz = 1;
+ optional bool barBaz = 2;
+ optional bool MooMoo = 3;
+ optional bool URLEncoder = 4;
+ optional bool XMap = 5;
+ optional bool UrLE_nco__der = 6;
+ optional bool __bigBang = 7;
+ optional bool U2 = 8;
+ optional bool bangBig__ = 9;
+}
diff --git a/src/protozero/test/example_proto/upper_import.proto b/src/protozero/test/example_proto/upper_import.proto
new file mode 100644
index 0000000..4aacd41
--- /dev/null
+++ b/src/protozero/test/example_proto/upper_import.proto
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+
+package foo.bar;
+
+message TrickyPublicImport {}
diff --git a/src/protozero/test/fake_scattered_buffer.cc b/src/protozero/test/fake_scattered_buffer.cc
new file mode 100644
index 0000000..3c48f48
--- /dev/null
+++ b/src/protozero/test/fake_scattered_buffer.cc
@@ -0,0 +1,73 @@
+/*
+ * 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 "src/protozero/test/fake_scattered_buffer.h"
+
+#include <sstream>
+#include <utility>
+
+#include "gtest/gtest.h"
+
+namespace protozero {
+
+namespace {
+
+std::string ToHex(const void* data, size_t length) {
+ std::ostringstream ss;
+ ss << std::hex << std::setfill('0');
+ ss << std::uppercase;
+ for (size_t i = 0; i < length; i++) {
+ char c = reinterpret_cast<const char*>(data)[i];
+ ss << std::setw(2) << (static_cast<unsigned>(c) & 0xFF);
+ }
+ return ss.str();
+}
+
+} // namespace
+
+FakeScatteredBuffer::FakeScatteredBuffer(size_t chunk_size)
+ : chunk_size_(chunk_size) {}
+
+FakeScatteredBuffer::~FakeScatteredBuffer() {}
+
+ContiguousMemoryRange FakeScatteredBuffer::GetNewBuffer() {
+ std::unique_ptr<uint8_t[]> chunk(new uint8_t[chunk_size_]);
+ uint8_t* begin = chunk.get();
+ memset(begin, 0, chunk_size_);
+ chunks_.push_back(std::move(chunk));
+ return {begin, begin + chunk_size_};
+}
+
+std::string FakeScatteredBuffer::GetChunkAsString(size_t chunk_index) {
+ return ToHex(chunks_[chunk_index].get(), chunk_size_);
+}
+
+void FakeScatteredBuffer::GetBytes(size_t start, size_t length, uint8_t* buf) {
+ ASSERT_LE(start + length, chunks_.size() * chunk_size_);
+ for (size_t pos = 0; pos < length; ++pos) {
+ size_t chunk_index = (start + pos) / chunk_size_;
+ size_t chunk_offset = (start + pos) % chunk_size_;
+ buf[pos] = chunks_[chunk_index].get()[chunk_offset];
+ }
+}
+
+std::string FakeScatteredBuffer::GetBytesAsString(size_t start, size_t length) {
+ std::unique_ptr<uint8_t[]> buffer(new uint8_t[length]);
+ GetBytes(start, length, buffer.get());
+ return ToHex(buffer.get(), length);
+}
+
+} // namespace protozero
diff --git a/src/protozero/test/fake_scattered_buffer.h b/src/protozero/test/fake_scattered_buffer.h
new file mode 100644
index 0000000..2ccfb81
--- /dev/null
+++ b/src/protozero/test/fake_scattered_buffer.h
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_PROTOZERO_TEST_FAKE_SCATTERED_BUFFER_H_
+#define SRC_PROTOZERO_TEST_FAKE_SCATTERED_BUFFER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "perfetto/protozero/scattered_stream_writer.h"
+
+namespace protozero {
+
+// A simple ScatteredStreamWriter::Delegate implementation which just allocates
+// chunks of a fixed size.
+class FakeScatteredBuffer : public ScatteredStreamWriter::Delegate {
+ public:
+ explicit FakeScatteredBuffer(size_t chunk_size);
+ ~FakeScatteredBuffer() override;
+
+ // ScatteredStreamWriter::Delegate implementation.
+ ContiguousMemoryRange GetNewBuffer() override;
+
+ std::string GetChunkAsString(size_t chunk_index);
+
+ void GetBytes(size_t start, size_t length, uint8_t* buf);
+ std::string GetBytesAsString(size_t start, size_t length);
+
+ const std::vector<std::unique_ptr<uint8_t[]>>& chunks() const {
+ return chunks_;
+ }
+
+ private:
+ const size_t chunk_size_;
+ std::vector<std::unique_ptr<uint8_t[]>> chunks_;
+};
+
+} // namespace protozero
+
+#endif // SRC_PROTOZERO_TEST_FAKE_SCATTERED_BUFFER_H_
diff --git a/src/protozero/test/protozero_conformance_unittest.cc b/src/protozero/test/protozero_conformance_unittest.cc
new file mode 100644
index 0000000..bb60620
--- /dev/null
+++ b/src/protozero/test/protozero_conformance_unittest.cc
@@ -0,0 +1,191 @@
+/*
+ * 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 <limits>
+#include <memory>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "perfetto/protozero/protozero_message_handle.h"
+#include "src/protozero/test/fake_scattered_buffer.h"
+
+// Autogenerated headers in out/*/gen/
+#include "src/protozero/test/example_proto/library.pbzero.h"
+#include "src/protozero/test/example_proto/test_messages.pb.h"
+#include "src/protozero/test/example_proto/test_messages.pbzero.h"
+
+namespace pbtest = foo::bar::pbzero; // Generated by the protozero plugin.
+namespace pbgold = foo::bar; // Generated by the official protobuf compiler.
+
+namespace protozero {
+namespace {
+
+constexpr size_t kChunkSize = 42;
+
+class ProtoZeroConformanceTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ buffer_.reset(new FakeScatteredBuffer(kChunkSize));
+ stream_writer_.reset(new ScatteredStreamWriter(buffer_.get()));
+ }
+
+ void TearDown() override {
+ root_messages_.clear();
+ stream_writer_.reset();
+ buffer_.reset();
+ google::protobuf::ShutdownProtobufLibrary();
+ }
+
+ protected:
+ template <class T>
+ T* CreateMessage() {
+ T* message = new T();
+ root_messages_.push_back(std::unique_ptr<T>(message));
+ message->Reset(stream_writer_.get());
+ return message;
+ }
+
+ size_t GetNumSerializedBytes() {
+ return buffer_->chunks().size() * kChunkSize -
+ stream_writer_->bytes_available();
+ }
+
+ void GetSerializedBytes(size_t start, size_t length, uint8_t* buffer) {
+ return buffer_->GetBytes(start, length, buffer);
+ }
+
+ private:
+ std::unique_ptr<FakeScatteredBuffer> buffer_;
+ std::unique_ptr<ScatteredStreamWriter> stream_writer_;
+ std::vector<std::unique_ptr<ProtoZeroMessage>> root_messages_;
+};
+
+TEST_F(ProtoZeroConformanceTest, SimpleFieldsNoNesting) {
+ auto* msg = CreateMessage<pbtest::EveryField>();
+
+ msg->set_field_int32(-1);
+ msg->set_field_int64(-333123456789ll);
+ msg->set_field_uint32(600);
+ msg->set_field_uint64(333123456789ll);
+ msg->set_field_sint32(-5);
+ msg->set_field_sint64(-9000);
+ msg->set_field_fixed32(12345);
+ msg->set_field_fixed64(444123450000ll);
+ msg->set_field_sfixed32(-69999);
+ msg->set_field_sfixed64(-200);
+ msg->set_field_float(3.14f);
+ msg->set_field_double(0.5555);
+ msg->set_field_bool(true);
+ msg->set_small_enum(pbtest::SmallEnum::TO_BE);
+ msg->set_signed_enum(pbtest::SignedEnum::NEGATIVE);
+ msg->set_big_enum(pbtest::BigEnum::BEGIN);
+ msg->set_field_string("FizzBuzz");
+ msg->set_field_bytes(reinterpret_cast<const uint8_t*>("\x11\x00\xBE\xEF"), 4);
+ msg->add_repeated_int32(1);
+ msg->add_repeated_int32(-1);
+ msg->add_repeated_int32(100);
+ msg->add_repeated_int32(2000000);
+
+ size_t msg_size = GetNumSerializedBytes();
+ EXPECT_EQ(126u, msg_size);
+
+ std::unique_ptr<uint8_t[]> msg_binary(new uint8_t[msg_size]);
+ GetSerializedBytes(0, msg_size, msg_binary.get());
+
+ pbgold::EveryField gold_msg;
+ gold_msg.ParseFromArray(msg_binary.get(), static_cast<int>(msg_size));
+ EXPECT_EQ(-1, gold_msg.field_int32());
+ EXPECT_EQ(-333123456789ll, gold_msg.field_int64());
+ EXPECT_EQ(600u, gold_msg.field_uint32());
+ EXPECT_EQ(333123456789ull, gold_msg.field_uint64());
+ EXPECT_EQ(-5, gold_msg.field_sint32());
+ EXPECT_EQ(-9000, gold_msg.field_sint64());
+ EXPECT_EQ(12345u, gold_msg.field_fixed32());
+ EXPECT_EQ(444123450000ull, gold_msg.field_fixed64());
+ EXPECT_EQ(-69999, gold_msg.field_sfixed32());
+ EXPECT_EQ(-200, gold_msg.field_sfixed64());
+ EXPECT_FLOAT_EQ(3.14f, gold_msg.field_float());
+ EXPECT_DOUBLE_EQ(0.5555, gold_msg.field_double());
+ EXPECT_EQ(true, gold_msg.field_bool());
+ EXPECT_EQ(pbgold::SmallEnum::TO_BE, gold_msg.small_enum());
+ EXPECT_EQ(pbgold::SignedEnum::NEGATIVE, gold_msg.signed_enum());
+ EXPECT_EQ(pbgold::BigEnum::BEGIN, gold_msg.big_enum());
+ EXPECT_EQ("FizzBuzz", gold_msg.field_string());
+ EXPECT_EQ(std::string("\x11\x00\xBE\xEF", 4), gold_msg.field_bytes());
+ EXPECT_EQ(4, gold_msg.repeated_int32_size());
+ EXPECT_EQ(1, gold_msg.repeated_int32(0));
+ EXPECT_EQ(-1, gold_msg.repeated_int32(1));
+ EXPECT_EQ(100, gold_msg.repeated_int32(2));
+ EXPECT_EQ(2000000, gold_msg.repeated_int32(3));
+}
+
+TEST_F(ProtoZeroConformanceTest, NestedMessages) {
+ auto* msg_a = CreateMessage<pbtest::NestedA>();
+
+ pbtest::NestedA::NestedB* msg_b = msg_a->add_repeated_a();
+ pbtest::NestedA::NestedB::NestedC* msg_c = msg_b->set_value_b();
+ msg_c->set_value_c(321);
+ msg_b = msg_a->add_repeated_a();
+ msg_c = msg_a->set_super_nested();
+ msg_c->set_value_c(1000);
+ msg_a->Finalize();
+
+ size_t msg_size = GetNumSerializedBytes();
+ EXPECT_EQ(26u, msg_size);
+
+ std::unique_ptr<uint8_t[]> msg_binary(new uint8_t[msg_size]);
+ GetSerializedBytes(0, msg_size, msg_binary.get());
+
+ pbgold::NestedA gold_msg_a;
+ gold_msg_a.ParseFromArray(msg_binary.get(), static_cast<int>(msg_size));
+ EXPECT_EQ(2, gold_msg_a.repeated_a_size());
+ EXPECT_EQ(321, gold_msg_a.repeated_a(0).value_b().value_c());
+ EXPECT_FALSE(gold_msg_a.repeated_a(1).has_value_b());
+ EXPECT_EQ(1000, gold_msg_a.super_nested().value_c());
+}
+
+TEST(ProtoZeroTest, Simple) {
+ // Test the includes for indirect public import: library.pbzero.h ->
+ // library_internals/galaxies.pbzero.h -> upper_import.pbzero.h .
+ EXPECT_LE(0u, sizeof(pbtest::TrickyPublicImport));
+}
+
+TEST(ProtoZeroTest, Reflection) {
+ // Tests camel case conversion as well.
+ EXPECT_EQ(1, pbtest::CamelCaseFields::kFooBarBazFieldNumber);
+ EXPECT_EQ(2, pbtest::CamelCaseFields::kBarBazFieldNumber);
+ EXPECT_EQ(3, pbtest::CamelCaseFields::kMooMooFieldNumber);
+ EXPECT_EQ(4, pbtest::CamelCaseFields::kURLEncoderFieldNumber);
+ EXPECT_EQ(5, pbtest::CamelCaseFields::kXMapFieldNumber);
+ EXPECT_EQ(6, pbtest::CamelCaseFields::kUrLENcoDerFieldNumber);
+ EXPECT_EQ(7, pbtest::CamelCaseFields::kBigBangFieldNumber);
+ EXPECT_EQ(8, pbtest::CamelCaseFields::kU2FieldNumber);
+ EXPECT_EQ(9, pbtest::CamelCaseFields::kBangBigFieldNumber);
+
+ const ProtoFieldDescriptor* reflection =
+ pbtest::EveryField::GetFieldDescriptor(
+ pbtest::EveryField::kFieldInt32FieldNumber);
+ EXPECT_STREQ("field_int32", reflection->name());
+ EXPECT_EQ(ProtoFieldDescriptor::Type::TYPE_INT32, reflection->type());
+ EXPECT_EQ(1u, reflection->number());
+ EXPECT_FALSE(reflection->is_repeated());
+ EXPECT_TRUE(reflection->is_valid());
+
+ EXPECT_FALSE(pbtest::TransgalacticParcel::GetFieldDescriptor(42)->is_valid());
+}
+
+} // namespace
+} // namespace protozero