Skip to main content

streaming_crypto/core_api/recovery/
checkpoint.rs

1// ## 1. `src/recovery/checkpoint.rs`
2
3// Purpose: capture digest state + metadata for resumption.
4
5//! recovery/checkpoint.rs
6//! Defines checkpoint structures for resumable hashing and decryption.
7use std::any::Any;
8// trait from RustCrypto digest 0.11 (Standard in 2026)
9use digest::{array::Array, common::hazmat::SerializableState}; 
10use sha2::{Sha256, Sha512};
11use sha3::{Sha3_256, Sha3_512};
12use blake3::{Hasher as Blake3Hasher};
13use crate::crypto::{DigestError, digest::{DigestAlg, DigestState}};
14
15pub trait Checkpointable: Send + Sync {
16    fn export(&self) -> Vec<u8>;
17    fn segment_index(&self) -> u32;
18    fn summary(&self) -> String;
19    fn as_any(&self) -> &dyn Any; 
20}
21
22#[derive(Debug, Clone)]
23pub enum SerializedState {
24    Sha256(Array<u8, <Sha256 as SerializableState>::SerializedStateSize>),
25    Sha512(Array<u8, <Sha512 as SerializableState>::SerializedStateSize>),
26    Sha3_256(Array<u8, <Sha3_256 as SerializableState>::SerializedStateSize>),
27    Sha3_512(Array<u8, <Sha3_512 as SerializableState>::SerializedStateSize>),
28    /// Blake3 is now marked as NoState because it does not support resume in 1.8.3
29    Blake3NoState, 
30}
31
32impl SerializedState {
33    pub fn to_bytes(&self) -> Vec<u8> {
34        match self {
35            SerializedState::Sha256(arr) => arr.to_vec(),
36            SerializedState::Sha512(arr) => arr.to_vec(),
37            SerializedState::Sha3_256(arr) => arr.to_vec(),
38            SerializedState::Sha3_512(arr) => arr.to_vec(),
39            SerializedState::Blake3NoState => Vec::new(),
40        }
41    }
42}
43
44#[derive(Debug, Clone)]
45pub struct SegmentCheckpoint {
46    pub alg: DigestAlg,
47    pub segment_index: u32,
48    pub next_frame_index: u32,
49    pub state: SerializedState, 
50}
51
52impl SegmentCheckpoint {
53    pub fn from_state(alg: DigestAlg, segment_index: u32, next_frame_index: u32, state: &DigestState) -> Self {
54        let state = match state {
55            DigestState::Sha256(h)   => SerializedState::Sha256(h.serialize()),
56            DigestState::Sha512(h)   => SerializedState::Sha512(h.serialize()),
57            DigestState::Sha3_256(h) => SerializedState::Sha3_256(h.serialize()),
58            DigestState::Sha3_512(h) => SerializedState::Sha3_512(h.serialize()),
59            // This effectively "drops" resume support by restarting the hash for this alg.
60            DigestState::Blake3(_)   => SerializedState::Blake3NoState, 
61        };
62        Self { alg, segment_index, next_frame_index, state }
63    }
64    pub fn resume_from_checkpoint(self) -> Result<DigestState, DigestError> {
65        match (self.alg, self.state) {
66            (DigestAlg::Sha256, SerializedState::Sha256(arr)) => Sha256::deserialize(&arr).map(DigestState::Sha256).map_err(|_| DigestError::InvalidFormat),
67            (DigestAlg::Sha512, SerializedState::Sha512(arr)) => Sha512::deserialize(&arr).map(DigestState::Sha512).map_err(|_| DigestError::InvalidFormat),
68            (DigestAlg::Sha3_256, SerializedState::Sha3_256(arr)) => Sha3_256::deserialize(&arr).map(DigestState::Sha3_256).map_err(|_| DigestError::InvalidFormat),
69            (DigestAlg::Sha3_512, SerializedState::Sha3_512(arr)) => Sha3_512::deserialize(&arr).map(DigestState::Sha3_512).map_err(|_| DigestError::InvalidFormat),
70            // Blake3 Refactor: Instead of an error, we return a fresh Hasher.
71            // This effectively "drops" resume support by restarting the hash for this alg.
72            (DigestAlg::Blake3, _) => Ok(DigestState::Blake3(Blake3Hasher::new())),
73            _ => Err(DigestError::InvalidFormat),
74        }
75    }
76
77}
78
79impl Checkpointable for SegmentCheckpoint {
80    fn export(&self) -> Vec<u8> { self.state.to_bytes() }
81    fn segment_index(&self) -> u32 { self.segment_index }
82    fn summary(&self) -> String { format!("SegmentCheckpoint: alg={:?}, segment={}, next={}", self.alg, self.segment_index, self.next_frame_index) }
83    fn as_any(&self) -> &dyn Any { self }
84}
85
86// DecryptState remains unchanged as it typically relies on simple counters (CTR/ChaCha)
87// which are natively resumable by just storing the counter [u8] or [u32].
88#[derive(Debug, Clone)]
89pub enum DecryptState {
90    AesCtr([u8; 16]),
91    ChaCha20([u8; 32]),
92}
93
94impl DecryptState {
95    pub fn to_bytes(&self) -> Vec<u8> {
96        match self {
97            DecryptState::AesCtr(arr) => arr.to_vec(),
98            DecryptState::ChaCha20(arr) => arr.to_vec(),
99        }
100    }
101    pub fn from_bytes(alg_type: &str, bytes: &[u8]) -> Option<Self> {
102        match alg_type {
103            "AesCtr" if bytes.len() == 16 => Some(DecryptState::AesCtr(bytes.try_into().ok()?)),
104            "ChaCha20" if bytes.len() == 32 => Some(DecryptState::ChaCha20(bytes.try_into().ok()?)),
105            _ => None,
106        }
107    }
108}
109
110pub struct DecryptCheckpoint {
111    pub segment_index: u32,
112    pub frame_index: u32,
113    pub state: DecryptState,
114}
115
116impl Checkpointable for DecryptCheckpoint {
117    fn export(&self) -> Vec<u8> { self.state.to_bytes() }
118    fn segment_index(&self) -> u32 { self.segment_index }
119    fn summary(&self) -> String { format!("DecryptCheckpoint: segment={}, frame={}", self.segment_index, self.frame_index) }
120    fn as_any(&self) -> &dyn Any { self }
121}