1use smallvec::SmallVec;
2use std::ops::Range;
3
4use crate::{
5 cache, data,
6 data::{File, delta, file::decode::Error},
7};
8
9#[derive(Debug, PartialEq, Eq, Hash, Ord, PartialOrd, Clone)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub enum ResolvedBase {
13 InPack(data::Entry),
15 #[expect(missing_docs)]
18 OutOfPack { kind: gix_object::Kind, end: usize },
19}
20
21#[derive(Debug)]
22struct Delta {
23 data: Range<usize>,
24 base_size: usize,
25 result_size: usize,
26
27 decompressed_size: usize,
28 data_offset: data::Offset,
29}
30
31#[derive(Debug, PartialEq, Eq, Hash, Ord, PartialOrd, Clone)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36pub struct Outcome {
37 pub kind: gix_object::Kind,
39 pub num_deltas: u32,
44 pub decompressed_size: u64,
46 pub compressed_size: usize,
48 pub object_size: u64,
50}
51
52impl Outcome {
53 pub(crate) fn default_from_kind(kind: gix_object::Kind) -> Self {
54 Self {
55 kind,
56 num_deltas: 0,
57 decompressed_size: 0,
58 compressed_size: 0,
59 object_size: 0,
60 }
61 }
62 fn from_object_entry(kind: gix_object::Kind, entry: &data::Entry, compressed_size: usize) -> Self {
63 Self {
64 kind,
65 num_deltas: 0,
66 decompressed_size: entry.decompressed_size,
67 compressed_size,
68 object_size: entry.decompressed_size,
69 }
70 }
71}
72
73impl<T> File<T>
75where
76 T: crate::FileData,
77{
78 fn decoded_object_size(&self, size: u64) -> Result<usize, Error> {
79 decoded_object_size(size, self.alloc_limit_bytes)
80 }
81
82 pub fn decompress_entry(
88 &self,
89 entry: &data::Entry,
90 inflate: &mut gix_zlib::Inflate,
91 out: &mut [u8],
92 ) -> Result<usize, Error> {
93 let size: usize = entry.decompressed_size.try_into().map_err(|_| Error::OutOfMemory)?;
94 if out.len() < size {
95 return Err(Error::OutOfMemory);
96 }
97 self.decompress_entry_from_data_offset(entry.data_offset, inflate, &mut out[..size])
98 }
99
100 pub fn entry(&self, offset: data::Offset) -> Result<data::Entry, data::entry::decode::Error> {
104 let pack_offset: usize = offset.try_into().expect("offset representable by machine");
105 if pack_offset > self.data.len() {
106 return Err(data::entry::decode::Error::Corrupt {
107 message: "an entry offset pointing beyond pack data",
108 });
109 }
110
111 let object_data = &self.data[pack_offset..];
112 data::Entry::from_bytes(object_data, offset, self.object_hash)
113 }
114
115 pub(crate) fn decompress_entry_from_data_offset(
121 &self,
122 data_offset: data::Offset,
123 inflate: &mut gix_zlib::Inflate,
124 out: &mut [u8],
125 ) -> Result<usize, Error> {
126 let (consumed_in, _consumed_out) =
127 self.decompress_complete_entry_from_data_offset(data_offset, inflate, out)?;
128 Ok(consumed_in)
129 }
130
131 pub(crate) fn decompress_complete_entry_from_data_offset(
139 &self,
140 data_offset: data::Offset,
141 inflate: &mut gix_zlib::Inflate,
142 out: &mut [u8],
143 ) -> Result<(usize, usize), Error> {
144 let (status, consumed_in, consumed_out) =
145 self.decompress_entry_from_data_offset_unchecked(data_offset, inflate, out)?;
146 if status != gix_zlib::Status::StreamEnd || consumed_out != out.len() {
147 return Err(data::entry::decode::Error::Corrupt {
148 message: "pack entry decompressed size does not match entry header",
149 }
150 .into());
151 }
152 Ok((consumed_in, consumed_out))
153 }
154
155 pub(crate) fn decompress_entry_from_data_offset_unchecked(
160 &self,
161 data_offset: data::Offset,
162 inflate: &mut gix_zlib::Inflate,
163 out: &mut [u8],
164 ) -> Result<(gix_zlib::Status, usize, usize), Error> {
165 let offset: usize = data_offset.try_into().expect("offset representable by machine");
166 if offset >= self.data.len() {
167 return Err(data::entry::decode::Error::Corrupt {
168 message: "an entry data offset pointing beyond pack data",
169 }
170 .into());
171 }
172
173 inflate.reset();
174 inflate.once(&self.data[offset..], out).map_err(Into::into)
175 }
176
177 pub fn decode_entry(
189 &self,
190 entry: data::Entry,
191 out: &mut Vec<u8>,
192 inflate: &mut gix_zlib::Inflate,
193 resolve: &dyn Fn(&gix_hash::oid, &mut Vec<u8>) -> Option<ResolvedBase>,
194 delta_cache: &mut dyn cache::DecodeEntry,
195 ) -> Result<Outcome, Error> {
196 use crate::data::entry::Header::*;
197 match entry.header {
198 Tree | Blob | Commit | Tag => {
199 let size = self.decoded_object_size(entry.decompressed_size)?;
200 if let Some(additional) = size.checked_sub(out.len()) {
201 out.try_reserve(additional)?;
202 }
203 out.resize(size, 0);
204 self.decompress_entry(&entry, inflate, out.as_mut_slice())
205 .map(|consumed_input| {
206 Outcome::from_object_entry(
207 entry.header.as_kind().expect("a non-delta entry"),
208 &entry,
209 consumed_input,
210 )
211 })
212 }
213 OfsDelta { .. } | RefDelta { .. } => self.resolve_deltas(entry, resolve, inflate, out, delta_cache),
214 }
215 }
216
217 fn resolve_deltas(
221 &self,
222 last: data::Entry,
223 resolve: &dyn Fn(&gix_hash::oid, &mut Vec<u8>) -> Option<ResolvedBase>,
224 inflate: &mut gix_zlib::Inflate,
225 out: &mut Vec<u8>,
226 cache: &mut dyn cache::DecodeEntry,
227 ) -> Result<Outcome, Error> {
228 let mut chain = SmallVec::<[Delta; 10]>::default();
230 let first_entry = last.clone();
231 let mut cursor = last;
232 let mut base_buffer_size: Option<usize> = None;
233 let mut object_kind: Option<gix_object::Kind> = None;
234 let mut consumed_input: Option<usize> = None;
235
236 let mut total_delta_data_size: u64 = 0;
238 while cursor.header.is_delta() {
239 if let Some((kind, packed_size)) = cache.get(self.id, cursor.data_offset, out) {
240 base_buffer_size = Some(out.len());
241 object_kind = Some(kind);
242 if total_delta_data_size == 0 {
245 consumed_input = Some(packed_size);
246 }
247 break;
248 }
249 total_delta_data_size = total_delta_data_size
252 .checked_add(cursor.decompressed_size)
253 .ok_or(Error::OutOfMemory)?;
254 if self
255 .alloc_limit_bytes
256 .is_some_and(|limit| total_delta_data_size > limit as u64)
257 {
258 return Err(Error::OutOfMemory);
259 }
260 let decompressed_size = self.decoded_object_size(cursor.decompressed_size)?;
261 chain.push(Delta {
262 data: Range {
263 start: 0,
264 end: decompressed_size,
265 },
266 base_size: 0,
267 result_size: 0,
268 decompressed_size,
269 data_offset: cursor.data_offset,
270 });
271 use crate::data::entry::Header;
272 cursor = match cursor.header {
273 Header::OfsDelta { base_distance } => {
274 self.entry(cursor.checked_base_pack_offset(base_distance).ok_or(
275 crate::data::entry::decode::Error::Corrupt {
276 message: "an ofs-delta base distance pointing before pack start",
277 },
278 )?)?
279 }
280 Header::RefDelta { base_id } => match resolve(base_id.as_ref(), out) {
281 Some(ResolvedBase::InPack(entry)) => entry,
282 Some(ResolvedBase::OutOfPack { end, kind }) => {
283 base_buffer_size = Some(end);
284 object_kind = Some(kind);
285 break;
286 }
287 None => return Err(Error::DeltaBaseUnresolved(base_id)),
288 },
289 _ => unreachable!("cursor.is_delta() only allows deltas here"),
290 };
291 }
292
293 if chain.is_empty() {
296 return Ok(Outcome::from_object_entry(
297 object_kind.expect("object kind as set by cache"),
298 &first_entry,
299 consumed_input.expect("consumed bytes as set by cache"),
300 ));
301 }
302
303 let total_delta_data_size: usize = total_delta_data_size.try_into().map_err(|_| Error::OutOfMemory)?;
307
308 let chain_len = chain.len();
309 let (first_buffer_end, second_buffer_end) = {
310 let delta_start = base_buffer_size.unwrap_or(0);
311
312 let delta_range = Range {
313 start: delta_start,
314 end: delta_start
315 .checked_add(total_delta_data_size)
316 .ok_or(Error::OutOfMemory)?,
317 };
318 out.try_reserve(delta_range.end.saturating_sub(out.len()))?;
319 out.resize(delta_range.end, 0);
320
321 let mut instructions = &mut out[delta_range.clone()];
322 let mut relative_delta_start = 0;
323 let mut biggest_result_size = 0;
324 for (delta_idx, delta) in chain.iter_mut().rev().enumerate() {
325 let (consumed_from_data_offset, consumed_out) = self.decompress_complete_entry_from_data_offset(
326 delta.data_offset,
327 inflate,
328 &mut instructions[..delta.decompressed_size],
329 )?;
330 let is_last_delta_to_be_applied = delta_idx + 1 == chain_len;
331 if is_last_delta_to_be_applied {
332 consumed_input = Some(consumed_from_data_offset);
333 }
334
335 let current_delta = &instructions[..consumed_out];
336 let (base_size, offset) = delta::decode_header_size(current_delta)?;
337 let mut bytes_consumed_by_header = offset;
338 biggest_result_size = biggest_result_size.max(base_size);
339 delta.base_size = self.decoded_object_size(base_size)?;
340
341 let (result_size, offset) = delta::decode_header_size(¤t_delta[offset..])?;
342 bytes_consumed_by_header += offset;
343 biggest_result_size = biggest_result_size.max(result_size);
344 delta.result_size = self.decoded_object_size(result_size)?;
345
346 delta.data.start = relative_delta_start + bytes_consumed_by_header;
348 delta.data.end = relative_delta_start + consumed_out;
349 relative_delta_start += delta.decompressed_size;
350
351 instructions = &mut instructions[delta.decompressed_size..];
352 }
353
354 if base_buffer_size.is_none() {
358 biggest_result_size = biggest_result_size.max(cursor.decompressed_size);
359 }
360 let biggest_result_size = self.decoded_object_size(biggest_result_size)?;
361 let first_buffer_size = biggest_result_size;
362 let second_buffer_size = first_buffer_size;
363 let out_size = first_buffer_size
364 .checked_add(second_buffer_size)
365 .and_then(|size| size.checked_add(total_delta_data_size))
366 .ok_or(Error::OutOfMemory)?;
367 out.try_reserve(out_size.saturating_sub(out.len()))?;
368 out.resize(out_size, 0);
369
370 let second_buffer_end = {
373 let end = first_buffer_size
374 .checked_add(second_buffer_size)
375 .ok_or(Error::OutOfMemory)?;
376 out.copy_within(delta_range, end);
379 end
380 };
381
382 if base_buffer_size.is_none() {
385 let base_entry = cursor;
386 debug_assert!(!base_entry.header.is_delta());
387 object_kind = base_entry.header.as_kind();
388 let base_size = self.decoded_object_size(base_entry.decompressed_size)?;
389 let out_base = &mut out[..base_size];
390 self.decompress_entry_from_data_offset(base_entry.data_offset, inflate, out_base)?;
391 }
392
393 (first_buffer_size, second_buffer_end)
394 };
395
396 let (buffers, instructions) = out.split_at_mut(second_buffer_end);
402 let (mut source_buf, mut target_buf) = buffers.split_at_mut(first_buffer_end);
403
404 let mut last_result_size = None;
405 for (
406 delta_idx,
407 Delta {
408 data,
409 base_size,
410 result_size,
411 ..
412 },
413 ) in chain.into_iter().rev().enumerate()
414 {
415 let data = &mut instructions[data];
416 if delta_idx + 1 == chain_len {
417 last_result_size = Some(result_size);
418 }
419 delta::apply(&source_buf[..base_size], &mut target_buf[..result_size], data)?;
420 std::mem::swap(&mut source_buf, &mut target_buf);
422 }
423
424 let last_result_size = last_result_size.expect("at least one delta chain item");
425 if chain_len % 2 == 1 {
434 target_buf[..last_result_size].copy_from_slice(&source_buf[..last_result_size]);
436 }
437 debug_assert!(out.len() >= last_result_size);
438 out.truncate(last_result_size);
439
440 let object_kind = object_kind.expect("a base object as root of any delta chain that we are here to resolve");
441 let consumed_input = consumed_input.expect("at least one decompressed delta object");
442 cache.put(
443 self.id,
444 first_entry.data_offset,
445 out.as_slice(),
446 object_kind,
447 consumed_input,
448 );
449 Ok(Outcome {
450 kind: object_kind,
451 num_deltas: chain_len as u32,
455 decompressed_size: first_entry.decompressed_size,
456 compressed_size: consumed_input,
457 object_size: last_result_size as u64,
458 })
459 }
460}
461
462fn decoded_object_size(size: u64, alloc_limit_bytes: Option<usize>) -> Result<usize, Error> {
464 let size: usize = size.try_into().map_err(|_| Error::OutOfMemory)?;
465 if alloc_limit_bytes.is_some_and(|limit| size > limit) {
466 return Err(Error::OutOfMemory);
467 }
468 Ok(size)
469}
470
471#[cfg(test)]
472mod tests {
473 use gix_testtools::size_ok;
474
475 use super::*;
476
477 #[test]
478 fn size_of_decode_entry_outcome() {
479 let actual = std::mem::size_of::<Outcome>();
480 let expected = 32;
481 assert!(
482 size_ok(actual, expected),
483 "this shouldn't change without use noticing as it's returned a lot: {actual} <~ {expected}"
484 );
485 }
486}