Skip to main content

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}