blob: 06ef53954fad1541ef6eea52ab65f5a4816f12f9 [file] [log] [blame]
/*
* Copyright (C) 2021 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 SRC_PROTOZERO_PROTO_RING_BUFFER_H_
#define SRC_PROTOZERO_PROTO_RING_BUFFER_H_
#include <stdint.h>
#include "perfetto/ext/base/paged_memory.h"
namespace protozero {
// This class buffers and tokenizes proto messages.
//
// From a logical level, it works with a sequence of protos like this.
// [ header 1 ] [ payload 1 ]
// [ header 2 ] [ payload 2 ]
// [ header 3 ] [ payload 3 ]
// Where [ header ] is a variable-length sequence of:
// [ Field ID = 1, type = length-delimited] [ length (varint) ].
//
// The input to this class is byte-oriented, not message-oriented (like a TCP
// stream or a pipe). The caller is not required to respect the boundaries of
// each message; only guarantee that data is not lost or duplicated. The
// following sequence of inbound events is possible:
// 1. [ hdr 1 (incomplete) ... ]
// 2. [ ... hdr 1 ] [ payload 1 ] [ hdr 2 ] [ payoad 2 ] [ hdr 3 ] [ pay... ]
// 3. [ ...load 3 ]
//
// This class maintains inbound requests in a ring buffer.
// The expected usage is:
// ring_buf.Append(data, len);
// for (;;) {
// auto msg = ring_buf.ReadMessage();
// if (!msg.valid())
// break;
// Decode(msg);
// }
//
// After each call to Append, the caller is expected to call ReadMessage() until
// it returns an invalid message (signalling no more messages could be decoded).
// Note that a single Append can "unblock" > 1 messages, which is why the caller
// needs to keep calling ReadMessage in a loop.
//
// Internal architecture
// ---------------------
// Internally this is similar to a ring-buffer, with the caveat that it never
// wraps, it only expands. Expansions are rare. The deal is that in most cases
// the read cursor follows very closely the write cursor. For instance, if the
// uderlying behaves as a dgram socket, after each Append, the read cursor will
// chase completely the write cursor. Even if the underyling stream is not
// always atomic, the expectation is that the read cursor will eventually reach
// the write one within few messages.
// A visual example, imagine we have four messages: 2it 4will 2be 4fine
// Visually:
//
// Append("2it4wi"): A message and a bit:
// [ 2it 4wi ]
// ^R ^W
//
// After the ReadMessage(), the 1st message will be read, but not the 2nd.
// [ 2it 4wi ]
// ^R ^W
//
// Append("ll2be4f")
// [ 2it 4will 2be 4f ]
// ^R ^W
//
// After the ReadMessage() loop:
// [ 2it 4will 2be 4f ]
// ^R ^W
// Append("ine")
// [ 2it 4will 2be 4fine ]
// ^R ^W
//
// In the next ReadMessage() the R cursor will chase the W cursor. When this
// happens (very frequent) we can just reset both cursors to 0 and restart.
// If we are unlucky and get to the end of the buffer, two things happen:
// 1. We try first to recompact the buffer, moving everything left by R.
// 2. If still there isn't enough space, we expand the buffer.
// Given that each message is expected to be at most kMaxMsgSize (64 MB), the
// expansion is bound at 2 * kMaxMsgSize.
class ProtoRingBuffer {
public:
static constexpr size_t kMaxMsgSize = 64 * 1024 * 1024;
struct Message {
const uint8_t* start = nullptr;
uint32_t len = 0;
uint32_t field_id = 0;
bool fatal_framing_error = false;
const uint8_t* end() const { return start + len; }
inline bool valid() const { return !!start; }
};
ProtoRingBuffer();
~ProtoRingBuffer();
ProtoRingBuffer(const ProtoRingBuffer&) = delete;
ProtoRingBuffer& operator=(const ProtoRingBuffer&) = delete;
// Appends data into the ring buffer, recompacting or resizing it if needed.
// Will invaildate the pointers previously handed out.
void Append(const void* data, size_t len);
// If a protobuf message can be read, it returns the boundaries of the message
// (without including the preamble) and advances the read cursor.
// If no message is avaiable, returns a null range.
// The returned pointer is only valid until the next call to Append(), as
// that can recompact or resize the underlying buffer.
Message ReadMessage();
// Exposed for testing.
size_t capacity() const { return buf_.size(); }
size_t avail() const { return buf_.size() - (wr_ - rd_); }
private:
perfetto::base::PagedMemory buf_;
Message fastpath_{};
bool failed_ = false; // Set in case of an unrecoverable framing faiulre.
size_t rd_ = 0; // Offset of the read cursor in |buf_|.
size_t wr_ = 0; // Offset of the write cursor in |buf_|.
};
} // namespace protozero
#endif // SRC_PROTOZERO_PROTO_RING_BUFFER_H_