This repository has no description
1use std::sync::atomic::AtomicBool;
2
3use gix_features::progress::{DynNestedProgress, Progress};
4use gix_object::WriteTo;
5
6use crate::index;
7
8///
9pub mod integrity {
10 use std::marker::PhantomData;
11
12 use gix_object::bstr::BString;
13
14 /// Returned by [`index::File::verify_integrity()`][crate::index::File::verify_integrity()].
15 #[derive(thiserror::Error, Debug)]
16 #[allow(missing_docs)]
17 pub enum Error {
18 #[error("Reserialization of an object failed")]
19 Io(#[from] std::io::Error),
20 #[error(
21 "The fan at index {index} is out of order as it's larger then the following value."
22 )]
23 Fan { index: usize },
24 #[error("{kind} object {id} could not be decoded")]
25 ObjectDecode {
26 source: gix_object::decode::Error,
27 kind: gix_object::Kind,
28 id: gix_hash::ObjectId,
29 },
30 #[error(
31 "{kind} object {id} wasn't re-encoded without change, wanted\n{expected}\n\nGOT\n\n{actual}"
32 )]
33 ObjectEncodeMismatch {
34 kind: gix_object::Kind,
35 id: gix_hash::ObjectId,
36 expected: BString,
37 actual: BString,
38 },
39 }
40
41 /// Returned by [`index::File::verify_integrity()`][crate::index::File::verify_integrity()].
42 pub struct Outcome {
43 /// The computed checksum of the index which matched the stored one.
44 pub actual_index_checksum: gix_hash::ObjectId,
45 /// The packs traversal outcome, if one was provided
46 pub pack_traverse_statistics: Option<crate::index::traverse::Statistics>,
47 }
48
49 /// Additional options to define how the integrity should be verified.
50 #[derive(Clone)]
51 pub struct Options<F> {
52 /// The thoroughness of the verification
53 pub verify_mode: crate::index::verify::Mode,
54 /// The way to traverse packs
55 pub traversal: crate::index::traverse::Algorithm,
56 /// The amount of threads to use of `Some(N)`, with `None|Some(0)` using all available cores are used.
57 pub thread_limit: Option<usize>,
58 /// A function to create a pack cache
59 pub make_pack_lookup_cache: F,
60 }
61
62 impl Default for Options<fn() -> crate::cache::Never> {
63 fn default() -> Self {
64 Options {
65 verify_mode: Default::default(),
66 traversal: Default::default(),
67 thread_limit: None,
68 make_pack_lookup_cache: || crate::cache::Never,
69 }
70 }
71 }
72
73 /// The progress ids used in [`index::File::verify_integrity()`][crate::index::File::verify_integrity()].
74 ///
75 /// Use this information to selectively extract the progress of interest in case the parent application has custom visualization.
76 #[derive(Debug, Copy, Clone)]
77 pub enum ProgressId {
78 /// The amount of bytes read to verify the index checksum.
79 ChecksumBytes,
80 /// A root progress for traversal which isn't actually used directly, but here to link to the respective `ProgressId` types.
81 Traverse(PhantomData<crate::index::verify::index::traverse::ProgressId>),
82 }
83
84 impl From<ProgressId> for gix_features::progress::Id {
85 fn from(v: ProgressId) -> Self {
86 match v {
87 ProgressId::ChecksumBytes => *b"PTHI",
88 ProgressId::Traverse(_) => gix_features::progress::UNKNOWN,
89 }
90 }
91 }
92}
93
94///
95pub mod checksum {
96 /// Returned by [`index::File::verify_checksum()`][crate::index::File::verify_checksum()].
97 pub type Error = crate::verify::checksum::Error;
98}
99
100/// Various ways in which a pack and index can be verified
101#[derive(Default, Debug, Eq, PartialEq, Hash, Clone, Copy)]
102pub enum Mode {
103 /// Validate the object hash and CRC32
104 HashCrc32,
105 /// Validate hash and CRC32, and decode each non-Blob object.
106 /// Each object should be valid, i.e. be decodable.
107 HashCrc32Decode,
108 /// Validate hash and CRC32, and decode and encode each non-Blob object.
109 /// Each object should yield exactly the same hash when re-encoded.
110 #[default]
111 HashCrc32DecodeEncode,
112}
113
114/// Information to allow verifying the integrity of an index with the help of its corresponding pack.
115pub struct PackContext<'a, F> {
116 /// The pack data file itself.
117 pub data: &'a crate::data::File,
118 /// The options further configuring the pack traversal and verification
119 pub options: integrity::Options<F>,
120}
121
122/// Verify and validate the content of the index file
123impl<T> index::File<T>
124where
125 T: crate::FileData + Sync,
126{
127 /// Returns the trailing hash stored at the end of this index file.
128 ///
129 /// It's a hash over all bytes of the index.
130 pub fn index_checksum(&self) -> gix_hash::ObjectId {
131 gix_hash::ObjectId::from_bytes_or_panic(&self.data[self.data.len() - self.hash_len..])
132 }
133
134 /// Returns the hash of the pack data file that this index file corresponds to.
135 ///
136 /// It should [`crate::data::File::checksum()`] of the corresponding pack data file.
137 pub fn pack_checksum(&self) -> gix_hash::ObjectId {
138 let from = self.data.len() - self.hash_len * 2;
139 gix_hash::ObjectId::from_bytes_or_panic(&self.data[from..][..self.hash_len])
140 }
141
142 /// Validate that our [`index_checksum()`][index::File::index_checksum()] matches the actual contents
143 /// of this index file, and return it if it does.
144 pub fn verify_checksum(
145 &self,
146 progress: &mut dyn Progress,
147 should_interrupt: &AtomicBool,
148 ) -> Result<gix_hash::ObjectId, checksum::Error> {
149 crate::verify::checksum_on_disk_or_mmap(
150 self.path(),
151 &self.data,
152 self.index_checksum(),
153 self.object_hash,
154 progress,
155 should_interrupt,
156 )
157 }
158
159 /// The most thorough validation of integrity of both index file and the corresponding pack data file, if provided.
160 /// Returns the checksum of the index file, the traversal outcome and the given progress if the integrity check is successful.
161 ///
162 /// If `pack` is provided, it is expected (and validated to be) the pack belonging to this index.
163 /// It will be used to validate internal integrity of the pack before checking each objects integrity
164 /// is indeed as advertised via its SHA1 as stored in this index, as well as the CRC32 hash.
165 /// The last member of the Option is a function returning an implementation of [`crate::cache::DecodeEntry`] to be used if
166 /// the [`index::traverse::Algorithm`] is `Lookup`.
167 /// To set this to `None`, use `None::<(_, _, _, fn() -> crate::cache::Never)>`.
168 ///
169 /// The `thread_limit` optionally specifies the amount of threads to be used for the [pack traversal][index::File::traverse()].
170 /// `make_cache` is only used in case a `pack` is specified, use existing implementations in the [`crate::cache`] module.
171 ///
172 /// # Tradeoffs
173 ///
174 /// The given `progress` is inevitably consumed if there is an error, which is a tradeoff chosen to easily allow using `?` in the
175 /// error case.
176 pub fn verify_integrity<C, F>(
177 &self,
178 pack: Option<PackContext<'_, F>>,
179 progress: &mut dyn DynNestedProgress,
180 should_interrupt: &AtomicBool,
181 ) -> Result<integrity::Outcome, index::traverse::Error<index::verify::integrity::Error>>
182 where
183 C: crate::cache::DecodeEntry,
184 F: Fn() -> C + Send + Clone,
185 {
186 if let Some(first_invalid) = crate::verify::fan(&self.fan) {
187 return Err(index::traverse::Error::Processor(integrity::Error::Fan {
188 index: first_invalid,
189 }));
190 }
191
192 match pack {
193 Some(PackContext {
194 data: pack,
195 options:
196 integrity::Options {
197 verify_mode,
198 traversal,
199 thread_limit,
200 make_pack_lookup_cache,
201 },
202 }) => self
203 .traverse(
204 pack,
205 progress,
206 should_interrupt,
207 {
208 let mut encode_buf = Vec::with_capacity(2048);
209 move |kind, data, index_entry, progress| {
210 Self::verify_entry(
211 verify_mode,
212 &mut encode_buf,
213 kind,
214 data,
215 index_entry,
216 progress,
217 )
218 }
219 },
220 index::traverse::Options {
221 traversal,
222 thread_limit,
223 check: index::traverse::SafetyCheck::All,
224 make_pack_lookup_cache,
225 },
226 )
227 .map(|o| integrity::Outcome {
228 actual_index_checksum: o.actual_index_checksum,
229 pack_traverse_statistics: Some(o.statistics),
230 }),
231 None => self
232 .verify_checksum(
233 &mut progress.add_child_with_id(
234 "Sha1 of index".into(),
235 integrity::ProgressId::ChecksumBytes.into(),
236 ),
237 should_interrupt,
238 )
239 .map_err(index::traverse::Error::IndexVerify)
240 .map(|id| integrity::Outcome {
241 actual_index_checksum: id,
242 pack_traverse_statistics: None,
243 }),
244 }
245 }
246
247 #[allow(clippy::too_many_arguments)]
248 fn verify_entry(
249 verify_mode: Mode,
250 encode_buf: &mut Vec<u8>,
251 object_kind: gix_object::Kind,
252 buf: &[u8],
253 index_entry: &index::Entry,
254 _progress: &dyn gix_features::progress::Progress,
255 ) -> Result<(), integrity::Error> {
256 if let Mode::HashCrc32Decode | Mode::HashCrc32DecodeEncode = verify_mode {
257 use gix_object::Kind::*;
258 match object_kind {
259 Tree | Commit | Tag => {
260 let object =
261 gix_object::ObjectRef::from_bytes(buf, object_kind, index_entry.oid.kind())
262 .map_err(|err| integrity::Error::ObjectDecode {
263 source: err,
264 kind: object_kind,
265 id: index_entry.oid,
266 })?;
267 if let Mode::HashCrc32DecodeEncode = verify_mode {
268 encode_buf.clear();
269 object.write_to(&mut *encode_buf)?;
270 if encode_buf.as_slice() != buf {
271 return Err(integrity::Error::ObjectEncodeMismatch {
272 kind: object_kind,
273 id: index_entry.oid,
274 expected: buf.into(),
275 actual: encode_buf.clone().into(),
276 });
277 }
278 }
279 }
280 Blob => {}
281 }
282 }
283 Ok(())
284 }
285}