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_