Add an Message::AppendScatteredBytes function

This takes an array of buffers which will all be copied into the field
as raw bytes; prior to this AppendBytes() only took a single buffer,
which meant an extra N memcpy's if a heap-allocated message composed
of multiple buffers needed to be copied into a submessage.

R=primiano@google.com

Change-Id: I34e3d8d63316357f190fe8530b3fb0fbcde01536
diff --git a/include/perfetto/protozero/message.h b/include/perfetto/protozero/message.h
index 555e128..cf5499d 100644
--- a/include/perfetto/protozero/message.h
+++ b/include/perfetto/protozero/message.h
@@ -146,6 +146,12 @@
   void AppendString(uint32_t field_id, const char* str);
   void AppendBytes(uint32_t field_id, const void* value, size_t size);
 
+  // Append raw bytes for a field, using the supplied |ranges| to
+  // copy from |num_ranges| individual buffers.
+  size_t AppendScatteredBytes(uint32_t field_id,
+                              ContiguousMemoryRange* ranges,
+                              size_t num_ranges);
+
   // Begins a nested message, using the static storage provided by the parent
   // class (see comment in |nested_messages_arena_|). The nested message ends
   // either when Finalize() is called or when any other Append* method is called
diff --git a/src/protozero/message.cc b/src/protozero/message.cc
index 843f76d..4559e21 100644
--- a/src/protozero/message.cc
+++ b/src/protozero/message.cc
@@ -86,6 +86,31 @@
   WriteToStream(src_u8, src_u8 + size);
 }
 
+size_t Message::AppendScatteredBytes(uint32_t field_id,
+                                     ContiguousMemoryRange* ranges,
+                                     size_t num_ranges) {
+  size_t size = 0;
+  for (size_t i = 0; i < num_ranges; ++i) {
+    size += ranges[i].size();
+  }
+
+  PERFETTO_DCHECK(size < proto_utils::kMaxMessageLength);
+
+  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);
+
+  for (size_t i = 0; i < num_ranges; ++i) {
+    auto& range = ranges[i];
+    WriteToStream(range.begin, range.end);
+  }
+
+  return size;
+}
+
 uint32_t Message::Finalize() {
   if (finalized_)
     return size_;
diff --git a/src/protozero/message_unittest.cc b/src/protozero/message_unittest.cc
index 7240f7e..c90acea 100644
--- a/src/protozero/message_unittest.cc
+++ b/src/protozero/message_unittest.cc
@@ -207,6 +207,28 @@
   ASSERT_EQ("2803", GetNextSerializedBytes(2));
 }
 
+// Tests using a AppendScatteredBytes to append raw bytes to
+// a message using multiple individual buffers.
+TEST_F(MessageTest, AppendScatteredBytes) {
+  Message* root_msg = NewMessage();
+
+  uint8_t buffer[42];
+  memset(buffer, 0x42, sizeof(buffer));
+
+  ContiguousMemoryRange ranges[] = {{buffer, buffer + sizeof(buffer)},
+                                    {buffer, buffer + sizeof(buffer)}};
+  root_msg->AppendScatteredBytes(1 /* field_id */, ranges, 2);
+  EXPECT_EQ(86u, root_msg->Finalize());
+  EXPECT_EQ(86u, GetNumSerializedBytes());
+
+  // field_id
+  EXPECT_EQ("0A", GetNextSerializedBytes(1));
+  // field length
+  EXPECT_EQ("54", GetNextSerializedBytes(1));
+  // start of contents
+  EXPECT_EQ("42424242", GetNextSerializedBytes(4));
+}
+
 // Checks that the size field of root and nested messages is properly written
 // on finalization.
 TEST_F(MessageTest, BackfillSizeOnFinalization) {