diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -623,4 +623,61 @@ BOOST_CHECK(!ParseFixedPoint("1.", 8, &amount)); } +template +static void CheckConvertBits(const std::vector &in, + const std::vector &expected) { + std::vector outpad; + bool ret = ConvertBits(outpad, in.begin(), in.end()); + BOOST_CHECK(ret); + BOOST_CHECK(outpad == expected); + + const bool dopad = (in.size() * F) % T; + std::vector outnopad; + ret = ConvertBits(outnopad, in.begin(), in.end()); + BOOST_CHECK(ret != dopad); + + if (dopad) { + // We should have skipped the last digit. + outnopad.push_back(expected.back()); + } + + BOOST_CHECK(outnopad == expected); + + // Check the other way around. + std::vector orignopad; + ret = ConvertBits(orignopad, expected.begin(), expected.end()); + BOOST_CHECK(ret == !((expected.size() * T) % F)); + BOOST_CHECK(orignopad == in); + + // Check with padding. We may get an extra 0 in that case. + std::vector origpad; + ret = ConvertBits(origpad, expected.begin(), expected.end()); + BOOST_CHECK(ret); + + if (dopad) { + BOOST_CHECK_EQUAL(origpad.back(), 0); + origpad.pop_back(); + } + + BOOST_CHECK(origpad == in); +} + +BOOST_AUTO_TEST_CASE(test_ConvertBits) { + CheckConvertBits<8, 5>({}, {}); + CheckConvertBits<8, 5>({0xff}, {0x1f, 0x1c}); + CheckConvertBits<8, 5>({0xff, 0xff}, {0x1f, 0x1f, 0x1f, 0x10}); + CheckConvertBits<8, 5>({0xff, 0xff, 0xff}, {0x1f, 0x1f, 0x1f, 0x1f, 0x1e}); + CheckConvertBits<8, 5>({0xff, 0xff, 0xff, 0xff}, + {0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x18}); + CheckConvertBits<8, 5>({0xff, 0xff, 0xff, 0xff, 0xff}, + {0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f}); + CheckConvertBits<8, 5>({0xff, 0xff, 0xff, 0xff, 0xff}, + {0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f}); + CheckConvertBits<8, 5>({0xff, 0xff, 0xff, 0xff, 0xff}, + {0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f}); + CheckConvertBits<8, 5>({0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, + {0x00, 0x04, 0x11, 0x14, 0x0a, 0x19, 0x1c, 0x09, + 0x15, 0x0f, 0x06, 0x1e, 0x1e}); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/utilstrencodings.h b/src/utilstrencodings.h --- a/src/utilstrencodings.h +++ b/src/utilstrencodings.h @@ -142,4 +142,39 @@ */ bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out); +/** + * Convert from one power-of-2 number base to another. + * + * If padding is enabled, this always return true. If not, then it returns true + * of all the bits of the input are encoded in the output. + */ +template +bool ConvertBits(O &out, I it, I end) { + size_t acc = 0; + size_t bits = 0; + constexpr size_t maxv = (1 << tobits) - 1; + constexpr size_t max_acc = (1 << (frombits + tobits - 1)) - 1; + while (it != end) { + acc = ((acc << frombits) | *it) & max_acc; + bits += frombits; + while (bits >= tobits) { + bits -= tobits; + out.push_back((acc >> bits) & maxv); + } + ++it; + } + + // We have remaining bits to encode but do not pad. + if (!pad && bits) { + return false; + } + + // We have remaining bits to encode so we do pad. + if (pad && bits) { + out.push_back((acc << (tobits - bits)) & maxv); + } + + return true; +} + #endif // BITCOIN_UTILSTRENCODINGS_H