1use alloc::vec::Vec;
10
11use crate::delta;
12use crate::err::{Error, Result};
13use crate::object::Kind;
14use crate::oid::Oid;
15use crate::sha1;
16use crate::zlib;
17
18pub struct Pack<'a> {
20 data: &'a [u8],
21 content_end: usize,
23 index: Vec<(Oid, u32)>,
25}
26
27const OBJ_COMMIT: u8 = 1;
29const OBJ_TREE: u8 = 2;
30const OBJ_BLOB: u8 = 3;
31const OBJ_TAG: u8 = 4;
32const OBJ_OFS_DELTA: u8 = 6;
33const OBJ_REF_DELTA: u8 = 7;
34
35const MAX_DELTA_DEPTH: usize = 4096;
37
38pub const DEFAULT_BASE_CACHE_BYTES: usize = 64 * 1024;
41const BASE_CACHE_ENTRIES: usize = 64;
43
44struct BaseCache {
50 entries: Vec<(u32, Kind, Vec<u8>)>,
51 bytes: usize,
52 budget: usize,
53}
54
55impl BaseCache {
56 fn new(budget: usize) -> Self {
57 Self {
58 entries: Vec::new(),
59 bytes: 0,
60 budget,
61 }
62 }
63
64 fn get(&self, offset: usize) -> Option<(Kind, &[u8])> {
65 self.entries
66 .iter()
67 .find(|(o, _, _)| *o as usize == offset)
68 .map(|(_, kind, body)| (*kind, body.as_slice()))
69 }
70
71 fn insert(&mut self, offset: usize, kind: Kind, body: &[u8]) {
72 if body.len() > self.budget || self.get(offset).is_some() {
73 return;
74 }
75 while !self.entries.is_empty()
76 && (self.bytes + body.len() > self.budget || self.entries.len() >= BASE_CACHE_ENTRIES)
77 {
78 let (_, _, evicted) = self.entries.remove(0);
79 self.bytes -= evicted.len();
80 }
81 self.bytes += body.len();
82 self.entries.push((offset as u32, kind, body.to_vec()));
83 }
84}
85
86impl<'a> Pack<'a> {
87 pub fn parse(data: &'a [u8]) -> Result<Self> {
88 Self::parse_with_cache(data, DEFAULT_BASE_CACHE_BYTES)
89 }
90
91 pub fn parse_with_cache(data: &'a [u8], cache_bytes: usize) -> Result<Self> {
94 if data.len() < 12 + 20 {
95 return Err(Error::UnexpectedEof);
96 }
97 if &data[0..4] != b"PACK" {
98 return Err(Error::Corrupt("pack magic"));
99 }
100 if u32::from_be_bytes(data[4..8].try_into().unwrap()) != 2 {
101 return Err(Error::Unsupported("pack version"));
102 }
103 if data.len() as u64 > u64::from(u32::MAX) {
104 return Err(Error::Unsupported("pack larger than 4 GiB"));
105 }
106 let count = u32::from_be_bytes(data[8..12].try_into().unwrap()) as usize;
107
108 let content_end = data.len() - 20;
109 if sha1::digest(&data[..content_end]) != data[content_end..] {
110 return Err(Error::Checksum("pack trailer"));
111 }
112
113 let mut index: Vec<(Oid, u32)> = Vec::with_capacity(count.min(1 << 16));
115 let mut pending: Vec<u32> = Vec::new();
116 let mut offset = 12usize;
117 for _ in 0..count {
118 if offset >= content_end {
119 return Err(Error::UnexpectedEof);
120 }
121 let (code, size, pos) = entry_header(data, offset, content_end)?;
122 let (_, zlib_start) = base_ref(data, code, pos, offset, content_end)?;
123 let inflated = zlib::inflate_zlib(&data[zlib_start..content_end], Some(size))?;
124 if inflated.data.len() != size {
125 return Err(Error::Corrupt("entry size"));
126 }
127 match code {
128 OBJ_COMMIT | OBJ_TREE | OBJ_BLOB | OBJ_TAG => {
129 let kind = kind_of(code)?;
130 index.push((
131 crate::object::compute_oid(kind, &inflated.data),
132 offset as u32,
133 ));
134 }
135 _ => pending.push(offset as u32),
136 }
137 offset = zlib_start + inflated.consumed;
138 }
139 if offset != content_end {
140 return Err(Error::Corrupt("trailing bytes after entries"));
141 }
142 index.sort_unstable();
143
144 let mut cache = BaseCache::new(cache_bytes);
148 while !pending.is_empty() {
149 let mut unresolved = Vec::new();
150 let mut resolved_any = false;
151 for &entry_offset in &pending {
152 match materialize(
153 data,
154 content_end,
155 &index,
156 entry_offset as usize,
157 Some(&mut cache),
158 ) {
159 Ok((kind, body)) => {
160 index.push((crate::object::compute_oid(kind, &body), entry_offset));
161 resolved_any = true;
162 }
163 Err(Error::MissingBase) => unresolved.push(entry_offset),
164 Err(e) => return Err(e),
165 }
166 }
167 if !resolved_any {
168 return Err(Error::MissingBase);
169 }
170 index.sort_unstable();
171 pending = unresolved;
172 }
173
174 Ok(Self {
175 data,
176 content_end,
177 index,
178 })
179 }
180
181 pub fn len(&self) -> usize {
183 self.index.len()
184 }
185
186 pub fn is_empty(&self) -> bool {
187 self.index.is_empty()
188 }
189
190 pub fn contains(&self, oid: &Oid) -> bool {
191 lookup(&self.index, oid).is_some()
192 }
193
194 pub fn oids(&self) -> impl Iterator<Item = &Oid> {
196 self.index.iter().map(|(oid, _)| oid)
197 }
198
199 pub fn read_object(&self, oid: &Oid) -> Result<Option<(Kind, Vec<u8>)>> {
201 match lookup(&self.index, oid) {
202 None => Ok(None),
203 Some(offset) => materialize(
204 self.data,
205 self.content_end,
206 &self.index,
207 offset as usize,
208 None,
209 )
210 .map(Some),
211 }
212 }
213}
214
215impl crate::Odb for Pack<'_> {
216 fn read(&self, oid: &Oid) -> Option<(Kind, Vec<u8>)> {
217 self.read_object(oid).ok().flatten()
220 }
221}
222
223fn lookup(index: &[(Oid, u32)], oid: &Oid) -> Option<u32> {
224 index
225 .binary_search_by(|(o, _)| o.cmp(oid))
226 .ok()
227 .map(|i| index[i].1)
228}
229
230fn kind_of(code: u8) -> Result<Kind> {
231 match code {
232 OBJ_COMMIT => Ok(Kind::Commit),
233 OBJ_TREE => Ok(Kind::Tree),
234 OBJ_BLOB => Ok(Kind::Blob),
235 OBJ_TAG => Ok(Kind::Tag),
236 _ => Err(Error::Corrupt("object type code")),
237 }
238}
239
240enum BaseRef {
242 None,
243 Offset(usize),
245 Oid(Oid),
246}
247
248fn entry_header(data: &[u8], offset: usize, end: usize) -> Result<(u8, usize, usize)> {
250 let mut pos = offset;
251 let mut byte = *data
252 .get(pos)
253 .filter(|_| pos < end)
254 .ok_or(Error::UnexpectedEof)?;
255 pos += 1;
256 let code = (byte >> 4) & 0x07;
257 let mut size = usize::from(byte & 0x0f);
258 let mut shift = 4;
259 while byte & 0x80 != 0 {
260 byte = *data
261 .get(pos)
262 .filter(|_| pos < end)
263 .ok_or(Error::UnexpectedEof)?;
264 pos += 1;
265 if shift >= usize::BITS {
266 return Err(Error::Corrupt("entry size varint"));
267 }
268 size |= usize::from(byte & 0x7f) << shift;
269 shift += 7;
270 }
271 Ok((code, size, pos))
272}
273
274fn base_ref(
280 data: &[u8],
281 code: u8,
282 pos: usize,
283 entry_offset: usize,
284 end: usize,
285) -> Result<(BaseRef, usize)> {
286 match code {
287 OBJ_COMMIT | OBJ_TREE | OBJ_BLOB | OBJ_TAG => Ok((BaseRef::None, pos)),
288 OBJ_OFS_DELTA => {
289 let mut p = pos;
291 let mut byte = *data
292 .get(p)
293 .filter(|_| p < end)
294 .ok_or(Error::UnexpectedEof)?;
295 p += 1;
296 let mut value = u64::from(byte & 0x7f);
297 while byte & 0x80 != 0 {
298 byte = *data
299 .get(p)
300 .filter(|_| p < end)
301 .ok_or(Error::UnexpectedEof)?;
302 p += 1;
303 value = value
304 .checked_add(1)
305 .and_then(|v| v.checked_shl(7))
306 .ok_or(Error::Corrupt("ofs delta varint"))?
307 | u64::from(byte & 0x7f);
308 }
309 let base = (entry_offset as u64)
310 .checked_sub(value)
311 .filter(|&b| b >= 12)
312 .ok_or(Error::Corrupt("ofs delta base offset"))?;
313 Ok((BaseRef::Offset(base as usize), p))
314 }
315 OBJ_REF_DELTA => {
316 if pos + 20 > end {
317 return Err(Error::UnexpectedEof);
318 }
319 let bytes = &data[pos..pos + 20];
320 Ok((
321 BaseRef::Oid(Oid::from_bytes(bytes.try_into().unwrap())),
322 pos + 20,
323 ))
324 }
325 _ => Err(Error::Corrupt("object type code")),
326 }
327}
328
329fn materialize(
336 data: &[u8],
337 content_end: usize,
338 index: &[(Oid, u32)],
339 offset: usize,
340 mut cache: Option<&mut BaseCache>,
341) -> Result<(Kind, Vec<u8>)> {
342 let mut chain: Vec<(usize, usize, usize)> = Vec::new();
345 let mut cur = offset;
346
347 let (kind, mut body) = loop {
348 if chain.len() > MAX_DELTA_DEPTH {
349 return Err(Error::Corrupt("delta chain too deep"));
350 }
351 if let Some((kind, body)) = cache.as_deref().and_then(|c| c.get(cur)) {
352 break (kind, body.to_vec());
353 }
354 let (code, size, pos) = entry_header(data, cur, content_end)?;
355 let (base, zlib_start) = base_ref(data, code, pos, cur, content_end)?;
356 match base {
357 BaseRef::None => {
358 let inflated = zlib::inflate_zlib(&data[zlib_start..content_end], Some(size))?;
359 if inflated.data.len() != size {
360 return Err(Error::Corrupt("entry size"));
361 }
362 let kind = kind_of(code)?;
363 if let Some(c) = cache.as_deref_mut() {
364 c.insert(cur, kind, &inflated.data);
365 }
366 break (kind, inflated.data);
367 }
368 BaseRef::Offset(base_offset) => {
369 chain.push((cur, zlib_start, size));
370 cur = base_offset;
371 }
372 BaseRef::Oid(oid) => {
373 chain.push((cur, zlib_start, size));
374 cur = lookup(index, &oid).ok_or(Error::MissingBase)? as usize;
375 }
376 }
377 };
378
379 for &(entry_offset, zlib_start, size) in chain.iter().rev() {
380 let inflated = zlib::inflate_zlib(&data[zlib_start..content_end], Some(size))?;
381 if inflated.data.len() != size {
382 return Err(Error::Corrupt("entry size"));
383 }
384 body = delta::apply(&body, &inflated.data)?;
385 if let Some(c) = cache.as_deref_mut() {
386 c.insert(entry_offset, kind, &body);
387 }
388 }
389 Ok((kind, body))
390}
391
392#[cfg(feature = "write")]
397pub fn write_pack(objects: &[(Kind, &[u8])]) -> Vec<u8> {
398 let mut out = Vec::new();
399 out.extend_from_slice(b"PACK");
400 out.extend_from_slice(&2u32.to_be_bytes());
401 out.extend_from_slice(&(objects.len() as u32).to_be_bytes());
402
403 for (kind, body) in objects {
404 let code: u8 = match kind {
405 Kind::Commit => OBJ_COMMIT,
406 Kind::Tree => OBJ_TREE,
407 Kind::Blob => OBJ_BLOB,
408 Kind::Tag => OBJ_TAG,
409 };
410 let mut size = body.len();
411 let mut byte = (code << 4) | (size & 0x0f) as u8;
412 size >>= 4;
413 while size > 0 {
414 out.push(byte | 0x80);
415 byte = (size & 0x7f) as u8;
416 size >>= 7;
417 }
418 out.push(byte);
419 out.extend_from_slice(&zlib::deflate_zlib(body));
420 }
421
422 let digest = sha1::digest(&out);
423 out.extend_from_slice(&digest);
424 out
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430
431 fn with_trailer(mut content: Vec<u8>) -> Vec<u8> {
435 let digest = sha1::digest(&content);
436 content.extend_from_slice(&digest);
437 content
438 }
439
440 fn header(count: u32) -> Vec<u8> {
441 let mut c = Vec::new();
442 c.extend_from_slice(b"PACK");
443 c.extend_from_slice(&2u32.to_be_bytes());
444 c.extend_from_slice(&count.to_be_bytes());
445 c
446 }
447
448 #[test]
451 fn ref_delta_base_into_trailer_rejected() {
452 let mut c = header(1);
453 c.push(0x70); c.extend_from_slice(&[0xaa; 5]); assert!(Pack::parse(&with_trailer(c)).is_err());
456 }
457
458 #[test]
460 fn ofs_delta_varint_into_trailer_rejected() {
461 let mut c = header(1);
462 c.push(0x60); c.extend_from_slice(&[0x80; 3]); assert!(Pack::parse(&with_trailer(c)).is_err());
465 }
466
467 #[test]
469 fn empty_pack() {
470 let pack = with_trailer(header(0));
471 assert!(Pack::parse(&pack).unwrap().is_empty());
472 }
473
474 #[cfg(feature = "write")]
476 #[test]
477 fn write_pack_roundtrip() {
478 let blob = b"hello\n".as_slice();
479 let large: Vec<u8> = (0..100_000usize).map(|i| (i % 251) as u8).collect();
480 let objects = [(Kind::Blob, blob), (Kind::Blob, large.as_slice())];
481 let data = write_pack(&objects);
482
483 let pack = Pack::parse(&data).unwrap();
484 assert_eq!(pack.len(), 2);
485 for (kind, body) in &objects {
486 let oid = crate::object::compute_oid(*kind, body);
487 let (got_kind, got_body) = pack.read_object(&oid).unwrap().unwrap();
488 assert_eq!(got_kind, *kind);
489 assert_eq!(got_body, *body);
490 }
491 }
492}