Skip to main content

git_zero_core/pack/
stream.rs

1//! Pull-based stored-block encoding. No provider reads occur between pulls.
2use super::encode::write_entry_header;
3use crate::{
4    Error, ObjectId, ObjectInfo, Repository, Result, io::SliceWriter, object::Hash,
5    zlib::adler::Adler32,
6};
7
8/// A resumable pack encoder. Retain the repository and selected order unchanged
9/// until EOF. `read` performs at most one object-body read, bounded by its output
10/// slice. The cursor owns hash/framing state, never object or pack-sized storage.
11pub struct Encoder<'a> {
12    order: Option<&'a [usize]>,
13    count: usize,
14    next: usize,
15    object: Option<LooseEncoder>,
16    hash: Hash,
17    pending: [u8; 32],
18    position: usize,
19    length: usize,
20    checksum: Option<ObjectId>,
21}
22
23impl<'a> Encoder<'a> {
24    /// Encode all objects (`None`) or the supplied unique indices, in order.
25    /// Invalid selections fail before the first output byte.
26    pub fn new(repository: &impl Repository, order: Option<&'a [usize]>) -> Result<Self> {
27        if let Some(order) = order {
28            super::encode::validate_order(repository, order)?;
29        }
30        let count = order.map_or(repository.object_count(), <[usize]>::len);
31        let count32 = u32::try_from(count).map_err(|_| Error::Unsupported)?;
32        let mut pending = [0; 32];
33        pending[..4].copy_from_slice(b"PACK");
34        pending[4..8].copy_from_slice(&2_u32.to_be_bytes());
35        pending[8..12].copy_from_slice(&count32.to_be_bytes());
36        Ok(Self {
37            order,
38            count,
39            next: 0,
40            object: None,
41            hash: repository.object_format().hash(),
42            pending,
43            position: 0,
44            length: 12,
45            checksum: None,
46        })
47    }
48
49    /// Return bytes to stage for transport. Preserve unsent bytes at the host;
50    /// calling again advances the cursor. Zero means complete, never pending.
51    /// Backend read failures leave the failed read resumable. After any failure,
52    /// discard bytes in that call's output slice. Earlier successful pulls stand.
53    pub fn read(&mut self, repository: &impl Repository, output: &mut [u8]) -> Result<usize> {
54        loop {
55            if self.position < self.length {
56                let count = drain(&self.pending[..self.length], &mut self.position, output)?;
57                if self.checksum.is_none() {
58                    self.hash.update(&output[..count]);
59                }
60                return Ok(count);
61            }
62            if self.checksum.is_some() {
63                return Ok(0);
64            }
65            if output.is_empty() {
66                return Err(Error::BufferTooSmall);
67            }
68            if let Some(object) = &mut self.object {
69                let count = object.read(repository, output)?;
70                if count != 0 {
71                    self.hash.update(&output[..count]);
72                    return Ok(count);
73                }
74                self.object = None;
75                self.next += 1;
76            }
77            self.next_entry(repository)?;
78        }
79    }
80
81    fn next_entry(&mut self, repository: &impl Repository) -> Result<()> {
82        if self.next == self.count {
83            let checksum = self.hash.clone().finish();
84            self.pending[..checksum.as_bytes().len()].copy_from_slice(checksum.as_bytes());
85            self.position = 0;
86            self.length = checksum.as_bytes().len();
87            self.checksum = Some(checksum);
88            return Ok(());
89        }
90        let index = self.order.map_or(self.next, |order| order[self.next]);
91        let info = repository.object_info(index)?;
92        let mut header = SliceWriter::new(&mut self.pending);
93        write_entry_header(info, &mut header)?;
94        self.length = header.len();
95        self.position = 0;
96        self.object = Some(LooseEncoder::from_info(index, info, false)?);
97        Ok(())
98    }
99
100    /// Immutable selection retained by this cursor. `None` denotes all objects.
101    /// This is metadata only; reading it never advances encoding.
102    pub fn selection(&self) -> Option<&'a [usize]> {
103        self.order
104    }
105
106    /// Checksum becomes available after its complete trailer has been drained.
107    pub fn checksum(&self) -> Option<ObjectId> {
108        self.checksum.filter(|_| self.position == self.length)
109    }
110}
111
112/// A resumable loose object encoder. Repository bytes and metadata must remain
113/// stable. Uses the same stored blocks as `write_loose_with_scratch`.
114pub struct LooseEncoder {
115    index: usize,
116    size: u64,
117    offset: u64,
118    block: u16,
119    adler: Adler32,
120    prefix: [u8; 32],
121    prefix_length: usize,
122    pending: [u8; 64],
123    position: usize,
124    length: usize,
125    phase: Phase,
126}
127
128enum Phase {
129    Prefix,
130    Block,
131    Body,
132    End,
133    Done,
134}
135
136impl LooseEncoder {
137    pub fn new(repository: &impl Repository, index: usize) -> Result<Self> {
138        Self::from_info(index, repository.object_info(index)?, true)
139    }
140
141    pub(super) fn from_info(index: usize, info: ObjectInfo, loose: bool) -> Result<Self> {
142        let mut prefix = [0; 32];
143        let prefix_length = if loose {
144            info.header(&mut prefix)?.len()
145        } else {
146            0
147        };
148        let mut pending = [0; 64];
149        pending[..2].copy_from_slice(&[0x78, 0x01]);
150        Ok(Self {
151            index,
152            size: info.size,
153            offset: 0,
154            block: 0,
155            adler: Adler32::new(),
156            prefix,
157            prefix_length,
158            pending,
159            position: 0,
160            length: 2,
161            phase: Phase::Prefix,
162        })
163    }
164
165    pub fn read(&mut self, repository: &impl Repository, output: &mut [u8]) -> Result<usize> {
166        loop {
167            if self.position < self.length {
168                return drain(&self.pending[..self.length], &mut self.position, output);
169            }
170            if matches!(self.phase, Phase::Done) {
171                return Ok(0);
172            }
173            if output.is_empty() {
174                return Err(Error::BufferTooSmall);
175            }
176            self.position = 0;
177            self.length = 0;
178            if let Some(count) = self.step(repository, output)? {
179                return Ok(count);
180            }
181        }
182    }
183
184    fn step(&mut self, repository: &impl Repository, output: &mut [u8]) -> Result<Option<usize>> {
185        match self.phase {
186            Phase::Prefix => self.start_prefix(),
187            Phase::Block => self.start_block(),
188            Phase::Body => return self.read_body(repository, output).map(Some),
189            Phase::End => self.end(),
190            Phase::Done => unreachable!(),
191        }
192        Ok(None)
193    }
194
195    fn start_prefix(&mut self) {
196        self.phase = Phase::Block;
197        if self.prefix_length == 0 {
198            return;
199        }
200        self.block_header(self.prefix_length as u16);
201        self.pending[5..5 + self.prefix_length].copy_from_slice(&self.prefix[..self.prefix_length]);
202        self.length += self.prefix_length;
203        self.adler.update(&self.prefix[..self.prefix_length]);
204    }
205
206    fn start_block(&mut self) {
207        if self.offset == self.size {
208            self.phase = Phase::End;
209            return;
210        }
211        self.block = (self.size - self.offset).min(u64::from(u16::MAX)) as u16;
212        self.block_header(self.block);
213        self.phase = Phase::Body;
214    }
215
216    fn read_body(&mut self, repository: &impl Repository, output: &mut [u8]) -> Result<usize> {
217        let capacity = usize::from(self.block).min(output.len());
218        let count = repository.read_object(self.index, self.offset, &mut output[..capacity])?;
219        if count == 0 || count > capacity {
220            return Err(Error::InvalidObject);
221        }
222        self.offset += count as u64;
223        self.block -= count as u16;
224        self.adler.update(&output[..count]);
225        if self.block == 0 {
226            self.phase = Phase::Block;
227        }
228        Ok(count)
229    }
230
231    fn end(&mut self) {
232        self.pending[..5].copy_from_slice(&[1, 0, 0, 0xff, 0xff]);
233        self.pending[5..9].copy_from_slice(&self.adler.value().to_be_bytes());
234        self.length = 9;
235        self.phase = Phase::Done;
236    }
237
238    fn block_header(&mut self, length: u16) {
239        self.pending[0] = 0;
240        self.pending[1..3].copy_from_slice(&length.to_le_bytes());
241        self.pending[3..5].copy_from_slice(&(!length).to_le_bytes());
242        self.length = 5;
243    }
244}
245
246pub(crate) fn drain(bytes: &[u8], position: &mut usize, output: &mut [u8]) -> Result<usize> {
247    if output.is_empty() {
248        return Err(Error::BufferTooSmall);
249    }
250    let count = output.len().min(bytes.len() - *position);
251    output[..count].copy_from_slice(&bytes[*position..*position + count]);
252    *position += count;
253    Ok(count)
254}