Skip to main content

gix_pack/index/
init.rs

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!("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        // Aborting here is needed for protection against malformed inputs, or the offset access done later can panic
188        // as it's done without explicit error handling.
189        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}