From b5b0a9a284a00f975ffcb2c31776e5bad11eb31e Mon Sep 17 00:00:00 2001 From: Leonid Yuriev Date: Sat, 15 Jan 2022 17:48:35 +0300 Subject: [PATCH] mdbx++: add `to_hex/to_base58/to_base64::output(std::ostream&)` without using temporary objects/buffers/strings. Change-Id: Ideffd0e7f450307e14d780dcdeb2458c1c7e4c18 --- mdbx.h++ | 23 ++++++++-- src/mdbx.c++ | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 3 deletions(-) diff --git a/mdbx.h++ b/mdbx.h++ index 67ef449d..542be2d9 100644 --- a/mdbx.h++ +++ b/mdbx.h++ @@ -1146,6 +1146,11 @@ struct LIBMDBX_API to_hex { /// \throws std::length_error if given buffer is too small. char *write_bytes(char *dest, size_t dest_size) const; + /// \brief Output hexadecimal dump of passed slice to the std::ostream. + /// \throws std::ios_base::failure corresponding to std::ostream::write() + /// behaviour. + ::std::ostream &output(::std::ostream &out) const; + /// \brief Checks whether a passed slice is empty, /// and therefore there will be no output bytes. bool is_empty() const noexcept { return source.empty(); } @@ -1195,6 +1200,12 @@ struct LIBMDBX_API to_base58 { /// \throws std::length_error if given buffer is too small. char *write_bytes(char *dest, size_t dest_size) const; + /// \brief Output [Base58](https://en.wikipedia.org/wiki/Base58) + /// dump of passed slice to the std::ostream. + /// \throws std::ios_base::failure corresponding to std::ostream::write() + /// behaviour. + ::std::ostream &output(::std::ostream &out) const; + /// \brief Checks whether a passed slice is empty, /// and therefore there will be no output bytes. bool is_empty() const noexcept { return source.empty(); } @@ -1243,6 +1254,12 @@ struct LIBMDBX_API to_base64 { /// \throws std::length_error if given buffer is too small. char *write_bytes(char *dest, size_t dest_size) const; + /// \brief Output [Base64](https://en.wikipedia.org/wiki/Base64) + /// dump of passed slice to the std::ostream. + /// \throws std::ios_base::failure corresponding to std::ostream::write() + /// behaviour. + ::std::ostream &output(::std::ostream &out) const; + /// \brief Checks whether a passed slice is empty, /// and therefore there will be no output bytes. bool is_empty() const noexcept { return source.empty(); } @@ -1253,15 +1270,15 @@ struct LIBMDBX_API to_base64 { }; inline ::std::ostream &operator<<(::std::ostream &out, const to_hex &wrapper) { - return out << wrapper.as_string(); + return wrapper.output(out); } inline ::std::ostream &operator<<(::std::ostream &out, const to_base58 &wrapper) { - return out << wrapper.as_string(); + return wrapper.output(out); } inline ::std::ostream &operator<<(::std::ostream &out, const to_base64 &wrapper) { - return out << wrapper.as_string(); + return wrapper.output(out); } /// \brief Hexadecimal decoder which satisfy \ref SliceTranscoder concept. diff --git a/src/mdbx.c++ b/src/mdbx.c++ index cb2d9abc..61d293a7 100644 --- a/src/mdbx.c++ +++ b/src/mdbx.c++ @@ -14,6 +14,7 @@ #include "defs.h" #include "internals.h" +#include #include #include // for isxdigit(), etc #include @@ -559,6 +560,28 @@ char *to_hex::write_bytes(char *__restrict dest, size_t dest_size) const { return dest; } +::std::ostream &to_hex::output(::std::ostream &out) const { + if (MDBX_LIKELY(!is_empty())) + MDBX_CXX20_LIKELY { + ::std::ostream::sentry sentry(out); + auto src = source.byte_ptr(); + const char alphabase = (uppercase ? 'A' : 'a') - 10; + unsigned width = 0; + for (const auto end = source.end_byte_ptr(); src != end; ++src) { + if (wrap_width && width >= wrap_width) { + out << ::std::endl; + width = 0; + } + const int8_t hi = *src >> 4; + const int8_t lo = *src & 15; + out.put(char(alphabase + hi + (((hi - 10) >> 7) & -7))); + out.put(char(alphabase + lo + (((lo - 10) >> 7) & -7))); + width += 2; + } + } + return out; +} + char *from_hex::write_bytes(char *__restrict dest, size_t dest_size) const { if (MDBX_UNLIKELY(source.length() % 2 && !ignore_spaces)) MDBX_CXX20_UNLIKELY throw std::domain_error( @@ -719,6 +742,65 @@ char *to_base58::write_bytes(char *__restrict dest, size_t dest_size) const { return dest; } +::std::ostream &to_base58::output(::std::ostream &out) const { + if (MDBX_LIKELY(!is_empty())) + MDBX_CXX20_LIKELY { + ::std::ostream::sentry sentry(out); + auto src = source.byte_ptr(); + size_t left = source.length(); + unsigned width = 0; + std::array buf; + + while (MDBX_LIKELY(left > 7)) { + uint64_t v; + std::memcpy(&v, src, 8); + src += 8; +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + v = bswap64(v); +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#else +#error "FIXME: Unsupported byte order" +#endif /* __BYTE_ORDER__ */ + buf[10] = b58_8to11(v); + buf[9] = b58_8to11(v); + buf[8] = b58_8to11(v); + buf[7] = b58_8to11(v); + buf[6] = b58_8to11(v); + buf[5] = b58_8to11(v); + buf[4] = b58_8to11(v); + buf[3] = b58_8to11(v); + buf[2] = b58_8to11(v); + buf[1] = b58_8to11(v); + buf[0] = b58_8to11(v); + assert(v == 0); + out.write(buf.begin(), 11); + left -= 8; + if (wrap_width && (width += 11) >= wrap_width && left) { + out << ::std::endl; + width = 0; + } + } + + if (left) { + uint64_t v = 0; + unsigned parrots = 31; + do { + v = (v << 8) + *src++; + parrots += 43; + } while (--left); + + auto ptr = buf.end(); + do { + *--ptr = b58_8to11(v); + parrots -= 32; + } while (parrots > 31); + assert(v == 0); + out.write(&*ptr, buf.end() - ptr); + } + } + return out; +} + const signed char b58_map[256] = { // 1 2 3 4 5 6 7 8 9 a b c d e f OO, IL, IL, IL, IL, IL, IL, IL, IL, SP, SP, SP, SP, SP, IL, IL, // 00 @@ -890,6 +972,43 @@ char *to_base64::write_bytes(char *__restrict dest, size_t dest_size) const { } } +::std::ostream &to_base64::output(::std::ostream &out) const { + if (MDBX_LIKELY(!is_empty())) + MDBX_CXX20_LIKELY { + ::std::ostream::sentry sentry(out); + auto src = source.byte_ptr(); + size_t left = source.length(); + unsigned width = 0; + std::array buf; + + while (true) { + switch (left) { + default: + MDBX_CXX20_LIKELY left -= 3; + b64_3to4(src[0], src[1], src[2], buf.begin()); + src += 3; + out.write(buf.begin(), 4); + if (wrap_width && (width += 4) >= wrap_width && left) { + out << ::std::endl; + width = 0; + } + continue; + case 2: + b64_3to4(src[0], src[1], 0, buf.begin()); + buf[3] = '='; + return out.write(buf.begin(), 4); + case 1: + b64_3to4(src[0], 0, 0, buf.begin()); + buf[2] = buf[3] = '='; + return out.write(buf.begin(), 4); + case 0: + return out; + } + } + } + return out; +} + static const signed char b64_map[256] = { // 1 2 3 4 5 6 7 8 9 a b c d e f OO, IL, IL, IL, IL, IL, IL, IL, IL, SP, SP, SP, SP, SP, IL, IL, // 00