blob: 3957015a28c75673c3a4392c104ad3f5d19f9468 [file] [log] [blame]
/*
* Copyright (C) 2016 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 NVRAM_MESSAGES_IO_H_
#define NVRAM_MESSAGES_IO_H_
extern "C" {
#include <stddef.h>
#include <stdint.h>
}
#include <nvram/messages/blob.h>
#include <nvram/messages/compiler.h>
namespace nvram {
// Abstraction used by the protobuf decoder to read data. The idea here is that
// |InputStreamBuffer| maintains a window of the data to be read. Access to the
// contents of the current window is direct, i.e. doesn't need to go through
// virtual dispatch to subclasses. Whenever the window is exhausted, the next
// window must be set up. This latter operation is left for implementation of
// the virtual |Advance()| member function in subclasses, which is entirely free
// to pull its data from anywhere.
class NVRAM_EXPORT InputStreamBuffer {
public:
InputStreamBuffer() = default;
InputStreamBuffer(const void* data, size_t size);
InputStreamBuffer(const void* start, const void* end);
virtual ~InputStreamBuffer() = default;
// Checks whether the stream is exhausted;
bool Done();
// Consume |size| bytes from the stream and store them in the provided |data|
// buffer. Returns false if insufficient bytes are available.
bool Read(void* data, size_t size);
// Consume a single byte and place it in |byte|. Returns true if successful,
// i.e. if there was a byte available.
bool ReadByte(uint8_t* byte);
// Discard |size| bytes from the stream. Returns false if there are fewer
// bytes available.
bool Skip(size_t size);
protected:
// Update the |pos_| and |end_| pointers for the next buffer window. Returns
// true if the window was successfully set up, false on I/O errors or stream
// exhaustion. The default implementation just returns false to signal
// immediate stream exhaustion. Subclasses should override this to pull in
// more data from the underlying data source.
virtual bool Advance();
// Pointers to the buffer to read from. |InputStreamBuffer| only advances
// |pos_| until reaching |end_|. At this point, |Advance| is called for the
// subclass to initialize the next buffer window and update the pointers.
const uint8_t* pos_ = nullptr;
const uint8_t* end_ = nullptr;
// Allow |NestedInputStreamBuffer| to mess with |pos_| and |end_|, also in its
// delegate, which isn't necessarily of type |NestedInputStreamBuffer|.
friend class NestedInputStreamBuffer;
};
// An |InputStreamBuffer| implementation that pulls its data from a delegate,
// but only up to a predetermined limit of bytes.
class NVRAM_EXPORT NestedInputStreamBuffer : public InputStreamBuffer {
public:
// Initialize a |NestedInputStreamBuffer| to provide at most |size| bytes from
// |delegate|. Note that |delegate| must remain valid throughout the life time
// of this |NestedInputStreamBuffer|.
NestedInputStreamBuffer(InputStreamBuffer* delegate, size_t size);
~NestedInputStreamBuffer() override = default;
private:
// InputStreamBuffer:
bool Advance() override;
// Determine the input window end based on |delegate|'s current window and the
// requested |size| to read. If |size| can be satisfied from |delegate|'s
// current window, return an end pointer within that window. If size is larger
// than the remaining bytes available in |delegate|'s input window, return
// |delegate|'s |end_| pointer.
static const uint8_t* ClampEnd(InputStreamBuffer* delegate, size_t size);
InputStreamBuffer* delegate_;
size_t remaining_;
};
// Abstraction used by the protobuf decoder to output data. This class maintains
// a current window of memory to write output to. Access to the current window's
// bytes is direct and doesn't require virtual dispatch. Once the capacity of
// the current window is exhausted, the virtual |Advance()| member function is
// invoked to set up a new window. Subclasses are entirely free to implement
// this operation as appropriate for their I/O mechanism, for example a
// socket-based implementations might flush the buffer to the socket and reset
// the window pointers to accept more output.
class NVRAM_EXPORT OutputStreamBuffer {
public:
OutputStreamBuffer() = default;
OutputStreamBuffer(void* data, size_t size);
OutputStreamBuffer(void* data, void* end);
virtual ~OutputStreamBuffer() = default;
// Checks whether the stream is exhausted.
bool Done();
// Writes a blob of |size| bytes provided in |data| to the underlying buffer.
// Returns false if there is not enough space available.
bool Write(const void* data, size_t size);
// Writes |byte| to the underlying buffer. Returns false if there is not
// enough space available.
bool WriteByte(uint8_t byte);
protected:
// Set up the next data buffer window in |pos_| and |end_|. Returns true on
// success, false on I/O errors or stream exhaustion. The default
// implementation unconditionally returns false, i.e. signaling stream
// exhaustion after the initial window is filled. Subclasses should override
// this to flush buffers to the underlying data sink and set up a fresh buffer
// for more data as appropriate.
virtual bool Advance();
// The |pos_| and |end_| pointers define a window of writable buffer space for
// |OutputStreamBuffer| to place data in. |pos_| grows towards |end_| as
// writes occur. Once |pos_| hits |end_|, |OutputStreamBuffer| will call
// |Advance|, which subclasses can implement to provide a new buffer window in
// |pos_| and |end_|.
uint8_t* pos_ = nullptr;
uint8_t* end_ = nullptr;
};
// An |OutputStreamBuffer| backed by a single data buffer.
class NVRAM_EXPORT ArrayOutputStreamBuffer : public OutputStreamBuffer {
public:
ArrayOutputStreamBuffer() = default;
ArrayOutputStreamBuffer(void* data, size_t size)
: OutputStreamBuffer(data, size), data_(pos_) {}
ArrayOutputStreamBuffer(void* data, void* end)
: OutputStreamBuffer(data, end), data_(pos_) {}
~ArrayOutputStreamBuffer() override = default;
// Returns the number of bytes already written.
size_t bytes_written() const { return pos_ - data_; }
private:
uint8_t* data_ = nullptr;
};
// An |OutputStream| implementation that doesn't write anything, but just counts
// the number of bytes written.
class NVRAM_EXPORT CountingOutputStreamBuffer : public OutputStreamBuffer {
public:
CountingOutputStreamBuffer();
~CountingOutputStreamBuffer() override = default;
size_t bytes_written() const {
return bytes_written_ + (pos_ - scratch_space_);
}
protected:
// OutputStreamBuffer:
bool Advance() override;
private:
// We share a single scratch buffer that all |CountingOutputStreamBuffer|
// instances use as the destination for writes. Its contents are pretty much
// unpredictable.
//
// TODO(mnissler): This adds a static 256 bytes memory allocation to each
// process linking to this code. If that becomes a problem, we might want to
// be smarter here and dynamically allocate a chunk of memory only when it's
// needed, or maybe even map some address space that's not even backed by
// actual memory (not sure that's possible).
static constexpr size_t kScratchSpaceSize = 256;
static uint8_t scratch_space_[kScratchSpaceSize];
// Number of bytes that had been written when the last |Advance()| call
// occurred.
size_t bytes_written_ = 0;
};
// An |OutputStreamBuffer| implementation that stores all data to a wrapped
// |Blob|, growing it as necessary.
class NVRAM_EXPORT BlobOutputStreamBuffer : public OutputStreamBuffer {
public:
// Construct a |BlobOutputStreamBuffer| that stores all written data to
// |blob|, which will get resized as necessary. Note that |blob| must remain
// valid for the life time of the |BlobOutputStreamBuffer| object.
explicit BlobOutputStreamBuffer(Blob* blob);
~BlobOutputStreamBuffer() override = default;
// Truncate the blob to match the current output size.
bool Truncate();
protected:
// OutputStreamBuffer:
bool Advance() override;
private:
Blob* blob_;
};
// Protobuf wire types.
enum class WireType : int8_t {
kVarint = 0,
kFixed64 = 1,
kLengthDelimited = 2,
kStartGroup = 3,
kEndGroup = 4,
kFixed32 = 5,
};
// A class implementing a parser for the low-level protobuf wire format. It
// obtains raw data from a wrapped |InputStream| and offers member functions
// that facilitate decoding the data according to the protobuf wire format.
class NVRAM_EXPORT ProtoReader {
public:
// Construct a new |ProtoReader| that consumes data from |stream_buffer|.
// |stream_buffer| must remain valid throughout the life time of the
// |ProtoReader|.
explicit ProtoReader(InputStreamBuffer* stream_buffer);
// Access to the underlying stream buffer.
InputStreamBuffer* stream_buffer() { return stream_buffer_; }
// Wire type of the current field.
WireType wire_type() const { return static_cast<WireType>(wire_type_); }
// Field number of the current field.
uint64_t field_number() const { return field_number_; }
// Size of the field data, if known in advance.
size_t field_size() const { return field_size_; }
// Whether all data is consumed.
bool Done() const { return stream_buffer_->Done(); }
// Reads the next wire tag from the current position in the underlying
// |stream_buffer_| and initializes internal fields. Previous state is
// discarded silently.
bool ReadWireTag();
// Read a varint-encoded field and advances to the next field. Returns true if
// successful.
bool ReadVarint(uint64_t* value);
// Read field data. Checks that |size| matches |field_size()| and copies out
// the data to the provided |data| buffer. Advances to the next field and
// returns true if successful.
bool ReadLengthDelimited(void* data, size_t size);
// Skips over the current field data.
bool SkipField();
private:
static constexpr int8_t kInvalidWireType = -1;
InputStreamBuffer* stream_buffer_;
// Information about the current field. |wire_type == kInvalidWireType|
// indicates that there is no current field to be consumed.
int8_t wire_type_ = kInvalidWireType;
uint64_t field_number_ = 0;
size_t field_size_ = 0;
};
// |ProtoWriter| contains logic to write raw data according to the protobuf wire
// format to an |OutputStreamBuffer|.
class NVRAM_EXPORT ProtoWriter {
public:
// Construct a |ProtoWriter| which will send its output to |stream_buffer|.
// |stream_buffer| must remain valid for the life time of the |ProtoWriter|.
explicit ProtoWriter(OutputStreamBuffer* stream_buffer);
// Access to the underlying stream buffer.
OutputStreamBuffer* stream_buffer() { return stream_buffer_; }
// Sets the field number to use when emitting a tag.
void set_field_number(uint64_t field_number) { field_number_ = field_number; }
// Whether the writer has exhausted the underlying |OutputStream|'s capacity.
bool Done() const { return stream_buffer_->Done(); }
// Write |value| as a varint-encoded field. Returns true if successful, i.e.
// the data was successfully written to |stream_buffer_|.
bool WriteVarint(uint64_t value);
// Write |size| bytes stored at |data| to |stream_buffer_|. Returns true if
// successful, i.e. the data was successfully written to |stream_buffer_|.
bool WriteLengthDelimited(const void* data, size_t size);
// Writes a wire tag for a length-delimited field, followed by a length
// indication for |size| data bytes. It is up to the caller to emit exactly
// |size| bytes to |stream_buffer()|, otherwise the encoded data will be
// malformed.
bool WriteLengthHeader(size_t size);
private:
// A helper to write a wire tag using the current field number and the
// provided wire type.
bool WriteWireTag(WireType wire_type);
OutputStreamBuffer* stream_buffer_;
uint64_t field_number_ = 0;
};
} // namespace nvram
#endif // NVRAM_MESSAGES_IO_H_