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 / init.rs
7.9 kB 211 lines
1use std::{ 2 mem::size_of, 3 path::{Path, PathBuf}, 4}; 5 6use crate::index::{self, FAN_LEN, V2_SIGNATURE, Version}; 7 8/// Returned by [`index::File::at()`]. 9#[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 25/// Instantiation 26impl index::File<crate::MMap> { 27 /// Open the pack index file at the given `path`. 28 /// 29 /// The `object_hash` is a way to read (and write) the same file format with different hashes, as the hash kind 30 /// isn't stored within the file format itself. 31 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 /// Instantiate an index file from `data` as assumed to be read or memory-mapped from `path`. 49 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!( 57 "Pack index of size {idx_len} is too small for even an empty index" 58 ), 59 }); 60 } 61 let (kind, fan, num_objects) = { 62 let (kind, d) = { 63 let (sig, d) = data.split_at(V2_SIGNATURE.len()); 64 if sig == V2_SIGNATURE { 65 (Version::V2, d) 66 } else { 67 (Version::V1, &data[..]) 68 } 69 }; 70 let d = { 71 if let Version::V2 = kind { 72 let (vd, dr) = d.split_at(N32_SIZE); 73 let version = crate::read_u32(vd); 74 if version != Version::V2 as u32 { 75 return Err(Error::UnsupportedVersion { version }); 76 } 77 dr 78 } else { 79 d 80 } 81 }; 82 let (fan, bytes_read) = read_fan(d); 83 let (_, _d) = d.split_at(bytes_read); 84 let num_objects = fan[FAN_LEN - 1]; 85 86 (kind, fan, num_objects) 87 }; 88 validate_fan(&fan)?; 89 validate_size(&data, kind, num_objects, hash_len)?; 90 Ok(Self { 91 data, 92 path, 93 version: kind, 94 num_objects, 95 fan, 96 hash_len, 97 object_hash, 98 }) 99 } 100} 101 102fn read_fan(d: &[u8]) -> ([u32; FAN_LEN], usize) { 103 assert!(d.len() >= FAN_LEN * N32_SIZE); 104 105 let mut fan = [0; FAN_LEN]; 106 for (c, f) in d.chunks_exact(N32_SIZE).zip(fan.iter_mut()) { 107 *f = crate::read_u32(c); 108 } 109 (fan, FAN_LEN * N32_SIZE) 110} 111 112fn validate_fan(fan: &[u32; FAN_LEN]) -> Result<(), Error> { 113 if !crate::fan_is_monotonically_increasing(fan) { 114 return Err(Error::Corrupt { 115 message: "Pack index fan-out table must be monotonically increasing".into(), 116 }); 117 } 118 Ok(()) 119} 120 121fn validate_size( 122 data: &[u8], 123 kind: Version, 124 num_objects: u32, 125 hash_len: usize, 126) -> Result<(), Error> { 127 let num_objects = num_objects as usize; 128 let footer_size = hash_len * 2; 129 let expected_size = match kind { 130 Version::V1 => FAN_LEN 131 .checked_mul(N32_SIZE) 132 .and_then(|size| size.checked_add(num_objects.checked_mul(N32_SIZE + hash_len)?)) 133 .and_then(|size| size.checked_add(footer_size)) 134 .ok_or_else(|| Error::Corrupt { 135 message: "Pack index size overflowed while validating version 1 layout".into(), 136 })?, 137 Version::V2 => { 138 let v2_header_size = V2_SIGNATURE.len() + N32_SIZE + FAN_LEN * N32_SIZE; 139 let oid_bytes = num_objects 140 .checked_mul(hash_len) 141 .ok_or_else(|| Error::Corrupt { 142 message: "Pack index size overflowed while validating object ids".into(), 143 })?; 144 let table_bytes = num_objects 145 .checked_mul(N32_SIZE) 146 .ok_or_else(|| Error::Corrupt { 147 message: "Pack index size overflowed while validating 32-bit tables".into(), 148 })?; 149 let offset32_start = v2_header_size 150 .checked_add(oid_bytes) 151 .and_then(|size| size.checked_add(table_bytes)) 152 .ok_or_else(|| Error::Corrupt { 153 message: "Pack index size overflowed while locating 32-bit offsets".into(), 154 })?; 155 let offset32_end = 156 offset32_start 157 .checked_add(table_bytes) 158 .ok_or_else(|| Error::Corrupt { 159 message: "Pack index size overflowed while locating 32-bit offsets".into(), 160 })?; 161 if offset32_end > data.len() { 162 return Err(Error::Corrupt { 163 message: format!( 164 "Pack index of size {} is too small for {} objects in version 2", 165 data.len(), 166 num_objects 167 ), 168 }); 169 } 170 let (large_offsets, max_large_offset_index) = data[offset32_start..offset32_end] 171 .chunks_exact(N32_SIZE) 172 .filter_map(|offset| { 173 let offset = crate::read_u32(offset); 174 (offset & (1 << 31) != 0).then_some((offset ^ (1 << 31)) as usize) 175 }) 176 .fold((0usize, 0usize), |(count, max_index), index| { 177 (count + 1, max_index.max(index)) 178 }); 179 v2_header_size 180 .checked_add(oid_bytes) 181 .and_then(|size| size.checked_add(table_bytes)) 182 .and_then(|size| size.checked_add(table_bytes)) 183 .and_then(|size| size.checked_add(large_offsets.checked_mul(size_of::<u64>())?)) 184 .and_then(|size| size.checked_add(footer_size)) 185 .ok_or_else(|| Error::Corrupt { 186 message: "Pack index size overflowed while validating version 2 layout".into(), 187 }) 188 .and_then(|expected_size| { 189 if large_offsets > 0 && max_large_offset_index >= large_offsets { 190 return Err(Error::Corrupt { 191 message: format!( 192 "Pack index references large offset {max_large_offset_index}, but only {large_offsets} large offsets are present" 193 ), 194 }); 195 } 196 Ok(expected_size) 197 })? 198 } 199 }; 200 if data.len() != expected_size { 201 // Aborting here is needed for protection against malformed inputs, or the offset access done later can panic 202 // as it's done without explicit error handling. 203 return Err(Error::Corrupt { 204 message: format!( 205 "Pack index size is incorrect, expected {expected_size} bytes for {num_objects} objects in version {kind:?}, but got {} bytes", 206 data.len() 207 ), 208 }); 209 } 210 Ok(()) 211}