1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-16 07:22:43 +01:00
nix/src/libutil/base-n.cc
John Ericson 991831227e Clean up Base* code
Make it separate from Hash, since other things can be base-encoded too.

This isn't really needed for Nix, but it makes the code easier to read
e.g. for someone reimplementing this stuff in a different language. (Of
course, Base16/Base64 should be gotten off-the-shelf, but now the hash
code, which is more bespoke, is less cluttered with the parts that would
be from some library.)

Many reimplementations of "Nix32" and our hash type already exist, so
this cleanup is coming years too late, but I say better late than never
/ it is always good to nudge the code in the direction of being a
"living spec".

Co-authored-by: Sergei Zimmerman <sergei@zimmerman.foo>
2025-08-06 11:51:44 -04:00

114 lines
2.9 KiB
C++

#include <string_view>
#include "nix/util/array-from-string-literal.hh"
#include "nix/util/util.hh"
#include "nix/util/base-n.hh"
using namespace std::literals;
namespace nix {
constexpr static const std::array<char, 16> base16Chars = "0123456789abcdef"_arrayNoNull;
std::string base16::encode(std::span<const std::byte> b)
{
std::string buf;
buf.reserve(b.size() * 2);
for (size_t i = 0; i < b.size(); i++) {
buf.push_back(base16Chars[(uint8_t) b.data()[i] >> 4]);
buf.push_back(base16Chars[(uint8_t) b.data()[i] & 0x0f]);
}
return buf;
}
std::string base16::decode(std::string_view s)
{
auto parseHexDigit = [&](char c) {
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
throw FormatError("invalid character in Base16 string: '%c'", c);
};
assert(s.size() % 2 == 0);
auto decodedSize = s.size() / 2;
std::string res;
res.reserve(decodedSize);
for (unsigned int i = 0; i < decodedSize; i++) {
res.push_back(parseHexDigit(s[i * 2]) << 4 | parseHexDigit(s[i * 2 + 1]));
}
return res;
}
constexpr static const std::array<char, 64> base64Chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"_arrayNoNull;
std::string base64::encode(std::span<const std::byte> s)
{
std::string res;
res.reserve((s.size() + 2) / 3 * 4);
int data = 0, nbits = 0;
for (std::byte c : s) {
data = data << 8 | (uint8_t) c;
nbits += 8;
while (nbits >= 6) {
nbits -= 6;
res.push_back(base64Chars[data >> nbits & 0x3f]);
}
}
if (nbits)
res.push_back(base64Chars[data << (6 - nbits) & 0x3f]);
while (res.size() % 4)
res.push_back('=');
return res;
}
std::string base64::decode(std::string_view s)
{
constexpr char npos = -1;
constexpr std::array<char, 256> base64DecodeChars = [&] {
std::array<char, 256> result{};
for (auto & c : result)
c = npos;
for (int i = 0; i < 64; i++)
result[base64Chars[i]] = i;
return result;
}();
std::string res;
// Some sequences are missing the padding consisting of up to two '='.
// vvv
res.reserve((s.size() + 2) / 4 * 3);
unsigned int d = 0, bits = 0;
for (char c : s) {
if (c == '=')
break;
if (c == '\n')
continue;
char digit = base64DecodeChars[(unsigned char) c];
if (digit == npos)
throw FormatError("invalid character in Base64 string: '%c'", c);
bits += 6;
d = d << 6 | digit;
if (bits >= 8) {
res.push_back(d >> (bits - 8) & 0xff);
bits -= 8;
}
}
return res;
}
} // namespace nix