This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / knot2 / third_party / gix-pack / src / index / verify.rs
11 kB 285 lines
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}