diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a4cb2dfe..b5d2e0903 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.6.1 + +Upgrade protozero to version 1.7.1 + ## 2.6.0 * Add another drop rate guessing options, from the same metrics -zg uses diff --git a/protozero/basic_pbf_builder.hpp b/protozero/basic_pbf_builder.hpp new file mode 100644 index 000000000..0ede726fa --- /dev/null +++ b/protozero/basic_pbf_builder.hpp @@ -0,0 +1,266 @@ +#ifndef PROTOZERO_BASIC_PBF_BUILDER_HPP +#define PROTOZERO_BASIC_PBF_BUILDER_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file basic_pbf_builder.hpp + * + * @brief Contains the basic_pbf_builder template class. + */ + +#include "basic_pbf_writer.hpp" +#include "types.hpp" + +#include + +namespace protozero { + +/** + * The basic_pbf_builder is used to write PBF formatted messages into a buffer. + * It is based on the basic_pbf_writer class and has all the same methods. The + * difference is that while the pbf_writer class takes an integer tag, + * this template class takes a tag of the template type T. The idea is that + * T will be an enumeration value and this helps reduce the possibility of + * programming errors. + * + * Almost all methods in this class can throw an std::bad_alloc exception if + * the underlying buffer class wants to resize. + * + * Read the tutorial to understand how this class is used. In most cases you + * want to use the pbf_builder class which uses a std::string as buffer type. + */ +template +class basic_pbf_builder : public basic_pbf_writer { + + static_assert(std::is_same::type>::value, + "T must be enum with underlying type protozero::pbf_tag_type"); + +public: + + /// The type of messages this class will build. + using enum_type = T; + + basic_pbf_builder() = default; + + /** + * Create a builder using the given string as a data store. The object + * stores a reference to that string and adds all data to it. The string + * doesn't have to be empty. The pbf_message object will just append data. + */ + explicit basic_pbf_builder(TBuffer& data) noexcept : + basic_pbf_writer{data} { + } + + /** + * Construct a pbf_builder for a submessage from the pbf_message or + * pbf_writer of the parent message. + * + * @param parent_writer The parent pbf_message or pbf_writer + * @param tag Tag of the field that will be written + */ + template + basic_pbf_builder(basic_pbf_writer& parent_writer, P tag) noexcept : + basic_pbf_writer{parent_writer, pbf_tag_type(tag)} { + } + +/// @cond INTERNAL +#define PROTOZERO_WRITER_WRAP_ADD_SCALAR(name, type) \ + void add_##name(T tag, type value) { \ + basic_pbf_writer::add_##name(pbf_tag_type(tag), value); \ + } + + PROTOZERO_WRITER_WRAP_ADD_SCALAR(bool, bool) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(enum, int32_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(int32, int32_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(sint32, int32_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(uint32, uint32_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(int64, int64_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(sint64, int64_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(uint64, uint64_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(fixed32, uint32_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(sfixed32, int32_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(fixed64, uint64_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(sfixed64, int64_t) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(float, float) + PROTOZERO_WRITER_WRAP_ADD_SCALAR(double, double) + +#undef PROTOZERO_WRITER_WRAP_ADD_SCALAR +/// @endcond + + /** + * Add "bytes" field to data. + * + * @param tag Tag of the field + * @param value Pointer to value to be written + * @param size Number of bytes to be written + */ + void add_bytes(T tag, const char* value, std::size_t size) { + basic_pbf_writer::add_bytes(pbf_tag_type(tag), value, size); + } + + /** + * Add "bytes" field to data. + * + * @param tag Tag of the field + * @param value Value to be written + */ + void add_bytes(T tag, const data_view& value) { + basic_pbf_writer::add_bytes(pbf_tag_type(tag), value); + } + + /** + * Add "bytes" field to data. + * + * @param tag Tag of the field + * @param value Value to be written + */ + void add_bytes(T tag, const std::string& value) { + basic_pbf_writer::add_bytes(pbf_tag_type(tag), value); + } + + /** + * Add "bytes" field to data. Bytes from the value are written until + * a null byte is encountered. The null byte is not added. + * + * @param tag Tag of the field + * @param value Pointer to zero-delimited value to be written + */ + void add_bytes(T tag, const char* value) { + basic_pbf_writer::add_bytes(pbf_tag_type(tag), value); + } + + /** + * Add "bytes" field to data using vectored input. All the data in the + * 2nd and further arguments is "concatenated" with only a single copy + * into the final buffer. + * + * This will work with objects of any type supporting the data() and + * size() methods like std::string or protozero::data_view. + * + * Example: + * @code + * std::string data1 = "abc"; + * std::string data2 = "xyz"; + * builder.add_bytes_vectored(1, data1, data2); + * @endcode + * + * @tparam Ts List of types supporting data() and size() methods. + * @param tag Tag of the field + * @param values List of objects of types Ts with data to be appended. + */ + template + void add_bytes_vectored(T tag, Ts&&... values) { + basic_pbf_writer::add_bytes_vectored(pbf_tag_type(tag), std::forward(values)...); + } + + /** + * Add "string" field to data. + * + * @param tag Tag of the field + * @param value Pointer to value to be written + * @param size Number of bytes to be written + */ + void add_string(T tag, const char* value, std::size_t size) { + basic_pbf_writer::add_string(pbf_tag_type(tag), value, size); + } + + /** + * Add "string" field to data. + * + * @param tag Tag of the field + * @param value Value to be written + */ + void add_string(T tag, const data_view& value) { + basic_pbf_writer::add_string(pbf_tag_type(tag), value); + } + + /** + * Add "string" field to data. + * + * @param tag Tag of the field + * @param value Value to be written + */ + void add_string(T tag, const std::string& value) { + basic_pbf_writer::add_string(pbf_tag_type(tag), value); + } + + /** + * Add "string" field to data. Bytes from the value are written until + * a null byte is encountered. The null byte is not added. + * + * @param tag Tag of the field + * @param value Pointer to value to be written + */ + void add_string(T tag, const char* value) { + basic_pbf_writer::add_string(pbf_tag_type(tag), value); + } + + /** + * Add "message" field to data. + * + * @param tag Tag of the field + * @param value Pointer to message to be written + * @param size Length of the message + */ + void add_message(T tag, const char* value, std::size_t size) { + basic_pbf_writer::add_message(pbf_tag_type(tag), value, size); + } + + /** + * Add "message" field to data. + * + * @param tag Tag of the field + * @param value Value to be written. The value must be a complete message. + */ + void add_message(T tag, const data_view& value) { + basic_pbf_writer::add_message(pbf_tag_type(tag), value); + } + + /** + * Add "message" field to data. + * + * @param tag Tag of the field + * @param value Value to be written. The value must be a complete message. + */ + void add_message(T tag, const std::string& value) { + basic_pbf_writer::add_message(pbf_tag_type(tag), value); + } + +/// @cond INTERNAL +#define PROTOZERO_WRITER_WRAP_ADD_PACKED(name) \ + template \ + void add_packed_##name(T tag, InputIterator first, InputIterator last) { \ + basic_pbf_writer::add_packed_##name(pbf_tag_type(tag), first, last); \ + } + + PROTOZERO_WRITER_WRAP_ADD_PACKED(bool) + PROTOZERO_WRITER_WRAP_ADD_PACKED(enum) + PROTOZERO_WRITER_WRAP_ADD_PACKED(int32) + PROTOZERO_WRITER_WRAP_ADD_PACKED(sint32) + PROTOZERO_WRITER_WRAP_ADD_PACKED(uint32) + PROTOZERO_WRITER_WRAP_ADD_PACKED(int64) + PROTOZERO_WRITER_WRAP_ADD_PACKED(sint64) + PROTOZERO_WRITER_WRAP_ADD_PACKED(uint64) + PROTOZERO_WRITER_WRAP_ADD_PACKED(fixed32) + PROTOZERO_WRITER_WRAP_ADD_PACKED(sfixed32) + PROTOZERO_WRITER_WRAP_ADD_PACKED(fixed64) + PROTOZERO_WRITER_WRAP_ADD_PACKED(sfixed64) + PROTOZERO_WRITER_WRAP_ADD_PACKED(float) + PROTOZERO_WRITER_WRAP_ADD_PACKED(double) + +#undef PROTOZERO_WRITER_WRAP_ADD_PACKED +/// @endcond + +}; // class basic_pbf_builder + +} // end namespace protozero + +#endif // PROTOZERO_BASIC_PBF_BUILDER_HPP diff --git a/protozero/basic_pbf_writer.hpp b/protozero/basic_pbf_writer.hpp new file mode 100644 index 000000000..f167c4d1d --- /dev/null +++ b/protozero/basic_pbf_writer.hpp @@ -0,0 +1,1054 @@ +#ifndef PROTOZERO_BASIC_PBF_WRITER_HPP +#define PROTOZERO_BASIC_PBF_WRITER_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file basic_pbf_writer.hpp + * + * @brief Contains the basic_pbf_writer template class. + */ + +#include "buffer_tmpl.hpp" +#include "config.hpp" +#include "data_view.hpp" +#include "types.hpp" +#include "varint.hpp" + +#if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace protozero { + +namespace detail { + + template class packed_field_varint; + template class packed_field_svarint; + template class packed_field_fixed; + +} // end namespace detail + +/** + * The basic_pbf_writer is used to write PBF formatted messages into a buffer. + * + * This uses TBuffer as the type for the underlaying buffer. In typical uses + * this is std::string, but you can use a different type that must support + * the right interface. Please see the documentation for details. + * + * Almost all methods in this class can throw an std::bad_alloc exception if + * the underlying buffer class wants to resize. + */ +template +class basic_pbf_writer { + + // A pointer to a buffer holding the data already written to the PBF + // message. For default constructed writers or writers that have been + // rolled back, this is a nullptr. + TBuffer* m_data = nullptr; + + // A pointer to a parent writer object if this is a submessage. If this + // is a top-level writer, it is a nullptr. + basic_pbf_writer* m_parent_writer = nullptr; + + // This is usually 0. If there is an open submessage, this is set in the + // parent to the rollback position, ie. the last position before the + // submessage was started. This is the position where the header of the + // submessage starts. + std::size_t m_rollback_pos = 0; + + // This is usually 0. If there is an open submessage, this is set in the + // parent to the position where the data of the submessage is written to. + std::size_t m_pos = 0; + + void add_varint(uint64_t value) { + protozero_assert(m_pos == 0 && "you can't add fields to a parent basic_pbf_writer if there is an existing basic_pbf_writer for a submessage"); + protozero_assert(m_data); + add_varint_to_buffer(m_data, value); + } + + void add_field(pbf_tag_type tag, pbf_wire_type type) { + protozero_assert(((tag > 0 && tag < 19000) || (tag > 19999 && tag <= ((1U << 29U) - 1))) && "tag out of range"); + const uint32_t b = (tag << 3U) | uint32_t(type); + add_varint(b); + } + + void add_tagged_varint(pbf_tag_type tag, uint64_t value) { + add_field(tag, pbf_wire_type::varint); + add_varint(value); + } + + template + void add_fixed(T value) { + protozero_assert(m_pos == 0 && "you can't add fields to a parent basic_pbf_writer if there is an existing basic_pbf_writer for a submessage"); + protozero_assert(m_data); +#if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN + byteswap_inplace(&value); +#endif + buffer_customization::append(m_data, reinterpret_cast(&value), sizeof(T)); + } + + template + void add_packed_fixed(pbf_tag_type tag, It first, It last, std::input_iterator_tag /*unused*/) { + if (first == last) { + return; + } + + basic_pbf_writer sw{*this, tag}; + + while (first != last) { + sw.add_fixed(*first++); + } + } + + template + void add_packed_fixed(pbf_tag_type tag, It first, It last, std::forward_iterator_tag /*unused*/) { + if (first == last) { + return; + } + + const auto length = std::distance(first, last); + add_length_varint(tag, sizeof(T) * pbf_length_type(length)); + reserve(sizeof(T) * std::size_t(length)); + + while (first != last) { + add_fixed(*first++); + } + } + + template + void add_packed_varint(pbf_tag_type tag, It first, It last) { + if (first == last) { + return; + } + + basic_pbf_writer sw{*this, tag}; + + while (first != last) { + sw.add_varint(uint64_t(*first++)); + } + } + + template + void add_packed_svarint(pbf_tag_type tag, It first, It last) { + if (first == last) { + return; + } + + basic_pbf_writer sw{*this, tag}; + + while (first != last) { + sw.add_varint(encode_zigzag64(*first++)); + } + } + + // The number of bytes to reserve for the varint holding the length of + // a length-delimited field. The length has to fit into pbf_length_type, + // and a varint needs 8 bit for every 7 bit. + enum : int { + reserve_bytes = sizeof(pbf_length_type) * 8 / 7 + 1 + }; + + // If m_rollpack_pos is set to this special value, it means that when + // the submessage is closed, nothing needs to be done, because the length + // of the submessage has already been written correctly. + enum : std::size_t { + size_is_known = std::numeric_limits::max() + }; + + void open_submessage(pbf_tag_type tag, std::size_t size) { + protozero_assert(m_pos == 0); + protozero_assert(m_data); + if (size == 0) { + m_rollback_pos = buffer_customization::size(m_data); + add_field(tag, pbf_wire_type::length_delimited); + buffer_customization::append_zeros(m_data, std::size_t(reserve_bytes)); + } else { + m_rollback_pos = size_is_known; + add_length_varint(tag, pbf_length_type(size)); + reserve(size); + } + m_pos = buffer_customization::size(m_data); + } + + void rollback_submessage() { + protozero_assert(m_pos != 0); + protozero_assert(m_rollback_pos != size_is_known); + protozero_assert(m_data); + buffer_customization::resize(m_data, m_rollback_pos); + m_pos = 0; + } + + void commit_submessage() { + protozero_assert(m_pos != 0); + protozero_assert(m_rollback_pos != size_is_known); + protozero_assert(m_data); + const auto length = pbf_length_type(buffer_customization::size(m_data) - m_pos); + + protozero_assert(buffer_customization::size(m_data) >= m_pos - reserve_bytes); + const auto n = add_varint_to_buffer(buffer_customization::at_pos(m_data, m_pos - reserve_bytes), length); + + buffer_customization::erase_range(m_data, m_pos - reserve_bytes + n, m_pos); + m_pos = 0; + } + + void close_submessage() { + protozero_assert(m_data); + if (m_pos == 0 || m_rollback_pos == size_is_known) { + return; + } + if (buffer_customization::size(m_data) - m_pos == 0) { + rollback_submessage(); + } else { + commit_submessage(); + } + } + + void add_length_varint(pbf_tag_type tag, pbf_length_type length) { + add_field(tag, pbf_wire_type::length_delimited); + add_varint(length); + } + +public: + + /** + * Create a writer using the specified buffer as a data store. The + * basic_pbf_writer stores a pointer to that buffer and adds all data to + * it. The buffer doesn't have to be empty. The basic_pbf_writer will just + * append data. + */ + explicit basic_pbf_writer(TBuffer& buffer) noexcept : + m_data{&buffer} { + } + + /** + * Create a writer without a data store. In this form the writer can not + * be used! + */ + basic_pbf_writer() noexcept = default; + + /** + * Construct a basic_pbf_writer for a submessage from the basic_pbf_writer + * of the parent message. + * + * @param parent_writer The basic_pbf_writer + * @param tag Tag (field number) of the field that will be written + * @param size Optional size of the submessage in bytes (use 0 for unknown). + * Setting this allows some optimizations but is only possible in + * a few very specific cases. + */ + basic_pbf_writer(basic_pbf_writer& parent_writer, pbf_tag_type tag, std::size_t size = 0) : + m_data{parent_writer.m_data}, + m_parent_writer{&parent_writer} { + m_parent_writer->open_submessage(tag, size); + } + + /// A basic_pbf_writer object can not be copied + basic_pbf_writer(const basic_pbf_writer&) = delete; + + /// A basic_pbf_writer object can not be copied + basic_pbf_writer& operator=(const basic_pbf_writer&) = delete; + + /** + * A basic_pbf_writer object can be moved. After this the other + * basic_pbf_writer will be invalid. + */ + basic_pbf_writer(basic_pbf_writer&& other) noexcept : + m_data{other.m_data}, + m_parent_writer{other.m_parent_writer}, + m_rollback_pos{other.m_rollback_pos}, + m_pos{other.m_pos} { + other.m_data = nullptr; + other.m_parent_writer = nullptr; + other.m_rollback_pos = 0; + other.m_pos = 0; + } + + /** + * A basic_pbf_writer object can be moved. After this the other + * basic_pbf_writer will be invalid. + */ + basic_pbf_writer& operator=(basic_pbf_writer&& other) noexcept { + m_data = other.m_data; + m_parent_writer = other.m_parent_writer; + m_rollback_pos = other.m_rollback_pos; + m_pos = other.m_pos; + other.m_data = nullptr; + other.m_parent_writer = nullptr; + other.m_rollback_pos = 0; + other.m_pos = 0; + return *this; + } + + ~basic_pbf_writer() noexcept { + try { + if (m_parent_writer != nullptr) { + m_parent_writer->close_submessage(); + } + } catch (...) { + // This try/catch is used to make the destructor formally noexcept. + // close_submessage() is not noexcept, but will not throw the way + // it is called here, so we are good. But to be paranoid, call... + std::terminate(); + } + } + + /** + * Check if this writer is valid. A writer is invalid if it was default + * constructed, moved from, or if commit() has been called on it. + * Otherwise it is valid. + */ + bool valid() const noexcept { + return m_data != nullptr; + } + + /** + * Swap the contents of this object with the other. + * + * @param other Other object to swap data with. + */ + void swap(basic_pbf_writer& other) noexcept { + using std::swap; + swap(m_data, other.m_data); + swap(m_parent_writer, other.m_parent_writer); + swap(m_rollback_pos, other.m_rollback_pos); + swap(m_pos, other.m_pos); + } + + /** + * Reserve size bytes in the underlying message store in addition to + * whatever the message store already holds. So unlike + * the `std::string::reserve()` method this is not an absolute size, + * but additional memory that should be reserved. + * + * @param size Number of bytes to reserve in underlying message store. + */ + void reserve(std::size_t size) { + protozero_assert(m_data); + buffer_customization::reserve_additional(m_data, size); + } + + /** + * Commit this submessage. This does the same as when the basic_pbf_writer + * goes out of scope and is destructed. + * + * @pre Must be a basic_pbf_writer of a submessage, ie one opened with the + * basic_pbf_writer constructor taking a parent message. + * @post The basic_pbf_writer is invalid and can't be used any more. + */ + void commit() { + protozero_assert(m_parent_writer && "you can't call commit() on a basic_pbf_writer without a parent"); + protozero_assert(m_pos == 0 && "you can't call commit() on a basic_pbf_writer that has an open nested submessage"); + m_parent_writer->close_submessage(); + m_parent_writer = nullptr; + m_data = nullptr; + } + + /** + * Cancel writing of this submessage. The complete submessage will be + * removed as if it was never created and no fields were added. + * + * @pre Must be a basic_pbf_writer of a submessage, ie one opened with the + * basic_pbf_writer constructor taking a parent message. + * @post The basic_pbf_writer is invalid and can't be used any more. + */ + void rollback() { + protozero_assert(m_parent_writer && "you can't call rollback() on a basic_pbf_writer without a parent"); + protozero_assert(m_pos == 0 && "you can't call rollback() on a basic_pbf_writer that has an open nested submessage"); + m_parent_writer->rollback_submessage(); + m_parent_writer = nullptr; + m_data = nullptr; + } + + ///@{ + /** + * @name Scalar field writer functions + */ + + /** + * Add "bool" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_bool(pbf_tag_type tag, bool value) { + add_field(tag, pbf_wire_type::varint); + protozero_assert(m_pos == 0 && "you can't add fields to a parent basic_pbf_writer if there is an existing basic_pbf_writer for a submessage"); + protozero_assert(m_data); + m_data->push_back(char(value)); + } + + /** + * Add "enum" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_enum(pbf_tag_type tag, int32_t value) { + add_tagged_varint(tag, uint64_t(value)); + } + + /** + * Add "int32" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_int32(pbf_tag_type tag, int32_t value) { + add_tagged_varint(tag, uint64_t(value)); + } + + /** + * Add "sint32" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_sint32(pbf_tag_type tag, int32_t value) { + add_tagged_varint(tag, encode_zigzag32(value)); + } + + /** + * Add "uint32" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_uint32(pbf_tag_type tag, uint32_t value) { + add_tagged_varint(tag, value); + } + + /** + * Add "int64" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_int64(pbf_tag_type tag, int64_t value) { + add_tagged_varint(tag, uint64_t(value)); + } + + /** + * Add "sint64" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_sint64(pbf_tag_type tag, int64_t value) { + add_tagged_varint(tag, encode_zigzag64(value)); + } + + /** + * Add "uint64" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_uint64(pbf_tag_type tag, uint64_t value) { + add_tagged_varint(tag, value); + } + + /** + * Add "fixed32" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_fixed32(pbf_tag_type tag, uint32_t value) { + add_field(tag, pbf_wire_type::fixed32); + add_fixed(value); + } + + /** + * Add "sfixed32" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_sfixed32(pbf_tag_type tag, int32_t value) { + add_field(tag, pbf_wire_type::fixed32); + add_fixed(value); + } + + /** + * Add "fixed64" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_fixed64(pbf_tag_type tag, uint64_t value) { + add_field(tag, pbf_wire_type::fixed64); + add_fixed(value); + } + + /** + * Add "sfixed64" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_sfixed64(pbf_tag_type tag, int64_t value) { + add_field(tag, pbf_wire_type::fixed64); + add_fixed(value); + } + + /** + * Add "float" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_float(pbf_tag_type tag, float value) { + add_field(tag, pbf_wire_type::fixed32); + add_fixed(value); + } + + /** + * Add "double" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_double(pbf_tag_type tag, double value) { + add_field(tag, pbf_wire_type::fixed64); + add_fixed(value); + } + + /** + * Add "bytes" field to data. + * + * @param tag Tag (field number) of the field + * @param value Pointer to value to be written + * @param size Number of bytes to be written + */ + void add_bytes(pbf_tag_type tag, const char* value, std::size_t size) { + protozero_assert(m_pos == 0 && "you can't add fields to a parent basic_pbf_writer if there is an existing basic_pbf_writer for a submessage"); + protozero_assert(m_data); + protozero_assert(size <= std::numeric_limits::max()); + add_length_varint(tag, pbf_length_type(size)); + buffer_customization::append(m_data, value, size); + } + + /** + * Add "bytes" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_bytes(pbf_tag_type tag, const data_view& value) { + add_bytes(tag, value.data(), value.size()); + } + + /** + * Add "bytes" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_bytes(pbf_tag_type tag, const std::string& value) { + add_bytes(tag, value.data(), value.size()); + } + + /** + * Add "bytes" field to data. Bytes from the value are written until + * a null byte is encountered. The null byte is not added. + * + * @param tag Tag (field number) of the field + * @param value Pointer to zero-delimited value to be written + */ + void add_bytes(pbf_tag_type tag, const char* value) { + add_bytes(tag, value, std::strlen(value)); + } + + /** + * Add "bytes" field to data using vectored input. All the data in the + * 2nd and further arguments is "concatenated" with only a single copy + * into the final buffer. + * + * This will work with objects of any type supporting the data() and + * size() methods like std::string or protozero::data_view. + * + * Example: + * @code + * std::string data1 = "abc"; + * std::string data2 = "xyz"; + * writer.add_bytes_vectored(1, data1, data2); + * @endcode + * + * @tparam Ts List of types supporting data() and size() methods. + * @param tag Tag (field number) of the field + * @param values List of objects of types Ts with data to be appended. + */ + template + void add_bytes_vectored(pbf_tag_type tag, Ts&&... values) { + protozero_assert(m_pos == 0 && "you can't add fields to a parent basic_pbf_writer if there is an existing basic_pbf_writer for a submessage"); + protozero_assert(m_data); + size_t sum_size = 0; + (void)std::initializer_list{sum_size += values.size()...}; + protozero_assert(sum_size <= std::numeric_limits::max()); + add_length_varint(tag, pbf_length_type(sum_size)); + buffer_customization::reserve_additional(m_data, sum_size); + (void)std::initializer_list{(buffer_customization::append(m_data, values.data(), values.size()), 0)...}; + } + + /** + * Add "string" field to data. + * + * @param tag Tag (field number) of the field + * @param value Pointer to value to be written + * @param size Number of bytes to be written + */ + void add_string(pbf_tag_type tag, const char* value, std::size_t size) { + add_bytes(tag, value, size); + } + + /** + * Add "string" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_string(pbf_tag_type tag, const data_view& value) { + add_bytes(tag, value.data(), value.size()); + } + + /** + * Add "string" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written + */ + void add_string(pbf_tag_type tag, const std::string& value) { + add_bytes(tag, value.data(), value.size()); + } + + /** + * Add "string" field to data. Bytes from the value are written until + * a null byte is encountered. The null byte is not added. + * + * @param tag Tag (field number) of the field + * @param value Pointer to value to be written + */ + void add_string(pbf_tag_type tag, const char* value) { + add_bytes(tag, value, std::strlen(value)); + } + + /** + * Add "message" field to data. + * + * @param tag Tag (field number) of the field + * @param value Pointer to message to be written + * @param size Length of the message + */ + void add_message(pbf_tag_type tag, const char* value, std::size_t size) { + add_bytes(tag, value, size); + } + + /** + * Add "message" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written. The value must be a complete message. + */ + void add_message(pbf_tag_type tag, const data_view& value) { + add_bytes(tag, value.data(), value.size()); + } + + /** + * Add "message" field to data. + * + * @param tag Tag (field number) of the field + * @param value Value to be written. The value must be a complete message. + */ + void add_message(pbf_tag_type tag, const std::string& value) { + add_bytes(tag, value.data(), value.size()); + } + + ///@} + + ///@{ + /** + * @name Repeated packed field writer functions + */ + + /** + * Add "repeated packed bool" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to bool. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template + void add_packed_bool(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_varint(tag, first, last); + } + + /** + * Add "repeated packed enum" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to int32_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template + void add_packed_enum(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_varint(tag, first, last); + } + + /** + * Add "repeated packed int32" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to int32_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template + void add_packed_int32(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_varint(tag, first, last); + } + + /** + * Add "repeated packed sint32" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to int32_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template + void add_packed_sint32(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_svarint(tag, first, last); + } + + /** + * Add "repeated packed uint32" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to uint32_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template + void add_packed_uint32(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_varint(tag, first, last); + } + + /** + * Add "repeated packed int64" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to int64_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template + void add_packed_int64(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_varint(tag, first, last); + } + + /** + * Add "repeated packed sint64" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to int64_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template + void add_packed_sint64(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_svarint(tag, first, last); + } + + /** + * Add "repeated packed uint64" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to uint64_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template + void add_packed_uint64(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_varint(tag, first, last); + } + + /** + * Add a "repeated packed" fixed-size field to data. The following + * fixed-size fields are available: + * + * uint32_t -> repeated packed fixed32 + * int32_t -> repeated packed sfixed32 + * uint64_t -> repeated packed fixed64 + * int64_t -> repeated packed sfixed64 + * double -> repeated packed double + * float -> repeated packed float + * + * @tparam ValueType One of the following types: (u)int32/64_t, double, float. + * @tparam InputIterator A type satisfying the InputIterator concept. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template + void add_packed_fixed(pbf_tag_type tag, InputIterator first, InputIterator last) { + static_assert(std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value, "Only some types are allowed"); + add_packed_fixed(tag, first, last, + typename std::iterator_traits::iterator_category{}); + } + + /** + * Add "repeated packed fixed32" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to uint32_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template + void add_packed_fixed32(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_fixed(tag, first, last, + typename std::iterator_traits::iterator_category{}); + } + + /** + * Add "repeated packed sfixed32" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to int32_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template + void add_packed_sfixed32(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_fixed(tag, first, last, + typename std::iterator_traits::iterator_category{}); + } + + /** + * Add "repeated packed fixed64" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to uint64_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template + void add_packed_fixed64(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_fixed(tag, first, last, + typename std::iterator_traits::iterator_category{}); + } + + /** + * Add "repeated packed sfixed64" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to int64_t. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template + void add_packed_sfixed64(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_fixed(tag, first, last, + typename std::iterator_traits::iterator_category{}); + } + + /** + * Add "repeated packed float" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to float. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template + void add_packed_float(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_fixed(tag, first, last, + typename std::iterator_traits::iterator_category{}); + } + + /** + * Add "repeated packed double" field to data. + * + * @tparam InputIterator A type satisfying the InputIterator concept. + * Dereferencing the iterator must yield a type assignable to double. + * @param tag Tag (field number) of the field + * @param first Iterator pointing to the beginning of the data + * @param last Iterator pointing one past the end of data + */ + template + void add_packed_double(pbf_tag_type tag, InputIterator first, InputIterator last) { + add_packed_fixed(tag, first, last, + typename std::iterator_traits::iterator_category{}); + } + + ///@} + + template friend class detail::packed_field_varint; + template friend class detail::packed_field_svarint; + template friend class detail::packed_field_fixed; + +}; // class basic_pbf_writer + +/** + * Swap two basic_pbf_writer objects. + * + * @param lhs First object. + * @param rhs Second object. + */ +template +inline void swap(basic_pbf_writer& lhs, basic_pbf_writer& rhs) noexcept { + lhs.swap(rhs); +} + +namespace detail { + + template + class packed_field { + + basic_pbf_writer m_writer{}; + + public: + + packed_field(const packed_field&) = delete; + packed_field& operator=(const packed_field&) = delete; + + packed_field(packed_field&&) noexcept = default; + packed_field& operator=(packed_field&&) noexcept = default; + + packed_field() = default; + + packed_field(basic_pbf_writer& parent_writer, pbf_tag_type tag) : + m_writer{parent_writer, tag} { + } + + packed_field(basic_pbf_writer& parent_writer, pbf_tag_type tag, std::size_t size) : + m_writer{parent_writer, tag, size} { + } + + ~packed_field() noexcept = default; + + bool valid() const noexcept { + return m_writer.valid(); + } + + void commit() { + m_writer.commit(); + } + + void rollback() { + m_writer.rollback(); + } + + basic_pbf_writer& writer() noexcept { + return m_writer; + } + + }; // class packed_field + + template + class packed_field_fixed : public packed_field { + + public: + + packed_field_fixed() : + packed_field{} { + } + + template + packed_field_fixed(basic_pbf_writer& parent_writer, P tag) : + packed_field{parent_writer, static_cast(tag)} { + } + + template + packed_field_fixed(basic_pbf_writer& parent_writer, P tag, std::size_t size) : + packed_field{parent_writer, static_cast(tag), size * sizeof(T)} { + } + + void add_element(T value) { + this->writer().template add_fixed(value); + } + + }; // class packed_field_fixed + + template + class packed_field_varint : public packed_field { + + public: + + packed_field_varint() : + packed_field{} { + } + + template + packed_field_varint(basic_pbf_writer& parent_writer, P tag) : + packed_field{parent_writer, static_cast(tag)} { + } + + void add_element(T value) { + this->writer().add_varint(uint64_t(value)); + } + + }; // class packed_field_varint + + template + class packed_field_svarint : public packed_field { + + public: + + packed_field_svarint() : + packed_field{} { + } + + template + packed_field_svarint(basic_pbf_writer& parent_writer, P tag) : + packed_field{parent_writer, static_cast(tag)} { + } + + void add_element(T value) { + this->writer().add_varint(encode_zigzag64(value)); + } + + }; // class packed_field_svarint + +} // end namespace detail + +} // end namespace protozero + +#endif // PROTOZERO_BASIC_PBF_WRITER_HPP diff --git a/protozero/buffer_fixed.hpp b/protozero/buffer_fixed.hpp new file mode 100644 index 000000000..b2e6d1d27 --- /dev/null +++ b/protozero/buffer_fixed.hpp @@ -0,0 +1,222 @@ +#ifndef PROTOZERO_BUFFER_FIXED_HPP +#define PROTOZERO_BUFFER_FIXED_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file buffer_fixed.hpp + * + * @brief Contains the fixed_size_buffer_adaptor class. + */ + +#include "buffer_tmpl.hpp" +#include "config.hpp" + +#include +#include +#include +#include + +namespace protozero { + +/** + * This class can be used instead of std::string if you want to create a + * vector tile in a fixed-size buffer. Any operation that needs more space + * than is available will fail with a std::length_error exception. + */ +class fixed_size_buffer_adaptor { + + char* m_data; + std::size_t m_capacity; + std::size_t m_size = 0; + +public: + + /// @cond usual container typedefs not documented + + using size_type = std::size_t; + + using value_type = char; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + + using iterator = pointer; + using const_iterator = const_pointer; + + /// @endcond + + /** + * Constructor. + * + * @param data Pointer to some memory allocated for the buffer. + * @param capacity Number of bytes available. + */ + fixed_size_buffer_adaptor(char* data, std::size_t capacity) noexcept : + m_data(data), + m_capacity(capacity) { + } + + /** + * Constructor. + * + * @param container Some container class supporting the member functions + * data() and size(). + */ + template + explicit fixed_size_buffer_adaptor(T& container) : + m_data(container.data()), + m_capacity(container.size()) { + } + + /// Returns a pointer to the data in the buffer. + const char* data() const noexcept { + return m_data; + } + + /// Returns a pointer to the data in the buffer. + char* data() noexcept { + return m_data; + } + + /// The capacity this buffer was created with. + std::size_t capacity() const noexcept { + return m_capacity; + } + + /// The number of bytes used in the buffer. Always <= capacity(). + std::size_t size() const noexcept { + return m_size; + } + + /// Return iterator to beginning of data. + char* begin() noexcept { + return m_data; + } + + /// Return iterator to beginning of data. + const char* begin() const noexcept { + return m_data; + } + + /// Return iterator to beginning of data. + const char* cbegin() const noexcept { + return m_data; + } + + /// Return iterator to end of data. + char* end() noexcept { + return m_data + m_size; + } + + /// Return iterator to end of data. + const char* end() const noexcept { + return m_data + m_size; + } + + /// Return iterator to end of data. + const char* cend() const noexcept { + return m_data + m_size; + } + +/// @cond INTERNAL + + // Do not rely on anything beyond this point + + void append(const char* data, std::size_t count) { + if (m_size + count > m_capacity) { + throw std::length_error{"fixed size data store exhausted"}; + } + std::copy_n(data, count, m_data + m_size); + m_size += count; + } + + void append_zeros(std::size_t count) { + if (m_size + count > m_capacity) { + throw std::length_error{"fixed size data store exhausted"}; + } + std::fill_n(m_data + m_size, count, '\0'); + m_size += count; + } + + void resize(std::size_t size) { + protozero_assert(size < m_size); + if (size > m_capacity) { + throw std::length_error{"fixed size data store exhausted"}; + } + m_size = size; + } + + void erase_range(std::size_t from, std::size_t to) { + protozero_assert(from <= m_size); + protozero_assert(to <= m_size); + protozero_assert(from < to); + std::copy(m_data + to, m_data + m_size, m_data + from); + m_size -= (to - from); + } + + char* at_pos(std::size_t pos) { + protozero_assert(pos <= m_size); + return m_data + pos; + } + + void push_back(char ch) { + if (m_size >= m_capacity) { + throw std::length_error{"fixed size data store exhausted"}; + } + m_data[m_size++] = ch; + } +/// @endcond + +}; // class fixed_size_buffer_adaptor + +/// @cond INTERNAL +template <> +struct buffer_customization { + + static std::size_t size(const fixed_size_buffer_adaptor* buffer) noexcept { + return buffer->size(); + } + + static void append(fixed_size_buffer_adaptor* buffer, const char* data, std::size_t count) { + buffer->append(data, count); + } + + static void append_zeros(fixed_size_buffer_adaptor* buffer, std::size_t count) { + buffer->append_zeros(count); + } + + static void resize(fixed_size_buffer_adaptor* buffer, std::size_t size) { + buffer->resize(size); + } + + static void reserve_additional(fixed_size_buffer_adaptor* /*buffer*/, std::size_t /*size*/) { + /* nothing to be done for fixed-size buffers */ + } + + static void erase_range(fixed_size_buffer_adaptor* buffer, std::size_t from, std::size_t to) { + buffer->erase_range(from, to); + } + + static char* at_pos(fixed_size_buffer_adaptor* buffer, std::size_t pos) { + return buffer->at_pos(pos); + } + + static void push_back(fixed_size_buffer_adaptor* buffer, char ch) { + buffer->push_back(ch); + } + +}; +/// @endcond + +} // namespace protozero + +#endif // PROTOZERO_BUFFER_FIXED_HPP diff --git a/protozero/buffer_string.hpp b/protozero/buffer_string.hpp new file mode 100644 index 000000000..02e8ad25b --- /dev/null +++ b/protozero/buffer_string.hpp @@ -0,0 +1,78 @@ +#ifndef PROTOZERO_BUFFER_STRING_HPP +#define PROTOZERO_BUFFER_STRING_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file buffer_string.hpp + * + * @brief Contains the customization points for buffer implementation based + * on std::string + */ + +#include "buffer_tmpl.hpp" +#include "config.hpp" + +#include +#include +#include + +namespace protozero { + +// Implementation of buffer customizations points for std::string + +/// @cond INTERNAL +template <> +struct buffer_customization { + + static std::size_t size(const std::string* buffer) noexcept { + return buffer->size(); + } + + static void append(std::string* buffer, const char* data, std::size_t count) { + buffer->append(data, count); + } + + static void append_zeros(std::string* buffer, std::size_t count) { + buffer->append(count, '\0'); + } + + static void resize(std::string* buffer, std::size_t size) { + protozero_assert(size < buffer->size()); + buffer->resize(size); + } + + static void reserve_additional(std::string* buffer, std::size_t size) { + buffer->reserve(buffer->size() + size); + } + + static void erase_range(std::string* buffer, std::size_t from, std::size_t to) { + protozero_assert(from <= buffer->size()); + protozero_assert(to <= buffer->size()); + protozero_assert(from <= to); + buffer->erase(std::next(buffer->begin(), static_cast(from)), + std::next(buffer->begin(), static_cast(to))); + } + + static char* at_pos(std::string* buffer, std::size_t pos) { + protozero_assert(pos <= buffer->size()); + return (&*buffer->begin()) + pos; + } + + static void push_back(std::string* buffer, char ch) { + buffer->push_back(ch); + } + +}; +/// @endcond + +} // namespace protozero + +#endif // PROTOZERO_BUFFER_STRING_HPP diff --git a/protozero/buffer_tmpl.hpp b/protozero/buffer_tmpl.hpp new file mode 100644 index 000000000..ac223996d --- /dev/null +++ b/protozero/buffer_tmpl.hpp @@ -0,0 +1,113 @@ +#ifndef PROTOZERO_BUFFER_TMPL_HPP +#define PROTOZERO_BUFFER_TMPL_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file buffer_tmpl.hpp + * + * @brief Contains the customization points for buffer implementations. + */ + +#include +#include +#include + +namespace protozero { + +// Implementation of buffer customizations points for std::string + +/// @cond INTERNAL +template +struct buffer_customization { + + /** + * Get the number of bytes currently used in the buffer. + * + * @param buffer Pointer to the buffer. + * @returns number of bytes used in the buffer. + */ + static std::size_t size(const std::string* buffer); + + /** + * Append count bytes from data to the buffer. + * + * @param buffer Pointer to the buffer. + * @param data Pointer to the data. + * @param count Number of bytes to be added to the buffer. + */ + static void append(std::string* buffer, const char* data, std::size_t count); + + /** + * Append count zero bytes to the buffer. + * + * @param buffer Pointer to the buffer. + * @param count Number of bytes to be added to the buffer. + */ + static void append_zeros(std::string* buffer, std::size_t count); + + /** + * Shrink the buffer to the specified size. The new size will always be + * smaller than the current size. + * + * @param buffer Pointer to the buffer. + * @param size New size of the buffer. + * + * @pre size < current size of buffer + */ + static void resize(std::string* buffer, std::size_t size); + + /** + * Reserve an additional size bytes for use in the buffer. This is used for + * variable-sized buffers to tell the buffer implementation that soon more + * memory will be used. The implementation can ignore this. + * + * @param buffer Pointer to the buffer. + * @param size Number of bytes to reserve. + */ + static void reserve_additional(std::string* buffer, std::size_t size); + + /** + * Delete data from the buffer. This must move back the data after the + * part being deleted and resize the buffer accordingly. + * + * @param buffer Pointer to the buffer. + * @param from Offset into the buffer where we want to erase from. + * @param to Offset into the buffer one past the last byte we want to erase. + * + * @pre from, to <= size of the buffer, from < to + */ + static void erase_range(std::string* buffer, std::size_t from, std::size_t to); + + /** + * Return a pointer to the memory at the specified position in the buffer. + * + * @param buffer Pointer to the buffer. + * @param pos The position in the buffer. + * @returns pointer to the memory in the buffer at the specified position. + * + * @pre pos <= size of the buffer + */ + static char* at_pos(std::string* buffer, std::size_t pos); + + /** + * Add a char to the buffer incrementing the number of chars in the buffer. + * + * @param buffer Pointer to the buffer. + * @param ch The character to add. + */ + static void push_back(std::string* buffer, char ch); + +}; +/// @endcond + +} // namespace protozero + +#endif // PROTOZERO_BUFFER_TMPL_HPP diff --git a/protozero/buffer_vector.hpp b/protozero/buffer_vector.hpp new file mode 100644 index 000000000..c163300c5 --- /dev/null +++ b/protozero/buffer_vector.hpp @@ -0,0 +1,78 @@ +#ifndef PROTOZERO_BUFFER_VECTOR_HPP +#define PROTOZERO_BUFFER_VECTOR_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file buffer_vector.hpp + * + * @brief Contains the customization points for buffer implementation based + * on std::vector + */ + +#include "buffer_tmpl.hpp" +#include "config.hpp" + +#include +#include +#include + +namespace protozero { + +// Implementation of buffer customizations points for std::vector + +/// @cond INTERNAL +template <> +struct buffer_customization> { + + static std::size_t size(const std::vector* buffer) noexcept { + return buffer->size(); + } + + static void append(std::vector* buffer, const char* data, std::size_t count) { + buffer->insert(buffer->end(), data, data + count); + } + + static void append_zeros(std::vector* buffer, std::size_t count) { + buffer->insert(buffer->end(), count, '\0'); + } + + static void resize(std::vector* buffer, std::size_t size) { + protozero_assert(size < buffer->size()); + buffer->resize(size); + } + + static void reserve_additional(std::vector* buffer, std::size_t size) { + buffer->reserve(buffer->size() + size); + } + + static void erase_range(std::vector* buffer, std::size_t from, std::size_t to) { + protozero_assert(from <= buffer->size()); + protozero_assert(to <= buffer->size()); + protozero_assert(from <= to); + buffer->erase(std::next(buffer->begin(), static_cast(from)), + std::next(buffer->begin(), static_cast(to))); + } + + static char* at_pos(std::vector* buffer, std::size_t pos) { + protozero_assert(pos <= buffer->size()); + return (&*buffer->begin()) + pos; + } + + static void push_back(std::vector* buffer, char ch) { + buffer->push_back(ch); + } + +}; +/// @endcond + +} // namespace protozero + +#endif // PROTOZERO_BUFFER_VECTOR_HPP diff --git a/protozero/byteswap.hpp b/protozero/byteswap.hpp index bca4844cf..75cae6910 100644 --- a/protozero/byteswap.hpp +++ b/protozero/byteswap.hpp @@ -16,10 +16,10 @@ documentation. * @brief Contains functions to swap bytes in values (for different endianness). */ -#include -#include +#include "config.hpp" -#include +#include +#include namespace protozero { namespace detail { @@ -28,10 +28,10 @@ inline uint32_t byteswap_impl(uint32_t value) noexcept { #ifdef PROTOZERO_USE_BUILTIN_BSWAP return __builtin_bswap32(value); #else - return ((value & 0xff000000) >> 24) | - ((value & 0x00ff0000) >> 8) | - ((value & 0x0000ff00) << 8) | - ((value & 0x000000ff) << 24); + return ((value & 0xff000000U) >> 24U) | + ((value & 0x00ff0000U) >> 8U) | + ((value & 0x0000ff00U) << 8U) | + ((value & 0x000000ffU) << 24U); #endif } @@ -39,46 +39,70 @@ inline uint64_t byteswap_impl(uint64_t value) noexcept { #ifdef PROTOZERO_USE_BUILTIN_BSWAP return __builtin_bswap64(value); #else - return ((value & 0xff00000000000000ULL) >> 56) | - ((value & 0x00ff000000000000ULL) >> 40) | - ((value & 0x0000ff0000000000ULL) >> 24) | - ((value & 0x000000ff00000000ULL) >> 8) | - ((value & 0x00000000ff000000ULL) << 8) | - ((value & 0x0000000000ff0000ULL) << 24) | - ((value & 0x000000000000ff00ULL) << 40) | - ((value & 0x00000000000000ffULL) << 56); + return ((value & 0xff00000000000000ULL) >> 56U) | + ((value & 0x00ff000000000000ULL) >> 40U) | + ((value & 0x0000ff0000000000ULL) >> 24U) | + ((value & 0x000000ff00000000ULL) >> 8U) | + ((value & 0x00000000ff000000ULL) << 8U) | + ((value & 0x0000000000ff0000ULL) << 24U) | + ((value & 0x000000000000ff00ULL) << 40U) | + ((value & 0x00000000000000ffULL) << 56U); #endif } +} // end namespace detail + +/// byteswap the data pointed to by ptr in-place. inline void byteswap_inplace(uint32_t* ptr) noexcept { - *ptr = byteswap_impl(*ptr); + *ptr = detail::byteswap_impl(*ptr); } +/// byteswap the data pointed to by ptr in-place. inline void byteswap_inplace(uint64_t* ptr) noexcept { - *ptr = byteswap_impl(*ptr); + *ptr = detail::byteswap_impl(*ptr); } +/// byteswap the data pointed to by ptr in-place. inline void byteswap_inplace(int32_t* ptr) noexcept { - auto bptr = reinterpret_cast(ptr); - *bptr = byteswap_impl(*bptr); + auto* bptr = reinterpret_cast(ptr); + *bptr = detail::byteswap_impl(*bptr); } +/// byteswap the data pointed to by ptr in-place. inline void byteswap_inplace(int64_t* ptr) noexcept { - auto bptr = reinterpret_cast(ptr); - *bptr = byteswap_impl(*bptr); + auto* bptr = reinterpret_cast(ptr); + *bptr = detail::byteswap_impl(*bptr); } +/// byteswap the data pointed to by ptr in-place. inline void byteswap_inplace(float* ptr) noexcept { - auto bptr = reinterpret_cast(ptr); - *bptr = byteswap_impl(*bptr); + static_assert(sizeof(float) == 4, "Expecting four byte float"); + + uint32_t tmp = 0; + std::memcpy(&tmp, ptr, 4); + tmp = detail::byteswap_impl(tmp); // uint32 overload + std::memcpy(ptr, &tmp, 4); } +/// byteswap the data pointed to by ptr in-place. inline void byteswap_inplace(double* ptr) noexcept { - auto bptr = reinterpret_cast(ptr); - *bptr = byteswap_impl(*bptr); + static_assert(sizeof(double) == 8, "Expecting eight byte double"); + + uint64_t tmp = 0; + std::memcpy(&tmp, ptr, 8); + tmp = detail::byteswap_impl(tmp); // uint64 overload + std::memcpy(ptr, &tmp, 8); } +namespace detail { + + // Added for backwards compatibility with any code that might use this + // function (even if it shouldn't have). Will be removed in a later + // version of protozero. + using ::protozero::byteswap_inplace; + } // end namespace detail + } // end namespace protozero #endif // PROTOZERO_BYTESWAP_HPP diff --git a/protozero/data_view.hpp b/protozero/data_view.hpp new file mode 100644 index 000000000..3ec87af34 --- /dev/null +++ b/protozero/data_view.hpp @@ -0,0 +1,236 @@ +#ifndef PROTOZERO_DATA_VIEW_HPP +#define PROTOZERO_DATA_VIEW_HPP + +/***************************************************************************** + +protozero - Minimalistic protocol buffer decoder and encoder in C++. + +This file is from https://github.com/mapbox/protozero where you can find more +documentation. + +*****************************************************************************/ + +/** + * @file data_view.hpp + * + * @brief Contains the implementation of the data_view class. + */ + +#include "config.hpp" + +#include +#include +#include +#include +#include + +namespace protozero { + +#ifdef PROTOZERO_USE_VIEW +using data_view = PROTOZERO_USE_VIEW; +#else + +/** + * Holds a pointer to some data and a length. + * + * This class is supposed to be compatible with the std::string_view + * that will be available in C++17. + */ +class data_view { + + const char* m_data = nullptr; + std::size_t m_size = 0; + +public: + + /** + * Default constructor. Construct an empty data_view. + */ + constexpr data_view() noexcept = default; + + /** + * Create data_view from pointer and size. + * + * @param ptr Pointer to the data. + * @param length Length of the data. + */ + constexpr data_view(const char* ptr, std::size_t length) noexcept + : m_data{ptr}, + m_size{length} { + } + + /** + * Create data_view from string. + * + * @param str String with the data. + */ + data_view(const std::string& str) noexcept // NOLINT(google-explicit-constructor, hicpp-explicit-conversions) + : m_data{str.data()}, + m_size{str.size()} { + } + + /** + * Create data_view from zero-terminated string. + * + * @param ptr Pointer to the data. + */ + data_view(const char* ptr) noexcept // NOLINT(google-explicit-constructor, hicpp-explicit-conversions) + : m_data{ptr}, + m_size{std::strlen(ptr)} { + } + + /** + * Swap the contents of this object with the other. + * + * @param other Other object to swap data with. + */ + void swap(data_view& other) noexcept { + using std::swap; + swap(m_data, other.m_data); + swap(m_size, other.m_size); + } + + /// Return pointer to data. + constexpr const char* data() const noexcept { + return m_data; + } + + /// Return length of data in bytes. + constexpr std::size_t size() const noexcept { + return m_size; + } + + /// Returns true if size is 0. + constexpr bool empty() const noexcept { + return m_size == 0; + } + +#ifndef PROTOZERO_STRICT_API + /** + * Convert data view to string. + * + * @pre Must not be default constructed data_view. + * + * @deprecated to_string() is not available in C++17 string_view so it + * should not be used to make conversion to that class easier + * in the future. + */ + std::string to_string() const { + protozero_assert(m_data); + return {m_data, m_size}; + } +#endif + + /** + * Convert data view to string. + * + * @pre Must not be default constructed data_view. + */ + explicit operator std::string() const { + protozero_assert(m_data); + return {m_data, m_size}; + } + + /** + * Compares the contents of this object with the given other object. + * + * @returns 0 if they are the same, <0 if this object is smaller than + * the other or >0 if it is larger. If both objects have the + * same size returns <0 if this object is lexicographically + * before the other, >0 otherwise. + * + * @pre Must not be default constructed data_view. + */ + int compare(data_view other) const noexcept { + assert(m_data && other.m_data); + const int cmp = std::memcmp(data(), other.data(), + std::min(size(), other.size())); + if (cmp == 0) { + if (size() == other.size()) { + return 0; + } + return size() < other.size() ? -1 : 1; + } + return cmp; + } + +}; // class data_view + +/** + * Swap two data_view objects. + * + * @param lhs First object. + * @param rhs Second object. + */ +inline void swap(data_view& lhs, data_view& rhs) noexcept { + lhs.swap(rhs); +} + +/** + * Two data_view instances are equal if they have the same size and the + * same content. + * + * @param lhs First object. + * @param rhs Second object. + */ +inline constexpr bool operator==(const data_view lhs, const data_view rhs) noexcept { + return lhs.size() == rhs.size() && + std::equal(lhs.data(), lhs.data() + lhs.size(), rhs.data()); +} + +/** + * Two data_view instances are not equal if they have different sizes or the + * content differs. + * + * @param lhs First object. + * @param rhs Second object. + */ +inline constexpr bool operator!=(const data_view lhs, const data_view rhs) noexcept { + return !(lhs == rhs); +} + +/** + * Returns true if lhs.compare(rhs) < 0. + * + * @param lhs First object. + * @param rhs Second object. + */ +inline bool operator<(const data_view lhs, const data_view rhs) noexcept { + return lhs.compare(rhs) < 0; +} + +/** + * Returns true if lhs.compare(rhs) <= 0. + * + * @param lhs First object. + * @param rhs Second object. + */ +inline bool operator<=(const data_view lhs, const data_view rhs) noexcept { + return lhs.compare(rhs) <= 0; +} + +/** + * Returns true if lhs.compare(rhs) > 0. + * + * @param lhs First object. + * @param rhs Second object. + */ +inline bool operator>(const data_view lhs, const data_view rhs) noexcept { + return lhs.compare(rhs) > 0; +} + +/** + * Returns true if lhs.compare(rhs) >= 0. + * + * @param lhs First object. + * @param rhs Second object. + */ +inline bool operator>=(const data_view lhs, const data_view rhs) noexcept { + return lhs.compare(rhs) >= 0; +} + +#endif + +} // end namespace protozero + +#endif // PROTOZERO_DATA_VIEW_HPP diff --git a/protozero/exception.hpp b/protozero/exception.hpp index ca4340e90..a3cd0f15d 100644 --- a/protozero/exception.hpp +++ b/protozero/exception.hpp @@ -29,7 +29,9 @@ namespace protozero { */ struct exception : std::exception { /// Returns the explanatory string. - const char* what() const noexcept override { return "pbf exception"; } + const char* what() const noexcept override { + return "pbf exception"; + } }; /** @@ -38,7 +40,9 @@ struct exception : std::exception { */ struct varint_too_long_exception : exception { /// Returns the explanatory string. - const char* what() const noexcept override { return "varint too long exception"; } + const char* what() const noexcept override { + return "varint too long exception"; + } }; /** @@ -47,7 +51,9 @@ struct varint_too_long_exception : exception { */ struct unknown_pbf_wire_type_exception : exception { /// Returns the explanatory string. - const char* what() const noexcept override { return "unknown pbf field type exception"; } + const char* what() const noexcept override { + return "unknown pbf field type exception"; + } }; /** @@ -60,7 +66,34 @@ struct unknown_pbf_wire_type_exception : exception { */ struct end_of_buffer_exception : exception { /// Returns the explanatory string. - const char* what() const noexcept override { return "end of buffer exception"; } + const char* what() const noexcept override { + return "end of buffer exception"; + } +}; + +/** + * This exception is thrown when a tag has an invalid value. Tags must be + * unsigned integers between 1 and 2^29-1. Tags between 19000 and 19999 are + * not allowed. See + * https://developers.google.com/protocol-buffers/docs/proto#assigning-tags + */ +struct invalid_tag_exception : exception { + /// Returns the explanatory string. + const char* what() const noexcept override { + return "invalid tag exception"; + } +}; + +/** + * This exception is thrown when a length field of a packed repeated field is + * invalid. For fixed size types the length must be a multiple of the size of + * the type. + */ +struct invalid_length_exception : exception { + /// Returns the explanatory string. + const char* what() const noexcept override { + return "invalid length exception"; + } }; } // end namespace protozero diff --git a/protozero/iterators.hpp b/protozero/iterators.hpp index a19f20242..ee8ef8ecf 100644 --- a/protozero/iterators.hpp +++ b/protozero/iterators.hpp @@ -16,17 +16,18 @@ documentation. * @brief Contains the iterators for access to packed repeated fields. */ -#include -#include -#include - -#include -#include +#include "config.hpp" +#include "varint.hpp" #if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN # include #endif +#include +#include +#include +#include + namespace protozero { /** @@ -55,18 +56,18 @@ class iterator_range : * Default constructor. Create empty iterator_range. */ constexpr iterator_range() : - P(iterator{}, iterator{}) { + P{iterator{}, iterator{}} { } /** * Create iterator range from two iterators. * - * @param first_iterator Iterator to beginning or range. - * @param last_iterator Iterator to end or range. + * @param first_iterator Iterator to beginning of range. + * @param last_iterator Iterator to end of range. */ constexpr iterator_range(iterator&& first_iterator, iterator&& last_iterator) : - P(std::forward(first_iterator), - std::forward(last_iterator)) { + P{std::forward(first_iterator), + std::forward(last_iterator)} { } /// Return iterator to beginning of range. @@ -89,11 +90,24 @@ class iterator_range : return this->second; } - /// Return true if this range is empty. - constexpr std::size_t empty() const noexcept { + /** + * Return true if this range is empty. + * + * Complexity: Constant. + */ + constexpr bool empty() const noexcept { return begin() == end(); } + /** + * Get the size of the range, ie the number of elements it contains. + * + * Complexity: Constant or linear depending on the underlaying iterator. + */ + std::size_t size() const noexcept { + return static_cast(std::distance(begin(), end())); + } + /** * Get element at the beginning of the range. * @@ -146,27 +160,22 @@ template class const_fixed_iterator { /// Pointer to current iterator position - const char* m_data; - - /// Pointer to end iterator position - const char* m_end; + const char* m_data = nullptr; public: - using iterator_category = std::forward_iterator_tag; + /// @cond usual iterator functions not documented + + using iterator_category = std::random_access_iterator_tag; using value_type = T; using difference_type = std::ptrdiff_t; using pointer = value_type*; using reference = value_type&; - const_fixed_iterator() noexcept : - m_data(nullptr), - m_end(nullptr) { - } + const_fixed_iterator() noexcept = default; - const_fixed_iterator(const char* data, const char* end) noexcept : - m_data(data), - m_end(end) { + explicit const_fixed_iterator(const char* data) noexcept : + m_data{data} { } const_fixed_iterator(const const_fixed_iterator&) noexcept = default; @@ -177,34 +186,99 @@ class const_fixed_iterator { ~const_fixed_iterator() noexcept = default; - value_type operator*() const { + value_type operator*() const noexcept { value_type result; std::memcpy(&result, m_data, sizeof(value_type)); #if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN - detail::byteswap_inplace(&result); + byteswap_inplace(&result); #endif return result; } - const_fixed_iterator& operator++() { + const_fixed_iterator& operator++() noexcept { m_data += sizeof(value_type); return *this; } - const_fixed_iterator operator++(int) { - const const_fixed_iterator tmp(*this); + const_fixed_iterator operator++(int) noexcept { + const const_fixed_iterator tmp{*this}; ++(*this); return tmp; } - bool operator==(const const_fixed_iterator& rhs) const noexcept { - return m_data == rhs.m_data && m_end == rhs.m_end; + const_fixed_iterator& operator--() noexcept { + m_data -= sizeof(value_type); + return *this; } - bool operator!=(const const_fixed_iterator& rhs) const noexcept { - return !(*this == rhs); + const_fixed_iterator operator--(int) noexcept { + const const_fixed_iterator tmp{*this}; + --(*this); + return tmp; + } + + friend bool operator==(const_fixed_iterator lhs, const_fixed_iterator rhs) noexcept { + return lhs.m_data == rhs.m_data; + } + + friend bool operator!=(const_fixed_iterator lhs, const_fixed_iterator rhs) noexcept { + return !(lhs == rhs); + } + + friend bool operator<(const_fixed_iterator lhs, const_fixed_iterator rhs) noexcept { + return lhs.m_data < rhs.m_data; + } + + friend bool operator>(const_fixed_iterator lhs, const_fixed_iterator rhs) noexcept { + return rhs < lhs; + } + + friend bool operator<=(const_fixed_iterator lhs, const_fixed_iterator rhs) noexcept { + return !(lhs > rhs); + } + + friend bool operator>=(const_fixed_iterator lhs, const_fixed_iterator rhs) noexcept { + return !(lhs < rhs); + } + + const_fixed_iterator& operator+=(difference_type val) noexcept { + m_data += (sizeof(value_type) * val); + return *this; + } + + friend const_fixed_iterator operator+(const_fixed_iterator lhs, difference_type rhs) noexcept { + const_fixed_iterator tmp{lhs}; + tmp.m_data += (sizeof(value_type) * rhs); + return tmp; + } + + friend const_fixed_iterator operator+(difference_type lhs, const_fixed_iterator rhs) noexcept { + const_fixed_iterator tmp{rhs}; + tmp.m_data += (sizeof(value_type) * lhs); + return tmp; + } + + const_fixed_iterator& operator-=(difference_type val) noexcept { + m_data -= (sizeof(value_type) * val); + return *this; } + friend const_fixed_iterator operator-(const_fixed_iterator lhs, difference_type rhs) noexcept { + const_fixed_iterator tmp{lhs}; + tmp.m_data -= (sizeof(value_type) * rhs); + return tmp; + } + + friend difference_type operator-(const_fixed_iterator lhs, const_fixed_iterator rhs) noexcept { + return static_cast(lhs.m_data - rhs.m_data) / static_cast(sizeof(T)); + } + + value_type operator[](difference_type n) const noexcept { + return *(*this + n); + } + + /// @endcond + }; // class const_fixed_iterator /** @@ -217,27 +291,40 @@ class const_varint_iterator { protected: /// Pointer to current iterator position - const char* m_data; + const char* m_data = nullptr; // NOLINT(misc-non-private-member-variables-in-classes, cppcoreguidelines-non-private-member-variables-in-classes,-warnings-as-errors) /// Pointer to end iterator position - const char* m_end; + const char* m_end = nullptr; // NOLINT(misc-non-private-member-variables-in-classes, cppcoreguidelines-non-private-member-variables-in-classes,-warnings-as-errors) public: + /// @cond usual iterator functions not documented + using iterator_category = std::forward_iterator_tag; using value_type = T; using difference_type = std::ptrdiff_t; using pointer = value_type*; using reference = value_type&; - const_varint_iterator() noexcept : - m_data(nullptr), - m_end(nullptr) { + static difference_type distance(const_varint_iterator begin, const_varint_iterator end) noexcept { + // The "distance" between default initialized const_varint_iterator's + // is always 0. + if (!begin.m_data) { + return 0; + } + // We know that each varint contains exactly one byte with the most + // significant bit not set. We can use this to quickly figure out + // how many varints there are without actually decoding the varints. + return std::count_if(begin.m_data, end.m_data, [](char c) noexcept { + return (static_cast(c) & 0x80U) == 0; + }); } + const_varint_iterator() noexcept = default; + const_varint_iterator(const char* data, const char* end) noexcept : - m_data(data), - m_end(end) { + m_data{data}, + m_end{end} { } const_varint_iterator(const const_varint_iterator&) noexcept = default; @@ -249,17 +336,20 @@ class const_varint_iterator { ~const_varint_iterator() noexcept = default; value_type operator*() const { + protozero_assert(m_data); const char* d = m_data; // will be thrown away return static_cast(decode_varint(&d, m_end)); } const_varint_iterator& operator++() { + protozero_assert(m_data); skip_varint(&m_data, m_end); return *this; } const_varint_iterator operator++(int) { - const const_varint_iterator tmp(*this); + protozero_assert(m_data); + const const_varint_iterator tmp{*this}; ++(*this); return tmp; } @@ -272,6 +362,8 @@ class const_varint_iterator { return !(*this == rhs); } + /// @endcond + }; // class const_varint_iterator /** @@ -283,6 +375,8 @@ class const_svarint_iterator : public const_varint_iterator { public: + /// @cond usual iterator functions not documented + using iterator_category = std::forward_iterator_tag; using value_type = T; using difference_type = std::ptrdiff_t; @@ -290,39 +384,98 @@ class const_svarint_iterator : public const_varint_iterator { using reference = value_type&; const_svarint_iterator() noexcept : - const_varint_iterator() { + const_varint_iterator{} { } const_svarint_iterator(const char* data, const char* end) noexcept : - const_varint_iterator(data, end) { + const_varint_iterator{data, end} { } const_svarint_iterator(const const_svarint_iterator&) = default; - const_svarint_iterator(const_svarint_iterator&&) = default; + const_svarint_iterator(const_svarint_iterator&&) noexcept = default; const_svarint_iterator& operator=(const const_svarint_iterator&) = default; - const_svarint_iterator& operator=(const_svarint_iterator&&) = default; + const_svarint_iterator& operator=(const_svarint_iterator&&) noexcept = default; ~const_svarint_iterator() = default; value_type operator*() const { + protozero_assert(this->m_data); const char* d = this->m_data; // will be thrown away return static_cast(decode_zigzag64(decode_varint(&d, this->m_end))); } const_svarint_iterator& operator++() { + protozero_assert(this->m_data); skip_varint(&this->m_data, this->m_end); return *this; } const_svarint_iterator operator++(int) { - const const_svarint_iterator tmp(*this); + protozero_assert(this->m_data); + const const_svarint_iterator tmp{*this}; ++(*this); return tmp; } + /// @endcond + }; // class const_svarint_iterator } // end namespace protozero +namespace std { + + // Specialize std::distance for all the protozero iterators. Because + // functions can't be partially specialized, we have to do this for + // every value_type we are using. + + /// @cond individual overloads do not need to be documented + + template <> + inline typename protozero::const_varint_iterator::difference_type + distance>(protozero::const_varint_iterator first, // NOLINT(readability-inconsistent-declaration-parameter-name) + protozero::const_varint_iterator last) { + return protozero::const_varint_iterator::distance(first, last); + } + + template <> + inline typename protozero::const_varint_iterator::difference_type + distance>(protozero::const_varint_iterator first, // NOLINT(readability-inconsistent-declaration-parameter-name) + protozero::const_varint_iterator last) { + return protozero::const_varint_iterator::distance(first, last); + } + + template <> + inline typename protozero::const_varint_iterator::difference_type + distance>(protozero::const_varint_iterator first, // NOLINT(readability-inconsistent-declaration-parameter-name) + protozero::const_varint_iterator last) { + return protozero::const_varint_iterator::distance(first, last); + } + + template <> + inline typename protozero::const_varint_iterator::difference_type + distance>(protozero::const_varint_iterator first, // NOLINT(readability-inconsistent-declaration-parameter-name) + protozero::const_varint_iterator last) { + return protozero::const_varint_iterator::distance(first, last); + } + + template <> + inline typename protozero::const_svarint_iterator::difference_type + distance>(protozero::const_svarint_iterator first, // NOLINT(readability-inconsistent-declaration-parameter-name) + protozero::const_svarint_iterator last) { + return protozero::const_svarint_iterator::distance(first, last); + } + + template <> + inline typename protozero::const_svarint_iterator::difference_type + distance>(protozero::const_svarint_iterator first, // NOLINT(readability-inconsistent-declaration-parameter-name) + protozero::const_svarint_iterator last) { + return protozero::const_svarint_iterator::distance(first, last); + } + + /// @endcond + +} // end namespace std + #endif // PROTOZERO_ITERATORS_HPP diff --git a/protozero/pbf_builder.hpp b/protozero/pbf_builder.hpp index 819739585..71a2dec2b 100644 --- a/protozero/pbf_builder.hpp +++ b/protozero/pbf_builder.hpp @@ -16,144 +16,16 @@ documentation. * @brief Contains the pbf_builder template class. */ -#include +#include "basic_pbf_builder.hpp" +#include "pbf_writer.hpp" -#include -#include +#include namespace protozero { -/** - * The pbf_builder is used to write PBF formatted messages into a buffer. It - * is based on the pbf_writer class and has all the same methods. The - * difference is that while the pbf_writer class takes an integer tag, - * this template class takes a tag of the template type T. The idea is that - * T will be an enumeration value and this helps reduce the possibility of - * programming errors. - * - * Almost all methods in this class can throw an std::bad_alloc exception if - * the std::string used as a buffer wants to resize. - * - * Read the tutorial to understand how this class is used. - */ +/// Specialization of basic_pbf_builder using std::string as buffer type. template -class pbf_builder : public pbf_writer { - - static_assert(std::is_same::type>::value, - "T must be enum with underlying type protozero::pbf_tag_type"); - -public: - - using enum_type = T; - - explicit pbf_builder(std::string& data) noexcept : - pbf_writer(data) { - } - - template - pbf_builder(pbf_writer& parent_writer, P tag) noexcept : - pbf_writer(parent_writer, pbf_tag_type(tag)) { - } - -/// @cond INTERNAL -#define PROTOZERO_WRITER_WRAP_ADD_SCALAR(name, type) \ - void add_##name(T tag, type value) { \ - pbf_writer::add_##name(pbf_tag_type(tag), value); \ - } - - PROTOZERO_WRITER_WRAP_ADD_SCALAR(bool, bool) - PROTOZERO_WRITER_WRAP_ADD_SCALAR(enum, int32_t) - PROTOZERO_WRITER_WRAP_ADD_SCALAR(int32, int32_t) - PROTOZERO_WRITER_WRAP_ADD_SCALAR(sint32, int32_t) - PROTOZERO_WRITER_WRAP_ADD_SCALAR(uint32, uint32_t) - PROTOZERO_WRITER_WRAP_ADD_SCALAR(int64, int64_t) - PROTOZERO_WRITER_WRAP_ADD_SCALAR(sint64, int64_t) - PROTOZERO_WRITER_WRAP_ADD_SCALAR(uint64, uint64_t) - PROTOZERO_WRITER_WRAP_ADD_SCALAR(fixed32, uint32_t) - PROTOZERO_WRITER_WRAP_ADD_SCALAR(sfixed32, int32_t) - PROTOZERO_WRITER_WRAP_ADD_SCALAR(fixed64, uint64_t) - PROTOZERO_WRITER_WRAP_ADD_SCALAR(sfixed64, int64_t) - PROTOZERO_WRITER_WRAP_ADD_SCALAR(float, float) - PROTOZERO_WRITER_WRAP_ADD_SCALAR(double, double) - -#undef PROTOZERO_WRITER_WRAP_ADD_SCALAR -/// @endcond - - void add_bytes(T tag, const char* value, std::size_t size) { - pbf_writer::add_bytes(pbf_tag_type(tag), value, size); - } - - void add_bytes(T tag, const data_view& value) { - pbf_writer::add_bytes(pbf_tag_type(tag), value); - } - - void add_bytes(T tag, const std::string& value) { - pbf_writer::add_bytes(pbf_tag_type(tag), value); - } - - void add_bytes(T tag, const char* value) { - pbf_writer::add_bytes(pbf_tag_type(tag), value); - } - - template - void add_bytes_vectored(T tag, Ts&&... values) { - pbf_writer::add_bytes_vectored(pbf_tag_type(tag), std::forward(values)...); - } - - void add_string(T tag, const char* value, std::size_t size) { - pbf_writer::add_string(pbf_tag_type(tag), value, size); - } - - void add_string(T tag, const data_view& value) { - pbf_writer::add_string(pbf_tag_type(tag), value); - } - - void add_string(T tag, const std::string& value) { - pbf_writer::add_string(pbf_tag_type(tag), value); - } - - void add_string(T tag, const char* value) { - pbf_writer::add_string(pbf_tag_type(tag), value); - } - - void add_message(T tag, const char* value, std::size_t size) { - pbf_writer::add_message(pbf_tag_type(tag), value, size); - } - - void add_message(T tag, const data_view& value) { - pbf_writer::add_message(pbf_tag_type(tag), value); - } - - void add_message(T tag, const std::string& value) { - pbf_writer::add_message(pbf_tag_type(tag), value); - } - -/// @cond INTERNAL -#define PROTOZERO_WRITER_WRAP_ADD_PACKED(name) \ - template \ - void add_packed_##name(T tag, InputIterator first, InputIterator last) { \ - pbf_writer::add_packed_##name(pbf_tag_type(tag), first, last); \ - } - - PROTOZERO_WRITER_WRAP_ADD_PACKED(bool) - PROTOZERO_WRITER_WRAP_ADD_PACKED(enum) - PROTOZERO_WRITER_WRAP_ADD_PACKED(int32) - PROTOZERO_WRITER_WRAP_ADD_PACKED(sint32) - PROTOZERO_WRITER_WRAP_ADD_PACKED(uint32) - PROTOZERO_WRITER_WRAP_ADD_PACKED(int64) - PROTOZERO_WRITER_WRAP_ADD_PACKED(sint64) - PROTOZERO_WRITER_WRAP_ADD_PACKED(uint64) - PROTOZERO_WRITER_WRAP_ADD_PACKED(fixed32) - PROTOZERO_WRITER_WRAP_ADD_PACKED(sfixed32) - PROTOZERO_WRITER_WRAP_ADD_PACKED(fixed64) - PROTOZERO_WRITER_WRAP_ADD_PACKED(sfixed64) - PROTOZERO_WRITER_WRAP_ADD_PACKED(float) - PROTOZERO_WRITER_WRAP_ADD_PACKED(double) - -#undef PROTOZERO_WRITER_WRAP_ADD_PACKED -/// @endcond - -}; // class pbf_builder +using pbf_builder = basic_pbf_builder; } // end namespace protozero diff --git a/protozero/pbf_message.hpp b/protozero/pbf_message.hpp index c599cf1ef..d7fd8b5d0 100644 --- a/protozero/pbf_message.hpp +++ b/protozero/pbf_message.hpp @@ -16,10 +16,10 @@ documentation. * @brief Contains the pbf_message template class. */ -#include +#include "pbf_reader.hpp" +#include "types.hpp" -#include -#include +#include namespace protozero { @@ -35,7 +35,7 @@ namespace protozero { * * std::string buffer; * // fill buffer... - * pbf_message message(buffer.data(), buffer.size()); + * pbf_message message{buffer.data(), buffer.size()}; * @endcode * * Sub-messages are created using get_message(): @@ -45,7 +45,7 @@ namespace protozero { * ... * }; * - * pbf_message message(...); + * pbf_message message{...}; * message.next(); * pbf_message submessage = message.get_message(); * @endcode @@ -64,29 +64,115 @@ namespace protozero { template class pbf_message : public pbf_reader { - static_assert(std::is_same::type>::value, "T must be enum with underlying type protozero::pbf_tag_type"); + static_assert(std::is_same::type>::value, + "T must be enum with underlying type protozero::pbf_tag_type"); public: + /// The type of messages this class will read. using enum_type = T; + /** + * Construct a pbf_message. All arguments are forwarded to the pbf_reader + * parent class. + */ template - pbf_message(Args&&... args) noexcept : - pbf_reader(std::forward(args)...) { + pbf_message(Args&&... args) noexcept : // NOLINT(google-explicit-constructor, hicpp-explicit-conversions) + pbf_reader{std::forward(args)...} { } + /** + * Set next field in the message as the current field. This is usually + * called in a while loop: + * + * @code + * pbf_message<...> message(...); + * while (message.next()) { + * // handle field + * } + * @endcode + * + * @returns `true` if there is a next field, `false` if not. + * @pre There must be no current field. + * @post If it returns `true` there is a current field now. + */ bool next() { return pbf_reader::next(); } + /** + * Set next field with given tag in the message as the current field. + * Fields with other tags are skipped. This is usually called in a while + * loop for repeated fields: + * + * @code + * pbf_message message{...}; + * while (message.next(Example1::repeated_fixed64_r)) { + * // handle field + * } + * @endcode + * + * or you can call it just once to get the one field with this tag: + * + * @code + * pbf_message message{...}; + * if (message.next(Example1::required_uint32_x)) { + * // handle field + * } + * @endcode + * + * Note that this will not check the wire type. The two-argument version + * of this function will also check the wire type. + * + * @returns `true` if there is a next field with this tag. + * @pre There must be no current field. + * @post If it returns `true` there is a current field now with the given tag. + */ bool next(T next_tag) { return pbf_reader::next(pbf_tag_type(next_tag)); } + /** + * Set next field with given tag and wire type in the message as the + * current field. Fields with other tags are skipped. This is usually + * called in a while loop for repeated fields: + * + * @code + * pbf_message message{...}; + * while (message.next(Example1::repeated_fixed64_r, pbf_wire_type::varint)) { + * // handle field + * } + * @endcode + * + * or you can call it just once to get the one field with this tag: + * + * @code + * pbf_message message{...}; + * if (message.next(Example1::required_uint32_x, pbf_wire_type::varint)) { + * // handle field + * } + * @endcode + * + * Note that this will also check the wire type. The one-argument version + * of this function will not check the wire type. + * + * @returns `true` if there is a next field with this tag. + * @pre There must be no current field. + * @post If it returns `true` there is a current field now with the given tag. + */ bool next(T next_tag, pbf_wire_type type) { return pbf_reader::next(pbf_tag_type(next_tag), type); } + /** + * The tag of the current field. The tag is the enum value for the field + * number from the description in the .proto file. + * + * Call next() before calling this function to set the current field. + * + * @returns tag of the current field. + * @pre There must be a current field (ie. next() must have returned `true`). + */ T tag() const noexcept { return T(pbf_reader::tag()); } diff --git a/protozero/pbf_reader.hpp b/protozero/pbf_reader.hpp index 905ca0aad..92bfdee5e 100644 --- a/protozero/pbf_reader.hpp +++ b/protozero/pbf_reader.hpp @@ -16,21 +16,23 @@ documentation. * @brief Contains the pbf_reader class. */ -#include -#include -#include -#include - -#include -#include -#include -#include -#include +#include "config.hpp" +#include "data_view.hpp" +#include "exception.hpp" +#include "iterators.hpp" +#include "types.hpp" +#include "varint.hpp" #if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN # include #endif +#include +#include +#include +#include +#include + namespace protozero { /** @@ -41,13 +43,13 @@ namespace protozero { * @code * std::string buffer; * // fill buffer... - * pbf_reader message(buffer.data(), buffer.size()); + * pbf_reader message{buffer.data(), buffer.size()}; * @endcode * * Sub-messages are created using get_message(): * * @code - * pbf_reader message(...); + * pbf_reader message{...}; * message.next(); * pbf_reader submessage = message.get_message(); * @endcode @@ -74,10 +76,11 @@ class pbf_reader { template T get_fixed() { T result; + const char* data = m_data; skip_bytes(sizeof(T)); - std::memcpy(&result, m_data - sizeof(T), sizeof(T)); + std::memcpy(&result, data, sizeof(T)); #if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN - detail::byteswap_inplace(&result); + byteswap_inplace(&result); #endif return result; } @@ -86,14 +89,17 @@ class pbf_reader { iterator_range> packed_fixed() { protozero_assert(tag() != 0 && "call next() before accessing field value"); const auto len = get_len_and_skip(); - protozero_assert(len % sizeof(T) == 0); - return iterator_range>{const_fixed_iterator(m_data - len, m_data), - const_fixed_iterator(m_data, m_data)}; + if (len % sizeof(T) != 0) { + throw invalid_length_exception{}; + } + return {const_fixed_iterator(m_data - len), + const_fixed_iterator(m_data)}; } template T get_varint() { - return static_cast(decode_varint(&m_data, m_end)); + const auto val = static_cast(decode_varint(&m_data, m_end)); + return val; } template @@ -107,14 +113,14 @@ class pbf_reader { } void skip_bytes(pbf_length_type len) { - if (m_data + len > m_end) { - throw end_of_buffer_exception(); + if (m_end - m_data < static_cast(len)) { + throw end_of_buffer_exception{}; } m_data += len; - // In debug builds reset the tag to zero so that we can detect (some) - // wrong code. #ifndef NDEBUG + // In debug builds reset the tag to zero so that we can detect (some) + // wrong code. m_tag = 0; #endif } @@ -129,8 +135,8 @@ class pbf_reader { iterator_range get_packed() { protozero_assert(tag() != 0 && "call next() before accessing field value"); const auto len = get_len_and_skip(); - return iterator_range{T{m_data - len, m_data}, - T{m_data, m_data}}; + return {T{m_data - len, m_data}, + T{m_data, m_data}}; } public: @@ -146,10 +152,8 @@ class pbf_reader { * @post There is no current field. */ explicit pbf_reader(const data_view& view) noexcept - : m_data(view.data()), - m_end(view.data() + view.size()), - m_wire_type(pbf_wire_type::unknown), - m_tag(0) { + : m_data{view.data()}, + m_end{view.data() + view.size()} { } /** @@ -163,12 +167,11 @@ class pbf_reader { * @post There is no current field. */ pbf_reader(const char* data, std::size_t size) noexcept - : m_data(data), - m_end(data + size), - m_wire_type(pbf_wire_type::unknown), - m_tag(0) { + : m_data{data}, + m_end{data + size} { } +#ifndef PROTOZERO_STRICT_API /** * Construct a pbf_reader message from a data pointer and a length. The * pointer will be stored inside the pbf_reader object, no data is copied. @@ -178,13 +181,13 @@ class pbf_reader { * The buffer must contain a complete protobuf message. * * @post There is no current field. + * @deprecated Use one of the other constructors. */ explicit pbf_reader(const std::pair& data) noexcept - : m_data(data.first), - m_end(data.first + data.second), - m_wire_type(pbf_wire_type::unknown), - m_tag(0) { + : m_data{data.first}, + m_end{data.first + data.second} { } +#endif /** * Construct a pbf_reader message from a std::string. A pointer to the @@ -197,10 +200,8 @@ class pbf_reader { * @post There is no current field. */ explicit pbf_reader(const std::string& data) noexcept - : m_data(data.data()), - m_end(data.data() + data.size()), - m_wire_type(pbf_wire_type::unknown), - m_tag(0) { + : m_data{data.data()}, + m_end{data.data() + data.size()} { } /** @@ -241,8 +242,15 @@ class pbf_reader { * are still fields available and to `false` if the last field has been * read. */ - operator bool() const noexcept { - return m_data < m_end; + operator bool() const noexcept { // NOLINT(google-explicit-constructor, hicpp-explicit-conversions) + return m_data != m_end; + } + + /** + * Get a view of the not yet read data. + */ + data_view data() const noexcept { + return {m_data, static_cast(m_end - m_data)}; } /** @@ -279,14 +287,15 @@ class pbf_reader { } const auto value = get_varint(); - m_tag = pbf_tag_type(value >> 3); + m_tag = pbf_tag_type(value >> 3U); // tags 0 and 19000 to 19999 are not allowed as per - // https://developers.google.com/protocol-buffers/docs/proto - protozero_assert(((m_tag > 0 && m_tag < 19000) || - (m_tag > 19999 && m_tag <= ((1 << 29) - 1))) && "tag out of range"); + // https://developers.google.com/protocol-buffers/docs/proto#assigning-tags + if (m_tag == 0 || (m_tag >= 19000 && m_tag <= 19999)) { + throw invalid_tag_exception{}; + } - m_wire_type = pbf_wire_type(value & 0x07); + m_wire_type = pbf_wire_type(value & 0x07U); switch (m_wire_type) { case pbf_wire_type::varint: case pbf_wire_type::fixed64: @@ -294,7 +303,7 @@ class pbf_reader { case pbf_wire_type::fixed32: break; default: - throw unknown_pbf_wire_type_exception(); + throw unknown_pbf_wire_type_exception{}; } return true; @@ -306,7 +315,7 @@ class pbf_reader { * loop for repeated fields: * * @code - * pbf_reader message(...); + * pbf_reader message{...}; * while (message.next(17)) { * // handle field * } @@ -315,7 +324,7 @@ class pbf_reader { * or you can call it just once to get the one field with this tag: * * @code - * pbf_reader message(...); + * pbf_reader message{...}; * if (message.next(17)) { * // handle field * } @@ -332,9 +341,8 @@ class pbf_reader { while (next()) { if (m_tag == next_tag) { return true; - } else { - skip(); } + skip(); } return false; } @@ -345,7 +353,7 @@ class pbf_reader { * called in a while loop for repeated fields: * * @code - * pbf_reader message(...); + * pbf_reader message{...}; * while (message.next(17, pbf_wire_type::varint)) { * // handle field * } @@ -354,7 +362,7 @@ class pbf_reader { * or you can call it just once to get the one field with this tag: * * @code - * pbf_reader message(...); + * pbf_reader message{...}; * if (message.next(17, pbf_wire_type::varint)) { * // handle field * } @@ -371,9 +379,8 @@ class pbf_reader { while (next()) { if (m_tag == next_tag && m_wire_type == type) { return true; - } else { - skip(); } + skip(); } return false; } @@ -417,7 +424,7 @@ class pbf_reader { * Use it like this: * * @code - * pbf_reader message(...); + * pbf_reader message{...}; * while (message.next()) { * switch (message.tag_and_type()) { * case tag_and_type(17, pbf_wire_type::length_delimited): @@ -468,7 +475,7 @@ class pbf_reader { skip_bytes(4); break; default: - protozero_assert(false && "can not be here because next() should have thrown already"); + break; } } @@ -487,9 +494,9 @@ class pbf_reader { bool get_bool() { protozero_assert(tag() != 0 && "call next() before accessing field value"); protozero_assert(has_wire_type(pbf_wire_type::varint) && "not a varint"); - protozero_assert((*m_data & 0x80) == 0 && "not a 1 byte varint"); - skip_bytes(1); - return m_data[-1] != 0; // -1 okay because we incremented m_data the line before + const bool result = m_data[0] != 0; + skip_varint(&m_data, m_end); + return result; } /** @@ -667,7 +674,7 @@ class pbf_reader { protozero_assert(tag() != 0 && "call next() before accessing field value"); protozero_assert(has_wire_type(pbf_wire_type::length_delimited) && "not of type string, bytes or message"); const auto len = get_len_and_skip(); - return data_view{m_data - len, len}; + return {m_data - len, len}; } #ifndef PROTOZERO_STRICT_API @@ -683,7 +690,7 @@ class pbf_reader { protozero_assert(tag() != 0 && "call next() before accessing field value"); protozero_assert(has_wire_type(pbf_wire_type::length_delimited) && "not of type string, bytes or message"); const auto len = get_len_and_skip(); - return std::make_pair(m_data - len, len); + return {m_data - len, len}; } #endif @@ -717,7 +724,7 @@ class pbf_reader { * @post The current field was consumed and there is no current field now. */ pbf_reader get_message() { - return pbf_reader(get_view()); + return pbf_reader{get_view()}; } ///@} @@ -746,6 +753,24 @@ class pbf_reader { /// Forward iterator for iterating over uint64 (varint) values. using const_uint64_iterator = const_varint_iterator; + /// Forward iterator for iterating over fixed32 values. + using const_fixed32_iterator = const_fixed_iterator; + + /// Forward iterator for iterating over sfixed32 values. + using const_sfixed32_iterator = const_fixed_iterator; + + /// Forward iterator for iterating over fixed64 values. + using const_fixed64_iterator = const_fixed_iterator; + + /// Forward iterator for iterating over sfixed64 values. + using const_sfixed64_iterator = const_fixed_iterator; + + /// Forward iterator for iterating over float values. + using const_float_iterator = const_fixed_iterator; + + /// Forward iterator for iterating over double values. + using const_double_iterator = const_fixed_iterator; + ///@{ /** * @name Repeated packed field accessor functions @@ -864,7 +889,7 @@ class pbf_reader { * @pre The current field must be of type "repeated packed fixed32". * @post The current field was consumed and there is no current field now. */ - auto get_packed_fixed32() -> decltype(packed_fixed()) { + iterator_range get_packed_fixed32() { return packed_fixed(); } @@ -877,7 +902,7 @@ class pbf_reader { * @pre The current field must be of type "repeated packed sfixed32". * @post The current field was consumed and there is no current field now. */ - auto get_packed_sfixed32() -> decltype(packed_fixed()) { + iterator_range get_packed_sfixed32() { return packed_fixed(); } @@ -890,7 +915,7 @@ class pbf_reader { * @pre The current field must be of type "repeated packed fixed64". * @post The current field was consumed and there is no current field now. */ - auto get_packed_fixed64() -> decltype(packed_fixed()) { + iterator_range get_packed_fixed64() { return packed_fixed(); } @@ -903,7 +928,7 @@ class pbf_reader { * @pre The current field must be of type "repeated packed sfixed64". * @post The current field was consumed and there is no current field now. */ - auto get_packed_sfixed64() -> decltype(packed_fixed()) { + iterator_range get_packed_sfixed64() { return packed_fixed(); } @@ -916,7 +941,7 @@ class pbf_reader { * @pre The current field must be of type "repeated packed float". * @post The current field was consumed and there is no current field now. */ - auto get_packed_float() -> decltype(packed_fixed()) { + iterator_range get_packed_float() { return packed_fixed(); } @@ -929,7 +954,7 @@ class pbf_reader { * @pre The current field must be of type "repeated packed double". * @post The current field was consumed and there is no current field now. */ - auto get_packed_double() -> decltype(packed_fixed()) { + iterator_range get_packed_double() { return packed_fixed(); } diff --git a/protozero/pbf_writer.hpp b/protozero/pbf_writer.hpp index af626bd99..9a07bd5b9 100644 --- a/protozero/pbf_writer.hpp +++ b/protozero/pbf_writer.hpp @@ -16,959 +16,60 @@ documentation. * @brief Contains the pbf_writer class. */ -#include +#include "basic_pbf_writer.hpp" +#include "buffer_string.hpp" + #include -#include -#include -#include #include -#include - -#include -#include -#include - -#if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN -# include -#endif namespace protozero { -namespace detail { - - template class packed_field_varint; - template class packed_field_svarint; - template class packed_field_fixed; - -} // end namespace detail - -/** - * The pbf_writer is used to write PBF formatted messages into a buffer. - * - * Almost all methods in this class can throw an std::bad_alloc exception if - * the std::string used as a buffer wants to resize. - */ -class pbf_writer { - - // A pointer to a string buffer holding the data already written to the - // PBF message. For default constructed writers or writers that have been - // rolled back, this is a nullptr. - std::string* m_data; - - // A pointer to a parent writer object if this is a submessage. If this - // is a top-level writer, it is a nullptr. - pbf_writer* m_parent_writer; - - // This is usually 0. If there is an open submessage, this is set in the - // parent to the rollback position, ie. the last position before the - // submessage was started. This is the position where the header of the - // submessage starts. - std::size_t m_rollback_pos = 0; - - // This is usually 0. If there is an open submessage, this is set in the - // parent to the position where the data of the submessage is written to. - std::size_t m_pos = 0; - - void add_varint(uint64_t value) { - protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage"); - protozero_assert(m_data); - write_varint(std::back_inserter(*m_data), value); - } - - void add_field(pbf_tag_type tag, pbf_wire_type type) { - protozero_assert(((tag > 0 && tag < 19000) || (tag > 19999 && tag <= ((1 << 29) - 1))) && "tag out of range"); - const uint32_t b = (tag << 3) | uint32_t(type); - add_varint(b); - } - - void add_tagged_varint(pbf_tag_type tag, uint64_t value) { - add_field(tag, pbf_wire_type::varint); - add_varint(value); - } - - template - void add_fixed(T value) { - protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage"); - protozero_assert(m_data); -#if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN - detail::byteswap_inplace(&value); -#endif - m_data->append(reinterpret_cast(&value), sizeof(T)); - } - - template - void add_packed_fixed(pbf_tag_type tag, It first, It last, std::input_iterator_tag) { - if (first == last) { - return; - } - - pbf_writer sw(*this, tag); - - while (first != last) { - sw.add_fixed(*first++); - } - } - - template - void add_packed_fixed(pbf_tag_type tag, It first, It last, std::forward_iterator_tag) { - if (first == last) { - return; - } - - const auto length = std::distance(first, last); - add_length_varint(tag, sizeof(T) * pbf_length_type(length)); - reserve(sizeof(T) * std::size_t(length)); - - while (first != last) { - add_fixed(*first++); - } - } - - template - void add_packed_varint(pbf_tag_type tag, It first, It last) { - if (first == last) { - return; - } - - pbf_writer sw(*this, tag); - - while (first != last) { - sw.add_varint(uint64_t(*first++)); - } - } - - template - void add_packed_svarint(pbf_tag_type tag, It first, It last) { - if (first == last) { - return; - } - - pbf_writer sw(*this, tag); - - while (first != last) { - sw.add_varint(encode_zigzag64(*first++)); - } - } - - // The number of bytes to reserve for the varint holding the length of - // a length-delimited field. The length has to fit into pbf_length_type, - // and a varint needs 8 bit for every 7 bit. - enum constant_reserve_bytes : int { - reserve_bytes = sizeof(pbf_length_type) * 8 / 7 + 1 - }; - - // If m_rollpack_pos is set to this special value, it means that when - // the submessage is closed, nothing needs to be done, because the length - // of the submessage has already been written correctly. - enum constant_size_is_known : std::size_t { - size_is_known = std::numeric_limits::max() - }; - - void open_submessage(pbf_tag_type tag, std::size_t size) { - protozero_assert(m_pos == 0); - protozero_assert(m_data); - if (size == 0) { - m_rollback_pos = m_data->size(); - add_field(tag, pbf_wire_type::length_delimited); - m_data->append(std::size_t(reserve_bytes), '\0'); - } else { - m_rollback_pos = size_is_known; - add_length_varint(tag, pbf_length_type(size)); - reserve(size); - } - m_pos = m_data->size(); - } - - void rollback_submessage() { - protozero_assert(m_pos != 0); - protozero_assert(m_rollback_pos != size_is_known); - protozero_assert(m_data); - m_data->resize(m_rollback_pos); - m_pos = 0; - } - - void commit_submessage() { - protozero_assert(m_pos != 0); - protozero_assert(m_rollback_pos != size_is_known); - protozero_assert(m_data); - const auto length = pbf_length_type(m_data->size() - m_pos); - - protozero_assert(m_data->size() >= m_pos - reserve_bytes); - const auto n = write_varint(m_data->begin() + long(m_pos) - reserve_bytes, length); - - m_data->erase(m_data->begin() + long(m_pos) - reserve_bytes + n, m_data->begin() + long(m_pos)); - m_pos = 0; - } - - void close_submessage() { - protozero_assert(m_data); - if (m_pos == 0 || m_rollback_pos == size_is_known) { - return; - } - if (m_data->size() - m_pos == 0) { - rollback_submessage(); - } else { - commit_submessage(); - } - } - - void add_length_varint(pbf_tag_type tag, pbf_length_type length) { - add_field(tag, pbf_wire_type::length_delimited); - add_varint(length); - } - -public: - - /** - * Create a writer using the given string as a data store. The pbf_writer - * stores a reference to that string and adds all data to it. The string - * doesn't have to be empty. The pbf_writer will just append data. - */ - explicit pbf_writer(std::string& data) noexcept : - m_data(&data), - m_parent_writer(nullptr) { - } - - /** - * Create a writer without a data store. In this form the writer can not - * be used! - */ - pbf_writer() noexcept : - m_data(nullptr), - m_parent_writer(nullptr) { - } - - /** - * Construct a pbf_writer for a submessage from the pbf_writer of the - * parent message. - * - * @param parent_writer The pbf_writer - * @param tag Tag (field number) of the field that will be written - * @param size Optional size of the submessage in bytes (use 0 for unknown). - * Setting this allows some optimizations but is only possible in - * a few very specific cases. - */ - pbf_writer(pbf_writer& parent_writer, pbf_tag_type tag, std::size_t size=0) : - m_data(parent_writer.m_data), - m_parent_writer(&parent_writer) { - m_parent_writer->open_submessage(tag, size); - } - - /// A pbf_writer object can be copied - pbf_writer(const pbf_writer&) noexcept = default; - - /// A pbf_writer object can be copied - pbf_writer& operator=(const pbf_writer&) noexcept = default; - - /// A pbf_writer object can be moved - pbf_writer(pbf_writer&&) noexcept = default; - - /// A pbf_writer object can be moved - pbf_writer& operator=(pbf_writer&&) noexcept = default; - - ~pbf_writer() { - if (m_parent_writer) { - m_parent_writer->close_submessage(); - } - } - - /** - * Swap the contents of this object with the other. - * - * @param other Other object to swap data with. - */ - void swap(pbf_writer& other) noexcept { - using std::swap; - swap(m_data, other.m_data); - swap(m_parent_writer, other.m_parent_writer); - swap(m_rollback_pos, other.m_rollback_pos); - swap(m_pos, other.m_pos); - } - - /** - * Reserve size bytes in the underlying message store in addition to - * whatever the message store already holds. So unlike - * the `std::string::reserve()` method this is not an absolute size, - * but additional memory that should be reserved. - * - * @param size Number of bytes to reserve in underlying message store. - */ - void reserve(std::size_t size) { - protozero_assert(m_data); - m_data->reserve(m_data->size() + size); - } - - /** - * Cancel writing of this submessage. The complete submessage will be - * removed as if it was never created and no fields were added. - * - * @pre Must be a pbf_writer of a submessage, ie one opened with the - * pbf_writer constructor taking a parent message. - */ - void rollback() { - protozero_assert(m_parent_writer && "you can't call rollback() on a pbf_writer without a parent"); - protozero_assert(m_pos == 0 && "you can't call rollback() on a pbf_writer that has an open nested submessage"); - m_parent_writer->rollback_submessage(); - m_data = nullptr; - } - - ///@{ - /** - * @name Scalar field writer functions - */ - - /** - * Add "bool" field to data. - * - * @param tag Tag (field number) of the field - * @param value Value to be written - */ - void add_bool(pbf_tag_type tag, bool value) { - add_field(tag, pbf_wire_type::varint); - protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage"); - protozero_assert(m_data); - m_data->append(1, value); - } - - /** - * Add "enum" field to data. - * - * @param tag Tag (field number) of the field - * @param value Value to be written - */ - void add_enum(pbf_tag_type tag, int32_t value) { - add_tagged_varint(tag, uint64_t(value)); - } - - /** - * Add "int32" field to data. - * - * @param tag Tag (field number) of the field - * @param value Value to be written - */ - void add_int32(pbf_tag_type tag, int32_t value) { - add_tagged_varint(tag, uint64_t(value)); - } - - /** - * Add "sint32" field to data. - * - * @param tag Tag (field number) of the field - * @param value Value to be written - */ - void add_sint32(pbf_tag_type tag, int32_t value) { - add_tagged_varint(tag, encode_zigzag32(value)); - } - - /** - * Add "uint32" field to data. - * - * @param tag Tag (field number) of the field - * @param value Value to be written - */ - void add_uint32(pbf_tag_type tag, uint32_t value) { - add_tagged_varint(tag, value); - } - - /** - * Add "int64" field to data. - * - * @param tag Tag (field number) of the field - * @param value Value to be written - */ - void add_int64(pbf_tag_type tag, int64_t value) { - add_tagged_varint(tag, uint64_t(value)); - } - - /** - * Add "sint64" field to data. - * - * @param tag Tag (field number) of the field - * @param value Value to be written - */ - void add_sint64(pbf_tag_type tag, int64_t value) { - add_tagged_varint(tag, encode_zigzag64(value)); - } - - /** - * Add "uint64" field to data. - * - * @param tag Tag (field number) of the field - * @param value Value to be written - */ - void add_uint64(pbf_tag_type tag, uint64_t value) { - add_tagged_varint(tag, value); - } - - /** - * Add "fixed32" field to data. - * - * @param tag Tag (field number) of the field - * @param value Value to be written - */ - void add_fixed32(pbf_tag_type tag, uint32_t value) { - add_field(tag, pbf_wire_type::fixed32); - add_fixed(value); - } - - /** - * Add "sfixed32" field to data. - * - * @param tag Tag (field number) of the field - * @param value Value to be written - */ - void add_sfixed32(pbf_tag_type tag, int32_t value) { - add_field(tag, pbf_wire_type::fixed32); - add_fixed(value); - } - - /** - * Add "fixed64" field to data. - * - * @param tag Tag (field number) of the field - * @param value Value to be written - */ - void add_fixed64(pbf_tag_type tag, uint64_t value) { - add_field(tag, pbf_wire_type::fixed64); - add_fixed(value); - } - - /** - * Add "sfixed64" field to data. - * - * @param tag Tag (field number) of the field - * @param value Value to be written - */ - void add_sfixed64(pbf_tag_type tag, int64_t value) { - add_field(tag, pbf_wire_type::fixed64); - add_fixed(value); - } - - /** - * Add "float" field to data. - * - * @param tag Tag (field number) of the field - * @param value Value to be written - */ - void add_float(pbf_tag_type tag, float value) { - add_field(tag, pbf_wire_type::fixed32); - add_fixed(value); - } - - /** - * Add "double" field to data. - * - * @param tag Tag (field number) of the field - * @param value Value to be written - */ - void add_double(pbf_tag_type tag, double value) { - add_field(tag, pbf_wire_type::fixed64); - add_fixed(value); - } - - /** - * Add "bytes" field to data. - * - * @param tag Tag (field number) of the field - * @param value Pointer to value to be written - * @param size Number of bytes to be written - */ - void add_bytes(pbf_tag_type tag, const char* value, std::size_t size) { - protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage"); - protozero_assert(m_data); - protozero_assert(size <= std::numeric_limits::max()); - add_length_varint(tag, pbf_length_type(size)); - m_data->append(value, size); - } - - /** - * Add "bytes" field to data. - * - * @param tag Tag (field number) of the field - * @param value Value to be written - */ - void add_bytes(pbf_tag_type tag, const data_view& value) { - add_bytes(tag, value.data(), value.size()); - } - - /** - * Add "bytes" field to data. - * - * @param tag Tag (field number) of the field - * @param value Value to be written - */ - void add_bytes(pbf_tag_type tag, const std::string& value) { - add_bytes(tag, value.data(), value.size()); - } - - /** - * Add "bytes" field to data. Bytes from the value are written until - * a null byte is encountered. The null byte is not added. - * - * @param tag Tag (field number) of the field - * @param value Pointer to zero-delimited value to be written - */ - void add_bytes(pbf_tag_type tag, const char* value) { - add_bytes(tag, value, std::strlen(value)); - } - - /** - * Add "bytes" field to data using vectored input. All the data in the - * 2nd and further arguments is "concatenated" with only a single copy - * into the final buffer. - * - * This will work with objects of any type supporting the data() and - * size() methods like std::string or protozero::data_view. - * - * Example: - * @code - * std::string data1 = "abc"; - * std::string data2 = "xyz"; - * writer.add_bytes_vectored(1, data1, data2); - * @endcode - * - * @tparam Ts List of types supporting data() and size() methods. - * @param tag Tag (field number) of the field - * @param values List of objects of types Ts with data to be appended. - */ - template - void add_bytes_vectored(pbf_tag_type tag, Ts&&... values) { - protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage"); - protozero_assert(m_data); - size_t sum_size = 0; - (void)std::initializer_list{sum_size += values.size()...}; - protozero_assert(sum_size <= std::numeric_limits::max()); - add_length_varint(tag, pbf_length_type(sum_size)); - m_data->reserve(m_data->size() + sum_size); - (void)std::initializer_list{(m_data->append(values.data(), values.size()), 0)...}; - } - - /** - * Add "string" field to data. - * - * @param tag Tag (field number) of the field - * @param value Pointer to value to be written - * @param size Number of bytes to be written - */ - void add_string(pbf_tag_type tag, const char* value, std::size_t size) { - add_bytes(tag, value, size); - } - - /** - * Add "string" field to data. - * - * @param tag Tag (field number) of the field - * @param value Value to be written - */ - void add_string(pbf_tag_type tag, const data_view& value) { - add_bytes(tag, value.data(), value.size()); - } - - /** - * Add "string" field to data. - * - * @param tag Tag (field number) of the field - * @param value Value to be written - */ - void add_string(pbf_tag_type tag, const std::string& value) { - add_bytes(tag, value.data(), value.size()); - } - - /** - * Add "string" field to data. Bytes from the value are written until - * a null byte is encountered. The null byte is not added. - * - * @param tag Tag (field number) of the field - * @param value Pointer to value to be written - */ - void add_string(pbf_tag_type tag, const char* value) { - add_bytes(tag, value, std::strlen(value)); - } - - /** - * Add "message" field to data. - * - * @param tag Tag (field number) of the field - * @param value Pointer to message to be written - * @param size Length of the message - */ - void add_message(pbf_tag_type tag, const char* value, std::size_t size) { - add_bytes(tag, value, size); - } - - /** - * Add "message" field to data. - * - * @param tag Tag (field number) of the field - * @param value Value to be written. The value must be a complete message. - */ - void add_message(pbf_tag_type tag, const data_view& value) { - add_bytes(tag, value.data(), value.size()); - } - - /** - * Add "message" field to data. - * - * @param tag Tag (field number) of the field - * @param value Value to be written. The value must be a complete message. - */ - void add_message(pbf_tag_type tag, const std::string& value) { - add_bytes(tag, value.data(), value.size()); - } - - ///@} - - ///@{ - /** - * @name Repeated packed field writer functions - */ - - /** - * Add "repeated packed bool" field to data. - * - * @tparam InputIterator A type satisfying the InputIterator concept. - * Dereferencing the iterator must yield a type assignable to bool. - * @param tag Tag (field number) of the field - * @param first Iterator pointing to the beginning of the data - * @param last Iterator pointing one past the end of data - */ - template - void add_packed_bool(pbf_tag_type tag, InputIterator first, InputIterator last) { - add_packed_varint(tag, first, last); - } - - /** - * Add "repeated packed enum" field to data. - * - * @tparam InputIterator A type satisfying the InputIterator concept. - * Dereferencing the iterator must yield a type assignable to int32_t. - * @param tag Tag (field number) of the field - * @param first Iterator pointing to the beginning of the data - * @param last Iterator pointing one past the end of data - */ - template - void add_packed_enum(pbf_tag_type tag, InputIterator first, InputIterator last) { - add_packed_varint(tag, first, last); - } - - /** - * Add "repeated packed int32" field to data. - * - * @tparam InputIterator A type satisfying the InputIterator concept. - * Dereferencing the iterator must yield a type assignable to int32_t. - * @param tag Tag (field number) of the field - * @param first Iterator pointing to the beginning of the data - * @param last Iterator pointing one past the end of data - */ - template - void add_packed_int32(pbf_tag_type tag, InputIterator first, InputIterator last) { - add_packed_varint(tag, first, last); - } - - /** - * Add "repeated packed sint32" field to data. - * - * @tparam InputIterator A type satisfying the InputIterator concept. - * Dereferencing the iterator must yield a type assignable to int32_t. - * @param tag Tag (field number) of the field - * @param first Iterator pointing to the beginning of the data - * @param last Iterator pointing one past the end of data - */ - template - void add_packed_sint32(pbf_tag_type tag, InputIterator first, InputIterator last) { - add_packed_svarint(tag, first, last); - } - - /** - * Add "repeated packed uint32" field to data. - * - * @tparam InputIterator A type satisfying the InputIterator concept. - * Dereferencing the iterator must yield a type assignable to uint32_t. - * @param tag Tag (field number) of the field - * @param first Iterator pointing to the beginning of the data - * @param last Iterator pointing one past the end of data - */ - template - void add_packed_uint32(pbf_tag_type tag, InputIterator first, InputIterator last) { - add_packed_varint(tag, first, last); - } - - /** - * Add "repeated packed int64" field to data. - * - * @tparam InputIterator A type satisfying the InputIterator concept. - * Dereferencing the iterator must yield a type assignable to int64_t. - * @param tag Tag (field number) of the field - * @param first Iterator pointing to the beginning of the data - * @param last Iterator pointing one past the end of data - */ - template - void add_packed_int64(pbf_tag_type tag, InputIterator first, InputIterator last) { - add_packed_varint(tag, first, last); - } - - /** - * Add "repeated packed sint64" field to data. - * - * @tparam InputIterator A type satisfying the InputIterator concept. - * Dereferencing the iterator must yield a type assignable to int64_t. - * @param tag Tag (field number) of the field - * @param first Iterator pointing to the beginning of the data - * @param last Iterator pointing one past the end of data - */ - template - void add_packed_sint64(pbf_tag_type tag, InputIterator first, InputIterator last) { - add_packed_svarint(tag, first, last); - } - - /** - * Add "repeated packed uint64" field to data. - * - * @tparam InputIterator A type satisfying the InputIterator concept. - * Dereferencing the iterator must yield a type assignable to uint64_t. - * @param tag Tag (field number) of the field - * @param first Iterator pointing to the beginning of the data - * @param last Iterator pointing one past the end of data - */ - template - void add_packed_uint64(pbf_tag_type tag, InputIterator first, InputIterator last) { - add_packed_varint(tag, first, last); - } - - /** - * Add "repeated packed fixed32" field to data. - * - * @tparam InputIterator A type satisfying the InputIterator concept. - * Dereferencing the iterator must yield a type assignable to uint32_t. - * @param tag Tag (field number) of the field - * @param first Iterator pointing to the beginning of the data - * @param last Iterator pointing one past the end of data - */ - template - void add_packed_fixed32(pbf_tag_type tag, InputIterator first, InputIterator last) { - add_packed_fixed(tag, first, last, - typename std::iterator_traits::iterator_category()); - } - - /** - * Add "repeated packed sfixed32" field to data. - * - * @tparam InputIterator A type satisfying the InputIterator concept. - * Dereferencing the iterator must yield a type assignable to int32_t. - * @param tag Tag (field number) of the field - * @param first Iterator pointing to the beginning of the data - * @param last Iterator pointing one past the end of data - */ - template - void add_packed_sfixed32(pbf_tag_type tag, InputIterator first, InputIterator last) { - add_packed_fixed(tag, first, last, - typename std::iterator_traits::iterator_category()); - } - - /** - * Add "repeated packed fixed64" field to data. - * - * @tparam InputIterator A type satisfying the InputIterator concept. - * Dereferencing the iterator must yield a type assignable to uint64_t. - * @param tag Tag (field number) of the field - * @param first Iterator pointing to the beginning of the data - * @param last Iterator pointing one past the end of data - */ - template - void add_packed_fixed64(pbf_tag_type tag, InputIterator first, InputIterator last) { - add_packed_fixed(tag, first, last, - typename std::iterator_traits::iterator_category()); - } - - /** - * Add "repeated packed sfixed64" field to data. - * - * @tparam InputIterator A type satisfying the InputIterator concept. - * Dereferencing the iterator must yield a type assignable to int64_t. - * @param tag Tag (field number) of the field - * @param first Iterator pointing to the beginning of the data - * @param last Iterator pointing one past the end of data - */ - template - void add_packed_sfixed64(pbf_tag_type tag, InputIterator first, InputIterator last) { - add_packed_fixed(tag, first, last, - typename std::iterator_traits::iterator_category()); - } - - /** - * Add "repeated packed float" field to data. - * - * @tparam InputIterator A type satisfying the InputIterator concept. - * Dereferencing the iterator must yield a type assignable to float. - * @param tag Tag (field number) of the field - * @param first Iterator pointing to the beginning of the data - * @param last Iterator pointing one past the end of data - */ - template - void add_packed_float(pbf_tag_type tag, InputIterator first, InputIterator last) { - add_packed_fixed(tag, first, last, - typename std::iterator_traits::iterator_category()); - } - - /** - * Add "repeated packed double" field to data. - * - * @tparam InputIterator A type satisfying the InputIterator concept. - * Dereferencing the iterator must yield a type assignable to double. - * @param tag Tag (field number) of the field - * @param first Iterator pointing to the beginning of the data - * @param last Iterator pointing one past the end of data - */ - template - void add_packed_double(pbf_tag_type tag, InputIterator first, InputIterator last) { - add_packed_fixed(tag, first, last, - typename std::iterator_traits::iterator_category()); - } - - ///@} - - template friend class detail::packed_field_varint; - template friend class detail::packed_field_svarint; - template friend class detail::packed_field_fixed; - -}; // class pbf_writer - /** - * Swap two pbf_writer objects. - * - * @param lhs First object. - * @param rhs Second object. + * Specialization of basic_pbf_writer using std::string as buffer type. */ -inline void swap(pbf_writer& lhs, pbf_writer& rhs) noexcept { - lhs.swap(rhs); -} - -namespace detail { - - class packed_field { - - protected: - - pbf_writer m_writer; - - public: - - packed_field(const packed_field&) = delete; - packed_field& operator=(const packed_field&) = delete; - - packed_field(packed_field&&) = default; - packed_field& operator=(packed_field&&) = default; - - packed_field(pbf_writer& parent_writer, pbf_tag_type tag) : - m_writer(parent_writer, tag) { - } - - packed_field(pbf_writer& parent_writer, pbf_tag_type tag, std::size_t size) : - m_writer(parent_writer, tag, size) { - } - - void rollback() { - m_writer.rollback(); - } - - }; // class packed_field - - template - class packed_field_fixed : public packed_field { - - public: - - template - packed_field_fixed(pbf_writer& parent_writer, P tag) : - packed_field(parent_writer, static_cast(tag)) { - } - - template - packed_field_fixed(pbf_writer& parent_writer, P tag, std::size_t size) : - packed_field(parent_writer, static_cast(tag), size * sizeof(T)) { - } - - void add_element(T value) { - m_writer.add_fixed(value); - } - - }; // class packed_field_fixed - - template - class packed_field_varint : public packed_field { - - public: - - template - packed_field_varint(pbf_writer& parent_writer, P tag) : - packed_field(parent_writer, static_cast(tag)) { - } - - void add_element(T value) { - m_writer.add_varint(uint64_t(value)); - } - - }; // class packed_field_varint - - template - class packed_field_svarint : public packed_field { - - public: - - template - packed_field_svarint(pbf_writer& parent_writer, P tag) : - packed_field(parent_writer, static_cast(tag)) { - } - - void add_element(T value) { - m_writer.add_varint(encode_zigzag64(value)); - } - - }; // class packed_field_svarint - -} // end namespace detail +using pbf_writer = basic_pbf_writer; /// Class for generating packed repeated bool fields. -using packed_field_bool = detail::packed_field_varint; +using packed_field_bool = detail::packed_field_varint; /// Class for generating packed repeated enum fields. -using packed_field_enum = detail::packed_field_varint; +using packed_field_enum = detail::packed_field_varint; /// Class for generating packed repeated int32 fields. -using packed_field_int32 = detail::packed_field_varint; +using packed_field_int32 = detail::packed_field_varint; /// Class for generating packed repeated sint32 fields. -using packed_field_sint32 = detail::packed_field_svarint; +using packed_field_sint32 = detail::packed_field_svarint; /// Class for generating packed repeated uint32 fields. -using packed_field_uint32 = detail::packed_field_varint; +using packed_field_uint32 = detail::packed_field_varint; /// Class for generating packed repeated int64 fields. -using packed_field_int64 = detail::packed_field_varint; +using packed_field_int64 = detail::packed_field_varint; /// Class for generating packed repeated sint64 fields. -using packed_field_sint64 = detail::packed_field_svarint; +using packed_field_sint64 = detail::packed_field_svarint; /// Class for generating packed repeated uint64 fields. -using packed_field_uint64 = detail::packed_field_varint; +using packed_field_uint64 = detail::packed_field_varint; /// Class for generating packed repeated fixed32 fields. -using packed_field_fixed32 = detail::packed_field_fixed; +using packed_field_fixed32 = detail::packed_field_fixed; /// Class for generating packed repeated sfixed32 fields. -using packed_field_sfixed32 = detail::packed_field_fixed; +using packed_field_sfixed32 = detail::packed_field_fixed; /// Class for generating packed repeated fixed64 fields. -using packed_field_fixed64 = detail::packed_field_fixed; +using packed_field_fixed64 = detail::packed_field_fixed; /// Class for generating packed repeated sfixed64 fields. -using packed_field_sfixed64 = detail::packed_field_fixed; +using packed_field_sfixed64 = detail::packed_field_fixed; /// Class for generating packed repeated float fields. -using packed_field_float = detail::packed_field_fixed; +using packed_field_float = detail::packed_field_fixed; /// Class for generating packed repeated double fields. -using packed_field_double = detail::packed_field_fixed; +using packed_field_double = detail::packed_field_fixed; } // end namespace protozero diff --git a/protozero/types.hpp b/protozero/types.hpp index 3dbdaf17b..3aefddfb5 100644 --- a/protozero/types.hpp +++ b/protozero/types.hpp @@ -16,6 +16,8 @@ documentation. * @brief Contains the declaration of low-level types used in the pbf format. */ +#include "config.hpp" + #include #include #include @@ -23,8 +25,6 @@ documentation. #include #include -#include - namespace protozero { /** @@ -40,8 +40,7 @@ using pbf_tag_type = uint32_t; enum class pbf_wire_type : uint32_t { varint = 0, // int32/64, uint32/64, sint32/64, bool, enum fixed64 = 1, // fixed64, sfixed64, double - length_delimited = 2, // string, bytes, embedded messages, - // packed repeated fields + length_delimited = 2, // string, bytes, nested messages, packed repeated fields fixed32 = 5, // fixed32, sfixed32, float unknown = 99 // used for default setting in this library }; @@ -54,7 +53,7 @@ enum class pbf_wire_type : uint32_t { */ template constexpr inline uint32_t tag_and_type(T tag, pbf_wire_type wire_type) noexcept { - return (static_cast(static_cast(tag)) << 3) | static_cast(wire_type); + return (static_cast(static_cast(tag)) << 3U) | static_cast(wire_type); } /** @@ -62,145 +61,6 @@ constexpr inline uint32_t tag_and_type(T tag, pbf_wire_type wire_type) noexcept */ using pbf_length_type = uint32_t; -#ifdef PROTOZERO_USE_VIEW -using data_view = PROTOZERO_USE_VIEW; -#else - -/** - * Holds a pointer to some data and a length. - * - * This class is supposed to be compatible with the std::string_view - * that will be available in C++17. - */ -class data_view { - - const char* m_data; - std::size_t m_size; - -public: - - /** - * Default constructor. Construct an empty data_view. - */ - constexpr data_view() noexcept - : m_data(nullptr), - m_size(0) { - } - - /** - * Create data_view from pointer and size. - * - * @param ptr Pointer to the data. - * @param length Length of the data. - */ - constexpr data_view(const char* ptr, std::size_t length) noexcept - : m_data(ptr), - m_size(length) { - } - - /** - * Create data_view from string. - * - * @param str String with the data. - */ - data_view(const std::string& str) noexcept - : m_data(str.data()), - m_size(str.size()) { - } - - /** - * Create data_view from zero-terminated string. - * - * @param ptr Pointer to the data. - */ - data_view(const char* ptr) noexcept - : m_data(ptr), - m_size(std::strlen(ptr)) { - } - - /** - * Swap the contents of this object with the other. - * - * @param other Other object to swap data with. - */ - void swap(data_view& other) noexcept { - using std::swap; - swap(m_data, other.m_data); - swap(m_size, other.m_size); - } - - /// Return pointer to data. - constexpr const char* data() const noexcept { - return m_data; - } - - /// Return length of data in bytes. - constexpr std::size_t size() const noexcept { - return m_size; - } - - /// Returns true if size is 0. - constexpr bool empty() const noexcept { - return m_size == 0; - } - - /** - * Convert data view to string. - * - * @pre Must not be default constructed data_view. - */ - std::string to_string() const { - protozero_assert(m_data); - return std::string{m_data, m_size}; - } - - /** - * Convert data view to string. - * - * @pre Must not be default constructed data_view. - */ - explicit operator std::string() const { - protozero_assert(m_data); - return std::string{m_data, m_size}; - } - -}; // class data_view - -/** - * Swap two data_view objects. - * - * @param lhs First object. - * @param rhs Second object. - */ -inline void swap(data_view& lhs, data_view& rhs) noexcept { - lhs.swap(rhs); -} - -/** - * Two data_view instances are equal if they have the same size and the - * same content. - * - * @param lhs First object. - * @param rhs Second object. - */ -inline bool operator==(const data_view& lhs, const data_view& rhs) noexcept { - return lhs.size() == rhs.size() && std::equal(lhs.data(), lhs.data() + lhs.size(), rhs.data()); -} - -/** - * Two data_view instances are not equal if they have different sizes or the - * content differs. - * - * @param lhs First object. - * @param rhs Second object. - */ -inline bool operator!=(const data_view& lhs, const data_view& rhs) noexcept { - return !(lhs == rhs); -} - -#endif - - } // end namespace protozero #endif // PROTOZERO_TYPES_HPP diff --git a/protozero/varint.hpp b/protozero/varint.hpp index d115d5fdd..b4648a442 100644 --- a/protozero/varint.hpp +++ b/protozero/varint.hpp @@ -16,9 +16,10 @@ documentation. * @brief Contains low-level varint and zigzag encoding and decoding functions. */ -#include +#include "buffer_tmpl.hpp" +#include "exception.hpp" -#include +#include namespace protozero { @@ -31,34 +32,34 @@ namespace detail { // from https://github.com/facebook/folly/blob/master/folly/Varint.h inline uint64_t decode_varint_impl(const char** data, const char* end) { - const int8_t* begin = reinterpret_cast(*data); - const int8_t* iend = reinterpret_cast(end); + const auto* begin = reinterpret_cast(*data); + const auto* iend = reinterpret_cast(end); const int8_t* p = begin; uint64_t val = 0; if (iend - begin >= max_varint_length) { // fast path do { - int64_t b; - b = *p++; val = uint64_t((b & 0x7f) ); if (b >= 0) break; - b = *p++; val |= uint64_t((b & 0x7f) << 7); if (b >= 0) break; - b = *p++; val |= uint64_t((b & 0x7f) << 14); if (b >= 0) break; - b = *p++; val |= uint64_t((b & 0x7f) << 21); if (b >= 0) break; - b = *p++; val |= uint64_t((b & 0x7f) << 28); if (b >= 0) break; - b = *p++; val |= uint64_t((b & 0x7f) << 35); if (b >= 0) break; - b = *p++; val |= uint64_t((b & 0x7f) << 42); if (b >= 0) break; - b = *p++; val |= uint64_t((b & 0x7f) << 49); if (b >= 0) break; - b = *p++; val |= uint64_t((b & 0x7f) << 56); if (b >= 0) break; - b = *p++; val |= uint64_t((b & 0x7f) << 63); if (b >= 0) break; - throw varint_too_long_exception(); + int64_t b = *p++; + val = ((uint64_t(b) & 0x7fU) ); if (b >= 0) { break; } + b = *p++; val |= ((uint64_t(b) & 0x7fU) << 7U); if (b >= 0) { break; } + b = *p++; val |= ((uint64_t(b) & 0x7fU) << 14U); if (b >= 0) { break; } + b = *p++; val |= ((uint64_t(b) & 0x7fU) << 21U); if (b >= 0) { break; } + b = *p++; val |= ((uint64_t(b) & 0x7fU) << 28U); if (b >= 0) { break; } + b = *p++; val |= ((uint64_t(b) & 0x7fU) << 35U); if (b >= 0) { break; } + b = *p++; val |= ((uint64_t(b) & 0x7fU) << 42U); if (b >= 0) { break; } + b = *p++; val |= ((uint64_t(b) & 0x7fU) << 49U); if (b >= 0) { break; } + b = *p++; val |= ((uint64_t(b) & 0x7fU) << 56U); if (b >= 0) { break; } + b = *p++; val |= ((uint64_t(b) & 0x01U) << 63U); if (b >= 0) { break; } + throw varint_too_long_exception{}; } while (false); } else { - int shift = 0; + unsigned int shift = 0; while (p != iend && *p < 0) { - val |= uint64_t(*p++ & 0x7f) << shift; + val |= (uint64_t(*p++) & 0x7fU) << shift; shift += 7; } if (p == iend) { - throw end_of_buffer_exception(); + throw end_of_buffer_exception{}; } val |= uint64_t(*p++) << shift; } @@ -88,8 +89,8 @@ namespace detail { */ inline uint64_t decode_varint(const char** data, const char* end) { // If this is a one-byte varint, decode it here. - if (end != *data && ((**data & 0x80) == 0)) { - uint64_t val = uint64_t(**data); + if (end != *data && ((static_cast(**data) & 0x80U) == 0)) { + const auto val = static_cast(**data); ++(*data); return val; } @@ -110,20 +111,20 @@ inline uint64_t decode_varint(const char** data, const char* end) { * before the end of the varint. */ inline void skip_varint(const char** data, const char* end) { - const int8_t* begin = reinterpret_cast(*data); - const int8_t* iend = reinterpret_cast(end); + const auto* begin = reinterpret_cast(*data); + const auto* iend = reinterpret_cast(end); const int8_t* p = begin; while (p != iend && *p < 0) { ++p; } - if (p >= begin + max_varint_length) { - throw varint_too_long_exception(); + if (p - begin >= max_varint_length) { + throw varint_too_long_exception{}; } if (p == iend) { - throw end_of_buffer_exception(); + throw end_of_buffer_exception{}; } ++p; @@ -138,18 +139,75 @@ inline void skip_varint(const char** data, const char* end) { * @param data Output iterator the varint encoded value will be written to * byte by byte. * @param value The integer that will be encoded. + * @returns the number of bytes written * @throws Any exception thrown by increment or dereference operator on data. + * @deprecated Use add_varint_to_buffer() instead. */ template inline int write_varint(T data, uint64_t value) { int n = 1; - while (value >= 0x80) { - *data++ = char((value & 0x7f) | 0x80); - value >>= 7; + while (value >= 0x80U) { + *data++ = char((value & 0x7fU) | 0x80U); + value >>= 7U; + ++n; + } + *data = char(value); + + return n; +} + +/** + * Varint encode a 64 bit integer. + * + * @tparam TBuffer A buffer type. + * @param buffer Output buffer the varint will be written to. + * @param value The integer that will be encoded. + * @returns the number of bytes written + * @throws Any exception thrown by calling the buffer_push_back() function. + */ +template +inline void add_varint_to_buffer(TBuffer* buffer, uint64_t value) { + while (value >= 0x80U) { + buffer_customization::push_back(buffer, char((value & 0x7fU) | 0x80U)); + value >>= 7U; + } + buffer_customization::push_back(buffer, char(value)); +} + +/** + * Varint encode a 64 bit integer. + * + * @param data Where to add the varint. There must be enough space available! + * @param value The integer that will be encoded. + * @returns the number of bytes written + */ +inline int add_varint_to_buffer(char* data, uint64_t value) noexcept { + int n = 1; + + while (value >= 0x80U) { + *data++ = char((value & 0x7fU) | 0x80U); + value >>= 7U; + ++n; + } + *data = char(value); + + return n; +} + +/** + * Get the length of the varint the specified value would produce. + * + * @param value The integer to be encoded. + * @returns the number of bytes the varint would have if we created it. + */ +inline int length_of_varint(uint64_t value) noexcept { + int n = 1; + + while (value >= 0x80U) { + value >>= 7U; ++n; } - *data++ = char(value); return n; } @@ -158,28 +216,28 @@ inline int write_varint(T data, uint64_t value) { * ZigZag encodes a 32 bit integer. */ inline constexpr uint32_t encode_zigzag32(int32_t value) noexcept { - return (static_cast(value) << 1) ^ (static_cast(value >> 31)); + return (static_cast(value) << 1U) ^ static_cast(-static_cast(static_cast(value) >> 31U)); } /** * ZigZag encodes a 64 bit integer. */ inline constexpr uint64_t encode_zigzag64(int64_t value) noexcept { - return (static_cast(value) << 1) ^ (static_cast(value >> 63)); + return (static_cast(value) << 1U) ^ static_cast(-static_cast(static_cast(value) >> 63U)); } /** * Decodes a 32 bit ZigZag-encoded integer. */ inline constexpr int32_t decode_zigzag32(uint32_t value) noexcept { - return static_cast(value >> 1) ^ -static_cast(value & 1); + return static_cast((value >> 1U) ^ static_cast(-static_cast(value & 1U))); } /** * Decodes a 64 bit ZigZag-encoded integer. */ inline constexpr int64_t decode_zigzag64(uint64_t value) noexcept { - return static_cast(value >> 1) ^ -static_cast(value & 1); + return static_cast((value >> 1U) ^ static_cast(-static_cast(value & 1U))); } } // end namespace protozero diff --git a/protozero/version.hpp b/protozero/version.hpp index 6d828236f..fc9b92879 100644 --- a/protozero/version.hpp +++ b/protozero/version.hpp @@ -20,15 +20,15 @@ documentation. #define PROTOZERO_VERSION_MAJOR 1 /// The minor version number -#define PROTOZERO_VERSION_MINOR 5 +#define PROTOZERO_VERSION_MINOR 7 /// The patch number -#define PROTOZERO_VERSION_PATCH 2 +#define PROTOZERO_VERSION_PATCH 1 /// The complete version number #define PROTOZERO_VERSION_CODE (PROTOZERO_VERSION_MAJOR * 10000 + PROTOZERO_VERSION_MINOR * 100 + PROTOZERO_VERSION_PATCH) /// Version number as string -#define PROTOZERO_VERSION_STRING "1.5.2" +#define PROTOZERO_VERSION_STRING "1.7.1" #endif // PROTOZERO_VERSION_HPP diff --git a/version.hpp b/version.hpp index 69748c5f7..eacd830cc 100644 --- a/version.hpp +++ b/version.hpp @@ -1,6 +1,6 @@ #ifndef VERSION_HPP #define VERSION_HPP -#define VERSION "v2.6.0" +#define VERSION "v2.6.1" #endif