blob: 73286633b868eab92de066e922b4f2c73334c098 [file] [log] [blame]
* 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
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* See the License for the specific language governing permissions and
* limitations under the License.
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <type_traits>
#include "perfetto/base/export.h"
#include "perfetto/base/logging.h"
#include "perfetto/protozero/contiguous_memory_range.h"
#include "perfetto/protozero/proto_utils.h"
#include "perfetto/protozero/scattered_stream_writer.h"
namespace perfetto {
namespace shm_fuzz {
class FakeProducer;
} // namespace shm_fuzz
} // namespace perfetto
namespace protozero {
class MessageHandleBase;
// Base class extended by the proto C++ stubs generated by the ProtoZero
// compiler. This class provides the minimal runtime required to support
// append-only operations and is designed for performance. None of the methods
// require any dynamic memory allocation.
class PERFETTO_EXPORT Message {
friend class MessageHandleBase;
// Grant end_to_end_shared_memory_fuzzer access in order to write raw
// bytes into the buffer.
friend class ::perfetto::shm_fuzz::FakeProducer;
// Adjust the |nested_messages_arena_| size when changing this, or the
// static_assert in the .cc file will bark.
static constexpr uint32_t kMaxNestingDepth = 10;
// Ctor and Dtor of Message are never called, with the exeception
// of root (non-nested) messages. Nested messages are allocated via placement
// new in the |nested_messages_arena_| and implictly destroyed when the arena
// of the root message goes away. This is fine as long as all the fields are
// PODs, which is checked by the static_assert in the ctor (see the Reset()
// method in the .cc file).
Message() = default;
// Clears up the state, allowing the message to be reused as a fresh one.
void Reset(ScatteredStreamWriter*);
// Commits all the changes to the buffer (backfills the size field of this and
// all nested messages) and seals the message. Returns the size of the message
// (and all nested sub-messages), without taking into account any chunking.
// Finalize is idempotent and can be called several times w/o side effects.
uint32_t Finalize();
// Optional. If is_valid() == true, the corresponding memory region (its
// length == proto_utils::kMessageLengthFieldSize) is backfilled with the size
// of this message (minus |size_already_written| below). This is the mechanism
// used by messages to backfill their corresponding size field in the parent
// message.
uint8_t* size_field() const { return size_field_; }
void set_size_field(uint8_t* size_field) { size_field_ = size_field; }
// This is to deal with case of backfilling the size of a root (non-nested)
// message which is split into multiple chunks. Upon finalization only the
// partial size that lies in the last chunk has to be backfilled.
void inc_size_already_written(uint32_t sz) { size_already_written_ += sz; }
Message* nested_message() { return nested_message_; }
bool is_finalized() const { return finalized_; }
void set_handle(MessageHandleBase* handle) { handle_ = handle; }
// Proto types: uint64, uint32, int64, int32, bool, enum.
template <typename T>
void AppendVarInt(uint32_t field_id, T value) {
if (nested_message_)
uint8_t buffer[proto_utils::kMaxSimpleFieldEncodedSize];
uint8_t* pos = buffer;
pos = proto_utils::WriteVarInt(proto_utils::MakeTagVarInt(field_id), pos);
// WriteVarInt encodes signed values in two's complement form.
pos = proto_utils::WriteVarInt(value, pos);
WriteToStream(buffer, pos);
// Proto types: sint64, sint32.
template <typename T>
void AppendSignedVarInt(uint32_t field_id, T value) {
AppendVarInt(field_id, proto_utils::ZigZagEncode(value));
// Proto types: bool, enum (small).
// Faster version of AppendVarInt for tiny numbers.
void AppendTinyVarInt(uint32_t field_id, int32_t value) {
PERFETTO_DCHECK(0 <= value && value < 0x80);
if (nested_message_)
uint8_t buffer[proto_utils::kMaxSimpleFieldEncodedSize];
uint8_t* pos = buffer;
// MakeTagVarInt gets super optimized here for constexpr.
pos = proto_utils::WriteVarInt(proto_utils::MakeTagVarInt(field_id), pos);
*pos++ = static_cast<uint8_t>(value);
WriteToStream(buffer, pos);
// Proto types: fixed64, sfixed64, fixed32, sfixed32, double, float.
template <typename T>
void AppendFixed(uint32_t field_id, T value) {
if (nested_message_)
uint8_t buffer[proto_utils::kMaxSimpleFieldEncodedSize];
uint8_t* pos = buffer;
pos = proto_utils::WriteVarInt(proto_utils::MakeTagFixed<T>(field_id), pos);
memcpy(pos, &value, sizeof(T));
pos += sizeof(T);
// TODO: Optimize memcpy performance, see .
WriteToStream(buffer, pos);
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
// in the parent class.
// The template argument T is supposed to be a stub class auto generated from
// a .proto, hence a subclass of Message.
template <class T>
T* BeginNestedMessage(uint32_t field_id) {
// This is to prevent subclasses (which should be autogenerated, though), to
// introduce extra state fields (which wouldn't be initialized by Reset()).
static_assert(std::is_base_of<Message, T>::value,
"T must be a subclass of Message");
static_assert(sizeof(T) == sizeof(Message),
"Message subclasses cannot introduce extra state.");
T* message = reinterpret_cast<T*>(nested_messages_arena_);
BeginNestedMessageInternal(field_id, message);
return message;
Message(const Message&) = delete;
Message& operator=(const Message&) = delete;
void BeginNestedMessageInternal(uint32_t field_id, Message*);
// Called by Finalize and Append* methods.
void EndNestedMessage();
void WriteToStream(const uint8_t* src_begin, const uint8_t* src_end) {
PERFETTO_DCHECK(src_begin <= src_end);
const uint32_t size = static_cast<uint32_t>(src_end - src_begin);
stream_writer_->WriteBytes(src_begin, size);
size_ += size;
// Only POD fields are allowed. This class's dtor is never called.
// See the comment on the static_assert in the the corresponding .cc file.
// The stream writer interface used for the serialization.
ScatteredStreamWriter* stream_writer_;
uint8_t* size_field_;
// Keeps track of the size of the current message.
uint32_t size_;
// See comment for inc_size_already_written().
uint32_t size_already_written_;
// When true, no more changes to the message are allowed. This is to DCHECK
// attempts of writing to a message which has been Finalize()-d.
bool finalized_;
// Used to detect attemps to create messages with a nesting level >
// kMaxNestingDepth. |nesting_depth_| == 0 for root (non-nested) messages.
uint8_t nesting_depth_;
// Current generation of message. Incremented on Reset.
// Used to detect stale handles.
uint32_t generation_;
MessageHandleBase* handle_;
// Pointer to the last child message created through BeginNestedMessage(), if
// any, nullptr otherwise. There is no need to keep track of more than one
// message per nesting level as the proto-zero API contract mandates that
// nested fields can be filled only in a stacked fashion. In other words,
// nested messages are finalized and sealed when any other field is set in the
// parent message (or the parent message itself is finalized) and cannot be
// accessed anymore afterwards.
// TODO(primiano): optimization: I think that nested_message_, when non-null.
// will always be @ (this) + offsetof(nested_messages_arena_).
Message* nested_message_;
// The root message owns the storage for all its nested messages, up to a max
// of kMaxNestingDepth levels (see the .cc file). Note that the boundaries of
// the arena are meaningful only for the root message.
// Unfortunately we cannot put the sizeof() math here because we cannot sizeof
// the current class in a header. However the .cc file has a static_assert
// that guarantees that (see the Reset() method in the .cc file).
alignas(sizeof(void*)) uint8_t nested_messages_arena_[512];
// DO NOT add any fields below |nested_messages_arena_|. The memory layout of
// nested messages would overflow the storage allocated by the root message.
} // namespace protozero