Files
FastSync/src/shared/delta.c
T
TapTap 0afda6b094 feat(checksum): --checksum-choice/--cc and --checksum-seed for whole-file digest
Adds real algorithm selection (xxh64 default, plus md5 via OpenSSL EVP) and a
64-bit seed for the per-file whole-file digest used by the --incremental/
--checksum handshake and basis-dir content verification. The seed also feeds
the delta path's per-block xxHash32 strong checksum (low 32 bits) so an
explicit seed deterministically changes those digests too. Sender and receiver
hash identically: the algorithm id and seed cross the config wire frame and the
STATUS_CHECK handshake now carries a length-prefixed, bounded digest instead of
a fixed 64-bit value. Unsupported algorithm names are rejected at parse time
(never a silent no-op). PROTOCOL_VERSION bumped 2.9.0 -> 2.10.0; defaults
(xxh64, seed 0) preserve prior byte-for-byte behavior.
2026-09-07 17:42:47 +02:00

730 lines
23 KiB
C

#include "delta.h"
#include "log.h"
#include "protocol.h"
#include <stdint.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#define XXH_STATIC_LINKING_ONLY
#define XXH_IMPLEMENTATION
#include <xxhash.h>
/* Maximum number of blocks/instructions allowed from the wire to prevent OOM */
#define MAX_DELTA_BLOCKS (1024U * 1024U) /* 1M signature blocks */
#define MAX_DELTA_INSTRUCTIONS (1024U * 1024U) /* 1M delta instructions */
uint32_t delta_adler32(const void* data, uint32_t len) {
const uint8_t* p = (const uint8_t*)data;
uint32_t s1 = 1;
uint32_t s2 = 0;
for (uint32_t i = 0; i < len; i++) {
s1 = (s1 + p[i]) % DELTA_ADLER32_MODULUS;
s2 = (s2 + s1) % DELTA_ADLER32_MODULUS;
}
return (s2 << 16) | s1;
}
uint32_t delta_xxhash32(const void* data, uint32_t len) {
return XXH32(data, len, 0);
}
uint32_t delta_xxhash32_seeded(const void* data, uint32_t len, uint32_t seed) {
return XXH32(data, len, seed);
}
uint64_t delta_xxhash64(const void* data, size_t len) {
return XXH64(data, len, 0);
}
DeltaSignature* delta_signature_create(const void* old_file_data, uint64_t old_file_size,
uint32_t block_size) {
return delta_signature_create_seeded(old_file_data, old_file_size, block_size, 0);
}
DeltaSignature* delta_signature_create_seeded(const void* old_file_data, uint64_t old_file_size,
uint32_t block_size, uint32_t seed) {
if (old_file_data == NULL || old_file_size == 0 || block_size == 0)
return NULL;
if (old_file_size > DELTA_MAX_FILE_SIZE || block_size > DELTA_BLOCK_SIZE_MAX ||
old_file_size > UINT32_MAX * (uint64_t)block_size)
return NULL;
uint32_t block_count = (uint32_t)((old_file_size + block_size - 1) / block_size);
DeltaSignature* sig = protocol_alloc(sizeof(DeltaSignature));
if (!sig)
return NULL;
sig->file_size = old_file_size;
sig->block_size = block_size;
sig->block_count = block_count;
if (block_count == 0) {
free(sig);
return NULL;
}
sig->blocks = protocol_alloc((size_t)block_count * sizeof(DeltaBlockSig));
if (!sig->blocks) {
free(sig);
return NULL;
}
const uint8_t* data = (const uint8_t*)old_file_data;
for (uint32_t i = 0; i < block_count; i++) {
uint64_t offset = (uint64_t)i * block_size;
uint32_t len =
(uint32_t)((old_file_size - offset < block_size) ? (old_file_size - offset) : block_size);
sig->blocks[i].adler32 = delta_adler32(data + offset, len);
sig->blocks[i].xxhash = delta_xxhash32_seeded(data + offset, len, seed);
}
return sig;
}
Data* delta_signature_serialize(const DeltaSignature* sig) {
if (!sig)
return NULL;
uint64_t block_bytes = (uint64_t)sig->block_count * (sizeof(uint32_t) + sizeof(uint32_t));
uint64_t total = sizeof(uint64_t) + sizeof(uint32_t) + sizeof(uint32_t) + block_bytes;
if (block_bytes > UINT64_MAX - (sizeof(uint64_t) + sizeof(uint32_t) + sizeof(uint32_t)) ||
total > SIZE_MAX)
return NULL;
uint8_t* buf = protocol_alloc((size_t)total);
if (!buf)
return NULL;
size_t pos = 0;
memcpy(buf + pos, &sig->file_size, sizeof(uint64_t));
pos += sizeof(uint64_t);
memcpy(buf + pos, &sig->block_size, sizeof(uint32_t));
pos += sizeof(uint32_t);
memcpy(buf + pos, &sig->block_count, sizeof(uint32_t));
pos += sizeof(uint32_t);
for (uint32_t i = 0; i < sig->block_count; i++) {
memcpy(buf + pos, &sig->blocks[i].adler32, sizeof(uint32_t));
pos += sizeof(uint32_t);
memcpy(buf + pos, &sig->blocks[i].xxhash, sizeof(uint32_t));
pos += sizeof(uint32_t);
}
return data_create(buf, (size_t)total);
}
DeltaSignature* delta_signature_deserialize(const Data* data) {
if (!data || data->size < sizeof(uint64_t) + sizeof(uint32_t) + sizeof(uint32_t))
return NULL;
const uint8_t* buf = (const uint8_t*)data->data;
size_t pos = 0;
DeltaSignature* sig = protocol_alloc(sizeof(DeltaSignature));
if (!sig)
return NULL;
memcpy(&sig->file_size, buf + pos, sizeof(uint64_t));
pos += sizeof(uint64_t);
memcpy(&sig->block_size, buf + pos, sizeof(uint32_t));
pos += sizeof(uint32_t);
memcpy(&sig->block_count, buf + pos, sizeof(uint32_t));
pos += sizeof(uint32_t);
// Reject unreasonably large block counts to prevent OOM
if (sig->block_count > MAX_DELTA_BLOCKS) {
log_message(LOG_LEVEL_ERROR, "Delta signature block count %u exceeds maximum %u",
sig->block_count, MAX_DELTA_BLOCKS);
free(sig);
return NULL;
}
if (sig->block_size == 0 || sig->block_size > DELTA_BLOCK_SIZE_MAX ||
sig->file_size > DELTA_MAX_FILE_SIZE || sig->file_size == 0 ||
(sig->file_size + sig->block_size - 1) / sig->block_size != sig->block_count) {
free(sig);
return NULL;
}
uint64_t expected = sizeof(uint64_t) + sizeof(uint32_t) + sizeof(uint32_t) +
(uint64_t)sig->block_count * (sizeof(uint32_t) + sizeof(uint32_t));
if (data->size < expected) {
free(sig);
return NULL;
}
uint64_t blocks_size = (uint64_t)sig->block_count * sizeof(DeltaBlockSig);
if (blocks_size > SIZE_MAX) {
free(sig);
return NULL;
}
sig->blocks = protocol_alloc((size_t)blocks_size);
if (!sig->blocks) {
free(sig);
return NULL;
}
for (uint32_t i = 0; i < sig->block_count; i++) {
memcpy(&sig->blocks[i].adler32, buf + pos, sizeof(uint32_t));
pos += sizeof(uint32_t);
memcpy(&sig->blocks[i].xxhash, buf + pos, sizeof(uint32_t));
pos += sizeof(uint32_t);
}
return sig;
}
void delta_signature_destroy(DeltaSignature* sig) {
if (!sig)
return;
free(sig->blocks);
free(sig);
}
static bool ensure_capacity(DeltaInstruction** instrs, uint32_t* capacity, uint32_t count) {
if (count < *capacity)
return true;
if (*capacity > MAX_DELTA_INSTRUCTIONS / 2)
return false;
uint32_t new_cap = *capacity * 2;
DeltaInstruction* tmp = protocol_realloc(*instrs, (size_t)new_cap * sizeof(DeltaInstruction));
if (!tmp)
return false;
*instrs = tmp;
*capacity = new_cap;
return true;
}
static bool flush_literal(DeltaInstruction** instrs, uint32_t* capacity, uint32_t* count,
const uint8_t* data, uint64_t start, uint64_t end) {
if (start >= end)
return true;
if (end - start > UINT32_MAX || *count >= MAX_DELTA_INSTRUCTIONS)
return false;
uint32_t lit_len = (uint32_t)(end - start);
if (!ensure_capacity(instrs, capacity, *count))
return false;
uint8_t* lit_data = protocol_alloc(lit_len);
if (!lit_data)
return false;
memcpy(lit_data, data + start, lit_len);
(*instrs)[*count].type = DELTA_INSTR_LITERAL;
(*instrs)[*count].literal.data = lit_data;
(*instrs)[*count].literal.length = lit_len;
(*count)++;
return true;
}
static void free_instructions(DeltaInstruction* instrs, uint32_t count) {
if (!instrs)
return;
for (uint32_t i = 0; i < count; i++)
if (instrs[i].type == DELTA_INSTR_LITERAL)
free(instrs[i].literal.data);
free(instrs);
}
/* Sentinel meaning "no signature block" in the lookup index chains. Block
* counts are bounded well below UINT32_MAX, so it doubles as a null link. */
#define DELTA_NO_BLOCK UINT32_MAX
/* Avalanche mix for the rolling checksum so blocks do not cluster in the
* bucket table when the weak checksum has little entropy (e.g. all-zero or
* patterned files). */
static uint32_t delta_adler_mix(uint32_t h) {
h ^= h >> 16;
h *= 0x7feb352dU;
h ^= h >> 15;
h *= 0x846ca68bU;
h ^= h >> 16;
return h;
}
/* Smallest power of two >= v. v must be non-zero. */
static uint32_t delta_next_pow2(uint32_t v) {
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
return v + 1;
}
/* Build a hash index over sig->blocks keyed by the (mixed) rolling checksum.
* All blocks sharing an Adler-32 value land in the same bucket; collisions
* are chained through a single contiguous allocation:
*
* [0, bucket_count) heads (first block per bucket)
* [bucket_count, 2*bucket_count) tails (last block per bucket)
* [2*bucket_count, ...) per-block chain links
*
* Blocks are inserted in ascending index order so every bucket chain is
* ordered exactly like the historical linear scan. Returns the base pointer
* (also the heads array) or NULL when no index could be allocated; callers
* then fall back to the linear scan. */
static uint32_t* delta_build_index(const DeltaSignature* sig, uint32_t bucket_count) {
if (sig->block_count == 0 || bucket_count == 0)
return NULL;
size_t entries = (size_t)2 * bucket_count + sig->block_count;
if (entries > SIZE_MAX / sizeof(uint32_t))
return NULL;
uint32_t* index = protocol_alloc(entries * sizeof(uint32_t));
if (!index)
return NULL;
uint32_t* heads = index;
uint32_t* tails = index + bucket_count;
uint32_t* next = index + 2 * bucket_count;
uint32_t mask = bucket_count - 1;
memset(heads, 0xFF, (size_t)bucket_count * sizeof(uint32_t));
memset(tails, 0xFF, (size_t)bucket_count * sizeof(uint32_t));
for (uint32_t j = 0; j < sig->block_count; j++) {
uint32_t b = delta_adler_mix(sig->blocks[j].adler32) & mask;
if (heads[b] == DELTA_NO_BLOCK)
heads[b] = j;
else
next[tails[b]] = j;
tails[b] = j;
next[j] = DELTA_NO_BLOCK;
}
return index;
}
/* Locate the signature block matching the byte window at new_data[i].
*
* Mirrors the original per-window behaviour exactly: only a full block_size
* window can match, candidates are accepted only when the weak (Adler-32) and
* strong (xxHash32) checksums both agree, and the lowest block index wins so
* the emitted op stream is byte-identical to the linear scan. When heads is
* non-NULL the candidate set is reached through the bucket index (expected
* O(1) per window); otherwise an exact linear scan is used. */
static uint32_t delta_find_match(const uint8_t* window, uint32_t window_len, uint32_t adler,
bool full_window, const DeltaSignature* sig, const uint32_t* heads,
const uint32_t* next, uint32_t mask, uint32_t seed) {
if (!full_window || sig->block_count == 0)
return DELTA_NO_BLOCK;
if (heads) {
uint32_t b = delta_adler_mix(adler) & mask;
uint32_t window_xxh = 0;
bool have_xxh = false;
for (uint32_t j = heads[b]; j != DELTA_NO_BLOCK; j = next[j]) {
if (sig->blocks[j].adler32 != adler)
continue;
if (!have_xxh) {
window_xxh = delta_xxhash32_seeded(window, window_len, seed);
have_xxh = true;
}
if (window_xxh == sig->blocks[j].xxhash)
return j;
}
return DELTA_NO_BLOCK;
}
/* Fallback used when the index could not be allocated. */
for (uint32_t j = 0; j < sig->block_count; j++) {
if (sig->blocks[j].adler32 == adler) {
uint32_t window_xxh = delta_xxhash32_seeded(window, window_len, seed);
if (window_xxh == sig->blocks[j].xxhash)
return j;
}
}
return DELTA_NO_BLOCK;
}
Delta* delta_compute(const void* new_file_data, uint64_t new_file_size, const DeltaSignature* sig,
uint32_t block_size) {
return delta_compute_seeded(new_file_data, new_file_size, sig, block_size, 0);
}
Delta* delta_compute_seeded(const void* new_file_data, uint64_t new_file_size,
const DeltaSignature* sig, uint32_t block_size, uint32_t seed) {
if (!new_file_data || !sig || !sig->blocks || new_file_size == 0 || block_size == 0 ||
block_size > DELTA_BLOCK_SIZE_MAX || sig->block_size != block_size)
return NULL;
const uint8_t* new_data = (const uint8_t*)new_file_data;
uint32_t capacity = 64;
uint32_t count = 0;
DeltaInstruction* instrs = protocol_alloc((size_t)capacity * sizeof(DeltaInstruction));
if (!instrs)
return NULL;
/* Build a one-time bucket index over the signature blocks keyed by the weak
* checksum. This turns the per-byte-window candidate lookup from an
* O(block_count) linear scan into an expected O(1) probe, which dominates
* the cost for large mostly-matching files (the diff steps one byte at a
* time through changed regions). On allocation failure the probe falls back
* to the original linear scan, so behaviour is unchanged under memory
* pressure. */
uint32_t* index = NULL;
const uint32_t* chain_next = NULL;
uint32_t mask = 0;
if (sig->block_count > 0) {
uint32_t bucket_count = delta_next_pow2(sig->block_count);
index = delta_build_index(sig, bucket_count);
if (index) {
chain_next = index + 2 * bucket_count;
mask = bucket_count - 1;
}
}
uint64_t literal_start = 0;
bool has_literal = false;
uint64_t i = 0;
uint32_t s1 = 1, s2 = 0;
bool rolling_valid = false;
while (i < new_file_size) {
uint32_t window_len =
(uint32_t)((new_file_size - i < block_size) ? (new_file_size - i) : block_size);
bool full_window = (window_len == block_size);
uint32_t adler;
if (rolling_valid && full_window) {
uint8_t old_byte = new_data[i - 1];
uint8_t new_byte = new_data[i + block_size - 1];
s1 = (s1 + DELTA_ADLER32_MODULUS - old_byte + new_byte) % DELTA_ADLER32_MODULUS;
s2 = (s2 + DELTA_ADLER32_MODULUS -
(uint32_t)((uint64_t)block_size * old_byte % DELTA_ADLER32_MODULUS) + s1 - 1) %
DELTA_ADLER32_MODULUS;
adler = (s2 << 16) | s1;
} else {
s1 = 1;
s2 = 0;
for (uint32_t k = 0; k < window_len; k++) {
s1 = (s1 + new_data[i + k]) % DELTA_ADLER32_MODULUS;
s2 = (s2 + s1) % DELTA_ADLER32_MODULUS;
}
adler = (s2 << 16) | s1;
rolling_valid = full_window;
}
bool matched = false;
uint32_t match_block = delta_find_match(new_data + i, window_len, adler, full_window, sig,
index, chain_next, mask, seed);
if (match_block != DELTA_NO_BLOCK) {
if (has_literal) {
if (!flush_literal(&instrs, &capacity, &count, new_data, literal_start, i)) {
free_instructions(instrs, count);
free(index);
return NULL;
}
has_literal = false;
}
if (!ensure_capacity(&instrs, &capacity, count)) {
free_instructions(instrs, count);
free(index);
return NULL;
}
instrs[count].type = DELTA_INSTR_BLOCK_MATCH;
instrs[count].match.block_index = match_block;
instrs[count].match.block_offset = 0;
instrs[count].match.length = window_len;
count++;
i += window_len;
rolling_valid = false;
matched = true;
}
if (!matched) {
if (!has_literal) {
literal_start = i;
has_literal = true;
}
i++;
}
}
free(index);
if (has_literal) {
if (!flush_literal(&instrs, &capacity, &count, new_data, literal_start, new_file_size)) {
free_instructions(instrs, count);
return NULL;
}
}
Delta* delta = protocol_alloc(sizeof(Delta));
if (!delta) {
free_instructions(instrs, count);
return NULL;
}
delta->new_file_size = new_file_size;
delta->instruction_count = count;
delta->instructions = instrs;
delta->delta_size = 0;
for (uint32_t k = 0; k < count; k++) {
if (delta->delta_size == UINT64_MAX) {
delta_destroy(delta);
return NULL;
}
delta->delta_size += 1;
if (instrs[k].type == DELTA_INSTR_BLOCK_MATCH) {
if (delta->delta_size > UINT64_MAX - sizeof(uint32_t) * 3) {
delta_destroy(delta);
return NULL;
}
delta->delta_size += sizeof(uint32_t) * 3;
} else {
uint64_t extra = sizeof(uint32_t) + instrs[k].literal.length;
if (delta->delta_size > UINT64_MAX - extra) {
delta_destroy(delta);
return NULL;
}
delta->delta_size += extra;
}
}
return delta;
}
Data* delta_serialize(const Delta* delta) {
if (!delta)
return NULL;
if (delta->instruction_count > 0 && !delta->instructions)
return NULL;
uint64_t header_size = sizeof(uint64_t) + sizeof(uint32_t);
if (delta->delta_size > UINT64_MAX - header_size || header_size + delta->delta_size > SIZE_MAX)
return NULL;
uint64_t total = header_size + delta->delta_size;
uint8_t* buf = protocol_alloc((size_t)total);
if (!buf)
return NULL;
size_t pos = 0;
memcpy(buf + pos, &delta->new_file_size, sizeof(uint64_t));
pos += sizeof(uint64_t);
memcpy(buf + pos, &delta->instruction_count, sizeof(uint32_t));
pos += sizeof(uint32_t);
for (uint32_t i = 0; i < delta->instruction_count; i++) {
uint8_t type = (uint8_t)delta->instructions[i].type;
memcpy(buf + pos, &type, sizeof(uint8_t));
pos += sizeof(uint8_t);
if (delta->instructions[i].type == DELTA_INSTR_BLOCK_MATCH) {
memcpy(buf + pos, &delta->instructions[i].match.block_index, sizeof(uint32_t));
pos += sizeof(uint32_t);
memcpy(buf + pos, &delta->instructions[i].match.block_offset, sizeof(uint32_t));
pos += sizeof(uint32_t);
memcpy(buf + pos, &delta->instructions[i].match.length, sizeof(uint32_t));
pos += sizeof(uint32_t);
} else {
memcpy(buf + pos, &delta->instructions[i].literal.length, sizeof(uint32_t));
pos += sizeof(uint32_t);
memcpy(buf + pos, delta->instructions[i].literal.data, delta->instructions[i].literal.length);
pos += delta->instructions[i].literal.length;
}
}
return data_create(buf, (size_t)total);
}
Delta* delta_deserialize(const Data* data) {
if (!data || data->size < sizeof(uint64_t) + sizeof(uint32_t))
return NULL;
const uint8_t* buf = (const uint8_t*)data->data;
size_t pos = 0;
Delta* delta = protocol_alloc(sizeof(Delta));
if (!delta)
return NULL;
memcpy(&delta->new_file_size, buf + pos, sizeof(uint64_t));
pos += sizeof(uint64_t);
memcpy(&delta->instruction_count, buf + pos, sizeof(uint32_t));
pos += sizeof(uint32_t);
// Reject unreasonably large instruction counts to prevent OOM
if (delta->instruction_count > MAX_DELTA_INSTRUCTIONS) {
log_message(LOG_LEVEL_ERROR, "Delta instruction count %u exceeds maximum %u",
delta->instruction_count, MAX_DELTA_INSTRUCTIONS);
free(delta);
return NULL;
}
delta->instructions =
delta->instruction_count == 0
? NULL
: protocol_alloc((size_t)delta->instruction_count * sizeof(DeltaInstruction));
if (delta->instruction_count > 0 && !delta->instructions) {
free(delta);
return NULL;
}
delta->delta_size = 0;
for (uint32_t i = 0; i < delta->instruction_count; i++) {
if (pos >= data->size) {
free_instructions(delta->instructions, i);
free(delta);
return NULL;
}
uint8_t type;
memcpy(&type, buf + pos, sizeof(uint8_t));
pos += sizeof(uint8_t);
delta->delta_size += 1;
if (type == DELTA_OP_BLOCK_MATCH) {
if (data->size - pos < sizeof(uint32_t) * 3) {
free_instructions(delta->instructions, i);
free(delta);
return NULL;
}
delta->instructions[i].type = DELTA_INSTR_BLOCK_MATCH;
memcpy(&delta->instructions[i].match.block_index, buf + pos, sizeof(uint32_t));
pos += sizeof(uint32_t);
memcpy(&delta->instructions[i].match.block_offset, buf + pos, sizeof(uint32_t));
pos += sizeof(uint32_t);
memcpy(&delta->instructions[i].match.length, buf + pos, sizeof(uint32_t));
pos += sizeof(uint32_t);
delta->delta_size += sizeof(uint32_t) * 3;
} else if (type == DELTA_OP_LITERAL) {
if (data->size - pos < sizeof(uint32_t)) {
free_instructions(delta->instructions, i);
free(delta);
return NULL;
}
delta->instructions[i].type = DELTA_INSTR_LITERAL;
memcpy(&delta->instructions[i].literal.length, buf + pos, sizeof(uint32_t));
pos += sizeof(uint32_t);
uint32_t lit_len = delta->instructions[i].literal.length;
if (lit_len > data->size - pos) {
free_instructions(delta->instructions, i);
free(delta);
return NULL;
}
delta->instructions[i].literal.data = protocol_alloc(lit_len ? lit_len : 1);
if (!delta->instructions[i].literal.data) {
log_message(LOG_LEVEL_ERROR, "Failed to allocate %u bytes for literal data", lit_len);
free_instructions(delta->instructions, i);
free(delta);
return NULL;
}
memcpy(delta->instructions[i].literal.data, buf + pos, lit_len);
pos += lit_len;
delta->delta_size += sizeof(uint32_t) + lit_len;
} else {
free_instructions(delta->instructions, i);
free(delta);
return NULL;
}
}
return delta;
}
void* delta_apply(const void* old_data, uint64_t old_size, const Delta* delta,
uint32_t block_size) {
if (!old_data || !delta || (delta->new_file_size > 0 && delta->instructions == NULL) ||
(delta->instruction_count > 0 && block_size == 0) ||
delta->new_file_size > DELTA_MAX_FILE_SIZE || delta->new_file_size > SIZE_MAX)
return NULL;
void* output = protocol_alloc(delta->new_file_size ? (size_t)delta->new_file_size : 1);
if (!output)
return NULL;
uint8_t* out = (uint8_t*)output;
const uint8_t* old = (const uint8_t*)old_data;
uint64_t out_pos = 0;
for (uint32_t i = 0; i < delta->instruction_count; i++) {
if (delta->instructions[i].type == DELTA_INSTR_BLOCK_MATCH) {
uint64_t src_offset = (uint64_t)delta->instructions[i].match.block_index * block_size;
if (src_offset > UINT64_MAX - delta->instructions[i].match.block_offset) {
free(output);
return NULL;
}
src_offset += delta->instructions[i].match.block_offset;
uint32_t len = delta->instructions[i].match.length;
if (src_offset > old_size || (uint64_t)len > old_size - src_offset ||
out_pos > delta->new_file_size || (uint64_t)len > delta->new_file_size - out_pos) {
free(output);
return NULL;
}
memcpy(out + out_pos, old + src_offset, len);
out_pos += len;
} else if (delta->instructions[i].type == DELTA_INSTR_LITERAL) {
uint32_t len = delta->instructions[i].literal.length;
if (out_pos > delta->new_file_size || (uint64_t)len > delta->new_file_size - out_pos) {
free(output);
return NULL;
}
memcpy(out + out_pos, delta->instructions[i].literal.data, len);
out_pos += len;
} else {
free(output);
return NULL;
}
}
if (out_pos != delta->new_file_size) {
free(output);
return NULL;
}
return output;
}
void delta_destroy(Delta* delta) {
if (!delta)
return;
for (uint32_t i = 0; i < delta->instruction_count; i++) {
if (delta->instructions[i].type == DELTA_INSTR_LITERAL)
free(delta->instructions[i].literal.data);
}
free(delta->instructions);
free(delta);
}
bool delta_should_attempt(uint64_t old_size, uint64_t new_size, uint64_t max_file_size) {
if (old_size < DELTA_MIN_FILE_SIZE || new_size < DELTA_MIN_FILE_SIZE)
return false;
if (old_size > max_file_size || new_size > max_file_size)
return false;
double large = (old_size > new_size) ? (double)old_size : (double)new_size;
double small = (old_size > new_size) ? (double)new_size : (double)old_size;
if (small == 0 || large / small > DELTA_MAX_SIZE_RATIO)
return false;
return true;
}
bool delta_is_worthwhile(const Delta* delta, uint64_t new_file_size) {
if (!delta || delta->instruction_count == 0 || new_file_size == 0)
return false;
bool has_match = false;
for (uint32_t i = 0; i < delta->instruction_count; i++) {
if (delta->instructions[i].type == DELTA_INSTR_BLOCK_MATCH) {
has_match = true;
break;
}
}
if (!has_match)
return false;
double ratio = (double)delta->delta_size / (double)new_file_size;
return ratio < DELTA_FALLBACK_RATIO;
}