Release 2.1.3: Fix disk backup corruption, solid PQ encryption, block I/O, and format mismatches
- Corrected LZHP prediction encoding in disk backups to prevent data corruption - Disabled spurious SOLID flag for per-block disk archives - Shared write_enc_header() across all encryption paths to eliminate format mismatches - Enabled solid compression with PQ encryption support - Updated block device restore to use O_SYNC + fsync/sync - Improved Termux/Android host detection for safer builds - Made zupt_w8(), zupt_w16le(), zupt_w64le() non-static for shared use
This commit is contained in:
parent
181da41550
commit
28b2744c67
39 changed files with 324 additions and 330 deletions
27
CHANGELOG.md
27
CHANGELOG.md
|
|
@ -5,6 +5,32 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
|
|||
|
||||
---
|
||||
|
||||
## [2.1.3] — 2026-04-11
|
||||
|
||||
### Fixed — LZHP Prediction Encoding Missing in Disk Backup (data corruption)
|
||||
- **Root cause:** `zupt_disk_backup()` LZHP compression path skipped the `zupt_predict_encode()` step. When byte prediction was active (`pred_active=1`), it stored the prediction table and wrote `cbuf[0] = 0x01`, but then compressed the **raw block data** instead of the prediction-encoded data. On restore, `decompress_block()` correctly applied `zupt_predict_decode()` to the decompressed output, producing corrupted data. Checksum mismatch on block 0 for any block with structured content (ext4 metadata, NTFS headers, partition tables).
|
||||
- **Impact:** ALL disk backups using LZHP codec (default on CPUs without AVX2) on non-random data were silently corrupted. VaptVupt codec was unaffected (no prediction path). Random/incompressible data was unaffected (prediction benefit < threshold → `pred_active=0`).
|
||||
- **Fix:** Added `zupt_predict_encode(rbuf, transformed, nread, pred)` before `zupt_lzh_compress()`, matching the correct path in `zupt_format.c` (lines 557–563). Allocated temporary buffer for prediction-encoded data, freed after compression.
|
||||
|
||||
### Fixed — Spurious SOLID Flag on Disk Archives
|
||||
- Disk backup no longer sets `ZUPT_FLAG_SOLID` in the archive header. Disk images are independent per-block archives, not solid streams. The SOLID flag caused `zupt_extract_archive()` to take the wrong code path if a disk archive was ever parsed by the extract function.
|
||||
|
||||
### Fixed — Shared Encryption Header (eliminates all format mismatches)
|
||||
- **Extracted `write_enc_header()`** from `zupt_format.c` as a shared non-static function. ALL three encryption write paths — `zupt_compress_files()`, `zupt_compress_solid()`, and `zupt_disk_backup()` — now call the same function.
|
||||
- **Solid compress now supports PQ encryption.**
|
||||
- **`zupt_w8()`, `zupt_w16le()`, `zupt_w64le()`** made non-static and declared in `zupt.h`.
|
||||
|
||||
### Fixed — Block Device Restore I/O
|
||||
- Restore uses POSIX raw I/O (`open()` + `write()` loop) with `O_SYNC` for block devices, `fsync()` + `sync()` before close.
|
||||
|
||||
### Fixed — Termux/Android Build
|
||||
- Arch-safety guard uses `$(CC) -dumpmachine` for host detection. Falls back to `uname -m`.
|
||||
|
||||
### Tests
|
||||
- **78 total:** 70 core + 8 disk (including LZHP+PQ+password on ext4 — the exact failing case). ASAN + UBSan clean.
|
||||
|
||||
---
|
||||
|
||||
## [2.1.2] — 2026-04-06
|
||||
|
||||
### Added — Full-Disk Backup/Restore
|
||||
|
|
@ -273,6 +299,7 @@ All 4 `.jazz` files rewritten to fix compilation errors:
|
|||
|
||||
| Version | Key Change | Tests |
|
||||
|---------|-----------|-------|
|
||||
| **2.1.3** | Shared `write_enc_header()` eliminates all format mismatches, solid PQ support, block device O_SYNC. Disk restore rewritten — uses shared block I/O, fixes checksum mismatch with all encryption formats | 77 PASS |
|
||||
| **2.1.2** | Full-disk backup/restore with sparse detection, all encryption modes, progress bar | 77 PASS |
|
||||
| **2.1.1** | Termux/Android build fix, arch-safety guard, Keccak UB fix, no stale .o in tarballs | 70 PASS |
|
||||
| **2.1.0** | VaptVupt 1.4.0: cross-block dictionary, context prefetch, faster adaptive window, integration API | 70 PASS |
|
||||
|
|
|
|||
26
Makefile
26
Makefile
|
|
@ -118,19 +118,29 @@ ALL_OBJS = $(ZUPT_OBJS) $(VV_SIMD_OBJS) $(VV_PLAIN_OBJS)
|
|||
# If pre-compiled .o files from a different architecture are present
|
||||
# (e.g. x86_64 .o files in an aarch64 build), the linker will fail
|
||||
# with "incompatible with <arch>". Detect and remove stale objects.
|
||||
# This happens when tarballs accidentally include build artifacts.
|
||||
# This happens when tarballs accidentally include build artifacts,
|
||||
# or when the same source tree is shared between different machines.
|
||||
#
|
||||
# Detection: uses $(CC) -dumpmachine which works on ALL platforms
|
||||
# including Termux (where /bin/sh does not exist).
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
STALE_OBJS := $(wildcard src/*.o jasmin/*.o)
|
||||
ifneq ($(STALE_OBJS),)
|
||||
# Check if any existing .o is for the wrong architecture
|
||||
FIRST_OBJ := $(firstword $(STALE_OBJS))
|
||||
OBJ_ARCH := $(shell file $(FIRST_OBJ) 2>/dev/null | grep -oE 'x86.64|aarch64|ARM|PowerPC|S/390|RISC-V' | head -1)
|
||||
HOST_ARCH := $(shell file /bin/sh 2>/dev/null | grep -oE 'x86.64|aarch64|ARM|PowerPC|S/390|RISC-V' | head -1)
|
||||
ifneq ($(OBJ_ARCH),$(HOST_ARCH))
|
||||
ifneq ($(OBJ_ARCH),)
|
||||
$(info [arch] Removing stale $(OBJ_ARCH) objects for $(HOST_ARCH) build)
|
||||
$(shell rm -f src/*.o jasmin/*.o)
|
||||
OBJ_ARCH := $(shell file $(FIRST_OBJ) 2>/dev/null | grep -oiE 'x86.64|aarch64|arm|powerpc|s390|riscv' | head -1)
|
||||
HOST_TRIPLE := $(shell $(CC) -dumpmachine 2>/dev/null)
|
||||
HOST_ARCH_CC := $(shell echo "$(HOST_TRIPLE)" | grep -oiE 'x86.64|aarch64|arm|powerpc|s390|riscv' | head -1)
|
||||
# Fallback: try uname -m if CC -dumpmachine fails
|
||||
ifeq ($(HOST_ARCH_CC),)
|
||||
HOST_ARCH_CC := $(shell uname -m 2>/dev/null | grep -oiE 'x86.64|aarch64|arm|powerpc|s390|riscv' | head -1)
|
||||
endif
|
||||
ifneq ($(OBJ_ARCH),)
|
||||
ifneq ($(HOST_ARCH_CC),)
|
||||
ifneq ($(OBJ_ARCH),$(HOST_ARCH_CC))
|
||||
$(info [arch] Removing stale $(OBJ_ARCH) objects for $(HOST_ARCH_CC) build)
|
||||
$(shell rm -f src/*.o jasmin/*.o)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
|
|
|||
12
README.md
12
README.md
|
|
@ -4,7 +4,7 @@
|
|||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
|
|
@ -372,11 +372,11 @@ All codecs are forward-compatible: archives created with any codec can be read b
|
|||
| v1.1–v1.4 | X25519 fix, NIST vectors, CPUID detection, Jasmin source files fixed |
|
||||
| v1.5 | Jasmin CT assembly linked (MAC verify + ML-KEM select active) |
|
||||
| v1.5.5 | Man page install, V=1 verbose, LDFLAGS/PIE, rpmlint, multi-arch Makefile |
|
||||
| v2.0 | VaptVupt 1.1.0 codec with auto hardware detection, all 5 Jasmin functions wired, AVX SIGILL fix, copy_match/litlen overflow fixes, ACSL proofs, mlock, fuzzing, canaries, AES-NI 4-block pipeline, MT decompression, adaptive compression, multi-architecture support (6 arches), --lzhp flag |
|
||||
| v2.1 | VaptVupt 1.4.0: cross-block dictionary carry, context decode prefetch, faster adaptive window trial (2.6× encode), integration API |
|
||||
| v2.1.1 | Termux/Android build fix, arch-safety guard removes stale cross-arch .o, Keccak ROL64 UB fix, zero UBSan violations |
|
||||
| **v2.1.2** | **Full-disk backup/restore (`zupt disk`), sparse detection, all encryption modes, per-block XXH64 verification, progress bar, 77 tests** |
|
||||
| v2.1.3 | Wait for it. |
|
||||
| v2.0 | VaptVupt 1.1.0 codec, auto hardware detection, all 5 Jasmin wired, AVX SIGILL fix, copy_match/litlen fixes, ACSL, mlock, fuzzing, canaries, AES-NI pipeline, MT decompress, multi-arch (6 arches), --lzhp flag |
|
||||
| v2.1.0 | VaptVupt 1.4.0: cross-block dictionary carry, context decode prefetch, faster adaptive window (2.6× encode), integration API |
|
||||
| v2.1.1 | Termux/Android build fix, arch-safety guard, Keccak ROL64 UB fix, zero UBSan violations |
|
||||
| v2.1.2 | Full-disk backup/restore (`zupt disk`), sparse detection, all encryption modes, progress bar |
|
||||
| **v2.1.3** | **Disk restore fix (POSIX raw I/O + O_SYNC for block devices, shared decompress_block), Termux build fix (CC -dumpmachine arch detection). LZHP prediction encoding fix (data corruption on structured data), shared write_enc_header, SOLID flag removed from disk, 78 tests** |
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md) for detailed per-version changes.
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@
|
|||
| **v2.0** | **✅** | **VaptVupt 1.1.0 codec with auto hardware detection, all 5 Jasmin wired, AVX SIGILL fix, copy_match/litlen fixes, ACSL, mlock, fuzzing, canaries, AES-NI pipeline, MT decompress, multi-arch (6 arches)** |
|
||||
| **v2.1** | **✅** | **VaptVupt 1.4.0: cross-block dictionary, context prefetch, faster adaptive window, integration API** |
|
||||
| **v2.1.1** | **✅** | **Termux/Android build fix, arch-safety guard, Keccak UB fix, no stale .o in tarballs** |
|
||||
| **v2.1.2** | **✅ Current** | **Full-disk backup/restore with sparse detection, all encryption modes, progress bar, 77 tests** |
|
||||
| **v2.1.2** | **✅** | **Full-disk backup/restore with sparse detection, all encryption modes, progress bar, 77 tests** |
|
||||
| **v2.1.3** | **✅** | **Disk restore rewritten — shared block I/O, fixes checksum mismatch on encrypted/PQ archives** |
|
||||
| **v2.1.3** | **✅ Current** | **LZHP prediction encoding fix, shared write_enc_header, SOLID flag removed from disk, block device O_SYNC, 78 tests** |
|
||||
|
||||
## Planned
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
#define zupt_mkdir(p) mkdir(p, 0755)
|
||||
#endif
|
||||
|
||||
#define ZUPT_VERSION_STRING "2.1.2"
|
||||
#define ZUPT_VERSION_STRING "2.1.3"
|
||||
#define ZUPT_FORMAT_MAJOR 1
|
||||
#define ZUPT_FORMAT_MINOR 4
|
||||
|
||||
|
|
@ -347,4 +347,15 @@ zupt_error_t zupt_disk_backup(const char *output_path, const char *source_path,
|
|||
zupt_error_t zupt_disk_restore(const char *archive_path, const char *target_path,
|
||||
zupt_options_t *opts);
|
||||
|
||||
/* ─── Internal Block I/O (used by format + disk modules) ─── */
|
||||
zupt_error_t read_block(FILE *f, zupt_block_t *b);
|
||||
zupt_error_t read_enc_header(FILE *f, zupt_archive_header_t *hdr, zupt_options_t *opts);
|
||||
zupt_error_t decompress_block(const zupt_block_t *b, const zupt_keyring_t *kr,
|
||||
uint64_t block_seq, uint8_t **out, size_t *olen);
|
||||
zupt_error_t write_enc_header(FILE *out, zupt_archive_header_t *hdr,
|
||||
zupt_options_t *opts);
|
||||
int zupt_w8(FILE *f, uint8_t v);
|
||||
int zupt_w16le(FILE *f, uint16_t v);
|
||||
int zupt_w64le(FILE *f, uint64_t v);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/vv_ans.o
BIN
src/vv_ans.o
Binary file not shown.
BIN
src/vv_decoder.o
BIN
src/vv_decoder.o
Binary file not shown.
BIN
src/vv_encoder.o
BIN
src/vv_encoder.o
Binary file not shown.
BIN
src/vv_huffman.o
BIN
src/vv_huffman.o
Binary file not shown.
BIN
src/vv_simd.o
BIN
src/vv_simd.o
Binary file not shown.
Binary file not shown.
BIN
src/zupt_cpuid.o
BIN
src/zupt_cpuid.o
Binary file not shown.
Binary file not shown.
395
src/zupt_disk.c
395
src/zupt_disk.c
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Zupt v2.1.2 — Full-Disk Backup/Restore
|
||||
* Zupt v2.1.3 — Full-Disk Backup/Restore
|
||||
* Copyright (c) 2026 Cristian Cezar Moisés — MIT License
|
||||
*
|
||||
* Reads a raw block device or file, compresses in streaming chunks,
|
||||
|
|
@ -224,7 +224,7 @@ zupt_error_t zupt_disk_backup(const char *output_path, const char *source_path,
|
|||
hdr.magic[4] = ZUPT_MAGIC_4; hdr.magic[5] = ZUPT_MAGIC_5;
|
||||
hdr.version_major = ZUPT_FORMAT_MAJOR;
|
||||
hdr.version_minor = ZUPT_FORMAT_MINOR;
|
||||
hdr.global_flags = ZUPT_FLAG_CKSUM_XXH64 | ZUPT_FLAG_SOLID | ZUPT_FLAG_DISK_IMAGE;
|
||||
hdr.global_flags = ZUPT_FLAG_CKSUM_XXH64 | ZUPT_FLAG_DISK_IMAGE;
|
||||
if (opts->encrypt) hdr.global_flags |= ZUPT_FLAG_ENCRYPTED;
|
||||
if (opts->threads > 1) hdr.global_flags |= ZUPT_FLAG_MULTITHREADED;
|
||||
hdr.creation_time = (uint64_t)time(NULL) * 1000000000ULL;
|
||||
|
|
@ -234,67 +234,12 @@ zupt_error_t zupt_disk_backup(const char *output_path, const char *source_path,
|
|||
if (fwrite(&hdr, sizeof(hdr), 1, out) != 1) write_err = 1;
|
||||
|
||||
/* ─── Encryption header ─── */
|
||||
/* ─── Encryption header (uses same code as zupt compress) ─── */
|
||||
if (opts->encrypt) {
|
||||
hdr.encryption_header_off = (uint64_t)ftello(out);
|
||||
|
||||
if (opts->pq_mode) {
|
||||
uint8_t enc_hdr_buf[1200];
|
||||
size_t enc_hdr_len = 0;
|
||||
fprintf(stderr, " Post-quantum key encapsulation (ML-KEM-768 + X25519)...\n");
|
||||
if (zupt_hybrid_encrypt_init(&opts->keyring, opts->keyfile,
|
||||
enc_hdr_buf, &enc_hdr_len) != 0) {
|
||||
fprintf(stderr, "Error: PQ hybrid key encapsulation failed.\n");
|
||||
fclose(src_f); fclose(out);
|
||||
return ZUPT_ERR_AUTH_FAIL;
|
||||
}
|
||||
|
||||
/* Write encryption block */
|
||||
uint8_t bm0 = 0xBB, bm1 = 0x01, bt = ZUPT_BLOCK_ENC_HEADER;
|
||||
fwrite(&bm0, 1, 1, out); fwrite(&bm1, 1, 1, out); fwrite(&bt, 1, 1, out);
|
||||
uint8_t cs[2] = {0, 0}; fwrite(cs, 1, 2, out); fwrite(cs, 1, 2, out);
|
||||
zupt_write_varint(out, enc_hdr_len);
|
||||
zupt_write_varint(out, enc_hdr_len);
|
||||
uint64_t ck = zupt_xxh64(enc_hdr_buf, enc_hdr_len, 0);
|
||||
uint8_t ck8[8]; for (int i = 0; i < 8; i++) ck8[i] = (uint8_t)(ck >> (i*8));
|
||||
fwrite(ck8, 1, 8, out);
|
||||
fwrite(enc_hdr_buf, 1, enc_hdr_len, out);
|
||||
|
||||
hdr.global_flags |= ZUPT_FLAG_PQ_HYBRID;
|
||||
fseeko(out, 0, SEEK_SET);
|
||||
fwrite(&hdr, sizeof(hdr), 1, out);
|
||||
fseeko(out, 0, SEEK_END);
|
||||
|
||||
fprintf(stderr, " Encryption: PQ Hybrid (ML-KEM-768 + X25519)\n\n");
|
||||
} else {
|
||||
uint8_t salt[ZUPT_SALT_SIZE], nonce[ZUPT_NONCE_SIZE];
|
||||
zupt_random_bytes(salt, ZUPT_SALT_SIZE);
|
||||
zupt_random_bytes(nonce, ZUPT_NONCE_SIZE);
|
||||
|
||||
fprintf(stderr, " Deriving encryption key (PBKDF2-SHA256, %d iterations)...\n",
|
||||
ZUPT_KDF_ITERATIONS);
|
||||
zupt_derive_keys(&opts->keyring, opts->password, salt, nonce, ZUPT_KDF_ITERATIONS);
|
||||
|
||||
uint8_t enc_hdr[53];
|
||||
enc_hdr[0] = 0x01; /* ZUPT_ENC_PBKDF2 */
|
||||
memcpy(enc_hdr + 1, salt, 32);
|
||||
memcpy(enc_hdr + 33, nonce, 16);
|
||||
uint32_t iter = ZUPT_KDF_ITERATIONS;
|
||||
memcpy(enc_hdr + 49, &iter, 4);
|
||||
|
||||
uint8_t bm0 = 0xBB, bm1 = 0x01, bt = ZUPT_BLOCK_ENC_HEADER;
|
||||
fwrite(&bm0, 1, 1, out); fwrite(&bm1, 1, 1, out); fwrite(&bt, 1, 1, out);
|
||||
uint8_t cs[2] = {0, 0}; fwrite(cs, 1, 2, out); fwrite(cs, 1, 2, out);
|
||||
zupt_write_varint(out, 53); zupt_write_varint(out, 53);
|
||||
uint64_t ck = zupt_xxh64(enc_hdr, 53, 0);
|
||||
uint8_t ck8[8]; for (int i = 0; i < 8; i++) ck8[i] = (uint8_t)(ck >> (i*8));
|
||||
fwrite(ck8, 1, 8, out);
|
||||
fwrite(enc_hdr, 1, 53, out);
|
||||
|
||||
fseeko(out, 0, SEEK_SET);
|
||||
fwrite(&hdr, sizeof(hdr), 1, out);
|
||||
fseeko(out, 0, SEEK_END);
|
||||
|
||||
fprintf(stderr, " Encryption: AES-256-CTR + HMAC-SHA256\n\n");
|
||||
zupt_error_t enc_err = write_enc_header(out, &hdr, opts);
|
||||
if (enc_err != ZUPT_OK) {
|
||||
fclose(src_f); fclose(out);
|
||||
return enc_err;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -346,17 +291,32 @@ zupt_error_t zupt_disk_backup(const char *output_path, const char *source_path,
|
|||
if (csz > 0 && (size_t)csz < nread)
|
||||
comp_size = (size_t)csz;
|
||||
} else if (codec == ZUPT_CODEC_ZUPT_LZHP) {
|
||||
/* LZHP with prediction */
|
||||
/* LZHP with prediction — must encode through prediction
|
||||
* table before compressing, matching zupt_format.c */
|
||||
float benefit = zupt_predict_benefit(rbuf, nread);
|
||||
if (benefit > 0.02f && nread > 256) {
|
||||
uint8_t pred[256];
|
||||
zupt_predict_build(rbuf, nread, pred);
|
||||
cbuf[0] = 0x01;
|
||||
memcpy(cbuf + 1, pred, 256);
|
||||
size_t plain = zupt_lzh_compress(rbuf, nread, cbuf + 257,
|
||||
comp_cap - 257, opts->level);
|
||||
if (plain > 0 && 257 + plain < nread)
|
||||
comp_size = 257 + plain;
|
||||
uint8_t *transformed = (uint8_t *)malloc(nread);
|
||||
if (transformed) {
|
||||
zupt_predict_encode(rbuf, transformed, nread, pred);
|
||||
size_t plain = zupt_lzh_compress(transformed, nread,
|
||||
cbuf + 257,
|
||||
comp_cap - 257, opts->level);
|
||||
free(transformed);
|
||||
if (plain > 0 && 257 + plain < nread) {
|
||||
cbuf[0] = 0x01;
|
||||
memcpy(cbuf + 1, pred, 256);
|
||||
comp_size = 257 + plain;
|
||||
} else {
|
||||
/* Prediction didn't help — fall back to plain LZH */
|
||||
cbuf[0] = 0x00;
|
||||
plain = zupt_lzh_compress(rbuf, nread, cbuf + 1,
|
||||
comp_cap - 1, opts->level);
|
||||
if (plain > 0 && 1 + plain < nread)
|
||||
comp_size = 1 + plain;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cbuf[0] = 0x00;
|
||||
size_t plain = zupt_lzh_compress(rbuf, nread, cbuf + 1,
|
||||
|
|
@ -535,25 +495,37 @@ zupt_error_t zupt_disk_backup(const char *output_path, const char *source_path,
|
|||
/* ═══════════════════════════════════════════════════════════════════
|
||||
* DISK RESTORE (extract archive → device/file)
|
||||
*
|
||||
* Uses the standard zupt_extract_archive path but writes to a single
|
||||
* file (the target device) instead of creating a directory tree.
|
||||
* For disk images, the archive contains exactly one index entry.
|
||||
* Rewritten for v2.1.3: uses the same read_block() / read_enc_header() /
|
||||
* decompress_block() functions as zupt_extract_archive(). This eliminates
|
||||
* the hand-rolled block parser that caused checksum mismatches due to
|
||||
* encryption header format differences (52-byte vs 53-byte) and seek
|
||||
* offset errors.
|
||||
*
|
||||
* Flow:
|
||||
* 1. Read archive header → validate magic + DISK_IMAGE flag
|
||||
* 2. Read encryption header (if encrypted) → derive keys using
|
||||
* read_enc_header() which handles PQ, PBKDF2, and legacy formats
|
||||
* 3. Read data blocks sequentially with read_block()
|
||||
* 4. Decompress+decrypt+checksum each block with decompress_block()
|
||||
* 5. Write decompressed data to target
|
||||
* ═══════════════════════════════════════════════════════════════════ */
|
||||
|
||||
zupt_error_t zupt_disk_restore(const char *archive_path, const char *target_path,
|
||||
zupt_options_t *opts) {
|
||||
/* Open archive */
|
||||
FILE *f = fopen(archive_path, "rb");
|
||||
if (!f) {
|
||||
fprintf(stderr, "Error: Cannot open '%s': %s\n", archive_path, strerror(errno));
|
||||
return ZUPT_ERR_IO;
|
||||
}
|
||||
|
||||
/* Read archive header */
|
||||
/* ─── Read archive header ─── */
|
||||
zupt_archive_header_t hdr;
|
||||
if (fread(&hdr, sizeof(hdr), 1, f) != 1) { fclose(f); return ZUPT_ERR_IO; }
|
||||
if (fread(&hdr, sizeof(hdr), 1, f) != 1) {
|
||||
fclose(f);
|
||||
fprintf(stderr, "Error: Cannot read archive header\n");
|
||||
return ZUPT_ERR_IO;
|
||||
}
|
||||
|
||||
/* Verify magic */
|
||||
if (hdr.magic[0] != ZUPT_MAGIC_0 || hdr.magic[1] != ZUPT_MAGIC_1 ||
|
||||
hdr.magic[2] != ZUPT_MAGIC_2 || hdr.magic[3] != ZUPT_MAGIC_3) {
|
||||
fclose(f);
|
||||
|
|
@ -561,208 +533,153 @@ zupt_error_t zupt_disk_restore(const char *archive_path, const char *target_path
|
|||
return ZUPT_ERR_BAD_MAGIC;
|
||||
}
|
||||
|
||||
/* Check disk image flag */
|
||||
if (!(hdr.global_flags & ZUPT_FLAG_DISK_IMAGE)) {
|
||||
fclose(f);
|
||||
fprintf(stderr, "Error: Archive is not a disk image. Use 'zupt extract' instead.\n");
|
||||
return ZUPT_ERR_INVALID;
|
||||
}
|
||||
|
||||
/* Handle encryption */
|
||||
/* ─── Read encryption header (uses same code as zupt extract) ─── */
|
||||
if (hdr.global_flags & ZUPT_FLAG_ENCRYPTED) {
|
||||
if (!opts->encrypt && opts->password[0] == '\0' && !opts->pq_mode) {
|
||||
fclose(f);
|
||||
fprintf(stderr, "Error: Archive is encrypted. Use -p or --pq to provide key.\n");
|
||||
return ZUPT_ERR_AUTH_FAIL;
|
||||
}
|
||||
opts->encrypt = 1;
|
||||
|
||||
/* Read encryption header block */
|
||||
fseeko(f, (int64_t)hdr.encryption_header_off, SEEK_SET);
|
||||
|
||||
/* Skip block magic (2B) + type (1B) + codec (2B) + flags (2B) */
|
||||
uint8_t skip[7];
|
||||
if (fread(skip, 1, 7, f) != 7) { fclose(f); return ZUPT_ERR_CORRUPT; }
|
||||
|
||||
uint64_t uncomp_sz, comp_sz;
|
||||
if (zupt_read_varint(f, &uncomp_sz) < 0) { fclose(f); return ZUPT_ERR_CORRUPT; }
|
||||
if (zupt_read_varint(f, &comp_sz) < 0) { fclose(f); return ZUPT_ERR_CORRUPT; }
|
||||
|
||||
/* Skip checksum (8B) */
|
||||
uint8_t ck_skip[8];
|
||||
if (fread(ck_skip, 1, 8, f) != 8) { fclose(f); return ZUPT_ERR_CORRUPT; }
|
||||
|
||||
uint8_t *enc_data = (uint8_t *)malloc((size_t)comp_sz);
|
||||
if (!enc_data) { fclose(f); return ZUPT_ERR_NOMEM; }
|
||||
if (fread(enc_data, 1, (size_t)comp_sz, f) != (size_t)comp_sz) {
|
||||
free(enc_data); fclose(f); return ZUPT_ERR_CORRUPT;
|
||||
zupt_error_t enc_err = read_enc_header(f, &hdr, opts);
|
||||
if (enc_err != ZUPT_OK) {
|
||||
fclose(f);
|
||||
fprintf(stderr, "Error: Encryption header read failed (%s)\n",
|
||||
zupt_strerror(enc_err));
|
||||
return enc_err;
|
||||
}
|
||||
|
||||
if (opts->pq_mode) {
|
||||
if (zupt_hybrid_decrypt_init(&opts->keyring, opts->keyfile,
|
||||
enc_data, (size_t)comp_sz) != 0) {
|
||||
free(enc_data); fclose(f);
|
||||
fprintf(stderr, "Error: PQ decryption failed.\n");
|
||||
return ZUPT_ERR_AUTH_FAIL;
|
||||
}
|
||||
} else {
|
||||
/* Password mode */
|
||||
if (comp_sz < 53) { free(enc_data); fclose(f); return ZUPT_ERR_CORRUPT; }
|
||||
uint8_t enc_type = enc_data[0];
|
||||
if (enc_type != 0x01) { free(enc_data); fclose(f); return ZUPT_ERR_CORRUPT; }
|
||||
uint8_t *salt = enc_data + 1;
|
||||
uint8_t *nonce = enc_data + 33;
|
||||
uint32_t iter;
|
||||
memcpy(&iter, enc_data + 49, 4);
|
||||
zupt_derive_keys(&opts->keyring, opts->password, salt, nonce, iter);
|
||||
}
|
||||
free(enc_data);
|
||||
}
|
||||
|
||||
/* Read footer to find index */
|
||||
/* ─── Read footer to get total block count ─── */
|
||||
int64_t after_enc_pos = ftello(f); /* Save position after enc header */
|
||||
|
||||
fseeko(f, -(int64_t)sizeof(zupt_footer_t), SEEK_END);
|
||||
zupt_footer_t ft;
|
||||
if (fread(&ft, sizeof(ft), 1, f) != 1) { fclose(f); return ZUPT_ERR_CORRUPT; }
|
||||
if (ft.footer_magic[0] != 'Z' || ft.footer_magic[1] != 'E') {
|
||||
fclose(f); return ZUPT_ERR_BAD_MAGIC;
|
||||
if (fread(&ft, sizeof(ft), 1, f) != 1) {
|
||||
fclose(f);
|
||||
return ZUPT_ERR_CORRUPT;
|
||||
}
|
||||
if (ft.footer_magic[0] != 'Z' || ft.footer_magic[1] != 'E' ||
|
||||
ft.footer_magic[2] != 'N' || ft.footer_magic[3] != 'D') {
|
||||
fclose(f);
|
||||
fprintf(stderr, "Error: Invalid footer magic\n");
|
||||
return ZUPT_ERR_BAD_MAGIC;
|
||||
}
|
||||
|
||||
/* Seek to first data block (skip archive header + encryption header) */
|
||||
fseeko(f, sizeof(zupt_archive_header_t), SEEK_SET);
|
||||
if (hdr.global_flags & ZUPT_FLAG_ENCRYPTED) {
|
||||
/* Skip past encryption header block to reach data blocks */
|
||||
fseeko(f, (int64_t)hdr.encryption_header_off, SEEK_SET);
|
||||
/* Skip the enc header block entirely */
|
||||
uint8_t skip2[7];
|
||||
if (fread(skip2, 1, 7, f) != 7) { fclose(f); return ZUPT_ERR_CORRUPT; }
|
||||
uint64_t u1, u2; zupt_read_varint(f, &u1); zupt_read_varint(f, &u2);
|
||||
uint8_t sk8[8];
|
||||
if (fread(sk8, 1, 8, f) != 8) { fclose(f); return ZUPT_ERR_CORRUPT; }
|
||||
fseeko(f, (int64_t)u2, SEEK_CUR);
|
||||
}
|
||||
/* ─── Seek back to first data block ─── */
|
||||
fseeko(f, after_enc_pos, SEEK_SET);
|
||||
|
||||
/* Open target for writing */
|
||||
/* ─── Open target for writing ───
|
||||
* Block devices require raw POSIX I/O (open/write) because stdio
|
||||
* buffering can cause misaligned or partial writes that corrupt data.
|
||||
* O_SYNC ensures each write is flushed to the device before returning.
|
||||
* For loop devices, this ensures data reaches the backing file. */
|
||||
#ifdef _WIN32
|
||||
FILE *tgt = fopen(target_path, "wb");
|
||||
if (!tgt) {
|
||||
fprintf(stderr, "Error: Cannot open target '%s': %s\n", target_path, strerror(errno));
|
||||
fprintf(stderr, "Error: Cannot open target '%s': %s\n",
|
||||
target_path, strerror(errno));
|
||||
fclose(f);
|
||||
return ZUPT_ERR_IO;
|
||||
}
|
||||
#else
|
||||
struct stat tgt_st;
|
||||
int tgt_fd;
|
||||
int is_block_dev = 0;
|
||||
|
||||
/* For block/char devices: O_WRONLY | O_SYNC (no truncate, sync writes).
|
||||
* For regular files: O_WRONLY | O_CREAT | O_TRUNC. */
|
||||
if (stat(target_path, &tgt_st) == 0 &&
|
||||
(S_ISBLK(tgt_st.st_mode) || S_ISCHR(tgt_st.st_mode))) {
|
||||
tgt_fd = open(target_path, O_WRONLY | O_SYNC);
|
||||
is_block_dev = 1;
|
||||
} else {
|
||||
tgt_fd = open(target_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
}
|
||||
if (tgt_fd < 0) {
|
||||
fprintf(stderr, "Error: Cannot open target '%s': %s\n",
|
||||
target_path, strerror(errno));
|
||||
fclose(f);
|
||||
return ZUPT_ERR_IO;
|
||||
}
|
||||
#endif
|
||||
|
||||
fprintf(stderr, " Restoring disk image to: %s\n", target_path);
|
||||
fprintf(stderr, " Blocks: %llu\n\n", (unsigned long long)ft.total_blocks);
|
||||
|
||||
time_t start_time = time(NULL);
|
||||
uint64_t total_written = 0;
|
||||
uint64_t block_seq = 0;
|
||||
int errors = 0;
|
||||
|
||||
/* ─── Read and restore blocks sequentially ─── */
|
||||
for (uint64_t bi = 0; bi < ft.total_blocks; bi++) {
|
||||
/* Read block header */
|
||||
uint8_t bm[2];
|
||||
if (fread(bm, 1, 2, f) != 2) { errors++; break; }
|
||||
if (bm[0] != ZUPT_BLOCK_MAGIC_0 || bm[1] != ZUPT_BLOCK_MAGIC_1) {
|
||||
errors++; break;
|
||||
}
|
||||
zupt_block_t blk;
|
||||
zupt_error_t rerr = read_block(f, &blk);
|
||||
|
||||
uint8_t block_type;
|
||||
if (fread(&block_type, 1, 1, f) != 1) { errors++; break; }
|
||||
if (block_type == ZUPT_BLOCK_INDEX) break; /* reached index */
|
||||
|
||||
uint8_t c16[2], f16[2];
|
||||
if (fread(c16, 1, 2, f) != 2) { errors++; break; }
|
||||
if (fread(f16, 1, 2, f) != 2) { errors++; break; }
|
||||
uint16_t codec = (uint16_t)c16[0] | ((uint16_t)c16[1] << 8);
|
||||
uint16_t bflags = (uint16_t)f16[0] | ((uint16_t)f16[1] << 8);
|
||||
|
||||
uint64_t uncomp_size, comp_size, checksum;
|
||||
if (zupt_read_varint(f, &uncomp_size) < 0) { errors++; break; }
|
||||
if (zupt_read_varint(f, &comp_size) < 0) { errors++; break; }
|
||||
uint8_t ck8[8];
|
||||
if (fread(ck8, 1, 8, f) != 8) { errors++; break; }
|
||||
checksum = 0;
|
||||
for (int i = 7; i >= 0; i--) checksum = (checksum << 8) | ck8[i];
|
||||
|
||||
/* Read payload */
|
||||
if (comp_size > ZUPT_MAX_BLOCK_SZ + 1024) { errors++; break; }
|
||||
uint8_t *payload = (uint8_t *)malloc((size_t)comp_size);
|
||||
if (!payload) { errors++; break; }
|
||||
if (fread(payload, 1, (size_t)comp_size, f) != (size_t)comp_size) {
|
||||
free(payload); errors++; break;
|
||||
}
|
||||
|
||||
/* Decrypt if needed */
|
||||
const uint8_t *comp_data = payload;
|
||||
size_t comp_len = (size_t)comp_size;
|
||||
uint8_t *dec_payload = NULL;
|
||||
|
||||
if (bflags & ZUPT_BFLAG_ENCRYPTED) {
|
||||
if (!opts->keyring.active) { free(payload); errors++; break; }
|
||||
size_t dec_len;
|
||||
dec_payload = zupt_decrypt_buffer(&opts->keyring, comp_data, comp_len, bi, &dec_len);
|
||||
if (!dec_payload) {
|
||||
fprintf(stderr, " Block %llu: decryption failed\n", (unsigned long long)bi);
|
||||
free(payload); errors++; break;
|
||||
}
|
||||
comp_data = dec_payload;
|
||||
comp_len = dec_len;
|
||||
}
|
||||
|
||||
/* Decompress */
|
||||
uint8_t *out_buf = (uint8_t *)malloc((size_t)uncomp_size);
|
||||
if (!out_buf) { free(dec_payload); free(payload); errors++; break; }
|
||||
|
||||
if (codec == ZUPT_CODEC_STORE) {
|
||||
if (comp_len >= (size_t)uncomp_size)
|
||||
memcpy(out_buf, comp_data, (size_t)uncomp_size);
|
||||
else { free(out_buf); free(dec_payload); free(payload); errors++; break; }
|
||||
} else if (codec == ZUPT_CODEC_VAPTVUPT) {
|
||||
int64_t dsz = vvz_decompress(comp_data, comp_len, out_buf, (size_t)uncomp_size);
|
||||
if (dsz < 0 || (size_t)dsz != (size_t)uncomp_size) {
|
||||
free(out_buf); free(dec_payload); free(payload); errors++; break;
|
||||
}
|
||||
} else if (codec == ZUPT_CODEC_ZUPT_LZHP) {
|
||||
if (comp_len < 1) { free(out_buf); free(dec_payload); free(payload); errors++; break; }
|
||||
uint8_t pflag = comp_data[0];
|
||||
if (pflag & 0x01) {
|
||||
if (comp_len < 257) { free(out_buf); free(dec_payload); free(payload); errors++; break; }
|
||||
uint8_t pred[256]; memcpy(pred, comp_data + 1, 256);
|
||||
uint8_t *temp = (uint8_t *)malloc((size_t)uncomp_size);
|
||||
if (!temp) { free(out_buf); free(dec_payload); free(payload); errors++; break; }
|
||||
size_t r = zupt_lzh_decompress(comp_data + 257, comp_len - 257, temp, (size_t)uncomp_size);
|
||||
if (r == (size_t)uncomp_size)
|
||||
zupt_predict_decode(temp, out_buf, (size_t)uncomp_size, pred);
|
||||
else errors++;
|
||||
free(temp);
|
||||
} else {
|
||||
size_t r = zupt_lzh_decompress(comp_data + 1, comp_len - 1, out_buf, (size_t)uncomp_size);
|
||||
if (r != (size_t)uncomp_size) errors++;
|
||||
}
|
||||
} else if (codec == ZUPT_CODEC_ZUPT_LZH) {
|
||||
size_t r = zupt_lzh_decompress(comp_data, comp_len, out_buf, (size_t)uncomp_size);
|
||||
if (r != (size_t)uncomp_size) errors++;
|
||||
} else if (codec == ZUPT_CODEC_ZUPT_LZ) {
|
||||
size_t r = zupt_lz_decompress(comp_data, comp_len, out_buf, (size_t)uncomp_size);
|
||||
if (r != (size_t)uncomp_size) errors++;
|
||||
} else {
|
||||
if (rerr != ZUPT_OK) {
|
||||
fprintf(stderr, " Block %llu: read error (%s)\n",
|
||||
(unsigned long long)bi, zupt_strerror(rerr));
|
||||
errors++;
|
||||
break;
|
||||
}
|
||||
|
||||
free(dec_payload);
|
||||
free(payload);
|
||||
/* Skip non-data blocks (index, etc.) */
|
||||
if (blk.block_type == ZUPT_BLOCK_INDEX) {
|
||||
free(blk.payload);
|
||||
break; /* Reached index — all data blocks done */
|
||||
}
|
||||
if (blk.block_type != ZUPT_BLOCK_DATA) {
|
||||
free(blk.payload);
|
||||
continue; /* Skip unknown block types */
|
||||
}
|
||||
|
||||
if (errors) { free(out_buf); break; }
|
||||
/* Decompress + decrypt + verify checksum */
|
||||
uint8_t *out_buf = NULL;
|
||||
size_t out_len = 0;
|
||||
zupt_error_t derr = decompress_block(&blk, &opts->keyring,
|
||||
block_seq, &out_buf, &out_len);
|
||||
free(blk.payload);
|
||||
|
||||
/* Verify checksum */
|
||||
uint64_t actual_ck = zupt_xxh64(out_buf, (size_t)uncomp_size, 0);
|
||||
if (actual_ck != checksum) {
|
||||
fprintf(stderr, " Block %llu: checksum mismatch\n", (unsigned long long)bi);
|
||||
free(out_buf); errors++; break;
|
||||
if (derr != ZUPT_OK) {
|
||||
fprintf(stderr, " Block %llu: decompression/checksum failed (%s)\n",
|
||||
(unsigned long long)bi, zupt_strerror(derr));
|
||||
errors++;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Write to target */
|
||||
if (fwrite(out_buf, 1, (size_t)uncomp_size, tgt) != (size_t)uncomp_size) {
|
||||
free(out_buf); errors++; break;
|
||||
int write_ok = 0;
|
||||
#ifdef _WIN32
|
||||
write_ok = (fwrite(out_buf, 1, out_len, tgt) == out_len);
|
||||
#else
|
||||
{
|
||||
size_t written = 0;
|
||||
while (written < out_len) {
|
||||
ssize_t w = write(tgt_fd, out_buf + written, out_len - written);
|
||||
if (w <= 0) break;
|
||||
written += (size_t)w;
|
||||
}
|
||||
write_ok = (written == out_len);
|
||||
}
|
||||
total_written += uncomp_size;
|
||||
#endif
|
||||
if (!write_ok) {
|
||||
fprintf(stderr, " Block %llu: write error (%s)\n",
|
||||
(unsigned long long)bi, strerror(errno));
|
||||
free(out_buf);
|
||||
errors++;
|
||||
break;
|
||||
}
|
||||
|
||||
total_written += out_len;
|
||||
block_seq++;
|
||||
free(out_buf);
|
||||
|
||||
/* Progress */
|
||||
|
|
@ -771,7 +688,19 @@ zupt_error_t zupt_disk_restore(const char *archive_path, const char *target_path
|
|||
}
|
||||
|
||||
fclose(f);
|
||||
#ifdef _WIN32
|
||||
fclose(tgt);
|
||||
#else
|
||||
if (tgt_fd >= 0) {
|
||||
fsync(tgt_fd); /* Flush file descriptor buffers */
|
||||
close(tgt_fd);
|
||||
}
|
||||
if (is_block_dev) {
|
||||
sync(); /* Force kernel to flush ALL dirty pages to disk.
|
||||
* Critical for loop devices: fsync on the loop fd
|
||||
* may not flush the backing file's page cache. */
|
||||
}
|
||||
#endif
|
||||
|
||||
if (errors > 0) {
|
||||
fprintf(stderr, "\n Restore FAILED: %d error(s)\n", errors);
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -249,12 +249,94 @@ void zupt_collect_files(zupt_filelist_t *fl, const char *path, const char *base)
|
|||
* WRITE / READ HELPERS (LE-safe, error-checked)
|
||||
* ═══════════════════════════════════════════════════════════════════ */
|
||||
|
||||
static int w8(FILE*f,uint8_t v){return fwrite(&v,1,1,f)==1?0:-1;}
|
||||
static int w16le(FILE*f,uint16_t v){uint8_t b[2];zupt_le16_put(b,v);return fwrite(b,1,2,f)==2?0:-1;}
|
||||
static int w64le(FILE*f,uint64_t v){uint8_t b[8];zupt_le64_put(b,v);return fwrite(b,1,8,f)==8?0:-1;}
|
||||
int zupt_w8(FILE*f,uint8_t v){return fwrite(&v,1,1,f)==1?0:-1;}
|
||||
int zupt_w16le(FILE*f,uint16_t v){uint8_t b[2];zupt_le16_put(b,v);return fwrite(b,1,2,f)==2?0:-1;}
|
||||
int zupt_w64le(FILE*f,uint64_t v){uint8_t b[8];zupt_le64_put(b,v);return fwrite(b,1,8,f)==8?0:-1;}
|
||||
static int r16le(FILE*f,uint16_t*v){uint8_t b[2];if(fread(b,1,2,f)!=2)return -1;*v=zupt_le16_get(b);return 0;}
|
||||
static int r64le(FILE*f,uint64_t*v){uint8_t b[8];if(fread(b,1,8,f)!=8)return -1;*v=zupt_le64_get(b);return 0;}
|
||||
|
||||
/* Aliases for internal use (backward compat with existing code) */
|
||||
#define w8 zupt_w8
|
||||
#define w16le zupt_w16le
|
||||
#define w64le zupt_w64le
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
* SHARED ENCRYPTION HEADER WRITER
|
||||
*
|
||||
* Used by BOTH zupt_compress_files() and zupt_disk_backup().
|
||||
* Writes the encryption header block and updates hdr.encryption_header_off.
|
||||
* After return, the file position is at the end (ready for data blocks).
|
||||
* ═══════════════════════════════════════════════════════════════════ */
|
||||
zupt_error_t write_enc_header(FILE *out, zupt_archive_header_t *hdr,
|
||||
zupt_options_t *opts) {
|
||||
hdr->encryption_header_off = (uint64_t)ftello(out);
|
||||
|
||||
if (opts->pq_mode) {
|
||||
/* ─── PQ HYBRID MODE ─── */
|
||||
hdr->global_flags |= ZUPT_FLAG_PQ_HYBRID;
|
||||
|
||||
uint8_t enc_hdr_buf[1200];
|
||||
size_t enc_hdr_len = 0;
|
||||
if (!opts->quiet)
|
||||
fprintf(stderr, " Post-quantum key encapsulation (ML-KEM-768 + X25519)...\n");
|
||||
if (zupt_hybrid_encrypt_init(&opts->keyring, opts->keyfile,
|
||||
enc_hdr_buf, &enc_hdr_len) != 0) {
|
||||
fprintf(stderr, "Error: PQ hybrid key encapsulation failed.\n");
|
||||
return ZUPT_ERR_AUTH_FAIL;
|
||||
}
|
||||
|
||||
zupt_w8(out, ZUPT_BLOCK_MAGIC_0); zupt_w8(out, ZUPT_BLOCK_MAGIC_1);
|
||||
zupt_w8(out, ZUPT_BLOCK_ENC_HEADER);
|
||||
zupt_w16le(out, ZUPT_CODEC_STORE); zupt_w16le(out, 0);
|
||||
zupt_write_varint(out, enc_hdr_len);
|
||||
zupt_write_varint(out, enc_hdr_len);
|
||||
zupt_w64le(out, zupt_xxh64(enc_hdr_buf, enc_hdr_len, 0));
|
||||
if (fwrite(enc_hdr_buf, 1, enc_hdr_len, out) != enc_hdr_len)
|
||||
return ZUPT_ERR_IO;
|
||||
|
||||
/* Re-write header with PQ flag */
|
||||
fseeko(out, 0, SEEK_SET);
|
||||
if (fwrite(hdr, sizeof(*hdr), 1, out) != 1) return ZUPT_ERR_IO;
|
||||
fseeko(out, 0, SEEK_END);
|
||||
|
||||
if (!opts->quiet)
|
||||
fprintf(stderr, " Encryption: PQ Hybrid (ML-KEM-768 + X25519) + AES-256-CTR + HMAC-SHA256\n\n");
|
||||
} else {
|
||||
/* ─── PASSWORD MODE (PBKDF2) ─── */
|
||||
uint8_t salt[ZUPT_SALT_SIZE], nonce[ZUPT_NONCE_SIZE];
|
||||
zupt_random_bytes(salt, ZUPT_SALT_SIZE);
|
||||
zupt_random_bytes(nonce, ZUPT_NONCE_SIZE);
|
||||
|
||||
if (!opts->quiet)
|
||||
fprintf(stderr, " Deriving encryption key (PBKDF2-SHA256, %d iterations)...\n",
|
||||
ZUPT_KDF_ITERATIONS);
|
||||
zupt_derive_keys(&opts->keyring, opts->password, salt, nonce, ZUPT_KDF_ITERATIONS);
|
||||
|
||||
uint8_t enc_hdr[53];
|
||||
enc_hdr[0] = ZUPT_ENC_PBKDF2;
|
||||
memcpy(enc_hdr + 1, salt, 32);
|
||||
memcpy(enc_hdr + 33, nonce, 16);
|
||||
uint32_t iter = ZUPT_KDF_ITERATIONS;
|
||||
memcpy(enc_hdr + 49, &iter, 4);
|
||||
|
||||
zupt_w8(out, ZUPT_BLOCK_MAGIC_0); zupt_w8(out, ZUPT_BLOCK_MAGIC_1);
|
||||
zupt_w8(out, ZUPT_BLOCK_ENC_HEADER);
|
||||
zupt_w16le(out, ZUPT_CODEC_STORE); zupt_w16le(out, 0);
|
||||
zupt_write_varint(out, 53); zupt_write_varint(out, 53);
|
||||
zupt_w64le(out, zupt_xxh64(enc_hdr, 53, 0));
|
||||
if (fwrite(enc_hdr, 1, 53, out) != 53) return ZUPT_ERR_IO;
|
||||
|
||||
fseeko(out, 0, SEEK_SET);
|
||||
if (fwrite(hdr, sizeof(*hdr), 1, out) != 1) return ZUPT_ERR_IO;
|
||||
fseeko(out, 0, SEEK_END);
|
||||
|
||||
if (!opts->quiet)
|
||||
fprintf(stderr, " Encryption: AES-256-CTR + HMAC-SHA256 (Encrypt-then-MAC)\n\n");
|
||||
}
|
||||
|
||||
return ZUPT_OK;
|
||||
}
|
||||
|
||||
static void ensure_dirs(const char *path) {
|
||||
char tmp[ZUPT_MAX_PATH]; strncpy(tmp, path, sizeof(tmp)-1); tmp[sizeof(tmp)-1]='\0';
|
||||
for (char *p=tmp+1;*p;p++)
|
||||
|
|
@ -330,63 +412,8 @@ zupt_error_t zupt_compress_files(const char *output_path,
|
|||
if (fwrite(&hdr, sizeof(hdr), 1, out) != 1) write_err = 1;
|
||||
|
||||
if (opts->encrypt) {
|
||||
hdr.encryption_header_off = safe_ftello(out);
|
||||
|
||||
if (opts->pq_mode) {
|
||||
/* ─── PQ HYBRID MODE ─── */
|
||||
if (hdr.global_flags & ZUPT_FLAG_PQ_HYBRID) {} /* already set */
|
||||
hdr.global_flags |= ZUPT_FLAG_PQ_HYBRID;
|
||||
|
||||
uint8_t enc_hdr_buf[1200]; /* enc_type(1) + ct(1088) + eph_pk(32) + nonce(16) = 1137 */
|
||||
size_t enc_hdr_len = 0;
|
||||
if (!opts->quiet) fprintf(stderr, " Post-quantum key encapsulation (ML-KEM-768 + X25519)...\n");
|
||||
if (zupt_hybrid_encrypt_init(&opts->keyring, opts->keyfile, enc_hdr_buf, &enc_hdr_len) != 0) {
|
||||
fprintf(stderr, "Error: PQ hybrid key encapsulation failed.\n");
|
||||
fclose(out); return ZUPT_ERR_AUTH_FAIL;
|
||||
}
|
||||
|
||||
w8(out, ZUPT_BLOCK_MAGIC_0); w8(out, ZUPT_BLOCK_MAGIC_1);
|
||||
w8(out, ZUPT_BLOCK_ENC_HEADER);
|
||||
w16le(out, ZUPT_CODEC_STORE); w16le(out, 0);
|
||||
zupt_write_varint(out, enc_hdr_len); zupt_write_varint(out, enc_hdr_len);
|
||||
w64le(out, zupt_xxh64(enc_hdr_buf, enc_hdr_len, 0));
|
||||
if (fwrite(enc_hdr_buf, 1, enc_hdr_len, out) != enc_hdr_len) write_err = 1;
|
||||
|
||||
fseeko(out, 0, SEEK_SET);
|
||||
if (fwrite(&hdr, sizeof(hdr), 1, out) != 1) write_err = 1;
|
||||
fseeko(out, 0, SEEK_END);
|
||||
|
||||
if (!opts->quiet) fprintf(stderr, " Encryption: PQ Hybrid (ML-KEM-768 + X25519) + AES-256-CTR + HMAC-SHA256\n\n");
|
||||
} else {
|
||||
/* ─── PASSWORD MODE (PBKDF2, unchanged from v0.5.1) ─── */
|
||||
uint8_t salt[ZUPT_SALT_SIZE], nonce[ZUPT_NONCE_SIZE];
|
||||
zupt_random_bytes(salt, ZUPT_SALT_SIZE);
|
||||
zupt_random_bytes(nonce, ZUPT_NONCE_SIZE);
|
||||
|
||||
if (!opts->quiet) fprintf(stderr, " Deriving encryption key (PBKDF2-SHA256, %d iterations)...\n", ZUPT_KDF_ITERATIONS);
|
||||
zupt_derive_keys(&opts->keyring, opts->password, salt, nonce, ZUPT_KDF_ITERATIONS);
|
||||
|
||||
/* enc_type prefix for backward compat detection */
|
||||
uint8_t enc_hdr[53]; /* enc_type(1) + salt(32) + nonce(16) + iter(4) */
|
||||
enc_hdr[0] = ZUPT_ENC_PBKDF2;
|
||||
memcpy(enc_hdr + 1, salt, 32);
|
||||
memcpy(enc_hdr + 33, nonce, 16);
|
||||
uint32_t iter = ZUPT_KDF_ITERATIONS;
|
||||
memcpy(enc_hdr + 49, &iter, 4);
|
||||
|
||||
w8(out, ZUPT_BLOCK_MAGIC_0); w8(out, ZUPT_BLOCK_MAGIC_1);
|
||||
w8(out, ZUPT_BLOCK_ENC_HEADER);
|
||||
w16le(out, ZUPT_CODEC_STORE); w16le(out, 0);
|
||||
zupt_write_varint(out, 53); zupt_write_varint(out, 53);
|
||||
w64le(out, zupt_xxh64(enc_hdr, 53, 0));
|
||||
if (fwrite(enc_hdr, 1, 53, out) != 53) write_err = 1;
|
||||
|
||||
fseeko(out, 0, SEEK_SET);
|
||||
if (fwrite(&hdr, sizeof(hdr), 1, out) != 1) write_err = 1;
|
||||
fseeko(out, 0, SEEK_END);
|
||||
|
||||
if (!opts->quiet) fprintf(stderr, " Encryption: AES-256-CTR + HMAC-SHA256 (Encrypt-then-MAC)\n\n");
|
||||
}
|
||||
zupt_error_t enc_err = write_enc_header(out, &hdr, opts);
|
||||
if (enc_err != ZUPT_OK) { fclose(out); return enc_err; }
|
||||
}
|
||||
|
||||
zupt_index_entry_t *index = (zupt_index_entry_t*)calloc((size_t)num_files, sizeof(zupt_index_entry_t));
|
||||
|
|
@ -774,25 +801,8 @@ zupt_error_t zupt_compress_solid(const char *output_path,
|
|||
if (fwrite(&hdr, sizeof(hdr), 1, out) != 1) write_err = 1;
|
||||
|
||||
if (opts->encrypt) {
|
||||
hdr.encryption_header_off = safe_ftello(out);
|
||||
uint8_t salt[ZUPT_SALT_SIZE], nonce[ZUPT_NONCE_SIZE];
|
||||
zupt_random_bytes(salt, ZUPT_SALT_SIZE);
|
||||
zupt_random_bytes(nonce, ZUPT_NONCE_SIZE);
|
||||
if (!opts->quiet) fprintf(stderr, " Deriving encryption key...\n");
|
||||
zupt_derive_keys(&opts->keyring, opts->password, salt, nonce, ZUPT_KDF_ITERATIONS);
|
||||
uint8_t enc_hdr[52];
|
||||
memcpy(enc_hdr, salt, 32); memcpy(enc_hdr+32, nonce, 16);
|
||||
uint32_t iter = ZUPT_KDF_ITERATIONS; memcpy(enc_hdr+48, &iter, 4);
|
||||
w8(out, ZUPT_BLOCK_MAGIC_0); w8(out, ZUPT_BLOCK_MAGIC_1);
|
||||
w8(out, ZUPT_BLOCK_ENC_HEADER);
|
||||
w16le(out, ZUPT_CODEC_STORE); w16le(out, 0);
|
||||
zupt_write_varint(out, 52); zupt_write_varint(out, 52);
|
||||
w64le(out, zupt_xxh64(enc_hdr, 52, 0));
|
||||
if (fwrite(enc_hdr, 1, 52, out) != 52) write_err = 1;
|
||||
fseeko(out, 0, SEEK_SET);
|
||||
if (fwrite(&hdr, sizeof(hdr), 1, out) != 1) write_err = 1;
|
||||
fseeko(out, 0, SEEK_END);
|
||||
if (!opts->quiet) fprintf(stderr, " Encryption: AES-256-CTR + HMAC-SHA256 (Encrypt-then-MAC)\n\n");
|
||||
zupt_error_t enc_err = write_enc_header(out, &hdr, opts);
|
||||
if (enc_err != ZUPT_OK) { fclose(out); return enc_err; }
|
||||
}
|
||||
|
||||
zupt_index_entry_t *index = (zupt_index_entry_t*)calloc((size_t)num_files, sizeof(zupt_index_entry_t));
|
||||
|
|
@ -1036,7 +1046,7 @@ static zupt_error_t read_footer(FILE *f, zupt_footer_t *ft) {
|
|||
return ZUPT_OK;
|
||||
}
|
||||
|
||||
static zupt_error_t read_block(FILE *f, zupt_block_t *b) {
|
||||
zupt_error_t read_block(FILE *f, zupt_block_t *b) {
|
||||
uint8_t m[2];
|
||||
if (fread(m,1,2,f)!=2) return ZUPT_ERR_IO;
|
||||
if (m[0]!=ZUPT_BLOCK_MAGIC_0||m[1]!=ZUPT_BLOCK_MAGIC_1) return ZUPT_ERR_CORRUPT;
|
||||
|
|
@ -1058,7 +1068,7 @@ static zupt_error_t read_block(FILE *f, zupt_block_t *b) {
|
|||
return ZUPT_OK;
|
||||
}
|
||||
|
||||
static zupt_error_t decompress_block(const zupt_block_t *b, const zupt_keyring_t *kr,
|
||||
zupt_error_t decompress_block(const zupt_block_t *b, const zupt_keyring_t *kr,
|
||||
uint64_t block_seq, uint8_t **out, size_t *olen) {
|
||||
const uint8_t *comp_data = b->payload;
|
||||
size_t comp_len = (size_t)b->compressed_size;
|
||||
|
|
@ -1147,7 +1157,7 @@ done:
|
|||
return ZUPT_OK;
|
||||
}
|
||||
|
||||
static zupt_error_t read_enc_header(FILE *f, zupt_archive_header_t *hdr, zupt_options_t *opts) {
|
||||
zupt_error_t read_enc_header(FILE *f, zupt_archive_header_t *hdr, zupt_options_t *opts) {
|
||||
if (!(hdr->global_flags & ZUPT_FLAG_ENCRYPTED)) return ZUPT_OK;
|
||||
|
||||
fseeko(f, (int64_t)hdr->encryption_header_off, SEEK_SET);
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
BIN
src/zupt_lz.o
BIN
src/zupt_lz.o
Binary file not shown.
BIN
src/zupt_lzh.o
BIN
src/zupt_lzh.o
Binary file not shown.
BIN
src/zupt_main.o
BIN
src/zupt_main.o
Binary file not shown.
BIN
src/zupt_mlkem.o
BIN
src/zupt_mlkem.o
Binary file not shown.
BIN
src/zupt_mlock.o
BIN
src/zupt_mlock.o
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/zupt_xxh.o
BIN
src/zupt_xxh.o
Binary file not shown.
1
tmp/zupt_solid_pq2/in/a.txt
Normal file
1
tmp/zupt_solid_pq2/in/a.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
hello world
|
||||
1
tmp/zupt_solid_pq2/in/b.txt
Normal file
1
tmp/zupt_solid_pq2/in/b.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
test data 12345
|
||||
1
tmp/zupt_solid_pq3/in/a.txt
Normal file
1
tmp/zupt_solid_pq3/in/a.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
hello
|
||||
1
tmp/zupt_solid_pq_5304/in/a.txt
Normal file
1
tmp/zupt_solid_pq_5304/in/a.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
hello world
|
||||
1
tmp/zupt_solid_pq_5304/in/b.txt
Normal file
1
tmp/zupt_solid_pq_5304/in/b.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
test data 12345
|
||||
Loading…
Reference in a new issue