Skip to main content

par2_rs/
types.rs

1use std::{fmt, sync::Arc};
2
3pub(crate) const MAX_SLICES_PER_FILE: usize = 32_768;
4
5/// The PAR2 spec caps a recovery set at 32768 input slices in total; the
6/// constant-assignment sequence has exactly that many valid entries.
7pub(crate) const MAX_TOTAL_INPUT_SLICES: usize = 32_768;
8
9/// The Main packet parser's exact limit for the combined file-ID area.
10pub(crate) const MAX_FILES_PER_SET: usize = 32_768;
11
12/// 16-byte MD5-based file identifier.
13///
14/// The ordering is over the raw identifier bytes. It carries no meaning of its
15/// own; it exists so identifiers can be used as stable tie-breakers and in
16/// ordered collections.
17#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub struct FileId(pub(crate) [u8; 16]);
19
20impl FileId {
21    pub fn from_bytes(bytes: [u8; 16]) -> Self {
22        Self(bytes)
23    }
24
25    pub fn as_bytes(&self) -> &[u8; 16] {
26        &self.0
27    }
28}
29
30impl fmt::Debug for FileId {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        write!(f, "FileId(")?;
33        for b in &self.0 {
34            write!(f, "{b:02x}")?;
35        }
36        write!(f, ")")
37    }
38}
39
40impl fmt::Display for FileId {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        for b in &self.0 {
43            write!(f, "{b:02x}")?;
44        }
45        Ok(())
46    }
47}
48
49/// 16-byte recovery set identifier (MD5 of main packet body).
50#[derive(Clone, Copy, PartialEq, Eq, Hash)]
51pub struct RecoverySetId(pub(crate) [u8; 16]);
52
53impl RecoverySetId {
54    pub fn from_bytes(bytes: [u8; 16]) -> Self {
55        Self(bytes)
56    }
57
58    pub fn as_bytes(&self) -> &[u8; 16] {
59        &self.0
60    }
61}
62
63impl fmt::Debug for RecoverySetId {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        write!(f, "RecoverySetId(")?;
66        for b in &self.0 {
67            write!(f, "{b:02x}")?;
68        }
69        write!(f, ")")
70    }
71}
72
73impl fmt::Display for RecoverySetId {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        for b in &self.0 {
76            write!(f, "{b:02x}")?;
77        }
78        Ok(())
79    }
80}
81
82/// Index of a slice within a file.
83pub type SliceIndex = u32;
84
85/// Exponent of a recovery block.
86pub type RecoveryExponent = u32;
87
88/// CRC32 + MD5 checksum pair for a single file slice.
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub struct SliceChecksum {
91    pub crc32: u32,
92    pub md5: [u8; 16],
93}
94
95/// Lightweight cooperative cancellation token.
96///
97/// Clone and share across threads. Once cancelled, all holders observe it.
98#[derive(Clone)]
99pub struct CancellationToken(std::sync::Arc<std::sync::atomic::AtomicBool>);
100
101impl CancellationToken {
102    pub fn new() -> Self {
103        Self(std::sync::Arc::new(std::sync::atomic::AtomicBool::new(
104            false,
105        )))
106    }
107
108    pub fn cancel(&self) {
109        self.0.store(true, std::sync::atomic::Ordering::Relaxed);
110    }
111
112    pub fn is_cancelled(&self) -> bool {
113        self.0.load(std::sync::atomic::Ordering::Relaxed)
114    }
115}
116
117impl Default for CancellationToken {
118    fn default() -> Self {
119        Self::new()
120    }
121}
122
123/// Progress update from a long-running PAR2 operation.
124///
125/// Phases that hash concurrently (creation's source scan) deliver updates
126/// from multiple threads: values are individually accurate but arrive
127/// unordered, so consumers should latch maxima rather than assume each
128/// update supersedes the previous one.
129#[derive(Debug, Clone)]
130pub struct ProgressUpdate {
131    /// What stage the operation is in.
132    pub stage: ProgressStage,
133    /// Current item index (0-based).
134    pub current: u32,
135    /// Total number of items.
136    pub total: u32,
137    /// Cumulative bytes processed so far.
138    pub bytes_processed: u64,
139    /// Total bytes in this operation when known.
140    pub total_bytes: Option<u64>,
141    /// Which pass within `stage` produced this update.
142    pub phase: ProgressPhase,
143}
144
145/// Pass within a [`ProgressStage`] that runs more than one of them.
146///
147/// `current`, `total`, and `bytes_processed` are counted per pass, so a stage
148/// with several passes restarts them at every pass boundary. The counts alone
149/// cannot mark that boundary: creation's source scan counts files while its
150/// recovery encode counts stripes, and the two totals coincide whenever a set
151/// has as many files as the encoder has stripes. Consumers that reset
152/// per-pass state must key on this field, never on a change in `total`.
153#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
154pub enum ProgressPhase {
155    /// The stage runs a single pass, or the emitter does not subdivide it.
156    #[default]
157    Whole,
158    /// Creation's source scan: reading and hashing the input files.
159    SourceScan,
160    /// Creation's recovery encode: computing the recovery slices.
161    RecoveryEncode,
162}
163
164/// Stage of a PAR2 operation for progress reporting.
165#[derive(Debug, Clone, Copy, PartialEq, Eq)]
166pub enum ProgressStage {
167    /// Reading and hashing source files for PAR2 creation.
168    Creating,
169    /// Verifying file integrity.
170    Verifying,
171    /// Reading recovery slice data.
172    ReadingRecovery,
173    /// Reconstructing missing slices via Reed-Solomon.
174    Repairing,
175    /// Writing repaired data back to files.
176    WritingRepaired,
177}
178
179/// Callback type for progress reporting.
180pub type ProgressCallback = Arc<dyn Fn(ProgressUpdate) + Send + Sync>;
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn file_id_display() {
188        let id = FileId([
189            0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54,
190            0x32, 0x10,
191        ]);
192        assert_eq!(format!("{id}"), "0123456789abcdeffedcba9876543210");
193    }
194
195    #[test]
196    fn file_id_debug() {
197        let id = FileId([0; 16]);
198        let dbg = format!("{id:?}");
199        assert!(dbg.starts_with("FileId("));
200        assert!(dbg.ends_with(')'));
201    }
202
203    #[test]
204    fn recovery_set_id_roundtrip() {
205        let bytes = [1u8; 16];
206        let id = RecoverySetId::from_bytes(bytes);
207        assert_eq!(*id.as_bytes(), bytes);
208    }
209
210    #[test]
211    fn file_id_equality() {
212        let a = FileId([0xAA; 16]);
213        let b = FileId([0xAA; 16]);
214        let c = FileId([0xBB; 16]);
215        assert_eq!(a, b);
216        assert_ne!(a, c);
217    }
218}