diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bebffb..1ec49d9 100644 --- a/CHANGELOG.md +++ b/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 | diff --git a/Makefile b/Makefile index 8f55d07..9ef3036 100644 --- a/Makefile +++ b/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 ". 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 diff --git a/README.md b/README.md index ec54b03..d5bcd1b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ![Build](https://img.shields.io/badge/build-passing-brightgreen) ![License](https://img.shields.io/badge/license-MIT-blue) -![Version](https://img.shields.io/badge/version-2.1.2-orange) +![Version](https://img.shields.io/badge/version-2.1.3-orange) ![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20Windows-lightgrey) ![openSUSE](https://img.shields.io/badge/platform-openSUSE-73BA25?logo=opensuse&logoColor=white) @@ -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. diff --git a/ROADMAP.md b/ROADMAP.md index 7a7ed24..8680798 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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 diff --git a/include/zupt.h b/include/zupt.h index 9c67669..7c72753 100644 --- a/include/zupt.h +++ b/include/zupt.h @@ -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 diff --git a/jasmin/zupt_aes_ctr.o b/jasmin/zupt_aes_ctr.o deleted file mode 100644 index 29bffc9..0000000 Binary files a/jasmin/zupt_aes_ctr.o and /dev/null differ diff --git a/jasmin/zupt_aes_ctr4.o b/jasmin/zupt_aes_ctr4.o deleted file mode 100644 index ca3ea4f..0000000 Binary files a/jasmin/zupt_aes_ctr4.o and /dev/null differ diff --git a/jasmin/zupt_mac_verify.o b/jasmin/zupt_mac_verify.o deleted file mode 100644 index cb4719a..0000000 Binary files a/jasmin/zupt_mac_verify.o and /dev/null differ diff --git a/jasmin/zupt_mlkem_select.o b/jasmin/zupt_mlkem_select.o deleted file mode 100644 index 699484f..0000000 Binary files a/jasmin/zupt_mlkem_select.o and /dev/null differ diff --git a/jasmin/zupt_x25519_fe.o b/jasmin/zupt_x25519_fe.o deleted file mode 100644 index f361a53..0000000 Binary files a/jasmin/zupt_x25519_fe.o and /dev/null differ diff --git a/src/vaptvupt_api.o b/src/vaptvupt_api.o deleted file mode 100644 index fdd79d9..0000000 Binary files a/src/vaptvupt_api.o and /dev/null differ diff --git a/src/vv_ans.o b/src/vv_ans.o deleted file mode 100644 index 5b83422..0000000 Binary files a/src/vv_ans.o and /dev/null differ diff --git a/src/vv_decoder.o b/src/vv_decoder.o deleted file mode 100644 index 48a878a..0000000 Binary files a/src/vv_decoder.o and /dev/null differ diff --git a/src/vv_encoder.o b/src/vv_encoder.o deleted file mode 100644 index ab5ef47..0000000 Binary files a/src/vv_encoder.o and /dev/null differ diff --git a/src/vv_huffman.o b/src/vv_huffman.o deleted file mode 100644 index f618f3c..0000000 Binary files a/src/vv_huffman.o and /dev/null differ diff --git a/src/vv_simd.o b/src/vv_simd.o deleted file mode 100644 index c7e7d65..0000000 Binary files a/src/vv_simd.o and /dev/null differ diff --git a/src/zupt_aes256.o b/src/zupt_aes256.o deleted file mode 100644 index 1deab45..0000000 Binary files a/src/zupt_aes256.o and /dev/null differ diff --git a/src/zupt_cpuid.o b/src/zupt_cpuid.o deleted file mode 100644 index 20f66ac..0000000 Binary files a/src/zupt_cpuid.o and /dev/null differ diff --git a/src/zupt_crypto.o b/src/zupt_crypto.o deleted file mode 100644 index 7eae40c..0000000 Binary files a/src/zupt_crypto.o and /dev/null differ diff --git a/src/zupt_disk.c b/src/zupt_disk.c index f6ff876..c008801 100644 --- a/src/zupt_disk.c +++ b/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); diff --git a/src/zupt_filetype.o b/src/zupt_filetype.o deleted file mode 100644 index beb37e1..0000000 Binary files a/src/zupt_filetype.o and /dev/null differ diff --git a/src/zupt_format.c b/src/zupt_format.c index 58f00d5..d7ef4b3 100644 --- a/src/zupt_format.c +++ b/src/zupt_format.c @@ -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); diff --git a/src/zupt_format.o b/src/zupt_format.o deleted file mode 100644 index ee81b98..0000000 Binary files a/src/zupt_format.o and /dev/null differ diff --git a/src/zupt_keccak.o b/src/zupt_keccak.o deleted file mode 100644 index 0eb452a..0000000 Binary files a/src/zupt_keccak.o and /dev/null differ diff --git a/src/zupt_lz.o b/src/zupt_lz.o deleted file mode 100644 index 745aca9..0000000 Binary files a/src/zupt_lz.o and /dev/null differ diff --git a/src/zupt_lzh.o b/src/zupt_lzh.o deleted file mode 100644 index cfe6abd..0000000 Binary files a/src/zupt_lzh.o and /dev/null differ diff --git a/src/zupt_main.o b/src/zupt_main.o deleted file mode 100644 index 84ba761..0000000 Binary files a/src/zupt_main.o and /dev/null differ diff --git a/src/zupt_mlkem.o b/src/zupt_mlkem.o deleted file mode 100644 index 73dd132..0000000 Binary files a/src/zupt_mlkem.o and /dev/null differ diff --git a/src/zupt_mlock.o b/src/zupt_mlock.o deleted file mode 100644 index 7efb119..0000000 Binary files a/src/zupt_mlock.o and /dev/null differ diff --git a/src/zupt_parallel.o b/src/zupt_parallel.o deleted file mode 100644 index 3f94caa..0000000 Binary files a/src/zupt_parallel.o and /dev/null differ diff --git a/src/zupt_predict.o b/src/zupt_predict.o deleted file mode 100644 index 3208025..0000000 Binary files a/src/zupt_predict.o and /dev/null differ diff --git a/src/zupt_sha256.o b/src/zupt_sha256.o deleted file mode 100644 index 02039f7..0000000 Binary files a/src/zupt_sha256.o and /dev/null differ diff --git a/src/zupt_x25519.o b/src/zupt_x25519.o deleted file mode 100644 index 98bae2a..0000000 Binary files a/src/zupt_x25519.o and /dev/null differ diff --git a/src/zupt_xxh.o b/src/zupt_xxh.o deleted file mode 100644 index 7140e61..0000000 Binary files a/src/zupt_xxh.o and /dev/null differ diff --git a/tmp/zupt_solid_pq2/in/a.txt b/tmp/zupt_solid_pq2/in/a.txt new file mode 100644 index 0000000..3b18e51 --- /dev/null +++ b/tmp/zupt_solid_pq2/in/a.txt @@ -0,0 +1 @@ +hello world diff --git a/tmp/zupt_solid_pq2/in/b.txt b/tmp/zupt_solid_pq2/in/b.txt new file mode 100644 index 0000000..4866c3c --- /dev/null +++ b/tmp/zupt_solid_pq2/in/b.txt @@ -0,0 +1 @@ +test data 12345 diff --git a/tmp/zupt_solid_pq3/in/a.txt b/tmp/zupt_solid_pq3/in/a.txt new file mode 100644 index 0000000..ce01362 --- /dev/null +++ b/tmp/zupt_solid_pq3/in/a.txt @@ -0,0 +1 @@ +hello diff --git a/tmp/zupt_solid_pq_5304/in/a.txt b/tmp/zupt_solid_pq_5304/in/a.txt new file mode 100644 index 0000000..3b18e51 --- /dev/null +++ b/tmp/zupt_solid_pq_5304/in/a.txt @@ -0,0 +1 @@ +hello world diff --git a/tmp/zupt_solid_pq_5304/in/b.txt b/tmp/zupt_solid_pq_5304/in/b.txt new file mode 100644 index 0000000..4866c3c --- /dev/null +++ b/tmp/zupt_solid_pq_5304/in/b.txt @@ -0,0 +1 @@ +test data 12345