1#![cfg(unix)]
16
17use super::repo_read_object::{ObjectKind, OBJECT_SIZE_LIMIT, RawObject, inflate_exact};
18use std::fs::File;
19use std::os::unix::fs::FileExt;
20use std::path::Path;
21
22const DELTA_DEPTH_LIMIT: usize = 100;
24
25const FANOUT_END: u64 = 8 + 256 * 4;
27
28#[derive(Debug)]
30pub struct Pack {
31 index: File,
33 data: File,
35 hash_length: usize,
37 count: u64,
39 data_length: u64,
41 fanout: Vec<u32>,
43}
44
45fn read_at(file: &File, position: u64, length: usize) -> Option<Vec<u8>> {
47 let mut buffer: Vec<u8> = vec![0; length];
48 file.read_exact_at(buffer.as_mut_slice(), position).ok()?;
49 return Some(buffer);
50}
51
52fn be32(file: &File, position: u64) -> Option<u32> {
54 let bytes: Vec<u8> = read_at(file, position, 4)?;
55 return Some(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]));
56}
57
58fn delta_size(delta: &[u8], cursor: &mut usize) -> Option<usize> {
60 let mut size: usize = 0;
61 let mut shift: u32 = 0;
62 loop {
63 let byte: u8 = *delta.get(*cursor)?;
64 *cursor += 1;
65 size |= usize::from(byte & 0x7f).checked_shl(shift)?;
66 if byte & 0x80 == 0 {
67 return Some(size);
68 }
69 shift += 7;
70 if shift > 56 {
71 return None;
72 }
73 }
74}
75
76pub fn apply_delta(base: &[u8], delta: &[u8]) -> Option<Vec<u8>> {
87 let mut cursor: usize = 0;
88 if delta_size(delta, &mut cursor)? != base.len() {
89 return None;
90 }
91 let result_size: usize = delta_size(delta, &mut cursor)?;
92 if result_size > OBJECT_SIZE_LIMIT {
93 return None;
94 }
95 let mut result: Vec<u8> = Vec::with_capacity(result_size);
96 while cursor < delta.len() {
97 let op: u8 = delta[cursor];
98 cursor += 1;
99 if op & 0x80 != 0 {
100 let mut offset: usize = 0;
101 let mut length: usize = 0;
102 for bit in 0..4 {
103 if op & (1 << bit) != 0 {
104 offset |= usize::from(*delta.get(cursor)?) << (8 * bit);
105 cursor += 1;
106 }
107 }
108 for bit in 0..3 {
109 if op & (0x10 << bit) != 0 {
110 length |= usize::from(*delta.get(cursor)?) << (8 * bit);
111 cursor += 1;
112 }
113 }
114 if length == 0 {
115 length = 0x10000;
116 }
117 result.extend_from_slice(base.get(offset..offset.checked_add(length)?)?);
118 } else if op != 0 {
119 let end: usize = cursor.checked_add(usize::from(op))?;
120 result.extend_from_slice(delta.get(cursor..end)?);
121 cursor = end;
122 } else {
123 return None;
124 }
125 if result.len() > result_size {
126 return None;
127 }
128 }
129 if result.len() != result_size {
130 return None;
131 }
132 return Some(result);
133}
134
135impl Pack {
136 pub fn open(index_path: &Path, hash_length: usize) -> Option<Pack> {
139 let index: File = File::open(index_path).ok()?;
140 let data: File = File::open(index_path.with_extension("pack")).ok()?;
141 let header: Vec<u8> = read_at(&index, 0, 8)?;
142 if header != b"\xfftOc\0\0\0\x02" {
143 return None;
144 }
145 let mut fanout: Vec<u32> = Vec::with_capacity(256);
146 for slot in 0..256_u64 {
147 fanout.push(be32(&index, 8 + slot * 4)?);
148 }
149 if fanout.windows(2).any(|pair| { return pair[0] > pair[1]; }) {
150 return None;
151 }
152 let count: u64 = u64::from(fanout[255]);
153 let index_length: u64 = index.metadata().ok()?.len();
154 let minimum: u64 = FANOUT_END + count * (hash_length as u64 + 8) + 2 * hash_length as u64;
155 if index_length < minimum {
156 return None;
157 }
158 let data_length: u64 = data.metadata().ok()?.len();
159 let pack_header: Vec<u8> = read_at(&data, 0, 12)?;
161 let pack_count: u32 = u32::from_be_bytes([pack_header[8], pack_header[9], pack_header[10], pack_header[11]]);
162 if &pack_header[..4] != b"PACK" || !matches!(pack_header[7], 2 | 3) || pack_header[4..7] != [0, 0, 0] || u64::from(pack_count) != count {
163 return None;
164 }
165 return Some(Pack { index, data, hash_length, count, data_length, fanout });
166 }
167
168 pub fn find(&self, name: &[u8]) -> Option<u64> {
170 if name.len() != self.hash_length {
171 return None;
172 }
173 let first: usize = usize::from(name[0]);
174 let mut low: u64 = if first == 0 { 0 } else { u64::from(self.fanout[first - 1]) };
175 let mut high: u64 = u64::from(self.fanout[first]);
176 let names_at: u64 = FANOUT_END;
177 while low < high {
178 let middle: u64 = low + (high - low) / 2;
179 let candidate: Vec<u8> = read_at(&self.index, names_at + middle * self.hash_length as u64, self.hash_length)?;
180 match candidate.as_slice().cmp(name) {
181 std::cmp::Ordering::Equal => return self.offset_of(middle),
182 std::cmp::Ordering::Less => low = middle + 1,
183 std::cmp::Ordering::Greater => high = middle,
184 }
185 }
186 return None;
187 }
188
189 fn offset_of(&self, position: u64) -> Option<u64> {
191 let offsets_at: u64 = FANOUT_END + self.count * (self.hash_length as u64 + 4);
192 let word: u32 = be32(&self.index, offsets_at + position * 4)?;
193 if word & 0x8000_0000 == 0 {
194 return Some(u64::from(word));
195 }
196 let large_at: u64 = offsets_at + self.count * 4 + u64::from(word & 0x7fff_ffff) * 8;
197 let bytes: Vec<u8> = read_at(&self.index, large_at, 8)?;
198 let mut array: [u8; 8] = [0; 8];
199 array.copy_from_slice(bytes.as_slice());
200 return Some(u64::from_be_bytes(array));
201 }
202
203 pub fn read(&self, offset: u64, by_name: &dyn Fn(&[u8]) -> Option<RawObject>, depth: usize) -> Option<RawObject> {
213 if depth > DELTA_DEPTH_LIMIT || offset < 12 || offset >= self.data_length {
214 return None;
215 }
216 let head: Vec<u8> = read_at(&self.data, offset, (32 + self.hash_length).min((self.data_length - offset) as usize))?;
218 let mut cursor: usize = 0;
219 let first: u8 = head[0];
220 cursor += 1;
221 let code: u8 = (first >> 4) & 7;
222 let mut size: usize = usize::from(first & 15);
223 let mut shift: u32 = 4;
224 let mut byte: u8 = first;
225 while byte & 0x80 != 0 {
226 byte = *head.get(cursor)?;
227 cursor += 1;
228 size |= usize::from(byte & 0x7f).checked_shl(shift)?;
229 shift += 7;
230 if shift > 60 {
231 return None;
232 }
233 }
234 match code {
235 1 => return self.base_object(ObjectKind::Commit, offset + cursor as u64, size),
236 2 => return self.base_object(ObjectKind::Tree, offset + cursor as u64, size),
237 3 => return self.base_object(ObjectKind::Blob, offset + cursor as u64, size),
238 4 => return self.base_object(ObjectKind::Tag, offset + cursor as u64, size),
239 6 => {
240 let mut c: u8 = *head.get(cursor)?;
241 cursor += 1;
242 let mut distance: u64 = u64::from(c & 0x7f);
243 while c & 0x80 != 0 {
244 c = *head.get(cursor)?;
245 cursor += 1;
246 distance = distance.checked_add(1)?.checked_shl(7)? | u64::from(c & 0x7f);
247 }
248 let base: RawObject = self.read(offset.checked_sub(distance)?, by_name, depth + 1)?;
249 return self.apply(base, offset + cursor as u64, size);
250 }
251 7 => {
252 let name: &[u8] = head.get(cursor..cursor + self.hash_length)?;
253 cursor += self.hash_length;
254 let base: RawObject = by_name(name)?;
255 return self.apply(base, offset + cursor as u64, size);
256 }
257 _ => return None,
258 }
259 }
260
261 fn inflated(&self, start: u64, size: usize) -> Option<Vec<u8>> {
263 if size > OBJECT_SIZE_LIMIT {
265 return None;
266 }
267 let remaining: u64 = self.data_length.checked_sub(start)?.checked_sub(self.hash_length as u64)?;
268 for slack in [size / 4 + 4096, size * 2 + 65536] {
269 let want: usize = usize::try_from(remaining).ok()?.min(size + slack);
270 let window: Vec<u8> = read_at(&self.data, start, want)?;
271 if let Some(found) = inflate_exact(window.as_slice(), size) {
272 return Some(found);
273 }
274 if want as u64 == remaining {
275 break;
276 }
277 }
278 return None;
279 }
280
281 fn base_object(&self, kind: ObjectKind, start: u64, size: usize) -> Option<RawObject> {
283 return Some(RawObject { kind, bytes: self.inflated(start, size)? });
284 }
285
286 fn apply(&self, base: RawObject, start: u64, delta_length: usize) -> Option<RawObject> {
288 let delta: Vec<u8> = self.inflated(start, delta_length)?;
289 return Some(RawObject { kind: base.kind, bytes: apply_delta(base.bytes.as_slice(), delta.as_slice())? });
290 }
291}
292
293#[cfg(test)]
294#[path = "repo_read_pack_tests.rs"]
295mod tests;