Introduce core ProtoZero support classes (Part 1)

Introduces the core classes to deal with zero-copy
zero-malloc append-only protobuf generation.
The actual ProtoZeroMessage will come in a separate
CL.

Change-Id: I4b278cfec6409f745532b17e9adaf2bb09ee2c92
diff --git a/BUILD.gn b/BUILD.gn
index 627e062..04236d6 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -29,6 +29,7 @@
     "//libftrace:libftrace_unittests",
     "//libtracing:libtracing_benchmarks",
     "//libtracing:libtracing_unittests",
+    "//protozero:protozero_unittests",
     "//tools/ftrace_proto_gen:ftrace_proto_gen_unittests",
     "//tools/sanitizers_unittests",
   ]
diff --git a/cpp_common/base.h b/cpp_common/base.h
new file mode 100644
index 0000000..fcee5f0
--- /dev/null
+++ b/cpp_common/base.h
@@ -0,0 +1,84 @@
+/*
+ * 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 CPP_COMMON_BASE_H_
+#define CPP_COMMON_BASE_H_
+
+// DO NOT include this file in public headers (include/) to avoid collisions.
+
+#include <errno.h>
+#include <stdlib.h>
+
+#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON)
+#define DCHECK_IS_ON() 0
+#else
+#define DCHECK_IS_ON() 1
+#endif
+
+#if DCHECK_IS_ON()
+#include <stdio.h>   // For fprintf.
+#include <string.h>  // For strerror.
+#endif
+
+#define HANDLE_EINTR(x)                                     \
+  ({                                                        \
+    decltype(x) eintr_wrapper_result;                       \
+    do {                                                    \
+      eintr_wrapper_result = (x);                           \
+    } while (eintr_wrapper_result == -1 && errno == EINTR); \
+    eintr_wrapper_result;                                   \
+  })
+
+#if DCHECK_IS_ON()
+#define DLOG(fmt, ...) fprintf(stderr, fmt "\n", ##__VA_ARGS__)
+#define DPLOG(x)                                                          \
+  fprintf(stderr, x " %s:%d (errno: %d %s)\n", __FILE__, __LINE__, errno, \
+          strerror(errno))
+#define DCHECK(x)      \
+  do {                 \
+    if (!(x)) {        \
+      DPLOG("CHECK "); \
+      abort();         \
+    }                  \
+  } while (0)
+#else
+#define DLOG(...) \
+  do {            \
+  } while (0)
+#define DPLOG(...) \
+  do {             \
+  } while (0)
+#define DCHECK(x) ::ignore_result(x)
+#endif  // DCHECK_IS_ON()
+
+#if DCHECK_IS_ON()
+#define CHECK(x) DCHECK(x)
+#else
+#define CHECK(x)                        \
+  do {                                  \
+    if (!__builtin_expect(!!(x), true)) \
+      abort();                          \
+  } while (0)
+#endif  // DCHECK_IS_ON()
+
+template <typename T, size_t N>
+char (&ArraySizeHelper(T (&array)[N]))[N];
+#define arraysize(array) (sizeof(ArraySizeHelper(array)))
+
+template <typename T>
+inline void ignore_result(const T&) {}
+
+#endif  // CPP_COMMON_BASE_H_
diff --git a/cpp_common/build_config.h b/cpp_common/build_config.h
new file mode 100644
index 0000000..a7a540f
--- /dev/null
+++ b/cpp_common/build_config.h
@@ -0,0 +1,42 @@
+/*
+ * 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 CPP_COMMON_BUILD_CONFIG_H_
+#define CPP_COMMON_BUILD_CONFIG_H_
+
+// Allows to define build flags that give a compiler error if the header that
+// defined the flag is not included, instead of silently ignoring the #if block.
+#define BUILDFLAG_CAT_INDIRECT(a, b) a##b
+#define BUILDFLAG_CAT(a, b) BUILDFLAG_CAT_INDIRECT(a, b)
+#define BUILDFLAG(flag) (BUILDFLAG_CAT(BUILDFLAG_DEFINE_, flag)())
+
+#if defined(ANDROID)
+#define BUILDFLAG_DEFINE_OS_ANDROID() 1
+#define BUILDFLAG_DEFINE_OS_MACOSX() 0
+#define BUILDFLAG_DEFINE_OS_LINUX() 0
+#elif defined(__APPLE__)
+#define BUILDFLAG_DEFINE_OS_ANDROID() 0
+#define BUILDFLAG_DEFINE_OS_MACOSX() 1
+#define BUILDFLAG_DEFINE_OS_LINUX() 0
+#elif defined(__linux__)
+#define BUILDFLAG_DEFINE_OS_ANDROID() 0
+#define BUILDFLAG_DEFINE_OS_MACOSX() 0
+#define BUILDFLAG_DEFINE_OS_LINUX() 1
+#else
+#error OS not supported (see build_config.h)
+#endif
+
+#endif  // CPP_COMMON_BUILD_CONFIG_H_
diff --git a/protozero/BUILD.gn b/protozero/BUILD.gn
new file mode 100644
index 0000000..074ead9
--- /dev/null
+++ b/protozero/BUILD.gn
@@ -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.
+
+config("protozero_config") {
+  include_dirs = [ "include" ]
+}
+
+source_set("protozero") {
+  all_dependent_configs = [ ":protozero_config" ]
+  sources = [
+    "src/proto_utils.cc",
+    "src/scattered_stream_writer.cc",
+  ]
+}
+
+executable("protozero_unittests") {
+  testonly = true
+  deps += [
+    ":protozero",
+    "//buildtools:gmock",
+    "//buildtools:gtest",
+    "//buildtools:gtest_main",
+  ]
+  sources = [
+    "src/proto_utils_unittest.cc",
+    "src/scattered_stream_writer_unittest.cc",
+    "src/test/fake_scattered_buffer.cc",
+  ]
+}
diff --git a/protozero/README.md b/protozero/README.md
new file mode 100644
index 0000000..113961d
--- /dev/null
+++ b/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/protozero/include/protozero/contiguous_memory_range.h b/protozero/include/protozero/contiguous_memory_range.h
new file mode 100644
index 0000000..a508e32
--- /dev/null
+++ b/protozero/include/protozero/contiguous_memory_range.h
@@ -0,0 +1,38 @@
+/*
+ * 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 PROTOZERO_INCLUDE_PROTOZERO_CONTIGUOUS_MEMORY_RANGE_H_
+#define PROTOZERO_INCLUDE_PROTOZERO_CONTIGUOUS_MEMORY_RANGE_H_
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+
+namespace protozero {
+
+// Keep this struct trivially constructible (no ctors, no default initializers).
+struct ContiguousMemoryRange {
+  uint8_t* begin;
+  uint8_t* end;  // STL style: one byte past the end of the buffer.
+
+  inline bool is_valid() const { return begin != nullptr; }
+  inline void reset() { begin = nullptr; }
+  inline size_t size() { return static_cast<size_t>(end - begin); }
+};
+
+}  // namespace protozero
+
+#endif  // PROTOZERO_INCLUDE_PROTOZERO_CONTIGUOUS_MEMORY_RANGE_H_
diff --git a/protozero/include/protozero/proto_utils.h b/protozero/include/protozero/proto_utils.h
new file mode 100644
index 0000000..61efde3
--- /dev/null
+++ b/protozero/include/protozero/proto_utils.h
@@ -0,0 +1,131 @@
+/*
+ * 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 PROTOZERO_INCLUDE_PROTOZERO_PROTO_UTILS_H_
+#define PROTOZERO_INCLUDE_PROTOZERO_PROTO_UTILS_H_
+
+#include <inttypes.h>
+#include <stddef.h>
+
+#include <type_traits>
+
+namespace protozero {
+namespace proto_utils {
+
+// See https://developers.google.com/protocol-buffers/docs/encoding wire types.
+
+enum FieldType : uint32_t {
+  kFieldTypeVarInt = 0,
+  kFieldTypeFixed64 = 1,
+  kFieldTypeLengthDelimited = 2,
+  kFieldTypeFixed32 = 5,
+};
+
+// Maximum message size supported: 256 MiB (4 x 7-bit due to varint encoding).
+constexpr size_t kMessageLengthFieldSize = 4;
+constexpr size_t kMaxMessageLength = (1u << (kMessageLengthFieldSize * 7)) - 1;
+
+// Field tag is encoded as 32-bit varint (5 bytes at most).
+// Largest value of simple (not length-delimited) field is 64-bit varint
+// (10 bytes at most). 15 bytes buffer is enough to store a simple field.
+constexpr size_t kMaxTagEncodedSize = 5;
+constexpr size_t kMaxSimpleFieldEncodedSize = kMaxTagEncodedSize + 10;
+
+// Proto types: (int|uint|sint)(32|64), bool, enum.
+constexpr uint32_t MakeTagVarInt(uint32_t field_id) {
+  return (field_id << 3) | kFieldTypeVarInt;
+}
+
+// Proto types: fixed64, sfixed64, fixed32, sfixed32, double, float.
+template <typename T>
+constexpr uint32_t MakeTagFixed(uint32_t field_id) {
+  static_assert(sizeof(T) == 8 || sizeof(T) == 4, "Value must be 4 or 8 bytes");
+  return (field_id << 3) |
+         (sizeof(T) == 8 ? kFieldTypeFixed64 : kFieldTypeFixed32);
+}
+
+// Proto types: string, bytes, embedded messages.
+constexpr uint32_t MakeTagLengthDelimited(uint32_t field_id) {
+  return (field_id << 3) | kFieldTypeLengthDelimited;
+}
+
+// Proto types: sint64, sint32.
+template <typename T>
+inline typename std::make_unsigned<T>::type ZigZagEncode(T value) {
+  return static_cast<typename std::make_unsigned<T>::type>(
+      (value << 1) ^ (value >> (sizeof(T) * 8 - 1)));
+}
+
+template <typename T>
+inline uint8_t* WriteVarInt(T value, uint8_t* target) {
+  // Avoid arithmetic (sign expanding) shifts.
+  using UnsignedType = typename std::make_unsigned<T>::type;
+  UnsignedType unsigned_value = static_cast<UnsignedType>(value);
+
+  while (unsigned_value >= 0x80) {
+    *target++ = static_cast<uint8_t>(unsigned_value) | 0x80;
+    unsigned_value >>= 7;
+  }
+  *target = static_cast<uint8_t>(unsigned_value);
+  return target + 1;
+}
+
+// Writes a fixed-size redundant encoding of the given |value|. This is
+// used to backfill fixed-size reservations for the length field using a
+// non-canonical varint encoding (e.g. \x81\x80\x80\x00 instead of \x01).
+// See https://github.com/google/protobuf/issues/1530.
+// In particular, this is used for nested messages. The size of a nested message
+// is not known until all its field have been written. |kMessageLengthFieldSize|
+// bytes are reserved to encode the size field and backfilled at the end.
+inline void WriteRedundantVarInt(uint32_t value, uint8_t* buf) {
+  for (size_t i = 0; i < kMessageLengthFieldSize; ++i) {
+    const uint8_t msb = (i < kMessageLengthFieldSize - 1) ? 0x80 : 0;
+    buf[i] = static_cast<uint8_t>(value) | msb;
+    value >>= 7;
+  }
+}
+
+template <uint32_t field_id>
+void StaticAssertSingleBytePreamble() {
+  static_assert(field_id < 16,
+                "Proto field id too big to fit in a single byte preamble");
+};
+
+// Parses a VarInt from the encoded buffer [start, end). |end| is STL-style and
+// points one byte past the end of buffer.
+// The parsed int value is stored in the output arg |value|. Returns a pointer
+// to the next unconsumed byte (so start < retval <= end).
+const uint8_t* ParseVarInt(const uint8_t* start,
+                           const uint8_t* end,
+                           uint64_t* value);
+
+// Parses a protobuf field and computes its id, type and value.
+// Returns a pointer to the next unconsumed byte (|start| < retval <= end) that
+// is either the beginning of the next field or the end of the parent message.
+// In the case of a kFieldTypeLengthDelimited field, |field_intvalue| will
+// store the length of the payload (either a string or a nested message). In
+// this case, the start of the payload will be at (return value) -
+// |field_intvalue|.
+const uint8_t* ParseField(const uint8_t* start,
+                          const uint8_t* end,
+                          uint32_t* field_id,
+                          FieldType* field_type,
+                          uint64_t* field_intvalue);
+
+}  // namespace proto_utils
+}  // namespace protozero
+
+#endif  // PROTOZERO_INCLUDE_PROTOZERO_PROTO_UTILS_H_
diff --git a/protozero/include/protozero/scattered_stream_writer.h b/protozero/include/protozero/scattered_stream_writer.h
new file mode 100644
index 0000000..d7930a1
--- /dev/null
+++ b/protozero/include/protozero/scattered_stream_writer.h
@@ -0,0 +1,120 @@
+/*
+ * 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 PROTOZERO_INCLUDE_PROTOZERO_SCATTERED_STREAM_WRITER_H_
+#define PROTOZERO_INCLUDE_PROTOZERO_SCATTERED_STREAM_WRITER_H_
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "protozero/contiguous_memory_range.h"
+
+namespace protozero {
+
+// This class deals with the following problem: append-only proto messages want
+// to write a stream of bytes, without caring about the implementation of the
+// underlying buffer (which concretely will be either the trace ring buffer
+// or a heap-allocated buffer). The main deal is: proto messages don't know in
+// advance what their size will be.
+// Due to the tracing buffer being split into fixed-size chunks, on some
+// occasions, these writes need to be spread over two (or more) non-contiguous
+// chunks of memory. Similarly, when the buffer is backed by the heap, we want
+// to avoid realloc() calls, as they might cause a full copy of the contents
+// of the buffer.
+// The purpose of this class is to abtract away the non-contiguous write logic.
+// This class knows how to deal with writes as long as they fall in the same
+// ContiguousMemoryRange and defers the chunk-chaining logic to the Delegate.
+class ScatteredStreamWriter {
+ public:
+  class Delegate {
+   public:
+    virtual ~Delegate();
+    virtual ContiguousMemoryRange GetNewBuffer() = 0;
+  };
+
+  explicit ScatteredStreamWriter(Delegate* delegate);
+  ~ScatteredStreamWriter();
+
+  inline void WriteByte(uint8_t value) {
+    if (write_ptr_ >= cur_range_.end)
+      Extend();
+    *write_ptr_++ = value;
+  }
+
+  // Assumes that the caller checked that there is enough headroom.
+  // TODO(primiano): perf optimization, this is a tracing hot path. The
+  // compiler can make strong optimization on memcpy if the size arg is a
+  // constexpr. Make a templated variant of this for fixed-size writes.
+  // TODO(primiano): restrict / noalias might also help.
+  inline void WriteBytesUnsafe(const uint8_t* src, size_t size) {
+    uint8_t* const end = write_ptr_ + size;
+    assert(end <= cur_range_.end);
+    memcpy(write_ptr_, src, size);
+    write_ptr_ = end;
+  }
+
+  inline void WriteBytes(const uint8_t* src, size_t size) {
+    uint8_t* const end = write_ptr_ + size;
+    if (__builtin_expect(end <= cur_range_.end, 1))
+      return WriteBytesUnsafe(src, size);
+    WriteBytesSlowPath(src, size);
+  }
+
+  void WriteBytesSlowPath(const uint8_t* src, size_t size);
+
+  // Reserves a fixed amount of bytes to be backfilled later. The reserved range
+  // is guaranteed to be contiguous and not span across chunks. |size| has to be
+  // <= than the size of a new buffer returned by the Delegate::GetNewBuffer().
+  ContiguousMemoryRange ReserveBytes(size_t size);
+
+  // Fast (but unsafe) version of the above. The caller must have previously
+  // checked that there are at least |size| contiguous bytes available.
+  // Returns only the start pointer of the reservation.
+  uint8_t* ReserveBytesUnsafe(size_t size) {
+    uint8_t* begin = write_ptr_;
+    write_ptr_ += size;
+    assert(write_ptr_ <= cur_range_.end);
+    return begin;
+  }
+
+  // Resets the buffer boundaries and the write pointer to the given |range|.
+  // Subsequent WriteByte(s) will write into |range|.
+  void Reset(ContiguousMemoryRange range);
+
+  // Number of contiguous free bytes in |cur_range_| that can be written without
+  // requesting a new buffer.
+  size_t bytes_available() const {
+    return static_cast<size_t>(cur_range_.end - write_ptr_);
+  }
+
+  uint8_t* write_ptr() const { return write_ptr_; }
+
+ private:
+  ScatteredStreamWriter(const ScatteredStreamWriter&) = delete;
+  ScatteredStreamWriter& operator=(const ScatteredStreamWriter&) = delete;
+
+  void Extend();
+
+  Delegate* const delegate_;
+  ContiguousMemoryRange cur_range_;
+  uint8_t* write_ptr_;
+};
+
+}  // namespace protozero
+
+#endif  // PROTOZERO_INCLUDE_PROTOZERO_SCATTERED_STREAM_WRITER_H_
diff --git a/protozero/src/proto_utils.cc b/protozero/src/proto_utils.cc
new file mode 100644
index 0000000..5bdaebc
--- /dev/null
+++ b/protozero/src/proto_utils.cc
@@ -0,0 +1,107 @@
+/*
+ * 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 "protozero/proto_utils.h"
+
+#include <string.h>
+
+#include <limits>
+
+#include "cpp_common/base.h"
+
+#define CHECK_PTR_LE(a, b) \
+  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 {
+    CHECK_PTR_LE(pos, end - 1);
+    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;
+  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;
+
+  DCHECK(raw_field_id <= std::numeric_limits<uint32_t>::max());
+  *field_id = static_cast<uint32_t>(raw_field_id);
+
+  switch (*field_type) {
+    case kFieldTypeFixed64: {
+      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: {
+      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;
+      CHECK_PTR_LE(pos, end);
+      break;
+    }
+  }
+  return pos;
+}
+
+}  // namespace proto_utils
+}  // namespace protozero
diff --git a/protozero/src/proto_utils_unittest.cc b/protozero/src/proto_utils_unittest.cc
new file mode 100644
index 0000000..45f03ca
--- /dev/null
+++ b/protozero/src/proto_utils_unittest.cc
@@ -0,0 +1,203 @@
+/*
+ * 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 "protozero/proto_utils.h"
+
+#include <limits>
+
+#include "cpp_common/base.h"
+#include "gtest/gtest.h"
+
+namespace protozero {
+namespace proto_utils {
+namespace {
+
+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/protozero/src/scattered_stream_writer.cc b/protozero/src/scattered_stream_writer.cc
new file mode 100644
index 0000000..c0dfaec
--- /dev/null
+++ b/protozero/src/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 "protozero/scattered_stream_writer.h"
+
+#include <algorithm>
+
+#include "cpp_common/base.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;
+  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();
+    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/protozero/src/scattered_stream_writer_unittest.cc b/protozero/src/scattered_stream_writer_unittest.cc
new file mode 100644
index 0000000..f47834c
--- /dev/null
+++ b/protozero/src/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 "protozero/scattered_stream_writer.h"
+
+#include <string.h>
+
+#include <memory>
+
+#include "cpp_common/base.h"
+#include "gtest/gtest.h"
+#include "protozero/src/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/protozero/src/test/fake_scattered_buffer.cc b/protozero/src/test/fake_scattered_buffer.cc
new file mode 100644
index 0000000..7dadb16
--- /dev/null
+++ b/protozero/src/test/fake_scattered_buffer.cc
@@ -0,0 +1,72 @@
+/*
+ * 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 "protozero/src/test/fake_scattered_buffer.h"
+
+#include <sstream>
+
+#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/protozero/src/test/fake_scattered_buffer.h b/protozero/src/test/fake_scattered_buffer.h
new file mode 100644
index 0000000..8a84528
--- /dev/null
+++ b/protozero/src/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 PROTOZERO_SRC_TEST_FAKE_SCATTERED_BUFFER_H_
+#define PROTOZERO_SRC_TEST_FAKE_SCATTERED_BUFFER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "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  // PROTOZERO_SRC_TEST_FAKE_SCATTERED_BUFFER_H_