forked from dlang/cdcdb
135 lines
4.4 KiB
D
135 lines
4.4 KiB
D
/// Модуль базовых структур и алгоритмов CDC (content-defined chunking).
|
||
module cdcdb.core;
|
||
|
||
import std.digest.sha : SHA256, digest;
|
||
|
||
/// Описание чанка данных.
|
||
///
|
||
/// Поля:
|
||
/// - `index` — порядковый номер чанка, начиная с 1.
|
||
/// - `offset` — смещение чанка в исходном буфере (в байтах).
|
||
/// - `size` — размер чанка (в байтах).
|
||
/// - `sha256` — SHA-256 содержимого (сырые 32 байта).
|
||
struct Chunk
|
||
{
|
||
size_t index;
|
||
size_t offset;
|
||
size_t size;
|
||
immutable(ubyte)[32] sha256;
|
||
}
|
||
|
||
/// Change Data Capture (CDC) — алгоритм нарезки потока на чанки по содержимому.
|
||
///
|
||
/// Класс реализует скользящее шифрование (rolling hash) с двумя масками:
|
||
/// строгой (`_maskS`) до «нормального» размера и более слабой (`_maskL`) до «максимального».
|
||
final class CDC
|
||
{
|
||
private:
|
||
size_t _minSize, _normalSize, _maxSize;
|
||
ulong _maskS, _maskL;
|
||
// Таблица случайных значений Gear (должна быть сгенерирована отдельно в "gear.d")
|
||
mixin(import("gear.d"));
|
||
|
||
/// Вычисляет длину следующего чанка, начиная с начала `src`.
|
||
///
|
||
/// Параметры:
|
||
/// - `src` — оставшийся участок данных.
|
||
///
|
||
/// Возвращает: длину чанка в байтах.
|
||
///
|
||
/// Детали:
|
||
/// - Если данных меньше либо равно минимальному размеру — возвращает их длину.
|
||
/// - Сначала ищется граница по строгой маске до `_normalSize`, затем по слабой до `_maxSize`.
|
||
size_t cut(const(ubyte)[] src) pure nothrow @safe @nogc
|
||
{
|
||
size_t size = src.length;
|
||
if (size == 0)
|
||
return 0;
|
||
if (size <= _minSize)
|
||
return size;
|
||
|
||
if (size > _maxSize)
|
||
size = _maxSize;
|
||
auto normalSize = _normalSize;
|
||
if (size < normalSize)
|
||
normalSize = size;
|
||
|
||
ulong fingerprint = 0;
|
||
size_t index;
|
||
|
||
// Инициализация без проверки на разрез
|
||
while (index < _minSize)
|
||
{
|
||
fingerprint = (fingerprint << 1) + _gear[src[index]];
|
||
++index;
|
||
}
|
||
// Строгая маска
|
||
while (index < normalSize)
|
||
{
|
||
fingerprint = (fingerprint << 1) + _gear[src[index]];
|
||
if ((fingerprint & _maskS) == 0)
|
||
return index;
|
||
++index;
|
||
}
|
||
// Слабая маска
|
||
while (index < size)
|
||
{
|
||
fingerprint = (fingerprint << 1) + _gear[src[index]];
|
||
if ((fingerprint & _maskL) == 0)
|
||
return index;
|
||
++index;
|
||
}
|
||
return size;
|
||
}
|
||
|
||
public:
|
||
/// Создаёт экземпляр CDC.
|
||
///
|
||
/// Параметры:
|
||
/// - `minSize` — минимальный размер чанка.
|
||
/// - `normalSize` — целевой (нормальный) размер чанка.
|
||
/// - `maxSize` — максимальный размер чанка.
|
||
/// - `maskS` — строгая маска (для поиска границы до `normalSize`).
|
||
/// - `maskL` — слабая маска (для поиска границы до `maxSize`).
|
||
///
|
||
/// Замечания:
|
||
/// - Требуется `0 < minSize < normalSize < maxSize`.
|
||
this(size_t minSize, size_t normalSize, size_t maxSize, ulong maskS, ulong maskL) @safe @nogc nothrow
|
||
{
|
||
assert(minSize > 0 && minSize < normalSize && normalSize < maxSize,
|
||
"Некорректные размеры: требуется 0 < min < normal < max");
|
||
_minSize = minSize;
|
||
_normalSize = normalSize;
|
||
_maxSize = maxSize;
|
||
_maskS = maskS;
|
||
_maskL = maskL;
|
||
}
|
||
|
||
/// Разбивает буфер `data` на последовательность чанков.
|
||
///
|
||
/// Параметры:
|
||
/// - `data` — исходные байты.
|
||
///
|
||
/// Возвращает: массив `Chunk` в порядке следования.
|
||
Chunk[] split(const(ubyte)[] data) @safe nothrow
|
||
{
|
||
Chunk[] chunks;
|
||
if (data.length == 0)
|
||
return chunks;
|
||
chunks.reserve(data.length / _normalSize);
|
||
size_t offset = 0;
|
||
size_t index = 1;
|
||
|
||
while (offset < data.length)
|
||
{
|
||
auto size = cut(data[offset .. $]);
|
||
auto bytes = data[offset .. offset + size];
|
||
ubyte[32] hash = digest!SHA256(bytes);
|
||
chunks ~= Chunk(index, offset, size, hash);
|
||
|
||
offset += size;
|
||
++index;
|
||
}
|
||
return chunks;
|
||
}
|
||
}
|