Skip to main content

git_zero_core/pack/
entry_range.rs

1//! Bounded reads of inflated ranges within an authenticated packed entry.
2use super::{DecodeStatus, EntryDecoder};
3use crate::{
4    Error, ObjectFormat, Result,
5    zlib::{InflateLimits, WINDOW_SIZE},
6};
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub struct ByteRange {
10    pub offset: u64,
11    pub length: u64,
12}
13
14#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub enum EntryRangeStatus {
16    /// Internal progress was made, possibly without consuming/emitting bytes.
17    Continue,
18    NeedInput,
19    NeedOutput,
20    Finished,
21}
22
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub struct EntryRangeProgress {
25    pub consumed: usize,
26    pub written: usize,
27    pub status: EntryRangeStatus,
28}
29
30/// Read plain body or delta-instruction bytes from a previously authenticated
31/// packed entry. Supply packed bytes starting at `entry_offset`. Inflation skips
32/// the prefix using caller scratch, then emits only the requested inflated range.
33/// One update performs at most one inflater step. Scratch retains pending bytes
34/// across output backpressure; no whole entry/body/pack is retained.
35///
36/// Completion can precede the entry's end. It does NOT verify the remaining
37/// compressed stream, entry CRC, pack checksum or canonical object identity.
38/// Authenticate the source and index before partial reads. For new packs first
39/// run Decoder to complete intake validation. Hosts may cache inflated pages to
40/// avoid repeated prefix inflation when serving RangeResolver requests.
41pub struct EntryRangeDecoder<'window, 'scratch> {
42    decoder: EntryDecoder<'window>,
43    scratch: &'scratch mut [u8],
44    range: ByteRange,
45    end: u64,
46    inflated: u64,
47    emitted: u64,
48    pending: usize,
49    pending_end: usize,
50    header_seen: bool,
51    finished: bool,
52    failure: Option<Error>,
53}
54
55impl<'window, 'scratch> EntryRangeDecoder<'window, 'scratch> {
56    pub fn new(
57        format: ObjectFormat,
58        entry_offset: u64,
59        window: &'window mut [u8; WINDOW_SIZE],
60        max_entry_bytes: u64,
61        inflate: InflateLimits,
62        range: ByteRange,
63        scratch: &'scratch mut [u8],
64    ) -> Result<Self> {
65        let end = range
66            .offset
67            .checked_add(range.length)
68            .ok_or(Error::InvalidObject)?;
69        if scratch.is_empty() && range.length != 0 {
70            return Err(Error::BufferTooSmall);
71        }
72        Ok(Self {
73            decoder: EntryDecoder::new(format, entry_offset, window, max_entry_bytes, inflate)?,
74            scratch,
75            range,
76            end,
77            inflated: 0,
78            emitted: 0,
79            pending: 0,
80            pending_end: 0,
81            header_seen: false,
82            finished: false,
83            failure: None,
84        })
85    }
86
87    pub fn wire_position(&self) -> u64 {
88        self.decoder.position()
89    }
90    pub fn inflated_position(&self) -> u64 {
91        self.inflated
92    }
93    pub fn position(&self) -> u64 {
94        self.emitted
95    }
96
97    /// Preserve packed input not consumed and output not yet sent. Finished
98    /// calls repeat without consuming following entries. Errors are terminal.
99    pub fn update(
100        &mut self,
101        input: &[u8],
102        output: &mut [u8],
103        end_of_input: bool,
104    ) -> Result<EntryRangeProgress> {
105        let result = self.advance(input, output, end_of_input);
106        if let Err(error) = result {
107            self.failure = Some(error);
108        }
109        result
110    }
111
112    pub fn finish(&self) -> Result<()> {
113        if let Some(error) = self.failure {
114            return Err(error);
115        }
116        if self.finished {
117            Ok(())
118        } else {
119            Err(Error::CorruptPack)
120        }
121    }
122
123    fn advance(
124        &mut self,
125        input: &[u8],
126        output: &mut [u8],
127        eof: bool,
128    ) -> Result<EntryRangeProgress> {
129        if let Some(error) = self.failure {
130            return Err(error);
131        }
132        if self.finished {
133            return Ok(EntryRangeProgress {
134                consumed: 0,
135                written: 0,
136                status: EntryRangeStatus::Finished,
137            });
138        }
139        if self.pending < self.pending_end {
140            return Ok(self.drain(0, output));
141        }
142        // An empty range still reads its header to check the requested bound.
143        if output.is_empty() && self.range.length != 0 {
144            return Ok(EntryRangeProgress {
145                consumed: 0,
146                written: 0,
147                status: EntryRangeStatus::NeedOutput,
148            });
149        }
150        let progress = self.decoder.update(input, self.scratch, eof)?;
151        if let DecodeStatus::EntryStart(header) = progress.status {
152            if self.end > header.size {
153                return Err(Error::InvalidObject);
154            }
155            self.header_seen = true;
156            if self.range.length == 0 {
157                self.finished = true;
158            }
159        }
160        let before = self.inflated;
161        self.inflated += progress.written as u64;
162        let first = before.max(self.range.offset);
163        let last = self.inflated.min(self.end);
164        if first < last {
165            self.pending = (first - before) as usize;
166            self.pending_end = (last - before) as usize;
167            return Ok(self.drain(progress.consumed, output));
168        }
169        let status = if self.finished {
170            EntryRangeStatus::Finished
171        } else {
172            match progress.status {
173                DecodeStatus::NeedInput => EntryRangeStatus::NeedInput,
174                DecodeStatus::EntryEnd { .. } => return Err(Error::CorruptPack),
175                _ => EntryRangeStatus::Continue,
176            }
177        };
178        Ok(EntryRangeProgress {
179            consumed: progress.consumed,
180            written: 0,
181            status,
182        })
183    }
184
185    fn drain(&mut self, consumed: usize, output: &mut [u8]) -> EntryRangeProgress {
186        let written = (self.pending_end - self.pending).min(output.len());
187        output[..written].copy_from_slice(&self.scratch[self.pending..self.pending + written]);
188        self.pending += written;
189        self.emitted += written as u64;
190        self.finished = self.header_seen && self.emitted == self.range.length;
191        let status = if self.finished {
192            EntryRangeStatus::Finished
193        } else if self.pending < self.pending_end {
194            EntryRangeStatus::NeedOutput
195        } else {
196            EntryRangeStatus::Continue
197        };
198        EntryRangeProgress {
199            consumed,
200            written,
201            status,
202        }
203    }
204}