From 1479a030d77ed8a28b89d468f45039308e6f6cb8 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Wed, 13 May 2020 14:47:01 -0300 Subject: [PATCH] Add it pair versions of {to,from}_{hex,base32z,base64} Previously you could only generate a string from a string_view, or could manage the string yourself and pass input iterators plus an output iterator. This commit adds an intermediate version that creates a string from a pair of input iterators. --- lokimq/base32z.h | 33 ++++++++++++++++++++++----------- lokimq/base64.h | 33 ++++++++++++++++++++++----------- lokimq/hex.h | 33 ++++++++++++++++++++++----------- tests/test_encoding.cpp | 20 ++++++++++++++++++++ 4 files changed, 86 insertions(+), 33 deletions(-) diff --git a/lokimq/base32z.h b/lokimq/base32z.h index e207a06..03e0c1c 100644 --- a/lokimq/base32z.h +++ b/lokimq/base32z.h @@ -100,14 +100,19 @@ void to_base32z(InputIt begin, InputIt end, OutputIt out) { *out++ = detail::b32z_lut.to_b32z(r << (5 - bits)); } -/// Creates a base32z string from an iterable, std::string-like object -template -std::string to_base32z(std::basic_string_view s) { +/// Creates a base32z string from an iterator pair of a byte sequence. +template +std::string to_base32z(It begin, It end) { std::string base32z; - base32z.reserve((s.size()*8 + 4) / 5); // == bytes*8/5, rounded up. - to_base32z(s.begin(), s.end(), std::back_inserter(base32z)); + if constexpr (std::is_base_of_v::iterator_category>) + base32z.reserve((std::distance(begin, end)*8 + 4) / 5); // == bytes*8/5, rounded up. + to_base32z(begin, end, std::back_inserter(base32z)); return base32z; } + +/// Creates a base32z string from an iterable, std::string-like object +template +std::string to_base32z(std::basic_string_view s) { return to_base32z(s.begin(), s.end()); } inline std::string to_base32z(std::string_view s) { return to_base32z<>(s); } /// Returns true if all elements in the range are base32z characters @@ -177,15 +182,21 @@ void from_base32z(InputIt begin, InputIt end, OutputIt out) { // character you added isn't there by not doing anything here. } +/// Convert a base32z sequence into a std::string of bytes. Undefined behaviour if any characters +/// are not valid (case-insensitive) base32z characters. +template +std::string from_base32z(It begin, It end) { + std::string bytes; + if constexpr (std::is_base_of_v::iterator_category>) + bytes.reserve((std::distance(begin, end)*5 + 7) / 8); // == chars*5/8, rounded up. + from_base32z(begin, end, std::back_inserter(bytes)); + return bytes; +} + /// Converts base32z digits from a std::string-like object into a std::string of bytes. Undefined /// behaviour if any characters are not valid (case-insensitive) base32z characters. template -std::string from_base32z(std::basic_string_view s) { - std::string bytes; - bytes.reserve((s.size()*5 + 7) / 8); // == chars*5/8, rounded up. - from_base32z(s.begin(), s.end(), std::back_inserter(bytes)); - return bytes; -} +std::string from_base32z(std::basic_string_view s) { return from_base32z(s.begin(), s.end()); } inline std::string from_base32z(std::string_view s) { return from_base32z<>(s); } } diff --git a/lokimq/base64.h b/lokimq/base64.h index 2b56ad3..91d126e 100644 --- a/lokimq/base64.h +++ b/lokimq/base64.h @@ -116,14 +116,19 @@ void to_base64(InputIt begin, InputIt end, OutputIt out) { } } -/// Creates a base64 string from an iterable, std::string-like object -template -std::string to_base64(std::basic_string_view s) { +/// Creates and returns a base64 string from an iterator pair of a character sequence +template +std::string to_base64(It begin, It end) { std::string base64; - base64.reserve((s.size() + 2) / 3 * 4); - to_base64(s.begin(), s.end(), std::back_inserter(base64)); + if constexpr (std::is_base_of_v::iterator_category>) + base64.reserve((std::distance(begin, end) + 2) / 3 * 4); // bytes*4/3, rounded up to the next multiple of 4 + to_base64(begin, end, std::back_inserter(base64)); return base64; } + +/// Creates a base64 string from an iterable, std::string-like object +template +std::string to_base64(std::basic_string_view s) { return to_base64(s.begin(), s.end()); } inline std::string to_base64(std::string_view s) { return to_base64<>(s); } /// Returns true if the range is a base64 encoded value; we allow (but do not require) '=' padding, @@ -193,15 +198,21 @@ void from_base64(InputIt begin, InputIt end, OutputIt out) { // character here instead of 5). } +/// Converts base64 digits from a iterator pair of characters into a std::string of bytes. +/// Undefined behaviour if any characters are not valid base64 characters. +template +std::string from_base64(It begin, It end) { + std::string bytes; + if constexpr (std::is_base_of_v::iterator_category>) + bytes.reserve(std::distance(begin, end)*6 / 8); // each digit carries 6 bits; this may overallocate by 1-2 bytes due to padding + from_base64(begin, end, std::back_inserter(bytes)); + return bytes; +} + /// Converts base64 digits from a std::string-like object into a std::string of bytes. Undefined /// behaviour if any characters are not valid base64 characters. template -std::string from_base64(std::basic_string_view s) { - std::string bytes; - bytes.reserve(s.size()*6 / 8); - from_base64(s.begin(), s.end(), std::back_inserter(bytes)); - return bytes; -} +std::string from_base64(std::basic_string_view s) { return from_base64(s.begin(), s.end()); } inline std::string from_base64(std::string_view s) { return from_base64<>(s); } } diff --git a/lokimq/hex.h b/lokimq/hex.h index e6c4928..a3ce862 100644 --- a/lokimq/hex.h +++ b/lokimq/hex.h @@ -71,14 +71,19 @@ void to_hex(InputIt begin, InputIt end, OutputIt out) { } } -/// Creates a hex string from an iterable, std::string-like object -template -std::string to_hex(std::basic_string_view s) { +/// Creates a string of hex digits from a character sequence iterator pair +template +std::string to_hex(It begin, It end) { std::string hex; - hex.reserve(s.size() * 2); - to_hex(s.begin(), s.end(), std::back_inserter(hex)); + if constexpr (std::is_base_of_v::iterator_category>) + hex.reserve(2 * std::distance(begin, end)); + to_hex(begin, end, std::back_inserter(hex)); return hex; } + +/// Creates a hex string from an iterable, std::string-like object +template +std::string to_hex(std::basic_string_view s) { return to_hex(s.begin(), s.end()); } inline std::string to_hex(std::string_view s) { return to_hex<>(s); } /// Returns true if all elements in the range are hex characters @@ -119,15 +124,21 @@ void from_hex(InputIt begin, InputIt end, OutputIt out) { } } +/// Converts a sequence of hex digits to a string of bytes and returns it. Undefined behaviour if +/// the input sequence is not an even-length sequence of [0-9a-fA-F] characters. +template +std::string from_hex(It begin, It end) { + std::string bytes; + if constexpr (std::is_base_of_v::iterator_category>) + bytes.reserve(std::distance(begin, end) / 2); + from_hex(begin, end, std::back_inserter(bytes)); + return bytes; +} + /// Converts hex digits from a std::string-like object into a std::string of bytes. Undefined /// behaviour if any characters are not in [0-9a-fA-F] or if the input sequence length is not even. template -std::string from_hex(std::basic_string_view s) { - std::string bytes; - bytes.reserve(s.size() / 2); - from_hex(s.begin(), s.end(), std::back_inserter(bytes)); - return bytes; -} +std::string from_hex(std::basic_string_view s) { return from_hex(s.begin(), s.end()); } inline std::string from_hex(std::string_view s) { return from_hex<>(s); } } diff --git a/tests/test_encoding.cpp b/tests/test_encoding.cpp index f6ad893..c2b3b7a 100644 --- a/tests/test_encoding.cpp +++ b/tests/test_encoding.cpp @@ -18,6 +18,8 @@ TEST_CASE("hex encoding/decoding", "[encoding][decoding][hex]") { lokimq::to_hex(chars.begin(), chars.end(), out.begin()); REQUIRE( out == expected ); + REQUIRE( lokimq::to_hex(chars.begin(), chars.end()) == "010a64fe" ); + REQUIRE( lokimq::from_hex("12345678ffEDbca9") == "\x12\x34\x56\x78\xff\xed\xbc\xa9"s ); REQUIRE( lokimq::is_hex("1234567890abcdefABCDEF1234567890abcdefABCDEF") ); @@ -28,6 +30,8 @@ TEST_CASE("hex encoding/decoding", "[encoding][decoding][hex]") { REQUIRE( lokimq::from_hex(pk_hex) == pk ); REQUIRE( lokimq::to_hex(pk) == pk_hex ); + REQUIRE( lokimq::from_hex(pk_hex.begin(), pk_hex.end()) == pk ); + std::vector bytes{{std::byte{0xff}, std::byte{0x42}, std::byte{0x12}, std::byte{0x34}}}; std::basic_string_view b{bytes.data(), bytes.size()}; REQUIRE( lokimq::to_hex(b) == "ff421234"s ); @@ -77,7 +81,15 @@ TEST_CASE("base32z encoding/decoding", "[encoding][decoding][base32z]") { REQUIRE( lokimq::to_base32z(lokimq::from_base32z("ybndrf4"s)) == "ybndrfa" ); REQUIRE( lokimq::to_base32z(pk) == pk_b32z ); + REQUIRE( lokimq::to_base32z(pk.begin(), pk.end()) == pk_b32z ); REQUIRE( lokimq::from_base32z(pk_b32z) == pk ); + REQUIRE( lokimq::from_base32z(pk_b32z.begin(), pk_b32z.end()) == pk ); + + std::string pk_b32z_again, pk_again; + lokimq::to_base32z(pk.begin(), pk.end(), std::back_inserter(pk_b32z_again)); + lokimq::from_base32z(pk_b32z.begin(), pk_b32z.end(), std::back_inserter(pk_again)); + REQUIRE( pk_b32z_again == pk_b32z ); + REQUIRE( pk_again == pk ); std::vector bytes{{std::byte{0}, std::byte{255}}}; std::basic_string_view b{bytes.data(), bytes.size()}; @@ -159,7 +171,15 @@ TEST_CASE("base64 encoding/decoding", "[encoding][decoding][base64]") { "any carnal pleasure."); REQUIRE( lokimq::to_base64(pk) == pk_b64 ); + REQUIRE( lokimq::to_base64(pk.begin(), pk.end()) == pk_b64 ); REQUIRE( lokimq::from_base64(pk_b64) == pk ); + REQUIRE( lokimq::from_base64(pk_b64.begin(), pk_b64.end()) == pk ); + + std::string pk_b64_again, pk_again; + lokimq::to_base64(pk.begin(), pk.end(), std::back_inserter(pk_b64_again)); + lokimq::from_base64(pk_b64.begin(), pk_b64.end(), std::back_inserter(pk_again)); + REQUIRE( pk_b64_again == pk_b64 ); + REQUIRE( pk_again == pk ); std::vector bytes{{std::byte{0}, std::byte{255}}}; std::basic_string_view b{bytes.data(), bytes.size()};