Skip to main content

dcp/sync/
delta.rs

1//! XOR Delta state synchronization.
2//!
3//! Provides efficient incremental state updates using XOR differences
4//! with run-length encoding for sparse changes.
5
6use crate::DCPError;
7use blake3;
8
9/// XOR delta for state synchronization
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct XorDelta {
12    /// Hash of previous state
13    pub prev_hash: [u8; 32],
14    /// Hash of new state
15    pub new_hash: [u8; 32],
16    /// Run-length encoded XOR patch
17    pub patch: Vec<u8>,
18    /// Original new state length (needed for applying delta)
19    pub new_len: usize,
20}
21
22impl XorDelta {
23    /// Compute delta between two states
24    pub fn compute(prev: &[u8], new: &[u8]) -> Self {
25        // Compute XOR difference
26        let max_len = prev.len().max(new.len());
27        let mut xor = Vec::with_capacity(max_len);
28
29        for i in 0..max_len {
30            let prev_byte = prev.get(i).copied().unwrap_or(0);
31            let new_byte = new.get(i).copied().unwrap_or(0);
32            xor.push(prev_byte ^ new_byte);
33        }
34
35        Self {
36            prev_hash: *blake3::hash(prev).as_bytes(),
37            new_hash: *blake3::hash(new).as_bytes(),
38            patch: rle_compress(&xor),
39            new_len: new.len(),
40        }
41    }
42
43    /// Apply delta to state
44    /// Returns the new state if successful
45    pub fn apply(&self, state: &[u8]) -> Result<Vec<u8>, DCPError> {
46        // Verify previous hash
47        let current_hash = *blake3::hash(state).as_bytes();
48        if current_hash != self.prev_hash {
49            return Err(DCPError::HashMismatch);
50        }
51
52        // Decompress the XOR patch
53        let xor = rle_decompress(&self.patch);
54
55        // Apply XOR to create new state
56        let mut new_state = Vec::with_capacity(self.new_len);
57        for i in 0..self.new_len {
58            let state_byte = state.get(i).copied().unwrap_or(0);
59            let xor_byte = xor.get(i).copied().unwrap_or(0);
60            new_state.push(state_byte ^ xor_byte);
61        }
62
63        // Verify new hash
64        let result_hash = *blake3::hash(&new_state).as_bytes();
65        if result_hash != self.new_hash {
66            return Err(DCPError::HashMismatch);
67        }
68
69        Ok(new_state)
70    }
71
72    /// Get the size of the compressed patch
73    pub fn patch_size(&self) -> usize {
74        self.patch.len()
75    }
76
77    /// Check if this delta represents a sparse change
78    /// (patch is smaller than the full state)
79    pub fn is_sparse(&self, full_state_size: usize) -> bool {
80        self.patch.len() < full_state_size
81    }
82
83    /// Verify the delta can be applied to a state
84    pub fn verify_prev_hash(&self, state: &[u8]) -> bool {
85        let current_hash = *blake3::hash(state).as_bytes();
86        current_hash == self.prev_hash
87    }
88}
89
90/// Run-length encode a byte slice
91/// Format: [count, value] pairs where count is 1-255
92/// For runs > 255, multiple pairs are used
93fn rle_compress(data: &[u8]) -> Vec<u8> {
94    if data.is_empty() {
95        return Vec::new();
96    }
97
98    let mut result = Vec::new();
99    let mut i = 0;
100
101    while i < data.len() {
102        let value = data[i];
103        let mut count = 1u8;
104
105        // Count consecutive identical bytes (max 255)
106        while i + (count as usize) < data.len()
107            && data[i + (count as usize)] == value
108            && count < 255
109        {
110            count += 1;
111        }
112
113        result.push(count);
114        result.push(value);
115        i += count as usize;
116    }
117
118    result
119}
120
121/// Run-length decode a byte slice
122fn rle_decompress(data: &[u8]) -> Vec<u8> {
123    let mut result = Vec::new();
124    let mut i = 0;
125
126    while i + 1 < data.len() {
127        let count = data[i] as usize;
128        let value = data[i + 1];
129
130        for _ in 0..count {
131            result.push(value);
132        }
133
134        i += 2;
135    }
136
137    result
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_rle_compress_decompress() {
146        let data = vec![0, 0, 0, 1, 1, 2, 2, 2, 2, 0];
147        let compressed = rle_compress(&data);
148        let decompressed = rle_decompress(&compressed);
149        assert_eq!(decompressed, data);
150    }
151
152    #[test]
153    fn test_rle_empty() {
154        let data: Vec<u8> = vec![];
155        let compressed = rle_compress(&data);
156        let decompressed = rle_decompress(&compressed);
157        assert!(compressed.is_empty());
158        assert!(decompressed.is_empty());
159    }
160
161    #[test]
162    fn test_rle_single_byte() {
163        let data = vec![42];
164        let compressed = rle_compress(&data);
165        let decompressed = rle_decompress(&compressed);
166        assert_eq!(decompressed, data);
167    }
168
169    #[test]
170    fn test_rle_long_run() {
171        // Test run longer than 255
172        let data = vec![0u8; 300];
173        let compressed = rle_compress(&data);
174        let decompressed = rle_decompress(&compressed);
175        assert_eq!(decompressed, data);
176        // Should be compressed to 4 bytes: [255, 0, 45, 0]
177        assert_eq!(compressed.len(), 4);
178    }
179
180    #[test]
181    fn test_delta_basic() {
182        let prev = b"hello world";
183        let new = b"hello rust!";
184
185        let delta = XorDelta::compute(prev, new);
186        let result = delta.apply(prev).unwrap();
187
188        assert_eq!(result, new);
189    }
190
191    #[test]
192    fn test_delta_sparse_change() {
193        // Large state with small change
194        let mut prev = vec![0u8; 1000];
195        let mut new = prev.clone();
196        new[500] = 1; // Single byte change
197
198        let delta = XorDelta::compute(&prev, &new);
199
200        // Delta should be smaller than full state
201        assert!(delta.is_sparse(new.len()));
202
203        let result = delta.apply(&prev).unwrap();
204        assert_eq!(result, new);
205    }
206
207    #[test]
208    fn test_delta_hash_mismatch() {
209        let prev = b"hello";
210        let new = b"world";
211
212        let delta = XorDelta::compute(prev, new);
213
214        // Try to apply to wrong state
215        let wrong_state = b"wrong";
216        let result = delta.apply(wrong_state);
217
218        assert_eq!(result, Err(DCPError::HashMismatch));
219    }
220
221    #[test]
222    fn test_delta_different_lengths() {
223        let prev = b"short";
224        let new = b"much longer string";
225
226        let delta = XorDelta::compute(prev, new);
227        let result = delta.apply(prev).unwrap();
228
229        assert_eq!(result, new);
230    }
231
232    #[test]
233    fn test_delta_shrinking() {
234        let prev = b"much longer string";
235        let new = b"short";
236
237        let delta = XorDelta::compute(prev, new);
238        let result = delta.apply(prev).unwrap();
239
240        assert_eq!(result, new);
241    }
242
243    #[test]
244    fn test_delta_empty_states() {
245        let prev: &[u8] = b"";
246        let new = b"new data";
247
248        let delta = XorDelta::compute(prev, new);
249        let result = delta.apply(prev).unwrap();
250
251        assert_eq!(result, new);
252    }
253
254    #[test]
255    fn test_delta_to_empty() {
256        let prev = b"old data";
257        let new: &[u8] = b"";
258
259        let delta = XorDelta::compute(prev, new);
260        let result = delta.apply(prev).unwrap();
261
262        assert_eq!(result, new);
263    }
264
265    #[test]
266    fn test_verify_prev_hash() {
267        let prev = b"hello";
268        let new = b"world";
269
270        let delta = XorDelta::compute(prev, new);
271
272        assert!(delta.verify_prev_hash(prev));
273        assert!(!delta.verify_prev_hash(new));
274    }
275}