aboutsummaryrefslogtreecommitdiff
path: root/include/perfetto/protozero/message.h
blob: b53dcda3cef625b9e50f3c65fde9fbc721ecb4ee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
/*
 * 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 INCLUDE_PERFETTO_PROTOZERO_MESSAGE_H_
#define INCLUDE_PERFETTO_PROTOZERO_MESSAGE_H_

#include <assert.h>
#include <stdint.h>
#include <string.h>

#include <string>
#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 {
 public:
  friend class MessageHandleBase;

  // 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_; }

#if PERFETTO_DCHECK_IS_ON()
  void set_handle(MessageHandleBase* handle) { handle_ = handle; }
#endif

  // Proto types: uint64, uint32, int64, int32, bool, enum.
  template <typename T>
  void AppendVarInt(uint32_t field_id, T value) {
    if (nested_message_)
      EndNestedMessage();

    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_)
      EndNestedMessage();

    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_)
      EndNestedMessage();

    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 http://crbug.com/624311 .
    WriteToStream(buffer, pos);
  }

  void AppendString(uint32_t field_id, const char* str);

  void AppendString(uint32_t field_id, const std::string& str) {
    AppendBytes(field_id, str.data(), str.size());
  }

  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;
  }

  ScatteredStreamWriter* stream_writer_for_testing() { return stream_writer_; }

  // Appends some raw bytes to the message. The use-case for this is preserving
  // unknown fields in the decode -> re-encode path of xxx.gen.cc classes
  // generated by the cppgen_plugin.cc.
  // The caller needs to guarantee that the appended data is properly
  // proto-encoded and each field has a proto preamble.
  void AppendRawProtoBytes(const void* data, size_t size) {
    const uint8_t* src = reinterpret_cast<const uint8_t*>(data);
    WriteToStream(src, src + size);
  }

 private:
  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(!finalized_);
    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 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_;

#if PERFETTO_DCHECK_IS_ON()
  // Current generation of message. Incremented on Reset.
  // Used to detect stale handles.
  uint32_t generation_;

  MessageHandleBase* handle_;
#endif

  // 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

#endif  // INCLUDE_PERFETTO_PROTOZERO_MESSAGE_H_