Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/libfetchers/git-utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "nix/fetchers/git-lfs-fetch.hh"
#include "nix/fetchers/cache.hh"
#include "nix/fetchers/fetch-settings.hh"
#include "nix/util/base-n.hh"
#include "nix/util/finally.hh"
#include "nix/util/processes.hh"
#include "nix/util/signals.hh"
Expand Down Expand Up @@ -608,7 +609,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
// Calculate sha256 fingerprint from public key and escape the regex symbol '+' to match the key literally
std::string keyDecoded;
try {
keyDecoded = base64Decode(k.key);
keyDecoded = base64::decode(k.key);
} catch (Error & e) {
e.addTrace({}, "while decoding public key '%s' used for git signature", k.key);
}
Expand Down
3 changes: 2 additions & 1 deletion src/libstore/machines.cc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "nix/util/base-n.hh"
#include "nix/store/machines.hh"
#include "nix/store/globals.hh"
#include "nix/store/store-open.hh"
Expand Down Expand Up @@ -158,7 +159,7 @@ static Machine parseBuilderLine(const StringSet & defaultSystems, const std::str
auto ensureBase64 = [&](size_t fieldIndex) {
const auto & str = tokens[fieldIndex];
try {
base64Decode(str);
base64::decode(str);
} catch (FormatError & e) {
e.addTrace({}, "while parsing machine specification at a column #%lu in a row: '%s'", fieldIndex, line);
throw;
Expand Down
3 changes: 2 additions & 1 deletion src/libstore/ssh.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
#include "nix/util/environment-variables.hh"
#include "nix/util/util.hh"
#include "nix/util/exec.hh"
#include "nix/util/base-n.hh"

namespace nix {

static std::string parsePublicHostKey(std::string_view host, std::string_view sshPublicHostKey)
{
try {
return base64Decode(sshPublicHostKey);
return base64::decode(sshPublicHostKey);
} catch (Error & e) {
e.addTrace({}, "while decoding ssh public host key for host '%s'", host);
throw;
Expand Down
68 changes: 68 additions & 0 deletions src/libutil-tests/base-n.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include <gtest/gtest.h>
#include <numeric>

#include "nix/util/base-n.hh"
#include "nix/util/error.hh"

namespace nix {

static const std::span<const std::byte> stringToByteSpan(const std::string_view s)
{
return {(const std::byte *) s.data(), s.size()};
}

/* ----------------------------------------------------------------------------
* base64::encode
* --------------------------------------------------------------------------*/

TEST(base64Encode, emptyString)
{
ASSERT_EQ(base64::encode(stringToByteSpan("")), "");
}

TEST(base64Encode, encodesAString)
{
ASSERT_EQ(base64::encode(stringToByteSpan("quod erat demonstrandum")), "cXVvZCBlcmF0IGRlbW9uc3RyYW5kdW0=");
}

TEST(base64Encode, encodeAndDecode)
{
auto s = "quod erat demonstrandum";
auto encoded = base64::encode(stringToByteSpan(s));
auto decoded = base64::decode(encoded);

ASSERT_EQ(decoded, s);
}

TEST(base64Encode, encodeAndDecodeNonPrintable)
{
char s[256];
std::iota(std::rbegin(s), std::rend(s), 0);

auto encoded = base64::encode(std::as_bytes(std::span<const char>{std::string_view{s}}));
auto decoded = base64::decode(encoded);

EXPECT_EQ(decoded.length(), 255u);
ASSERT_EQ(decoded, s);
}

/* ----------------------------------------------------------------------------
* base64::decode
* --------------------------------------------------------------------------*/

TEST(base64Decode, emptyString)
{
ASSERT_EQ(base64::decode(""), "");
}

TEST(base64Decode, decodeAString)
{
ASSERT_EQ(base64::decode("cXVvZCBlcmF0IGRlbW9uc3RyYW5kdW0="), "quod erat demonstrandum");
}

TEST(base64Decode, decodeThrowsOnInvalidChar)
{
ASSERT_THROW(base64::decode("cXVvZCBlcm_0IGRlbW9uc3RyYW5kdW0="), Error);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add a test where there are extra trailing padding = characters at the end? Both snix/lix ran into the fact that parser isn't entirely correct in that regard and nixpkgs relies on some of the broken behaviour.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

} // namespace nix
1 change: 1 addition & 0 deletions src/libutil-tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ subdir('nix-meson-build-support/common')

sources = files(
'args.cc',
'base-n.cc',
'canon-path.cc',
'checked-arithmetic.cc',
'chunked-vector.cc',
Expand Down
55 changes: 1 addition & 54 deletions src/libutil-tests/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "nix/util/file-system.hh"
#include "nix/util/terminal.hh"
#include "nix/util/strings.hh"
#include "nix/util/base-n.hh"

#include <limits.h>
#include <gtest/gtest.h>
Expand Down Expand Up @@ -48,60 +49,6 @@ TEST(hasSuffix, trivialCase)
ASSERT_TRUE(hasSuffix("foobar", "bar"));
}

/* ----------------------------------------------------------------------------
* base64Encode
* --------------------------------------------------------------------------*/

TEST(base64Encode, emptyString)
{
ASSERT_EQ(base64Encode(""), "");
}

TEST(base64Encode, encodesAString)
{
ASSERT_EQ(base64Encode("quod erat demonstrandum"), "cXVvZCBlcmF0IGRlbW9uc3RyYW5kdW0=");
}

TEST(base64Encode, encodeAndDecode)
{
auto s = "quod erat demonstrandum";
auto encoded = base64Encode(s);
auto decoded = base64Decode(encoded);

ASSERT_EQ(decoded, s);
}

TEST(base64Encode, encodeAndDecodeNonPrintable)
{
char s[256];
std::iota(std::rbegin(s), std::rend(s), 0);

auto encoded = base64Encode(s);
auto decoded = base64Decode(encoded);

EXPECT_EQ(decoded.length(), 255u);
ASSERT_EQ(decoded, s);
}

/* ----------------------------------------------------------------------------
* base64Decode
* --------------------------------------------------------------------------*/

TEST(base64Decode, emptyString)
{
ASSERT_EQ(base64Decode(""), "");
}

TEST(base64Decode, decodeAString)
{
ASSERT_EQ(base64Decode("cXVvZCBlcmF0IGRlbW9uc3RyYW5kdW0="), "quod erat demonstrandum");
}

TEST(base64Decode, decodeThrowsOnInvalidChar)
{
ASSERT_THROW(base64Decode("cXVvZCBlcm_0IGRlbW9uc3RyYW5kdW0="), Error);
}

/* ----------------------------------------------------------------------------
* getLine
* --------------------------------------------------------------------------*/
Expand Down
114 changes: 114 additions & 0 deletions src/libutil/base-n.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#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
43 changes: 37 additions & 6 deletions src/libutil/base-nix-32.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <cassert>

#include "nix/util/base-nix-32.hh"
#include "nix/util/util.hh"

namespace nix {

Expand All @@ -16,12 +17,12 @@ constexpr const std::array<unsigned char, 256> BaseNix32::reverseMap = [] {
return map;
}();

std::string BaseNix32::encode(std::span<const uint8_t> originalData)
std::string BaseNix32::encode(std::span<const std::byte> bs)
{
if (originalData.size() == 0)
if (bs.size() == 0)
return {};

size_t len = encodedLength(originalData.size());
size_t len = encodedLength(bs.size());
assert(len);

std::string s;
Expand All @@ -31,12 +32,42 @@ std::string BaseNix32::encode(std::span<const uint8_t> originalData)
unsigned int b = n * 5;
unsigned int i = b / 8;
unsigned int j = b % 8;
unsigned char c =
(originalData.data()[i] >> j) | (i >= originalData.size() - 1 ? 0 : originalData.data()[i + 1] << (8 - j));
s.push_back(characters[c & 0x1f]);
std::byte c = (bs.data()[i] >> j) | (i >= bs.size() - 1 ? std::byte{0} : bs.data()[i + 1] << (8 - j));
s.push_back(characters[uint8_t(c & std::byte{0x1f})]);
}

return s;
}

std::string BaseNix32::decode(std::string_view s)
{
std::string res;
res.reserve((s.size() * 5 + 7) / 8); // ceiling(size * 5/8)

for (unsigned int n = 0; n < s.size(); ++n) {
char c = s[s.size() - n - 1];
auto digit_opt = BaseNix32::lookupReverse(c);

if (!digit_opt)
throw FormatError("invalid character in Nix32 (Nix's Base32 variation) string: '%c'", c);

uint8_t digit = *digit_opt;

unsigned int b = n * 5;
unsigned int i = b / 8;
unsigned int j = b % 8;

// Ensure res has enough space
res.resize(i + 1);
res[i] |= digit << j;

if (digit >> (8 - j)) {
res.resize(i + 2);
res[i + 1] |= digit >> (8 - j);
}
}

return res;
}

} // namespace nix
Loading
Loading