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