veridian_kernel/pkg/
delta.rs1#[cfg(feature = "alloc")]
8extern crate alloc;
9
10#[cfg(feature = "alloc")]
11use alloc::vec::Vec;
12
13use crate::error::KernelError;
14
15const BLOCK_SIZE: usize = 256;
17
18#[cfg(feature = "alloc")]
20#[derive(Debug, Clone)]
21pub enum DeltaOp {
22 Copy { offset: usize, len: usize },
24 Insert { data: Vec<u8> },
26}
27
28#[cfg(feature = "alloc")]
32#[derive(Debug, Clone)]
33pub struct BinaryDelta {
34 pub source_hash: [u8; 32],
36 pub target_hash: [u8; 32],
38 pub operations: Vec<DeltaOp>,
40 pub compressed_size: usize,
42}
43
44#[cfg(feature = "alloc")]
46#[derive(Debug, Clone)]
47pub struct DeltaMetadata {
48 pub version_from: super::Version,
50 pub version_to: super::Version,
52 pub delta_size: usize,
54 pub full_size: usize,
56}
57
58#[cfg(feature = "alloc")]
59impl DeltaMetadata {
60 pub fn savings_ratio(&self) -> f64 {
62 if self.full_size == 0 {
63 return 0.0;
64 }
65 1.0 - (self.delta_size as f64 / self.full_size as f64)
66 }
67}
68
69#[cfg(feature = "alloc")]
76pub fn compute_delta(old: &[u8], new: &[u8]) -> BinaryDelta {
77 use alloc::collections::BTreeMap;
78
79 let source_hash = *crate::crypto::hash::sha256(old).as_bytes();
80 let target_hash = *crate::crypto::hash::sha256(new).as_bytes();
81
82 let mut block_map: BTreeMap<u64, Vec<usize>> = BTreeMap::new();
84 let mut offset = 0;
85 while offset + BLOCK_SIZE <= old.len() {
86 let hash = super::manifest::fnv1a_hash(&old[offset..offset + BLOCK_SIZE]);
87 block_map.entry(hash).or_insert_with(Vec::new).push(offset);
88 offset += BLOCK_SIZE;
89 }
90
91 let mut operations: Vec<DeltaOp> = Vec::new();
92 let mut pos = 0;
93 let mut pending_insert: Vec<u8> = Vec::new();
94
95 while pos < new.len() {
96 let remaining = new.len() - pos;
97
98 if remaining >= BLOCK_SIZE {
99 let block_hash = super::manifest::fnv1a_hash(&new[pos..pos + BLOCK_SIZE]);
100
101 let found = block_map.get(&block_hash).and_then(|offsets| {
103 offsets.iter().find(|&&old_offset| {
104 old_offset + BLOCK_SIZE <= old.len()
105 && old[old_offset..old_offset + BLOCK_SIZE] == new[pos..pos + BLOCK_SIZE]
106 })
107 });
108
109 if let Some(&old_offset) = found {
110 if !pending_insert.is_empty() {
112 operations.push(DeltaOp::Insert {
113 data: core::mem::take(&mut pending_insert),
114 });
115 }
116
117 let mut copy_len = BLOCK_SIZE;
119 while pos + copy_len < new.len()
120 && old_offset + copy_len < old.len()
121 && new[pos + copy_len] == old[old_offset + copy_len]
122 {
123 copy_len += 1;
124 }
125
126 operations.push(DeltaOp::Copy {
127 offset: old_offset,
128 len: copy_len,
129 });
130 pos += copy_len;
131 continue;
132 }
133 }
134
135 pending_insert.push(new[pos]);
137 pos += 1;
138 }
139
140 if !pending_insert.is_empty() {
142 operations.push(DeltaOp::Insert {
143 data: pending_insert,
144 });
145 }
146
147 let compressed_size = operations.iter().fold(0usize, |acc, op| match op {
149 DeltaOp::Copy { .. } => acc + 12, DeltaOp::Insert { data } => acc + 4 + data.len(), });
152
153 BinaryDelta {
154 source_hash,
155 target_hash,
156 operations,
157 compressed_size,
158 }
159}
160
161#[cfg(feature = "alloc")]
165pub fn apply_delta(old: &[u8], delta: &BinaryDelta) -> Result<Vec<u8>, KernelError> {
166 let mut result = Vec::new();
167
168 for op in &delta.operations {
169 match op {
170 DeltaOp::Copy { offset, len } => {
171 if *offset + *len > old.len() {
172 return Err(KernelError::InvalidArgument {
173 name: "delta_copy",
174 value: "source_out_of_bounds",
175 });
176 }
177 result.extend_from_slice(&old[*offset..*offset + *len]);
178 }
179 DeltaOp::Insert { data } => {
180 result.extend_from_slice(data);
181 }
182 }
183 }
184
185 Ok(result)
186}
187
188#[cfg(feature = "alloc")]
190pub fn verify_delta_result(result: &[u8], expected_hash: &[u8; 32]) -> bool {
191 crate::crypto::hash::sha256(result).as_bytes() == expected_hash
192}
193
194#[cfg(feature = "alloc")]
207pub fn serialize_delta(delta: &BinaryDelta) -> Vec<u8> {
208 let mut buf = Vec::new();
209
210 buf.extend_from_slice(&delta.source_hash);
211 buf.extend_from_slice(&delta.target_hash);
212 buf.extend_from_slice(&(delta.operations.len() as u32).to_le_bytes());
213
214 for op in &delta.operations {
215 match op {
216 DeltaOp::Copy { offset, len } => {
217 buf.push(0); buf.extend_from_slice(&(*offset as u32).to_le_bytes());
219 buf.extend_from_slice(&(*len as u32).to_le_bytes());
220 }
221 DeltaOp::Insert { data } => {
222 buf.push(1); buf.extend_from_slice(&(data.len() as u32).to_le_bytes());
224 buf.extend_from_slice(data);
225 }
226 }
227 }
228
229 buf
230}
231
232#[cfg(feature = "alloc")]
234pub fn deserialize_delta(data: &[u8]) -> Result<BinaryDelta, KernelError> {
235 if data.len() < 68 {
237 return Err(KernelError::InvalidArgument {
238 name: "delta_data",
239 value: "too_short",
240 });
241 }
242
243 let mut source_hash = [0u8; 32];
244 let mut target_hash = [0u8; 32];
245 source_hash.copy_from_slice(&data[0..32]);
246 target_hash.copy_from_slice(&data[32..64]);
247
248 let op_count = u32::from_le_bytes([data[64], data[65], data[66], data[67]]) as usize;
249
250 let mut operations = Vec::with_capacity(op_count);
251 let mut pos = 68;
252
253 for _ in 0..op_count {
254 if pos >= data.len() {
255 return Err(KernelError::InvalidArgument {
256 name: "delta_data",
257 value: "truncated_ops",
258 });
259 }
260
261 let tag = data[pos];
262 pos += 1;
263
264 match tag {
265 0 => {
266 if pos + 8 > data.len() {
268 return Err(KernelError::InvalidArgument {
269 name: "delta_data",
270 value: "truncated_copy",
271 });
272 }
273 let offset =
274 u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]])
275 as usize;
276 pos += 4;
277 let len =
278 u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]])
279 as usize;
280 pos += 4;
281 operations.push(DeltaOp::Copy { offset, len });
282 }
283 1 => {
284 if pos + 4 > data.len() {
286 return Err(KernelError::InvalidArgument {
287 name: "delta_data",
288 value: "truncated_insert_len",
289 });
290 }
291 let len =
292 u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]])
293 as usize;
294 pos += 4;
295 if pos + len > data.len() {
296 return Err(KernelError::InvalidArgument {
297 name: "delta_data",
298 value: "truncated_insert_data",
299 });
300 }
301 let insert_data = data[pos..pos + len].to_vec();
302 pos += len;
303 operations.push(DeltaOp::Insert { data: insert_data });
304 }
305 _ => {
306 return Err(KernelError::InvalidArgument {
307 name: "delta_tag",
308 value: "unknown_op_type",
309 });
310 }
311 }
312 }
313
314 Ok(BinaryDelta {
315 source_hash,
316 target_hash,
317 operations,
318 compressed_size: data.len(),
319 })
320}