fix: address PR review - rolling adler32, extract helpers, configurable max file size
CI / build-and-test (push) Successful in 29s
CI / build-and-test (pull_request) Successful in 28s

- Fix rolling adler32: add missing -1 in s2 update formula (was producing
  wrong checksums, causing zero block matches)
- Fix file_send_sendfile signature to match typedef (add unused
  compression_level param)
- Extract receive_delta_file() from receive_incremental_check() to reduce
  nesting depth
- Extract send_file_incremental() helper in client_send.c
- Add delta_max_file_size to Config, serialized over wire
- Add --delta-max CLI flag
- Add test_large_file_delta (200KB) and extra delta_should_attempt cases
- Remove unused pos_in_block variable from delta_compute
This commit is contained in:
2026-07-19 02:02:28 +02:00
parent e16db56580
commit 598e4e7514
9 changed files with 339 additions and 261 deletions
+83 -47
View File
@@ -128,6 +128,33 @@ void delta_signature_destroy(DeltaSignature *sig) {
free(sig);
}
static bool ensure_capacity(DeltaInstruction **instrs, uint32_t *capacity,
uint32_t count) {
if (count < *capacity) return true;
uint32_t new_cap = *capacity * 2;
DeltaInstruction *tmp = realloc(*instrs, 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;
uint32_t lit_len = (uint32_t)(end - start);
if (!ensure_capacity(instrs, capacity, *count)) return false;
uint8_t *lit_data = malloc(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;
}
Delta *delta_compute(const void *new_file_data, uint64_t new_file_size,
const DeltaSignature *sig, uint32_t block_size) {
if (!new_file_data || !sig || new_file_size == 0 || block_size == 0)
@@ -144,49 +171,67 @@ Delta *delta_compute(const void *new_file_data, uint64_t new_file_size,
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);
uint32_t adler = delta_adler32(new_data + i, window_len);
uint32_t xxh = delta_xxhash32(new_data + i, window_len);
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;
for (uint32_t j = 0; j < sig->block_count; j++) {
if (adler == sig->blocks[j].adler32 && xxh == sig->blocks[j].xxhash) {
if (has_literal) {
uint32_t lit_len = (uint32_t)(i - literal_start);
if (count == capacity) {
capacity *= 2;
DeltaInstruction *tmp = realloc(instrs, capacity * sizeof(DeltaInstruction));
if (!tmp) { free(instrs); return NULL; }
instrs = tmp;
if (adler == sig->blocks[j].adler32 && full_window) {
uint32_t xxh = delta_xxhash32(new_data + i, window_len);
if (xxh == sig->blocks[j].xxhash) {
if (has_literal) {
if (!flush_literal(&instrs, &capacity, &count, new_data,
literal_start, i)) {
free(instrs);
return NULL;
}
has_literal = false;
}
uint8_t *lit_data = malloc(lit_len);
if (!lit_data) { free(instrs); return NULL; }
memcpy(lit_data, new_data + literal_start, lit_len);
instrs[count].type = DELTA_INSTR_LITERAL;
instrs[count].literal.data = lit_data;
instrs[count].literal.length = lit_len;
if (!ensure_capacity(&instrs, &capacity, count)) {
free(instrs);
return NULL;
}
instrs[count].type = DELTA_INSTR_BLOCK_MATCH;
instrs[count].match.block_index = j;
instrs[count].match.block_offset = 0;
instrs[count].match.length = window_len;
count++;
has_literal = false;
}
if (count == capacity) {
capacity *= 2;
DeltaInstruction *tmp = realloc(instrs, capacity * sizeof(DeltaInstruction));
if (!tmp) { free(instrs); return NULL; }
instrs = tmp;
i += window_len;
rolling_valid = false;
matched = true;
break;
}
instrs[count].type = DELTA_INSTR_BLOCK_MATCH;
instrs[count].match.block_index = j;
instrs[count].match.block_offset = 0;
instrs[count].match.length = window_len;
count++;
i += window_len;
matched = true;
break;
}
}
@@ -200,20 +245,11 @@ Delta *delta_compute(const void *new_file_data, uint64_t new_file_size,
}
if (has_literal) {
uint32_t lit_len = (uint32_t)(new_file_size - literal_start);
if (count == capacity) {
capacity *= 2;
DeltaInstruction *tmp = realloc(instrs, capacity * sizeof(DeltaInstruction));
if (!tmp) { free(instrs); return NULL; }
instrs = tmp;
if (!flush_literal(&instrs, &capacity, &count, new_data,
literal_start, new_file_size)) {
free(instrs);
return NULL;
}
uint8_t *lit_data = malloc(lit_len);
if (!lit_data) { free(instrs); return NULL; }
memcpy(lit_data, new_data + literal_start, lit_len);
instrs[count].type = DELTA_INSTR_LITERAL;
instrs[count].literal.data = lit_data;
instrs[count].literal.length = lit_len;
count++;
}
Delta *delta = malloc(sizeof(Delta));
@@ -421,10 +457,10 @@ void delta_destroy(Delta *delta) {
free(delta);
}
bool delta_should_attempt(uint64_t old_size, uint64_t new_size) {
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 > DELTA_MAX_FILE_SIZE || new_size > DELTA_MAX_FILE_SIZE)
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;