Skip to main content

git_zero_core/pack/
index_stream.rs

1//! Pull-based version-2 indexes over immutable, sorted entry metadata.
2use super::{IndexEntry, index::validate_entries};
3use crate::{Error, ObjectId, Result, object::Hash};
4
5#[derive(Clone, Copy)]
6enum Phase {
7    Header,
8    Fanout,
9    Ids,
10    Crcs,
11    Offsets,
12    LargeOffsets,
13    PackChecksum,
14    Trailer,
15    Done,
16}
17
18/// An allocation-free cursor for the same bytes as `write_index`. Metadata must
19/// remain immutable until EOF. Each pull copies at most one field (32 bytes),
20/// regardless of the supplied buffer size; zero means complete, never pending.
21/// Construction checks metadata before output. It does not authenticate a pack.
22pub struct IndexEncoder<'a> {
23    entries: &'a [IndexEntry],
24    pack_checksum: ObjectId,
25    hash: Hash,
26    phase: Phase,
27    next: usize,
28    fanout_end: usize,
29    large: u32,
30    pending: [u8; 32],
31    position: usize,
32    length: usize,
33    size: u64,
34}
35
36impl<'a> IndexEncoder<'a> {
37    pub fn new(entries: &'a [IndexEntry], pack_checksum: ObjectId) -> Result<Self> {
38        validate_entries(entries, pack_checksum)?;
39        let large = entries
40            .iter()
41            .filter(|entry| entry.offset >= 0x8000_0000)
42            .count();
43        let size = 1032_u64
44            .checked_add((entries.len() as u64) * (pack_checksum.format().id_length() as u64 + 8))
45            .and_then(|size| size.checked_add(large as u64 * 8))
46            .and_then(|size| size.checked_add(2 * pack_checksum.format().id_length() as u64))
47            .ok_or(Error::LimitExceeded)?;
48        Ok(Self {
49            entries,
50            pack_checksum,
51            hash: pack_checksum.format().hash(),
52            phase: Phase::Header,
53            next: 0,
54            fanout_end: 0,
55            large: 0,
56            pending: [0; 32],
57            position: 0,
58            length: 0,
59            size,
60        })
61    }
62
63    /// Complete wire length, including both checksums and any 64-bit offsets.
64    pub fn size(&self) -> u64 {
65        self.size
66    }
67
68    /// Empty buffers fail without advancing an active cursor. EOF is repeatable.
69    pub fn read(&mut self, output: &mut [u8]) -> Result<usize> {
70        if self.position == self.length {
71            if matches!(self.phase, Phase::Done) {
72                return Ok(0);
73            }
74            if output.is_empty() {
75                return Err(Error::BufferTooSmall);
76            }
77            self.refill();
78        }
79        if self.position == self.length {
80            return Ok(0);
81        }
82        if output.is_empty() {
83            return Err(Error::BufferTooSmall);
84        }
85        let count = output.len().min(self.length - self.position);
86        output[..count].copy_from_slice(&self.pending[self.position..self.position + count]);
87        if !matches!(self.phase, Phase::Done) {
88            self.hash.update(&output[..count]);
89        }
90        self.position += count;
91        Ok(count)
92    }
93
94    fn field(&mut self, bytes: &[u8]) {
95        self.pending[..bytes.len()].copy_from_slice(bytes);
96        self.position = 0;
97        self.length = bytes.len();
98    }
99
100    fn advance(&mut self, phase: Phase) {
101        self.phase = phase;
102        self.next = 0;
103    }
104
105    fn refill(&mut self) {
106        loop {
107            match self.phase {
108                Phase::Header => {
109                    self.field(b"\xfftOc\0\0\0\x02");
110                    self.advance(Phase::Fanout);
111                }
112                Phase::Fanout if self.next < 256 => {
113                    while self.fanout_end < self.entries.len()
114                        && self.entries[self.fanout_end].id.as_bytes()[0] as usize <= self.next
115                    {
116                        self.fanout_end += 1;
117                    }
118                    self.field(&(self.fanout_end as u32).to_be_bytes());
119                    self.next += 1;
120                }
121                Phase::Fanout => {
122                    self.advance(Phase::Ids);
123                    continue;
124                }
125                Phase::Ids if self.next < self.entries.len() => {
126                    self.field(self.entries[self.next].id.as_bytes());
127                    self.next += 1;
128                }
129                Phase::Ids => {
130                    self.advance(Phase::Crcs);
131                    continue;
132                }
133                Phase::Crcs if self.next < self.entries.len() => {
134                    self.field(&self.entries[self.next].crc32.to_be_bytes());
135                    self.next += 1;
136                }
137                Phase::Crcs => {
138                    self.advance(Phase::Offsets);
139                    continue;
140                }
141                Phase::Offsets if self.next < self.entries.len() => {
142                    let offset = self.entries[self.next].offset;
143                    let value = if offset < 0x8000_0000 {
144                        offset as u32
145                    } else {
146                        let value = 0x8000_0000 | self.large;
147                        self.large += 1;
148                        value
149                    };
150                    self.field(&value.to_be_bytes());
151                    self.next += 1;
152                }
153                Phase::Offsets => {
154                    self.advance(Phase::LargeOffsets);
155                    continue;
156                }
157                Phase::LargeOffsets => {
158                    while self.next < self.entries.len()
159                        && self.entries[self.next].offset < 0x8000_0000
160                    {
161                        self.next += 1;
162                    }
163                    if self.next == self.entries.len() {
164                        self.advance(Phase::PackChecksum);
165                        continue;
166                    }
167                    self.field(&self.entries[self.next].offset.to_be_bytes());
168                    self.next += 1;
169                }
170                Phase::PackChecksum => {
171                    let checksum = self.pack_checksum;
172                    self.field(checksum.as_bytes());
173                    self.advance(Phase::Trailer);
174                }
175                Phase::Trailer => {
176                    self.field(self.hash.clone().finish().as_bytes());
177                    self.advance(Phase::Done);
178                }
179                Phase::Done => {
180                    self.position = 0;
181                    self.length = 0;
182                }
183            }
184            return;
185        }
186    }
187}