base32Encode now takes std::span<std::byte>, with a base32EncodeStr convenience wrapper which takes std::string_view. Co-authored-by: Qyriad <qyriad@qyriad.me> Change-Id: I6a6a6964f799dc84ecbfb55c7ca03a064cff71d9
179 lines
5.6 KiB
C++
179 lines
5.6 KiB
C++
#include "lix/libutil/references.hh"
|
|
#include "lix/libutil/strings.hh"
|
|
#include "lix/libutil/hash.hh"
|
|
#include "lix/libutil/logging.hh"
|
|
|
|
#include <cstdlib>
|
|
#include <mutex>
|
|
#include <algorithm>
|
|
|
|
|
|
namespace nix {
|
|
|
|
|
|
static size_t refLength = 32; /* characters */
|
|
|
|
|
|
static void search(
|
|
std::string_view s,
|
|
StringSet & hashes,
|
|
StringSet & seen)
|
|
{
|
|
static std::once_flag initialised;
|
|
static bool isBase32[256];
|
|
std::call_once(initialised, [](){
|
|
for (unsigned int i = 0; i < 256; ++i) isBase32[i] = false;
|
|
for (unsigned int i = 0; i < base32Chars.size(); ++i)
|
|
isBase32[(unsigned char) base32Chars[i]] = true;
|
|
});
|
|
|
|
for (size_t i = 0; i + refLength <= s.size(); ) {
|
|
int j;
|
|
bool match = true;
|
|
for (j = refLength - 1; j >= 0; --j)
|
|
if (!isBase32[(unsigned char) s[i + j]]) {
|
|
i += j + 1;
|
|
match = false;
|
|
break;
|
|
}
|
|
if (!match) continue;
|
|
std::string ref(s.substr(i, refLength));
|
|
if (hashes.erase(ref)) {
|
|
debug("found reference to '%1%' at offset '%2%'", ref, i);
|
|
seen.insert(ref);
|
|
}
|
|
++i;
|
|
}
|
|
}
|
|
|
|
|
|
void RefScanSink::operator () (std::string_view data)
|
|
{
|
|
/* It's possible that a reference spans the previous and current
|
|
fragment, so search in the concatenation of the tail of the
|
|
previous fragment and the start of the current fragment. */
|
|
auto s = tail;
|
|
auto tailLen = std::min(data.size(), refLength);
|
|
s.append(data.data(), tailLen); // NOLINT(bugprone-suspicious-stringview-data-usage)
|
|
search(s, hashes, seen);
|
|
|
|
search(data, hashes, seen);
|
|
|
|
auto rest = refLength - tailLen;
|
|
if (rest < tail.size())
|
|
tail = tail.substr(tail.size() - rest);
|
|
tail.append(data.data() + data.size() - tailLen, tailLen);
|
|
}
|
|
|
|
|
|
RewritingSource::RewritingSource(const std::string & from, const std::string & to, Source & inner)
|
|
: RewritingSource({{from, to}}, inner)
|
|
{
|
|
}
|
|
|
|
RewritingSource::RewritingSource(StringMap rewrites, Source & inner)
|
|
: RewritingSource(may_change_size, std::move(rewrites), inner)
|
|
{
|
|
for (auto & [from, to] : this->rewrites) {
|
|
assert(from.size() == to.size());
|
|
}
|
|
}
|
|
|
|
RewritingSource::RewritingSource(may_change_size_t, StringMap rewrites, Source & inner)
|
|
: maxRewriteSize([&, result = size_t(0)]() mutable {
|
|
for (auto & [k, v] : rewrites) {
|
|
result = std::max(result, k.size());
|
|
}
|
|
return result;
|
|
}())
|
|
, initials([&]() -> std::string {
|
|
std::string initials;
|
|
for (const auto & [k, v] : rewrites) {
|
|
assert(!k.empty());
|
|
initials.push_back(k[0]);
|
|
}
|
|
std::ranges::sort(initials);
|
|
auto [firstDupe, _end] = std::ranges::unique(initials);
|
|
return {initials.begin(), firstDupe};
|
|
}())
|
|
, rewrites(std::move(rewrites))
|
|
, inner(&inner)
|
|
{
|
|
}
|
|
|
|
size_t RewritingSource::read(char * data, size_t len)
|
|
{
|
|
if (rewrites.empty()) {
|
|
return inner->read(data, len);
|
|
}
|
|
|
|
if (unreturned.empty()) {
|
|
// always make sure to have at least *two* full rewrites in the buffer,
|
|
// otherwise we may end up incorrectly rewriting if the replacement map
|
|
// contains keys that are proper infixes of other keys in the map. take
|
|
// for example the set { ab -> cc, babb -> bbbb } on the input babb. if
|
|
// we feed the input bytewise without additional windowing we will miss
|
|
// the full babb match once the second b has been seen and bab has been
|
|
// rewritten to ccb, even though babb occurs first in the input string.
|
|
while (inner && buffered.size() < std::max(2 * maxRewriteSize, len)) {
|
|
try {
|
|
auto read = inner->read(data, std::min(2 * maxRewriteSize, len));
|
|
buffered.append(data, read);
|
|
} catch (EndOfFile &) {
|
|
inner = nullptr;
|
|
}
|
|
}
|
|
|
|
if (buffered.empty() && !inner) {
|
|
throw EndOfFile("rewritten source exhausted");
|
|
}
|
|
|
|
const size_t reserved = inner ? maxRewriteSize : 0;
|
|
size_t j = 0;
|
|
while ((j = buffered.find_first_of(initials, j)) < buffered.size() - reserved) {
|
|
size_t skip = 1;
|
|
for (const auto & [from, to] : rewrites) {
|
|
if (buffered.compare(j, from.size(), from) == 0) {
|
|
buffered.replace(j, from.size(), to);
|
|
skip = to.size();
|
|
break;
|
|
}
|
|
}
|
|
j += skip;
|
|
}
|
|
|
|
rewritten = std::move(buffered);
|
|
buffered = rewritten.substr(rewritten.size() - reserved);
|
|
unreturned = rewritten;
|
|
unreturned.remove_suffix(reserved);
|
|
}
|
|
|
|
len = std::min(len, unreturned.size());
|
|
memcpy(data, unreturned.data(), len);
|
|
unreturned.remove_prefix(len);
|
|
return len;
|
|
}
|
|
|
|
HashResult computeHashModulo(HashType ht, const std::string & modulus, Source & source)
|
|
{
|
|
HashSink hashSink(ht);
|
|
LengthSink lengthSink;
|
|
RewritingSource rewritingSource(modulus, std::string(modulus.size(), 0), source);
|
|
|
|
TeeSink tee{hashSink, lengthSink};
|
|
rewritingSource.drainInto(tee);
|
|
|
|
/* Hash the positions of the self-references. This ensures that a
|
|
NAR with self-references and a NAR with some of the
|
|
self-references already zeroed out do not produce a hash
|
|
collision. FIXME: proof. */
|
|
// NOTE(horrors) RewritingSink didn't track any matches!
|
|
//for (auto & pos : rewritingSource.matches)
|
|
// hashSink(fmt("|%d", pos));
|
|
|
|
auto h = hashSink.finish();
|
|
return {h.first, lengthSink.length};
|
|
}
|
|
|
|
}
|