1use super::IndexEntry;
3use crate::{Error, ObjectFormat, ObjectId, Result};
4
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub struct IndexRange {
9 pub offset: u64,
10 pub length: usize,
11}
12
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum LookupStatus {
15 Read(IndexRange),
16 Found { row: u32, entry: IndexEntry },
17 Missing,
18}
19
20pub struct IndexHeader {
26 fanout: [u32; 256],
27 format: ObjectFormat,
28 crc_start: u64,
29 offset_start: u64,
30 large_start: u64,
31 large_count: u32,
32 pack_end: u64,
33}
34
35impl IndexHeader {
36 pub const BYTES: usize = 1032;
37
38 pub fn parse(
39 format: ObjectFormat,
40 header: &[u8],
41 index_bytes: u64,
42 pack_bytes: u64,
43 ) -> Result<Self> {
44 if header.len() != Self::BYTES || &header[..8] != b"\xfftOc\0\0\0\x02" {
45 return Err(Error::CorruptPack);
46 }
47 let mut fanout = [0; 256];
48 let mut previous = 0;
49 for (slot, bytes) in fanout.iter_mut().zip(header[8..].chunks_exact(4)) {
50 *slot = number(bytes);
51 if *slot < previous {
52 return Err(Error::CorruptPack);
53 }
54 previous = *slot;
55 }
56 let count = u64::from(previous);
57 let crc_start = Self::BYTES as u64 + count * format.id_length() as u64;
58 let offset_start = crc_start + count * 4;
59 let large_start = offset_start + count * 4;
60 let table_bytes = index_bytes
61 .checked_sub(large_start + 2 * format.id_length() as u64)
62 .ok_or(Error::CorruptPack)?;
63 if table_bytes % 8 != 0 || table_bytes / 8 > count || table_bytes / 8 > 0x8000_0000 {
64 return Err(Error::CorruptPack);
65 }
66 let pack_end = pack_bytes
67 .checked_sub(format.id_length() as u64)
68 .filter(|end| *end >= 12)
69 .ok_or(Error::CorruptPack)?;
70 Ok(Self {
71 fanout,
72 format,
73 crc_start,
74 offset_start,
75 large_start,
76 large_count: (table_bytes / 8) as u32,
77 pack_end,
78 })
79 }
80
81 pub fn object_count(&self) -> u32 {
82 self.fanout[255]
83 }
84
85 pub fn lookup(&self, id: ObjectId) -> Result<IndexLookup> {
88 if id.format() != self.format {
89 return Err(Error::InvalidObjectId);
90 }
91 let first = usize::from(id.as_bytes()[0]);
92 let lower = if first == 0 {
93 0
94 } else {
95 self.fanout[first - 1]
96 };
97 let upper = self.fanout[first];
98 let mut lookup = IndexLookup {
99 id,
100 lower,
101 upper,
102 row: 0,
103 crc32: 0,
104 crc_start: self.crc_start,
105 offset_start: self.offset_start,
106 large_start: self.large_start,
107 large_count: self.large_count,
108 pack_end: self.pack_end,
109 state: State::Missing,
110 };
111 lookup.search();
112 Ok(lookup)
113 }
114}
115
116#[derive(Clone, Copy)]
117enum State {
118 Id(IndexRange),
119 Crc(IndexRange),
120 Offset(IndexRange),
121 Large(IndexRange),
122 Found(IndexEntry),
123 Missing,
124 Failed(Error),
125}
126
127pub struct IndexLookup {
131 id: ObjectId,
132 lower: u32,
133 upper: u32,
134 row: u32,
135 crc32: u32,
136 crc_start: u64,
137 offset_start: u64,
138 large_start: u64,
139 large_count: u32,
140 pack_end: u64,
141 state: State,
142}
143
144impl IndexLookup {
145 pub fn status(&self) -> Result<LookupStatus> {
146 Ok(match self.state {
147 State::Id(range) | State::Crc(range) | State::Offset(range) | State::Large(range) => {
148 LookupStatus::Read(range)
149 }
150 State::Found(entry) => LookupStatus::Found {
151 row: self.row,
152 entry,
153 },
154 State::Missing => LookupStatus::Missing,
155 State::Failed(error) => return Err(error),
156 })
157 }
158
159 pub fn update(&mut self, bytes: &[u8]) -> Result<LookupStatus> {
160 let result = self.reply(bytes).and_then(|()| self.status());
161 if let Err(error) = result {
162 self.state = State::Failed(error);
163 }
164 result
165 }
166
167 fn search(&mut self) {
168 if self.lower == self.upper {
169 self.state = State::Missing;
170 return;
171 }
172 self.row = self.lower + (self.upper - self.lower) / 2;
173 self.state = State::Id(IndexRange {
174 offset: IndexHeader::BYTES as u64
175 + u64::from(self.row) * self.id.format().id_length() as u64,
176 length: self.id.format().id_length(),
177 });
178 }
179
180 fn reply(&mut self, bytes: &[u8]) -> Result<()> {
181 let LookupStatus::Read(range) = self.status()? else {
182 return if bytes.is_empty() {
183 Ok(())
184 } else {
185 Err(Error::CorruptPack)
186 };
187 };
188 if bytes.len() != range.length {
189 return Err(Error::CorruptPack);
190 }
191 match self.state {
192 State::Id(_) => self.candidate(bytes)?,
193 State::Crc(_) => {
194 self.crc32 = number(bytes);
195 self.state = State::Offset(IndexRange {
196 offset: self.offset_start + u64::from(self.row) * 4,
197 length: 4,
198 });
199 }
200 State::Offset(_) => {
201 let offset = number(bytes);
202 if offset & 0x8000_0000 == 0 {
203 self.found(u64::from(offset))?;
204 } else {
205 let index = offset & 0x7fff_ffff;
206 if index >= self.large_count {
207 return Err(Error::CorruptPack);
208 }
209 self.state = State::Large(IndexRange {
210 offset: self.large_start + u64::from(index) * 8,
211 length: 8,
212 });
213 }
214 }
215 State::Large(_) => self.found(u64::from_be_bytes(
216 bytes.try_into().map_err(|_| Error::CorruptPack)?,
217 ))?,
218 _ => unreachable!(),
219 }
220 Ok(())
221 }
222
223 fn candidate(&mut self, bytes: &[u8]) -> Result<()> {
224 if bytes[0] != self.id.as_bytes()[0] {
225 return Err(Error::CorruptPack);
226 }
227 match bytes.cmp(self.id.as_bytes()) {
228 core::cmp::Ordering::Less => {
229 self.lower = self.row + 1;
230 self.search();
231 }
232 core::cmp::Ordering::Greater => {
233 self.upper = self.row;
234 self.search();
235 }
236 core::cmp::Ordering::Equal => {
237 self.state = State::Crc(IndexRange {
238 offset: self.crc_start + u64::from(self.row) * 4,
239 length: 4,
240 });
241 }
242 }
243 Ok(())
244 }
245
246 fn found(&mut self, offset: u64) -> Result<()> {
247 if offset < 12 || offset >= self.pack_end {
248 return Err(Error::CorruptPack);
249 }
250 self.state = State::Found(IndexEntry {
251 id: self.id,
252 offset,
253 crc32: self.crc32,
254 });
255 Ok(())
256 }
257}
258
259fn number(bytes: &[u8]) -> u32 {
260 u32::from_be_bytes(bytes.try_into().expect("four-byte index field"))
262}