1use std::{
2 mem::size_of,
3 path::{Path, PathBuf},
4};
5
6use crate::index::{self, FAN_LEN, V2_SIGNATURE, Version};
7
8#[derive(thiserror::Error, Debug)]
10#[allow(missing_docs)]
11pub enum Error {
12 #[error("Could not open pack index file at '{path}'")]
13 Io {
14 source: std::io::Error,
15 path: std::path::PathBuf,
16 },
17 #[error("{message}")]
18 Corrupt { message: String },
19 #[error("Unsupported index version: {version})")]
20 UnsupportedVersion { version: u32 },
21}
22
23const N32_SIZE: usize = size_of::<u32>();
24
25impl index::File<crate::MMap> {
27 pub fn at(path: impl AsRef<Path>, object_hash: gix_hash::Kind) -> Result<Self, Error> {
32 Self::at_inner(path.as_ref(), object_hash)
33 }
34
35 fn at_inner(path: &Path, object_hash: gix_hash::Kind) -> Result<Self, Error> {
36 let data = crate::mmap::read_only(path).map_err(|source| Error::Io {
37 source,
38 path: path.to_owned(),
39 })?;
40 Self::from_data(data, path.to_owned(), object_hash)
41 }
42}
43
44impl<T> index::File<T>
45where
46 T: crate::FileData,
47{
48 pub fn from_data(data: T, path: PathBuf, object_hash: gix_hash::Kind) -> Result<Self, Error> {
50 let idx_len = data.len();
51 let hash_len = object_hash.len_in_bytes();
52
53 let footer_size = hash_len * 2;
54 if idx_len < FAN_LEN * N32_SIZE + footer_size {
55 return Err(Error::Corrupt {
56 message: format!("Pack index of size {idx_len} is too small for even an empty index"),
57 });
58 }
59 let (kind, fan, num_objects) = {
60 let (kind, d) = {
61 let (sig, d) = data.split_at(V2_SIGNATURE.len());
62 if sig == V2_SIGNATURE {
63 (Version::V2, d)
64 } else {
65 (Version::V1, &data[..])
66 }
67 };
68 let d = {
69 if let Version::V2 = kind {
70 let (vd, dr) = d.split_at(N32_SIZE);
71 let version = crate::read_u32(vd);
72 if version != Version::V2 as u32 {
73 return Err(Error::UnsupportedVersion { version });
74 }
75 dr
76 } else {
77 d
78 }
79 };
80 let (fan, bytes_read) = read_fan(d);
81 let (_, _d) = d.split_at(bytes_read);
82 let num_objects = fan[FAN_LEN - 1];
83
84 (kind, fan, num_objects)
85 };
86 validate_fan(&fan)?;
87 validate_size(&data, kind, num_objects, hash_len)?;
88 Ok(Self {
89 data,
90 path,
91 version: kind,
92 num_objects,
93 fan,
94 hash_len,
95 object_hash,
96 })
97 }
98}
99
100fn read_fan(d: &[u8]) -> ([u32; FAN_LEN], usize) {
101 assert!(d.len() >= FAN_LEN * N32_SIZE);
102
103 let mut fan = [0; FAN_LEN];
104 for (c, f) in d.chunks_exact(N32_SIZE).zip(fan.iter_mut()) {
105 *f = crate::read_u32(c);
106 }
107 (fan, FAN_LEN * N32_SIZE)
108}
109
110fn validate_fan(fan: &[u32; FAN_LEN]) -> Result<(), Error> {
111 if !crate::fan_is_monotonically_increasing(fan) {
112 return Err(Error::Corrupt {
113 message: "Pack index fan-out table must be monotonically increasing".into(),
114 });
115 }
116 Ok(())
117}
118
119fn validate_size(data: &[u8], kind: Version, num_objects: u32, hash_len: usize) -> Result<(), Error> {
120 let num_objects = num_objects as usize;
121 let footer_size = hash_len * 2;
122 let expected_size = match kind {
123 Version::V1 => FAN_LEN
124 .checked_mul(N32_SIZE)
125 .and_then(|size| size.checked_add(num_objects.checked_mul(N32_SIZE + hash_len)?))
126 .and_then(|size| size.checked_add(footer_size))
127 .ok_or_else(|| Error::Corrupt {
128 message: "Pack index size overflowed while validating version 1 layout".into(),
129 })?,
130 Version::V2 => {
131 let v2_header_size = V2_SIGNATURE.len() + N32_SIZE + FAN_LEN * N32_SIZE;
132 let oid_bytes = num_objects.checked_mul(hash_len).ok_or_else(|| Error::Corrupt {
133 message: "Pack index size overflowed while validating object ids".into(),
134 })?;
135 let table_bytes = num_objects.checked_mul(N32_SIZE).ok_or_else(|| Error::Corrupt {
136 message: "Pack index size overflowed while validating 32-bit tables".into(),
137 })?;
138 let offset32_start = v2_header_size
139 .checked_add(oid_bytes)
140 .and_then(|size| size.checked_add(table_bytes))
141 .ok_or_else(|| Error::Corrupt {
142 message: "Pack index size overflowed while locating 32-bit offsets".into(),
143 })?;
144 let offset32_end = offset32_start.checked_add(table_bytes).ok_or_else(|| Error::Corrupt {
145 message: "Pack index size overflowed while locating 32-bit offsets".into(),
146 })?;
147 if offset32_end > data.len() {
148 return Err(Error::Corrupt {
149 message: format!(
150 "Pack index of size {} is too small for {} objects in version 2",
151 data.len(),
152 num_objects
153 ),
154 });
155 }
156 let (large_offsets, max_large_offset_index) = data[offset32_start..offset32_end]
157 .chunks_exact(N32_SIZE)
158 .filter_map(|offset| {
159 let offset = crate::read_u32(offset);
160 (offset & (1 << 31) != 0).then_some((offset ^ (1 << 31)) as usize)
161 })
162 .fold((0usize, 0usize), |(count, max_index), index| {
163 (count + 1, max_index.max(index))
164 });
165 v2_header_size
166 .checked_add(oid_bytes)
167 .and_then(|size| size.checked_add(table_bytes))
168 .and_then(|size| size.checked_add(table_bytes))
169 .and_then(|size| size.checked_add(large_offsets.checked_mul(size_of::<u64>())?))
170 .and_then(|size| size.checked_add(footer_size))
171 .ok_or_else(|| Error::Corrupt {
172 message: "Pack index size overflowed while validating version 2 layout".into(),
173 })
174 .and_then(|expected_size| {
175 if large_offsets > 0 && max_large_offset_index >= large_offsets {
176 return Err(Error::Corrupt {
177 message: format!(
178 "Pack index references large offset {max_large_offset_index}, but only {large_offsets} large offsets are present"
179 ),
180 });
181 }
182 Ok(expected_size)
183 })?
184 }
185 };
186 if data.len() != expected_size {
187 return Err(Error::Corrupt {
190 message: format!(
191 "Pack index size is incorrect, expected {expected_size} bytes for {num_objects} objects in version {kind:?}, but got {} bytes",
192 data.len()
193 ),
194 });
195 }
196 Ok(())
197}