rust_par2/types.rs
1//! Data types for PAR2 file sets.
2
3use std::collections::HashMap;
4use std::fmt;
5
6/// 16-byte MD5 hash.
7pub type Md5Hash = [u8; 16];
8
9/// 16-byte identifier (Recovery Set ID, File ID, etc.).
10pub type Id16 = [u8; 16];
11
12/// A parsed PAR2 file set containing all metadata needed for verification.
13#[derive(Debug, Clone)]
14pub struct Par2FileSet {
15 /// Recovery Set ID — all packets in a set share this.
16 pub recovery_set_id: Id16,
17 /// Slice (block) size in bytes.
18 pub slice_size: u64,
19 /// Files described in this PAR2 set, keyed by File ID.
20 pub files: HashMap<Id16, Par2File>,
21 /// Number of recovery slices available (counted from RecoverySlice packets).
22 pub recovery_block_count: u32,
23 /// Creator software string, if present.
24 pub creator: Option<String>,
25}
26
27/// Metadata for a single file in the PAR2 set.
28#[derive(Debug, Clone)]
29pub struct Par2File {
30 /// File ID (MD5 of hash16k + hash + file_id internal data).
31 pub file_id: Id16,
32 /// Full file MD5 hash.
33 pub hash: Md5Hash,
34 /// MD5 hash of the first 16 KiB of the file.
35 pub hash_16k: Md5Hash,
36 /// File size in bytes.
37 pub size: u64,
38 /// Filename (from the PAR2 packet, UTF-8 or best-effort decoded).
39 pub filename: String,
40 /// Per-slice checksums (MD5 + CRC32), in order. From IFSC packets.
41 pub slices: Vec<SliceChecksum>,
42}
43
44/// Checksum data for a single slice (block) of a file.
45#[derive(Debug, Clone, Copy)]
46pub struct SliceChecksum {
47 /// MD5 hash of this slice.
48 pub md5: Md5Hash,
49 /// CRC32 of this slice (the full slice, zero-padded if it's the last partial slice).
50 pub crc32: u32,
51}
52
53/// Result of verifying a PAR2 file set against actual files on disk.
54#[derive(Debug)]
55pub struct VerifyResult {
56 /// Files that are intact (MD5 matches).
57 pub intact: Vec<VerifiedFile>,
58 /// Files that are damaged (exist but MD5 doesn't match).
59 pub damaged: Vec<DamagedFile>,
60 /// Files that are missing entirely.
61 pub missing: Vec<MissingFile>,
62 /// Total number of recovery blocks available in the PAR2 set.
63 pub recovery_blocks_available: u32,
64 /// Whether repair is theoretically possible (enough recovery blocks).
65 pub repair_possible: bool,
66}
67
68impl VerifyResult {
69 /// Returns true if all files are intact.
70 pub fn all_correct(&self) -> bool {
71 self.damaged.is_empty() && self.missing.is_empty()
72 }
73
74 /// Total number of damaged/missing blocks that need repair.
75 pub fn blocks_needed(&self) -> u32 {
76 let damaged_blocks: u32 = self.damaged.iter().map(|d| d.damaged_block_count).sum();
77 let missing_blocks: u32 = self.missing.iter().map(|m| m.block_count).sum();
78 damaged_blocks + missing_blocks
79 }
80}
81
82/// A file that passed verification.
83#[derive(Debug)]
84pub struct VerifiedFile {
85 pub filename: String,
86 pub size: u64,
87}
88
89/// A file that exists but has damage.
90#[derive(Debug)]
91pub struct DamagedFile {
92 pub filename: String,
93 pub size: u64,
94 /// Number of blocks in this file that are damaged.
95 pub damaged_block_count: u32,
96 /// Total blocks in this file.
97 pub total_block_count: u32,
98 /// Indices of the specific damaged blocks within this file (0-based).
99 pub damaged_block_indices: Vec<u32>,
100}
101
102/// A file that is missing entirely.
103#[derive(Debug)]
104pub struct MissingFile {
105 pub filename: String,
106 pub expected_size: u64,
107 /// Number of blocks this file contributes to the repair requirement.
108 pub block_count: u32,
109}
110
111impl fmt::Display for VerifyResult {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 if self.all_correct() {
114 write!(f, "All {} files correct", self.intact.len())
115 } else {
116 write!(
117 f,
118 "{} intact, {} damaged, {} missing — {} blocks needed, {} available",
119 self.intact.len(),
120 self.damaged.len(),
121 self.missing.len(),
122 self.blocks_needed(),
123 self.recovery_blocks_available,
124 )
125 }
126 }
127}