blob: bc3baf12330f36b7d7c6cb810bb6c6c9a0703c0a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* https://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 avro_Buffer_hh__
#define avro_Buffer_hh__
#ifndef _WIN32
#include <sys/uio.h>
#endif
#include <utility>
#include <vector>
#include "../Config.hh"
#include "detail/BufferDetail.hh"
#include "detail/BufferDetailIterator.hh"
/**
* \file Buffer.hh
*
* \brief Definitions for InputBuffer and OutputBuffer classes
*
**/
namespace avro {
class OutputBuffer;
class InputBuffer;
/**
* The OutputBuffer (write-only buffer)
*
* Use cases for OutputBuffer
*
* - write message to buffer using ostream class or directly
* - append messages to headers
* - building up streams of messages via append
* - converting to read-only buffers for sending
* - extracting parts of the messages into read-only buffers
*
* -# ASIO access:
* - write to a buffer(s) by asio using iterator
* - convert to read buffer for deserializing
*
* OutputBuffer is assignable and copy-constructable. On copy or assignment,
* only a pointer is copied, so the two resulting copies are identical, so
* modifying one will modify both.
**/
class AVRO_DECL OutputBuffer {
public:
typedef detail::size_type size_type;
typedef detail::data_type data_type;
/**
* The asio library expects a const_iterator (the const-ness refers to the
* fact that the underlying avro of buffers will not be modified, even
* though the data in those buffers is being modified). The iterator
* provides the list of addresses an operation can write to.
**/
typedef detail::OutputBufferIterator const_iterator;
/**
* Default constructor. Will pre-allocate at least the requested size, but
* can grow larger on demand.
*
* Destructor uses the default, which resets a shared pointer, deleting the
* underlying data if no other copies of exist.
*
* Copy and assignment operators are not explicitly provided because the
* default ones work fine. The default makes only a shallow copy, so the
* copies will refer to the same memory. This is required by asio
* functions, which will implicitly make copies for asynchronous
* operations. Therefore, the user must be careful that if they create
* multiple copies of the same OutputBuffer, only one is being modified
* otherwise undefined behavior may occur.
*
**/
explicit OutputBuffer(size_type reserveSize = 0) : pimpl_(new detail::BufferImpl) {
if (reserveSize) {
reserve(reserveSize);
}
}
/**
* Reserve enough space for a wroteTo() operation. When using writeTo(),
* the buffer will grow dynamically as needed. But when using the iterator
* to write (followed by wroteTo()), data may only be written to the space
* available, so this ensures there is enough room in the buffer before
* the write operation.
**/
void reserve(size_type reserveSize) {
pimpl_->reserveFreeSpace(reserveSize);
}
/**
* Write a block of data to the buffer. The buffer size will automatically
* grow if the size is larger than what is currently free.
**/
size_type writeTo(const data_type *data, size_type size) {
return pimpl_->writeTo(data, size);
}
/**
* Write a single value to the buffer. The buffer size will automatically
* grow if there is not room for the byte. The value must be a
* "fundamental" type, e.g. int, float, etc. (otherwise use the other
* writeTo tests).
**/
template<typename T>
void writeTo(T val) {
pimpl_->writeTo(val, std::is_fundamental<T>());
}
/**
* Update the state of the buffer after writing through the iterator
* interface. This function exists primarily for the boost:asio which
* writes directly to the buffer using its iterator. In this case, the
* internal state of the buffer does not reflect that the data was written
* This informs the buffer how much data was written.
*
* The buffer does not automatically resize in this case, the bytes written
* cannot exceed the amount of free space. Attempting to write more will
* throw a std::length_error exception.
**/
size_type wroteTo(size_type size) {
int wrote = 0;
if (size) {
if (size > freeSpace()) {
throw std::length_error("Impossible to write more data than free space");
}
wrote = pimpl_->wroteTo(size);
}
return wrote;
}
/**
* Does the buffer have any data?
**/
bool empty() const {
return (pimpl_->size() == 0);
}
/**
* Returns the size of the buffer, in bytes.
*/
size_type size() const {
return pimpl_->size();
}
/**
* Returns the current free space that is available to write to in the
* buffer, in bytes. This is not a strict limit in size, as writeTo() can
* automatically increase capacity if necessary.
**/
size_type freeSpace() const {
return pimpl_->freeSpace();
}
/**
* Appends the data in the argument to the end of this buffer. The
* argument can be either an InputBuffer or OutputBuffer.
*
**/
template<class BufferType>
void append(const BufferType &buf) {
// don't append an empty buffer
if (buf.size()) {
pimpl_->append(*(buf.pimpl_.get()));
}
}
/**
* Return an iterator pointing to the first data chunk of this buffer
* that may be written to.
**/
const_iterator begin() const {
return const_iterator(pimpl_->beginWrite());
}
/**
* Return the end iterator for writing.
**/
const_iterator end() const {
return const_iterator(pimpl_->endWrite());
}
/**
* Discard any data in this buffer.
**/
void discardData() {
pimpl_->discardData();
}
/**
* Discard the specified number of bytes from this data, starting at the beginning.
* Throws if the size is greater than the number of bytes.
**/
void discardData(size_t bytes) {
if (bytes > 0) {
if (bytes < pimpl_->size()) {
pimpl_->discardData(bytes);
} else if (bytes == pimpl_->size()) {
pimpl_->discardData();
} else {
throw std::out_of_range("trying to discard more data than exists");
}
}
}
/**
* Remove bytes from this buffer, starting from the beginning, and place
* them into a new buffer. Throws if the number of requested bytes exceeds
* the size of the buffer. Data and freeSpace in the buffer after bytes
* remains in this buffer.
**/
InputBuffer extractData(size_type bytes);
/**
* Remove all bytes from this buffer, returning them in a new buffer.
* After removing data, some freeSpace may remain in this buffer.
**/
InputBuffer extractData();
/**
* Clone this buffer, creating a copy that contains the same data.
**/
OutputBuffer clone() const {
detail::BufferImpl::SharedPtr newImpl(new detail::BufferImpl(*pimpl_));
return OutputBuffer(newImpl);
}
/**
* Add unmanaged data to the buffer. The buffer will not automatically
* free the data, but it will call the supplied function when the data is
* no longer referenced by the buffer (or copies of the buffer).
**/
void appendForeignData(const data_type *data, size_type size, const detail::free_func &func) {
pimpl_->appendForeignData(data, size, func);
}
/**
* Returns the number of chunks that contain free space.
**/
int numChunks() const {
return pimpl_->numFreeChunks();
}
/**
* Returns the number of chunks that contain data
**/
int numDataChunks() const {
return pimpl_->numDataChunks();
}
private:
friend class InputBuffer;
friend class BufferReader;
explicit OutputBuffer(detail::BufferImpl::SharedPtr pimpl) : pimpl_(std::move(pimpl)) {}
detail::BufferImpl::SharedPtr pimpl_; ///< Must never be null.
};
/**
* The InputBuffer (read-only buffer)
*
* InputBuffer is an immutable buffer which that may be constructed from an
* OutputBuffer, or several of OutputBuffer's methods. Once the data is
* transfered to an InputBuffer it cannot be modified, only read (via
* BufferReader, istream, or its iterator).
*
* Assignments and copies are shallow copies.
*
* -# ASIO access: - iterate using const_iterator for sending messages
*
**/
class AVRO_DECL InputBuffer {
public:
typedef detail::size_type size_type;
typedef detail::data_type data_type;
// needed for asio
typedef detail::InputBufferIterator const_iterator;
/**
* Default InputBuffer creates an empty buffer.
*
* Copy/assignment functions use the default ones. They will do a shallow
* copy, and because InputBuffer is immutable, the copies will be
* identical.
*
* Destructor also uses the default, which resets a shared pointer,
* deleting the underlying data if no other copies of exist.
**/
InputBuffer() : pimpl_(new detail::BufferImpl) {}
/**
* Construct an InputBuffer that contains the contents of an OutputBuffer.
* The two buffers will have the same contents, but this copy will be
* immutable, while the the OutputBuffer may still be written to.
*
* If you wish to move the data from the OutputBuffer to a new InputBuffer
* (leaving only free space in the OutputBuffer),
* OutputBuffer::extractData() will do this more efficiently.
*
* Implicit conversion is allowed.
**/
// NOLINTNEXTLINE(google-explicit-constructor)
InputBuffer(const OutputBuffer &src) : pimpl_(new detail::BufferImpl(*src.pimpl_)) {}
/**
* Does the buffer have any data?
**/
bool empty() const {
return (pimpl_->size() == 0);
}
/**
* Returns the size of the buffer, in bytes.
**/
size_type size() const {
return pimpl_->size();
}
/**
* Return an iterator pointing to the first data chunk of this buffer
* that contains data.
**/
const_iterator begin() const {
return const_iterator(pimpl_->beginRead());
}
/**
* Return the end iterator.
**/
const_iterator end() const {
return const_iterator(pimpl_->endRead());
}
/**
* Returns the number of chunks containing data.
**/
int numChunks() const {
return pimpl_->numDataChunks();
}
private:
friend class OutputBuffer; // for append function
friend class istreambuf;
friend class BufferReader;
explicit InputBuffer(const detail::BufferImpl::SharedPtr &pimpl) : pimpl_(pimpl) {}
/**
* Class to indicate that a copy of a OutputBuffer to InputBuffer should be
* a shallow copy, used to enable reading of the contents of an
* OutputBuffer without need to convert it to InputBuffer using a deep
* copy. It is private and only used by BufferReader and istreambuf
* classes.
*
* Writing to an OutputBuffer while it is being read may lead to undefined
* behavior.
**/
class ShallowCopy {};
/**
* Make a shallow copy of an OutputBuffer in order to read it without
* causing conversion overhead.
**/
InputBuffer(const OutputBuffer &src, const ShallowCopy &) : pimpl_(src.pimpl_) {}
/**
* Make a shallow copy of an InputBuffer. The default copy constructor
* already provides shallow copy, this is just provided for generic
* algorithms that wish to treat InputBuffer and OutputBuffer in the same
* manner.
**/
InputBuffer(const InputBuffer &src, const ShallowCopy &) : pimpl_(src.pimpl_) {}
detail::BufferImpl::ConstSharedPtr pimpl_; ///< Must never be null.
};
/*
* Implementations of some OutputBuffer functions are inlined here
* because InputBuffer definition was required before.
*/
inline InputBuffer OutputBuffer::extractData() {
detail::BufferImpl::SharedPtr newImpl(new detail::BufferImpl);
if (pimpl_->size()) {
pimpl_->extractData(*newImpl);
}
return InputBuffer(newImpl);
}
inline InputBuffer OutputBuffer::extractData(size_type bytes) {
if (bytes > pimpl_->size()) {
throw std::out_of_range("trying to extract more data than exists");
}
detail::BufferImpl::SharedPtr newImpl(new detail::BufferImpl);
if (bytes > 0) {
if (bytes < pimpl_->size()) {
pimpl_->extractData(*newImpl, bytes);
} else {
pimpl_->extractData(*newImpl);
}
}
return InputBuffer(newImpl);
}
#ifndef _WIN32
/**
* Create an array of iovec structures from the buffer. This utility is used
* to support writev and readv function calls. The caller should ensure the
* buffer object is not deleted while using the iovec vector.
*
* If the BufferType is an InputBuffer, the iovec will point to the data that
* already exists in the buffer, for reading.
*
* If the BufferType is an OutputBuffer, the iovec will point to the free
* space, which may be written to. Before writing, the caller should call
* OutputBuffer::reserve() to create enough room for the desired write (which
* can be verified by calling OutputBuffer::freeSpace()), and after writing,
* they MUST call OutputBuffer::wroteTo(), otherwise the buffer will not know
* the space is not free anymore.
*
**/
template<class BufferType>
inline void toIovec(BufferType &buf, std::vector<struct iovec> &iov) {
const int chunks = buf.numChunks();
iov.resize(chunks);
typename BufferType::const_iterator iter = buf.begin();
for (int i = 0; i < chunks; ++i) {
iov[i].iov_base = const_cast<typename BufferType::data_type *>(iter->data());
iov[i].iov_len = iter->size();
++iter;
}
}
#endif
} // namespace avro
#endif