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 => return Err(io::Error::other(format!("Object type {other} is unsupported"))),
86        };
87        Ok(data::Entry {
88            header: object,
89            decompressed_size: size,
90            data_offset: pack_offset + consumed as u64,
91        })
92    }
93}
94
95#[inline]
96fn streaming_parse_header_info(read: &mut dyn io::Read) -> Result<(u8, u64, usize), io::Error> {
97    let mut byte = [0u8; 1];
98    read.read_exact(&mut byte)?;
99    let mut c = byte[0];
100    let mut i = 1;
101    let type_id = (c >> 4) & 0b0000_0111;
102    let mut size = u64::from(c) & 0b0000_1111;
103    let mut s = 4;
104    while c & 0b1000_0000 != 0 {
105        read.read_exact(&mut byte)?;
106        c = byte[0];
107        i += 1;
108        size += u64::from(c & 0b0111_1111) << s;
109        s += 7;
110    }
111    Ok((type_id, size, i))
112}
113
114/// Parses the header of a pack-entry, yielding object type id, decompressed object size, and consumed bytes
115#[inline]
116fn parse_header_info(data: &[u8]) -> (u8, u64, usize) {
117    let mut c = data[0];
118    let mut i = 1;
119    let type_id = (c >> 4) & 0b0000_0111;
120    let mut size = u64::from(c) & 0b0000_1111;
121    let mut s = 4;
122    while c & 0b1000_0000 != 0 {
123        c = data[i];
124        i += 1;
125        size += u64::from(c & 0b0111_1111) << s;
126        s += 7;
127    }
128    (type_id, size, i)
129}