/* * charybdis5 * Copyright (C) 2016 Jason Volk * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice is present in all copies. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #pragma once #define HAVE_IRCD_BUFFER_H // Forward declarations from boost::asio because it is not included here. IRCd // buffers are not based directly on the boost ones but are easily converted // when passing our buffer to an asio function. namespace boost::asio { struct const_buffer; struct mutable_buffer; } /// Lightweight buffer interface compatible with boost::asio IO buffers and vectors /// /// A const_buffer is a pair of iterators like `const char *` meant for sending /// data; a mutable_buffer is a pair of iterators meant for receiving. In fact, /// those types store signed char* and our convention is to represent readable /// data with them. The const_raw_buffer and mutable_raw_buffer use unsigned /// char* pairs and our convention is to represent unreadable/binary data with /// them. Remember, these are conventions, not guarantees from these types. The /// const_buffer is analogous (but doesn't inherit from) a string_view, so they /// play well and convert easily between each other. /// /// These templates offer tools for individual buffers as well as tools for /// iterations of buffers. An iteration of buffers is an iovector that is /// passed to our sockets etc. The ircd::iov template can host an iteration of /// buffers. The `template template` functions are tools for a container of /// buffers of any permutation. /// namespace ircd::buffer { template struct buffer; struct const_buffer; struct mutable_buffer; struct const_raw_buffer; struct mutable_raw_buffer; template struct fixed_buffer; template struct unique_buffer; template using fixed_const_buffer = fixed_buffer; template using fixed_mutable_buffer = fixed_buffer; template using fixed_const_raw_buffer = fixed_buffer; template using fixed_mutable_raw_buffer = fixed_buffer; template class I> using const_buffers = I; template class I> using mutable_buffers = I; template class I> using const_raw_buffers = I; template class I> using mutable_raw_buffers = I; // Preconstructed null buffers extern const mutable_buffer null_buffer; extern const ilist null_buffers; // Single buffer iteration of contents template const it &begin(const buffer &buffer); template const it &end(const buffer &buffer); template it &begin(buffer &buffer); template it &end(buffer &buffer); template it rbegin(const buffer &buffer); template it rend(const buffer &buffer); // Single buffer tools template bool null(const buffer &buffer); template bool full(const buffer &buffer); template bool empty(const buffer &buffer); template bool operator!(const buffer &buffer); template size_t size(const buffer &buffer); template const it &data(const buffer &buffer); template size_t consume(buffer &buffer, const size_t &bytes); template it copy(it &dest, const it &stop, const const_raw_buffer &); template size_t copy(const it &dest, const size_t &max, const const_raw_buffer &buffer); size_t copy(const mutable_raw_buffer &dst, const const_raw_buffer &src); size_t reverse(const mutable_raw_buffer &dst, const const_raw_buffer &src); void reverse(const mutable_raw_buffer &buf); void zero(const mutable_raw_buffer &buf); // Iterable of buffers tools template class I, class T> size_t size(const I &buffers); template class I, class T> size_t copy(const mutable_raw_buffer &, const I &buffer); template class I, class T> size_t consume(I &buffers, const size_t &bytes); // Convenience copy to stream template std::ostream &operator<<(std::ostream &s, const buffer &buffer); template class I, class T> std::ostream &operator<<(std::ostream &s, const I &buffers); } // Export these important aliases down to main ircd namespace namespace ircd { using buffer::const_buffer; using buffer::mutable_buffer; using buffer::const_raw_buffer; using buffer::mutable_raw_buffer; using buffer::fixed_buffer; using buffer::unique_buffer; using buffer::null_buffer; using buffer::fixed_const_buffer; using buffer::fixed_mutable_buffer; using buffer::fixed_const_raw_buffer; using buffer::fixed_mutable_raw_buffer; using buffer::const_buffers; using buffer::mutable_buffers; using buffer::size; using buffer::data; using buffer::copy; using buffer::consume; } /// Base for all buffer types /// template struct ircd::buffer::buffer :std::tuple { using iterator = it; using value_type = typename std::remove_pointer::type; operator string_view() const; operator std::string_view() const; explicit operator std::string() const; auto &begin() const { return std::get<0>(*this); } auto &begin() { return std::get<0>(*this); } auto &end() const { return std::get<1>(*this); } auto &end() { return std::get<1>(*this); } auto &operator[](const size_t &i) const { return *(begin() + i); } auto &operator[](const size_t &i) { return *(begin() + i); } buffer(const it &start, const it &stop) :std::tuple{start, stop} {} buffer(const it &start, const size_t &size) :buffer{start, start + size} {} buffer() :buffer{nullptr, nullptr} {} }; namespace ircd::buffer { template struct mutable_buffer_base; } /// Base for mutable buffers, or buffers which can be written to because they /// are not const. /// template struct ircd::buffer::mutable_buffer_base :buffer { using iterator = typename buffer::iterator; using value_type = typename buffer::value_type; // Allows boost::spirit to append to the buffer; this means the size() of // this buffer becomes a consumption counter and the real size of the buffer // must be kept separately. This is the lowlevel basis for a stream buffer. void insert(const iterator &it, const value_type &v) { assert(it >= this->begin() && it <= this->end()); memmove(it + 1, it, std::distance(it, this->end())); *it = v; ++std::get<1>(*this); } using buffer::buffer; mutable_buffer_base() :buffer{} {} template mutable_buffer_base(value_type (&buf)[SIZE]) :buffer{buf, SIZE} {} template mutable_buffer_base(std::array &buf) :buffer{reinterpret_cast(buf.data()), SIZE} {} // lvalue string reference offered to write through to a std::string as // the buffer. not explicit; should be hard to bind by accident... mutable_buffer_base(std::string &buf) :mutable_buffer_base{const_cast(buf.data()), buf.size()} {} }; /// A writable buffer of signed char data. Convention is for this buffer to /// represent readable strings, which may or may not be null terminated. This /// is just a convention; not a gurantee of the type. /// struct ircd::buffer::mutable_buffer :mutable_buffer_base { // Conversion offered for the analogous asio buffer operator boost::asio::mutable_buffer() const; using mutable_buffer_base::mutable_buffer_base; mutable_buffer(const std::function &closure) { closure(*this); } mutable_buffer() = default; }; /// A writable buffer of unsigned signed char data. Convention is for this /// buffer to represent unreadable binary data. It may also be constructed /// from a mutable_buffer because a buffer of any data (this one) can also /// be readable data. The inverse is not true, mutable_buffer cannot be /// constructed from this class. /// struct ircd::buffer::mutable_raw_buffer :mutable_buffer_base { // Conversion offered for the analogous asio buffer operator boost::asio::mutable_buffer() const; using mutable_buffer_base::mutable_buffer_base; mutable_raw_buffer(const mutable_buffer &b) :mutable_buffer_base{reinterpret_cast(data(b)), size(b)} {} mutable_raw_buffer(const std::function &closure) { closure(*this); } mutable_raw_buffer() = default; }; namespace ircd::buffer { template struct const_buffer_base; } template struct ircd::buffer::const_buffer_base :buffer { using iterator = typename buffer::iterator; using value_type = typename buffer::value_type; using mutable_value_type = typename std::remove_const::type; using buffer::buffer; const_buffer_base() :buffer{} {} template const_buffer_base(const value_type (&buf)[SIZE]) :buffer{buf, SIZE} {} template const_buffer_base(const std::array &buf) :buffer{reinterpret_cast(buf.data()), SIZE} {} const_buffer_base(const mutable_buffer &b) :buffer{reinterpret_cast(data(b)), size(b)} {} const_buffer_base(const string_view &s) :buffer{reinterpret_cast(s.data()), s.size()} {} explicit const_buffer_base(const std::string &s) :buffer{reinterpret_cast(s.data()), s.size()} {} }; struct ircd::buffer::const_buffer :const_buffer_base { operator boost::asio::const_buffer() const; using const_buffer_base::const_buffer_base; explicit const_buffer(const std::string &s) :const_buffer_base{s} {} }; struct ircd::buffer::const_raw_buffer :const_buffer_base { operator boost::asio::const_buffer() const; using const_buffer_base::const_buffer_base; const_raw_buffer(const const_buffer &b) :const_buffer_base{reinterpret_cast(data(b)), size(b)} {} const_raw_buffer(const mutable_raw_buffer &b) :const_buffer_base{reinterpret_cast(data(b)), size(b)} {} explicit const_raw_buffer(const std::string &s) :const_buffer_base{reinterpret_cast(s.data()), s.size()} {} }; /// fixed_buffer wraps an std::array with construction and conversions apropos /// the ircd::buffer suite. fixed_buffer should be punnable. Its only memory /// footprint is the array itself and /// template struct ircd::buffer::fixed_buffer :std::array::type, SIZE> { using mutable_type = typename std::remove_const::type; using const_type = typename std::add_const::type; using array_type = std::array; operator buffer() const { return { std::begin(*this), std::end(*this) }; } operator buffer() { return { std::begin(*this), std::end(*this) }; } using array_type::array_type; fixed_buffer(const nullptr_t &) :array_type{{0}} {} fixed_buffer(const std::function &closure) { closure(mutable_buffer{reinterpret_cast(this->data()), this->size()}); } fixed_buffer(buffer b) :array_type{std::begin(b), std::end(b)} {} fixed_buffer() = default; }; static_assert ( // Assertion over an arbitrary but common template configuration. std::is_standard_layout>::value, "ircd::buffer::fixed_buffer must be standard layout" ); /// Like unique_ptr, this template holds ownership of an allocated buffer /// /// template struct ircd::buffer::unique_buffer :buffer { unique_buffer(std::unique_ptr &&, const size_t &size); unique_buffer(const size_t &size); unique_buffer(); unique_buffer(unique_buffer &&) noexcept; unique_buffer(const unique_buffer &) = delete; unique_buffer &operator=(unique_buffer &&) noexcept; unique_buffer &operator=(const unique_buffer &) = delete; ~unique_buffer() noexcept; }; template ircd::buffer::unique_buffer::unique_buffer() :buffer { nullptr, nullptr } {} template ircd::buffer::unique_buffer::unique_buffer(std::unique_ptr &&b, const size_t &size) :buffer { typename buffer::iterator(b.release()), size } {} template ircd::buffer::unique_buffer::unique_buffer(const size_t &size) :unique_buffer { std::unique_ptr { //TODO: Can't use a template parameter to the attribute even though // it's known at compile time. Hardcoding this until fixed with better // aligned dynamic memory. //new __attribute__((aligned(alignment))) uint8_t[size] new __attribute__((aligned(16))) uint8_t[size] }, size } { // Alignment can only be 16 bytes for now assert(alignment == 16); } template ircd::buffer::unique_buffer::unique_buffer(unique_buffer &&other) noexcept :buffer { std::move(static_cast(other)) } { get<0>(other) = nullptr; } template ircd::buffer::unique_buffer & ircd::buffer::unique_buffer::operator=(unique_buffer &&other) noexcept { this->~unique_buffer(); static_cast(*this) = std::move(static_cast(other)); get<0>(other) = nullptr; return *this; } template ircd::buffer::unique_buffer::~unique_buffer() noexcept { delete[] data(*this); } template class buffers, class T> std::ostream & ircd::buffer::operator<<(std::ostream &s, const buffers &b) { std::for_each(std::begin(b), std::end(b), [&s] (const T &b) { s << b; }); return s; } template class buffers, class T> size_t ircd::buffer::consume(buffers &b, const size_t &bytes) { ssize_t remain(bytes); for(auto it(std::begin(b)); it != std::end(b) && remain > 0; ++it) { using buffer = typename buffers::value_type; using iterator = typename buffer::iterator; buffer &b(const_cast(*it)); const ssize_t bsz(size(b)); const ssize_t csz{std::min(remain, bsz)}; remain -= consume(b, csz); assert(remain >= 0); } assert(ssize_t(bytes) >= remain); return bytes - remain; } template class buffers, class T> size_t ircd::buffer::copy(const mutable_raw_buffer &dest, const buffers &b) { size_t ret(0); for(const T &b : b) ret += copy(data(dest) + ret, size(dest) - ret, b); return ret; } template class buffers, class T> size_t ircd::buffer::size(const buffers &b) { return std::accumulate(std::begin(b), std::end(b), size_t(0), [] (auto ret, const T &b) { return ret += size(b); }); } template std::ostream & ircd::buffer::operator<<(std::ostream &s, const buffer &buffer) { assert(!null(buffer) || get<1>(buffer) == nullptr); s.write(data(buffer), size(buffer)); return s; } inline void ircd::buffer::reverse(const mutable_raw_buffer &buf) { std::reverse(data(buf), data(buf) + size(buf)); } inline size_t ircd::buffer::reverse(const mutable_raw_buffer &dst, const const_raw_buffer &src) { const size_t ret{std::min(size(dst), size(src))}; std::reverse_copy(data(src), data(src) + ret, data(dst)); return ret; } inline size_t ircd::buffer::copy(const mutable_raw_buffer &dst, const const_raw_buffer &src) { auto e{begin(dst)}; copy(e, end(dst), src); assert(std::distance(begin(dst), e) >= 0); return std::distance(begin(dst), e); } template size_t ircd::buffer::copy(const it &dest, const size_t &max, const const_raw_buffer &src) { if(!max) return 0; auto start{dest}; const auto stop{dest + max - 1}; assert(stop >= start); copy(start, stop, src); assert(start <= stop); assert(start < dest + max); *start = '\0'; assert(std::distance(dest, start) >= 1); return std::distance(dest, start); } template it ircd::buffer::copy(it &dest, const it &stop, const const_raw_buffer &src) { const it ret{dest}; const ssize_t srcsz(size(src)); assert(ret <= stop); const ssize_t remain{std::distance(ret, stop)}; const ssize_t cpsz{std::min(srcsz, remain)}; assert(cpsz <= srcsz); assert(cpsz <= remain); assert(remain >= 0); memcpy(ret, data(src), cpsz); dest += cpsz; assert(dest <= stop); return ret; } template size_t ircd::buffer::consume(buffer &buffer, const size_t &bytes) { assert(!null(buffer)); assert(bytes <= size(buffer)); get<0>(buffer) += bytes; assert(get<0>(buffer) <= get<1>(buffer)); return size(buffer); } template const it & ircd::buffer::data(const buffer &buffer) { return get<0>(buffer); } template size_t ircd::buffer::size(const buffer &buffer) { assert(get<0>(buffer) <= get<1>(buffer)); assert(!null(buffer) || get<1>(buffer) == nullptr); return std::distance(get<0>(buffer), get<1>(buffer)); } template bool ircd::buffer::operator!(const buffer &buffer) { return empty(buffer); } template bool ircd::buffer::empty(const buffer &buffer) { return null(buffer) || std::distance(get<0>(buffer), get<1>(buffer)) == 0; } template bool ircd::buffer::full(const buffer &buffer) { return std::distance(get<0>(buffer), get<1>(buffer)) == 0; } template bool ircd::buffer::null(const buffer &buffer) { return get<0>(buffer) == nullptr; } template it ircd::buffer::rend(const buffer &buffer) { return std::reverse_iterator(get<0>(buffer)); } template it ircd::buffer::rbegin(const buffer &buffer) { return std::reverse_iterator(get<0>(buffer) + size(buffer)); } template it & ircd::buffer::end(buffer &buffer) { return get<1>(buffer); } template it & ircd::buffer::begin(buffer &buffer) { return get<0>(buffer); } template const it & ircd::buffer::end(const buffer &buffer) { return get<1>(buffer); } template const it & ircd::buffer::begin(const buffer &buffer) { return get<0>(buffer); } template ircd::buffer::buffer::operator std::string() const { return { reinterpret_cast(data(*this)), size(*this) }; } template ircd::buffer::buffer::operator std::string_view() const { return { reinterpret_cast(data(*this)), size(*this) }; } template ircd::buffer::buffer::operator string_view() const { return { reinterpret_cast(data(*this)), size(*this) }; }