gix_pack/data/entry/
decode.rs

1use std::io;
2
3use gix_features::decode::{leb64, leb64_from_read};
4
5use super::{BLOB, COMMIT, OFS_DELTA, REF_DELTA, TAG, TREE};
6use crate::data;
7
8/// The error returned by [data::Entry::from_bytes()].
9#[derive(Debug, thiserror::Error)]
10#[allow(missing_docs)]
11#[error("Object type {type_id} is unsupported")]
12pub struct Error {
13    pub type_id: u8,
14}
15
16/// Decoding
17impl data::Entry {
18    /// Decode an entry from the given entry data `d`, providing the `pack_offset` to allow tracking the start of the entry data section.
19    ///
20    /// # Panics
21    ///
22    /// If we cannot understand the header, garbage data is likely to trigger this.
23    pub fn from_bytes(d: &[u8], pack_offset: data::Offset, hash_len: usize) -> Result<data::Entry, Error> {
24        let (type_id, size, mut consumed) = parse_header_info(d);
25
26        use crate::data::entry::Header::*;
27        let object = match type_id {
28            OFS_DELTA => {
29                let (distance, leb_bytes) = leb64(&d[consumed..]);
30                let delta = OfsDelta {
31                    base_distance: distance,
32                };
33                consumed += leb_bytes;
34                delta
35            }
36            REF_DELTA => {
37                let delta = RefDelta {
38                    base_id: gix_hash::ObjectId::from_bytes_or_panic(&d[consumed..][..hash_len]),
39                };
40                consumed += hash_len;
41                delta
42            }
43            BLOB => Blob,
44            TREE => Tree,
45            COMMIT => Commit,
46            TAG => Tag,
47            other => return Err(Error { type_id: other }),
48        };
49        Ok(data::Entry {
50            header: object,
51            decompressed_size: size,
52            data_offset: pack_offset + consumed as u64,
53        })
54    }
55
56    /// Instantiate an `Entry` from the reader `r`, providing the `pack_offset` to allow tracking the start of the entry data section.
57    pub fn from_read(r: &mut dyn io::Read, pack_offset: data::Offset, hash_len: usize) -> io::Result<data::Entry> {
58        let (type_id, size, mut consumed) = streaming_parse_header_info(r)?;
59
60        use crate::data::entry::Header::*;
61        let object = match type_id {
62            OFS_DELTA => {
63                let (distance, leb_bytes) = leb64_from_read(r)?;
64                let delta = OfsDelta {
65                    base_distance: distance,
66                };
67                consumed += leb_bytes;
68                delta
69            }
70            REF_DELTA => {
71                let mut buf = gix_hash::Kind::buf();
72                let hash = &mut buf[..hash_len];
73                r.read_exact(hash)?;
74                #[allow(clippy::redundant_slicing)]
75                let delta = RefDelta {
76                    base_id: gix_hash::ObjectId::from_bytes_or_panic(&hash[..]),
77                };
78                consumed += hash_len;
79                delta
80            }
81            BLOB => Blob,
82            TREE => Tree,
83            COMMIT => Commit,
84            TAG => Tag,
85            other => {
86                return Err(io::Error::new(
87                    io::ErrorKind::Other,
88                    format!("Object type {other} is unsupported"),
89                ))
90            }
91        };
92        Ok(data::Entry {
93            header: object,
94            decompressed_size: size,
95            data_offset: pack_offset + consumed as u64,
96        })
97    }
98}
99
100#[inline]
101fn streaming_parse_header_info(read: &mut dyn io::Read) -> Result<(u8, u64, usize), io::Error> {
102    let mut byte = [0u8; 1];
103    read.read_exact(&mut byte)?;
104    let mut c = byte[0];
105    let mut i = 1;
106    let type_id = (c >> 4) & 0b0000_0111;
107    let mut size = u64::from(c) & 0b0000_1111;
108    let mut s = 4;
109    while c & 0b1000_0000 != 0 {
110        read.read_exact(&mut byte)?;
111        c = byte[0];
112        i += 1;
113        size += u64::from(c & 0b0111_1111) << s;
114        s += 7;
115    }
116    Ok((type_id, size, i))
117}
118
119/// Parses the header of a pack-entry, yielding object type id, decompressed object size, and consumed bytes
120#[inline]
121fn parse_header_info(data: &[u8]) -> (u8, u64, usize) {
122    let mut c = data[0];
123    let mut i = 1;
124    let type_id = (c >> 4) & 0b0000_0111;
125    let mut size = u64::from(c) & 0b0000_1111;
126    let mut s = 4;
127    while c & 0b1000_0000 != 0 {
128        c = data[i];
129        i += 1;
130        size += u64::from(c & 0b0111_1111) << s;
131        s += 7;
132    }
133    (type_id, size, i)
134}